Skip to content

Commit d1033b6

Browse files
committed
added privacy-preserving client logo cache
1 parent 43509b7 commit d1033b6

File tree

6 files changed

+252
-3
lines changed

6 files changed

+252
-3
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*******************************************************************************
2+
* Copyright 2015 The MITRE Corporation
3+
* and the MIT Internet Trust Consortium
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*******************************************************************************/
17+
18+
package org.mitre.openid.connect.model;
19+
20+
/**
21+
* @author jricher
22+
*
23+
*/
24+
public class CachedImage {
25+
26+
private byte[] data;
27+
private String contentType;
28+
private long length;
29+
30+
/**
31+
* @return the data
32+
*/
33+
public byte[] getData() {
34+
return data;
35+
}
36+
/**
37+
* @param data the data to set
38+
*/
39+
public void setData(byte[] data) {
40+
this.data = data;
41+
}
42+
/**
43+
* @return the contentType
44+
*/
45+
public String getContentType() {
46+
return contentType;
47+
}
48+
/**
49+
* @param contentType the contentType to set
50+
*/
51+
public void setContentType(String contentType) {
52+
this.contentType = contentType;
53+
}
54+
/**
55+
* @return the length
56+
*/
57+
public long getLength() {
58+
return length;
59+
}
60+
/**
61+
* @param length the length to set
62+
*/
63+
public void setLength(long length) {
64+
this.length = length;
65+
}
66+
67+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*******************************************************************************
2+
* Copyright 2015 The MITRE Corporation
3+
* and the MIT Internet Trust Consortium
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*******************************************************************************/
17+
18+
package org.mitre.openid.connect.service;
19+
20+
import org.mitre.oauth2.model.ClientDetailsEntity;
21+
import org.mitre.openid.connect.model.CachedImage;
22+
23+
/**
24+
* @author jricher
25+
*
26+
*/
27+
public interface ClientLogoLoadingService {
28+
29+
/**
30+
* @param client
31+
* @return
32+
*/
33+
public CachedImage getLogo(ClientDetailsEntity client);
34+
35+
}

openid-connect-server-webapp/src/main/webapp/WEB-INF/views/approve.jsp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
<c:if test="${ not empty client.logoUri }">
8585
<ul class="thumbnails">
8686
<li class="span5">
87-
<a class="thumbnail" data-toggle="modal" data-target="#logoModal"><img src="${ fn:escapeXml(client.logoUri) }" /></a>
87+
<a class="thumbnail" data-toggle="modal" data-target="#logoModal"><img src="api/clients/${ client.id }/logo" /></a>
8888
</li>
8989
</ul>
9090
<!-- Modal -->
@@ -103,7 +103,7 @@
103103
</h3>
104104
</div>
105105
<div class="modal-body">
106-
<img src="${ fn:escapeXml(client.logoUri) }" />
106+
<img src="api/clients/${ client.id }/logo" />
107107
<c:if test="${ not empty client.clientUri }">
108108
<a href="<c:out value="${ client.clientUri }" />"><c:out value="${ client.clientUri }" /></a>
109109
</c:if>

openid-connect-server-webapp/src/main/webapp/resources/template/client.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
<div class="media">
3333
<% if (client.logoUri) { %>
34-
<span class="pull-left"><img class="media-object client-logo" src="<%- client.logoUri %>"></span>
34+
<span class="pull-left"><img class="media-object client-logo" src="api/clients/<%- client.id ->/logo"></span>
3535
<% } %>
3636

3737
<div class="media-body">
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*******************************************************************************
2+
* Copyright 2015 The MITRE Corporation
3+
* and the MIT Internet Trust Consortium
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*******************************************************************************/
17+
18+
package org.mitre.openid.connect.service.impl;
19+
20+
import java.io.IOException;
21+
import java.util.concurrent.ExecutionException;
22+
import java.util.concurrent.TimeUnit;
23+
24+
import org.apache.commons.io.IOUtils;
25+
import org.apache.http.HttpEntity;
26+
import org.apache.http.HttpException;
27+
import org.apache.http.HttpResponse;
28+
import org.apache.http.client.HttpClient;
29+
import org.apache.http.client.methods.HttpGet;
30+
import org.apache.http.impl.client.HttpClientBuilder;
31+
import org.mitre.oauth2.model.ClientDetailsEntity;
32+
import org.mitre.openid.connect.model.CachedImage;
33+
import org.mitre.openid.connect.service.ClientLogoLoadingService;
34+
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
35+
import org.springframework.stereotype.Service;
36+
37+
import com.google.common.base.Strings;
38+
import com.google.common.cache.CacheBuilder;
39+
import com.google.common.cache.CacheLoader;
40+
import com.google.common.cache.LoadingCache;
41+
import com.google.common.util.concurrent.UncheckedExecutionException;
42+
43+
/**
44+
* @author jricher
45+
*
46+
*/
47+
@Service("inMemoryClientLogoLoadingService")
48+
public class InMemoryClientLogoLoadingService implements ClientLogoLoadingService {
49+
50+
private LoadingCache<ClientDetailsEntity, CachedImage> cache;
51+
52+
53+
/**
54+
*
55+
*/
56+
public InMemoryClientLogoLoadingService() {
57+
58+
cache = CacheBuilder.newBuilder()
59+
.maximumSize(100)
60+
.expireAfterAccess(14, TimeUnit.DAYS)
61+
.build(new ClientLogoFetcher());
62+
63+
}
64+
65+
66+
/* (non-Javadoc)
67+
* @see org.mitre.openid.connect.service.ClientLogoLoadingService#getLogo(org.mitre.oauth2.model.ClientDetailsEntity)
68+
*/
69+
@Override
70+
public CachedImage getLogo(ClientDetailsEntity client) {
71+
try {
72+
if (client != null && !Strings.isNullOrEmpty(client.getLogoUri())) {
73+
return cache.get(client);
74+
} else {
75+
return null;
76+
}
77+
} catch (UncheckedExecutionException | ExecutionException e) {
78+
return null;
79+
}
80+
}
81+
82+
/**
83+
* @author jricher
84+
*
85+
*/
86+
public class ClientLogoFetcher extends CacheLoader<ClientDetailsEntity, CachedImage> {
87+
private HttpClient httpClient = HttpClientBuilder.create().useSystemProperties().build();
88+
private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
89+
90+
/* (non-Javadoc)
91+
* @see com.google.common.cache.CacheLoader#load(java.lang.Object)
92+
*/
93+
@Override
94+
public CachedImage load(ClientDetailsEntity key) throws Exception {
95+
try {
96+
HttpResponse response = httpClient.execute(new HttpGet(key.getLogoUri()));
97+
98+
HttpEntity entity = response.getEntity();
99+
100+
CachedImage image = new CachedImage();
101+
102+
image.setContentType(entity.getContentType().getValue());
103+
image.setLength(entity.getContentLength());
104+
image.setData(IOUtils.toByteArray(entity.getContent()));
105+
106+
return image;
107+
} catch (IOException e) {
108+
throw new IllegalArgumentException("Unable to load client image.");
109+
}
110+
}
111+
112+
}
113+
114+
115+
}

openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientAPI.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
2525
import org.mitre.oauth2.service.ClientDetailsEntityService;
2626
import org.mitre.oauth2.web.AuthenticationUtilities;
27+
import org.mitre.openid.connect.model.CachedImage;
28+
import org.mitre.openid.connect.service.ClientLogoLoadingService;
2729
import org.mitre.openid.connect.view.ClientEntityViewForAdmins;
2830
import org.mitre.openid.connect.view.ClientEntityViewForUsers;
2931
import org.mitre.openid.connect.view.HttpCodeView;
@@ -32,8 +34,10 @@
3234
import org.slf4j.Logger;
3335
import org.slf4j.LoggerFactory;
3436
import org.springframework.beans.factory.annotation.Autowired;
37+
import org.springframework.http.HttpHeaders;
3538
import org.springframework.http.HttpStatus;
3639
import org.springframework.http.MediaType;
40+
import org.springframework.http.ResponseEntity;
3741
import org.springframework.security.access.prepost.PreAuthorize;
3842
import org.springframework.security.core.Authentication;
3943
import org.springframework.stereotype.Controller;
@@ -74,6 +78,9 @@ public class ClientAPI {
7478
@Autowired
7579
private ClientDetailsEntityService clientService;
7680

81+
@Autowired
82+
private ClientLogoLoadingService clientLogoLoadingService;
83+
7784
private JsonParser parser = new JsonParser();
7885

7986
private Gson gson = new GsonBuilder()
@@ -393,5 +400,30 @@ public String apiShowClient(@PathVariable("id") Long id, Model model, Authentica
393400
return ClientEntityViewForUsers.VIEWNAME;
394401
}
395402
}
403+
404+
/**
405+
* Get the logo image for a client
406+
* @param id
407+
*/
408+
@RequestMapping(value = "/{id}/logo", method=RequestMethod.GET, produces = { MediaType.IMAGE_GIF_VALUE, MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE })
409+
public ResponseEntity<byte[]> getClientLogo(@PathVariable("id") Long id, Model model) {
410+
411+
ClientDetailsEntity client = clientService.getClientById(id);
412+
413+
if (client == null) {
414+
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
415+
} else if (Strings.isNullOrEmpty(client.getLogoUri())) {
416+
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
417+
} else {
418+
// get the image from cache
419+
CachedImage image = clientLogoLoadingService.getLogo(client);
420+
421+
HttpHeaders headers = new HttpHeaders();
422+
headers.setContentType(MediaType.parseMediaType(image.getContentType()));
423+
headers.setContentLength(image.getLength());
424+
425+
return new ResponseEntity<>(image.getData(), headers, HttpStatus.OK);
426+
}
427+
}
396428

397429
}

0 commit comments

Comments
 (0)