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 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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/) 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> <parent>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<version>1.2.3-SNAPSHOT</version> <version>1.2.4-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<artifactId>openid-connect-client</artifactId> <artifactId>openid-connect-client</artifactId>

View File

@ -298,6 +298,7 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
validatedToken = restTemplate.postForObject(introspectionUrl, form, String.class); validatedToken = restTemplate.postForObject(introspectionUrl, form, String.class);
} catch (RestClientException rce) { } catch (RestClientException rce) {
logger.error("validateToken", rce); logger.error("validateToken", rce);
return null;
} }
if (validatedToken != null) { if (validatedToken != null) {
// parse the json // parse the json

View File

@ -18,6 +18,8 @@ package org.mitre.openid.connect.client;
import java.io.IOException; import java.io.IOException;
import java.net.URI; 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.HttpClient;
import org.apache.http.client.utils.URIBuilder; import org.apache.http.client.utils.URIBuilder;
@ -37,11 +39,15 @@ import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import com.google.common.base.Strings; 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.JsonObject;
import com.google.gson.JsonParser; 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 * @author jricher
* *
*/ */
@ -52,7 +58,33 @@ public class UserInfoFetcher {
*/ */
private static final Logger logger = LoggerFactory.getLogger(UserInfoFetcher.class); 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) { 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 HttpClient httpClient = HttpClientBuilder.create()
.useSystemProperties()
.build();
private HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
public UserInfo load(final PendingOIDCAuthenticationToken token) {
ServerConfiguration serverConfiguration = token.getServerConfiguration(); ServerConfiguration serverConfiguration = token.getServerConfiguration();
@ -68,13 +100,6 @@ public class UserInfoFetcher {
try { try {
// 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; String userInfoString = null;
if (serverConfiguration.getUserInfoTokenMethod() == null || serverConfiguration.getUserInfoTokenMethod().equals(UserInfoTokenMethod.HEADER)) { if (serverConfiguration.getUserInfoTokenMethod() == null || serverConfiguration.getUserInfoTokenMethod().equals(UserInfoTokenMethod.HEADER)) {
@ -122,6 +147,7 @@ public class UserInfoFetcher {
} }
} }
}
protected UserInfo fromJson(JsonObject userInfoJson) { protected UserInfo fromJson(JsonObject userInfoJson) {
return DefaultUserInfo.fromJson(userInfoJson); return DefaultUserInfo.fromJson(userInfoJson);

View File

@ -39,6 +39,8 @@ import org.springframework.http.MediaType;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.oauth2.common.OAuth2AccessToken; 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 org.springframework.web.client.RestTemplate;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
@ -191,8 +193,8 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf
HttpEntity<String> entity = new HttpEntity<>(serializedClient, headers); HttpEntity<String> entity = new HttpEntity<>(serializedClient, headers);
try {
String registered = restTemplate.postForObject(serverConfig.getRegistrationEndpointUri(), entity, String.class); String registered = restTemplate.postForObject(serverConfig.getRegistrationEndpointUri(), entity, String.class);
// TODO: handle HTTP errors
RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered); RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered);
@ -200,6 +202,9 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf
registeredClientService.save(serverConfig.getIssuer(), client); registeredClientService.save(serverConfig.getIssuer(), client);
return client; return client;
} catch (RestClientException rce) {
throw new InvalidClientException("Error registering client with server");
}
} else { } else {
if (knownClient.getClientId() == null) { if (knownClient.getClientId() == null) {
@ -211,12 +216,16 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf
HttpEntity<String> entity = new HttpEntity<>(headers); HttpEntity<String> entity = new HttpEntity<>(headers);
try {
String registered = restTemplate.exchange(knownClient.getRegistrationClientUri(), HttpMethod.GET, entity, String.class).getBody(); String registered = restTemplate.exchange(knownClient.getRegistrationClientUri(), HttpMethod.GET, entity, String.class).getBody();
// TODO: handle HTTP errors // TODO: handle HTTP errors
RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered); RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered);
return client; return client;
} catch (RestClientException rce) {
throw new InvalidClientException("Error loading previously registered client information from server");
}
} else { } else {
// it's got a client ID from the store, don't bother trying to load it // it's got a client ID from the store, don't bother trying to load it
return knownClient; return knownClient;

View File

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<version>1.2.3-SNAPSHOT</version> <version>1.2.4-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<artifactId>openid-connect-common</artifactId> <artifactId>openid-connect-common</artifactId>

View File

@ -32,12 +32,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.gson.JsonParseException;
import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.JWKSet;
/** /**
@ -136,6 +138,7 @@ public class JWKSetCacheService {
*/ */
@Override @Override
public JWTEncryptionAndDecryptionService load(String key) throws Exception { public JWTEncryptionAndDecryptionService load(String key) throws Exception {
try {
String jsonString = restTemplate.getForObject(key, String.class); String jsonString = restTemplate.getForObject(key, String.class);
JWKSet jwkSet = JWKSet.parse(jsonString); JWKSet jwkSet = JWKSet.parse(jsonString);
@ -144,6 +147,9 @@ public class JWKSetCacheService {
JWTEncryptionAndDecryptionService service = new DefaultJWTEncryptionAndDecryptionService(keyStore); JWTEncryptionAndDecryptionService service = new DefaultJWTEncryptionAndDecryptionService(keyStore);
return service; 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> <parent>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<version>1.2.3-SNAPSHOT</version> <version>1.2.4-SNAPSHOT</version>
</parent> </parent>
<artifactId>openid-connect-server-webapp</artifactId> <artifactId>openid-connect-server-webapp</artifactId>
<packaging>war</packaging> <packaging>war</packaging>

View File

@ -84,7 +84,7 @@
<c:if test="${ not empty client.logoUri }"> <c:if test="${ not empty client.logoUri }">
<ul class="thumbnails"> <ul class="thumbnails">
<li class="span5"> <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> </li>
</ul> </ul>
<!-- Modal --> <!-- Modal -->
@ -103,7 +103,7 @@
</h3> </h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<img src="${ fn:escapeXml(client.logoUri) }" /> <img src="api/clients/${ client.id }/logo" />
<c:if test="${ not empty client.clientUri }"> <c:if test="${ not empty client.clientUri }">
<a href="<c:out value="${ client.clientUri }" />"><c:out value="${ client.clientUri }" /></a> <a href="<c:out value="${ client.clientUri }" />"><c:out value="${ client.clientUri }" /></a>
</c:if> </c:if>

View File

@ -186,3 +186,9 @@ h1,label {
.user-profile dd, .user-profile dt { .user-profile dd, .user-profile dt {
height: 20px; height: 20px;
} }
/* Client table images */
.client-logo {
max-width: 64px;
max-height: 64px
}

View File

@ -1002,6 +1002,11 @@ $(function () {
$.ajaxSetup({cache:false}); $.ajaxSetup({cache:false});
app = new AppRouter(); 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 // grab all hashed URLs and send them through the app router instead
$(document).on('click', 'a[href^="manage/#"]', function(event) { $(document).on('click', 'a[href^="manage/#"]', function(event) {
event.preventDefault(); event.preventDefault();

View File

@ -364,7 +364,7 @@
"more": "More", "more": "More",
"about": { "about": {
"title": "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": { "contact": {
"title": "Contact", "title": "Contact",

View File

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

View File

@ -1,6 +1,6 @@
<!-- <!--
Copyright 2015 The MITRE Corporation 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -28,7 +28,13 @@
</td> </td>
<td> <td>
<div>
<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> <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>
<div> <div>
@ -42,6 +48,9 @@
<span class="label allow-introspection"><i class="icon-eye-open icon-white"></i></span> <span class="label allow-introspection"><i class="icon-eye-open icon-white"></i></span>
<% } %> <% } %>
</div> </div>
</div>
<div> <div>
<small class="muted" title="<%- hoverCreationDate %>"><i class="icon-time"></i> <span data-i18n="client.client-table.registered">Registered</span> <%- displayCreationDate %></small> <small class="muted" title="<%- hoverCreationDate %>"><i class="icon-time"></i> <span data-i18n="client.client-table.registered">Registered</span> <%- displayCreationDate %></small>
</div> </div>

View File

@ -1,6 +1,6 @@
<!-- <!--
Copyright 2015 The MITRE Corporation 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -22,7 +22,7 @@
<parent> <parent>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<version>1.2.3-SNAPSHOT</version> <version>1.2.4-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<build> <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.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; 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.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.TokenRequest;
@ -84,6 +85,10 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
@Autowired @Autowired
private SystemScopeService scopeService; private SystemScopeService scopeService;
@Autowired
private ApprovedSiteService approvedSiteService;
@Override @Override
public Set<OAuth2AccessTokenEntity> getAllAccessTokensForUser(String id) { public Set<OAuth2AccessTokenEntity> getAllAccessTokensForUser(String id) {
@ -91,7 +96,7 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
Set<OAuth2AccessTokenEntity> results = Sets.newLinkedHashSet(); Set<OAuth2AccessTokenEntity> results = Sets.newLinkedHashSet();
for (OAuth2AccessTokenEntity token : all) { for (OAuth2AccessTokenEntity token : all) {
if (token.getAuthenticationHolder().getAuthentication().getName().equals(id)) { if (clearExpiredAccessToken(token) != null && token.getAuthenticationHolder().getAuthentication().getName().equals(id)) {
results.add(token); results.add(token);
} }
} }
@ -106,7 +111,7 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
Set<OAuth2RefreshTokenEntity> results = Sets.newLinkedHashSet(); Set<OAuth2RefreshTokenEntity> results = Sets.newLinkedHashSet();
for (OAuth2RefreshTokenEntity token : all) { for (OAuth2RefreshTokenEntity token : all) {
if (token.getAuthenticationHolder().getAuthentication().getName().equals(id)) { if (clearExpiredRefreshToken(token) != null && token.getAuthenticationHolder().getAuthentication().getName().equals(id)) {
results.add(token); results.add(token);
} }
} }
@ -116,17 +121,49 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
@Override @Override
public OAuth2AccessTokenEntity getAccessTokenById(Long id) { public OAuth2AccessTokenEntity getAccessTokenById(Long id) {
return tokenRepository.getAccessTokenById(id); return clearExpiredAccessToken(tokenRepository.getAccessTokenById(id));
} }
@Override @Override
public OAuth2RefreshTokenEntity getRefreshTokenById(Long id) { 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 @Override
public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentication) throws AuthenticationException, InvalidClientException { public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentication) throws AuthenticationException, InvalidClientException {
@ -238,7 +275,7 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
@Override @Override
public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, TokenRequest authRequest) throws AuthenticationException { public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, TokenRequest authRequest) throws AuthenticationException {
OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenByValue(refreshTokenValue); OAuth2RefreshTokenEntity refreshToken = clearExpiredRefreshToken(tokenRepository.getRefreshTokenByValue(refreshTokenValue));
if (refreshToken == null) { if (refreshToken == null) {
throw new InvalidTokenException("Invalid refresh token: " + refreshTokenValue); throw new InvalidTokenException("Invalid refresh token: " + refreshTokenValue);
@ -331,20 +368,14 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
@Override @Override
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException { public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException {
OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenByValue(accessTokenValue); OAuth2AccessTokenEntity accessToken = clearExpiredAccessToken(tokenRepository.getAccessTokenByValue(accessTokenValue));
if (accessToken == null) { if (accessToken == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue); throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
} } else {
if (accessToken.isExpired()) {
//tokenRepository.removeAccessToken(accessToken);
revokeAccessToken(accessToken);
throw new InvalidTokenException("Expired access token: " + accessTokenValue);
}
return accessToken.getAuthenticationHolder().getAuthentication(); return accessToken.getAuthenticationHolder().getAuthentication();
} }
}
/** /**
@ -352,11 +383,10 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
*/ */
@Override @Override
public OAuth2AccessTokenEntity readAccessToken(String accessTokenValue) throws AuthenticationException { public OAuth2AccessTokenEntity readAccessToken(String accessTokenValue) throws AuthenticationException {
OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenByValue(accessTokenValue); OAuth2AccessTokenEntity accessToken = clearExpiredAccessToken(tokenRepository.getAccessTokenByValue(accessTokenValue));
if (accessToken == null) { if (accessToken == null) {
throw new InvalidTokenException("Access token for value " + accessTokenValue + " was not found"); throw new InvalidTokenException("Access token for value " + accessTokenValue + " was not found");
} } else {
else {
return accessToken; 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.model.ClientDetailsEntity.AuthMethod;
import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.mitre.oauth2.web.AuthenticationUtilities; 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.ClientEntityViewForAdmins;
import org.mitre.openid.connect.view.ClientEntityViewForUsers; import org.mitre.openid.connect.view.ClientEntityViewForUsers;
import org.mitre.openid.connect.view.HttpCodeView; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -74,6 +78,9 @@ public class ClientAPI {
@Autowired @Autowired
private ClientDetailsEntityService clientService; private ClientDetailsEntityService clientService;
@Autowired
private ClientLogoLoadingService clientLogoLoadingService;
private JsonParser parser = new JsonParser(); private JsonParser parser = new JsonParser();
private Gson gson = new GsonBuilder() private Gson gson = new GsonBuilder()
@ -394,4 +401,29 @@ public class ClientAPI {
} }
} }
/**
* 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> <modelVersion>4.0.0</modelVersion>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<version>1.2.3-SNAPSHOT</version> <version>1.2.4-SNAPSHOT</version>
<name>MITREid Connect</name> <name>MITREid Connect</name>
<packaging>pom</packaging> <packaging>pom</packaging>
<parent> <parent>
@ -259,7 +259,7 @@
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId> <artifactId>spring-framework-bom</artifactId>
<version>4.1.8.RELEASE</version> <version>4.1.9.RELEASE</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@ -280,7 +280,7 @@
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId> <artifactId>spring-security-bom</artifactId>
<version>3.2.8.RELEASE</version> <version>3.2.9.RELEASE</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>

View File

@ -20,7 +20,7 @@
<parent> <parent>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<version>1.2.3-SNAPSHOT</version> <version>1.2.4-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<artifactId>uma-server-webapp</artifactId> <artifactId>uma-server-webapp</artifactId>

View File

@ -1,6 +1,6 @@
<!-- <!--
Copyright 2015 The MITRE Corporation 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -20,7 +20,7 @@
<parent> <parent>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<version>1.2.3-SNAPSHOT</version> <version>1.2.4-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<artifactId>uma-server</artifactId> <artifactId>uma-server</artifactId>