Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5870322259 | ||
|
|
bc298ca9f2 | ||
|
|
7ea1efb8b4 | ||
|
|
6c4448036c | ||
|
|
ff1e3928ac | ||
|
|
b5ce4db1c9 | ||
|
|
ea186b51c9 | ||
|
|
790c9c320a | ||
|
|
d31aa27b0f |
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
|
||||
+1
-12
@@ -9,12 +9,10 @@
|
||||
.settings
|
||||
.classpath
|
||||
|
||||
|
||||
# NetBeans #
|
||||
############
|
||||
nbactions.xml
|
||||
nb-configuration.xml
|
||||
catalog.xml
|
||||
nbproject
|
||||
|
||||
# Compiled source #
|
||||
###################
|
||||
@@ -46,12 +44,3 @@ nbproject
|
||||
#########
|
||||
target
|
||||
|
||||
# Maven shade
|
||||
#############
|
||||
*dependency-reduced-pom.xml
|
||||
|
||||
# nodejs #
|
||||
##########
|
||||
# KEYCLOAK-5391: We will re-exclude node_modules when node_modules handling is worked out.
|
||||
# For now, we keep our js libraries checked into GitHub, so we don't ignore.
|
||||
#node_modules
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
language: java
|
||||
dist: precise
|
||||
|
||||
cache:
|
||||
cache: false
|
||||
|
||||
env:
|
||||
global:
|
||||
- MAVEN_SKIP_RC=true
|
||||
- MAVEN_OPTS="-Xms512m -Xmx1536m"
|
||||
matrix:
|
||||
- TESTS=unit
|
||||
- TESTS=server-group1
|
||||
- TESTS=server-group2
|
||||
- TESTS=server-group3
|
||||
- TESTS=server-group4
|
||||
- TESTS=old
|
||||
- TESTS=crossdc
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
install: true
|
||||
|
||||
before_install:
|
||||
- "export PHANTOMJS_VERSION=2.1.1"
|
||||
- "phantomjs --version"
|
||||
- "export PATH=$PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64/bin:$PATH"
|
||||
- "phantomjs --version"
|
||||
- "if [ $(phantomjs --version) != '$PHANTOMJS_VERSION' ]; then rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; fi"
|
||||
- "if [ $(phantomjs --version) != '$PHANTOMJS_VERSION' ]; then wget https://github.com/Medium/phantomjs/releases/download/v$PHANTOMJS_VERSION/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2; fi"
|
||||
- "if [ $(phantomjs --version) != '$PHANTOMJS_VERSION' ]; then tar -xvf $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi"
|
||||
- "phantomjs --version"
|
||||
|
||||
script:
|
||||
- ./travis-run-tests.sh $TESTS
|
||||
|
||||
sudo: false
|
||||
@@ -1,79 +1,36 @@
|
||||
Keycloak
|
||||
keycloak
|
||||
========
|
||||
|
||||
Open Source Identity and Access Management for modern Applications and Services.
|
||||
Please visit [http://keycloak.org](http://keycloak.org) for more information on Keycloak including how to download, documentation,
|
||||
and video tutorials.
|
||||
|
||||
For more information about Keycloak visit [Keycloak homepage](http://keycloak.org) and [Keycloak blog](http://blog.keycloak.org).
|
||||
Keycloak is an SSO Service for web apps and REST services.
|
||||
It can be used for social applications as well as enterprise applications. It is based on OpenID Connect with support for SAML 2.0 as well.
|
||||
Here's some of the features:
|
||||
|
||||
* SSO and Single Log Out for browser applications
|
||||
* Social Broker. Enable Google, Facebook, Yahoo, Twitter social login with no code required.
|
||||
* Optional LDAP/Active Directory integration
|
||||
* Optional User Registration
|
||||
* Password and TOTP support (via Google Authenticator). Client cert auth coming soon.
|
||||
* User session management from both admin and user perspective
|
||||
* Customizable themes for user facing pages: login, grant pages, account management, emails, and admin console all customizable!
|
||||
* OAuth Bearer token auth for REST Services
|
||||
* Integrated Browser App to REST Service token propagation
|
||||
* Admin REST API
|
||||
* OAuth 2.0 Grant requests
|
||||
* CORS Support
|
||||
* CORS Web Origin management and validation
|
||||
* Completely centrally managed user and role mapping metadata. Minimal configuration at the application side
|
||||
* Admin Console for managing users, roles, role mappings, applications, user sessions, allowed CORS web origins, and OAuth clients.
|
||||
* Deployable as a WAR, appliance, or an Openshift cloud service (SaaS).
|
||||
* Supports JBoss AS7, EAP 6.x, and Wildfly applications. Plans to support Node.js, RAILS, GRAILS, and other non-Java applications.
|
||||
* Javascript/HTML 5 adapter for pure Javascript apps
|
||||
* Session management from admin console
|
||||
* Revocation policies
|
||||
* Password policies
|
||||
* OpenID Connect Support
|
||||
* SAML Support
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Ensure you have JDK 8 (or newer), Maven 3.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/utils/pom.xml exec:java -Pkeycloak-server
|
||||
|
||||
|
||||
To start Keycloak from the server distribution first build the distribution it as specified above, then run:
|
||||
|
||||
tar xfz distribution/server-dist/target/keycloak-<VERSION>.tar.gz
|
||||
cd keycloak-<VERSION>
|
||||
bin/standalone.sh
|
||||
|
||||
To stop the server press `Ctrl + C`.
|
||||
|
||||
Reporting security vulnerabilities
|
||||
----------------------------------
|
||||
|
||||
If you've found a security vulnerability, please look at the [instructions on how to properly report it](http://www.keycloak.org/security.html)
|
||||
|
||||
Help and Documentation
|
||||
----------------------
|
||||
* [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>4.0.0.Beta3</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>
|
||||
-524
@@ -1,524 +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 isAutodetectBearerOnly() {
|
||||
return delegate.isAutodetectBearerOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
|
||||
delegate.setAutodetectBearerOnly(autodetectBearerOnly);
|
||||
}
|
||||
|
||||
@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 int getConfidentialPort() {
|
||||
return delegate.getConfidentialPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfidentialPort(int confidentialPort) {
|
||||
delegate.setConfidentialPort(confidentialPort);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
-168
@@ -1,168 +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 exposeHeaders = deployment.getCorsExposedHeaders();
|
||||
|
||||
if (deployment.getPolicyEnforcer() != null) {
|
||||
if (exposeHeaders != null) {
|
||||
exposeHeaders += ",";
|
||||
}
|
||||
exposeHeaders += "WWW-Authenticate";
|
||||
}
|
||||
|
||||
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
|
||||
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
|
||||
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
|
||||
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");
|
||||
if (exposeHeaders != null) {
|
||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, exposeHeaders);
|
||||
}
|
||||
} else {
|
||||
log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI());
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-127
@@ -1,127 +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));
|
||||
int seperatorIndex = userpw.indexOf(":");
|
||||
String user = userpw.substring(0, seperatorIndex);
|
||||
String pw = userpw.substring(seperatorIndex + 1);
|
||||
atr = getToken(user, pw);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
-194
@@ -1,194 +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.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
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) {
|
||||
log.debug("Verifying access_token");
|
||||
if (log.isTraceEnabled()) {
|
||||
try {
|
||||
JWSInput jwsInput = new JWSInput(tokenString);
|
||||
String wireString = jwsInput.getWireString();
|
||||
log.tracef("\taccess_token: %s", wireString.substring(0, wireString.lastIndexOf(".")) + ".signature");
|
||||
} catch (JWSInputException e) {
|
||||
log.errorf(e, "Failed to parse access_token: %s", tokenString);
|
||||
}
|
||||
}
|
||||
try {
|
||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
} catch (VerificationException e) {
|
||||
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();
|
||||
}
|
||||
log.debug("successful authorized");
|
||||
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) {
|
||||
if (deployment.getPolicyEnforcer() != null) {
|
||||
deployment.getPolicyEnforcer().enforce(OIDCHttpFacade.class.cast(facade));
|
||||
return true;
|
||||
}
|
||||
OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
|
||||
facade.getRequest().setError(error);
|
||||
facade.getResponse().addHeader("WWW-Authenticate", challenge);
|
||||
if(deployment.isDelegateBearerErrorResponseSending()){
|
||||
facade.getResponse().setStatus(401);
|
||||
}
|
||||
else {
|
||||
facade.getResponse().sendError(401);
|
||||
}
|
||||
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,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.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";
|
||||
String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class HttpAdapterUtils {
|
||||
|
||||
|
||||
public static <T> T sendJsonHttpRequest(KeycloakDeployment deployment, HttpRequestBase httpRequest, Class<T> clazz) throws HttpClientAdapterException {
|
||||
try {
|
||||
HttpResponse response = deployment.getClient().execute(httpRequest);
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
if (status != 200) {
|
||||
close(response);
|
||||
throw new HttpClientAdapterException("Unexpected status = " + status);
|
||||
}
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (entity == null) {
|
||||
throw new HttpClientAdapterException("There was no entity.");
|
||||
}
|
||||
InputStream is = entity.getContent();
|
||||
try {
|
||||
return JsonSerialization.readValue(is, clazz);
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new HttpClientAdapterException("IO error", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void close(HttpResponse response) {
|
||||
if (response.getEntity() != null) {
|
||||
try {
|
||||
response.getEntity().getContent().close();
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-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);
|
||||
}
|
||||
}
|
||||
-400
@@ -1,400 +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;
|
||||
import org.apache.http.client.params.ClientPNames;
|
||||
import org.apache.http.client.params.CookiePolicy;
|
||||
|
||||
/**
|
||||
* 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(boolean disable) {
|
||||
this.disableCookieCache = disable;
|
||||
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();
|
||||
params.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY);
|
||||
|
||||
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(true); // 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());
|
||||
}
|
||||
}
|
||||
-480
@@ -1,480 +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>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakDeployment {
|
||||
|
||||
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 int confidentialPort = -1;
|
||||
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 String corsExposedHeaders;
|
||||
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;
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636
|
||||
protected boolean pkce = false;
|
||||
protected boolean ignoreOAuthQueryParameter;
|
||||
|
||||
protected Map<String, String> redirectRewriteRules;
|
||||
|
||||
protected boolean delegateBearerErrorResponseSending = false;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
authServerBaseUrl = authUrlBuilder.build().toString();
|
||||
|
||||
String login = authUrlBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(getRealm()).toString();
|
||||
authUrl = KeycloakUriBuilder.fromUri(login);
|
||||
realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString();
|
||||
|
||||
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 int getConfidentialPort() {
|
||||
return confidentialPort;
|
||||
}
|
||||
|
||||
public void setConfidentialPort(int confidentialPort) {
|
||||
this.confidentialPort = confidentialPort;
|
||||
}
|
||||
|
||||
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 String getCorsExposedHeaders() {
|
||||
return corsExposedHeaders;
|
||||
}
|
||||
|
||||
public void setCorsExposedHeaders(String corsExposedHeaders) {
|
||||
this.corsExposedHeaders = corsExposedHeaders;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636
|
||||
public boolean isPkce() {
|
||||
return pkce;
|
||||
}
|
||||
|
||||
public void setPkce(boolean pkce) {
|
||||
this.pkce = pkce;
|
||||
}
|
||||
|
||||
public void setIgnoreOAuthQueryParameter(boolean ignoreOAuthQueryParameter) {
|
||||
this.ignoreOAuthQueryParameter = ignoreOAuthQueryParameter;
|
||||
}
|
||||
|
||||
public boolean isOAuthQueryParameterEnabled() {
|
||||
return !this.ignoreOAuthQueryParameter;
|
||||
}
|
||||
|
||||
public Map<String, String> getRedirectRewriteRules() {
|
||||
return redirectRewriteRules;
|
||||
}
|
||||
|
||||
public void setRewriteRedirectRules(Map<String, String> redirectRewriteRules) {
|
||||
this.redirectRewriteRules = redirectRewriteRules;
|
||||
}
|
||||
|
||||
public boolean isDelegateBearerErrorResponseSending() {
|
||||
return delegateBearerErrorResponseSending;
|
||||
}
|
||||
|
||||
public void setDelegateBearerErrorResponseSending(boolean delegateBearerErrorResponseSending) {
|
||||
this.delegateBearerErrorResponseSending = delegateBearerErrorResponseSending;
|
||||
}
|
||||
}
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters;
|
||||
|
||||
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>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakDeploymentBuilder {
|
||||
|
||||
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.getConfidentialPort() != -1) {
|
||||
deployment.setConfidentialPort(adapterConfig.getConfidentialPort());
|
||||
}
|
||||
|
||||
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.setCorsExposedHeaders(adapterConfig.getCorsExposedHeaders());
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636
|
||||
if (adapterConfig.isPkce()) {
|
||||
deployment.setPkce(true);
|
||||
}
|
||||
|
||||
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());
|
||||
deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter());
|
||||
deployment.setRewriteRedirectRules(adapterConfig.getRedirectRewriteRules());
|
||||
|
||||
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
|
||||
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-425
@@ -1,425 +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.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.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
|
||||
/**
|
||||
* @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 = deployment.getConfidentialPort() != -1 ? deployment.getConfidentialPort() : 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);
|
||||
|
||||
String uiLocales = getQueryParamValue(OAuth2Constants.UI_LOCALES_PARAM);
|
||||
url = UriUtils.stripQueryParam(url, OAuth2Constants.UI_LOCALES_PARAM);
|
||||
|
||||
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, rewrittenRedirectUri(url))
|
||||
.queryParam(OAuth2Constants.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);
|
||||
}
|
||||
if (uiLocales != null && uiLocales.length() > 0) {
|
||||
redirectUriBuilder.queryParam(OAuth2Constants.UI_LOCALES_PARAM, uiLocales);
|
||||
}
|
||||
|
||||
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, rewrittenRedirectUri(strippedOauthParametersRequestUri), httpSessionId);
|
||||
} catch (ServerRequest.HttpFailure failure) {
|
||||
log.error("failed to turn code into token");
|
||||
log.error("status from server: " + failure.getStatus());
|
||||
if (failure.getError() != null) {
|
||||
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();
|
||||
|
||||
log.debug("Verifying tokens");
|
||||
if (log.isTraceEnabled()) {
|
||||
logToken("\taccess_token", tokenString);
|
||||
logToken("\tid_token", idTokenString);
|
||||
logToken("\trefresh_token", refreshToken);
|
||||
}
|
||||
|
||||
try {
|
||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
if (idTokenString != null) {
|
||||
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)
|
||||
.replaceQueryParam(OAuth2Constants.SESSION_STATE, null);
|
||||
return builder.build().toString();
|
||||
}
|
||||
|
||||
private String rewrittenRedirectUri(String originalUri) {
|
||||
Map<String, String> rewriteRules = deployment.getRedirectRewriteRules();
|
||||
if(rewriteRules != null && !rewriteRules.isEmpty()) {
|
||||
try {
|
||||
URL url = new URL(originalUri);
|
||||
Map.Entry<String, String> rule = rewriteRules.entrySet().iterator().next();
|
||||
StringBuilder redirectUriBuilder = new StringBuilder(url.getProtocol());
|
||||
redirectUriBuilder.append("://"+ url.getAuthority());
|
||||
redirectUriBuilder.append(url.getPath().replaceFirst(rule.getKey(), rule.getValue()));
|
||||
return redirectUriBuilder.toString();
|
||||
} catch (MalformedURLException ex) {
|
||||
log.error("Not a valid request url");
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
return originalUri;
|
||||
}
|
||||
|
||||
private void logToken(String name, String token) {
|
||||
try {
|
||||
JWSInput jwsInput = new JWSInput(token);
|
||||
String wireString = jwsInput.getWireString();
|
||||
log.tracef("\t%s: %s", name, wireString.substring(0, wireString.lastIndexOf(".")) + ".signature");
|
||||
} catch (JWSInputException e) {
|
||||
log.errorf(e, "Failed to parse %s: %s", name, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
-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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-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.adapters;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:froehlich.ch@gmail.com">Christian Froehlich</a>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
|
||||
public static final String ACCESS_TOKEN = "access_token";
|
||||
protected Logger log = Logger.getLogger(QueryParamterTokenRequestAuthenticator.class);
|
||||
|
||||
public QueryParamterTokenRequestAuthenticator(KeycloakDeployment deployment) {
|
||||
super(deployment);
|
||||
}
|
||||
|
||||
public AuthOutcome authenticate(HttpFacade exchange) {
|
||||
if(!deployment.isOAuthQueryParameterEnabled()) {
|
||||
return AuthOutcome.NOT_ATTEMPTED;
|
||||
}
|
||||
tokenString = null;
|
||||
tokenString = getAccessTokenFromQueryParamter(exchange);
|
||||
if (tokenString == null || tokenString.trim().isEmpty()) {
|
||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_QUERY_PARAMETER_ACCESS_TOKEN, null, null);
|
||||
return AuthOutcome.NOT_ATTEMPTED;
|
||||
}
|
||||
return (authenticateToken(exchange, tokenString));
|
||||
}
|
||||
|
||||
String getAccessTokenFromQueryParamter(HttpFacade exchange) {
|
||||
try {
|
||||
if (exchange != null && exchange.getRequest() != null) {
|
||||
return exchange.getRequest().getQueryParamValue(ACCESS_TOKEN);
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
-167
@@ -1,167 +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;
|
||||
if (tokenStore != null) {
|
||||
tokenStore.refreshCallback(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setAuthorizationContext(AuthorizationContext authorizationContext) {
|
||||
this.authorizationContext = authorizationContext;
|
||||
}
|
||||
}
|
||||
-241
@@ -1,241 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters;
|
||||
|
||||
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;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
|
||||
/**
|
||||
* @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.warnf("SSL is required to authenticate. Remote address %s is secure: %s, SSL required for: %s .",
|
||||
facade.getRequest().getRemoteAddr(), facade.getRequest().isSecure(), deployment.getSslRequired().name());
|
||||
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,299 +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 org.jboss.logging.Logger;
|
||||
|
||||
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 {
|
||||
|
||||
private static Logger logger = Logger.getLogger(ServerRequest.class);
|
||||
|
||||
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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636#section-4
|
||||
public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId, String codeVerifier) throws IOException, HttpFailure {
|
||||
List<NameValuePair> formparams = new ArrayList<>();
|
||||
redirectUri = stripOauthParametersFromRedirect(redirectUri);
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "authorization_code"));
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
|
||||
if (sessionId != null) {
|
||||
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_STATE, sessionId));
|
||||
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_HOST, HostUtils.getHostName()));
|
||||
}
|
||||
// https://tools.ietf.org/html/rfc7636#section-4
|
||||
if (codeVerifier != null) {
|
||||
logger.debugf("add to POST parameters of Token Request, codeVerifier = %s", codeVerifier);
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.CODE_VERIFIER, codeVerifier));
|
||||
} else {
|
||||
logger.debug("add to POST parameters of Token Request without codeVerifier");
|
||||
}
|
||||
|
||||
HttpPost post = new HttpPost(deployment.getTokenUrl());
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
||||
|
||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||
post.setEntity(form);
|
||||
HttpResponse response = deployment.getClient().execute(post);
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (status != 200) {
|
||||
error(status, entity);
|
||||
}
|
||||
if (entity == null) {
|
||||
throw new HttpFailure(status, null);
|
||||
}
|
||||
InputStream is = entity.getContent();
|
||||
try {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
int c;
|
||||
while ((c = is.read()) != -1) {
|
||||
os.write(c);
|
||||
}
|
||||
byte[] bytes = os.toByteArray();
|
||||
String json = new String(bytes);
|
||||
try {
|
||||
return JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new IOException(json, e);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static AccessTokenResponse invokeRefresh(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
|
||||
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
|
||||
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(ClientIdAndSecretCredentialsProvider.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;
|
||||
}
|
||||
}
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
package org.keycloak.adapters.authentication;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
|
||||
/**
|
||||
* Client authentication based on JWT signed by client secret instead of private key .
|
||||
* See <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">specs</a> for more details.
|
||||
*
|
||||
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
|
||||
*/
|
||||
public class JWTClientSecretCredentialsProvider implements ClientCredentialsProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(JWTClientSecretCredentialsProvider.class);
|
||||
|
||||
public static final String PROVIDER_ID = "secret-jwt";
|
||||
|
||||
private SecretKey clientSecret;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(KeycloakDeployment deployment, Object config) {
|
||||
if (config == null || !(config instanceof Map)) {
|
||||
throw new RuntimeException("Configuration of jwt credentials by client secret is missing or incorrect for client '" + deployment.getResourceName() + "'. Check your adapter configuration");
|
||||
}
|
||||
|
||||
Map<String, Object> cfg = (Map<String, Object>) config;
|
||||
String clientSecretString = (String) cfg.get("secret");
|
||||
if (clientSecretString == null) {
|
||||
throw new RuntimeException("Missing parameter secret-jwt in configuration of jwt for client " + deployment.getResourceName());
|
||||
}
|
||||
setClientSecret(clientSecretString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
|
||||
String signedToken = createSignedRequestToken(deployment.getResourceName(), deployment.getRealmInfoUrl());
|
||||
formParams.put(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT);
|
||||
formParams.put(OAuth2Constants.CLIENT_ASSERTION, signedToken);
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecretString) {
|
||||
// Get client secret and validate signature
|
||||
// According to <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">OIDC's client authentication spec</a>,
|
||||
// The HMAC (Hash-based Message Authentication Code) is calculated using the octets of the UTF-8 representation of the client_secret as the shared key.
|
||||
// Use "HmacSHA256" consulting <a href="https://docs.oracle.com/javase/jp/8/docs/api/javax/crypto/Mac.html">java8 api</a>
|
||||
// because it must be implemented in every java platform.
|
||||
try {
|
||||
clientSecret = new SecretKeySpec(clientSecretString.getBytes("UTF-8"), "HmacSHA256");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("Failed to create secret key spec due to unsupported encoding.");
|
||||
}
|
||||
}
|
||||
|
||||
public String createSignedRequestToken(String clientId, String realmInfoUrl) {
|
||||
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
|
||||
// JOSE header {"alg":"HS256","typ" : "JWT"} no need "kid" due to using only one registered client secret.
|
||||
// Use "HmacSHA256" consulting <a href="https://docs.oracle.com/javase/jp/8/docs/api/javax/crypto/Mac.html">java8 api</a>.
|
||||
// because it must be implemented in every java platform.
|
||||
return new JWSBuilder().jsonContent(jwt).hmac256(clientSecret);
|
||||
}
|
||||
|
||||
private JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
|
||||
// According to <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">OIDC's client authentication spec</a>,
|
||||
// JWT claims is the same as one by private_key_jwt
|
||||
|
||||
JsonWebToken reqToken = new JsonWebToken();
|
||||
reqToken.id(AdapterUtils.generateId());
|
||||
reqToken.issuer(clientId);
|
||||
reqToken.subject(clientId);
|
||||
reqToken.audience(realmInfoUrl);
|
||||
|
||||
int now = Time.currentTime();
|
||||
reqToken.issuedAt(now);
|
||||
// the same as in KEYCLOAK-2986, JWTClientCredentialsProvider's timeout field
|
||||
reqToken.expiration(now + 10);
|
||||
reqToken.notBefore(now);
|
||||
return reqToken;
|
||||
}
|
||||
|
||||
}
|
||||
-345
@@ -1,345 +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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessToken.Authorization;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.MethodConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.ScopeEnforcementMode;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public abstract class AbstractPolicyEnforcer {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(AbstractPolicyEnforcer.class);
|
||||
private static final String HTTP_METHOD_DELETE = "DELETE";
|
||||
|
||||
private final PolicyEnforcer policyEnforcer;
|
||||
|
||||
protected AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||
this.policyEnforcer = policyEnforcer;
|
||||
}
|
||||
|
||||
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
|
||||
EnforcementMode enforcementMode = getEnforcerConfig().getEnforcementMode();
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
Request request = httpFacade.getRequest();
|
||||
PathConfig pathConfig = getPathConfig(request);
|
||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||
|
||||
if (securityContext == null) {
|
||||
if (pathConfig != null) {
|
||||
challenge(pathConfig, getRequiredScopes(pathConfig, request), httpFacade);
|
||||
}
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
AccessToken accessToken = securityContext.getToken();
|
||||
|
||||
if (accessToken != null) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
||||
}
|
||||
|
||||
if (pathConfig == null) {
|
||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Could not find a configuration for path [%s]", getPath(request));
|
||||
}
|
||||
|
||||
if (isDefaultAccessDeniedUri(request)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
handleAccessDenied(httpFacade);
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
}
|
||||
|
||||
MethodConfig methodConfig = getRequiredScopes(pathConfig, request);
|
||||
|
||||
if (isAuthorized(pathConfig, methodConfig, accessToken, httpFacade)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (methodConfig != null && ScopeEnforcementMode.DISABLED.equals(methodConfig.getScopesEnforcementMode())) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||
}
|
||||
|
||||
if (!challenge(pathConfig, methodConfig, httpFacade)) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
|
||||
}
|
||||
handleAccessDenied(httpFacade);
|
||||
}
|
||||
}
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
protected abstract boolean challenge(PathConfig pathConfig, MethodConfig methodConfig, OIDCHttpFacade facade);
|
||||
|
||||
protected boolean isAuthorized(PathConfig actualPathConfig, MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
||||
Request request = httpFacade.getRequest();
|
||||
|
||||
if (isDefaultAccessDeniedUri(request)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Authorization authorization = accessToken.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasPermission = false;
|
||||
List<Permission> grantedPermissions = authorization.getPermissions();
|
||||
|
||||
for (Permission permission : grantedPermissions) {
|
||||
if (permission.getResourceId() != null) {
|
||||
if (isResourcePermission(actualPathConfig, permission)) {
|
||||
hasPermission = true;
|
||||
|
||||
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasResourceScopePermission(methodConfig, permission)) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, grantedPermissions);
|
||||
}
|
||||
if (HTTP_METHOD_DELETE.equalsIgnoreCase(request.getMethod()) && actualPathConfig.isInstance()) {
|
||||
policyEnforcer.getPathMatcher().removeFromCache(getPath(request));
|
||||
}
|
||||
|
||||
return hasValidClaims(actualPathConfig, httpFacade, authorization);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (hasResourceScopePermission(methodConfig, permission)) {
|
||||
hasPermission = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermission && EnforcementMode.PERMISSIVE.equals(actualPathConfig.getEnforcementMode())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Authorization FAILED for path [%s]. Not enough permissions [%s].", actualPathConfig, grantedPermissions);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasValidClaims(PathConfig actualPathConfig, OIDCHttpFacade httpFacade, Authorization authorization) {
|
||||
Map<String, Map<String, Object>> claimInformationPointConfig = actualPathConfig.getClaimInformationPointConfig();
|
||||
|
||||
if (claimInformationPointConfig != null) {
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
|
||||
for (Entry<String, Map<String, Object>> entry : claimInformationPointConfig.entrySet()) {
|
||||
ClaimInformationPointProviderFactory factory = policyEnforcer.getClaimInformationPointProviderFactories().get(entry.getKey());
|
||||
|
||||
if (factory == null) {
|
||||
throw new RuntimeException("Could not find claim information provider with name [" + entry.getKey() + "]");
|
||||
}
|
||||
|
||||
claims.putAll(factory.create(entry.getValue()).resolve(httpFacade));
|
||||
}
|
||||
|
||||
Map<String, List<String>> grantedClaims = authorization.getClaims();
|
||||
|
||||
if (grantedClaims != null) {
|
||||
if (claims.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (Entry<String, List<String>> entry : grantedClaims.entrySet()) {
|
||||
List<String> requestClaims = claims.get(entry.getKey());
|
||||
|
||||
if (requestClaims == null || requestClaims.isEmpty() || !entry.getValue().containsAll(requestClaims)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void handleAccessDenied(OIDCHttpFacade httpFacade) {
|
||||
httpFacade.getResponse().sendError(403);
|
||||
}
|
||||
|
||||
protected AuthzClient getAuthzClient() {
|
||||
return policyEnforcer.getClient();
|
||||
}
|
||||
|
||||
protected PolicyEnforcerConfig getEnforcerConfig() {
|
||||
return policyEnforcer.getEnforcerConfig();
|
||||
}
|
||||
|
||||
protected PolicyEnforcer getPolicyEnforcer() {
|
||||
return policyEnforcer;
|
||||
}
|
||||
|
||||
private boolean isDefaultAccessDeniedUri(Request request) {
|
||||
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
|
||||
return accessDeniedPath != null && request.getURI().contains(accessDeniedPath);
|
||||
}
|
||||
|
||||
private boolean hasResourceScopePermission(MethodConfig methodConfig, Permission permission) {
|
||||
List<String> requiredScopes = methodConfig.getScopes();
|
||||
Set<String> allowedScopes = permission.getScopes();
|
||||
|
||||
if (allowedScopes.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PolicyEnforcerConfig.ScopeEnforcementMode enforcementMode = methodConfig.getScopesEnforcementMode();
|
||||
|
||||
if (PolicyEnforcerConfig.ScopeEnforcementMode.ALL.equals(enforcementMode)) {
|
||||
return allowedScopes.containsAll(requiredScopes);
|
||||
}
|
||||
|
||||
if (PolicyEnforcerConfig.ScopeEnforcementMode.ANY.equals(enforcementMode)) {
|
||||
for (String requiredScope : requiredScopes) {
|
||||
if (allowedScopes.contains(requiredScope)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requiredScopes.isEmpty();
|
||||
}
|
||||
|
||||
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
|
||||
return new ClientAuthorizationContext(getAuthzClient()) {
|
||||
@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 String getPath(Request request) {
|
||||
return request.getRelativePath();
|
||||
}
|
||||
|
||||
private MethodConfig getRequiredScopes(PathConfig pathConfig, Request request) {
|
||||
String method = request.getMethod();
|
||||
|
||||
for (MethodConfig methodConfig : pathConfig.getMethods()) {
|
||||
if (methodConfig.getMethod().equals(method)) {
|
||||
return methodConfig;
|
||||
}
|
||||
}
|
||||
|
||||
MethodConfig methodConfig = new MethodConfig();
|
||||
|
||||
methodConfig.setMethod(request.getMethod());
|
||||
methodConfig.setScopes(pathConfig.getScopes());
|
||||
methodConfig.setScopesEnforcementMode(PolicyEnforcerConfig.ScopeEnforcementMode.ANY);
|
||||
|
||||
return methodConfig;
|
||||
}
|
||||
|
||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
|
||||
return new ClientAuthorizationContext(accessToken, pathConfig, getAuthzClient());
|
||||
}
|
||||
|
||||
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.getResourceId().equals(actualPathConfig.getId());
|
||||
}
|
||||
|
||||
private PathConfig getPathConfig(Request request) {
|
||||
return isDefaultAccessDeniedUri(request) ? null : policyEnforcer.getPathMatcher().matches(getPath(request));
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface ClaimInformationPointProvider {
|
||||
|
||||
Map<String, List<String>> resolve(HttpFacade httpFacade);
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface ClaimInformationPointProviderFactory<C extends ClaimInformationPointProvider> {
|
||||
|
||||
String getName();
|
||||
|
||||
void init(PolicyEnforcer policyEnforcer);
|
||||
|
||||
C create(Map<String, Object> config);
|
||||
}
|
||||
-255
@@ -1,255 +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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.resource.PermissionResource;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.PermissionRequest;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
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, PolicyEnforcerConfig.MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
||||
AccessToken original = accessToken;
|
||||
|
||||
if (super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
accessToken = requestAuthorizationToken(pathConfig, methodConfig, 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) {
|
||||
List<Permission> grantedPermissions = authorization.getPermissions();
|
||||
List<Permission> newPermissions = newAuthorization.getPermissions();
|
||||
|
||||
for (Permission newPermission : newPermissions) {
|
||||
if (!grantedPermissions.contains(newPermission)) {
|
||||
grantedPermissions.add(newPermission);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, List<String>> newClaims = newAuthorization.getClaims();
|
||||
|
||||
if (newClaims != null) {
|
||||
Map<String, List<String>> claims = authorization.getClaims();
|
||||
|
||||
if (claims == null) {
|
||||
claims = new HashMap<>();
|
||||
authorization.setClaims(claims);
|
||||
}
|
||||
|
||||
claims.putAll(newClaims);
|
||||
}
|
||||
}
|
||||
|
||||
original.setAuthorization(authorization);
|
||||
|
||||
return super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade) {
|
||||
if (isBearerAuthorization(httpFacade)) {
|
||||
HttpFacade.Response response = httpFacade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String ticket = getPermissionTicket(pathConfig, methodConfig, authzClient, httpFacade);
|
||||
|
||||
if (ticket != null) {
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", new StringBuilder("UMA realm=\"").append(authzClient.getConfiguration().getRealm()).append("\"").append(",as_uri=\"")
|
||||
.append(authzClient.getServerConfiguration().getIssuer()).append("\"").append(",ticket=\"").append(ticket).append("\"").toString());
|
||||
} else {
|
||||
response.setStatus(403);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending challenge");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
handleAccessDenied(httpFacade);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleAccessDenied(OIDCHttpFacade facade) {
|
||||
KeycloakSecurityContext securityContext = facade.getSecurityContext();
|
||||
|
||||
if (securityContext == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
|
||||
if (accessDeniedPath != null) {
|
||||
response.setStatus(302);
|
||||
response.setHeader("Location", accessDeniedPath);
|
||||
} else {
|
||||
response.sendError(403);
|
||||
}
|
||||
}
|
||||
|
||||
private AccessToken requestAuthorizationToken(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade) {
|
||||
if (getPolicyEnforcer().getDeployment().isBearerOnly() || (isBearerAuthorization(httpFacade) && getEnforcerConfig().getUserManagedAccess() != null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||
String accessTokenString = securityContext.getTokenString();
|
||||
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
|
||||
AccessToken accessToken = securityContext.getToken();
|
||||
AuthorizationRequest authzRequest = new AuthorizationRequest();
|
||||
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
String ticket = getPermissionTicket(pathConfig, methodConfig, getAuthzClient(), httpFacade);
|
||||
authzRequest.setTicket(ticket);
|
||||
} else {
|
||||
if (accessToken.getAuthorization() != null) {
|
||||
authzRequest.addPermission(pathConfig.getId(), methodConfig.getScopes());
|
||||
}
|
||||
|
||||
Map<String, List<String>> claims = getClaims(pathConfig, httpFacade);
|
||||
|
||||
if (!claims.isEmpty()) {
|
||||
authzRequest.setClaimTokenFormat("urn:ietf:params:oauth:token-type:jwt");
|
||||
authzRequest.setClaimToken(Base64.encodeBytes(JsonSerialization.writeValueAsBytes(claims)));
|
||||
}
|
||||
}
|
||||
|
||||
if (accessToken.getAuthorization() != null) {
|
||||
authzRequest.setRpt(accessTokenString);
|
||||
}
|
||||
|
||||
LOGGER.debug("Obtaining authorization for authenticated user.");
|
||||
AuthorizationResponse authzResponse = getAuthzClient().authorization(accessTokenString).authorize(authzRequest);
|
||||
|
||||
if (authzResponse != null) {
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getToken(), deployment);
|
||||
}
|
||||
} catch (AuthorizationDeniedException ignore) {
|
||||
LOGGER.debug("Authorization denied", ignore);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unexpected error during authorization request.", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getPermissionTicket(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AuthzClient authzClient, HttpFacade httpFacade) {
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
ProtectionResource protection = authzClient.protection();
|
||||
PermissionResource permission = protection.permission();
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
|
||||
permissionRequest.setResourceId(pathConfig.getId());
|
||||
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
|
||||
|
||||
Map<String, List<String>> claims = getClaims(pathConfig, httpFacade);
|
||||
|
||||
if (!claims.isEmpty()) {
|
||||
permissionRequest.setClaims(claims);
|
||||
}
|
||||
|
||||
return permission.create(permissionRequest).getTicket();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, List<String>> getClaims(PathConfig pathConfig, HttpFacade httpFacade) {
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
Map<String, Map<String, Object>> claimInformationPointConfig = pathConfig.getClaimInformationPointConfig();
|
||||
|
||||
if (claimInformationPointConfig != null) {
|
||||
for (Entry<String, Map<String, Object>> claimDef : claimInformationPointConfig.entrySet()) {
|
||||
ClaimInformationPointProviderFactory factory = getPolicyEnforcer().getClaimInformationPointProviderFactories().get(claimDef.getKey());
|
||||
|
||||
if (factory != null) {
|
||||
claims.putAll(factory.create(claimDef.getValue()).resolve(httpFacade));
|
||||
}
|
||||
}
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
private boolean isBearerAuthorization(OIDCHttpFacade httpFacade) {
|
||||
List<String> authHeaders = httpFacade.getRequest().getHeaders("Authorization");
|
||||
if (authHeaders == null || authHeaders.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String authHeader : authHeaders) {
|
||||
String[] split = authHeader.trim().split("\\s+");
|
||||
if (split == null || split.length != 2) continue;
|
||||
if (!split[0].equalsIgnoreCase("Bearer")) continue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
|
||||
/**
|
||||
* A simple LRU cache implementation supporting expiration and maximum number of entries.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PathCache {
|
||||
|
||||
/**
|
||||
* The load factor.
|
||||
*/
|
||||
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
|
||||
|
||||
private final Map<String, CacheEntry> cache;
|
||||
|
||||
private final AtomicBoolean writing = new AtomicBoolean(false);
|
||||
|
||||
private final long maxAge;
|
||||
private final boolean enabled;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param maxEntries the maximum number of entries to keep in the cache
|
||||
* @param maxAge the time in milliseconds that an entry can stay in the cache. If {@code -1}, entries never expire
|
||||
*/
|
||||
public PathCache(final int maxEntries, long maxAge) {
|
||||
cache = new LinkedHashMap<String, CacheEntry>(16, DEFAULT_LOAD_FACTOR, true) {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry eldest) {
|
||||
return cache.size() > maxEntries;
|
||||
}
|
||||
};
|
||||
this.maxAge = maxAge;
|
||||
this.enabled = maxAge > 0;
|
||||
}
|
||||
|
||||
public void put(String uri, PathConfig newValue) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (parkForWriteAndCheckInterrupt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CacheEntry cacheEntry = cache.get(uri);
|
||||
|
||||
if (cacheEntry == null) {
|
||||
cache.put(uri, new CacheEntry(uri, newValue, maxAge));
|
||||
}
|
||||
} finally {
|
||||
writing.lazySet(false);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containsKey(String uri) {
|
||||
return cache.containsKey(uri);
|
||||
}
|
||||
|
||||
public PathConfig get(String uri) {
|
||||
if (parkForReadAndCheckInterrupt()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CacheEntry cached = cache.get(uri);
|
||||
|
||||
if (cached != null) {
|
||||
return removeIfExpired(cached);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
try {
|
||||
if (parkForWriteAndCheckInterrupt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache.remove(key);
|
||||
} finally {
|
||||
writing.lazySet(false);
|
||||
}
|
||||
}
|
||||
|
||||
private PathConfig removeIfExpired(CacheEntry cached) {
|
||||
if (cached == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cached.isExpired()) {
|
||||
remove(cached.key());
|
||||
return null;
|
||||
}
|
||||
|
||||
return cached.value();
|
||||
}
|
||||
|
||||
private boolean parkForWriteAndCheckInterrupt() {
|
||||
while (!writing.compareAndSet(false, true)) {
|
||||
LockSupport.parkNanos(1L);
|
||||
if (Thread.interrupted()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean parkForReadAndCheckInterrupt() {
|
||||
while (writing.get()) {
|
||||
LockSupport.parkNanos(1L);
|
||||
if (Thread.interrupted()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final class CacheEntry {
|
||||
|
||||
final String key;
|
||||
final PathConfig value;
|
||||
final long expiration;
|
||||
|
||||
CacheEntry(String key, PathConfig value, long maxAge) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
if(maxAge == -1) {
|
||||
expiration = -1;
|
||||
} else {
|
||||
expiration = System.currentTimeMillis() + maxAge;
|
||||
}
|
||||
}
|
||||
|
||||
String key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
PathConfig value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
boolean isExpired() {
|
||||
return expiration != -1 ? System.currentTimeMillis() > expiration : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
-334
@@ -1,334 +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.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.ClientAuthenticator;
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||
import org.keycloak.common.util.PathMatcher;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathCacheConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
|
||||
/**
|
||||
* @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 PathConfigMatcher pathMatcher;
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final Map<String, ClaimInformationPointProviderFactory> claimInformationPointProviderFactories = new HashMap<>();
|
||||
|
||||
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
|
||||
this.deployment = deployment;
|
||||
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
|
||||
Configuration configuration = new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient());
|
||||
this.authzClient = AuthzClient.create(configuration, new ClientAuthenticator() {
|
||||
@Override
|
||||
public void configureClientCredentials(Map<String, List<String>> requestParams, Map<String, String> requestHeaders) {
|
||||
Map<String, String> formparams = new HashMap<>();
|
||||
ClientCredentialsProviderUtils.setClientCredentials(PolicyEnforcer.this.deployment, requestHeaders, formparams);
|
||||
for (Entry<String, String> param : formparams.entrySet()) {
|
||||
requestParams.put(param.getKey(), Arrays.asList(param.getValue()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
||||
pathMatcher = new PathConfigMatcher(paths, enforcerConfig, authzClient);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Initialization complete. Path configurations:");
|
||||
for (PathConfig pathConfig : this.paths.values()) {
|
||||
LOGGER.debug(pathConfig);
|
||||
}
|
||||
}
|
||||
|
||||
loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, ClaimInformationPointProviderFactory.class.getClassLoader()));
|
||||
loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, Thread.currentThread().getContextClassLoader()));
|
||||
}
|
||||
|
||||
public AuthorizationContext enforce(OIDCHttpFacade facade) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugv("Policy enforcement is enabled. Enforcing policy decisions for path [{0}].", facade.getRequest().getURI());
|
||||
}
|
||||
|
||||
AuthorizationContext 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;
|
||||
}
|
||||
|
||||
public PolicyEnforcerConfig getEnforcerConfig() {
|
||||
return enforcerConfig;
|
||||
}
|
||||
|
||||
public AuthzClient getClient() {
|
||||
return authzClient;
|
||||
}
|
||||
|
||||
public Map<String, PathConfig> getPaths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
public PathConfigMatcher getPathMatcher() {
|
||||
return pathMatcher;
|
||||
}
|
||||
|
||||
public KeycloakDeployment getDeployment() {
|
||||
return deployment;
|
||||
}
|
||||
|
||||
public Map<String, ClaimInformationPointProviderFactory> getClaimInformationPointProviderFactories() {
|
||||
return claimInformationPointProviderFactories;
|
||||
}
|
||||
|
||||
private void loadClaimInformationPointProviders(ServiceLoader<ClaimInformationPointProviderFactory> loader) {
|
||||
Iterator<ClaimInformationPointProviderFactory> iterator = loader.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
ClaimInformationPointProviderFactory factory = iterator.next();
|
||||
|
||||
factory.init(this);
|
||||
|
||||
claimInformationPointProviderFactories.put(factory.getName(), factory);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||
boolean loadPathsFromServer = true;
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
if (!PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
loadPathsFromServer = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (loadPathsFromServer) {
|
||||
LOGGER.info("No path provided in configuration.");
|
||||
Map<String, PathConfig> paths = configureAllPathsForResourceServer(protectedResource);
|
||||
|
||||
paths.putAll(configureDefinedPaths(protectedResource, enforcerConfig));
|
||||
|
||||
return paths;
|
||||
} 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 LinkedHashMap<String, PathConfig>());
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
ResourceRepresentation resource;
|
||||
String resourceName = pathConfig.getName();
|
||||
String path = pathConfig.getPath();
|
||||
|
||||
if (resourceName != null) {
|
||||
LOGGER.debugf("Trying to find resource with name [%s] for path [%s].", resourceName, path);
|
||||
resource = protectedResource.findByName(resourceName);
|
||||
} else {
|
||||
LOGGER.debugf("Trying to find resource with uri [%s] for path [%s].", path, path);
|
||||
List<ResourceRepresentation> resources = protectedResource.findByUri(path);
|
||||
|
||||
if (resources.isEmpty()) {
|
||||
resources = protectedResource.findByMatchingUri(path);
|
||||
}
|
||||
|
||||
if (resources.size() == 1) {
|
||||
resource = resources.get(0);
|
||||
} else if (resources.size() > 1) {
|
||||
throw new RuntimeException("Multiple resources found with the same uri");
|
||||
} else {
|
||||
resource = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (resource != null) {
|
||||
pathConfig.setId(resource.getId());
|
||||
}
|
||||
|
||||
PathConfig existingPath = null;
|
||||
|
||||
for (PathConfig current : paths.values()) {
|
||||
if (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>());
|
||||
|
||||
if (!enforcerConfig.getLazyLoadPaths()) {
|
||||
for (String id : protectedResource.findAll()) {
|
||||
ResourceRepresentation resourceDescription = protectedResource.findById(id);
|
||||
|
||||
if (resourceDescription.getUri() != null) {
|
||||
PathConfig pathConfig = PathConfig.createPathConfig(resourceDescription);
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
public class PathConfigMatcher extends PathMatcher<PathConfig> {
|
||||
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final PathCache pathCache;
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
|
||||
public PathConfigMatcher(Map<String, PathConfig> paths, PolicyEnforcerConfig enforcerConfig, AuthzClient authzClient) {
|
||||
this.paths = paths;
|
||||
this.enforcerConfig = enforcerConfig;
|
||||
PathCacheConfig cacheConfig = enforcerConfig.getPathCacheConfig();
|
||||
|
||||
if (cacheConfig == null) {
|
||||
cacheConfig = new PathCacheConfig();
|
||||
}
|
||||
|
||||
pathCache = new PathCache(cacheConfig.getMaxEntries(), cacheConfig.getLifespan());
|
||||
this.authzClient = authzClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathConfig matches(String targetUri) {
|
||||
PathConfig pathConfig = pathCache.get(targetUri);
|
||||
|
||||
if (pathCache.containsKey(targetUri) || pathConfig != null) {
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
pathConfig = super.matches(targetUri);
|
||||
|
||||
if (enforcerConfig.getLazyLoadPaths() || enforcerConfig.getPathCacheConfig() != null) {
|
||||
if ((pathConfig == null || (pathConfig.getPath().contains("*")))) {
|
||||
try {
|
||||
List<ResourceRepresentation> matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri);
|
||||
|
||||
if (!matchingResources.isEmpty()) {
|
||||
pathConfig = PathConfig.createPathConfig(matchingResources.get(0));
|
||||
}
|
||||
} catch (Exception cause) {
|
||||
LOGGER.errorf(cause, "Could not lazy load resource with path [" + targetUri + "] from server");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathCache.put(targetUri, pathConfig);
|
||||
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPath(PathConfig entry) {
|
||||
return entry.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<PathConfig> getPaths() {
|
||||
return paths.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
|
||||
if (originalConfig.hasPattern()) {
|
||||
ProtectedResource resource = authzClient.protection().resource();
|
||||
|
||||
// search by an exact match
|
||||
List<ResourceRepresentation> search = resource.findByUri(path);
|
||||
|
||||
// if exact match not found, try to obtain from current path the parent path.
|
||||
// if path is /resource/1/test and pattern from pathConfig is /resource/{id}/*, parent path is /resource/1
|
||||
// this logic allows to match sub resources of a resource instance (/resource/1) to the parent resource,
|
||||
// so any permission granted to parent also applies to sub resources
|
||||
if (search.isEmpty()) {
|
||||
search = resource.findByUri(buildUriFromTemplate(originalConfig.getPath(), path, true));
|
||||
}
|
||||
|
||||
if (!search.isEmpty()) {
|
||||
ResourceRepresentation targetResource = search.get(0);
|
||||
PathConfig config = PathConfig.createPathConfig(targetResource);
|
||||
|
||||
config.setScopes(originalConfig.getScopes());
|
||||
config.setMethods(originalConfig.getMethods());
|
||||
config.setParentConfig(originalConfig);
|
||||
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void removeFromCache(String pathConfig) {
|
||||
pathCache.remove(pathConfig);
|
||||
}
|
||||
};
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization.cip;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.util.PlaceHolders;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ClaimsInformationPointProvider implements ClaimInformationPointProvider {
|
||||
|
||||
private final Map<String, Object> config;
|
||||
|
||||
public ClaimsInformationPointProvider(Map<String, Object> config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> resolve(HttpFacade httpFacade) {
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
|
||||
for (Entry<String, Object> configEntry : config.entrySet()) {
|
||||
String claimName = configEntry.getKey();
|
||||
Object claimValue = configEntry.getValue();
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
if (claimValue instanceof String) {
|
||||
values = getValues(claimValue.toString(), httpFacade);
|
||||
} else if (claimValue instanceof Collection) {
|
||||
Iterator iterator = Collection.class.cast(claimValue).iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
List<String> resolvedValues = getValues(iterator.next().toString(), httpFacade);
|
||||
|
||||
if (!resolvedValues.isEmpty()) {
|
||||
values.addAll(resolvedValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!values.isEmpty()) {
|
||||
claims.put(claimName, values);
|
||||
}
|
||||
}
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
private List<String> getValues(String value, HttpFacade httpFacade) {
|
||||
return PlaceHolders.resolve(value, httpFacade);
|
||||
}
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization.cip;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ClaimsInformationPointProviderFactory implements ClaimInformationPointProviderFactory<ClaimsInformationPointProvider> {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "claims";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(PolicyEnforcer policyEnforcer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClaimsInformationPointProvider create(Map<String, Object> config) {
|
||||
return new ClaimsInformationPointProvider(config);
|
||||
}
|
||||
}
|
||||
-210
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization.cip;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.authorization.util.JsonUtils;
|
||||
import org.keycloak.adapters.authorization.util.PlaceHolders;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class HttpClaimInformationPointProvider implements ClaimInformationPointProvider {
|
||||
|
||||
private final Map<String, Object> config;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public HttpClaimInformationPointProvider(Map<String, Object> config, PolicyEnforcer policyEnforcer) {
|
||||
this.config = config;
|
||||
this.httpClient = policyEnforcer.getDeployment().getClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> resolve(HttpFacade httpFacade) {
|
||||
try {
|
||||
InputStream responseStream = executeRequest(httpFacade);
|
||||
|
||||
try (InputStream inputStream = new BufferedInputStream(responseStream)) {
|
||||
JsonNode jsonNode = JsonSerialization.mapper.readTree(inputStream);
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
Map<String, Object> claimsDef = (Map<String, Object>) config.get("claims");
|
||||
|
||||
if (claimsDef == null) {
|
||||
Iterator<String> nodeNames = jsonNode.fieldNames();
|
||||
|
||||
while (nodeNames.hasNext()) {
|
||||
String nodeName = nodeNames.next();
|
||||
claims.put(nodeName, JsonUtils.getValues(jsonNode.get(nodeName)));
|
||||
}
|
||||
} else {
|
||||
for (Entry<String, Object> claimDef : claimsDef.entrySet()) {
|
||||
List<String> jsonPaths = new ArrayList<>();
|
||||
|
||||
if (claimDef.getValue() instanceof Collection) {
|
||||
jsonPaths.addAll(Collection.class.cast(claimDef.getValue()));
|
||||
} else {
|
||||
jsonPaths.add(claimDef.getValue().toString());
|
||||
}
|
||||
|
||||
List<String> claimValues = new ArrayList<>();
|
||||
|
||||
for (String path : jsonPaths) {
|
||||
claimValues.addAll(JsonUtils.getValues(jsonNode, path));
|
||||
}
|
||||
|
||||
claims.put(claimDef.getKey(), claimValues);
|
||||
}
|
||||
}
|
||||
|
||||
return claims;
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Could not obtain claims from http claim information point [" + config.get("url") + "] response", cause);
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream executeRequest(HttpFacade httpFacade) {
|
||||
String method = config.get("method").toString();
|
||||
|
||||
if (method == null) {
|
||||
method = "GET";
|
||||
}
|
||||
|
||||
RequestBuilder builder = null;
|
||||
|
||||
if ("GET".equalsIgnoreCase(method)) {
|
||||
builder = RequestBuilder.get();
|
||||
} else {
|
||||
builder = RequestBuilder.post();
|
||||
}
|
||||
|
||||
builder.setUri(config.get("url").toString());
|
||||
|
||||
byte[] bytes = new byte[0];
|
||||
|
||||
try {
|
||||
setParameters(builder, httpFacade);
|
||||
|
||||
if (config.containsKey("headers")) {
|
||||
setHeaders(builder, httpFacade);
|
||||
}
|
||||
|
||||
HttpResponse response = httpClient.execute(builder.build());
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
||||
if (entity != null) {
|
||||
bytes = EntityUtils.toByteArray(entity);
|
||||
}
|
||||
|
||||
StatusLine statusLine = response.getStatusLine();
|
||||
int statusCode = statusLine.getStatusCode();
|
||||
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
throw new HttpResponseException("Unexpected response from server: " + statusCode + " / " + statusLine.getReasonPhrase(), statusCode, statusLine.getReasonPhrase(), bytes);
|
||||
}
|
||||
|
||||
return new ByteArrayInputStream(bytes);
|
||||
} catch (Exception cause) {
|
||||
try {
|
||||
throw new RuntimeException("Error executing http method [" + builder + "]. Response : " + StreamUtil.readString(new ByteArrayInputStream(bytes), Charset.forName("UTF-8")), cause);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error executing http method [" + builder + "]", cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setHeaders(RequestBuilder builder, HttpFacade httpFacade) {
|
||||
Object headersDef = config.get("headers");
|
||||
|
||||
if (headersDef != null) {
|
||||
Map<String, Object> headers = Map.class.cast(headersDef);
|
||||
|
||||
for (Entry<String, Object> header : headers.entrySet()) {
|
||||
Object value = header.getValue();
|
||||
List<String> headerValues = new ArrayList<>();
|
||||
|
||||
if (value instanceof Collection) {
|
||||
Collection values = Collection.class.cast(value);
|
||||
Iterator iterator = values.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
headerValues.addAll(PlaceHolders.resolve(iterator.next().toString(), httpFacade));
|
||||
}
|
||||
} else {
|
||||
headerValues.addAll(PlaceHolders.resolve(value.toString(), httpFacade));
|
||||
}
|
||||
|
||||
for (String headerValue : headerValues) {
|
||||
builder.addHeader(header.getKey(), headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setParameters(RequestBuilder builder, HttpFacade httpFacade) {
|
||||
Object config = this.config.get("parameters");
|
||||
|
||||
if (config != null) {
|
||||
Map<String, Object> paramsDef = Map.class.cast(config);
|
||||
|
||||
for (Entry<String, Object> paramDef : paramsDef.entrySet()) {
|
||||
Object value = paramDef.getValue();
|
||||
List<String> paramValues = new ArrayList<>();
|
||||
|
||||
if (value instanceof Collection) {
|
||||
Collection values = Collection.class.cast(value);
|
||||
Iterator iterator = values.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
paramValues.addAll(PlaceHolders.resolve(iterator.next().toString(), httpFacade));
|
||||
}
|
||||
} else {
|
||||
paramValues.addAll(PlaceHolders.resolve(value.toString(), httpFacade));
|
||||
}
|
||||
|
||||
for (String paramValue : paramValues) {
|
||||
builder.addParameter(paramDef.getKey(), paramValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization.cip;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class HttpClaimInformationPointProviderFactory implements ClaimInformationPointProviderFactory<HttpClaimInformationPointProvider> {
|
||||
|
||||
private PolicyEnforcer policyEnforcer;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(PolicyEnforcer policyEnforcer) {
|
||||
this.policyEnforcer = policyEnforcer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClaimInformationPointProvider create(Map<String, Object> config) {
|
||||
return new HttpClaimInformationPointProvider(config, policyEnforcer);
|
||||
}
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class JsonUtils {
|
||||
|
||||
public static List<String> getValues(JsonNode jsonNode, String path) {
|
||||
return getValues(jsonNode.at(path));
|
||||
}
|
||||
|
||||
public static List<String> getValues(JsonNode jsonNode) {
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
if (jsonNode.isArray()) {
|
||||
Iterator<JsonNode> iterator = jsonNode.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
String value = iterator.next().textValue();
|
||||
|
||||
if (value != null) {
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String value = jsonNode.textValue();
|
||||
|
||||
if (value != null) {
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization.util;
|
||||
|
||||
import static org.keycloak.adapters.authorization.util.PlaceHolders.getParameter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class KeycloakSecurityContextPlaceHolderResolver implements PlaceHolderResolver {
|
||||
|
||||
public static final String NAME = "keycloak";
|
||||
|
||||
@Override
|
||||
public List<String> resolve(String placeHolder, HttpFacade httpFacade) {
|
||||
String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
|
||||
OIDCHttpFacade oidcHttpFacade = OIDCHttpFacade.class.cast(httpFacade);
|
||||
KeycloakSecurityContext securityContext = oidcHttpFacade.getSecurityContext();
|
||||
|
||||
if (securityContext == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (source.endsWith("access_token")) {
|
||||
return Arrays.asList(securityContext.getTokenString());
|
||||
}
|
||||
|
||||
if (source.endsWith("id_token")) {
|
||||
return Arrays.asList(securityContext.getIdTokenString());
|
||||
}
|
||||
|
||||
JsonNode jsonNode;
|
||||
|
||||
if (source.startsWith("access_token[")) {
|
||||
jsonNode = JsonSerialization.mapper.valueToTree(securityContext.getToken());
|
||||
} else if (source.startsWith("id_token[")) {
|
||||
jsonNode = JsonSerialization.mapper.valueToTree(securityContext.getIdToken());
|
||||
} else {
|
||||
throw new RuntimeException("Invalid placeholder [" + placeHolder + "]");
|
||||
}
|
||||
|
||||
return JsonUtils.getValues(jsonNode, getParameter(source, "Invalid placeholder [" + placeHolder + "]"));
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization.util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface PlaceHolderResolver {
|
||||
|
||||
List<String> resolve(String placeHolder, HttpFacade httpFacade);
|
||||
|
||||
}
|
||||
-107
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PlaceHolders {
|
||||
|
||||
private static Map<String, PlaceHolderResolver> resolvers = new HashMap<>();
|
||||
|
||||
static {
|
||||
resolvers.put(RequestPlaceHolderResolver.NAME, new RequestPlaceHolderResolver());
|
||||
resolvers.put(KeycloakSecurityContextPlaceHolderResolver.NAME, new KeycloakSecurityContextPlaceHolderResolver());
|
||||
}
|
||||
|
||||
private static Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(.+?)\\}");
|
||||
private static Pattern PLACEHOLDER_PARAM_PATTERN = Pattern.compile("\\[(.+?)\\]");
|
||||
|
||||
public static List<String> resolve(String value, HttpFacade httpFacade) {
|
||||
Map<String, List<String>> placeHolders = parsePlaceHolders(value, httpFacade);
|
||||
|
||||
if (!placeHolders.isEmpty()) {
|
||||
value = formatPlaceHolder(value);
|
||||
|
||||
for (Entry<String, List<String>> entry : placeHolders.entrySet()) {
|
||||
List<String> values = entry.getValue();
|
||||
|
||||
if (values.isEmpty() || values.size() > 1) {
|
||||
return values;
|
||||
}
|
||||
|
||||
value = value.replaceAll(entry.getKey(), values.get(0)).trim();
|
||||
}
|
||||
}
|
||||
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
|
||||
static String getParameter(String source, String messageIfNotFound) {
|
||||
Matcher matcher = PLACEHOLDER_PARAM_PATTERN.matcher(source);
|
||||
|
||||
while (matcher.find()) {
|
||||
return matcher.group(1).replaceAll("'", "");
|
||||
}
|
||||
|
||||
if (messageIfNotFound != null) {
|
||||
throw new RuntimeException(messageIfNotFound);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Map<String, List<String>> parsePlaceHolders(String value, HttpFacade httpFacade) {
|
||||
Map<String, List<String>> placeHolders = new HashMap<>();
|
||||
Matcher matcher = PLACEHOLDER_PATTERN.matcher(value);
|
||||
|
||||
while (matcher.find()) {
|
||||
String placeHolder = matcher.group(1);
|
||||
int resolverNameIdx = placeHolder.indexOf('.');
|
||||
|
||||
if (resolverNameIdx == -1) {
|
||||
throw new RuntimeException("Invalid placeholder [" + value + "]. Could not find resolver name.");
|
||||
}
|
||||
|
||||
PlaceHolderResolver resolver = resolvers.get(placeHolder.substring(0, resolverNameIdx));
|
||||
|
||||
if (resolver != null) {
|
||||
List<String> resolved = resolver.resolve(placeHolder, httpFacade);
|
||||
|
||||
if (resolved != null) {
|
||||
placeHolders.put(formatPlaceHolder(placeHolder), resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return placeHolders;
|
||||
}
|
||||
|
||||
private static String formatPlaceHolder(String placeHolder) {
|
||||
return placeHolder.replaceAll("\\{", "").replace("}", "").replace("[", "").replace("]", "").replace("[", "").replace("]", "");
|
||||
}
|
||||
}
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization.util;
|
||||
|
||||
import static org.keycloak.adapters.authorization.util.PlaceHolders.getParameter;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Cookie;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class RequestPlaceHolderResolver implements PlaceHolderResolver {
|
||||
|
||||
static String NAME = "request";
|
||||
|
||||
@Override
|
||||
public List<String> resolve(String placeHolder, HttpFacade httpFacade) {
|
||||
String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
|
||||
Request request = httpFacade.getRequest();
|
||||
|
||||
if (source.startsWith("parameter")) {
|
||||
String parameterName = getParameter(source, "Could not obtain parameter name from placeholder [" + source + "]");
|
||||
String parameterValue = request.getQueryParamValue(parameterName);
|
||||
|
||||
if (parameterValue == null) {
|
||||
parameterValue = request.getFirstParam(parameterName);
|
||||
}
|
||||
|
||||
if (parameterValue != null) {
|
||||
return Arrays.asList(parameterValue);
|
||||
}
|
||||
} else if (source.startsWith("header")) {
|
||||
String headerName = getParameter(source, "Could not obtain header name from placeholder [" + source + "]");
|
||||
List<String> headerValue = request.getHeaders(headerName);
|
||||
|
||||
if (headerValue != null) {
|
||||
return headerValue;
|
||||
}
|
||||
} else if (source.startsWith("cookie")) {
|
||||
String cookieName = getParameter(source, "Could not obtain cookie name from placeholder [" + source + "]");
|
||||
Cookie cookieValue = request.getCookie(cookieName);
|
||||
|
||||
if (cookieValue != null) {
|
||||
return Arrays.asList(cookieValue.getValue());
|
||||
}
|
||||
} else if (source.startsWith("remoteAddr")) {
|
||||
String value = request.getRemoteAddr();
|
||||
|
||||
if (value != null) {
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
} else if (source.startsWith("method")) {
|
||||
String value = request.getMethod();
|
||||
|
||||
if (value != null) {
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
} else if (source.startsWith("uri")) {
|
||||
String value = request.getURI();
|
||||
|
||||
if (value != null) {
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
} else if (source.startsWith("relativePath")) {
|
||||
String value = request.getRelativePath();
|
||||
|
||||
if (value != null) {
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
} else if (source.startsWith("secure")) {
|
||||
return Arrays.asList(String.valueOf(request.isSecure()));
|
||||
} else if (source.startsWith("body")) {
|
||||
String contentType = request.getHeader("Content-Type");
|
||||
|
||||
if (contentType == null) {
|
||||
contentType = "";
|
||||
}
|
||||
|
||||
InputStream body = request.getInputStream(true);
|
||||
|
||||
try {
|
||||
if (body == null || body.available() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Failed to check available bytes in request input stream", cause);
|
||||
}
|
||||
|
||||
if (body.markSupported()) {
|
||||
body.mark(0);
|
||||
}
|
||||
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
try {
|
||||
switch (contentType) {
|
||||
case "application/json":
|
||||
try {
|
||||
JsonNode jsonNode = JsonSerialization.mapper.readTree(new BufferedInputStream(body) {
|
||||
@Override
|
||||
public void close() {
|
||||
// we can't close the stream because it may be used later by the application
|
||||
}
|
||||
});
|
||||
String path = getParameter(source, null);
|
||||
|
||||
if (path == null) {
|
||||
values.addAll(JsonUtils.getValues(jsonNode));
|
||||
} else {
|
||||
values.addAll(JsonUtils.getValues(jsonNode, path));
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Could not extract claim from request JSON body", cause);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
StringBuilder value = new StringBuilder();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(body));
|
||||
|
||||
try {
|
||||
int ch;
|
||||
|
||||
while ((ch = reader.read()) != -1) {
|
||||
value.append((char) ch);
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Could not extract claim from request body", cause);
|
||||
}
|
||||
|
||||
values.add(value.toString());
|
||||
}
|
||||
} finally {
|
||||
if (body.markSupported()) {
|
||||
try {
|
||||
body.reset();
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Failed to reset request input stream", cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
-253
@@ -1,253 +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.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.Principal;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
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";
|
||||
public static final String PROFILE_RESOURCE = "profile:";
|
||||
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 = null;
|
||||
if (keycloakConfigFile.startsWith(PROFILE_RESOURCE)) {
|
||||
try {
|
||||
is = new URL(keycloakConfigFile).openStream();
|
||||
} catch (MalformedURLException mfue) {
|
||||
throw new RuntimeException(mfue);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
} else {
|
||||
is = FindFile.findFile(keycloakConfigFile);
|
||||
}
|
||||
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 && kid != null) {
|
||||
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);
|
||||
|
||||
}
|
||||
-20
@@ -1,20 +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
|
||||
org.keycloak.adapters.authentication.JWTClientSecretCredentialsProvider
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
#
|
||||
# * Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
# * and other contributors as indicated by the @author tags.
|
||||
# *
|
||||
# * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# * you may not use this file except in compliance with the License.
|
||||
# * You may obtain a copy of the License at
|
||||
# *
|
||||
# * http://www.apache.org/licenses/LICENSE-2.0
|
||||
# *
|
||||
# * Unless required by applicable law or agreed to in writing, software
|
||||
# * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# * See the License for the specific language governing permissions and
|
||||
# * limitations under the License.
|
||||
#
|
||||
|
||||
org.keycloak.adapters.authorization.cip.ClaimsInformationPointProviderFactory
|
||||
org.keycloak.adapters.authorization.cip.HttpClaimInformationPointProviderFactory
|
||||
-103
@@ -1,103 +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.authentication.JWTClientSecretCredentialsProvider;
|
||||
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.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
*/
|
||||
public class KeycloakDeploymentBuilderTest {
|
||||
|
||||
@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());
|
||||
assertEquals("X-Custom3, X-Custom4", deployment.getCorsExposedHeaders());
|
||||
assertTrue(deployment.isBearerOnly());
|
||||
assertTrue(deployment.isPublicClient());
|
||||
assertTrue(deployment.isEnableBasicAuth());
|
||||
assertTrue(deployment.isExposeToken());
|
||||
assertFalse(deployment.isOAuthQueryParameterEnabled());
|
||||
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
|
||||
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
||||
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
|
||||
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());
|
||||
assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadSecretJwtCredentials() throws Exception {
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-secret-jwt.json"));
|
||||
assertEquals(JWTClientSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
||||
}
|
||||
}
|
||||
-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;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
*/
|
||||
public class KeycloakDeploymentTest {
|
||||
@Test
|
||||
public void shouldNotEnableOAuthQueryParamWhenIgnoreIsTrue() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
keycloakDeployment.setIgnoreOAuthQueryParameter(true);
|
||||
assertFalse(keycloakDeployment.isOAuthQueryParameterEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEnableOAuthQueryParamWhenIgnoreIsFalse() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
keycloakDeployment.setIgnoreOAuthQueryParameter(false);
|
||||
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEnableOAuthQueryParamWhenIgnoreNotSet() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
|
||||
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stripDefaultPorts() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
keycloakDeployment.setRealm("test");
|
||||
AdapterConfig config = new AdapterConfig();
|
||||
config.setAuthServerUrl("http://localhost:80/auth");
|
||||
keycloakDeployment.setAuthServerBaseUrl(config);
|
||||
|
||||
assertEquals("http://localhost/auth", keycloakDeployment.getAuthServerBaseUrl());
|
||||
|
||||
config.setAuthServerUrl("https://localhost:443/auth");
|
||||
keycloakDeployment.setAuthServerBaseUrl(config);
|
||||
|
||||
assertEquals("https://localhost/auth", keycloakDeployment.getAuthServerBaseUrl());
|
||||
}
|
||||
|
||||
}
|
||||
-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,12 +0,0 @@
|
||||
{
|
||||
"realm": "demo",
|
||||
"resource": "customer-portal",
|
||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url": "https://localhost:8443/auth",
|
||||
"ssl-required": "external",
|
||||
"credentials": {
|
||||
"secret-jwt": {
|
||||
"secret": "234234-234234-234234"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +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",
|
||||
"cors-exposed-headers": "X-Custom3, X-Custom4",
|
||||
"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,
|
||||
"ignore-oauth-query-parameter": true,
|
||||
"redirect-rewrite-rules" : {
|
||||
"^/wsmaster/api/(.*)$" : "/api/$1"
|
||||
}
|
||||
}
|
||||
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>4.0.0.Beta3</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>4.0.0.Beta3</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>
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
package org.keycloak.adapters.jbossweb;
|
||||
|
||||
import org.apache.catalina.Container;
|
||||
import org.apache.catalina.Valve;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.tomcat.AbstractAuthenticatedActionsValve;
|
||||
|
||||
public class AuthenticatedActionsValve extends AbstractAuthenticatedActionsValve {
|
||||
|
||||
public AuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
|
||||
super(deploymentContext, next, container);
|
||||
}
|
||||
}
|
||||
-76
@@ -1,76 +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.Container;
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.Valve;
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.connector.Response;
|
||||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.deploy.LoginConfig;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.tomcat.AbstractAuthenticatedActionsValve;
|
||||
import org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve;
|
||||
import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractAuthenticatedActionsValve createAuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
|
||||
return new AuthenticatedActionsValve(deploymentContext, next, container);
|
||||
}
|
||||
}
|
||||
@@ -1,114 +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>4.0.0.Beta3</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>
|
||||
</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>
|
||||
</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
|
||||
Loaded 100 of 11747 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user