Removed unnecessary modules
parent
c8ddea070e
commit
f963790943
|
@ -1,12 +0,0 @@
|
||||||
local-values.conf
|
|
||||||
target
|
|
||||||
*~
|
|
||||||
bin
|
|
||||||
*.idea
|
|
||||||
*.iml
|
|
||||||
*.eml
|
|
||||||
.project
|
|
||||||
.settings
|
|
||||||
.classpath
|
|
||||||
/target
|
|
||||||
.springBeans
|
|
|
@ -1,12 +0,0 @@
|
||||||
# OpenID Connect Client #
|
|
||||||
|
|
||||||
## Overview ##
|
|
||||||
|
|
||||||
This project contains an OpenID Connect Client implemented as a Spring Security AuthenticationFilter. The client facilitates a user's authentication into the secured application to an OpenID Connect Server following the OpenID Connect standard protocol.
|
|
||||||
|
|
||||||
## Configuring ##
|
|
||||||
|
|
||||||
For an example of the Client configuration, see the [Simple Web App](https://github.com/mitreid-connect/simple-web-app) project.
|
|
||||||
|
|
||||||
Full documentation is available on the [project documentation wiki pages](https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/wiki/Client-configuration).
|
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
|
|
||||||
Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT 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>
|
|
||||||
<artifactId>openid-connect-parent</artifactId>
|
|
||||||
<groupId>org.mitre</groupId>
|
|
||||||
<version>1.3.4-SNAPSHOT</version>
|
|
||||||
<relativePath>..</relativePath>
|
|
||||||
</parent>
|
|
||||||
<artifactId>openid-connect-client</artifactId>
|
|
||||||
<description>OpenID Connect Client filter for Spring Security</description>
|
|
||||||
<name>OpenID Connect Client</name>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.mitre</groupId>
|
|
||||||
<artifactId>openid-connect-common</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<source>${java-version}</source>
|
|
||||||
<target>${java-version}</target>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<!-- BUILD SOURCE FILES -->
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-source-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>attach-sources</id>
|
|
||||||
<goals>
|
|
||||||
<goal>jar-no-fork</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<!-- BUILD JavaDoc FILES -->
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>attach-sources</id>
|
|
||||||
<goals>
|
|
||||||
<goal>jar</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,3 +0,0 @@
|
||||||
Manifest-Version: 1.0
|
|
||||||
Class-Path:
|
|
||||||
|
|
|
@ -1,392 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.oauth2.introspectingfilter;
|
|
||||||
|
|
||||||
import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_BASIC;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
|
||||||
import org.mitre.oauth2.introspectingfilter.service.IntrospectionAuthorityGranter;
|
|
||||||
import org.mitre.oauth2.introspectingfilter.service.IntrospectionConfigurationService;
|
|
||||||
import org.mitre.oauth2.introspectingfilter.service.impl.SimpleIntrospectionAuthorityGranter;
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.client.ClientHttpRequest;
|
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
|
||||||
import org.springframework.security.oauth2.common.util.OAuth2Utils;
|
|
||||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
|
||||||
import org.springframework.security.oauth2.provider.OAuth2Request;
|
|
||||||
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
|
|
||||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.client.RestClientException;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import com.nimbusds.jose.util.Base64;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This ResourceServerTokenServices implementation introspects incoming tokens at a
|
|
||||||
* server's introspection endpoint URL and passes an Authentication object along
|
|
||||||
* based on the response from the introspection endpoint.
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class IntrospectingTokenService implements ResourceServerTokenServices {
|
|
||||||
|
|
||||||
private IntrospectionConfigurationService introspectionConfigurationService;
|
|
||||||
private IntrospectionAuthorityGranter introspectionAuthorityGranter = new SimpleIntrospectionAuthorityGranter();
|
|
||||||
|
|
||||||
private int defaultExpireTime = 300000; // 5 minutes in milliseconds
|
|
||||||
private boolean forceCacheExpireTime = false; // force removal of cached tokens based on default expire time
|
|
||||||
private boolean cacheNonExpiringTokens = false;
|
|
||||||
private boolean cacheTokens = true;
|
|
||||||
|
|
||||||
private HttpComponentsClientHttpRequestFactory factory;
|
|
||||||
|
|
||||||
public IntrospectingTokenService() {
|
|
||||||
this(HttpClientBuilder.create().useSystemProperties().build());
|
|
||||||
}
|
|
||||||
|
|
||||||
public IntrospectingTokenService(HttpClient httpClient) {
|
|
||||||
this.factory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inner class to store in the hash map
|
|
||||||
private class TokenCacheObject {
|
|
||||||
OAuth2AccessToken token;
|
|
||||||
OAuth2Authentication auth;
|
|
||||||
Date cacheExpire;
|
|
||||||
|
|
||||||
private TokenCacheObject(OAuth2AccessToken token, OAuth2Authentication auth) {
|
|
||||||
this.token = token;
|
|
||||||
this.auth = auth;
|
|
||||||
|
|
||||||
// we don't need to check the cacheTokens values, because this won't actually be added to the cache if cacheTokens is false
|
|
||||||
// if the token isn't null we use the token expire time
|
|
||||||
// if forceCacheExpireTime is also true, we also make sure that the token expire time is shorter than the default expire time
|
|
||||||
if ((this.token.getExpiration() != null) && (!forceCacheExpireTime || (forceCacheExpireTime && (this.token.getExpiration().getTime() - System.currentTimeMillis() <= defaultExpireTime)))) {
|
|
||||||
this.cacheExpire = this.token.getExpiration();
|
|
||||||
} else { // if the token doesn't have an expire time, or if the using forceCacheExpireTime the token expire time is longer than the default, then use the default expire time
|
|
||||||
Calendar cal = Calendar.getInstance();
|
|
||||||
cal.add(Calendar.MILLISECOND, defaultExpireTime);
|
|
||||||
this.cacheExpire = cal.getTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, TokenCacheObject> authCache = new HashMap<>();
|
|
||||||
/**
|
|
||||||
* Logger for this class
|
|
||||||
*/
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(IntrospectingTokenService.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the introspectionConfigurationService
|
|
||||||
*/
|
|
||||||
public IntrospectionConfigurationService getIntrospectionConfigurationService() {
|
|
||||||
return introspectionConfigurationService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param introspectionConfigurationService the introspectionConfigurationService to set
|
|
||||||
*/
|
|
||||||
public void setIntrospectionConfigurationService(IntrospectionConfigurationService introspectionUrlProvider) {
|
|
||||||
this.introspectionConfigurationService = introspectionUrlProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param introspectionAuthorityGranter the introspectionAuthorityGranter to set
|
|
||||||
*/
|
|
||||||
public void setIntrospectionAuthorityGranter(IntrospectionAuthorityGranter introspectionAuthorityGranter) {
|
|
||||||
this.introspectionAuthorityGranter = introspectionAuthorityGranter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the introspectionAuthorityGranter
|
|
||||||
*/
|
|
||||||
public IntrospectionAuthorityGranter getIntrospectionAuthorityGranter() {
|
|
||||||
return introspectionAuthorityGranter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the default cache expire time in milliseconds
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public int getDefaultExpireTime() {
|
|
||||||
return defaultExpireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set the default cache expire time in milliseconds
|
|
||||||
* @param defaultExpireTime
|
|
||||||
*/
|
|
||||||
public void setDefaultExpireTime(int defaultExpireTime) {
|
|
||||||
this.defaultExpireTime = defaultExpireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check if forcing a cache expire time maximum value
|
|
||||||
* @return the forceCacheExpireTime setting
|
|
||||||
*/
|
|
||||||
public boolean isForceCacheExpireTime() {
|
|
||||||
return forceCacheExpireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set forcing a cache expire time maximum value
|
|
||||||
* @param forceCacheExpireTime
|
|
||||||
*/
|
|
||||||
public void setForceCacheExpireTime(boolean forceCacheExpireTime) {
|
|
||||||
this.forceCacheExpireTime = forceCacheExpireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Are non-expiring tokens cached using the default cache time
|
|
||||||
* @return state of cacheNonExpiringTokens
|
|
||||||
*/
|
|
||||||
public boolean isCacheNonExpiringTokens() {
|
|
||||||
return cacheNonExpiringTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* should non-expiring tokens be cached using the default cache timeout
|
|
||||||
* @param cacheNonExpiringTokens
|
|
||||||
*/
|
|
||||||
public void setCacheNonExpiringTokens(boolean cacheNonExpiringTokens) {
|
|
||||||
this.cacheNonExpiringTokens = cacheNonExpiringTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is the service caching tokens, or is it hitting the introspection end point every time
|
|
||||||
* @return true is caching tokens locally, false hits the introspection end point every time
|
|
||||||
*/
|
|
||||||
public boolean isCacheTokens() {
|
|
||||||
return cacheTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure if the client should cache tokens locally or not
|
|
||||||
* @param cacheTokens
|
|
||||||
*/
|
|
||||||
public void setCacheTokens(boolean cacheTokens) {
|
|
||||||
this.cacheTokens = cacheTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check to see if the introspection end point response for a token has been cached locally
|
|
||||||
* This call will return the token if it has been cached and is still valid according to
|
|
||||||
* the cache expire time on the TokenCacheObject. If a cached value has been found but is
|
|
||||||
* expired, either by default expire times or the token's own expire time, then the token is
|
|
||||||
* removed from the cache and null is returned.
|
|
||||||
* @param key is the token to check
|
|
||||||
* @return the cached TokenCacheObject or null
|
|
||||||
*/
|
|
||||||
private TokenCacheObject checkCache(String key) {
|
|
||||||
if (cacheTokens && authCache.containsKey(key)) {
|
|
||||||
TokenCacheObject tco = authCache.get(key);
|
|
||||||
|
|
||||||
if (tco != null && tco.cacheExpire != null && tco.cacheExpire.after(new Date())) {
|
|
||||||
return tco;
|
|
||||||
} else {
|
|
||||||
// if the token is expired, don't keep things around.
|
|
||||||
authCache.remove(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OAuth2Request createStoredRequest(final JsonObject token) {
|
|
||||||
String clientId = token.get("client_id").getAsString();
|
|
||||||
Set<String> scopes = new HashSet<>();
|
|
||||||
if (token.has("scope")) {
|
|
||||||
scopes.addAll(OAuth2Utils.parseParameterList(token.get("scope").getAsString()));
|
|
||||||
}
|
|
||||||
Map<String, String> parameters = new HashMap<>();
|
|
||||||
parameters.put("client_id", clientId);
|
|
||||||
parameters.put("scope", OAuth2Utils.formatParameterList(scopes));
|
|
||||||
OAuth2Request storedRequest = new OAuth2Request(parameters, clientId, null, true, scopes, null, null, null, null);
|
|
||||||
return storedRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Authentication createUserAuthentication(JsonObject token) {
|
|
||||||
JsonElement userId = token.get("user_id");
|
|
||||||
if(userId == null) {
|
|
||||||
userId = token.get("sub");
|
|
||||||
if (userId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new PreAuthenticatedAuthenticationToken(userId.getAsString(), token, introspectionAuthorityGranter.getAuthorities(token));
|
|
||||||
}
|
|
||||||
|
|
||||||
private OAuth2AccessToken createAccessToken(final JsonObject token, final String tokenString) {
|
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessTokenImpl(token, tokenString);
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a token string against the introspection endpoint,
|
|
||||||
* then parse it and store it in the local cache if caching is enabled.
|
|
||||||
*
|
|
||||||
* @param accessToken Token to pass to the introspection endpoint
|
|
||||||
* @return TokenCacheObject containing authentication and token if the token was valid, otherwise null
|
|
||||||
*/
|
|
||||||
private TokenCacheObject parseToken(String accessToken) {
|
|
||||||
|
|
||||||
// find out which URL to ask
|
|
||||||
String introspectionUrl;
|
|
||||||
RegisteredClient client;
|
|
||||||
try {
|
|
||||||
introspectionUrl = introspectionConfigurationService.getIntrospectionUrl(accessToken);
|
|
||||||
client = introspectionConfigurationService.getClientConfiguration(accessToken);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
logger.error("Unable to load introspection URL or client configuration", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Use the SpringFramework RestTemplate to send the request to the
|
|
||||||
// endpoint
|
|
||||||
String validatedToken = null;
|
|
||||||
|
|
||||||
RestTemplate restTemplate;
|
|
||||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
|
|
||||||
|
|
||||||
final String clientId = client.getClientId();
|
|
||||||
final String clientSecret = client.getClientSecret();
|
|
||||||
|
|
||||||
if (SECRET_BASIC.equals(client.getTokenEndpointAuthMethod())){
|
|
||||||
// use BASIC auth if configured to do so
|
|
||||||
restTemplate = new RestTemplate(factory) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
|
|
||||||
ClientHttpRequest httpRequest = super.createRequest(url, method);
|
|
||||||
httpRequest.getHeaders().add("Authorization",
|
|
||||||
String.format("Basic %s", Base64.encode(String.format("%s:%s", clientId, clientSecret)) ));
|
|
||||||
return httpRequest;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else { //Alternatively use form based auth
|
|
||||||
restTemplate = new RestTemplate(factory);
|
|
||||||
|
|
||||||
form.add("client_id", clientId);
|
|
||||||
form.add("client_secret", clientSecret);
|
|
||||||
}
|
|
||||||
|
|
||||||
form.add("token", accessToken);
|
|
||||||
|
|
||||||
try {
|
|
||||||
validatedToken = restTemplate.postForObject(introspectionUrl, form, String.class);
|
|
||||||
} catch (RestClientException rce) {
|
|
||||||
logger.error("validateToken", rce);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (validatedToken != null) {
|
|
||||||
// parse the json
|
|
||||||
JsonElement jsonRoot = new JsonParser().parse(validatedToken);
|
|
||||||
if (!jsonRoot.isJsonObject()) {
|
|
||||||
return null; // didn't get a proper JSON object
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject tokenResponse = jsonRoot.getAsJsonObject();
|
|
||||||
|
|
||||||
if (tokenResponse.get("error") != null) {
|
|
||||||
// report an error?
|
|
||||||
logger.error("Got an error back: " + tokenResponse.get("error") + ", " + tokenResponse.get("error_description"));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tokenResponse.get("active").getAsBoolean()) {
|
|
||||||
// non-valid token
|
|
||||||
logger.info("Server returned non-active token");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// create an OAuth2Authentication
|
|
||||||
OAuth2Authentication auth = new OAuth2Authentication(createStoredRequest(tokenResponse), createUserAuthentication(tokenResponse));
|
|
||||||
// create an OAuth2AccessToken
|
|
||||||
OAuth2AccessToken token = createAccessToken(tokenResponse, accessToken);
|
|
||||||
|
|
||||||
if (token.getExpiration() == null || token.getExpiration().after(new Date())) {
|
|
||||||
// Store them in the cache
|
|
||||||
TokenCacheObject tco = new TokenCacheObject(token, auth);
|
|
||||||
if (cacheTokens && (cacheNonExpiringTokens || token.getExpiration() != null)) {
|
|
||||||
authCache.put(accessToken, tco);
|
|
||||||
}
|
|
||||||
return tco;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// when the token is invalid for whatever reason
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException {
|
|
||||||
// First check if the in memory cache has an Authentication object, and
|
|
||||||
// that it is still valid
|
|
||||||
// If Valid, return it
|
|
||||||
TokenCacheObject cacheAuth = checkCache(accessToken);
|
|
||||||
if (cacheAuth != null) {
|
|
||||||
return cacheAuth.auth;
|
|
||||||
} else {
|
|
||||||
cacheAuth = parseToken(accessToken);
|
|
||||||
if (cacheAuth != null) {
|
|
||||||
return cacheAuth.auth;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OAuth2AccessToken readAccessToken(String accessToken) {
|
|
||||||
// First check if the in memory cache has a Token object, and that it is
|
|
||||||
// still valid
|
|
||||||
// If Valid, return it
|
|
||||||
TokenCacheObject cacheAuth = checkCache(accessToken);
|
|
||||||
if (cacheAuth != null) {
|
|
||||||
return cacheAuth.token;
|
|
||||||
} else {
|
|
||||||
cacheAuth = parseToken(accessToken);
|
|
||||||
if (cacheAuth != null) {
|
|
||||||
return cacheAuth.token;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.oauth2.introspectingfilter;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
|
||||||
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
|
|
||||||
|
|
||||||
import com.google.common.base.Splitter;
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
|
|
||||||
|
|
||||||
public class OAuth2AccessTokenImpl implements OAuth2AccessToken {
|
|
||||||
|
|
||||||
private JsonObject introspectionResponse;
|
|
||||||
private String tokenString;
|
|
||||||
private Set<String> scopes = new HashSet<>();
|
|
||||||
private Date expireDate;
|
|
||||||
|
|
||||||
|
|
||||||
public OAuth2AccessTokenImpl(JsonObject introspectionResponse, String tokenString) {
|
|
||||||
this.setIntrospectionResponse(introspectionResponse);
|
|
||||||
this.tokenString = tokenString;
|
|
||||||
if (introspectionResponse.get("scope") != null) {
|
|
||||||
scopes = Sets.newHashSet(Splitter.on(" ").split(introspectionResponse.get("scope").getAsString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (introspectionResponse.get("exp") != null) {
|
|
||||||
expireDate = new Date(introspectionResponse.get("exp").getAsLong() * 1000L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getAdditionalInformation() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getScope() {
|
|
||||||
return scopes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OAuth2RefreshToken getRefreshToken() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTokenType() {
|
|
||||||
return BEARER_TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isExpired() {
|
|
||||||
if (expireDate != null && expireDate.before(new Date())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Date getExpiration() {
|
|
||||||
return expireDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExpiresIn() {
|
|
||||||
if (expireDate != null) {
|
|
||||||
return (int)TimeUnit.MILLISECONDS.toSeconds(expireDate.getTime() - (new Date()).getTime());
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getValue() {
|
|
||||||
return tokenString;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the token
|
|
||||||
*/
|
|
||||||
public JsonObject getIntrospectionResponse() {
|
|
||||||
return introspectionResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param token the token to set
|
|
||||||
*/
|
|
||||||
public void setIntrospectionResponse(JsonObject token) {
|
|
||||||
this.introspectionResponse = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.oauth2.introspectingfilter.service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface IntrospectionAuthorityGranter {
|
|
||||||
|
|
||||||
public List<GrantedAuthority> getAuthorities(JsonObject introspectionResponse);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.oauth2.introspectingfilter.service;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface IntrospectionConfigurationService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the introspection URL based on the access token.
|
|
||||||
* @param accessToken
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getIntrospectionUrl(String accessToken);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the client configuration to use to connect to the
|
|
||||||
* introspection endpoint. In particular, this cares about
|
|
||||||
* the clientId, clientSecret, and tokenEndpointAuthMethod
|
|
||||||
* fields.
|
|
||||||
*/
|
|
||||||
public RegisteredClient getClientConfiguration(String accessToken);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.oauth2.introspectingfilter.service.impl;
|
|
||||||
|
|
||||||
import java.text.ParseException;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.introspectingfilter.service.IntrospectionConfigurationService;
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.client.service.ClientConfigurationService;
|
|
||||||
import org.mitre.openid.connect.client.service.ServerConfigurationService;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.nimbusds.jwt.JWT;
|
|
||||||
import com.nimbusds.jwt.JWTParser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Parses the incoming accesstoken as a JWT and determines the issuer based on
|
|
||||||
* the "iss" field inside the JWT. Uses the ServerConfigurationService to determine
|
|
||||||
* the introspection URL for that issuer.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class JWTParsingIntrospectionConfigurationService implements IntrospectionConfigurationService {
|
|
||||||
|
|
||||||
private ServerConfigurationService serverConfigurationService;
|
|
||||||
private ClientConfigurationService clientConfigurationService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the serverConfigurationService
|
|
||||||
*/
|
|
||||||
public ServerConfigurationService getServerConfigurationService() {
|
|
||||||
return serverConfigurationService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param serverConfigurationService the serverConfigurationService to set
|
|
||||||
*/
|
|
||||||
public void setServerConfigurationService(ServerConfigurationService serverConfigurationService) {
|
|
||||||
this.serverConfigurationService = serverConfigurationService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param clientConfigurationService the clientConfigurationService to set
|
|
||||||
*/
|
|
||||||
public void setClientConfigurationService(ClientConfigurationService clientConfigurationService) {
|
|
||||||
this.clientConfigurationService = clientConfigurationService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getIssuer(String accessToken) {
|
|
||||||
try {
|
|
||||||
JWT jwt = JWTParser.parse(accessToken);
|
|
||||||
|
|
||||||
String issuer = jwt.getJWTClaimsSet().getIssuer();
|
|
||||||
|
|
||||||
return issuer;
|
|
||||||
|
|
||||||
} catch (ParseException e) {
|
|
||||||
throw new IllegalArgumentException("Unable to parse JWT", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.oauth2.introspectingfilter.IntrospectionConfigurationService#getIntrospectionUrl(java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getIntrospectionUrl(String accessToken) {
|
|
||||||
String issuer = getIssuer(accessToken);
|
|
||||||
if (!Strings.isNullOrEmpty(issuer)) {
|
|
||||||
ServerConfiguration server = serverConfigurationService.getServerConfiguration(issuer);
|
|
||||||
if (server != null) {
|
|
||||||
if (!Strings.isNullOrEmpty(server.getIntrospectionEndpointUri())) {
|
|
||||||
return server.getIntrospectionEndpointUri();
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Server does not have Introspection Endpoint defined");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Could not find server configuration for issuer " + issuer);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("No issuer claim found in JWT");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.oauth2.introspectingfilter.service.IntrospectionConfigurationService#getClientConfiguration(java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public RegisteredClient getClientConfiguration(String accessToken) {
|
|
||||||
|
|
||||||
String issuer = getIssuer(accessToken);
|
|
||||||
if (!Strings.isNullOrEmpty(issuer)) {
|
|
||||||
ServerConfiguration server = serverConfigurationService.getServerConfiguration(issuer);
|
|
||||||
if (server != null) {
|
|
||||||
RegisteredClient client = clientConfigurationService.getClientConfiguration(server);
|
|
||||||
if (client != null) {
|
|
||||||
return client;
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Could not find client configuration for issuer " + issuer);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Could not find server configuration for issuer " + issuer);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("No issuer claim found in JWT");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
|
|
||||||
package org.mitre.oauth2.introspectingfilter.service.impl;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.introspectingfilter.service.IntrospectionAuthorityGranter;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.oauth2.common.util.OAuth2Utils;
|
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class ScopeBasedIntrospectionAuthoritiesGranter implements IntrospectionAuthorityGranter {
|
|
||||||
|
|
||||||
private List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_API");
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.oauth2.introspectingfilter.IntrospectionAuthorityGranter#getAuthorities(net.minidev.json.JSONObject)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public List<GrantedAuthority> getAuthorities(JsonObject introspectionResponse) {
|
|
||||||
List<GrantedAuthority> auth = new ArrayList<>(getAuthorities());
|
|
||||||
|
|
||||||
if (introspectionResponse.has("scope") && introspectionResponse.get("scope").isJsonPrimitive()) {
|
|
||||||
String scopeString = introspectionResponse.get("scope").getAsString();
|
|
||||||
Set<String> scopes = OAuth2Utils.parseParameterList(scopeString);
|
|
||||||
for (String scope : scopes) {
|
|
||||||
auth.add(new SimpleGrantedAuthority("OAUTH_SCOPE_" + scope));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the authorities
|
|
||||||
*/
|
|
||||||
public List<GrantedAuthority> getAuthorities() {
|
|
||||||
return authorities;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param authorities the authorities to set
|
|
||||||
*/
|
|
||||||
public void setAuthorities(List<GrantedAuthority> authorities) {
|
|
||||||
this.authorities = authorities;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.oauth2.introspectingfilter.service.impl;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.introspectingfilter.service.IntrospectionAuthorityGranter;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Grants the same set of authorities no matter what's passed in.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class SimpleIntrospectionAuthorityGranter implements IntrospectionAuthorityGranter {
|
|
||||||
|
|
||||||
private List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_API");
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.oauth2.introspectingfilter.IntrospectionAuthorityGranter#getAuthorities(net.minidev.json.JSONObject)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public List<GrantedAuthority> getAuthorities(JsonObject introspectionResponse) {
|
|
||||||
return authorities;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the authorities
|
|
||||||
*/
|
|
||||||
public List<GrantedAuthority> getAuthorities() {
|
|
||||||
return authorities;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param authorities the authorities to set
|
|
||||||
*/
|
|
||||||
public void setAuthorities(List<GrantedAuthority> authorities) {
|
|
||||||
this.authorities = authorities;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.oauth2.introspectingfilter.service.impl;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.introspectingfilter.service.IntrospectionConfigurationService;
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Always provides the (configured) IntrospectionURL and RegisteredClient regardless
|
|
||||||
* of token. Useful for talking to a single, trusted authorization server.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class StaticIntrospectionConfigurationService implements IntrospectionConfigurationService {
|
|
||||||
|
|
||||||
private String introspectionUrl;
|
|
||||||
private RegisteredClient clientConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the clientConfiguration
|
|
||||||
*/
|
|
||||||
public RegisteredClient getClientConfiguration() {
|
|
||||||
return clientConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param clientConfiguration the clientConfiguration to set
|
|
||||||
*/
|
|
||||||
public void setClientConfiguration(RegisteredClient client) {
|
|
||||||
this.clientConfiguration = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the introspectionUrl
|
|
||||||
*/
|
|
||||||
public String getIntrospectionUrl() {
|
|
||||||
return introspectionUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param introspectionUrl the introspectionUrl to set
|
|
||||||
*/
|
|
||||||
public void setIntrospectionUrl(String introspectionUrl) {
|
|
||||||
this.introspectionUrl = introspectionUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.oauth2.introspectingfilter.IntrospectionConfigurationService#getIntrospectionUrl(java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getIntrospectionUrl(String accessToken) {
|
|
||||||
return getIntrospectionUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.oauth2.introspectingfilter.service.IntrospectionConfigurationService#getClientConfiguration(java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public RegisteredClient getClientConfiguration(String accessToken) {
|
|
||||||
return getClientConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client;
|
|
||||||
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
|
|
||||||
public class AuthorizationEndpointException extends AuthenticationServiceException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 6953119789654778380L;
|
|
||||||
|
|
||||||
private String error;
|
|
||||||
|
|
||||||
private String errorDescription;
|
|
||||||
|
|
||||||
private String errorURI;
|
|
||||||
|
|
||||||
public AuthorizationEndpointException(String error, String errorDescription, String errorURI) {
|
|
||||||
super("Error from Authorization Endpoint: " + error + " " + errorDescription + " " + errorURI);
|
|
||||||
this.error = error;
|
|
||||||
this.errorDescription = errorDescription;
|
|
||||||
this.errorURI = errorURI;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorDescription() {
|
|
||||||
return errorDescription;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorURI() {
|
|
||||||
return errorURI;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see java.lang.Object#toString()
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "AuthorizationEndpointException [error=" + error + ", errorDescription=" + errorDescription + ", errorURI=" + errorURI + "]";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client;
|
|
||||||
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.mitre.openid.connect.model.UserInfo;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
|
|
||||||
import com.nimbusds.jwt.JWT;
|
|
||||||
import com.nimbusds.jwt.JWTClaimsSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Simple mapper that adds ROLE_USER to the authorities map for all queries,
|
|
||||||
* plus adds ROLE_ADMIN if the subject and issuer pair are found in the
|
|
||||||
* configurable "admins" set.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class NamedAdminAuthoritiesMapper implements OIDCAuthoritiesMapper {
|
|
||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(NamedAdminAuthoritiesMapper.class);
|
|
||||||
|
|
||||||
private static final SimpleGrantedAuthority ROLE_ADMIN = new SimpleGrantedAuthority("ROLE_ADMIN");
|
|
||||||
private static final SimpleGrantedAuthority ROLE_USER = new SimpleGrantedAuthority("ROLE_USER");
|
|
||||||
|
|
||||||
private Set<SubjectIssuerGrantedAuthority> admins = new HashSet<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<? extends GrantedAuthority> mapAuthorities(JWT idToken, UserInfo userInfo) {
|
|
||||||
|
|
||||||
Set<GrantedAuthority> out = new HashSet<>();
|
|
||||||
try {
|
|
||||||
JWTClaimsSet claims = idToken.getJWTClaimsSet();
|
|
||||||
|
|
||||||
SubjectIssuerGrantedAuthority authority = new SubjectIssuerGrantedAuthority(claims.getSubject(), claims.getIssuer());
|
|
||||||
out.add(authority);
|
|
||||||
|
|
||||||
if (admins.contains(authority)) {
|
|
||||||
out.add(ROLE_ADMIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
// everybody's a user by default
|
|
||||||
out.add(ROLE_USER);
|
|
||||||
|
|
||||||
} catch (ParseException e) {
|
|
||||||
logger.error("Unable to parse ID Token inside of authorities mapper (huh?)");
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the admins
|
|
||||||
*/
|
|
||||||
public Set<SubjectIssuerGrantedAuthority> getAdmins() {
|
|
||||||
return admins;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param admins the admins to set
|
|
||||||
*/
|
|
||||||
public void setAdmins(Set<SubjectIssuerGrantedAuthority> admins) {
|
|
||||||
this.admins = admins;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,902 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client;
|
|
||||||
|
|
||||||
import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.PRIVATE_KEY;
|
|
||||||
import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_BASIC;
|
|
||||||
import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_JWT;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.config.RequestConfig;
|
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
|
||||||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
|
||||||
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
|
|
||||||
import org.mitre.jwt.signer.service.impl.SymmetricKeyJWTValidatorCacheService;
|
|
||||||
import org.mitre.oauth2.model.PKCEAlgorithm;
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.client.model.IssuerServiceResponse;
|
|
||||||
import org.mitre.openid.connect.client.service.AuthRequestOptionsService;
|
|
||||||
import org.mitre.openid.connect.client.service.AuthRequestUrlBuilder;
|
|
||||||
import org.mitre.openid.connect.client.service.ClientConfigurationService;
|
|
||||||
import org.mitre.openid.connect.client.service.IssuerService;
|
|
||||||
import org.mitre.openid.connect.client.service.ServerConfigurationService;
|
|
||||||
import org.mitre.openid.connect.client.service.impl.StaticAuthRequestOptionsService;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.mitre.openid.connect.model.PendingOIDCAuthenticationToken;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.client.ClientHttpRequest;
|
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
|
||||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.client.RestClientException;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
import org.springframework.web.util.UriUtils;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import com.nimbusds.jose.Algorithm;
|
|
||||||
import com.nimbusds.jose.JWSAlgorithm;
|
|
||||||
import com.nimbusds.jose.JWSHeader;
|
|
||||||
import com.nimbusds.jose.util.Base64;
|
|
||||||
import com.nimbusds.jose.util.Base64URL;
|
|
||||||
import com.nimbusds.jwt.JWT;
|
|
||||||
import com.nimbusds.jwt.JWTClaimsSet;
|
|
||||||
import com.nimbusds.jwt.JWTParser;
|
|
||||||
import com.nimbusds.jwt.PlainJWT;
|
|
||||||
import com.nimbusds.jwt.SignedJWT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OpenID Connect Authentication Filter class
|
|
||||||
*
|
|
||||||
* @author nemonik, jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
|
|
||||||
|
|
||||||
protected final static String REDIRECT_URI_SESION_VARIABLE = "redirect_uri";
|
|
||||||
protected final static String CODE_VERIFIER_SESSION_VARIABLE = "code_verifier";
|
|
||||||
protected final static String STATE_SESSION_VARIABLE = "state";
|
|
||||||
protected final static String NONCE_SESSION_VARIABLE = "nonce";
|
|
||||||
protected final static String ISSUER_SESSION_VARIABLE = "issuer";
|
|
||||||
protected final static String TARGET_SESSION_VARIABLE = "target";
|
|
||||||
protected final static int HTTP_SOCKET_TIMEOUT = 30000;
|
|
||||||
|
|
||||||
public final static String FILTER_PROCESSES_URL = "/openid_connect_login";
|
|
||||||
|
|
||||||
// Allow for time sync issues by having a window of X seconds.
|
|
||||||
private int timeSkewAllowance = 300;
|
|
||||||
|
|
||||||
// fetches and caches public keys for servers
|
|
||||||
@Autowired(required=false)
|
|
||||||
private JWKSetCacheService validationServices;
|
|
||||||
|
|
||||||
// creates JWT signer/validators for symmetric keys
|
|
||||||
@Autowired(required=false)
|
|
||||||
private SymmetricKeyJWTValidatorCacheService symmetricCacheService;
|
|
||||||
|
|
||||||
// signer based on keypair for this client (for outgoing auth requests)
|
|
||||||
@Autowired(required=false)
|
|
||||||
private JWTSigningAndValidationService authenticationSignerService;
|
|
||||||
|
|
||||||
@Autowired(required=false)
|
|
||||||
private HttpClient httpClient;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Modular services to build out client filter.
|
|
||||||
*/
|
|
||||||
// looks at the request and determines which issuer to use for lookup on the server
|
|
||||||
private IssuerService issuerService;
|
|
||||||
// holds server information (auth URI, token URI, etc.), indexed by issuer
|
|
||||||
private ServerConfigurationService servers;
|
|
||||||
// holds client information (client ID, redirect URI, etc.), indexed by issuer of the server
|
|
||||||
private ClientConfigurationService clients;
|
|
||||||
// provides extra options to inject into the outbound request
|
|
||||||
private AuthRequestOptionsService authOptions = new StaticAuthRequestOptionsService(); // initialize with an empty set of options
|
|
||||||
// builds the actual request URI based on input from all other services
|
|
||||||
private AuthRequestUrlBuilder authRequestBuilder;
|
|
||||||
|
|
||||||
// private helpers to handle target link URLs
|
|
||||||
private TargetLinkURIAuthenticationSuccessHandler targetSuccessHandler = new TargetLinkURIAuthenticationSuccessHandler();
|
|
||||||
private TargetLinkURIChecker deepLinkFilter;
|
|
||||||
|
|
||||||
protected int httpSocketTimeout = HTTP_SOCKET_TIMEOUT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OpenIdConnectAuthenticationFilter constructor
|
|
||||||
*/
|
|
||||||
public OIDCAuthenticationFilter() {
|
|
||||||
super(FILTER_PROCESSES_URL);
|
|
||||||
targetSuccessHandler.passthrough = super.getSuccessHandler();
|
|
||||||
super.setAuthenticationSuccessHandler(targetSuccessHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterPropertiesSet() {
|
|
||||||
super.afterPropertiesSet();
|
|
||||||
|
|
||||||
// if our JOSE validators don't get wired in, drop defaults into place
|
|
||||||
|
|
||||||
if (validationServices == null) {
|
|
||||||
validationServices = new JWKSetCacheService();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (symmetricCacheService == null) {
|
|
||||||
symmetricCacheService = new SymmetricKeyJWTValidatorCacheService();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is the main entry point for the filter.
|
|
||||||
*
|
|
||||||
* (non-Javadoc)
|
|
||||||
*
|
|
||||||
* @see org.springframework.security.web.authentication.
|
|
||||||
* AbstractAuthenticationProcessingFilter
|
|
||||||
* #attemptAuthentication(javax.servlet.http.HttpServletRequest,
|
|
||||||
* javax.servlet.http.HttpServletResponse)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
|
|
||||||
|
|
||||||
if (!Strings.isNullOrEmpty(request.getParameter("error"))) {
|
|
||||||
|
|
||||||
// there's an error coming back from the server, need to handle this
|
|
||||||
handleError(request, response);
|
|
||||||
return null; // no auth, response is sent to display page or something
|
|
||||||
|
|
||||||
} else if (!Strings.isNullOrEmpty(request.getParameter("code"))) {
|
|
||||||
|
|
||||||
// we got back the code, need to process this to get our tokens
|
|
||||||
Authentication auth = handleAuthorizationCodeResponse(request, response);
|
|
||||||
return auth;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// not an error, not a code, must be an initial login of some type
|
|
||||||
handleAuthorizationRequest(request, response);
|
|
||||||
|
|
||||||
return null; // no auth, response redirected to the server's Auth Endpoint (or possibly to the account chooser)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiate an Authorization request
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* The request from which to extract parameters and perform the
|
|
||||||
* authentication
|
|
||||||
* @param response
|
|
||||||
* @throws IOException
|
|
||||||
* If an input or output exception occurs
|
|
||||||
*/
|
|
||||||
protected void handleAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
|
||||||
|
|
||||||
HttpSession session = request.getSession();
|
|
||||||
|
|
||||||
IssuerServiceResponse issResp = issuerService.getIssuer(request);
|
|
||||||
|
|
||||||
if (issResp == null) {
|
|
||||||
logger.error("Null issuer response returned from service.");
|
|
||||||
throw new AuthenticationServiceException("No issuer found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (issResp.shouldRedirect()) {
|
|
||||||
response.sendRedirect(issResp.getRedirectUrl());
|
|
||||||
} else {
|
|
||||||
String issuer = issResp.getIssuer();
|
|
||||||
|
|
||||||
if (!Strings.isNullOrEmpty(issResp.getTargetLinkUri())) {
|
|
||||||
// there's a target URL in the response, we should save this so we can forward to it later
|
|
||||||
session.setAttribute(TARGET_SESSION_VARIABLE, issResp.getTargetLinkUri());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(issuer)) {
|
|
||||||
logger.error("No issuer found: " + issuer);
|
|
||||||
throw new AuthenticationServiceException("No issuer found: " + issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerConfiguration serverConfig = servers.getServerConfiguration(issuer);
|
|
||||||
if (serverConfig == null) {
|
|
||||||
logger.error("No server configuration found for issuer: " + issuer);
|
|
||||||
throw new AuthenticationServiceException("No server configuration found for issuer: " + issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
session.setAttribute(ISSUER_SESSION_VARIABLE, serverConfig.getIssuer());
|
|
||||||
|
|
||||||
RegisteredClient clientConfig = clients.getClientConfiguration(serverConfig);
|
|
||||||
if (clientConfig == null) {
|
|
||||||
logger.error("No client configuration found for issuer: " + issuer);
|
|
||||||
throw new AuthenticationServiceException("No client configuration found for issuer: " + issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
String redirectUri = null;
|
|
||||||
if (clientConfig.getRegisteredRedirectUri() != null && clientConfig.getRegisteredRedirectUri().size() == 1) {
|
|
||||||
// if there's a redirect uri configured (and only one), use that
|
|
||||||
redirectUri = Iterables.getOnlyElement(clientConfig.getRegisteredRedirectUri());
|
|
||||||
} else {
|
|
||||||
// otherwise our redirect URI is this current URL, with no query parameters
|
|
||||||
redirectUri = request.getRequestURL().toString();
|
|
||||||
}
|
|
||||||
session.setAttribute(REDIRECT_URI_SESION_VARIABLE, redirectUri);
|
|
||||||
|
|
||||||
// this value comes back in the id token and is checked there
|
|
||||||
String nonce = createNonce(session);
|
|
||||||
|
|
||||||
// this value comes back in the auth code response
|
|
||||||
String state = createState(session);
|
|
||||||
|
|
||||||
Map<String, String> options = authOptions.getOptions(serverConfig, clientConfig, request);
|
|
||||||
|
|
||||||
// if we're using PKCE, handle the challenge here
|
|
||||||
if (clientConfig.getCodeChallengeMethod() != null) {
|
|
||||||
String codeVerifier = createCodeVerifier(session);
|
|
||||||
options.put("code_challenge_method", clientConfig.getCodeChallengeMethod().getName());
|
|
||||||
if (clientConfig.getCodeChallengeMethod().equals(PKCEAlgorithm.plain)) {
|
|
||||||
options.put("code_challenge", codeVerifier);
|
|
||||||
} else if (clientConfig.getCodeChallengeMethod().equals(PKCEAlgorithm.S256)) {
|
|
||||||
try {
|
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
|
||||||
String hash = Base64URL.encode(digest.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII))).toString();
|
|
||||||
options.put("code_challenge", hash);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String authRequest = authRequestBuilder.buildAuthRequestUrl(serverConfig, clientConfig, redirectUri, nonce, state, options, issResp.getLoginHint());
|
|
||||||
|
|
||||||
logger.debug("Auth Request: " + authRequest);
|
|
||||||
|
|
||||||
response.sendRedirect(authRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param request
|
|
||||||
* The request from which to extract parameters and perform the
|
|
||||||
* authentication
|
|
||||||
* @return The authenticated user token, or null if authentication is
|
|
||||||
* incomplete.
|
|
||||||
*/
|
|
||||||
protected Authentication handleAuthorizationCodeResponse(HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
|
|
||||||
String authorizationCode = request.getParameter("code");
|
|
||||||
|
|
||||||
HttpSession session = request.getSession();
|
|
||||||
|
|
||||||
// check for state, if it doesn't match we bail early
|
|
||||||
String storedState = getStoredState(session);
|
|
||||||
String requestState = request.getParameter("state");
|
|
||||||
if (storedState == null || !storedState.equals(requestState)) {
|
|
||||||
throw new AuthenticationServiceException("State parameter mismatch on return. Expected " + storedState + " got " + requestState);
|
|
||||||
}
|
|
||||||
|
|
||||||
// look up the issuer that we set out to talk to
|
|
||||||
String issuer = getStoredSessionString(session, ISSUER_SESSION_VARIABLE);
|
|
||||||
|
|
||||||
// pull the configurations based on that issuer
|
|
||||||
ServerConfiguration serverConfig = servers.getServerConfiguration(issuer);
|
|
||||||
final RegisteredClient clientConfig = clients.getClientConfiguration(serverConfig);
|
|
||||||
|
|
||||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
|
|
||||||
form.add("grant_type", "authorization_code");
|
|
||||||
form.add("code", authorizationCode);
|
|
||||||
form.setAll(authOptions.getTokenOptions(serverConfig, clientConfig, request));
|
|
||||||
|
|
||||||
String codeVerifier = getStoredCodeVerifier(session);
|
|
||||||
if (codeVerifier != null) {
|
|
||||||
form.add("code_verifier", codeVerifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
String redirectUri = getStoredSessionString(session, REDIRECT_URI_SESION_VARIABLE);
|
|
||||||
if (redirectUri != null) {
|
|
||||||
form.add("redirect_uri", redirectUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Token Endpoint interaction
|
|
||||||
|
|
||||||
if(httpClient == null) {
|
|
||||||
httpClient = HttpClientBuilder.create()
|
|
||||||
.useSystemProperties()
|
|
||||||
.setDefaultRequestConfig(RequestConfig.custom()
|
|
||||||
.setSocketTimeout(httpSocketTimeout)
|
|
||||||
.build())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
|
||||||
|
|
||||||
RestTemplate restTemplate;
|
|
||||||
|
|
||||||
if (SECRET_BASIC.equals(clientConfig.getTokenEndpointAuthMethod())){
|
|
||||||
// use BASIC auth if configured to do so
|
|
||||||
restTemplate = new RestTemplate(factory) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
|
|
||||||
ClientHttpRequest httpRequest = super.createRequest(url, method);
|
|
||||||
httpRequest.getHeaders().add("Authorization",
|
|
||||||
String.format("Basic %s", Base64.encode(String.format("%s:%s",
|
|
||||||
UriUtils.encodePathSegment(clientConfig.getClientId(), "UTF-8"),
|
|
||||||
UriUtils.encodePathSegment(clientConfig.getClientSecret(), "UTF-8")))));
|
|
||||||
|
|
||||||
return httpRequest;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// we're not doing basic auth, figure out what other flavor we have
|
|
||||||
restTemplate = new RestTemplate(factory);
|
|
||||||
|
|
||||||
if (SECRET_JWT.equals(clientConfig.getTokenEndpointAuthMethod()) || PRIVATE_KEY.equals(clientConfig.getTokenEndpointAuthMethod())) {
|
|
||||||
// do a symmetric secret signed JWT for auth
|
|
||||||
|
|
||||||
|
|
||||||
JWTSigningAndValidationService signer = null;
|
|
||||||
JWSAlgorithm alg = clientConfig.getTokenEndpointAuthSigningAlg();
|
|
||||||
|
|
||||||
if (SECRET_JWT.equals(clientConfig.getTokenEndpointAuthMethod()) &&
|
|
||||||
(JWSAlgorithm.HS256.equals(alg)
|
|
||||||
|| JWSAlgorithm.HS384.equals(alg)
|
|
||||||
|| JWSAlgorithm.HS512.equals(alg))) {
|
|
||||||
|
|
||||||
// generate one based on client secret
|
|
||||||
signer = symmetricCacheService.getSymmetricValidtor(clientConfig.getClient());
|
|
||||||
|
|
||||||
} else if (PRIVATE_KEY.equals(clientConfig.getTokenEndpointAuthMethod())) {
|
|
||||||
|
|
||||||
// needs to be wired in to the bean
|
|
||||||
signer = authenticationSignerService;
|
|
||||||
|
|
||||||
if (alg == null) {
|
|
||||||
alg = authenticationSignerService.getDefaultSigningAlgorithm();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signer == null) {
|
|
||||||
throw new AuthenticationServiceException("Couldn't find required signer service for use with private key auth.");
|
|
||||||
}
|
|
||||||
|
|
||||||
JWTClaimsSet.Builder claimsSet = new JWTClaimsSet.Builder();
|
|
||||||
|
|
||||||
claimsSet.issuer(clientConfig.getClientId());
|
|
||||||
claimsSet.subject(clientConfig.getClientId());
|
|
||||||
claimsSet.audience(Lists.newArrayList(serverConfig.getTokenEndpointUri()));
|
|
||||||
claimsSet.jwtID(UUID.randomUUID().toString());
|
|
||||||
|
|
||||||
// TODO: make this configurable
|
|
||||||
Date exp = new Date(System.currentTimeMillis() + (60 * 1000)); // auth good for 60 seconds
|
|
||||||
claimsSet.expirationTime(exp);
|
|
||||||
|
|
||||||
Date now = new Date(System.currentTimeMillis());
|
|
||||||
claimsSet.issueTime(now);
|
|
||||||
claimsSet.notBeforeTime(now);
|
|
||||||
|
|
||||||
JWSHeader header = new JWSHeader(alg, null, null, null, null, null, null, null, null, null,
|
|
||||||
signer.getDefaultSignerKeyId(),
|
|
||||||
null, null);
|
|
||||||
SignedJWT jwt = new SignedJWT(header, claimsSet.build());
|
|
||||||
|
|
||||||
signer.signJwt(jwt, alg);
|
|
||||||
|
|
||||||
form.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
|
|
||||||
form.add("client_assertion", jwt.serialize());
|
|
||||||
} else {
|
|
||||||
//Alternatively use form based auth
|
|
||||||
form.add("client_id", clientConfig.getClientId());
|
|
||||||
form.add("client_secret", clientConfig.getClientSecret());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("tokenEndpointURI = " + serverConfig.getTokenEndpointUri());
|
|
||||||
logger.debug("form = " + form);
|
|
||||||
|
|
||||||
String jsonString = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
jsonString = restTemplate.postForObject(serverConfig.getTokenEndpointUri(), form, String.class);
|
|
||||||
} catch (RestClientException e) {
|
|
||||||
|
|
||||||
// Handle error
|
|
||||||
|
|
||||||
logger.error("Token Endpoint error response: " + e.getMessage());
|
|
||||||
|
|
||||||
throw new AuthenticationServiceException("Unable to obtain Access Token: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("from TokenEndpoint jsonString = " + jsonString);
|
|
||||||
|
|
||||||
JsonElement jsonRoot = new JsonParser().parse(jsonString);
|
|
||||||
if (!jsonRoot.isJsonObject()) {
|
|
||||||
throw new AuthenticationServiceException("Token Endpoint did not return a JSON object: " + jsonRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject tokenResponse = jsonRoot.getAsJsonObject();
|
|
||||||
|
|
||||||
if (tokenResponse.get("error") != null) {
|
|
||||||
|
|
||||||
// Handle error
|
|
||||||
|
|
||||||
String error = tokenResponse.get("error").getAsString();
|
|
||||||
|
|
||||||
logger.error("Token Endpoint returned: " + error);
|
|
||||||
|
|
||||||
throw new AuthenticationServiceException("Unable to obtain Access Token. Token Endpoint returned: " + error);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Extract the id_token to insert into the
|
|
||||||
// OIDCAuthenticationToken
|
|
||||||
|
|
||||||
// get out all the token strings
|
|
||||||
String accessTokenValue = null;
|
|
||||||
String idTokenValue = null;
|
|
||||||
String refreshTokenValue = null;
|
|
||||||
|
|
||||||
if (tokenResponse.has("access_token")) {
|
|
||||||
accessTokenValue = tokenResponse.get("access_token").getAsString();
|
|
||||||
} else {
|
|
||||||
throw new AuthenticationServiceException("Token Endpoint did not return an access_token: " + jsonString);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokenResponse.has("id_token")) {
|
|
||||||
idTokenValue = tokenResponse.get("id_token").getAsString();
|
|
||||||
} else {
|
|
||||||
logger.error("Token Endpoint did not return an id_token");
|
|
||||||
throw new AuthenticationServiceException("Token Endpoint did not return an id_token");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokenResponse.has("refresh_token")) {
|
|
||||||
refreshTokenValue = tokenResponse.get("refresh_token").getAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
JWT idToken = JWTParser.parse(idTokenValue);
|
|
||||||
|
|
||||||
// validate our ID Token over a number of tests
|
|
||||||
JWTClaimsSet idClaims = idToken.getJWTClaimsSet();
|
|
||||||
|
|
||||||
// check the signature
|
|
||||||
JWTSigningAndValidationService jwtValidator = null;
|
|
||||||
|
|
||||||
Algorithm tokenAlg = idToken.getHeader().getAlgorithm();
|
|
||||||
|
|
||||||
Algorithm clientAlg = clientConfig.getIdTokenSignedResponseAlg();
|
|
||||||
|
|
||||||
if (clientAlg != null) {
|
|
||||||
if (!clientAlg.equals(tokenAlg)) {
|
|
||||||
throw new AuthenticationServiceException("Token algorithm " + tokenAlg + " does not match expected algorithm " + clientAlg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idToken instanceof PlainJWT) {
|
|
||||||
|
|
||||||
if (clientAlg == null) {
|
|
||||||
throw new AuthenticationServiceException("Unsigned ID tokens can only be used if explicitly configured in client.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokenAlg != null && !tokenAlg.equals(Algorithm.NONE)) {
|
|
||||||
throw new AuthenticationServiceException("Unsigned token received, expected signature with " + tokenAlg);
|
|
||||||
}
|
|
||||||
} else if (idToken instanceof SignedJWT) {
|
|
||||||
|
|
||||||
SignedJWT signedIdToken = (SignedJWT)idToken;
|
|
||||||
|
|
||||||
if (tokenAlg.equals(JWSAlgorithm.HS256)
|
|
||||||
|| tokenAlg.equals(JWSAlgorithm.HS384)
|
|
||||||
|| tokenAlg.equals(JWSAlgorithm.HS512)) {
|
|
||||||
|
|
||||||
// generate one based on client secret
|
|
||||||
jwtValidator = symmetricCacheService.getSymmetricValidtor(clientConfig.getClient());
|
|
||||||
} else {
|
|
||||||
// otherwise load from the server's public key
|
|
||||||
jwtValidator = validationServices.getValidator(serverConfig.getJwksUri());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jwtValidator != null) {
|
|
||||||
if(!jwtValidator.validateSignature(signedIdToken)) {
|
|
||||||
throw new AuthenticationServiceException("Signature validation failed");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.error("No validation service found. Skipping signature validation");
|
|
||||||
throw new AuthenticationServiceException("Unable to find an appropriate signature validator for ID Token.");
|
|
||||||
}
|
|
||||||
} // TODO: encrypted id tokens
|
|
||||||
|
|
||||||
// check the issuer
|
|
||||||
if (idClaims.getIssuer() == null) {
|
|
||||||
throw new AuthenticationServiceException("Id Token Issuer is null");
|
|
||||||
} else if (!idClaims.getIssuer().equals(serverConfig.getIssuer())){
|
|
||||||
throw new AuthenticationServiceException("Issuers do not match, expected " + serverConfig.getIssuer() + " got " + idClaims.getIssuer());
|
|
||||||
}
|
|
||||||
|
|
||||||
// check expiration
|
|
||||||
if (idClaims.getExpirationTime() == null) {
|
|
||||||
throw new AuthenticationServiceException("Id Token does not have required expiration claim");
|
|
||||||
} else {
|
|
||||||
// it's not null, see if it's expired
|
|
||||||
Date now = new Date(System.currentTimeMillis() - (timeSkewAllowance * 1000));
|
|
||||||
if (now.after(idClaims.getExpirationTime())) {
|
|
||||||
throw new AuthenticationServiceException("Id Token is expired: " + idClaims.getExpirationTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check not before
|
|
||||||
if (idClaims.getNotBeforeTime() != null) {
|
|
||||||
Date now = new Date(System.currentTimeMillis() + (timeSkewAllowance * 1000));
|
|
||||||
if (now.before(idClaims.getNotBeforeTime())){
|
|
||||||
throw new AuthenticationServiceException("Id Token not valid untill: " + idClaims.getNotBeforeTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check issued at
|
|
||||||
if (idClaims.getIssueTime() == null) {
|
|
||||||
throw new AuthenticationServiceException("Id Token does not have required issued-at claim");
|
|
||||||
} else {
|
|
||||||
// since it's not null, see if it was issued in the future
|
|
||||||
Date now = new Date(System.currentTimeMillis() + (timeSkewAllowance * 1000));
|
|
||||||
if (now.before(idClaims.getIssueTime())) {
|
|
||||||
throw new AuthenticationServiceException("Id Token was issued in the future: " + idClaims.getIssueTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check audience
|
|
||||||
if (idClaims.getAudience() == null) {
|
|
||||||
throw new AuthenticationServiceException("Id token audience is null");
|
|
||||||
} else if (!idClaims.getAudience().contains(clientConfig.getClientId())) {
|
|
||||||
throw new AuthenticationServiceException("Audience does not match, expected " + clientConfig.getClientId() + " got " + idClaims.getAudience());
|
|
||||||
}
|
|
||||||
|
|
||||||
// compare the nonce to our stored claim
|
|
||||||
String nonce = idClaims.getStringClaim("nonce");
|
|
||||||
if (Strings.isNullOrEmpty(nonce)) {
|
|
||||||
|
|
||||||
logger.error("ID token did not contain a nonce claim.");
|
|
||||||
|
|
||||||
throw new AuthenticationServiceException("ID token did not contain a nonce claim.");
|
|
||||||
}
|
|
||||||
|
|
||||||
String storedNonce = getStoredNonce(session);
|
|
||||||
if (!nonce.equals(storedNonce)) {
|
|
||||||
logger.error("Possible replay attack detected! The comparison of the nonce in the returned "
|
|
||||||
+ "ID Token to the session " + NONCE_SESSION_VARIABLE + " failed. Expected " + storedNonce + " got " + nonce + ".");
|
|
||||||
|
|
||||||
throw new AuthenticationServiceException(
|
|
||||||
"Possible replay attack detected! The comparison of the nonce in the returned "
|
|
||||||
+ "ID Token to the session " + NONCE_SESSION_VARIABLE + " failed. Expected " + storedNonce + " got " + nonce + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// construct an PendingOIDCAuthenticationToken and return a Authentication object w/the userId and the idToken
|
|
||||||
|
|
||||||
PendingOIDCAuthenticationToken token = new PendingOIDCAuthenticationToken(idClaims.getSubject(), idClaims.getIssuer(),
|
|
||||||
serverConfig,
|
|
||||||
idToken, accessTokenValue, refreshTokenValue);
|
|
||||||
|
|
||||||
Authentication authentication = this.getAuthenticationManager().authenticate(token);
|
|
||||||
|
|
||||||
return authentication;
|
|
||||||
} catch (ParseException e) {
|
|
||||||
throw new AuthenticationServiceException("Couldn't parse idToken: ", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle Authorization Endpoint error
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* The request from which to extract parameters and handle the
|
|
||||||
* error
|
|
||||||
* @param response
|
|
||||||
* The response, needed to do a redirect to display the error
|
|
||||||
* @throws IOException
|
|
||||||
* If an input or output exception occurs
|
|
||||||
*/
|
|
||||||
protected void handleError(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
|
||||||
|
|
||||||
String error = request.getParameter("error");
|
|
||||||
String errorDescription = request.getParameter("error_description");
|
|
||||||
String errorURI = request.getParameter("error_uri");
|
|
||||||
|
|
||||||
throw new AuthorizationEndpointException(error, errorDescription, errorURI);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the named stored session variable as a string. Return null if not found or not a string.
|
|
||||||
* @param session
|
|
||||||
* @param key
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private static String getStoredSessionString(HttpSession session, String key) {
|
|
||||||
Object o = session.getAttribute(key);
|
|
||||||
if (o != null && o instanceof String) {
|
|
||||||
return o.toString();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a cryptographically random nonce and store it in the session
|
|
||||||
* @param session
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected static String createNonce(HttpSession session) {
|
|
||||||
String nonce = new BigInteger(50, new SecureRandom()).toString(16);
|
|
||||||
session.setAttribute(NONCE_SESSION_VARIABLE, nonce);
|
|
||||||
|
|
||||||
return nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the nonce we stored in the session
|
|
||||||
* @param session
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected static String getStoredNonce(HttpSession session) {
|
|
||||||
return getStoredSessionString(session, NONCE_SESSION_VARIABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a cryptographically random state and store it in the session
|
|
||||||
* @param session
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected static String createState(HttpSession session) {
|
|
||||||
String state = new BigInteger(50, new SecureRandom()).toString(16);
|
|
||||||
session.setAttribute(STATE_SESSION_VARIABLE, state);
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the state we stored in the session
|
|
||||||
* @param session
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected static String getStoredState(HttpSession session) {
|
|
||||||
return getStoredSessionString(session, STATE_SESSION_VARIABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a random code challenge and store it in the session
|
|
||||||
* @param session
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected static String createCodeVerifier(HttpSession session) {
|
|
||||||
String challenge = new BigInteger(50, new SecureRandom()).toString(16);
|
|
||||||
session.setAttribute(CODE_VERIFIER_SESSION_VARIABLE, challenge);
|
|
||||||
return challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the stored challenge from our session
|
|
||||||
* @param session
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected static String getStoredCodeVerifier(HttpSession session) {
|
|
||||||
return getStoredSessionString(session, CODE_VERIFIER_SESSION_VARIABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) {
|
|
||||||
targetSuccessHandler.passthrough = successHandler;
|
|
||||||
super.setAuthenticationSuccessHandler(targetSuccessHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a successful authentication event. If the issuer service sets
|
|
||||||
* a target URL, we'll go to that. Otherwise we'll let the superclass handle
|
|
||||||
* it for us with the configured behavior.
|
|
||||||
*/
|
|
||||||
protected class TargetLinkURIAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
|
||||||
|
|
||||||
private AuthenticationSuccessHandler passthrough;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationSuccess(HttpServletRequest request,
|
|
||||||
HttpServletResponse response, Authentication authentication)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
|
|
||||||
HttpSession session = request.getSession();
|
|
||||||
|
|
||||||
// check to see if we've got a target
|
|
||||||
String target = getStoredSessionString(session, TARGET_SESSION_VARIABLE);
|
|
||||||
|
|
||||||
if (!Strings.isNullOrEmpty(target)) {
|
|
||||||
session.removeAttribute(TARGET_SESSION_VARIABLE);
|
|
||||||
|
|
||||||
if (deepLinkFilter != null) {
|
|
||||||
target = deepLinkFilter.filter(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
response.sendRedirect(target);
|
|
||||||
} else {
|
|
||||||
// if the target was blank, use the default behavior here
|
|
||||||
passthrough.onAuthenticationSuccess(request, response, authentication);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Getters and setters for configuration variables
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
public int getTimeSkewAllowance() {
|
|
||||||
return timeSkewAllowance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimeSkewAllowance(int timeSkewAllowance) {
|
|
||||||
this.timeSkewAllowance = timeSkewAllowance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the validationServices
|
|
||||||
*/
|
|
||||||
public JWKSetCacheService getValidationServices() {
|
|
||||||
return validationServices;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param validationServices the validationServices to set
|
|
||||||
*/
|
|
||||||
public void setValidationServices(JWKSetCacheService validationServices) {
|
|
||||||
this.validationServices = validationServices;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the servers
|
|
||||||
*/
|
|
||||||
public ServerConfigurationService getServerConfigurationService() {
|
|
||||||
return servers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param servers the servers to set
|
|
||||||
*/
|
|
||||||
public void setServerConfigurationService(ServerConfigurationService servers) {
|
|
||||||
this.servers = servers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the clients
|
|
||||||
*/
|
|
||||||
public ClientConfigurationService getClientConfigurationService() {
|
|
||||||
return clients;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param clients the clients to set
|
|
||||||
*/
|
|
||||||
public void setClientConfigurationService(ClientConfigurationService clients) {
|
|
||||||
this.clients = clients;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the issuerService
|
|
||||||
*/
|
|
||||||
public IssuerService getIssuerService() {
|
|
||||||
return issuerService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param issuerService the issuerService to set
|
|
||||||
*/
|
|
||||||
public void setIssuerService(IssuerService issuerService) {
|
|
||||||
this.issuerService = issuerService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the authRequestBuilder
|
|
||||||
*/
|
|
||||||
public AuthRequestUrlBuilder getAuthRequestUrlBuilder() {
|
|
||||||
return authRequestBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param authRequestBuilder the authRequestBuilder to set
|
|
||||||
*/
|
|
||||||
public void setAuthRequestUrlBuilder(AuthRequestUrlBuilder authRequestBuilder) {
|
|
||||||
this.authRequestBuilder = authRequestBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the authOptions
|
|
||||||
*/
|
|
||||||
public AuthRequestOptionsService getAuthRequestOptionsService() {
|
|
||||||
return authOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param authOptions the authOptions to set
|
|
||||||
*/
|
|
||||||
public void setAuthRequestOptionsService(AuthRequestOptionsService authOptions) {
|
|
||||||
this.authOptions = authOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SymmetricKeyJWTValidatorCacheService getSymmetricCacheService() {
|
|
||||||
return symmetricCacheService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSymmetricCacheService(SymmetricKeyJWTValidatorCacheService symmetricCacheService) {
|
|
||||||
this.symmetricCacheService = symmetricCacheService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TargetLinkURIAuthenticationSuccessHandler getTargetLinkURIAuthenticationSuccessHandler() {
|
|
||||||
return targetSuccessHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTargetLinkURIAuthenticationSuccessHandler(
|
|
||||||
TargetLinkURIAuthenticationSuccessHandler targetSuccessHandler) {
|
|
||||||
this.targetSuccessHandler = targetSuccessHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TargetLinkURIChecker targetLinkURIChecker() {
|
|
||||||
return deepLinkFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTargetLinkURIChecker(TargetLinkURIChecker deepLinkFilter) {
|
|
||||||
this.deepLinkFilter = deepLinkFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import org.mitre.openid.connect.model.OIDCAuthenticationToken;
|
|
||||||
import org.mitre.openid.connect.model.PendingOIDCAuthenticationToken;
|
|
||||||
import org.mitre.openid.connect.model.UserInfo;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.nimbusds.jwt.JWT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author nemonik, Justin Richer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class OIDCAuthenticationProvider implements AuthenticationProvider {
|
|
||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(OIDCAuthenticationProvider.class);
|
|
||||||
|
|
||||||
private UserInfoFetcher userInfoFetcher = new UserInfoFetcher();
|
|
||||||
|
|
||||||
private OIDCAuthoritiesMapper authoritiesMapper = new NamedAdminAuthoritiesMapper();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
*
|
|
||||||
* @see org.springframework.security.authentication.AuthenticationProvider#
|
|
||||||
* authenticate(org.springframework.security.core.Authentication)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
|
|
||||||
|
|
||||||
if (!supports(authentication.getClass())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authentication instanceof PendingOIDCAuthenticationToken) {
|
|
||||||
|
|
||||||
PendingOIDCAuthenticationToken token = (PendingOIDCAuthenticationToken) authentication;
|
|
||||||
|
|
||||||
// get the ID Token value out
|
|
||||||
JWT idToken = token.getIdToken();
|
|
||||||
|
|
||||||
// load the user info if we can
|
|
||||||
UserInfo userInfo = userInfoFetcher.loadUserInfo(token);
|
|
||||||
|
|
||||||
if (userInfo == null) {
|
|
||||||
// user info not found -- could be an error, could be fine
|
|
||||||
} else {
|
|
||||||
// if we found userinfo, double check it
|
|
||||||
if (!Strings.isNullOrEmpty(userInfo.getSub()) && !userInfo.getSub().equals(token.getSub())) {
|
|
||||||
// the userinfo came back and the user_id fields don't match what was in the id_token
|
|
||||||
throw new UsernameNotFoundException("user_id mismatch between id_token and user_info call: " + token.getSub() + " / " + userInfo.getSub());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return createAuthenticationToken(token, authoritiesMapper.mapAuthorities(idToken, userInfo), userInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override this function to return a different kind of Authentication, processes the authorities differently,
|
|
||||||
* or do post-processing based on the UserInfo object.
|
|
||||||
*
|
|
||||||
* @param token
|
|
||||||
* @param authorities
|
|
||||||
* @param userInfo
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected Authentication createAuthenticationToken(PendingOIDCAuthenticationToken token, Collection<? extends GrantedAuthority> authorities, UserInfo userInfo) {
|
|
||||||
return new OIDCAuthenticationToken(token.getSub(),
|
|
||||||
token.getIssuer(),
|
|
||||||
userInfo, authorities,
|
|
||||||
token.getIdToken(), token.getAccessTokenValue(), token.getRefreshTokenValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param userInfoFetcher
|
|
||||||
*/
|
|
||||||
public void setUserInfoFetcher(UserInfoFetcher userInfoFetcher) {
|
|
||||||
this.userInfoFetcher = userInfoFetcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param authoritiesMapper
|
|
||||||
*/
|
|
||||||
public void setAuthoritiesMapper(OIDCAuthoritiesMapper authoritiesMapper) {
|
|
||||||
this.authoritiesMapper = authoritiesMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
*
|
|
||||||
* @see
|
|
||||||
* org.springframework.security.authentication.AuthenticationProvider#supports
|
|
||||||
* (java.lang.Class)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean supports(Class<?> authentication) {
|
|
||||||
return PendingOIDCAuthenticationToken.class.isAssignableFrom(authentication);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
|
|
||||||
package org.mitre.openid.connect.client;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import org.mitre.openid.connect.model.UserInfo;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
|
|
||||||
import com.nimbusds.jwt.JWT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface OIDCAuthoritiesMapper {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param idToken the ID Token (parsed as a JWT, cannot be @null)
|
|
||||||
* @param userInfo userInfo of the current user (could be @null)
|
|
||||||
* @return the set of authorities to map to this user
|
|
||||||
*/
|
|
||||||
Collection<? extends GrantedAuthority> mapAuthorities(JWT idToken, UserInfo userInfo);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple target URI checker, checks whether the string in question starts
|
|
||||||
* with a configured prefix. Returns "/" if the match fails.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class StaticPrefixTargetLinkURIChecker implements TargetLinkURIChecker {
|
|
||||||
|
|
||||||
private String prefix = "";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String filter(String target) {
|
|
||||||
if (target == null) {
|
|
||||||
return "/";
|
|
||||||
} else if (target.startsWith(prefix)) {
|
|
||||||
return target;
|
|
||||||
} else {
|
|
||||||
return "/";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPrefix() {
|
|
||||||
return prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrefix(String prefix) {
|
|
||||||
this.prefix = prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,125 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client;
|
|
||||||
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Simple authority representing a user at an issuer.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class SubjectIssuerGrantedAuthority implements GrantedAuthority {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 5584978219226664794L;
|
|
||||||
|
|
||||||
private final String subject;
|
|
||||||
private final String issuer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param subject
|
|
||||||
* @param issuer
|
|
||||||
*/
|
|
||||||
public SubjectIssuerGrantedAuthority(String subject, String issuer) {
|
|
||||||
if (Strings.isNullOrEmpty(subject) || Strings.isNullOrEmpty(issuer)) {
|
|
||||||
throw new IllegalArgumentException("Neither subject nor issuer may be null or empty");
|
|
||||||
}
|
|
||||||
this.subject = subject;
|
|
||||||
this.issuer = issuer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string formed by concatenating the subject with the issuer, separated by _ and prepended with OIDC_
|
|
||||||
*
|
|
||||||
* For example, the user "bob" from issuer "http://id.example.com/" would return the authority string of:
|
|
||||||
*
|
|
||||||
* OIDC_bob_http://id.example.com/
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getAuthority() {
|
|
||||||
return "OIDC_" + subject + "_" + issuer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the subject
|
|
||||||
*/
|
|
||||||
public String getSubject() {
|
|
||||||
return subject;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the issuer
|
|
||||||
*/
|
|
||||||
public String getIssuer() {
|
|
||||||
return issuer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see java.lang.Object#hashCode()
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((issuer == null) ? 0 : issuer.hashCode());
|
|
||||||
result = prime * result + ((subject == null) ? 0 : subject.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see java.lang.Object#equals(java.lang.Object)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!(obj instanceof SubjectIssuerGrantedAuthority)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
SubjectIssuerGrantedAuthority other = (SubjectIssuerGrantedAuthority) obj;
|
|
||||||
if (issuer == null) {
|
|
||||||
if (other.issuer != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!issuer.equals(other.issuer)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (subject == null) {
|
|
||||||
if (other.subject != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!subject.equals(other.subject)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return getAuthority();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client;
|
|
||||||
|
|
||||||
public interface TargetLinkURIChecker {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the parameter to make sure that it's a valid deep-link into this application.
|
|
||||||
*
|
|
||||||
* @param target
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String filter(String target);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration.UserInfoTokenMethod;
|
|
||||||
import org.mitre.openid.connect.model.DefaultUserInfo;
|
|
||||||
import org.mitre.openid.connect.model.PendingOIDCAuthenticationToken;
|
|
||||||
import org.mitre.openid.connect.model.UserInfo;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.client.ClientHttpRequest;
|
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class to fetch userinfo from the userinfo endpoint, if available. Caches the results.
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class UserInfoFetcher {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class
|
|
||||||
*/
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(UserInfoFetcher.class);
|
|
||||||
|
|
||||||
private LoadingCache<PendingOIDCAuthenticationToken, UserInfo> cache;
|
|
||||||
|
|
||||||
public UserInfoFetcher() {
|
|
||||||
this(HttpClientBuilder.create().useSystemProperties().build());
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserInfoFetcher(HttpClient httpClient) {
|
|
||||||
cache = CacheBuilder.newBuilder()
|
|
||||||
.expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch
|
|
||||||
.maximumSize(100)
|
|
||||||
.build(new UserInfoLoader(httpClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserInfo loadUserInfo(final PendingOIDCAuthenticationToken token) {
|
|
||||||
try {
|
|
||||||
return cache.get(token);
|
|
||||||
} catch (UncheckedExecutionException | ExecutionException e) {
|
|
||||||
logger.warn("Couldn't load User Info from token: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class UserInfoLoader extends CacheLoader<PendingOIDCAuthenticationToken, UserInfo> {
|
|
||||||
private HttpComponentsClientHttpRequestFactory factory;
|
|
||||||
|
|
||||||
UserInfoLoader(HttpClient httpClient) {
|
|
||||||
this.factory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserInfo load(final PendingOIDCAuthenticationToken token) throws URISyntaxException {
|
|
||||||
|
|
||||||
ServerConfiguration serverConfiguration = token.getServerConfiguration();
|
|
||||||
|
|
||||||
if (serverConfiguration == null) {
|
|
||||||
logger.warn("No server configuration found.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(serverConfiguration.getUserInfoUri())) {
|
|
||||||
logger.warn("No userinfo endpoint, not fetching.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String userInfoString = null;
|
|
||||||
|
|
||||||
if (serverConfiguration.getUserInfoTokenMethod() == null || serverConfiguration.getUserInfoTokenMethod().equals(UserInfoTokenMethod.HEADER)) {
|
|
||||||
RestTemplate restTemplate = new RestTemplate(factory) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
|
|
||||||
ClientHttpRequest httpRequest = super.createRequest(url, method);
|
|
||||||
httpRequest.getHeaders().add("Authorization", String.format("Bearer %s", token.getAccessTokenValue()));
|
|
||||||
return httpRequest;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
userInfoString = restTemplate.getForObject(serverConfiguration.getUserInfoUri(), String.class);
|
|
||||||
|
|
||||||
} else if (serverConfiguration.getUserInfoTokenMethod().equals(UserInfoTokenMethod.FORM)) {
|
|
||||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
|
|
||||||
form.add("access_token", token.getAccessTokenValue());
|
|
||||||
|
|
||||||
RestTemplate restTemplate = new RestTemplate(factory);
|
|
||||||
userInfoString = restTemplate.postForObject(serverConfiguration.getUserInfoUri(), form, String.class);
|
|
||||||
} else if (serverConfiguration.getUserInfoTokenMethod().equals(UserInfoTokenMethod.QUERY)) {
|
|
||||||
URIBuilder builder = new URIBuilder(serverConfiguration.getUserInfoUri());
|
|
||||||
builder.setParameter("access_token", token.getAccessTokenValue());
|
|
||||||
|
|
||||||
RestTemplate restTemplate = new RestTemplate(factory);
|
|
||||||
userInfoString = restTemplate.getForObject(builder.toString(), String.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!Strings.isNullOrEmpty(userInfoString)) {
|
|
||||||
|
|
||||||
JsonObject userInfoJson = new JsonParser().parse(userInfoString).getAsJsonObject();
|
|
||||||
|
|
||||||
UserInfo userInfo = fromJson(userInfoJson);
|
|
||||||
|
|
||||||
return userInfo;
|
|
||||||
} else {
|
|
||||||
// didn't get anything throw exception
|
|
||||||
throw new IllegalArgumentException("Unable to load user info");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected UserInfo fromJson(JsonObject userInfoJson) {
|
|
||||||
return DefaultUserInfo.fromJson(userInfoJson);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,130 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client.keypublisher;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
|
||||||
import org.mitre.openid.connect.view.JWKSetView;
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.nimbusds.jose.jwk.JWK;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class ClientKeyPublisher implements BeanDefinitionRegistryPostProcessor {
|
|
||||||
|
|
||||||
private JWTSigningAndValidationService signingAndValidationService;
|
|
||||||
|
|
||||||
private String jwkPublishUrl;
|
|
||||||
|
|
||||||
private BeanDefinitionRegistry registry;
|
|
||||||
|
|
||||||
private String jwkViewName = JWKSetView.VIEWNAME;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the jwkPublishUrl field is set on this bean, set up a listener on that URL to publish keys.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
|
||||||
if (!Strings.isNullOrEmpty(getJwkPublishUrl())) {
|
|
||||||
|
|
||||||
// add a mapping to this class
|
|
||||||
BeanDefinitionBuilder clientKeyMapping = BeanDefinitionBuilder.rootBeanDefinition(ClientKeyPublisherMapping.class);
|
|
||||||
// custom view resolver
|
|
||||||
BeanDefinitionBuilder viewResolver = BeanDefinitionBuilder.rootBeanDefinition(JwkViewResolver.class);
|
|
||||||
|
|
||||||
if (!Strings.isNullOrEmpty(getJwkPublishUrl())) {
|
|
||||||
clientKeyMapping.addPropertyValue("jwkPublishUrl", getJwkPublishUrl());
|
|
||||||
|
|
||||||
// randomize view name to make sure it doesn't conflict with local views
|
|
||||||
jwkViewName = JWKSetView.VIEWNAME + "-" + UUID.randomUUID().toString();
|
|
||||||
viewResolver.addPropertyValue("jwkViewName", jwkViewName);
|
|
||||||
|
|
||||||
// view bean
|
|
||||||
BeanDefinitionBuilder jwkView = BeanDefinitionBuilder.rootBeanDefinition(JWKSetView.class);
|
|
||||||
registry.registerBeanDefinition(JWKSetView.VIEWNAME, jwkView.getBeanDefinition());
|
|
||||||
viewResolver.addPropertyReference("jwk", JWKSetView.VIEWNAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
registry.registerBeanDefinition("clientKeyMapping", clientKeyMapping.getBeanDefinition());
|
|
||||||
registry.registerBeanDefinition("jwkViewResolver", viewResolver.getBeanDefinition());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(org.springframework.beans.factory.support.BeanDefinitionRegistry)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
|
||||||
this.registry = registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a view to publish all keys in JWK format. Only used if jwkPublishUrl is set.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public ModelAndView publishClientJwk() {
|
|
||||||
|
|
||||||
// map from key id to key
|
|
||||||
Map<String, JWK> keys = signingAndValidationService.getAllPublicKeys();
|
|
||||||
|
|
||||||
return new ModelAndView(jwkViewName, "keys", keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the jwkPublishUrl
|
|
||||||
*/
|
|
||||||
public String getJwkPublishUrl() {
|
|
||||||
return jwkPublishUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param jwkPublishUrl the jwkPublishUrl to set
|
|
||||||
*/
|
|
||||||
public void setJwkPublishUrl(String jwkPublishUrl) {
|
|
||||||
this.jwkPublishUrl = jwkPublishUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the signingAndValidationService
|
|
||||||
*/
|
|
||||||
public JWTSigningAndValidationService getSigningAndValidationService() {
|
|
||||||
return signingAndValidationService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param signingAndValidationService the signingAndValidationService to set
|
|
||||||
*/
|
|
||||||
public void setSigningAndValidationService(JWTSigningAndValidationService signingAndValidationService) {
|
|
||||||
this.signingAndValidationService = signingAndValidationService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.keypublisher;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
|
|
||||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
|
||||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class ClientKeyPublisherMapping extends RequestMappingInfoHandlerMapping {
|
|
||||||
|
|
||||||
private String jwkPublishUrl;
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#isHandler(java.lang.Class)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected boolean isHandler(Class<?> beanType) {
|
|
||||||
return beanType.equals(ClientKeyPublisher.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map the "jwkKeyPublish" method to our jwkPublishUrl.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
|
|
||||||
|
|
||||||
if (method.getName().equals("publishClientJwk") && getJwkPublishUrl() != null) {
|
|
||||||
return new RequestMappingInfo(
|
|
||||||
new PatternsRequestCondition(new String[] {getJwkPublishUrl()}, getUrlPathHelper(), getPathMatcher(), false, false),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the jwkPublishUrl
|
|
||||||
*/
|
|
||||||
public String getJwkPublishUrl() {
|
|
||||||
return jwkPublishUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param jwkPublishUrl the jwkPublishUrl to set
|
|
||||||
*/
|
|
||||||
public void setJwkPublishUrl(String jwkPublishUrl) {
|
|
||||||
this.jwkPublishUrl = jwkPublishUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.keypublisher;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.web.servlet.View;
|
|
||||||
import org.springframework.web.servlet.ViewResolver;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Simple view resolver to map JWK view names to appropriate beans
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class JwkViewResolver implements ViewResolver, Ordered {
|
|
||||||
|
|
||||||
private String jwkViewName = "jwkKeyList";
|
|
||||||
private View jwk;
|
|
||||||
|
|
||||||
private int order = HIGHEST_PRECEDENCE; // highest precedence, most specific -- avoids hitting the catch-all view resolvers
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map "jwkKeyList" to the jwk property on this bean.
|
|
||||||
* Everything else returns null
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public View resolveViewName(String viewName, Locale locale) throws Exception {
|
|
||||||
if (viewName != null) {
|
|
||||||
if (viewName.equals(getJwkViewName())) {
|
|
||||||
return getJwk();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the jwk
|
|
||||||
*/
|
|
||||||
public View getJwk() {
|
|
||||||
return jwk;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param jwk the jwk to set
|
|
||||||
*/
|
|
||||||
public void setJwk(View jwk) {
|
|
||||||
this.jwk = jwk;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the order
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int getOrder() {
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param order the order to set
|
|
||||||
*/
|
|
||||||
public void setOrder(int order) {
|
|
||||||
this.order = order;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the jwkViewName
|
|
||||||
*/
|
|
||||||
public String getJwkViewName() {
|
|
||||||
return jwkViewName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param jwkViewName the jwkViewName to set
|
|
||||||
*/
|
|
||||||
public void setJwkViewName(String jwkViewName) {
|
|
||||||
this.jwkViewName = jwkViewName;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Data container to facilitate returns from the IssuerService API.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class IssuerServiceResponse {
|
|
||||||
|
|
||||||
private String issuer;
|
|
||||||
private String loginHint;
|
|
||||||
private String targetLinkUri;
|
|
||||||
private String redirectUrl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param issuer
|
|
||||||
* @param loginHint
|
|
||||||
* @param targetLinkUri
|
|
||||||
*/
|
|
||||||
public IssuerServiceResponse(String issuer, String loginHint, String targetLinkUri) {
|
|
||||||
this.issuer = issuer;
|
|
||||||
this.loginHint = loginHint;
|
|
||||||
this.targetLinkUri = targetLinkUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param redirectUrl
|
|
||||||
*/
|
|
||||||
public IssuerServiceResponse(String redirectUrl) {
|
|
||||||
this.redirectUrl = redirectUrl;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @return the issuer
|
|
||||||
*/
|
|
||||||
public String getIssuer() {
|
|
||||||
return issuer;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param issuer the issuer to set
|
|
||||||
*/
|
|
||||||
public void setIssuer(String issuer) {
|
|
||||||
this.issuer = issuer;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @return the loginHint
|
|
||||||
*/
|
|
||||||
public String getLoginHint() {
|
|
||||||
return loginHint;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param loginHint the loginHint to set
|
|
||||||
*/
|
|
||||||
public void setLoginHint(String loginHint) {
|
|
||||||
this.loginHint = loginHint;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @return the targetLinkUri
|
|
||||||
*/
|
|
||||||
public String getTargetLinkUri() {
|
|
||||||
return targetLinkUri;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param targetLinkUri the targetLinkUri to set
|
|
||||||
*/
|
|
||||||
public void setTargetLinkUri(String targetLinkUri) {
|
|
||||||
this.targetLinkUri = targetLinkUri;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @return the redirectUrl
|
|
||||||
*/
|
|
||||||
public String getRedirectUrl() {
|
|
||||||
return redirectUrl;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param redirectUrl the redirectUrl to set
|
|
||||||
*/
|
|
||||||
public void setRedirectUrl(String redirectUrl) {
|
|
||||||
this.redirectUrl = redirectUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the redirect url has been set, then we should send a redirect using it instead of processing things.
|
|
||||||
*/
|
|
||||||
public boolean shouldRedirect() {
|
|
||||||
return this.redirectUrl != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* This service provides any extra options that need to be passed to the authentication request,
|
|
||||||
* either through the authorization endpoint (getOptions) or the token endpoint (getTokenOptions).
|
|
||||||
* These options may depend on the server configuration, client configuration, or HTTP request.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface AuthRequestOptionsService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The set of options needed at the authorization endpoint.
|
|
||||||
*
|
|
||||||
* @param server
|
|
||||||
* @param client
|
|
||||||
* @param request
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Map<String, String> getOptions(ServerConfiguration server, RegisteredClient client, HttpServletRequest request);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The set of options needed at the token endpoint.
|
|
||||||
*
|
|
||||||
* @param server
|
|
||||||
* @param client
|
|
||||||
* @param request
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Map<String, String> getTokenOptions(ServerConfiguration server, RegisteredClient client, HttpServletRequest request);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a URL string to the IdP's authorization endpoint.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface AuthRequestUrlBuilder {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param serverConfig
|
|
||||||
* @param clientConfig
|
|
||||||
* @param redirectUri
|
|
||||||
* @param nonce
|
|
||||||
* @param state
|
|
||||||
* @param loginHint
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map<String, String> options, String loginHint);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface ClientConfigurationService {
|
|
||||||
|
|
||||||
public RegisteredClient getClientConfiguration(ServerConfiguration issuer);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.mitre.openid.connect.client.model.IssuerServiceResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Gets an issuer for the given request. Might do dynamic discovery, or might be statically configured.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface IssuerService {
|
|
||||||
|
|
||||||
public IssuerServiceResponse getIssuer(HttpServletRequest request);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface RegisteredClientService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a remembered client (if one exists) to talk to the given issuer. This
|
|
||||||
* client likely doesn't have its full configuration information but contains
|
|
||||||
* the information needed to fetch it.
|
|
||||||
* @param issuer
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
RegisteredClient getByIssuer(String issuer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save this client's information for talking to the given issuer. This will
|
|
||||||
* save only enough information to fetch the client's full configuration from
|
|
||||||
* the server.
|
|
||||||
* @param client
|
|
||||||
*/
|
|
||||||
void save(String issuer, RegisteredClient client);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service;
|
|
||||||
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface ServerConfigurationService {
|
|
||||||
|
|
||||||
public ServerConfiguration getServerConfiguration(String issuer);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,247 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor;
|
|
||||||
import org.mitre.openid.connect.client.service.ClientConfigurationService;
|
|
||||||
import org.mitre.openid.connect.client.service.RegisteredClientService;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpEntity;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
|
||||||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
|
||||||
import org.springframework.web.client.RestClientException;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class DynamicRegistrationClientConfigurationService implements ClientConfigurationService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class
|
|
||||||
*/
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(DynamicRegistrationClientConfigurationService.class);
|
|
||||||
|
|
||||||
private LoadingCache<ServerConfiguration, RegisteredClient> clients;
|
|
||||||
|
|
||||||
private RegisteredClientService registeredClientService = new InMemoryRegisteredClientService();
|
|
||||||
|
|
||||||
private RegisteredClient template;
|
|
||||||
|
|
||||||
private Set<String> whitelist = new HashSet<>();
|
|
||||||
private Set<String> blacklist = new HashSet<>();
|
|
||||||
|
|
||||||
public DynamicRegistrationClientConfigurationService() {
|
|
||||||
this(HttpClientBuilder.create().useSystemProperties().build());
|
|
||||||
}
|
|
||||||
|
|
||||||
public DynamicRegistrationClientConfigurationService(HttpClient httpClient) {
|
|
||||||
clients = CacheBuilder.newBuilder().build(new DynamicClientRegistrationLoader(httpClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RegisteredClient getClientConfiguration(ServerConfiguration issuer) {
|
|
||||||
try {
|
|
||||||
if (!whitelist.isEmpty() && !whitelist.contains(issuer.getIssuer())) {
|
|
||||||
throw new AuthenticationServiceException("Whitelist was nonempty, issuer was not in whitelist: " + issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blacklist.contains(issuer.getIssuer())) {
|
|
||||||
throw new AuthenticationServiceException("Issuer was in blacklist: " + issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return clients.get(issuer);
|
|
||||||
} catch (UncheckedExecutionException | ExecutionException e) {
|
|
||||||
logger.warn("Unable to get client configuration", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the template
|
|
||||||
*/
|
|
||||||
public RegisteredClient getTemplate() {
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param template the template to set
|
|
||||||
*/
|
|
||||||
public void setTemplate(RegisteredClient template) {
|
|
||||||
// make sure the template doesn't have unwanted fields set on it
|
|
||||||
if (template != null) {
|
|
||||||
template.setClientId(null);
|
|
||||||
template.setClientSecret(null);
|
|
||||||
template.setRegistrationClientUri(null);
|
|
||||||
template.setRegistrationAccessToken(null);
|
|
||||||
}
|
|
||||||
this.template = template;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the registeredClientService
|
|
||||||
*/
|
|
||||||
public RegisteredClientService getRegisteredClientService() {
|
|
||||||
return registeredClientService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param registeredClientService the registeredClientService to set
|
|
||||||
*/
|
|
||||||
public void setRegisteredClientService(RegisteredClientService registeredClientService) {
|
|
||||||
this.registeredClientService = registeredClientService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the whitelist
|
|
||||||
*/
|
|
||||||
public Set<String> getWhitelist() {
|
|
||||||
return whitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param whitelist the whitelist to set
|
|
||||||
*/
|
|
||||||
public void setWhitelist(Set<String> whitelist) {
|
|
||||||
this.whitelist = whitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the blacklist
|
|
||||||
*/
|
|
||||||
public Set<String> getBlacklist() {
|
|
||||||
return blacklist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param blacklist the blacklist to set
|
|
||||||
*/
|
|
||||||
public void setBlacklist(Set<String> blacklist) {
|
|
||||||
this.blacklist = blacklist;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loader class that fetches the client information.
|
|
||||||
*
|
|
||||||
* If a client has been registered (ie, it's known to the RegisteredClientService), then this
|
|
||||||
* will fetch the client's configuration from the server.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class DynamicClientRegistrationLoader extends CacheLoader<ServerConfiguration, RegisteredClient> {
|
|
||||||
private HttpComponentsClientHttpRequestFactory httpFactory;
|
|
||||||
private Gson gson = new Gson(); // note that this doesn't serialize nulls by default
|
|
||||||
|
|
||||||
public DynamicClientRegistrationLoader() {
|
|
||||||
this(HttpClientBuilder.create().useSystemProperties().build());
|
|
||||||
}
|
|
||||||
|
|
||||||
public DynamicClientRegistrationLoader(HttpClient httpClient) {
|
|
||||||
this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RegisteredClient load(ServerConfiguration serverConfig) throws Exception {
|
|
||||||
RestTemplate restTemplate = new RestTemplate(httpFactory);
|
|
||||||
|
|
||||||
|
|
||||||
RegisteredClient knownClient = registeredClientService.getByIssuer(serverConfig.getIssuer());
|
|
||||||
if (knownClient == null) {
|
|
||||||
|
|
||||||
// dynamically register this client
|
|
||||||
JsonObject jsonRequest = ClientDetailsEntityJsonProcessor.serialize(template);
|
|
||||||
String serializedClient = gson.toJson(jsonRequest);
|
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
||||||
headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON));
|
|
||||||
|
|
||||||
HttpEntity<String> entity = new HttpEntity<>(serializedClient, headers);
|
|
||||||
|
|
||||||
try {
|
|
||||||
String registered = restTemplate.postForObject(serverConfig.getRegistrationEndpointUri(), entity, String.class);
|
|
||||||
|
|
||||||
RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered);
|
|
||||||
|
|
||||||
// save this client for later
|
|
||||||
registeredClientService.save(serverConfig.getIssuer(), client);
|
|
||||||
|
|
||||||
return client;
|
|
||||||
} catch (RestClientException rce) {
|
|
||||||
throw new InvalidClientException("Error registering client with server");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (knownClient.getClientId() == null) {
|
|
||||||
|
|
||||||
// load this client's information from the server
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.set("Authorization", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, knownClient.getRegistrationAccessToken()));
|
|
||||||
headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON));
|
|
||||||
|
|
||||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
|
||||||
|
|
||||||
try {
|
|
||||||
String registered = restTemplate.exchange(knownClient.getRegistrationClientUri(), HttpMethod.GET, entity, String.class).getBody();
|
|
||||||
// TODO: handle HTTP errors
|
|
||||||
|
|
||||||
RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered);
|
|
||||||
|
|
||||||
return client;
|
|
||||||
} catch (RestClientException rce) {
|
|
||||||
throw new InvalidClientException("Error loading previously registered client information from server");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// it's got a client ID from the store, don't bother trying to load it
|
|
||||||
return knownClient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,215 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import static org.mitre.util.JsonUtils.getAsBoolean;
|
|
||||||
import static org.mitre.util.JsonUtils.getAsEncryptionMethodList;
|
|
||||||
import static org.mitre.util.JsonUtils.getAsJweAlgorithmList;
|
|
||||||
import static org.mitre.util.JsonUtils.getAsJwsAlgorithmList;
|
|
||||||
import static org.mitre.util.JsonUtils.getAsString;
|
|
||||||
import static org.mitre.util.JsonUtils.getAsStringList;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
|
||||||
import org.mitre.openid.connect.client.service.ServerConfigurationService;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Dynamically fetches OpenID Connect server configurations based on the issuer. Caches the server configurations.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class DynamicServerConfigurationService implements ServerConfigurationService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class
|
|
||||||
*/
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(DynamicServerConfigurationService.class);
|
|
||||||
|
|
||||||
// map of issuer -> server configuration, loaded dynamically from service discovery
|
|
||||||
private LoadingCache<String, ServerConfiguration> servers;
|
|
||||||
|
|
||||||
private Set<String> whitelist = new HashSet<>();
|
|
||||||
private Set<String> blacklist = new HashSet<>();
|
|
||||||
|
|
||||||
public DynamicServerConfigurationService() {
|
|
||||||
this(HttpClientBuilder.create().useSystemProperties().build());
|
|
||||||
}
|
|
||||||
|
|
||||||
public DynamicServerConfigurationService(HttpClient httpClient) {
|
|
||||||
// initialize the cache
|
|
||||||
servers = CacheBuilder.newBuilder().build(new OpenIDConnectServiceConfigurationFetcher(httpClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the whitelist
|
|
||||||
*/
|
|
||||||
public Set<String> getWhitelist() {
|
|
||||||
return whitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param whitelist the whitelist to set
|
|
||||||
*/
|
|
||||||
public void setWhitelist(Set<String> whitelist) {
|
|
||||||
this.whitelist = whitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the blacklist
|
|
||||||
*/
|
|
||||||
public Set<String> getBlacklist() {
|
|
||||||
return blacklist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param blacklist the blacklist to set
|
|
||||||
*/
|
|
||||||
public void setBlacklist(Set<String> blacklist) {
|
|
||||||
this.blacklist = blacklist;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ServerConfiguration getServerConfiguration(String issuer) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
if (!whitelist.isEmpty() && !whitelist.contains(issuer)) {
|
|
||||||
throw new AuthenticationServiceException("Whitelist was nonempty, issuer was not in whitelist: " + issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blacklist.contains(issuer)) {
|
|
||||||
throw new AuthenticationServiceException("Issuer was in blacklist: " + issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return servers.get(issuer);
|
|
||||||
} catch (UncheckedExecutionException | ExecutionException e) {
|
|
||||||
logger.warn("Couldn't load configuration for " + issuer + ": " + e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private class OpenIDConnectServiceConfigurationFetcher extends CacheLoader<String, ServerConfiguration> {
|
|
||||||
private HttpComponentsClientHttpRequestFactory httpFactory;
|
|
||||||
private JsonParser parser = new JsonParser();
|
|
||||||
|
|
||||||
OpenIDConnectServiceConfigurationFetcher(HttpClient httpClient) {
|
|
||||||
this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ServerConfiguration load(String issuer) throws Exception {
|
|
||||||
RestTemplate restTemplate = new RestTemplate(httpFactory);
|
|
||||||
|
|
||||||
// data holder
|
|
||||||
ServerConfiguration conf = new ServerConfiguration();
|
|
||||||
|
|
||||||
// construct the well-known URI
|
|
||||||
String url = issuer + "/.well-known/openid-configuration";
|
|
||||||
|
|
||||||
// fetch the value
|
|
||||||
String jsonString = restTemplate.getForObject(url, String.class);
|
|
||||||
|
|
||||||
JsonElement parsed = parser.parse(jsonString);
|
|
||||||
if (parsed.isJsonObject()) {
|
|
||||||
|
|
||||||
JsonObject o = parsed.getAsJsonObject();
|
|
||||||
|
|
||||||
// sanity checks
|
|
||||||
if (!o.has("issuer")) {
|
|
||||||
throw new IllegalStateException("Returned object did not have an 'issuer' field");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!issuer.equals(o.get("issuer").getAsString())) {
|
|
||||||
logger.info("Issuer used for discover was " + issuer + " but final issuer is " + o.get("issuer").getAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.setIssuer(o.get("issuer").getAsString());
|
|
||||||
|
|
||||||
|
|
||||||
conf.setAuthorizationEndpointUri(getAsString(o, "authorization_endpoint"));
|
|
||||||
conf.setTokenEndpointUri(getAsString(o, "token_endpoint"));
|
|
||||||
conf.setJwksUri(getAsString(o, "jwks_uri"));
|
|
||||||
conf.setUserInfoUri(getAsString(o, "userinfo_endpoint"));
|
|
||||||
conf.setRegistrationEndpointUri(getAsString(o, "registration_endpoint"));
|
|
||||||
conf.setIntrospectionEndpointUri(getAsString(o, "introspection_endpoint"));
|
|
||||||
conf.setAcrValuesSupported(getAsStringList(o, "acr_values_supported"));
|
|
||||||
conf.setCheckSessionIframe(getAsString(o, "check_session_iframe"));
|
|
||||||
conf.setClaimsLocalesSupported(getAsStringList(o, "claims_locales_supported"));
|
|
||||||
conf.setClaimsParameterSupported(getAsBoolean(o, "claims_parameter_supported"));
|
|
||||||
conf.setClaimsSupported(getAsStringList(o, "claims_supported"));
|
|
||||||
conf.setDisplayValuesSupported(getAsStringList(o, "display_values_supported"));
|
|
||||||
conf.setEndSessionEndpoint(getAsString(o, "end_session_endpoint"));
|
|
||||||
conf.setGrantTypesSupported(getAsStringList(o, "grant_types_supported"));
|
|
||||||
conf.setIdTokenSigningAlgValuesSupported(getAsJwsAlgorithmList(o, "id_token_signing_alg_values_supported"));
|
|
||||||
conf.setIdTokenEncryptionAlgValuesSupported(getAsJweAlgorithmList(o, "id_token_encryption_alg_values_supported"));
|
|
||||||
conf.setIdTokenEncryptionEncValuesSupported(getAsEncryptionMethodList(o, "id_token_encryption_enc_values_supported"));
|
|
||||||
conf.setOpPolicyUri(getAsString(o, "op_policy_uri"));
|
|
||||||
conf.setOpTosUri(getAsString(o, "op_tos_uri"));
|
|
||||||
conf.setRequestObjectEncryptionAlgValuesSupported(getAsJweAlgorithmList(o, "request_object_encryption_alg_values_supported"));
|
|
||||||
conf.setRequestObjectEncryptionEncValuesSupported(getAsEncryptionMethodList(o, "request_object_encryption_enc_values_supported"));
|
|
||||||
conf.setRequestObjectSigningAlgValuesSupported(getAsJwsAlgorithmList(o, "request_object_signing_alg_values_supported"));
|
|
||||||
conf.setRequestParameterSupported(getAsBoolean(o, "request_parameter_supported"));
|
|
||||||
conf.setRequestUriParameterSupported(getAsBoolean(o, "request_uri_parameter_supported"));
|
|
||||||
conf.setResponseTypesSupported(getAsStringList(o, "response_types_supported"));
|
|
||||||
conf.setScopesSupported(getAsStringList(o, "scopes_supported"));
|
|
||||||
conf.setSubjectTypesSupported(getAsStringList(o, "subject_types_supported"));
|
|
||||||
conf.setServiceDocumentation(getAsString(o, "service_documentation"));
|
|
||||||
conf.setTokenEndpointAuthMethodsSupported(getAsStringList(o, "token_endpoint_auth_methods"));
|
|
||||||
conf.setTokenEndpointAuthSigningAlgValuesSupported(getAsJwsAlgorithmList(o, "token_endpoint_auth_signing_alg_values_supported"));
|
|
||||||
conf.setUiLocalesSupported(getAsStringList(o, "ui_locales_supported"));
|
|
||||||
conf.setUserinfoEncryptionAlgValuesSupported(getAsJweAlgorithmList(o, "userinfo_encryption_alg_values_supported"));
|
|
||||||
conf.setUserinfoEncryptionEncValuesSupported(getAsEncryptionMethodList(o, "userinfo_encryption_enc_values_supported"));
|
|
||||||
conf.setUserinfoSigningAlgValuesSupported(getAsJwsAlgorithmList(o, "userinfo_signing_alg_values_supported"));
|
|
||||||
|
|
||||||
return conf;
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Couldn't parse server discovery results for " + url);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,147 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
|
||||||
import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService;
|
|
||||||
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.client.service.AuthRequestUrlBuilder;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.nimbusds.jose.EncryptionMethod;
|
|
||||||
import com.nimbusds.jose.JWEAlgorithm;
|
|
||||||
import com.nimbusds.jose.JWEHeader;
|
|
||||||
import com.nimbusds.jwt.EncryptedJWT;
|
|
||||||
import com.nimbusds.jwt.JWTClaimsSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class EncryptedAuthRequestUrlBuilder implements AuthRequestUrlBuilder {
|
|
||||||
|
|
||||||
private JWKSetCacheService encrypterService;
|
|
||||||
|
|
||||||
private JWEAlgorithm alg;
|
|
||||||
private EncryptionMethod enc;
|
|
||||||
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.AuthRequestUrlBuilder#buildAuthRequestUrl(org.mitre.openid.connect.config.ServerConfiguration, org.mitre.oauth2.model.RegisteredClient, java.lang.String, java.lang.String, java.lang.String, java.util.Map)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map<String, String> options, String loginHint) {
|
|
||||||
|
|
||||||
// create our signed JWT for the request object
|
|
||||||
JWTClaimsSet.Builder claims = new JWTClaimsSet.Builder();
|
|
||||||
|
|
||||||
//set parameters to JwtClaims
|
|
||||||
claims.claim("response_type", "code");
|
|
||||||
claims.claim("client_id", clientConfig.getClientId());
|
|
||||||
claims.claim("scope", Joiner.on(" ").join(clientConfig.getScope()));
|
|
||||||
|
|
||||||
// build our redirect URI
|
|
||||||
claims.claim("redirect_uri", redirectUri);
|
|
||||||
|
|
||||||
// this comes back in the id token
|
|
||||||
claims.claim("nonce", nonce);
|
|
||||||
|
|
||||||
// this comes back in the auth request return
|
|
||||||
claims.claim("state", state);
|
|
||||||
|
|
||||||
// Optional parameters
|
|
||||||
for (Entry<String, String> option : options.entrySet()) {
|
|
||||||
claims.claim(option.getKey(), option.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there's a login hint, send it
|
|
||||||
if (!Strings.isNullOrEmpty(loginHint)) {
|
|
||||||
claims.claim("login_hint", loginHint);
|
|
||||||
}
|
|
||||||
|
|
||||||
EncryptedJWT jwt = new EncryptedJWT(new JWEHeader(alg, enc), claims.build());
|
|
||||||
|
|
||||||
JWTEncryptionAndDecryptionService encryptor = encrypterService.getEncrypter(serverConfig.getJwksUri());
|
|
||||||
|
|
||||||
encryptor.encryptJwt(jwt);
|
|
||||||
|
|
||||||
try {
|
|
||||||
URIBuilder uriBuilder = new URIBuilder(serverConfig.getAuthorizationEndpointUri());
|
|
||||||
uriBuilder.addParameter("request", jwt.serialize());
|
|
||||||
|
|
||||||
// build out the URI
|
|
||||||
return uriBuilder.build().toString();
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new AuthenticationServiceException("Malformed Authorization Endpoint Uri", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the encrypterService
|
|
||||||
*/
|
|
||||||
public JWKSetCacheService getEncrypterService() {
|
|
||||||
return encrypterService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param encrypterService the encrypterService to set
|
|
||||||
*/
|
|
||||||
public void setEncrypterService(JWKSetCacheService encrypterService) {
|
|
||||||
this.encrypterService = encrypterService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the alg
|
|
||||||
*/
|
|
||||||
public JWEAlgorithm getAlg() {
|
|
||||||
return alg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param alg the alg to set
|
|
||||||
*/
|
|
||||||
public void setAlg(JWEAlgorithm alg) {
|
|
||||||
this.alg = alg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the enc
|
|
||||||
*/
|
|
||||||
public EncryptionMethod getEnc() {
|
|
||||||
return enc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param enc the enc to set
|
|
||||||
*/
|
|
||||||
public void setEnc(EncryptionMethod enc) {
|
|
||||||
this.enc = enc;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.client.service.ClientConfigurationService;
|
|
||||||
import org.mitre.openid.connect.client.service.RegisteredClientService;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Houses both a static client configuration and a dynamic client configuration
|
|
||||||
* service in one object. Checks the static service first, then falls through to
|
|
||||||
* the dynamic service.
|
|
||||||
*
|
|
||||||
* Provides configuration passthrough for the template, registered client service, whitelist,
|
|
||||||
* and blacklist for the dynamic service, and to the static service's client map.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class HybridClientConfigurationService implements ClientConfigurationService {
|
|
||||||
|
|
||||||
private StaticClientConfigurationService staticClientService = new StaticClientConfigurationService();
|
|
||||||
|
|
||||||
private DynamicRegistrationClientConfigurationService dynamicClientService = new DynamicRegistrationClientConfigurationService();
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.ClientConfigurationService#getClientConfiguration(org.mitre.openid.connect.config.ServerConfiguration)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public RegisteredClient getClientConfiguration(ServerConfiguration issuer) {
|
|
||||||
|
|
||||||
RegisteredClient client = staticClientService.getClientConfiguration(issuer);
|
|
||||||
if (client != null) {
|
|
||||||
return client;
|
|
||||||
} else {
|
|
||||||
return dynamicClientService.getClientConfiguration(issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.StaticClientConfigurationService#getClients()
|
|
||||||
*/
|
|
||||||
public Map<String, RegisteredClient> getClients() {
|
|
||||||
return staticClientService.getClients();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param clients
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.StaticClientConfigurationService#setClients(java.util.Map)
|
|
||||||
*/
|
|
||||||
public void setClients(Map<String, RegisteredClient> clients) {
|
|
||||||
staticClientService.setClients(clients);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.DynamicRegistrationClientConfigurationService#getTemplate()
|
|
||||||
*/
|
|
||||||
public RegisteredClient getTemplate() {
|
|
||||||
return dynamicClientService.getTemplate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param template
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.DynamicRegistrationClientConfigurationService#setTemplate(org.mitre.oauth2.model.RegisteredClient)
|
|
||||||
*/
|
|
||||||
public void setTemplate(RegisteredClient template) {
|
|
||||||
dynamicClientService.setTemplate(template);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.DynamicRegistrationClientConfigurationService#getRegisteredClientService()
|
|
||||||
*/
|
|
||||||
public RegisteredClientService getRegisteredClientService() {
|
|
||||||
return dynamicClientService.getRegisteredClientService();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param registeredClientService
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.DynamicRegistrationClientConfigurationService#setRegisteredClientService(org.mitre.openid.connect.client.service.RegisteredClientService)
|
|
||||||
*/
|
|
||||||
public void setRegisteredClientService(RegisteredClientService registeredClientService) {
|
|
||||||
dynamicClientService.setRegisteredClientService(registeredClientService);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.DynamicRegistrationClientConfigurationService#getWhitelist()
|
|
||||||
*/
|
|
||||||
public Set<String> getWhitelist() {
|
|
||||||
return dynamicClientService.getWhitelist();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param whitelist
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.DynamicRegistrationClientConfigurationService#setWhitelist(java.util.Set)
|
|
||||||
*/
|
|
||||||
public void setWhitelist(Set<String> whitelist) {
|
|
||||||
dynamicClientService.setWhitelist(whitelist);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.DynamicRegistrationClientConfigurationService#getBlacklist()
|
|
||||||
*/
|
|
||||||
public Set<String> getBlacklist() {
|
|
||||||
return dynamicClientService.getBlacklist();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param blacklist
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.DynamicRegistrationClientConfigurationService#setBlacklist(java.util.Set)
|
|
||||||
*/
|
|
||||||
public void setBlacklist(Set<String> blacklist) {
|
|
||||||
dynamicClientService.setBlacklist(blacklist);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,124 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.mitre.openid.connect.client.model.IssuerServiceResponse;
|
|
||||||
import org.mitre.openid.connect.client.service.IssuerService;
|
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Issuer service that tries to parse input from the inputs from a third-party
|
|
||||||
* account chooser service (if possible), but falls back to webfinger discovery
|
|
||||||
* if not.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class HybridIssuerService implements IssuerService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.ThirdPartyIssuerService#getAccountChooserUrl()
|
|
||||||
*/
|
|
||||||
public String getAccountChooserUrl() {
|
|
||||||
return thirdPartyIssuerService.getAccountChooserUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param accountChooserUrl
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.ThirdPartyIssuerService#setAccountChooserUrl(java.lang.String)
|
|
||||||
*/
|
|
||||||
public void setAccountChooserUrl(String accountChooserUrl) {
|
|
||||||
thirdPartyIssuerService.setAccountChooserUrl(accountChooserUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.WebfingerIssuerService#isForceHttps()
|
|
||||||
*/
|
|
||||||
public boolean isForceHttps() {
|
|
||||||
return webfingerIssuerService.isForceHttps();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param forceHttps
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.WebfingerIssuerService#setForceHttps(boolean)
|
|
||||||
*/
|
|
||||||
public void setForceHttps(boolean forceHttps) {
|
|
||||||
webfingerIssuerService.setForceHttps(forceHttps);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ThirdPartyIssuerService thirdPartyIssuerService = new ThirdPartyIssuerService();
|
|
||||||
private WebfingerIssuerService webfingerIssuerService = new WebfingerIssuerService();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IssuerServiceResponse getIssuer(HttpServletRequest request) {
|
|
||||||
|
|
||||||
IssuerServiceResponse resp = thirdPartyIssuerService.getIssuer(request);
|
|
||||||
if (resp.shouldRedirect()) {
|
|
||||||
// if it wants us to redirect, try the webfinger approach first
|
|
||||||
return webfingerIssuerService.getIssuer(request);
|
|
||||||
} else {
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getWhitelist() {
|
|
||||||
return Sets.union(thirdPartyIssuerService.getWhitelist(), webfingerIssuerService.getWhitelist());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWhitelist(Set<String> whitelist) {
|
|
||||||
thirdPartyIssuerService.setWhitelist(whitelist);
|
|
||||||
webfingerIssuerService.setWhitelist(whitelist);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getBlacklist() {
|
|
||||||
return Sets.union(thirdPartyIssuerService.getBlacklist(), webfingerIssuerService.getWhitelist());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBlacklist(Set<String> blacklist) {
|
|
||||||
thirdPartyIssuerService.setBlacklist(blacklist);
|
|
||||||
webfingerIssuerService.setBlacklist(blacklist);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getParameterName() {
|
|
||||||
return webfingerIssuerService.getParameterName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setParameterName(String parameterName) {
|
|
||||||
webfingerIssuerService.setParameterName(parameterName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLoginPageUrl() {
|
|
||||||
return webfingerIssuerService.getLoginPageUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLoginPageUrl(String loginPageUrl) {
|
|
||||||
webfingerIssuerService.setLoginPageUrl(loginPageUrl);
|
|
||||||
thirdPartyIssuerService.setAccountChooserUrl(loginPageUrl); // set the same URL on both, but this one gets ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.mitre.openid.connect.client.service.ServerConfigurationService;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Houses both a static server configuration and a dynamic server configuration
|
|
||||||
* service in one object. Checks the static service first, then falls through to
|
|
||||||
* the dynamic service.
|
|
||||||
*
|
|
||||||
* Provides configuration passthrough to the dynamic service's whitelist and blacklist,
|
|
||||||
* and to the static service's server map.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class HybridServerConfigurationService implements ServerConfigurationService {
|
|
||||||
|
|
||||||
private StaticServerConfigurationService staticServerService = new StaticServerConfigurationService();
|
|
||||||
|
|
||||||
private DynamicServerConfigurationService dynamicServerService = new DynamicServerConfigurationService();
|
|
||||||
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.ServerConfigurationService#getServerConfiguration(java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public ServerConfiguration getServerConfiguration(String issuer) {
|
|
||||||
ServerConfiguration server = staticServerService.getServerConfiguration(issuer);
|
|
||||||
if (server != null) {
|
|
||||||
return server;
|
|
||||||
} else {
|
|
||||||
return dynamicServerService.getServerConfiguration(issuer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.StaticServerConfigurationService#getServers()
|
|
||||||
*/
|
|
||||||
public Map<String, ServerConfiguration> getServers() {
|
|
||||||
return staticServerService.getServers();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param servers
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.StaticServerConfigurationService#setServers(java.util.Map)
|
|
||||||
*/
|
|
||||||
public void setServers(Map<String, ServerConfiguration> servers) {
|
|
||||||
staticServerService.setServers(servers);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.DynamicServerConfigurationService#getWhitelist()
|
|
||||||
*/
|
|
||||||
public Set<String> getWhitelist() {
|
|
||||||
return dynamicServerService.getWhitelist();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param whitelist
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.DynamicServerConfigurationService#setWhitelist(java.util.Set)
|
|
||||||
*/
|
|
||||||
public void setWhitelist(Set<String> whitelist) {
|
|
||||||
dynamicServerService.setWhitelist(whitelist);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.DynamicServerConfigurationService#getBlacklist()
|
|
||||||
*/
|
|
||||||
public Set<String> getBlacklist() {
|
|
||||||
return dynamicServerService.getBlacklist();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param blacklist
|
|
||||||
* @see org.mitre.openid.connect.client.service.impl.DynamicServerConfigurationService#setBlacklist(java.util.Set)
|
|
||||||
*/
|
|
||||||
public void setBlacklist(Set<String> blacklist) {
|
|
||||||
dynamicServerService.setBlacklist(blacklist);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.client.service.RegisteredClientService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class InMemoryRegisteredClientService implements RegisteredClientService {
|
|
||||||
|
|
||||||
private Map<String, RegisteredClient> clients = new HashMap<>();
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.RegisteredClientService#getByIssuer(java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public RegisteredClient getByIssuer(String issuer) {
|
|
||||||
return clients.get(issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.RegisteredClientService#save(org.mitre.oauth2.model.RegisteredClient)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void save(String issuer, RegisteredClient client) {
|
|
||||||
clients.put(issuer, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor;
|
|
||||||
import org.mitre.openid.connect.client.service.RegisteredClientService;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.google.common.reflect.TypeToken;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.google.gson.JsonDeserializationContext;
|
|
||||||
import com.google.gson.JsonDeserializer;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
import com.google.gson.JsonSerializationContext;
|
|
||||||
import com.google.gson.JsonSerializer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class JsonFileRegisteredClientService implements RegisteredClientService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class
|
|
||||||
*/
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(JsonFileRegisteredClientService.class);
|
|
||||||
|
|
||||||
private Gson gson = new GsonBuilder()
|
|
||||||
.registerTypeAdapter(RegisteredClient.class, new JsonSerializer<RegisteredClient>() {
|
|
||||||
@Override
|
|
||||||
public JsonElement serialize(RegisteredClient src, Type typeOfSrc, JsonSerializationContext context) {
|
|
||||||
return ClientDetailsEntityJsonProcessor.serialize(src);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.registerTypeAdapter(RegisteredClient.class, new JsonDeserializer<RegisteredClient>() {
|
|
||||||
@Override
|
|
||||||
public RegisteredClient deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
|
||||||
return ClientDetailsEntityJsonProcessor.parseRegistered(json);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setPrettyPrinting()
|
|
||||||
.create();
|
|
||||||
|
|
||||||
private File file;
|
|
||||||
|
|
||||||
private Map<String, RegisteredClient> clients = new HashMap<>();
|
|
||||||
|
|
||||||
public JsonFileRegisteredClientService(String filename) {
|
|
||||||
this.file = new File(filename);
|
|
||||||
load();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.RegisteredClientService#getByIssuer(java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public RegisteredClient getByIssuer(String issuer) {
|
|
||||||
return clients.get(issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.RegisteredClientService#save(java.lang.String, org.mitre.oauth2.model.RegisteredClient)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void save(String issuer, RegisteredClient client) {
|
|
||||||
clients.put(issuer, client);
|
|
||||||
write();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sync the map of clients out to disk.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
private void write() {
|
|
||||||
try {
|
|
||||||
if (!file.exists()) {
|
|
||||||
// create a new file
|
|
||||||
logger.info("Creating saved clients list in " + file);
|
|
||||||
file.createNewFile();
|
|
||||||
}
|
|
||||||
FileWriter out = new FileWriter(file);
|
|
||||||
|
|
||||||
gson.toJson(clients, new TypeToken<Map<String, RegisteredClient>>(){}.getType(), out);
|
|
||||||
|
|
||||||
out.close();
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Could not write to output file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the map in from disk.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
private void load() {
|
|
||||||
try {
|
|
||||||
if (!file.exists()) {
|
|
||||||
logger.info("No sved clients file found in " + file);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FileReader in = new FileReader(file);
|
|
||||||
|
|
||||||
clients = gson.fromJson(in, new TypeToken<Map<String, RegisteredClient>>(){}.getType());
|
|
||||||
|
|
||||||
in.close();
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Could not read from input file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.client.service.AuthRequestUrlBuilder;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Builds an auth request redirect URI with normal query parameters.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class PlainAuthRequestUrlBuilder implements AuthRequestUrlBuilder {
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.AuthRequestUrlBuilder#buildAuthRequest(javax.servlet.http.HttpServletRequest, org.mitre.openid.connect.config.ServerConfiguration, org.springframework.security.oauth2.provider.ClientDetails)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map<String, String> options, String loginHint) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
URIBuilder uriBuilder = new URIBuilder(serverConfig.getAuthorizationEndpointUri());
|
|
||||||
uriBuilder.addParameter("response_type", "code");
|
|
||||||
uriBuilder.addParameter("client_id", clientConfig.getClientId());
|
|
||||||
uriBuilder.addParameter("scope", Joiner.on(" ").join(clientConfig.getScope()));
|
|
||||||
|
|
||||||
uriBuilder.addParameter("redirect_uri", redirectUri);
|
|
||||||
|
|
||||||
uriBuilder.addParameter("nonce", nonce);
|
|
||||||
|
|
||||||
uriBuilder.addParameter("state", state);
|
|
||||||
|
|
||||||
// Optional parameters:
|
|
||||||
for (Entry<String, String> option : options.entrySet()) {
|
|
||||||
uriBuilder.addParameter(option.getKey(), option.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there's a login hint, send it
|
|
||||||
if (!Strings.isNullOrEmpty(loginHint)) {
|
|
||||||
uriBuilder.addParameter("login_hint", loginHint);
|
|
||||||
}
|
|
||||||
|
|
||||||
return uriBuilder.build().toString();
|
|
||||||
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new AuthenticationServiceException("Malformed Authorization Endpoint Uri", e);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
|
||||||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.client.service.AuthRequestUrlBuilder;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.nimbusds.jose.JWSAlgorithm;
|
|
||||||
import com.nimbusds.jose.JWSHeader;
|
|
||||||
import com.nimbusds.jwt.JWTClaimsSet;
|
|
||||||
import com.nimbusds.jwt.SignedJWT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class SignedAuthRequestUrlBuilder implements AuthRequestUrlBuilder {
|
|
||||||
|
|
||||||
private JWTSigningAndValidationService signingAndValidationService;
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.AuthRequestUrlBuilder#buildAuthRequestUrl(org.mitre.openid.connect.config.ServerConfiguration, org.springframework.security.oauth2.provider.ClientDetails, java.lang.String, java.lang.String, java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map<String, String> options, String loginHint) {
|
|
||||||
|
|
||||||
// create our signed JWT for the request object
|
|
||||||
JWTClaimsSet.Builder claims = new JWTClaimsSet.Builder();
|
|
||||||
|
|
||||||
//set parameters to JwtClaims
|
|
||||||
claims.claim("response_type", "code");
|
|
||||||
claims.claim("client_id", clientConfig.getClientId());
|
|
||||||
claims.claim("scope", Joiner.on(" ").join(clientConfig.getScope()));
|
|
||||||
|
|
||||||
// build our redirect URI
|
|
||||||
claims.claim("redirect_uri", redirectUri);
|
|
||||||
|
|
||||||
// this comes back in the id token
|
|
||||||
claims.claim("nonce", nonce);
|
|
||||||
|
|
||||||
// this comes back in the auth request return
|
|
||||||
claims.claim("state", state);
|
|
||||||
|
|
||||||
// Optional parameters
|
|
||||||
for (Entry<String, String> option : options.entrySet()) {
|
|
||||||
claims.claim(option.getKey(), option.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there's a login hint, send it
|
|
||||||
if (!Strings.isNullOrEmpty(loginHint)) {
|
|
||||||
claims.claim("login_hint", loginHint);
|
|
||||||
}
|
|
||||||
|
|
||||||
JWSAlgorithm alg = clientConfig.getRequestObjectSigningAlg();
|
|
||||||
if (alg == null) {
|
|
||||||
alg = signingAndValidationService.getDefaultSigningAlgorithm();
|
|
||||||
}
|
|
||||||
|
|
||||||
SignedJWT jwt = new SignedJWT(new JWSHeader(alg), claims.build());
|
|
||||||
|
|
||||||
signingAndValidationService.signJwt(jwt, alg);
|
|
||||||
|
|
||||||
try {
|
|
||||||
URIBuilder uriBuilder = new URIBuilder(serverConfig.getAuthorizationEndpointUri());
|
|
||||||
uriBuilder.addParameter("request", jwt.serialize());
|
|
||||||
|
|
||||||
// build out the URI
|
|
||||||
return uriBuilder.build().toString();
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new AuthenticationServiceException("Malformed Authorization Endpoint Uri", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the signingAndValidationService
|
|
||||||
*/
|
|
||||||
public JWTSigningAndValidationService getSigningAndValidationService() {
|
|
||||||
return signingAndValidationService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param signingAndValidationService the signingAndValidationService to set
|
|
||||||
*/
|
|
||||||
public void setSigningAndValidationService(JWTSigningAndValidationService signingAndValidationService) {
|
|
||||||
this.signingAndValidationService = signingAndValidationService;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.client.service.AuthRequestOptionsService;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Always returns the same set of options.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class StaticAuthRequestOptionsService implements AuthRequestOptionsService {
|
|
||||||
|
|
||||||
private Map<String, String> options = new HashMap<>();
|
|
||||||
private Map<String, String> tokenOptions = new HashMap<>();
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.AuthRequestOptionsService#getOptions(org.mitre.openid.connect.config.ServerConfiguration, org.mitre.oauth2.model.RegisteredClient, javax.servlet.http.HttpServletRequest)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Map<String, String> getOptions(ServerConfiguration server, RegisteredClient client, HttpServletRequest request) {
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.AuthRequestOptionsService#getTokenOptions(org.mitre.openid.connect.config.ServerConfiguration, org.mitre.oauth2.model.RegisteredClient, javax.servlet.http.HttpServletRequest)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Map<String, String> getTokenOptions(ServerConfiguration server, RegisteredClient client, HttpServletRequest request) {
|
|
||||||
return tokenOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the options object directly
|
|
||||||
*/
|
|
||||||
public Map<String, String> getOptions() {
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param options the options to set
|
|
||||||
*/
|
|
||||||
public void setOptions(Map<String, String> options) {
|
|
||||||
this.options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the tokenOptions
|
|
||||||
*/
|
|
||||||
public Map<String, String> getTokenOptions() {
|
|
||||||
return tokenOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param tokenOptions the tokenOptions to set
|
|
||||||
*/
|
|
||||||
public void setTokenOptions(Map<String, String> tokenOptions) {
|
|
||||||
this.tokenOptions = tokenOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.client.service.ClientConfigurationService;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client configuration service that holds a static map from issuer URL to a ClientDetails object to use at that issuer.
|
|
||||||
*
|
|
||||||
* Designed to be configured as a bean.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class StaticClientConfigurationService implements ClientConfigurationService {
|
|
||||||
|
|
||||||
// Map of issuer URL -> client configuration information
|
|
||||||
private Map<String, RegisteredClient> clients;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the clients
|
|
||||||
*/
|
|
||||||
public Map<String, RegisteredClient> getClients() {
|
|
||||||
return clients;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param clients the clients to set
|
|
||||||
*/
|
|
||||||
public void setClients(Map<String, RegisteredClient> clients) {
|
|
||||||
this.clients = clients;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the client configured for this issuer
|
|
||||||
*
|
|
||||||
* @see org.mitre.openid.connect.client.service.ClientConfigurationService#getClientConfiguration(java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public RegisteredClient getClientConfiguration(ServerConfiguration issuer) {
|
|
||||||
|
|
||||||
return clients.get(issuer.getIssuer());
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void afterPropertiesSet() {
|
|
||||||
if (clients == null || clients.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Clients map cannot be null or empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
|
|
||||||
import org.mitre.openid.connect.client.service.ServerConfigurationService;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Statically configured server configuration service that maps issuer URLs to server configurations to use at that issuer.
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class StaticServerConfigurationService implements ServerConfigurationService {
|
|
||||||
|
|
||||||
// map of issuer url -> server configuration information
|
|
||||||
private Map<String, ServerConfiguration> servers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the servers
|
|
||||||
*/
|
|
||||||
public Map<String, ServerConfiguration> getServers() {
|
|
||||||
return servers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param servers the servers to set
|
|
||||||
*/
|
|
||||||
public void setServers(Map<String, ServerConfiguration> servers) {
|
|
||||||
this.servers = servers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.ServerConfigurationService#getServerConfiguration(java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public ServerConfiguration getServerConfiguration(String issuer) {
|
|
||||||
return servers.get(issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void afterPropertiesSet() {
|
|
||||||
if (servers == null || servers.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Servers map cannot be null or empty.");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.mitre.openid.connect.client.model.IssuerServiceResponse;
|
|
||||||
import org.mitre.openid.connect.client.service.IssuerService;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class StaticSingleIssuerService implements IssuerService {
|
|
||||||
|
|
||||||
private String issuer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the issuer
|
|
||||||
*/
|
|
||||||
public String getIssuer() {
|
|
||||||
return issuer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param issuer the issuer to set
|
|
||||||
*/
|
|
||||||
public void setIssuer(String issuer) {
|
|
||||||
this.issuer = issuer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Always returns the configured issuer URL
|
|
||||||
*
|
|
||||||
* @see org.mitre.openid.connect.client.service.IssuerService#getIssuer(javax.servlet.http.HttpServletRequest)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public IssuerServiceResponse getIssuer(HttpServletRequest request) {
|
|
||||||
return new IssuerServiceResponse(getIssuer(), null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void afterPropertiesSet() {
|
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(issuer)) {
|
|
||||||
throw new IllegalArgumentException("Issuer must not be null or empty.");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,139 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
|
||||||
import org.mitre.openid.connect.client.model.IssuerServiceResponse;
|
|
||||||
import org.mitre.openid.connect.client.service.IssuerService;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Determines the issuer using an account chooser or other third-party-initiated login
|
|
||||||
*
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class ThirdPartyIssuerService implements IssuerService {
|
|
||||||
|
|
||||||
private String accountChooserUrl;
|
|
||||||
|
|
||||||
private Set<String> whitelist = new HashSet<>();
|
|
||||||
private Set<String> blacklist = new HashSet<>();
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.IssuerService#getIssuer(javax.servlet.http.HttpServletRequest)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public IssuerServiceResponse getIssuer(HttpServletRequest request) {
|
|
||||||
|
|
||||||
// if the issuer is passed in, return that
|
|
||||||
String iss = request.getParameter("iss");
|
|
||||||
if (!Strings.isNullOrEmpty(iss)) {
|
|
||||||
if (!whitelist.isEmpty() && !whitelist.contains(iss)) {
|
|
||||||
throw new AuthenticationServiceException("Whitelist was nonempty, issuer was not in whitelist: " + iss);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blacklist.contains(iss)) {
|
|
||||||
throw new AuthenticationServiceException("Issuer was in blacklist: " + iss);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IssuerServiceResponse(iss, request.getParameter("login_hint"), request.getParameter("target_link_uri"));
|
|
||||||
} else {
|
|
||||||
|
|
||||||
try {
|
|
||||||
// otherwise, need to forward to the account chooser
|
|
||||||
String redirectUri = request.getRequestURL().toString();
|
|
||||||
URIBuilder builder = new URIBuilder(accountChooserUrl);
|
|
||||||
|
|
||||||
builder.addParameter("redirect_uri", redirectUri);
|
|
||||||
|
|
||||||
return new IssuerServiceResponse(builder.build().toString());
|
|
||||||
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new AuthenticationServiceException("Account Chooser URL is not valid", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the accountChooserUrl
|
|
||||||
*/
|
|
||||||
public String getAccountChooserUrl() {
|
|
||||||
return accountChooserUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param accountChooserUrl the accountChooserUrl to set
|
|
||||||
*/
|
|
||||||
public void setAccountChooserUrl(String accountChooserUrl) {
|
|
||||||
this.accountChooserUrl = accountChooserUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the whitelist
|
|
||||||
*/
|
|
||||||
public Set<String> getWhitelist() {
|
|
||||||
return whitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param whitelist the whitelist to set
|
|
||||||
*/
|
|
||||||
public void setWhitelist(Set<String> whitelist) {
|
|
||||||
this.whitelist = whitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the blacklist
|
|
||||||
*/
|
|
||||||
public Set<String> getBlacklist() {
|
|
||||||
return blacklist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param blacklist the blacklist to set
|
|
||||||
*/
|
|
||||||
public void setBlacklist(Set<String> blacklist) {
|
|
||||||
this.blacklist = blacklist;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void afterPropertiesSet() {
|
|
||||||
if (Strings.isNullOrEmpty(this.accountChooserUrl)) {
|
|
||||||
throw new IllegalArgumentException("Account Chooser URL cannot be null or empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,305 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
|
||||||
import org.mitre.discovery.util.WebfingerURLNormalizer;
|
|
||||||
import org.mitre.openid.connect.client.model.IssuerServiceResponse;
|
|
||||||
import org.mitre.openid.connect.client.service.IssuerService;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
import org.springframework.web.client.RestClientException;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
import org.springframework.web.util.UriComponents;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use Webfinger to discover the appropriate issuer for a user-given input string.
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class WebfingerIssuerService implements IssuerService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class
|
|
||||||
*/
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(WebfingerIssuerService.class);
|
|
||||||
|
|
||||||
// map of user input -> issuer, loaded dynamically from webfinger discover
|
|
||||||
private LoadingCache<String, LoadingResult> issuers;
|
|
||||||
|
|
||||||
// private data shuttle class to get back two bits of info from the cache loader
|
|
||||||
private class LoadingResult {
|
|
||||||
public String loginHint;
|
|
||||||
public String issuer;
|
|
||||||
public LoadingResult(String loginHint, String issuer) {
|
|
||||||
this.loginHint = loginHint;
|
|
||||||
this.issuer = issuer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> whitelist = new HashSet<>();
|
|
||||||
private Set<String> blacklist = new HashSet<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Name of the incoming parameter to check for discovery purposes.
|
|
||||||
*/
|
|
||||||
private String parameterName = "identifier";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* URL of the page to forward to if no identifier is given.
|
|
||||||
*/
|
|
||||||
private String loginPageUrl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strict enfocement of "https"
|
|
||||||
*/
|
|
||||||
private boolean forceHttps = true;
|
|
||||||
|
|
||||||
public WebfingerIssuerService() {
|
|
||||||
this(HttpClientBuilder.create().useSystemProperties().build());
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebfingerIssuerService(HttpClient httpClient) {
|
|
||||||
issuers = CacheBuilder.newBuilder().build(new WebfingerIssuerFetcher(httpClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.mitre.openid.connect.client.service.IssuerService#getIssuer(javax.servlet.http.HttpServletRequest)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public IssuerServiceResponse getIssuer(HttpServletRequest request) {
|
|
||||||
|
|
||||||
String identifier = request.getParameter(parameterName);
|
|
||||||
if (!Strings.isNullOrEmpty(identifier)) {
|
|
||||||
try {
|
|
||||||
LoadingResult lr = issuers.get(identifier);
|
|
||||||
if (!whitelist.isEmpty() && !whitelist.contains(lr.issuer)) {
|
|
||||||
throw new AuthenticationServiceException("Whitelist was nonempty, issuer was not in whitelist: " + lr.issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blacklist.contains(lr.issuer)) {
|
|
||||||
throw new AuthenticationServiceException("Issuer was in blacklist: " + lr.issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IssuerServiceResponse(lr.issuer, lr.loginHint, request.getParameter("target_link_uri"));
|
|
||||||
} catch (UncheckedExecutionException | ExecutionException e) {
|
|
||||||
logger.warn("Issue fetching issuer for user input: " + identifier + ": " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
logger.warn("No user input given, directing to login page: " + loginPageUrl);
|
|
||||||
return new IssuerServiceResponse(loginPageUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the parameterName
|
|
||||||
*/
|
|
||||||
public String getParameterName() {
|
|
||||||
return parameterName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param parameterName the parameterName to set
|
|
||||||
*/
|
|
||||||
public void setParameterName(String parameterName) {
|
|
||||||
this.parameterName = parameterName;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the loginPageUrl
|
|
||||||
*/
|
|
||||||
public String getLoginPageUrl() {
|
|
||||||
return loginPageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param loginPageUrl the loginPageUrl to set
|
|
||||||
*/
|
|
||||||
public void setLoginPageUrl(String loginPageUrl) {
|
|
||||||
this.loginPageUrl = loginPageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the whitelist
|
|
||||||
*/
|
|
||||||
public Set<String> getWhitelist() {
|
|
||||||
return whitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param whitelist the whitelist to set
|
|
||||||
*/
|
|
||||||
public void setWhitelist(Set<String> whitelist) {
|
|
||||||
this.whitelist = whitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the blacklist
|
|
||||||
*/
|
|
||||||
public Set<String> getBlacklist() {
|
|
||||||
return blacklist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param blacklist the blacklist to set
|
|
||||||
*/
|
|
||||||
public void setBlacklist(Set<String> blacklist) {
|
|
||||||
this.blacklist = blacklist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the forceHttps
|
|
||||||
*/
|
|
||||||
public boolean isForceHttps() {
|
|
||||||
return forceHttps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param forceHttps the forceHttps to set
|
|
||||||
*/
|
|
||||||
public void setForceHttps(boolean forceHttps) {
|
|
||||||
this.forceHttps = forceHttps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private class WebfingerIssuerFetcher extends CacheLoader<String, LoadingResult> {
|
|
||||||
private HttpComponentsClientHttpRequestFactory httpFactory;
|
|
||||||
private JsonParser parser = new JsonParser();
|
|
||||||
|
|
||||||
WebfingerIssuerFetcher(HttpClient httpClient) {
|
|
||||||
this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoadingResult load(String identifier) throws Exception {
|
|
||||||
|
|
||||||
UriComponents key = WebfingerURLNormalizer.normalizeResource(identifier);
|
|
||||||
|
|
||||||
RestTemplate restTemplate = new RestTemplate(httpFactory);
|
|
||||||
// construct the URL to go to
|
|
||||||
|
|
||||||
String scheme = key.getScheme();
|
|
||||||
|
|
||||||
// preserving http scheme is strictly for demo system use only.
|
|
||||||
if (!Strings.isNullOrEmpty(scheme) &&scheme.equals("http")) {
|
|
||||||
if (forceHttps) {
|
|
||||||
throw new IllegalArgumentException("Scheme must not be 'http'");
|
|
||||||
} else {
|
|
||||||
logger.warn("Webfinger endpoint MUST use the https URI scheme, overriding by configuration");
|
|
||||||
scheme = "http://"; // add on colon and slashes.
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// otherwise we don't know the scheme, assume HTTPS
|
|
||||||
scheme = "https://";
|
|
||||||
}
|
|
||||||
|
|
||||||
// do a webfinger lookup
|
|
||||||
URIBuilder builder = new URIBuilder(scheme
|
|
||||||
+ key.getHost()
|
|
||||||
+ (key.getPort() >= 0 ? ":" + key.getPort() : "")
|
|
||||||
+ Strings.nullToEmpty(key.getPath())
|
|
||||||
+ "/.well-known/webfinger"
|
|
||||||
+ (Strings.isNullOrEmpty(key.getQuery()) ? "" : "?" + key.getQuery())
|
|
||||||
);
|
|
||||||
builder.addParameter("resource", identifier);
|
|
||||||
builder.addParameter("rel", "http://openid.net/specs/connect/1.0/issuer");
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// do the fetch
|
|
||||||
logger.info("Loading: " + builder.toString());
|
|
||||||
String webfingerResponse = restTemplate.getForObject(builder.build(), String.class);
|
|
||||||
|
|
||||||
JsonElement json = parser.parse(webfingerResponse);
|
|
||||||
|
|
||||||
if (json != null && json.isJsonObject()) {
|
|
||||||
// find the issuer
|
|
||||||
JsonArray links = json.getAsJsonObject().get("links").getAsJsonArray();
|
|
||||||
for (JsonElement link : links) {
|
|
||||||
if (link.isJsonObject()) {
|
|
||||||
JsonObject linkObj = link.getAsJsonObject();
|
|
||||||
if (linkObj.has("href")
|
|
||||||
&& linkObj.has("rel")
|
|
||||||
&& linkObj.get("rel").getAsString().equals("http://openid.net/specs/connect/1.0/issuer")) {
|
|
||||||
|
|
||||||
// we found the issuer, return it
|
|
||||||
String href = linkObj.get("href").getAsString();
|
|
||||||
|
|
||||||
if (identifier.equals(href)
|
|
||||||
|| identifier.startsWith("http")) {
|
|
||||||
// try to avoid sending a URL as the login hint
|
|
||||||
return new LoadingResult(null, href);
|
|
||||||
} else {
|
|
||||||
// otherwise pass back whatever the user typed as a login hint
|
|
||||||
return new LoadingResult(identifier, href);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (JsonParseException | RestClientException e) {
|
|
||||||
logger.warn("Failure in fetching webfinger input", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// we couldn't find it!
|
|
||||||
|
|
||||||
if (key.getScheme().equals("http") || key.getScheme().equals("https")) {
|
|
||||||
// if it looks like HTTP then punt: return the input, hope for the best
|
|
||||||
logger.warn("Returning normalized input string as issuer, hoping for the best: " + identifier);
|
|
||||||
return new LoadingResult(null, identifier);
|
|
||||||
} else {
|
|
||||||
// if it's not HTTP, give up
|
|
||||||
logger.warn("Couldn't find issuer: " + identifier);
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.oauth2.introspectingfilter;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
public class TestOAuth2AccessTokenImpl {
|
|
||||||
|
|
||||||
private static String tokenString = "thisisatokenstring";
|
|
||||||
|
|
||||||
private static Set<String> scopes = ImmutableSet.of("bar", "foo");
|
|
||||||
private static String scopeString = "foo bar";
|
|
||||||
|
|
||||||
private static Date exp = new Date(123 * 1000L);
|
|
||||||
private static Long expVal = 123L;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFullToken() {
|
|
||||||
|
|
||||||
|
|
||||||
JsonObject tokenObj = new JsonObject();
|
|
||||||
tokenObj.addProperty("active", true);
|
|
||||||
tokenObj.addProperty("scope", scopeString);
|
|
||||||
tokenObj.addProperty("exp", expVal);
|
|
||||||
tokenObj.addProperty("sub", "subject");
|
|
||||||
tokenObj.addProperty("client_id", "123-456-789");
|
|
||||||
|
|
||||||
OAuth2AccessTokenImpl tok = new OAuth2AccessTokenImpl(tokenObj, tokenString);
|
|
||||||
|
|
||||||
assertThat(tok.getScope(), is(equalTo(scopes)));
|
|
||||||
assertThat(tok.getExpiration(), is(equalTo(exp)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNullExp() {
|
|
||||||
|
|
||||||
|
|
||||||
JsonObject tokenObj = new JsonObject();
|
|
||||||
tokenObj.addProperty("active", true);
|
|
||||||
tokenObj.addProperty("scope", scopeString);
|
|
||||||
tokenObj.addProperty("sub", "subject");
|
|
||||||
tokenObj.addProperty("client_id", "123-456-789");
|
|
||||||
|
|
||||||
OAuth2AccessTokenImpl tok = new OAuth2AccessTokenImpl(tokenObj, tokenString);
|
|
||||||
|
|
||||||
assertThat(tok.getScope(), is(equalTo(scopes)));
|
|
||||||
assertThat(tok.getExpiration(), is(equalTo(null)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNullScopes() {
|
|
||||||
|
|
||||||
|
|
||||||
JsonObject tokenObj = new JsonObject();
|
|
||||||
tokenObj.addProperty("active", true);
|
|
||||||
tokenObj.addProperty("exp", expVal);
|
|
||||||
tokenObj.addProperty("sub", "subject");
|
|
||||||
tokenObj.addProperty("client_id", "123-456-789");
|
|
||||||
|
|
||||||
OAuth2AccessTokenImpl tok = new OAuth2AccessTokenImpl(tokenObj, tokenString);
|
|
||||||
|
|
||||||
assertThat(tok.getScope(), is(equalTo(Collections.EMPTY_SET)));
|
|
||||||
assertThat(tok.getExpiration(), is(equalTo(exp)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNullScopesNullExp() {
|
|
||||||
|
|
||||||
|
|
||||||
JsonObject tokenObj = new JsonObject();
|
|
||||||
tokenObj.addProperty("active", true);
|
|
||||||
tokenObj.addProperty("sub", "subject");
|
|
||||||
tokenObj.addProperty("client_id", "123-456-789");
|
|
||||||
|
|
||||||
OAuth2AccessTokenImpl tok = new OAuth2AccessTokenImpl(tokenObj, tokenString);
|
|
||||||
|
|
||||||
assertThat(tok.getScope(), is(equalTo(Collections.EMPTY_SET)));
|
|
||||||
assertThat(tok.getExpiration(), is(equalTo(null)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
|
|
||||||
package org.mitre.oauth2.introspectingfilter.service.impl;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author jricher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class TestScopeBasedIntrospectionAuthoritiesGranter {
|
|
||||||
|
|
||||||
private JsonObject introspectionResponse;
|
|
||||||
|
|
||||||
private ScopeBasedIntrospectionAuthoritiesGranter granter = new ScopeBasedIntrospectionAuthoritiesGranter();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws java.lang.Exception
|
|
||||||
*/
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
introspectionResponse = new JsonObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test method for {@link org.mitre.oauth2.introspectingfilter.service.impl.ScopeBasedIntrospectionAuthoritiesGranter#getAuthorities(com.google.gson.JsonObject)}.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetAuthoritiesJsonObject_withScopes() {
|
|
||||||
introspectionResponse.addProperty("scope", "foo bar baz batman");
|
|
||||||
|
|
||||||
List<GrantedAuthority> expected = new ArrayList<>();
|
|
||||||
expected.add(new SimpleGrantedAuthority("ROLE_API"));
|
|
||||||
expected.add(new SimpleGrantedAuthority("OAUTH_SCOPE_foo"));
|
|
||||||
expected.add(new SimpleGrantedAuthority("OAUTH_SCOPE_bar"));
|
|
||||||
expected.add(new SimpleGrantedAuthority("OAUTH_SCOPE_baz"));
|
|
||||||
expected.add(new SimpleGrantedAuthority("OAUTH_SCOPE_batman"));
|
|
||||||
|
|
||||||
List<GrantedAuthority> authorities = granter.getAuthorities(introspectionResponse);
|
|
||||||
|
|
||||||
assertTrue(authorities.containsAll(expected));
|
|
||||||
assertTrue(expected.containsAll(authorities));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test method for {@link org.mitre.oauth2.introspectingfilter.service.impl.ScopeBasedIntrospectionAuthoritiesGranter#getAuthorities(com.google.gson.JsonObject)}.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetAuthoritiesJsonObject_withoutScopes() {
|
|
||||||
|
|
||||||
List<GrantedAuthority> expected = new ArrayList<>();
|
|
||||||
expected.add(new SimpleGrantedAuthority("ROLE_API"));
|
|
||||||
|
|
||||||
List<GrantedAuthority> authorities = granter.getAuthorities(introspectionResponse);
|
|
||||||
|
|
||||||
assertTrue(authorities.containsAll(expected));
|
|
||||||
assertTrue(expected.containsAll(authorities));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
public class TestOIDCAuthenticationFilter {
|
|
||||||
|
|
||||||
private OIDCAuthenticationFilter filter = new OIDCAuthenticationFilter();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void attemptAuthentication_error() throws Exception {
|
|
||||||
|
|
||||||
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
|
|
||||||
Mockito.when(request.getParameter("error")).thenReturn("Error");
|
|
||||||
Mockito.when(request.getParameter("error_description")).thenReturn("Description");
|
|
||||||
Mockito.when(request.getParameter("error_uri")).thenReturn("http://example.com");
|
|
||||||
|
|
||||||
try {
|
|
||||||
filter.attemptAuthentication(request, mock(HttpServletResponse.class));
|
|
||||||
|
|
||||||
fail("AuthorizationEndpointException expected.");
|
|
||||||
}
|
|
||||||
catch (AuthorizationEndpointException exception) {
|
|
||||||
assertThat(exception.getMessage(),
|
|
||||||
is("Error from Authorization Endpoint: Error Description http://example.com"));
|
|
||||||
|
|
||||||
assertThat(exception.getError(), is("Error"));
|
|
||||||
assertThat(exception.getErrorDescription(), is("Description"));
|
|
||||||
assertThat(exception.getErrorURI(), is("http://example.com"));
|
|
||||||
|
|
||||||
assertThat(exception, is(instanceOf(AuthenticationServiceException.class)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.mockito.InjectMocks;
|
|
||||||
import org.mockito.Matchers;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
|
||||||
import static org.hamcrest.CoreMatchers.nullValue;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author wkim
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
public class TestHybridClientConfigurationService {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private StaticClientConfigurationService mockStaticService;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private DynamicRegistrationClientConfigurationService mockDynamicService;
|
|
||||||
|
|
||||||
@InjectMocks
|
|
||||||
private HybridClientConfigurationService hybridService;
|
|
||||||
|
|
||||||
// test fixture
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private RegisteredClient mockClient;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ServerConfiguration mockServerConfig;
|
|
||||||
|
|
||||||
private String issuer = "https://www.example.com/";
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void prepare() {
|
|
||||||
|
|
||||||
Mockito.reset(mockDynamicService, mockStaticService);
|
|
||||||
|
|
||||||
Mockito.when(mockServerConfig.getIssuer()).thenReturn(issuer);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getClientConfiguration_useStatic() {
|
|
||||||
|
|
||||||
Mockito.when(mockStaticService.getClientConfiguration(mockServerConfig)).thenReturn(mockClient);
|
|
||||||
|
|
||||||
RegisteredClient result = hybridService.getClientConfiguration(mockServerConfig);
|
|
||||||
|
|
||||||
Mockito.verify(mockStaticService).getClientConfiguration(mockServerConfig);
|
|
||||||
Mockito.verify(mockDynamicService, Mockito.never()).getClientConfiguration(Matchers.any(ServerConfiguration.class));
|
|
||||||
assertEquals(mockClient, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getClientConfiguration_useDynamic() {
|
|
||||||
|
|
||||||
Mockito.when(mockStaticService.getClientConfiguration(mockServerConfig)).thenReturn(null);
|
|
||||||
Mockito.when(mockDynamicService.getClientConfiguration(mockServerConfig)).thenReturn(mockClient);
|
|
||||||
|
|
||||||
RegisteredClient result = hybridService.getClientConfiguration(mockServerConfig);
|
|
||||||
|
|
||||||
Mockito.verify(mockStaticService).getClientConfiguration(mockServerConfig);
|
|
||||||
Mockito.verify(mockDynamicService).getClientConfiguration(mockServerConfig);
|
|
||||||
assertEquals(mockClient, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the behavior when the issuer is not known.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void getClientConfiguration_noIssuer() {
|
|
||||||
|
|
||||||
// The mockServerConfig is known to both services
|
|
||||||
Mockito.when(mockStaticService.getClientConfiguration(mockServerConfig)).thenReturn(mockClient);
|
|
||||||
Mockito.when(mockDynamicService.getClientConfiguration(mockServerConfig)).thenReturn(mockClient);
|
|
||||||
|
|
||||||
// But oh noes! We're going to ask it to find us some other issuer
|
|
||||||
ServerConfiguration badIssuer = Mockito.mock(ServerConfiguration.class);
|
|
||||||
Mockito.when(badIssuer.getIssuer()).thenReturn("www.badexample.com");
|
|
||||||
|
|
||||||
RegisteredClient result = hybridService.getClientConfiguration(badIssuer);
|
|
||||||
|
|
||||||
Mockito.verify(mockStaticService).getClientConfiguration(badIssuer);
|
|
||||||
Mockito.verify(mockDynamicService).getClientConfiguration(badIssuer);
|
|
||||||
assertThat(result, is(nullValue()));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.mockito.InjectMocks;
|
|
||||||
import org.mockito.Matchers;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
|
||||||
import static org.hamcrest.CoreMatchers.nullValue;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author wkim
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
public class TestHybridServerConfigurationService {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private StaticServerConfigurationService mockStaticService;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private DynamicServerConfigurationService mockDynamicService;
|
|
||||||
|
|
||||||
@InjectMocks
|
|
||||||
private HybridServerConfigurationService hybridService;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ServerConfiguration mockServerConfig;
|
|
||||||
|
|
||||||
private String issuer = "https://www.example.com/";
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void prepare() {
|
|
||||||
|
|
||||||
Mockito.reset(mockDynamicService, mockStaticService);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getServerConfiguration_useStatic() {
|
|
||||||
|
|
||||||
Mockito.when(mockStaticService.getServerConfiguration(issuer)).thenReturn(mockServerConfig);
|
|
||||||
|
|
||||||
ServerConfiguration result = hybridService.getServerConfiguration(issuer);
|
|
||||||
|
|
||||||
Mockito.verify(mockStaticService).getServerConfiguration(issuer);
|
|
||||||
Mockito.verify(mockDynamicService, Mockito.never()).getServerConfiguration(Matchers.anyString());
|
|
||||||
assertEquals(mockServerConfig, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getServerConfiguration_useDynamic() {
|
|
||||||
|
|
||||||
Mockito.when(mockStaticService.getServerConfiguration(issuer)).thenReturn(null);
|
|
||||||
Mockito.when(mockDynamicService.getServerConfiguration(issuer)).thenReturn(mockServerConfig);
|
|
||||||
|
|
||||||
ServerConfiguration result = hybridService.getServerConfiguration(issuer);
|
|
||||||
|
|
||||||
Mockito.verify(mockStaticService).getServerConfiguration(issuer);
|
|
||||||
Mockito.verify(mockDynamicService).getServerConfiguration(issuer);
|
|
||||||
assertEquals(mockServerConfig, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the behavior when the issuer is not known.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void getServerConfiguration_noIssuer() {
|
|
||||||
|
|
||||||
Mockito.when(mockStaticService.getServerConfiguration(issuer)).thenReturn(mockServerConfig);
|
|
||||||
Mockito.when(mockDynamicService.getServerConfiguration(issuer)).thenReturn(mockServerConfig);
|
|
||||||
|
|
||||||
String badIssuer = "www.badexample.com";
|
|
||||||
|
|
||||||
ServerConfiguration result = hybridService.getServerConfiguration(badIssuer);
|
|
||||||
|
|
||||||
Mockito.verify(mockStaticService).getServerConfiguration(badIssuer);
|
|
||||||
Mockito.verify(mockDynamicService).getServerConfiguration(badIssuer);
|
|
||||||
assertThat(result, is(nullValue()));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author wkim
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class TestPlainAuthRequestUrlBuilder {
|
|
||||||
|
|
||||||
// Test fixture:
|
|
||||||
ServerConfiguration serverConfig;
|
|
||||||
RegisteredClient clientConfig;
|
|
||||||
|
|
||||||
private PlainAuthRequestUrlBuilder urlBuilder = new PlainAuthRequestUrlBuilder();
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void prepare() {
|
|
||||||
|
|
||||||
serverConfig = Mockito.mock(ServerConfiguration.class);
|
|
||||||
Mockito.when(serverConfig.getAuthorizationEndpointUri()).thenReturn("https://server.example.com/authorize");
|
|
||||||
|
|
||||||
clientConfig = Mockito.mock(RegisteredClient.class);
|
|
||||||
Mockito.when(clientConfig.getClientId()).thenReturn("s6BhdRkqt3");
|
|
||||||
Mockito.when(clientConfig.getScope()).thenReturn(Sets.newHashSet("openid", "profile"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void buildAuthRequestUrl() {
|
|
||||||
|
|
||||||
String expectedUrl = "https://server.example.com/authorize?" +
|
|
||||||
"response_type=code" +
|
|
||||||
"&client_id=s6BhdRkqt3" +
|
|
||||||
"&scope=openid+profile" + // plus sign used for space per application/x-www-form-encoded standard
|
|
||||||
"&redirect_uri=https%3A%2F%2Fclient.example.org%2F" +
|
|
||||||
"&nonce=34fasf3ds" +
|
|
||||||
"&state=af0ifjsldkj" +
|
|
||||||
"&foo=bar";
|
|
||||||
|
|
||||||
Map<String, String> options = ImmutableMap.of("foo", "bar");
|
|
||||||
|
|
||||||
String actualUrl = urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, "https://client.example.org/", "34fasf3ds", "af0ifjsldkj", options, null);
|
|
||||||
|
|
||||||
assertThat(actualUrl, equalTo(expectedUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void buildAuthRequestUrl_withLoginHint() {
|
|
||||||
|
|
||||||
String expectedUrl = "https://server.example.com/authorize?" +
|
|
||||||
"response_type=code" +
|
|
||||||
"&client_id=s6BhdRkqt3" +
|
|
||||||
"&scope=openid+profile" + // plus sign used for space per application/x-www-form-encoded standard
|
|
||||||
"&redirect_uri=https%3A%2F%2Fclient.example.org%2F" +
|
|
||||||
"&nonce=34fasf3ds" +
|
|
||||||
"&state=af0ifjsldkj" +
|
|
||||||
"&foo=bar" +
|
|
||||||
"&login_hint=bob";
|
|
||||||
|
|
||||||
Map<String, String> options = ImmutableMap.of("foo", "bar");
|
|
||||||
|
|
||||||
String actualUrl = urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, "https://client.example.org/", "34fasf3ds", "af0ifjsldkj", options, "bob");
|
|
||||||
|
|
||||||
assertThat(actualUrl, equalTo(expectedUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = AuthenticationServiceException.class)
|
|
||||||
public void buildAuthRequestUrl_badUri() {
|
|
||||||
|
|
||||||
Mockito.when(serverConfig.getAuthorizationEndpointUri()).thenReturn("e=mc^2");
|
|
||||||
|
|
||||||
Map<String, String> options = ImmutableMap.of("foo", "bar");
|
|
||||||
|
|
||||||
urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, "example.com", "", "", options, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,204 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mitre.jwt.signer.service.impl.DefaultJWTSigningAndValidationService;
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
import org.springframework.web.util.UriComponents;
|
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import com.nimbusds.jose.Algorithm;
|
|
||||||
import com.nimbusds.jose.jwk.JWK;
|
|
||||||
import com.nimbusds.jose.jwk.KeyUse;
|
|
||||||
import com.nimbusds.jose.jwk.RSAKey;
|
|
||||||
import com.nimbusds.jose.util.Base64URL;
|
|
||||||
import com.nimbusds.jwt.JWTClaimsSet;
|
|
||||||
import com.nimbusds.jwt.SignedJWT;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author wkim
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class TestSignedAuthRequestUrlBuilder {
|
|
||||||
|
|
||||||
// Test fixture:
|
|
||||||
private ServerConfiguration serverConfig;
|
|
||||||
private RegisteredClient clientConfig;
|
|
||||||
|
|
||||||
private String redirectUri = "https://client.example.org/";
|
|
||||||
private String nonce = "34fasf3ds";
|
|
||||||
private String state = "af0ifjsldkj";
|
|
||||||
private String responseType = "code";
|
|
||||||
private Map<String, String> options = ImmutableMap.of("foo", "bar");
|
|
||||||
|
|
||||||
|
|
||||||
// RSA key properties:
|
|
||||||
// {@link package com.nimbusds.jose.jwk#RSAKey}
|
|
||||||
private String n = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zw" +
|
|
||||||
"u1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc" +
|
|
||||||
"5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8K" +
|
|
||||||
"JZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh" +
|
|
||||||
"6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw";
|
|
||||||
private String e = "AQAB";
|
|
||||||
private String d = "X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknc" +
|
|
||||||
"hnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5" +
|
|
||||||
"N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSa" +
|
|
||||||
"wm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk1" +
|
|
||||||
"9Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q";
|
|
||||||
private String alg = "RS256";
|
|
||||||
private String kid = "2011-04-29";
|
|
||||||
private String loginHint = "bob";
|
|
||||||
|
|
||||||
private DefaultJWTSigningAndValidationService signingAndValidationService;
|
|
||||||
|
|
||||||
private SignedAuthRequestUrlBuilder urlBuilder = new SignedAuthRequestUrlBuilder();
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException {
|
|
||||||
|
|
||||||
RSAKey key = new RSAKey(new Base64URL(n), new Base64URL(e), new Base64URL(d), KeyUse.SIGNATURE, null, new Algorithm(alg), kid, null, null, null, null, null);
|
|
||||||
Map<String, JWK> keys = Maps.newHashMap();
|
|
||||||
keys.put("client", key);
|
|
||||||
|
|
||||||
signingAndValidationService = new DefaultJWTSigningAndValidationService(keys);
|
|
||||||
signingAndValidationService.setDefaultSignerKeyId("client");
|
|
||||||
signingAndValidationService.setDefaultSigningAlgorithmName(alg);
|
|
||||||
|
|
||||||
urlBuilder.setSigningAndValidationService(signingAndValidationService);
|
|
||||||
|
|
||||||
serverConfig = Mockito.mock(ServerConfiguration.class);
|
|
||||||
Mockito.when(serverConfig.getAuthorizationEndpointUri()).thenReturn("https://server.example.com/authorize");
|
|
||||||
|
|
||||||
clientConfig = Mockito.mock(RegisteredClient.class);
|
|
||||||
Mockito.when(clientConfig.getClientId()).thenReturn("s6BhdRkqt3");
|
|
||||||
Mockito.when(clientConfig.getScope()).thenReturn(Sets.newHashSet("openid", "profile"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This test takes the URI from the result of building a signed request
|
|
||||||
* and checks that the JWS object parsed from the request URI matches up
|
|
||||||
* with the expected claim values.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void buildAuthRequestUrl() {
|
|
||||||
|
|
||||||
String requestUri = urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, redirectUri, nonce, state, options, null);
|
|
||||||
|
|
||||||
// parsing the result
|
|
||||||
UriComponentsBuilder builder = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
builder = UriComponentsBuilder.fromUri(new URI(requestUri));
|
|
||||||
} catch (URISyntaxException e1) {
|
|
||||||
fail("URISyntaxException was thrown.");
|
|
||||||
}
|
|
||||||
|
|
||||||
UriComponents components = builder.build();
|
|
||||||
String jwtString = components.getQueryParams().get("request").get(0);
|
|
||||||
JWTClaimsSet claims = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
SignedJWT jwt = SignedJWT.parse(jwtString);
|
|
||||||
claims = jwt.getJWTClaimsSet();
|
|
||||||
} catch (ParseException e) {
|
|
||||||
fail("ParseException was thrown.");
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(responseType, claims.getClaim("response_type"));
|
|
||||||
assertEquals(clientConfig.getClientId(), claims.getClaim("client_id"));
|
|
||||||
|
|
||||||
List<String> scopeList = Arrays.asList(((String) claims.getClaim("scope")).split(" "));
|
|
||||||
assertTrue(scopeList.containsAll(clientConfig.getScope()));
|
|
||||||
|
|
||||||
assertEquals(redirectUri, claims.getClaim("redirect_uri"));
|
|
||||||
assertEquals(nonce, claims.getClaim("nonce"));
|
|
||||||
assertEquals(state, claims.getClaim("state"));
|
|
||||||
for (String claim : options.keySet()) {
|
|
||||||
assertEquals(options.get(claim), claims.getClaim(claim));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void buildAuthRequestUrl_withLoginHint() {
|
|
||||||
|
|
||||||
String requestUri = urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, redirectUri, nonce, state, options, loginHint);
|
|
||||||
|
|
||||||
// parsing the result
|
|
||||||
UriComponentsBuilder builder = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
builder = UriComponentsBuilder.fromUri(new URI(requestUri));
|
|
||||||
} catch (URISyntaxException e1) {
|
|
||||||
fail("URISyntaxException was thrown.");
|
|
||||||
}
|
|
||||||
|
|
||||||
UriComponents components = builder.build();
|
|
||||||
String jwtString = components.getQueryParams().get("request").get(0);
|
|
||||||
JWTClaimsSet claims = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
SignedJWT jwt = SignedJWT.parse(jwtString);
|
|
||||||
claims = jwt.getJWTClaimsSet();
|
|
||||||
} catch (ParseException e) {
|
|
||||||
fail("ParseException was thrown.");
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(responseType, claims.getClaim("response_type"));
|
|
||||||
assertEquals(clientConfig.getClientId(), claims.getClaim("client_id"));
|
|
||||||
|
|
||||||
List<String> scopeList = Arrays.asList(((String) claims.getClaim("scope")).split(" "));
|
|
||||||
assertTrue(scopeList.containsAll(clientConfig.getScope()));
|
|
||||||
|
|
||||||
assertEquals(redirectUri, claims.getClaim("redirect_uri"));
|
|
||||||
assertEquals(nonce, claims.getClaim("nonce"));
|
|
||||||
assertEquals(state, claims.getClaim("state"));
|
|
||||||
for (String claim : options.keySet()) {
|
|
||||||
assertEquals(options.get(claim), claims.getClaim(claim));
|
|
||||||
}
|
|
||||||
assertEquals(loginHint, claims.getClaim("login_hint"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = AuthenticationServiceException.class)
|
|
||||||
public void buildAuthRequestUrl_badUri() {
|
|
||||||
|
|
||||||
Mockito.when(serverConfig.getAuthorizationEndpointUri()).thenReturn("e=mc^2");
|
|
||||||
|
|
||||||
urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, "example.com", "", "", options, null);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mitre.oauth2.model.RegisteredClient;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
|
||||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
|
||||||
import static org.hamcrest.CoreMatchers.nullValue;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author wkim
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
public class TestStaticClientConfigurationService {
|
|
||||||
|
|
||||||
private StaticClientConfigurationService service;
|
|
||||||
|
|
||||||
private String issuer = "https://www.example.com/";
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private RegisteredClient mockClient;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ServerConfiguration mockServerConfig;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void prepare() {
|
|
||||||
|
|
||||||
service = new StaticClientConfigurationService();
|
|
||||||
|
|
||||||
Map<String, RegisteredClient> clients = new HashMap<>();
|
|
||||||
clients.put(issuer, mockClient);
|
|
||||||
|
|
||||||
service.setClients(clients);
|
|
||||||
|
|
||||||
Mockito.when(mockServerConfig.getIssuer()).thenReturn(issuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getClientConfiguration_success() {
|
|
||||||
|
|
||||||
RegisteredClient result = service.getClientConfiguration(mockServerConfig);
|
|
||||||
|
|
||||||
assertThat(mockClient, is(notNullValue()));
|
|
||||||
assertEquals(mockClient, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the behavior when the issuer is not known.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void getClientConfiguration_noIssuer() {
|
|
||||||
Mockito.when(mockServerConfig.getIssuer()).thenReturn("www.badexample.net");
|
|
||||||
|
|
||||||
RegisteredClient actualClient = service.getClientConfiguration(mockServerConfig);
|
|
||||||
|
|
||||||
assertThat(actualClient, is(nullValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mitre.openid.connect.config.ServerConfiguration;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
|
||||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
|
||||||
import static org.hamcrest.CoreMatchers.nullValue;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author wkim
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
public class TestStaticServerConfigurationService {
|
|
||||||
|
|
||||||
|
|
||||||
private StaticServerConfigurationService service;
|
|
||||||
|
|
||||||
private String issuer = "https://www.example.com/";
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ServerConfiguration mockServerConfig;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void prepare() {
|
|
||||||
|
|
||||||
service = new StaticServerConfigurationService();
|
|
||||||
|
|
||||||
Map<String, ServerConfiguration> servers = new HashMap<>();
|
|
||||||
servers.put(issuer, mockServerConfig);
|
|
||||||
|
|
||||||
service.setServers(servers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getServerConfiguration_success() {
|
|
||||||
|
|
||||||
ServerConfiguration result = service.getServerConfiguration(issuer);
|
|
||||||
|
|
||||||
assertThat(mockServerConfig, is(notNullValue()));
|
|
||||||
assertEquals(mockServerConfig, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the behavior when the issuer is not known.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void getClientConfiguration_noIssuer() {
|
|
||||||
|
|
||||||
ServerConfiguration result = service.getServerConfiguration("www.badexample.net");
|
|
||||||
|
|
||||||
assertThat(result, is(nullValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,130 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
* Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
*
|
|
||||||
* Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.mitre.openid.connect.client.service.impl;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mitre.openid.connect.client.model.IssuerServiceResponse;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
|
||||||
import static org.hamcrest.CoreMatchers.nullValue;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author wkim
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class TestThirdPartyIssuerService {
|
|
||||||
|
|
||||||
// Test fixture:
|
|
||||||
private HttpServletRequest request;
|
|
||||||
|
|
||||||
private String iss = "https://server.example.org";
|
|
||||||
private String login_hint = "I'm not telling you nothin!";
|
|
||||||
private String target_link_uri = "https://www.example.com";
|
|
||||||
private String redirect_uri = "https://www.example.com";
|
|
||||||
|
|
||||||
private String accountChooserUrl = "https://www.example.com/account";
|
|
||||||
|
|
||||||
private ThirdPartyIssuerService service = new ThirdPartyIssuerService();
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void prepare() {
|
|
||||||
|
|
||||||
service.setAccountChooserUrl(accountChooserUrl);
|
|
||||||
|
|
||||||
request = Mockito.mock(HttpServletRequest.class);
|
|
||||||
Mockito.when(request.getParameter("iss")).thenReturn(iss);
|
|
||||||
Mockito.when(request.getParameter("login_hint")).thenReturn(login_hint);
|
|
||||||
Mockito.when(request.getParameter("target_link_uri")).thenReturn(target_link_uri);
|
|
||||||
Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer(redirect_uri));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getIssuer_hasIssuer() {
|
|
||||||
|
|
||||||
IssuerServiceResponse response = service.getIssuer(request);
|
|
||||||
|
|
||||||
assertThat(response.getIssuer(), equalTo(iss));
|
|
||||||
assertThat(response.getLoginHint(), equalTo(login_hint));
|
|
||||||
assertThat(response.getTargetLinkUri(), equalTo(target_link_uri));
|
|
||||||
|
|
||||||
assertThat(response.getRedirectUrl(), nullValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getIssuer_noIssuer() {
|
|
||||||
|
|
||||||
Mockito.when(request.getParameter("iss")).thenReturn(null);
|
|
||||||
|
|
||||||
IssuerServiceResponse response = service.getIssuer(request);
|
|
||||||
|
|
||||||
assertThat(response.getIssuer(), nullValue());
|
|
||||||
assertThat(response.getLoginHint(), nullValue());
|
|
||||||
assertThat(response.getTargetLinkUri(), nullValue());
|
|
||||||
|
|
||||||
String expectedRedirectUrl = accountChooserUrl + "?redirect_uri=" + "https%3A%2F%2Fwww.example.com"; // url-encoded string of the request url
|
|
||||||
assertThat(response.getRedirectUrl(), equalTo(expectedRedirectUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getIssuer_isWhitelisted() {
|
|
||||||
|
|
||||||
service.setWhitelist(Sets.newHashSet(iss));
|
|
||||||
|
|
||||||
IssuerServiceResponse response = service.getIssuer(request);
|
|
||||||
|
|
||||||
assertThat(response.getIssuer(), equalTo(iss));
|
|
||||||
assertThat(response.getLoginHint(), equalTo(login_hint));
|
|
||||||
assertThat(response.getTargetLinkUri(), equalTo(target_link_uri));
|
|
||||||
|
|
||||||
assertThat(response.getRedirectUrl(), nullValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = AuthenticationServiceException.class)
|
|
||||||
public void getIssuer_notWhitelisted() {
|
|
||||||
|
|
||||||
service.setWhitelist(Sets.newHashSet("some.other.site"));
|
|
||||||
|
|
||||||
service.getIssuer(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = AuthenticationServiceException.class)
|
|
||||||
public void getIssuer_blacklisted() {
|
|
||||||
|
|
||||||
service.setBlacklist(Sets.newHashSet(iss));
|
|
||||||
|
|
||||||
service.getIssuer(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = AuthenticationServiceException.class)
|
|
||||||
public void getIssuer_badUri() {
|
|
||||||
|
|
||||||
Mockito.when(request.getParameter("iss")).thenReturn(null);
|
|
||||||
service.setAccountChooserUrl("e=mc^2");
|
|
||||||
|
|
||||||
service.getIssuer(request);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{"jwk":
|
|
||||||
[
|
|
||||||
{"alg":"RSA",
|
|
||||||
"mod": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
|
|
||||||
"exp":"AQAB",
|
|
||||||
"kid":"2011-04-29"}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{"jwk":
|
|
||||||
[
|
|
||||||
{"alg":"RSA",
|
|
||||||
"mod": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
|
|
||||||
"exp":"AQAB",
|
|
||||||
"kid":"2011-04-29"}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
|
|
||||||
Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xmlns:jwt-signer="http://www.mitre.org/schema/openid-connect/jwt-signer"
|
|
||||||
xsi:schemaLocation=
|
|
||||||
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
|
||||||
http://www.mitre.org/schema/openid-connect/jwt-signer http://www.mitre.org/schema/openid-connect/jwt-signer/jwt-signer-1.0.xsd" >
|
|
||||||
|
|
||||||
<!-- Creates an in-memory database populated with test jdbc -->
|
|
||||||
<bean id="dataSource" class="org.mitre.jdbc.datasource.H2DataSourceFactory">
|
|
||||||
<property name="databaseName" value="connect"/>
|
|
||||||
<property name="scriptLocations" >
|
|
||||||
<list>
|
|
||||||
<!-- OpenID Connect Data model -->
|
|
||||||
<value>file:db/tables/accesstoken.sql</value>
|
|
||||||
<value>file:db/tables/address.sql</value>
|
|
||||||
<value>file:db/tables/approvedsite.sql</value>
|
|
||||||
<value>file:db/tables/authorities.sql</value>
|
|
||||||
<value>file:db/tables/clientdetails.sql</value>
|
|
||||||
<value>file:db/tables/event.sql</value>
|
|
||||||
<value>file:db/tables/granttypes.sql</value>
|
|
||||||
<value>file:db/tables/idtoken.sql</value>
|
|
||||||
<value>file:db/tables/idtokenclaims.sql</value>
|
|
||||||
<value>file:db/tables/refreshtoken.sql</value>
|
|
||||||
<value>file:db/tables/scope.sql</value>
|
|
||||||
<value>file:db/tables/userinfo.sql</value>
|
|
||||||
<value>file:db/tables/whitelistedsite.sql</value>
|
|
||||||
<!-- Preloaded data -->
|
|
||||||
<value>classpath:test-data.sql</value>
|
|
||||||
</list>
|
|
||||||
</property>
|
|
||||||
<property name="dateConversionPatterns">
|
|
||||||
<map>
|
|
||||||
<entry key="yyyy/mm/dd hh24:mi:ss" value="yyy/MM/dd HH:mm:ss" />
|
|
||||||
<entry key="yyyy-mm-dd" value="yyyy-MM-dd" />
|
|
||||||
</map>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
</beans>
|
|
|
@ -1,15 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICxDCCAi0CBECcV/wwDQYJKoZIhvcNAQEEBQAwgagxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIEwVU
|
|
||||||
ZXhhczEPMA0GA1UEBxMGQXVzdGluMSowKAYDVQQKEyFUaGUgVW5pdmVyc2l0eSBvZiBUZXhhcyBh
|
|
||||||
dCBBdXN0aW4xKDAmBgNVBAsTH0luZm9ybWF0aW9uIFRlY2hub2xvZ3kgU2VydmljZXMxIjAgBgNV
|
|
||||||
BAMTGXhtbGdhdGV3YXkuaXRzLnV0ZXhhcy5lZHUwHhcNMDQwNTA4MDM0NjA0WhcNMDQwODA2MDM0
|
|
||||||
NjA0WjCBqDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZBdXN0aW4xKjAo
|
|
||||||
BgNVBAoTIVRoZSBVbml2ZXJzaXR5IG9mIFRleGFzIGF0IEF1c3RpbjEoMCYGA1UECxMfSW5mb3Jt
|
|
||||||
YXRpb24gVGVjaG5vbG9neSBTZXJ2aWNlczEiMCAGA1UEAxMZeG1sZ2F0ZXdheS5pdHMudXRleGFz
|
|
||||||
LmVkdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsmc+6+NjLmanvh+FvBziYdBwTiz+d/DZ
|
|
||||||
Uy2jyvij6f8Xly6zkhHLSsuBzw08wPzr2K+F359bf9T3uiZMuao//FBGtDrTYpvQwkn4PFZwSeY2
|
|
||||||
Ynw4edxp1JEWT2zfOY+QJDfNgpsYQ9hrHDwqnpbMVVqjdBq5RgTKGhFBj9kxEq0CAwEAATANBgkq
|
|
||||||
hkiG9w0BAQQFAAOBgQCPYGXF6oRbnjti3CPtjfwORoO7ab1QzNS9Z2rLMuPnt6POlm1A3UPEwCS8
|
|
||||||
6flTlAqg19Sh47H7+Iq/LuzotKvUE5ugK52QRNMa4c0OSaO5UEM5EfVox1pT9tZV1Z3whYYMhThg
|
|
||||||
oC4y/On0NUVMN5xfF/GpSACga/bVjoNvd8HWEg==
|
|
||||||
-----END CERTIFICATE-----
|
|
|
@ -1,15 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICxDCCAi0CBECcV/wwDQYJKoZIhvcNAQEEBQAwgagxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIEwVU
|
|
||||||
ZXhhczEPMA0GA1UEBxMGQXVzdGluMSowKAYDVQQKEyFUaGUgVW5pdmVyc2l0eSBvZiBUZXhhcyBh
|
|
||||||
dCBBdXN0aW4xKDAmBgNVBAsTH0luZm9ybWF0aW9uIFRlY2hub2xvZ3kgU2VydmljZXMxIjAgBgNV
|
|
||||||
BAMTGXhtbGdhdGV3YXkuaXRzLnV0ZXhhcy5lZHUwHhcNMDQwNTA4MDM0NjA0WhcNMDQwODA2MDM0
|
|
||||||
NjA0WjCBqDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZBdXN0aW4xKjAo
|
|
||||||
BgNVBAoTIVRoZSBVbml2ZXJzaXR5IG9mIFRleGFzIGF0IEF1c3RpbjEoMCYGA1UECxMfSW5mb3Jt
|
|
||||||
YXRpb24gVGVjaG5vbG9neSBTZXJ2aWNlczEiMCAGA1UEAxMZeG1sZ2F0ZXdheS5pdHMudXRleGFz
|
|
||||||
LmVkdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsmc+6+NjLmanvh+FvBziYdBwTiz+d/DZ
|
|
||||||
Uy2jyvij6f8Xly6zkhHLSsuBzw08wPzr2K+F359bf9T3uiZMuao//FBGtDrTYpvQwkn4PFZwSeY2
|
|
||||||
Ynw4edxp1JEWT2zfOY+QJDfNgpsYQ9hrHDwqnpbMVVqjdBq5RgTKGhFBj9kxEq0CAwEAATANBgkq
|
|
||||||
hkiG9w0BAQQFAAOBgQCPYGXF6oRbnjti3CPtjfwORoO7ab1QzNS9Z2rLMuPnt6POlm1A3UPEwCS8
|
|
||||||
6flTlAqg19Sh47H7+Iq/LuzotKvUE5ugK52QRNMa4c0OSaO5UEM5EfVox1pT9tZV1Z3whYYMhThg
|
|
||||||
oC4y/On0NUVMN5xfF/GpSACga/bVjoNvd8HWEg==
|
|
||||||
-----END CERTIFICATE-----
|
|
|
@ -1,12 +0,0 @@
|
||||||
local-values.conf
|
|
||||||
target
|
|
||||||
*~
|
|
||||||
bin
|
|
||||||
*.idea
|
|
||||||
*.iml
|
|
||||||
*.eml
|
|
||||||
.project
|
|
||||||
.settings
|
|
||||||
.classpath
|
|
||||||
/target
|
|
||||||
.springBeans
|
|
|
@ -1,132 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright 2018 The MIT Internet Trust Consortium
|
|
||||||
|
|
||||||
Portions copyright 2011-2013 The MITRE Corporation
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT 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>
|
|
||||||
<artifactId>openid-connect-parent</artifactId>
|
|
||||||
<groupId>org.mitre</groupId>
|
|
||||||
<version>1.3.4-SNAPSHOT</version>
|
|
||||||
<relativePath>..</relativePath>
|
|
||||||
</parent>
|
|
||||||
<artifactId>openid-connect-common</artifactId>
|
|
||||||
<description>OpenID Connect Common modules</description>
|
|
||||||
<name>OpenID Connect Common</name>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-core</artifactId>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>commons-logging</groupId>
|
|
||||||
<artifactId>commons-logging</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-webmvc</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.security</groupId>
|
|
||||||
<artifactId>spring-security-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.guava</groupId>
|
|
||||||
<artifactId>guava</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpclient</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.security.oauth</groupId>
|
|
||||||
<artifactId>spring-security-oauth2</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.nimbusds</groupId>
|
|
||||||
<artifactId>nimbus-jose-jwt</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.persistence</groupId>
|
|
||||||
<artifactId>javax.persistence</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.code.gson</groupId>
|
|
||||||
<artifactId>gson</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-api</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.bouncycastle</groupId>
|
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<source>${java-version}</source>
|
|
||||||
<target>${java-version}</target>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<!-- BUILD SOURCE FILES -->
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-source-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>attach-sources</id>
|
|
||||||
<goals>
|
|
||||||
<goal>jar-no-fork</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<!-- BUILD JavaDoc FILES -->
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>attach-sources</id>
|
|
||||||
<goals>
|
|
||||||
<goal>jar</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,3 +0,0 @@
|
||||||
Manifest-Version: 1.0
|
|
||||||
Class-Path:
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue