1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package edu.internet2.middleware.shibboleth.common.attribute.resolver.provider;
18
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Set;
27 import java.util.concurrent.locks.Lock;
28
29 import org.jgrapht.DirectedGraph;
30 import org.jgrapht.graph.DefaultEdge;
31 import org.opensaml.common.SAMLObject;
32 import org.opensaml.saml1.core.NameIdentifier;
33 import org.opensaml.saml2.core.NameID;
34 import org.opensaml.xml.util.DatatypeHelper;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37 import org.springframework.context.ApplicationContext;
38
39 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
40 import edu.internet2.middleware.shibboleth.common.attribute.resolver.AttributeResolutionException;
41 import edu.internet2.middleware.shibboleth.common.attribute.resolver.AttributeResolver;
42 import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.attributeDefinition.AttributeDefinition;
43 import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.attributeDefinition.ContextualAttributeDefinition;
44 import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.ContextualDataConnector;
45 import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.DataConnector;
46 import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.principalConnector.ContextualPrincipalConnector;
47 import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.principalConnector.PrincipalConnector;
48 import edu.internet2.middleware.shibboleth.common.config.BaseReloadableService;
49 import edu.internet2.middleware.shibboleth.common.profile.provider.SAMLProfileRequestContext;
50 import edu.internet2.middleware.shibboleth.common.service.ServiceException;
51
52
53
54
55
56
57
58
59 public class ShibbolethAttributeResolver extends BaseReloadableService implements
60 AttributeResolver<SAMLProfileRequestContext> {
61
62
63 public static final Collection<Class> PLUGIN_TYPES = Arrays.asList(new Class[] { DataConnector.class,
64 AttributeDefinition.class, PrincipalConnector.class, });
65
66
67 private final Logger log = LoggerFactory.getLogger(ShibbolethAttributeResolver.class.getName());
68
69
70 private Map<String, DataConnector> dataConnectors;
71
72
73 private Map<String, AttributeDefinition> definitions;
74
75
76 private Map<String, PrincipalConnector> principalConnectors;
77
78
79 public ShibbolethAttributeResolver() {
80 super();
81 dataConnectors = new HashMap<String, DataConnector>();
82 definitions = new HashMap<String, AttributeDefinition>();
83 principalConnectors = new HashMap<String, PrincipalConnector>();
84 }
85
86
87
88
89
90
91 public Map<String, AttributeDefinition> getAttributeDefinitions() {
92 return definitions;
93 }
94
95
96
97
98
99
100 public Map<String, DataConnector> getDataConnectors() {
101 return dataConnectors;
102 }
103
104
105
106
107
108
109 public Map<String, PrincipalConnector> getPrincipalConnectors() {
110 return principalConnectors;
111 }
112
113
114 public Map<String, BaseAttribute> resolveAttributes(SAMLProfileRequestContext attributeRequestContext)
115 throws AttributeResolutionException {
116 ShibbolethResolutionContext resolutionContext = new ShibbolethResolutionContext(attributeRequestContext);
117
118 log.debug("{} resolving attributes for principal {}", getId(), attributeRequestContext.getPrincipalName());
119
120 if (getAttributeDefinitions().size() == 0) {
121 log.debug("No attribute definitions loaded in {} so no attributes can be resolved for principal {}",
122 getId(), attributeRequestContext.getPrincipalName());
123 return new HashMap<String, BaseAttribute>();
124 }
125
126 Lock readLock = getReadWriteLock().readLock();
127 readLock.lock();
128 Map<String, BaseAttribute> resolvedAttributes = null;
129 try {
130 resolvedAttributes = resolveAttributes(resolutionContext);
131 cleanResolvedAttributes(resolvedAttributes, resolutionContext);
132 } finally {
133 readLock.unlock();
134 }
135
136 log.debug(getId() + " resolved, for principal {}, the attributes: {}",
137 attributeRequestContext.getPrincipalName(), resolvedAttributes.keySet());
138 return resolvedAttributes;
139 }
140
141
142 public void validate() throws AttributeResolutionException {
143 for (DataConnector plugin : dataConnectors.values()) {
144 if (plugin != null) {
145 validateDataConnector(plugin);
146 }
147 }
148
149 for (AttributeDefinition plugin : definitions.values()) {
150 if (plugin != null) {
151 plugin.validate();
152 }
153 }
154
155 for (PrincipalConnector plugin : principalConnectors.values()) {
156 if (plugin != null) {
157 plugin.validate();
158 }
159 }
160 }
161
162
163
164
165
166
167
168
169
170
171 protected void validateDataConnector(DataConnector connector) throws AttributeResolutionException {
172 try {
173 connector.validate();
174 } catch (AttributeResolutionException e) {
175 if (connector.getFailoverDependencyId() != null) {
176 DataConnector failoverConnector = dataConnectors.get(connector.getFailoverDependencyId());
177 if (failoverConnector != null) {
178 validateDataConnector(failoverConnector);
179 return;
180 }
181 }
182
183 throw e;
184 }
185 }
186
187
188
189
190
191
192
193
194
195
196
197 public String resolvePrincipalName(SAMLProfileRequestContext requestContext) throws AttributeResolutionException {
198 String nameIdFormat = getNameIdentifierFormat(requestContext.getSubjectNameIdentifier());
199
200 log.debug("Resolving principal name from name identifier of format: {}", nameIdFormat);
201
202 PrincipalConnector effectiveConnector = null;
203 for (PrincipalConnector connector : principalConnectors.values()) {
204 if (connector.getFormat().equals(nameIdFormat)) {
205 if (connector.getRelyingParties().contains(requestContext.getInboundMessageIssuer())) {
206 effectiveConnector = connector;
207 break;
208 }
209
210 if (connector.getRelyingParties().isEmpty()) {
211 effectiveConnector = connector;
212 }
213 }
214 }
215
216 if (effectiveConnector == null) {
217 throw new AttributeResolutionException(
218 "No principal connector available to resolve a subject name with format " + nameIdFormat
219 + " for relying party " + requestContext.getInboundMessageIssuer());
220 }
221 log.debug("Using principal connector {} to resolve principal name.", effectiveConnector.getId());
222 effectiveConnector = new ContextualPrincipalConnector(effectiveConnector);
223
224 ShibbolethResolutionContext resolutionContext = new ShibbolethResolutionContext(requestContext);
225
226
227 resolveDependencies(effectiveConnector, resolutionContext);
228
229 return effectiveConnector.resolve(resolutionContext);
230 }
231
232
233
234
235
236
237
238
239 protected String getNameIdentifierFormat(SAMLObject nameIdentifier) {
240 String subjectNameFormat = null;
241
242 if (nameIdentifier instanceof NameIdentifier) {
243 NameIdentifier identifier = (NameIdentifier) nameIdentifier;
244 subjectNameFormat = identifier.getFormat();
245 } else if (nameIdentifier instanceof NameID) {
246 NameID identifier = (NameID) nameIdentifier;
247 subjectNameFormat = identifier.getFormat();
248 }
249
250 if (DatatypeHelper.isEmpty(subjectNameFormat)) {
251 subjectNameFormat = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified";
252 }
253
254 return subjectNameFormat;
255 }
256
257
258
259
260
261
262
263
264
265
266
267 protected Map<String, BaseAttribute> resolveAttributes(ShibbolethResolutionContext resolutionContext)
268 throws AttributeResolutionException {
269 Collection<String> attributeIDs = resolutionContext.getAttributeRequestContext().getRequestedAttributesIds();
270 Map<String, BaseAttribute> resolvedAttributes = new HashMap<String, BaseAttribute>();
271
272
273 if (attributeIDs == null || attributeIDs.isEmpty()) {
274 log.debug("Specific attributes for principal {} were not requested, resolving all attributes.",
275 resolutionContext.getAttributeRequestContext().getPrincipalName());
276 attributeIDs = getAttributeDefinitions().keySet();
277 }
278
279 Lock readLock = getReadWriteLock().readLock();
280 readLock.lock();
281 try {
282 for (String attributeID : attributeIDs) {
283 BaseAttribute resolvedAttribute = resolveAttribute(attributeID, resolutionContext);
284 if (resolvedAttribute != null) {
285 resolvedAttributes.put(resolvedAttribute.getId(), resolvedAttribute);
286 }
287 }
288 } finally {
289 readLock.unlock();
290 }
291
292 return resolvedAttributes;
293 }
294
295
296
297
298
299
300
301
302
303
304
305
306
307 protected BaseAttribute resolveAttribute(String attributeID, ShibbolethResolutionContext resolutionContext)
308 throws AttributeResolutionException {
309
310 AttributeDefinition definition = resolutionContext.getResolvedAttributeDefinitions().get(attributeID);
311
312 if (definition == null) {
313 log.debug("Resolving attribute {} for principal {}", attributeID, resolutionContext
314 .getAttributeRequestContext().getPrincipalName());
315
316 definition = getAttributeDefinitions().get(attributeID);
317 if (definition == null) {
318 log.warn("{} requested attribute {} but no attribute definition exists for that attribute",
319 resolutionContext.getAttributeRequestContext().getInboundMessageIssuer(), attributeID);
320 return null;
321 } else {
322
323 definition = new ContextualAttributeDefinition(definition);
324
325
326 resolutionContext.getResolvedPlugins().put(attributeID, definition);
327 }
328 }
329
330
331 resolveDependencies(definition, resolutionContext);
332
333
334 BaseAttribute attribute = definition.resolve(resolutionContext);
335 log.debug("Resolved attribute {} containing {} values", attributeID, attribute.getValues().size());
336 return attribute;
337 }
338
339
340
341
342
343
344
345
346
347 protected void resolveDataConnector(String connectorID, ShibbolethResolutionContext resolutionContext)
348 throws AttributeResolutionException {
349
350 DataConnector dataConnector = resolutionContext.getResolvedDataConnectors().get(connectorID);
351
352 if (dataConnector == null) {
353 log.debug("Resolving data connector {} for principal {}", connectorID, resolutionContext
354 .getAttributeRequestContext().getPrincipalName());
355
356 dataConnector = getDataConnectors().get(connectorID);
357 if (dataConnector == null) {
358 log.warn("{} requested to resolve data connector {} but does not have such a data connector", getId(),
359 connectorID);
360 } else {
361
362 dataConnector = new ContextualDataConnector(dataConnector);
363
364
365 resolutionContext.getResolvedPlugins().put(connectorID, dataConnector);
366 }
367 }
368
369
370 resolveDependencies(dataConnector, resolutionContext);
371
372 try {
373 dataConnector.resolve(resolutionContext);
374 } catch (AttributeResolutionException e) {
375 String failoverDataConnectorId = dataConnector.getFailoverDependencyId();
376
377 if (DatatypeHelper.isEmpty(failoverDataConnectorId)) {
378 log.error("Received the following error from data connector " + dataConnector.getId()
379 + ", no failover data connector available", e);
380 throw e;
381 }
382
383 log.warn("Received the following error from data connector " + dataConnector.getId()
384 + ", trying its failover connector " + failoverDataConnectorId, e.getMessage());
385 log.debug("Error recieved from data connector " + dataConnector.getId(), e);
386 resolveDataConnector(failoverDataConnectorId, resolutionContext);
387
388 DataConnector failoverConnector = resolutionContext.getResolvedDataConnectors()
389 .get(failoverDataConnectorId);
390 log.debug("Using failover connector {} in place of {} for the remainder of this resolution",
391 failoverConnector.getId(), connectorID);
392 resolutionContext.getResolvedPlugins().put(connectorID, failoverConnector);
393 }
394 }
395
396
397
398
399
400
401
402
403
404 protected void resolveDependencies(ResolutionPlugIn<?> plugin, ShibbolethResolutionContext resolutionContext)
405 throws AttributeResolutionException {
406
407 for (String dependency : plugin.getDependencyIds()) {
408 if (dataConnectors.containsKey(dependency)) {
409 resolveDataConnector(dependency, resolutionContext);
410 } else if (definitions.containsKey(dependency)) {
411 resolveAttribute(dependency, resolutionContext);
412 }
413 }
414 }
415
416
417
418
419
420
421
422 protected void cleanResolvedAttributes(Map<String, BaseAttribute> resolvedAttributes,
423 ShibbolethResolutionContext resolutionContext) {
424 AttributeDefinition attributeDefinition;
425
426 Iterator<Entry<String, BaseAttribute>> attributeItr = resolvedAttributes.entrySet().iterator();
427 BaseAttribute<?> resolvedAttribute;
428 Set<Object> values;
429 while (attributeItr.hasNext()) {
430 resolvedAttribute = attributeItr.next().getValue();
431
432
433 if (resolvedAttribute == null) {
434 attributeItr.remove();
435 continue;
436 }
437
438
439 attributeDefinition = getAttributeDefinitions().get(resolvedAttribute.getId());
440 if (attributeDefinition.isDependencyOnly()) {
441 log.debug("Removing dependency-only attribute {} from resolution result for principal {}.",
442 resolvedAttribute.getId(), resolutionContext.getAttributeRequestContext().getPrincipalName());
443 attributeItr.remove();
444 continue;
445 }
446
447
448 if (resolvedAttribute.getValues().size() == 0) {
449 log.debug("Removing attribute {} from resolution result for principal {}. It contains no values.",
450 resolvedAttribute.getId(), resolutionContext.getAttributeRequestContext().getPrincipalName());
451 attributeItr.remove();
452 continue;
453 }
454
455
456 Iterator<?> valueItr = resolvedAttribute.getValues().iterator();
457 values = new HashSet<Object>();
458 while (valueItr.hasNext()) {
459 Object value = valueItr.next();
460 if (!values.add(value)) {
461 log.debug("Removing duplicate value {} of attribute {} from resolution result", value,
462 resolvedAttribute.getId());
463 valueItr.remove();
464 }
465 }
466 }
467 }
468
469
470
471
472
473
474
475 protected void addVertex(DirectedGraph<ResolutionPlugIn, DefaultEdge> graph, ResolutionPlugIn<?> plugin) {
476 graph.addVertex(plugin);
477 ResolutionPlugIn<?> dependency = null;
478
479
480 for (String id : plugin.getDependencyIds()) {
481 if (dataConnectors.containsKey(id)) {
482 dependency = dataConnectors.get(id);
483 } else if (definitions.containsKey(id)) {
484 dependency = definitions.get(id);
485 }
486
487 if (dependency != null) {
488 graph.addVertex(dependency);
489 graph.addEdge(plugin, dependency);
490 }
491 }
492 }
493
494
495 protected void onNewContextCreated(ApplicationContext newServiceContext) throws ServiceException {
496 String[] beanNames;
497
498 Map<String, DataConnector> oldDataConnectors = dataConnectors;
499 Map<String, DataConnector> newDataConnectors = new HashMap<String, DataConnector>();
500 DataConnector dConnector;
501 beanNames = newServiceContext.getBeanNamesForType(DataConnector.class);
502 log.debug("Loading {} data connectors", beanNames.length);
503 for (String beanName : beanNames) {
504 dConnector = (DataConnector) newServiceContext.getBean(beanName);
505 newDataConnectors.put(dConnector.getId(), dConnector);
506 }
507
508 Map<String, AttributeDefinition> oldAttributeDefinitions = definitions;
509 Map<String, AttributeDefinition> newAttributeDefinitions = new HashMap<String, AttributeDefinition>();
510 AttributeDefinition aDefinition;
511 beanNames = newServiceContext.getBeanNamesForType(AttributeDefinition.class);
512 log.debug("Loading {} attribute definitions", beanNames.length);
513 for (String beanName : beanNames) {
514 aDefinition = (AttributeDefinition) newServiceContext.getBean(beanName);
515 newAttributeDefinitions.put(aDefinition.getId(), aDefinition);
516 }
517
518 Map<String, PrincipalConnector> oldPrincipalConnectors = principalConnectors;
519 Map<String, PrincipalConnector> newPrincipalConnectors = new HashMap<String, PrincipalConnector>();
520 PrincipalConnector pConnector;
521 beanNames = newServiceContext.getBeanNamesForType(PrincipalConnector.class);
522 log.debug("Loading {} principal connectors", beanNames.length);
523 for (String beanName : beanNames) {
524 pConnector = (PrincipalConnector) newServiceContext.getBean(beanName);
525 newPrincipalConnectors.put(pConnector.getId(), pConnector);
526 }
527
528 try {
529 dataConnectors = newDataConnectors;
530 definitions = newAttributeDefinitions;
531 principalConnectors = newPrincipalConnectors;
532 validate();
533 } catch (AttributeResolutionException e) {
534 dataConnectors = oldDataConnectors;
535 definitions = oldAttributeDefinitions;
536 principalConnectors = oldPrincipalConnectors;
537 throw new ServiceException(getId() + " configuration is not valid, retaining old configuration", e);
538 }
539 }
540 }