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: {}", attributeRequestContext
137 .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 plugin.validate();
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
172 public String resolvePrincipalName(SAMLProfileRequestContext requestContext) throws AttributeResolutionException {
173 String nameIdFormat = getNameIdentifierFormat(requestContext.getSubjectNameIdentifier());
174
175 log.debug("Resolving principal name from name identifier of format: {}", nameIdFormat);
176
177 PrincipalConnector effectiveConnector = null;
178 for (PrincipalConnector connector : principalConnectors.values()) {
179 if (connector.getFormat().equals(nameIdFormat)) {
180 if (connector.getRelyingParties().contains(requestContext.getInboundMessageIssuer())) {
181 effectiveConnector = connector;
182 break;
183 }
184
185 if (connector.getRelyingParties().isEmpty()) {
186 effectiveConnector = connector;
187 }
188 }
189 }
190
191 if (effectiveConnector == null) {
192 throw new AttributeResolutionException(
193 "No principal connector available to resolve a subject name with format " + nameIdFormat
194 + " for relying party " + requestContext.getInboundMessageIssuer());
195 }
196 log.debug("Using principal connector {} to resolve principal name.", effectiveConnector.getId());
197 effectiveConnector = new ContextualPrincipalConnector(effectiveConnector);
198
199 ShibbolethResolutionContext resolutionContext = new ShibbolethResolutionContext(requestContext);
200
201
202 resolveDependencies(effectiveConnector, resolutionContext);
203
204 return effectiveConnector.resolve(resolutionContext);
205 }
206
207
208
209
210
211
212
213
214 protected String getNameIdentifierFormat(SAMLObject nameIdentifier) {
215 String subjectNameFormat = null;
216
217 if (nameIdentifier instanceof NameIdentifier) {
218 NameIdentifier identifier = (NameIdentifier) nameIdentifier;
219 subjectNameFormat = identifier.getFormat();
220 } else if (nameIdentifier instanceof NameID) {
221 NameID identifier = (NameID) nameIdentifier;
222 subjectNameFormat = identifier.getFormat();
223 }
224
225 if (DatatypeHelper.isEmpty(subjectNameFormat)) {
226 subjectNameFormat = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified";
227 }
228
229 return subjectNameFormat;
230 }
231
232
233
234
235
236
237
238
239
240
241
242 protected Map<String, BaseAttribute> resolveAttributes(ShibbolethResolutionContext resolutionContext)
243 throws AttributeResolutionException {
244 Collection<String> attributeIDs = resolutionContext.getAttributeRequestContext().getRequestedAttributesIds();
245 Map<String, BaseAttribute> resolvedAttributes = new HashMap<String, BaseAttribute>();
246
247
248 if (attributeIDs == null || attributeIDs.isEmpty()) {
249 log.debug("Specific attributes for principal {} were not requested, resolving all attributes.",
250 resolutionContext.getAttributeRequestContext().getPrincipalName());
251 attributeIDs = getAttributeDefinitions().keySet();
252 }
253
254 Lock readLock = getReadWriteLock().readLock();
255 readLock.lock();
256 try {
257 for (String attributeID : attributeIDs) {
258 BaseAttribute resolvedAttribute = resolveAttribute(attributeID, resolutionContext);
259 if (resolvedAttribute != null) {
260 resolvedAttributes.put(resolvedAttribute.getId(), resolvedAttribute);
261 }
262 }
263 } finally {
264 readLock.unlock();
265 }
266
267 return resolvedAttributes;
268 }
269
270
271
272
273
274
275
276
277
278
279
280
281
282 protected BaseAttribute resolveAttribute(String attributeID, ShibbolethResolutionContext resolutionContext)
283 throws AttributeResolutionException {
284
285 AttributeDefinition definition = resolutionContext.getResolvedAttributeDefinitions().get(attributeID);
286
287 if (definition == null) {
288 log.debug("Resolving attribute {} for principal {}", attributeID, resolutionContext
289 .getAttributeRequestContext().getPrincipalName());
290
291 definition = getAttributeDefinitions().get(attributeID);
292 if (definition == null) {
293 log.warn("{} requested attribute {} but no attribute definition exists for that attribute",
294 resolutionContext.getAttributeRequestContext().getInboundMessageIssuer(), attributeID);
295 return null;
296 } else {
297
298 definition = new ContextualAttributeDefinition(definition);
299
300
301 resolutionContext.getResolvedPlugins().put(attributeID, definition);
302 }
303 }
304
305
306 resolveDependencies(definition, resolutionContext);
307
308
309 BaseAttribute attribute = definition.resolve(resolutionContext);
310 log.debug("Resolved attribute {} containing {} values", attributeID, attribute.getValues().size());
311 return attribute;
312 }
313
314
315
316
317
318
319
320
321
322 protected void resolveDataConnector(String connectorID, ShibbolethResolutionContext resolutionContext)
323 throws AttributeResolutionException {
324
325 DataConnector dataConnector = resolutionContext.getResolvedDataConnectors().get(connectorID);
326
327 if (dataConnector == null) {
328 log.debug("Resolving data connector {} for principal {}", connectorID, resolutionContext
329 .getAttributeRequestContext().getPrincipalName());
330
331 dataConnector = getDataConnectors().get(connectorID);
332 if (dataConnector == null) {
333 log.warn("{} requested to resolve data connector {} but does not have such a data connector", getId(),
334 connectorID);
335 } else {
336
337 dataConnector = new ContextualDataConnector(dataConnector);
338
339
340 resolutionContext.getResolvedPlugins().put(connectorID, dataConnector);
341 }
342 }
343
344
345 resolveDependencies(dataConnector, resolutionContext);
346
347 try {
348 dataConnector.resolve(resolutionContext);
349 } catch (AttributeResolutionException e) {
350 String failoverDataConnectorId = dataConnector.getFailoverDependencyId();
351
352 if (DatatypeHelper.isEmpty(failoverDataConnectorId)) {
353 log.error("Received the following error from data connector " + dataConnector.getId()
354 + ", no failover data connector available", e);
355 throw e;
356 }
357
358 log.warn("Received the following error from data connector " + dataConnector.getId()
359 + ", trying its failover connector " + failoverDataConnectorId, e.getMessage());
360 log.debug("Error recieved from data connector " + dataConnector.getId(), e);
361 resolveDataConnector(failoverDataConnectorId, resolutionContext);
362
363 DataConnector failoverConnector = resolutionContext.getResolvedDataConnectors()
364 .get(failoverDataConnectorId);
365 log.debug("Using failover connector {} in place of {} for the remainder of this resolution",
366 failoverConnector.getId(), connectorID);
367 resolutionContext.getResolvedPlugins().put(connectorID, failoverConnector);
368 }
369 }
370
371
372
373
374
375
376
377
378
379 protected void resolveDependencies(ResolutionPlugIn<?> plugin, ShibbolethResolutionContext resolutionContext)
380 throws AttributeResolutionException {
381
382 for (String dependency : plugin.getDependencyIds()) {
383 if (dataConnectors.containsKey(dependency)) {
384 resolveDataConnector(dependency, resolutionContext);
385 } else if (definitions.containsKey(dependency)) {
386 resolveAttribute(dependency, resolutionContext);
387 }
388 }
389 }
390
391
392
393
394
395
396
397 protected void cleanResolvedAttributes(Map<String, BaseAttribute> resolvedAttributes,
398 ShibbolethResolutionContext resolutionContext) {
399 AttributeDefinition attributeDefinition;
400
401 Iterator<Entry<String, BaseAttribute>> attributeItr = resolvedAttributes.entrySet().iterator();
402 BaseAttribute<?> resolvedAttribute;
403 Set<Object> values;
404 while (attributeItr.hasNext()) {
405 resolvedAttribute = attributeItr.next().getValue();
406
407
408 if (resolvedAttribute == null) {
409 attributeItr.remove();
410 continue;
411 }
412
413
414 attributeDefinition = getAttributeDefinitions().get(resolvedAttribute.getId());
415 if (attributeDefinition.isDependencyOnly()) {
416 log.debug("Removing dependency-only attribute {} from resolution result for principal {}.",
417 resolvedAttribute.getId(), resolutionContext.getAttributeRequestContext().getPrincipalName());
418 attributeItr.remove();
419 continue;
420 }
421
422
423 if (resolvedAttribute.getValues().size() == 0) {
424 log.debug("Removing attribute {} from resolution result for principal {}. It contains no values.",
425 resolvedAttribute.getId(), resolutionContext.getAttributeRequestContext().getPrincipalName());
426 attributeItr.remove();
427 continue;
428 }
429
430
431 Iterator<?> valueItr = resolvedAttribute.getValues().iterator();
432 values = new HashSet<Object>();
433 while (valueItr.hasNext()) {
434 Object value = valueItr.next();
435 if (!values.add(value)) {
436 log.debug("Removing duplicate value {} of attribute {} from resolution result", value,
437 resolvedAttribute.getId());
438 valueItr.remove();
439 }
440 }
441 }
442 }
443
444
445
446
447
448
449
450 protected void addVertex(DirectedGraph<ResolutionPlugIn, DefaultEdge> graph, ResolutionPlugIn<?> plugin) {
451 graph.addVertex(plugin);
452 ResolutionPlugIn<?> dependency = null;
453
454
455 for (String id : plugin.getDependencyIds()) {
456 if (dataConnectors.containsKey(id)) {
457 dependency = dataConnectors.get(id);
458 } else if (definitions.containsKey(id)) {
459 dependency = definitions.get(id);
460 }
461
462 if (dependency != null) {
463 graph.addVertex(dependency);
464 graph.addEdge(plugin, dependency);
465 }
466 }
467 }
468
469
470 protected void onNewContextCreated(ApplicationContext newServiceContext) throws ServiceException {
471 String[] beanNames;
472
473 Map<String, DataConnector> oldDataConnectors = dataConnectors;
474 Map<String, DataConnector> newDataConnectors = new HashMap<String, DataConnector>();
475 DataConnector dConnector;
476 beanNames = newServiceContext.getBeanNamesForType(DataConnector.class);
477 log.debug("Loading {} data connectors", beanNames.length);
478 for (String beanName : beanNames) {
479 dConnector = (DataConnector) newServiceContext.getBean(beanName);
480 newDataConnectors.put(dConnector.getId(), dConnector);
481 }
482
483 Map<String, AttributeDefinition> oldAttributeDefinitions = definitions;
484 Map<String, AttributeDefinition> newAttributeDefinitions = new HashMap<String, AttributeDefinition>();
485 AttributeDefinition aDefinition;
486 beanNames = newServiceContext.getBeanNamesForType(AttributeDefinition.class);
487 log.debug("Loading {} attribute definitions", beanNames.length);
488 for (String beanName : beanNames) {
489 aDefinition = (AttributeDefinition) newServiceContext.getBean(beanName);
490 newAttributeDefinitions.put(aDefinition.getId(), aDefinition);
491 }
492
493 Map<String, PrincipalConnector> oldPrincipalConnectors = principalConnectors;
494 Map<String, PrincipalConnector> newPrincipalConnectors = new HashMap<String, PrincipalConnector>();
495 PrincipalConnector pConnector;
496 beanNames = newServiceContext.getBeanNamesForType(PrincipalConnector.class);
497 log.debug("Loading {} principal connectors", beanNames.length);
498 for (String beanName : beanNames) {
499 pConnector = (PrincipalConnector) newServiceContext.getBean(beanName);
500 newPrincipalConnectors.put(pConnector.getId(), pConnector);
501 }
502
503 try {
504 dataConnectors = newDataConnectors;
505 definitions = newAttributeDefinitions;
506 principalConnectors = newPrincipalConnectors;
507 validate();
508 } catch (AttributeResolutionException e) {
509 dataConnectors = oldDataConnectors;
510 definitions = oldAttributeDefinitions;
511 principalConnectors = oldPrincipalConnectors;
512 throw new ServiceException(getId() + " configuration is not valid, retaining old configuration", e);
513 }
514 }
515 }