1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.opensaml.security;
18
19 import java.lang.ref.SoftReference;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.concurrent.locks.Lock;
27 import java.util.concurrent.locks.ReadWriteLock;
28 import java.util.concurrent.locks.ReentrantReadWriteLock;
29
30 import javax.xml.namespace.QName;
31
32 import org.opensaml.Configuration;
33 import org.opensaml.saml2.metadata.KeyDescriptor;
34 import org.opensaml.saml2.metadata.RoleDescriptor;
35 import org.opensaml.saml2.metadata.provider.MetadataProvider;
36 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
37 import org.opensaml.saml2.metadata.provider.ObservableMetadataProvider;
38 import org.opensaml.xml.security.CriteriaSet;
39 import org.opensaml.xml.security.SecurityException;
40 import org.opensaml.xml.security.credential.AbstractCriteriaFilteringCredentialResolver;
41 import org.opensaml.xml.security.credential.BasicCredential;
42 import org.opensaml.xml.security.credential.Credential;
43 import org.opensaml.xml.security.credential.UsageType;
44 import org.opensaml.xml.security.criteria.EntityIDCriteria;
45 import org.opensaml.xml.security.criteria.UsageCriteria;
46 import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver;
47 import org.opensaml.xml.security.keyinfo.KeyInfoCriteria;
48 import org.opensaml.xml.util.DatatypeHelper;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 public class MetadataCredentialResolver extends AbstractCriteriaFilteringCredentialResolver {
68
69
70 private final Logger log = LoggerFactory.getLogger(MetadataCredentialResolver.class);
71
72
73 private MetadataProvider metadata;
74
75
76 private Map<MetadataCacheKey, SoftReference<Collection<Credential>>> cache;
77
78
79 private KeyInfoCredentialResolver keyInfoCredentialResolver;
80
81
82 private ReadWriteLock rwlock;
83
84
85
86
87
88
89
90
91 public MetadataCredentialResolver(MetadataProvider metadataProvider) {
92 super();
93 if (metadataProvider == null) {
94 throw new IllegalArgumentException("Metadata provider may not be null");
95 }
96 metadata = metadataProvider;
97
98 cache = new HashMap<MetadataCacheKey, SoftReference<Collection<Credential>>>();
99
100 keyInfoCredentialResolver = Configuration.getGlobalSecurityConfiguration()
101 .getDefaultKeyInfoCredentialResolver();
102
103 rwlock = new ReentrantReadWriteLock();
104
105 if (metadata instanceof ObservableMetadataProvider) {
106 ObservableMetadataProvider observable = (ObservableMetadataProvider) metadataProvider;
107 observable.getObservers().add(new MetadataProviderObserver());
108 }
109
110 }
111
112
113
114
115
116
117 public KeyInfoCredentialResolver getKeyInfoCredentialResolver() {
118 return keyInfoCredentialResolver;
119 }
120
121
122
123
124
125
126 public void setKeyInfoCredentialResolver(KeyInfoCredentialResolver keyInfoResolver) {
127 keyInfoCredentialResolver = keyInfoResolver;
128 }
129
130
131
132
133
134
135 protected ReadWriteLock getReadWriteLock() {
136 return rwlock;
137 }
138
139
140 protected Iterable<Credential> resolveFromSource(CriteriaSet criteriaSet) throws SecurityException {
141
142 checkCriteriaRequirements(criteriaSet);
143
144 String entityID = criteriaSet.get(EntityIDCriteria.class).getEntityID();
145 MetadataCriteria mdCriteria = criteriaSet.get(MetadataCriteria.class);
146 QName role = mdCriteria.getRole();
147 String protocol = mdCriteria.getProtocol();
148 UsageCriteria usageCriteria = criteriaSet.get(UsageCriteria.class);
149 UsageType usage = null;
150 if (usageCriteria != null) {
151 usage = usageCriteria.getUsage();
152 } else {
153 usage = UsageType.UNSPECIFIED;
154 }
155
156
157 log.debug("Forcing on-demand metadata provider refresh if necessary");
158 try {
159 metadata.getMetadata();
160 } catch (MetadataProviderException e) {
161
162 }
163
164 MetadataCacheKey cacheKey = new MetadataCacheKey(entityID, role, protocol, usage);
165 Collection<Credential> credentials = retrieveFromCache(cacheKey);
166
167 if (credentials == null) {
168 credentials = retrieveFromMetadata(entityID, role, protocol, usage);
169 cacheCredentials(cacheKey, credentials);
170 }
171
172 return credentials;
173 }
174
175
176
177
178
179
180 protected void checkCriteriaRequirements(CriteriaSet criteriaSet) {
181 EntityIDCriteria entityCriteria = criteriaSet.get(EntityIDCriteria.class);
182 MetadataCriteria mdCriteria = criteriaSet.get(MetadataCriteria.class);
183 if (entityCriteria == null) {
184 throw new IllegalArgumentException("Entity criteria must be supplied");
185 }
186 if (mdCriteria == null) {
187 throw new IllegalArgumentException("SAML metadata criteria must be supplied");
188 }
189 if (DatatypeHelper.isEmpty(entityCriteria.getEntityID())) {
190 throw new IllegalArgumentException("Credential owner entity ID criteria value must be supplied");
191 }
192 if (mdCriteria.getRole() == null) {
193 throw new IllegalArgumentException("Credential metadata role criteria value must be supplied");
194 }
195 }
196
197
198
199
200
201
202
203
204 protected Collection<Credential> retrieveFromCache(MetadataCacheKey cacheKey) {
205 log.debug("Attempting to retrieve credentials from cache using index: {}", cacheKey);
206 Lock readLock = getReadWriteLock().readLock();
207 readLock.lock();
208 log.trace("Read lock over cache acquired");
209 try {
210 if (cache.containsKey(cacheKey)) {
211 SoftReference<Collection<Credential>> reference = cache.get(cacheKey);
212 if (reference.get() != null) {
213 log.debug("Retrieved credentials from cache using index: {}", cacheKey);
214 return reference.get();
215 }
216 }
217 } finally {
218 readLock.unlock();
219 log.trace("Read lock over cache released");
220 }
221
222 log.debug("Unable to retrieve credentials from cache using index: {}", cacheKey);
223 return null;
224 }
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239 protected Collection<Credential> retrieveFromMetadata(String entityID, QName role, String protocol, UsageType usage)
240 throws SecurityException {
241
242 log.debug("Attempting to retrieve credentials from metadata for entity: {}", entityID);
243 Collection<Credential> credentials = new HashSet<Credential>(3);
244
245 List<RoleDescriptor> roleDescriptors = getRoleDescriptors(entityID, role, protocol);
246 if(roleDescriptors == null || roleDescriptors.isEmpty()){
247 return credentials;
248 }
249
250 for (RoleDescriptor roleDescriptor : roleDescriptors) {
251 List<KeyDescriptor> keyDescriptors = roleDescriptor.getKeyDescriptors();
252 if(keyDescriptors == null || keyDescriptors.isEmpty()){
253 return credentials;
254 }
255 for (KeyDescriptor keyDescriptor : keyDescriptors) {
256 UsageType mdUsage = keyDescriptor.getUse();
257 if (mdUsage == null) {
258 mdUsage = UsageType.UNSPECIFIED;
259 }
260 if (matchUsage(mdUsage, usage)) {
261 if (keyDescriptor.getKeyInfo() != null) {
262 CriteriaSet critSet = new CriteriaSet();
263 critSet.add(new KeyInfoCriteria(keyDescriptor.getKeyInfo()));
264
265 Iterable<Credential> creds = getKeyInfoCredentialResolver().resolve(critSet);
266 if(credentials == null){
267 continue;
268 }
269 for (Credential cred : creds) {
270 if (cred instanceof BasicCredential) {
271 BasicCredential basicCred = (BasicCredential) cred;
272 basicCred.setEntityId(entityID);
273 basicCred.setUsageType(mdUsage);
274 basicCred.getCredentalContextSet().add(new SAMLMDCredentialContext(keyDescriptor));
275 }
276 credentials.add(cred);
277 }
278 }
279 }
280 }
281
282 }
283
284 return credentials;
285 }
286
287
288
289
290
291
292
293
294 protected boolean matchUsage(UsageType metadataUsage, UsageType criteriaUsage) {
295 if (metadataUsage == UsageType.UNSPECIFIED || criteriaUsage == UsageType.UNSPECIFIED) {
296 return true;
297 }
298 return metadataUsage == criteriaUsage;
299 }
300
301
302
303
304
305
306
307
308
309
310 protected List<RoleDescriptor> getRoleDescriptors(String entityID, QName role, String protocol)
311 throws SecurityException {
312 try {
313 if (log.isDebugEnabled()) {
314 log.debug("Retrieving metadata for entity '{}' in role '{}' for protocol '{}'",
315 new Object[] {entityID, role, protocol});
316 }
317
318 if (DatatypeHelper.isEmpty(protocol)) {
319 return metadata.getRole(entityID, role);
320 } else {
321 RoleDescriptor roleDescriptor = metadata.getRole(entityID, role, protocol);
322 if (roleDescriptor == null) {
323 return null;
324 }
325 List<RoleDescriptor> roles = new ArrayList<RoleDescriptor>();
326 roles.add(roleDescriptor);
327 return roles;
328 }
329 } catch (MetadataProviderException e) {
330 log.error("Unable to read metadata from provider", e);
331 throw new SecurityException("Unable to read metadata provider", e);
332 }
333 }
334
335
336
337
338
339
340
341 protected void cacheCredentials(MetadataCacheKey cacheKey, Collection<Credential> credentials) {
342 Lock writeLock = getReadWriteLock().writeLock();
343 writeLock.lock();
344 log.trace("Write lock over cache acquired");
345 try {
346 cache.put(cacheKey, new SoftReference<Collection<Credential>>(credentials));
347 log.debug("Added new credential collection to cache with key: {}", cacheKey);
348 } finally {
349 writeLock.unlock();
350 log.trace("Write lock over cache released");
351 }
352 }
353
354
355
356
357 protected class MetadataCacheKey {
358
359
360 private String id;
361
362
363 private QName role;
364
365
366 private String protocol;
367
368
369 private UsageType usage;
370
371
372
373
374
375
376
377
378
379 protected MetadataCacheKey(String entityID, QName entityRole, String entityProtocol, UsageType entityUsage) {
380 if (entityID == null) {
381 throw new IllegalArgumentException("Entity ID may not be null");
382 }
383 if (entityRole == null) {
384 throw new IllegalArgumentException("Entity role may not be null");
385 }
386 if (entityUsage == null) {
387 throw new IllegalArgumentException("Credential usage may not be null");
388 }
389 id = entityID;
390 role = entityRole;
391 protocol = entityProtocol;
392 usage = entityUsage;
393 }
394
395
396 public boolean equals(Object obj) {
397 if (obj == this) {
398 return true;
399 }
400 if (!(obj instanceof MetadataCacheKey)) {
401 return false;
402 }
403 MetadataCacheKey other = (MetadataCacheKey) obj;
404 if (!this.id.equals(other.id) || !this.role.equals(other.role) || this.usage != other.usage) {
405 return false;
406 }
407 if (this.protocol == null) {
408 if (other.protocol != null) {
409 return false;
410 }
411 } else {
412 if (!this.protocol.equals(other.protocol)) {
413 return false;
414 }
415 }
416 return true;
417 }
418
419
420 public int hashCode() {
421 int result = 17;
422 result = 37 * result + id.hashCode();
423 result = 37 * result + role.hashCode();
424 if (protocol != null) {
425 result = 37 * result + protocol.hashCode();
426 }
427 result = 37 * result + usage.hashCode();
428 return result;
429 }
430
431
432 public String toString() {
433 return String.format("[%s,%s,%s,%s]", id, role, protocol, usage);
434 }
435
436 }
437
438
439
440
441 protected class MetadataProviderObserver implements ObservableMetadataProvider.Observer {
442
443
444 public void onEvent(MetadataProvider provider) {
445 Lock writeLock = getReadWriteLock().writeLock();
446 writeLock.lock();
447 log.trace("Write lock over cache acquired");
448 try {
449 cache.clear();
450 log.debug("Credential cache cleared");
451 } finally {
452 writeLock.unlock();
453 log.trace("Write lock over cache released");
454 }
455 }
456 }
457 }