Merge branch 'master' into multiparty

multiparty
Justin Richer 2015-12-21 16:01:42 -05:00
commit 0f3e33e870
33 changed files with 486 additions and 145 deletions

View File

@ -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.

View File

@ -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`.

View File

@ -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>

View File

@ -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

View File

@ -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) {

View File

@ -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;

View File

@ -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>

View File

@ -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");
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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>

View File

@ -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>

View File

@ -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
}

View File

@ -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();

View File

@ -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",

View File

@ -343,7 +343,7 @@
"more": "Mer",
"about": {
"title": "Om&nbsp;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",

View File

@ -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.

View File

@ -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.

View File

@ -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>

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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.");
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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.

View File

@ -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>