Merge branch 'master' into multiparty
commit
0f3e33e870
|
@ -1,5 +1,5 @@
|
|||
Copyright 2015 The MITRE Corporation
|
||||
and the MIT Kerberos and Internet Trust Consortium
|
||||
and 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.
|
||||
|
|
|
@ -31,4 +31,4 @@ The authors and key contributors of the project include:
|
|||
|
||||
|
||||
Copyright ©2015, [The MITRE Corporation](http://www.mitre.org/)
|
||||
and the [MIT Internet Trust Consortium](http://kit.mit.edu/). Licensed under the Apache 2.0 license, for details see `LICENSE.txt`.
|
||||
and the [MIT Internet Trust Consortium](http://www.mit-trust.org/). Licensed under the Apache 2.0 license, for details see `LICENSE.txt`.
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<parent>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<groupId>org.mitre</groupId>
|
||||
<version>1.2.3-SNAPSHOT</version>
|
||||
<version>1.2.4-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>openid-connect-client</artifactId>
|
||||
|
|
|
@ -298,6 +298,7 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
|
|||
validatedToken = restTemplate.postForObject(introspectionUrl, form, String.class);
|
||||
} catch (RestClientException rce) {
|
||||
logger.error("validateToken", rce);
|
||||
return null;
|
||||
}
|
||||
if (validatedToken != null) {
|
||||
// parse the json
|
||||
|
|
|
@ -18,6 +18,8 @@ package org.mitre.openid.connect.client;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
|
@ -37,11 +39,15 @@ 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.
|
||||
* Utility class to fetch userinfo from the userinfo endpoint, if available. Caches the results.
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
|
@ -52,75 +58,95 @@ public class UserInfoFetcher {
|
|||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(UserInfoFetcher.class);
|
||||
|
||||
private LoadingCache<PendingOIDCAuthenticationToken, UserInfo> cache;
|
||||
|
||||
public UserInfoFetcher() {
|
||||
cache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch
|
||||
.maximumSize(100)
|
||||
.build(new UserInfoLoader());
|
||||
}
|
||||
|
||||
public UserInfo loadUserInfo(final PendingOIDCAuthenticationToken token) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
return cache.get(token);
|
||||
} catch (UncheckedExecutionException | ExecutionException e) {
|
||||
logger.warn("Couldn't load User Info from token: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
// if we got this far, try to actually get the userinfo
|
||||
HttpClient httpClient = HttpClientBuilder.create()
|
||||
.useSystemProperties()
|
||||
.build();
|
||||
|
||||
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||
|
||||
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, return null
|
||||
}
|
||||
|
||||
|
||||
private class UserInfoLoader extends CacheLoader<PendingOIDCAuthenticationToken, UserInfo> {
|
||||
private HttpClient httpClient = HttpClientBuilder.create()
|
||||
.useSystemProperties()
|
||||
.build();
|
||||
private HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||
|
||||
public UserInfo load(final PendingOIDCAuthenticationToken token) {
|
||||
|
||||
ServerConfiguration serverConfiguration = token.getServerConfiguration();
|
||||
|
||||
if (serverConfiguration == null) {
|
||||
logger.warn("No server configuration found.");
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error fetching userinfo", e);
|
||||
return null;
|
||||
|
||||
if (Strings.isNullOrEmpty(serverConfiguration.getUserInfoUri())) {
|
||||
logger.warn("No userinfo endpoint, not fetching.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
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, return null
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error fetching userinfo", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected UserInfo fromJson(JsonObject userInfoJson) {
|
||||
|
|
|
@ -39,6 +39,8 @@ 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;
|
||||
|
@ -191,15 +193,18 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf
|
|||
|
||||
HttpEntity<String> entity = new HttpEntity<>(serializedClient, headers);
|
||||
|
||||
String registered = restTemplate.postForObject(serverConfig.getRegistrationEndpointUri(), entity, String.class);
|
||||
// TODO: handle HTTP errors
|
||||
|
||||
RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered);
|
||||
|
||||
// save this client for later
|
||||
registeredClientService.save(serverConfig.getIssuer(), client);
|
||||
|
||||
return client;
|
||||
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) {
|
||||
|
@ -211,12 +216,16 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf
|
|||
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
String registered = restTemplate.exchange(knownClient.getRegistrationClientUri(), HttpMethod.GET, entity, String.class).getBody();
|
||||
// TODO: handle HTTP errors
|
||||
|
||||
RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered);
|
||||
|
||||
return client;
|
||||
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;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<parent>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<groupId>org.mitre</groupId>
|
||||
<version>1.2.3-SNAPSHOT</version>
|
||||
<version>1.2.4-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>openid-connect-common</artifactId>
|
||||
|
|
|
@ -32,12 +32,14 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
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.util.concurrent.UncheckedExecutionException;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
|
||||
/**
|
||||
|
@ -136,14 +138,18 @@ public class JWKSetCacheService {
|
|||
*/
|
||||
@Override
|
||||
public JWTEncryptionAndDecryptionService load(String key) throws Exception {
|
||||
String jsonString = restTemplate.getForObject(key, String.class);
|
||||
JWKSet jwkSet = JWKSet.parse(jsonString);
|
||||
|
||||
JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet);
|
||||
|
||||
JWTEncryptionAndDecryptionService service = new DefaultJWTEncryptionAndDecryptionService(keyStore);
|
||||
|
||||
return service;
|
||||
try {
|
||||
String jsonString = restTemplate.getForObject(key, String.class);
|
||||
JWKSet jwkSet = JWKSet.parse(jsonString);
|
||||
|
||||
JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet);
|
||||
|
||||
JWTEncryptionAndDecryptionService service = new DefaultJWTEncryptionAndDecryptionService(keyStore);
|
||||
|
||||
return service;
|
||||
} catch (JsonParseException | RestClientException e) {
|
||||
throw new IllegalArgumentException("Unable to load JWK Set");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2015 The MITRE Corporation
|
||||
* and 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.model;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public class CachedImage {
|
||||
|
||||
private byte[] data;
|
||||
private String contentType;
|
||||
private long length;
|
||||
|
||||
/**
|
||||
* @return the data
|
||||
*/
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
/**
|
||||
* @param data the data to set
|
||||
*/
|
||||
public void setData(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
/**
|
||||
* @return the contentType
|
||||
*/
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
/**
|
||||
* @param contentType the contentType to set
|
||||
*/
|
||||
public void setContentType(String contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
/**
|
||||
* @return the length
|
||||
*/
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
/**
|
||||
* @param length the length to set
|
||||
*/
|
||||
public void setLength(long length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2015 The MITRE Corporation
|
||||
* and 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.service;
|
||||
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.openid.connect.model.CachedImage;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public interface ClientLogoLoadingService {
|
||||
|
||||
/**
|
||||
* @param client
|
||||
* @return
|
||||
*/
|
||||
public CachedImage getLogo(ClientDetailsEntity client);
|
||||
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
<parent>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.2.3-SNAPSHOT</version>
|
||||
<version>1.2.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>openid-connect-server-webapp</artifactId>
|
||||
<packaging>war</packaging>
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
<c:if test="${ not empty client.logoUri }">
|
||||
<ul class="thumbnails">
|
||||
<li class="span5">
|
||||
<a class="thumbnail" data-toggle="modal" data-target="#logoModal"><img src="${ fn:escapeXml(client.logoUri) }" /></a>
|
||||
<a class="thumbnail" data-toggle="modal" data-target="#logoModal"><img src="api/clients/${ client.id }/logo" /></a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Modal -->
|
||||
|
@ -103,7 +103,7 @@
|
|||
</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<img src="${ fn:escapeXml(client.logoUri) }" />
|
||||
<img src="api/clients/${ client.id }/logo" />
|
||||
<c:if test="${ not empty client.clientUri }">
|
||||
<a href="<c:out value="${ client.clientUri }" />"><c:out value="${ client.clientUri }" /></a>
|
||||
</c:if>
|
||||
|
|
|
@ -186,3 +186,9 @@ h1,label {
|
|||
.user-profile dd, .user-profile dt {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* Client table images */
|
||||
.client-logo {
|
||||
max-width: 64px;
|
||||
max-height: 64px
|
||||
}
|
||||
|
|
|
@ -1002,6 +1002,11 @@ $(function () {
|
|||
$.ajaxSetup({cache:false});
|
||||
app = new AppRouter();
|
||||
|
||||
app.on('route', function(name, args) {
|
||||
// scroll to top of page on new route selection
|
||||
$("html, body").animate({ scrollTop: 0 }, "slow");
|
||||
});
|
||||
|
||||
// grab all hashed URLs and send them through the app router instead
|
||||
$(document).on('click', 'a[href^="manage/#"]', function(event) {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -364,7 +364,7 @@
|
|||
"more": "More",
|
||||
"about": {
|
||||
"title": "About",
|
||||
"body": "This OpenID Connect service is built from the MITREid Connect Open Source project, from \n<a href=\"http://www.mitre.org/\">The MITRE Corporation</a> and the <a href=\"http://kit.mit.edu/\">MIT Kerberos and Internet Trust Consortium</a>."
|
||||
"body": "This OpenID Connect service is built from the MITREid Connect Open Source project, from \n<a href=\"http://www.mitre.org/\">The MITRE Corporation</a> and the <a href=\"http://kit.mit.edu/\">MIT Internet Trust Consortium</a>."
|
||||
},
|
||||
"contact": {
|
||||
"title": "Contact",
|
||||
|
|
|
@ -343,7 +343,7 @@
|
|||
"more": "Mer",
|
||||
"about": {
|
||||
"title": "Om tjänsten",
|
||||
"body": "\nDenna OpenID Connect-tjänst är byggd från det öpnna källkodsprojektet MITREid, av \n<a href=\"http://www.mitre.org/\">MITRE Corporation</a> och <a href=\"http://kit.mit.edu/\">MIT Kerberos and Internet Trust Consortium</a>."
|
||||
"body": "\nDenna OpenID Connect-tjänst är byggd från det öpnna källkodsprojektet MITREid, av \n<a href=\"http://www.mitre.org/\">MITRE Corporation</a> och <a href=\"http://kit.mit.edu/\">MIT Internet Trust Consortium</a>."
|
||||
},
|
||||
"contact": {
|
||||
"title": "Kontakt",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
Copyright 2015 The MITRE Corporation
|
||||
and the MIT Kerberos and Internet Trust Consortium
|
||||
and 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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
Copyright 2015 The MITRE Corporation
|
||||
and the MIT Kerberos and Internet Trust Consortium
|
||||
and 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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
Copyright 2015 The MITRE Corporation
|
||||
and the MIT Kerberos and Internet Trust Consortium
|
||||
and 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.
|
||||
|
@ -28,20 +28,29 @@
|
|||
</td>
|
||||
|
||||
<td>
|
||||
<div>
|
||||
<span class="clientid-substring" title="<%- client.clientId %> (click to display client ID)"><%- client.clientName != null ? client.clientName : ( client.clientId.substr(0,8) + '...' ) %></span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" readonly style="cursor: text" class="clientid-full input-xxlarge" value="<%- client.clientId %>" />
|
||||
</div>
|
||||
<div>
|
||||
<% if (client.dynamicallyRegistered) { %>
|
||||
<span class="label label-inverse dynamically-registered"><i class="icon-globe icon-white"></i></span>
|
||||
<% } %>
|
||||
<% if (client.allowIntrospection) { %>
|
||||
<span class="label allow-introspection"><i class="icon-eye-open icon-white"></i></span>
|
||||
|
||||
<div class="media">
|
||||
<% if (client.logoUri) { %>
|
||||
<span class="pull-left"><img class="media-object client-logo" src="api/clients/<%- client.id ->/logo"></span>
|
||||
<% } %>
|
||||
|
||||
<div class="media-body">
|
||||
<span class="clientid-substring" title="<%- client.clientId %> (click to display client ID)"><%- client.clientName != null ? client.clientName : ( client.clientId.substr(0,8) + '...' ) %></span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" readonly style="cursor: text" class="clientid-full input-xxlarge" value="<%- client.clientId %>" />
|
||||
</div>
|
||||
<div>
|
||||
<% if (client.dynamicallyRegistered) { %>
|
||||
<span class="label label-inverse dynamically-registered"><i class="icon-globe icon-white"></i></span>
|
||||
<% } %>
|
||||
<% if (client.allowIntrospection) { %>
|
||||
<span class="label allow-introspection"><i class="icon-eye-open icon-white"></i></span>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<small class="muted" title="<%- hoverCreationDate %>"><i class="icon-time"></i> <span data-i18n="client.client-table.registered">Registered</span> <%- displayCreationDate %></small>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
Copyright 2015 The MITRE Corporation
|
||||
and the MIT Kerberos and Internet Trust Consortium
|
||||
and 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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
Copyright 2015 The MITRE Corporation
|
||||
and the MIT Kerberos and Internet Trust Consortium
|
||||
and 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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
Copyright 2015 The MITRE Corporation
|
||||
and the MIT Kerberos and Internet Trust Consortium
|
||||
and 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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
Copyright 2015 The MITRE Corporation
|
||||
and the MIT Kerberos and Internet Trust Consortium
|
||||
and 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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
Copyright 2015 The MITRE Corporation
|
||||
and the MIT Kerberos and Internet Trust Consortium
|
||||
and 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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
Copyright 2015 The MITRE Corporation
|
||||
and the MIT Kerberos and Internet Trust Consortium
|
||||
and 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.
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<parent>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.2.3-SNAPSHOT</version>
|
||||
<version>1.2.4-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<build>
|
||||
|
|
|
@ -46,6 +46,7 @@ import org.springframework.security.core.AuthenticationException;
|
|||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
|
||||
import org.springframework.security.oauth2.provider.ClientAlreadyExistsException;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Request;
|
||||
import org.springframework.security.oauth2.provider.TokenRequest;
|
||||
|
@ -84,6 +85,10 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
|
|||
@Autowired
|
||||
private SystemScopeService scopeService;
|
||||
|
||||
@Autowired
|
||||
private ApprovedSiteService approvedSiteService;
|
||||
|
||||
|
||||
@Override
|
||||
public Set<OAuth2AccessTokenEntity> getAllAccessTokensForUser(String id) {
|
||||
|
||||
|
@ -91,7 +96,7 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
|
|||
Set<OAuth2AccessTokenEntity> results = Sets.newLinkedHashSet();
|
||||
|
||||
for (OAuth2AccessTokenEntity token : all) {
|
||||
if (token.getAuthenticationHolder().getAuthentication().getName().equals(id)) {
|
||||
if (clearExpiredAccessToken(token) != null && token.getAuthenticationHolder().getAuthentication().getName().equals(id)) {
|
||||
results.add(token);
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +111,7 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
|
|||
Set<OAuth2RefreshTokenEntity> results = Sets.newLinkedHashSet();
|
||||
|
||||
for (OAuth2RefreshTokenEntity token : all) {
|
||||
if (token.getAuthenticationHolder().getAuthentication().getName().equals(id)) {
|
||||
if (clearExpiredRefreshToken(token) != null && token.getAuthenticationHolder().getAuthentication().getName().equals(id)) {
|
||||
results.add(token);
|
||||
}
|
||||
}
|
||||
|
@ -116,18 +121,50 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
|
|||
|
||||
@Override
|
||||
public OAuth2AccessTokenEntity getAccessTokenById(Long id) {
|
||||
return tokenRepository.getAccessTokenById(id);
|
||||
return clearExpiredAccessToken(tokenRepository.getAccessTokenById(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2RefreshTokenEntity getRefreshTokenById(Long id) {
|
||||
return tokenRepository.getRefreshTokenById(id);
|
||||
return clearExpiredRefreshToken(tokenRepository.getRefreshTokenById(id));
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private ApprovedSiteService approvedSiteService;
|
||||
|
||||
|
||||
/**
|
||||
* Utility function to delete an access token that's expired before returning it.
|
||||
* @param token the token to check
|
||||
* @return null if the token is null or expired, the input token (unchanged) if it hasn't
|
||||
*/
|
||||
private OAuth2AccessTokenEntity clearExpiredAccessToken(OAuth2AccessTokenEntity token) {
|
||||
if (token == null) {
|
||||
return null;
|
||||
} else if (token.isExpired()) {
|
||||
// immediately revoke expired token
|
||||
logger.debug("Clearing expired access token: " + token.getValue());
|
||||
revokeAccessToken(token);
|
||||
return null;
|
||||
} else {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to delete a refresh token that's expired before returning it.
|
||||
* @param token the token to check
|
||||
* @return null if the token is null or expired, the input token (unchanged) if it hasn't
|
||||
*/
|
||||
private OAuth2RefreshTokenEntity clearExpiredRefreshToken(OAuth2RefreshTokenEntity token) {
|
||||
if (token == null) {
|
||||
return null;
|
||||
} else if (token.isExpired()) {
|
||||
// immediately revoke expired token
|
||||
logger.debug("Clearing expired refresh token: " + token.getValue());
|
||||
revokeRefreshToken(token);
|
||||
return null;
|
||||
} else {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentication) throws AuthenticationException, InvalidClientException {
|
||||
if (authentication != null && authentication.getOAuth2Request() != null) {
|
||||
|
@ -238,7 +275,7 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
|
|||
@Override
|
||||
public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, TokenRequest authRequest) throws AuthenticationException {
|
||||
|
||||
OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenByValue(refreshTokenValue);
|
||||
OAuth2RefreshTokenEntity refreshToken = clearExpiredRefreshToken(tokenRepository.getRefreshTokenByValue(refreshTokenValue));
|
||||
|
||||
if (refreshToken == null) {
|
||||
throw new InvalidTokenException("Invalid refresh token: " + refreshTokenValue);
|
||||
|
@ -331,19 +368,13 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
|
|||
@Override
|
||||
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException {
|
||||
|
||||
OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenByValue(accessTokenValue);
|
||||
OAuth2AccessTokenEntity accessToken = clearExpiredAccessToken(tokenRepository.getAccessTokenByValue(accessTokenValue));
|
||||
|
||||
if (accessToken == null) {
|
||||
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
|
||||
} else {
|
||||
return accessToken.getAuthenticationHolder().getAuthentication();
|
||||
}
|
||||
|
||||
if (accessToken.isExpired()) {
|
||||
//tokenRepository.removeAccessToken(accessToken);
|
||||
revokeAccessToken(accessToken);
|
||||
throw new InvalidTokenException("Expired access token: " + accessTokenValue);
|
||||
}
|
||||
|
||||
return accessToken.getAuthenticationHolder().getAuthentication();
|
||||
}
|
||||
|
||||
|
||||
|
@ -352,11 +383,10 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
|
|||
*/
|
||||
@Override
|
||||
public OAuth2AccessTokenEntity readAccessToken(String accessTokenValue) throws AuthenticationException {
|
||||
OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenByValue(accessTokenValue);
|
||||
OAuth2AccessTokenEntity accessToken = clearExpiredAccessToken(tokenRepository.getAccessTokenByValue(accessTokenValue));
|
||||
if (accessToken == null) {
|
||||
throw new InvalidTokenException("Access token for value " + accessTokenValue + " was not found");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return accessToken;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2015 The MITRE Corporation
|
||||
* and 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.service.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.openid.connect.model.CachedImage;
|
||||
import org.mitre.openid.connect.service.ClientLogoLoadingService;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
@Service("inMemoryClientLogoLoadingService")
|
||||
public class InMemoryClientLogoLoadingService implements ClientLogoLoadingService {
|
||||
|
||||
private LoadingCache<ClientDetailsEntity, CachedImage> cache;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public InMemoryClientLogoLoadingService() {
|
||||
|
||||
cache = CacheBuilder.newBuilder()
|
||||
.maximumSize(100)
|
||||
.expireAfterAccess(14, TimeUnit.DAYS)
|
||||
.build(new ClientLogoFetcher());
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.mitre.openid.connect.service.ClientLogoLoadingService#getLogo(org.mitre.oauth2.model.ClientDetailsEntity)
|
||||
*/
|
||||
@Override
|
||||
public CachedImage getLogo(ClientDetailsEntity client) {
|
||||
try {
|
||||
if (client != null && !Strings.isNullOrEmpty(client.getLogoUri())) {
|
||||
return cache.get(client);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (UncheckedExecutionException | ExecutionException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public class ClientLogoFetcher extends CacheLoader<ClientDetailsEntity, CachedImage> {
|
||||
private HttpClient httpClient = HttpClientBuilder.create().useSystemProperties().build();
|
||||
private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.google.common.cache.CacheLoader#load(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public CachedImage load(ClientDetailsEntity key) throws Exception {
|
||||
try {
|
||||
HttpResponse response = httpClient.execute(new HttpGet(key.getLogoUri()));
|
||||
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
||||
CachedImage image = new CachedImage();
|
||||
|
||||
image.setContentType(entity.getContentType().getValue());
|
||||
image.setLength(entity.getContentLength());
|
||||
image.setData(IOUtils.toByteArray(entity.getContent()));
|
||||
|
||||
return image;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Unable to load client image.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -24,6 +24,8 @@ import org.mitre.oauth2.model.ClientDetailsEntity;
|
|||
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
|
||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||
import org.mitre.oauth2.web.AuthenticationUtilities;
|
||||
import org.mitre.openid.connect.model.CachedImage;
|
||||
import org.mitre.openid.connect.service.ClientLogoLoadingService;
|
||||
import org.mitre.openid.connect.view.ClientEntityViewForAdmins;
|
||||
import org.mitre.openid.connect.view.ClientEntityViewForUsers;
|
||||
import org.mitre.openid.connect.view.HttpCodeView;
|
||||
|
@ -32,8 +34,10 @@ import org.mitre.openid.connect.view.JsonErrorView;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
@ -74,6 +78,9 @@ public class ClientAPI {
|
|||
@Autowired
|
||||
private ClientDetailsEntityService clientService;
|
||||
|
||||
@Autowired
|
||||
private ClientLogoLoadingService clientLogoLoadingService;
|
||||
|
||||
private JsonParser parser = new JsonParser();
|
||||
|
||||
private Gson gson = new GsonBuilder()
|
||||
|
@ -393,5 +400,30 @@ public class ClientAPI {
|
|||
return ClientEntityViewForUsers.VIEWNAME;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the logo image for a client
|
||||
* @param id
|
||||
*/
|
||||
@RequestMapping(value = "/{id}/logo", method=RequestMethod.GET, produces = { MediaType.IMAGE_GIF_VALUE, MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE })
|
||||
public ResponseEntity<byte[]> getClientLogo(@PathVariable("id") Long id, Model model) {
|
||||
|
||||
ClientDetailsEntity client = clientService.getClientById(id);
|
||||
|
||||
if (client == null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
} else if (Strings.isNullOrEmpty(client.getLogoUri())) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
// get the image from cache
|
||||
CachedImage image = clientLogoLoadingService.getLogo(client);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.parseMediaType(image.getContentType()));
|
||||
headers.setContentLength(image.getLength());
|
||||
|
||||
return new ResponseEntity<>(image.getData(), headers, HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
6
pom.xml
6
pom.xml
|
@ -19,7 +19,7 @@
|
|||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.2.3-SNAPSHOT</version>
|
||||
<version>1.2.4-SNAPSHOT</version>
|
||||
<name>MITREid Connect</name>
|
||||
<packaging>pom</packaging>
|
||||
<parent>
|
||||
|
@ -259,7 +259,7 @@
|
|||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>4.1.8.RELEASE</version>
|
||||
<version>4.1.9.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
@ -280,7 +280,7 @@
|
|||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-bom</artifactId>
|
||||
<version>3.2.8.RELEASE</version>
|
||||
<version>3.2.9.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<parent>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.2.3-SNAPSHOT</version>
|
||||
<version>1.2.4-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>uma-server-webapp</artifactId>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
Copyright 2015 The MITRE Corporation
|
||||
and the MIT Kerberos and Internet Trust Consortium
|
||||
and 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.
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<parent>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.2.3-SNAPSHOT</version>
|
||||
<version>1.2.4-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>uma-server</artifactId>
|
||||
|
|
Loading…
Reference in New Issue