1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package edu.internet2.middleware.shibboleth.idp.profile.saml2;
19
20 import java.util.ArrayList;
21
22 import org.opensaml.common.SAMLObjectBuilder;
23 import org.opensaml.common.binding.BasicEndpointSelector;
24 import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
25 import org.opensaml.common.xml.SAMLConstants;
26 import org.opensaml.saml2.core.AttributeQuery;
27 import org.opensaml.saml2.core.AttributeStatement;
28 import org.opensaml.saml2.core.NameID;
29 import org.opensaml.saml2.core.Response;
30 import org.opensaml.saml2.core.Statement;
31 import org.opensaml.saml2.core.StatusCode;
32 import org.opensaml.saml2.core.Subject;
33 import org.opensaml.saml2.metadata.AssertionConsumerService;
34 import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
35 import org.opensaml.saml2.metadata.Endpoint;
36 import org.opensaml.saml2.metadata.EntityDescriptor;
37 import org.opensaml.saml2.metadata.SPSSODescriptor;
38 import org.opensaml.saml2.metadata.provider.MetadataProvider;
39 import org.opensaml.ws.message.decoder.MessageDecodingException;
40 import org.opensaml.ws.transport.http.HTTPInTransport;
41 import org.opensaml.ws.transport.http.HTTPOutTransport;
42 import org.opensaml.xml.security.SecurityException;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import edu.internet2.middleware.shibboleth.common.attribute.provider.BasicAttribute;
47 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
48 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
49 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AttributeQueryConfiguration;
50 import edu.internet2.middleware.shibboleth.idp.session.AuthenticationMethodInformation;
51 import edu.internet2.middleware.shibboleth.idp.session.Session;
52
53
54 public class AttributeQueryProfileHandler extends AbstractSAML2ProfileHandler {
55
56
57 private static Logger log = LoggerFactory.getLogger(AttributeQueryProfileHandler.class);
58
59
60 private SAMLObjectBuilder<NameID> nameIDBuilder;
61
62
63 private SAMLObjectBuilder<AssertionConsumerService> acsEndpointBuilder;
64
65
66 public AttributeQueryProfileHandler() {
67 super();
68
69 nameIDBuilder = (SAMLObjectBuilder<NameID>) getBuilderFactory().getBuilder(
70 NameID.DEFAULT_ELEMENT_NAME);
71 acsEndpointBuilder = (SAMLObjectBuilder<AssertionConsumerService>) getBuilderFactory().getBuilder(
72 AssertionConsumerService.DEFAULT_ELEMENT_NAME);
73 }
74
75
76 public String getProfileId() {
77 return AttributeQueryConfiguration.PROFILE_ID;
78 }
79
80
81 public void processRequest(HTTPInTransport inTransport, HTTPOutTransport outTransport) throws ProfileException {
82 Response samlResponse;
83
84 AttributeQueryContext requestContext = new AttributeQueryContext();
85
86 try {
87 decodeRequest(requestContext, inTransport, outTransport);
88
89 if (requestContext.getProfileConfiguration() == null) {
90 String msg = "SAML 2 Attribute Query profile is not configured for relying party "
91 + requestContext.getInboundMessage();
92 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI,
93 msg));
94 log.warn(msg);
95 samlResponse = buildErrorResponse(requestContext);
96 } else {
97 checkSamlVersion(requestContext);
98
99
100 AttributeQuery query = requestContext.getInboundSAMLMessage();
101 if (query.getAttributes() != null && !query.getAttributes().isEmpty()) {
102 log.warn("Specific attributes requested in query '{}'. This functionality is not yet supported",
103 requestContext.getInboundMessageIssuer());
104 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI,
105 StatusCode.REQUEST_UNSUPPORTED_URI,
106 "Request of specific attributes during an attribute query is not supported"));
107 throw new ProfileException();
108 }
109
110
111 resolvePrincipal(requestContext);
112
113 Session idpSession = getSessionManager().getSession(requestContext.getPrincipalName());
114 if (idpSession != null) {
115 requestContext.setUserSession(idpSession);
116 AuthenticationMethodInformation authnInfo = idpSession.getAuthenticationMethods().get(
117 requestContext.getInboundMessageIssuer());
118 if (authnInfo != null) {
119 requestContext.setPrincipalAuthenticationMethod(authnInfo.getAuthenticationMethod());
120 }
121 }
122
123 resolveAttributes(requestContext);
124
125
126 ArrayList<Statement> statements = new ArrayList<Statement>();
127 AttributeStatement attributeStatement = buildAttributeStatement(requestContext);
128 if (attributeStatement != null) {
129 requestContext.setReleasedAttributes(requestContext.getAttributes().keySet());
130 statements.add(attributeStatement);
131 }
132
133
134 samlResponse = buildResponse(requestContext, "urn:oasis:names:tc:SAML:2.0:cm:sender-vouches",
135 statements);
136 }
137 } catch (ProfileException e) {
138 samlResponse = buildErrorResponse(requestContext);
139 }
140
141 requestContext.setOutboundSAMLMessage(samlResponse);
142 requestContext.setOutboundSAMLMessageId(samlResponse.getID());
143 requestContext.setOutboundSAMLMessageIssueInstant(samlResponse.getIssueInstant());
144
145 encodeResponse(requestContext);
146 writeAuditLogEntry(requestContext);
147 }
148
149
150
151
152
153
154
155
156
157
158 protected void decodeRequest(AttributeQueryContext requestContext, HTTPInTransport inTransport,
159 HTTPOutTransport outTransport) throws ProfileException {
160 if (log.isDebugEnabled()) {
161 log.debug("Decoding message with decoder binding '{}'", getInboundMessageDecoder(requestContext)
162 .getBindingURI());
163 }
164
165 requestContext.setCommunicationProfileId(getProfileId());
166
167 MetadataProvider metadataProvider = getMetadataProvider();
168 requestContext.setMetadataProvider(metadataProvider);
169
170 requestContext.setInboundMessageTransport(inTransport);
171 requestContext.setInboundSAMLProtocol(SAMLConstants.SAML20P_NS);
172 requestContext.setSecurityPolicyResolver(getSecurityPolicyResolver());
173 requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
174
175 requestContext.setOutboundMessageTransport(outTransport);
176 requestContext.setOutboundSAMLProtocol(SAMLConstants.SAML20P_NS);
177
178 try {
179 SAMLMessageDecoder decoder = getInboundMessageDecoder(requestContext);
180 requestContext.setMessageDecoder(decoder);
181 decoder.decode(requestContext);
182 log.debug("Decoded request from relying party '{}'", requestContext.getInboundMessageIssuer());
183
184 if (!(requestContext.getInboundSAMLMessage() instanceof AttributeQuery)) {
185 log.warn("Incoming message was not a AttributeQuery, it was a {}", requestContext
186 .getInboundSAMLMessage().getClass().getName());
187 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER_URI, null,
188 "Invalid SAML AttributeQuery message."));
189 throw new ProfileException("Invalid SAML AttributeQuery message.");
190 }
191 } catch (MessageDecodingException e) {
192 String msg = "Error decoding attribute query message";
193 log.warn(msg, e);
194 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, msg));
195 throw new ProfileException(msg);
196 } catch (SecurityException e) {
197 String msg = "Message did not meet security requirements";
198 log.warn(msg, e);
199 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI, msg));
200 throw new ProfileException(msg, e);
201 } finally {
202
203 populateRequestContext(requestContext);
204 }
205 }
206
207
208 protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
209 throws ProfileException {
210 super.populateRelyingPartyInformation(requestContext);
211
212 EntityDescriptor relyingPartyMetadata = requestContext.getPeerEntityMetadata();
213 if (relyingPartyMetadata != null) {
214 requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
215 requestContext.setPeerEntityRoleMetadata(relyingPartyMetadata.getSPSSODescriptor(SAMLConstants.SAML20P_NS));
216 }
217 }
218
219
220 protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
221 throws ProfileException {
222 super.populateAssertingPartyInformation(requestContext);
223
224 EntityDescriptor localEntityDescriptor = requestContext.getLocalEntityMetadata();
225 if (localEntityDescriptor != null) {
226 requestContext.setLocalEntityRole(AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
227 requestContext.setLocalEntityRoleMetadata(localEntityDescriptor
228 .getAttributeAuthorityDescriptor(SAMLConstants.SAML20P_NS));
229 }
230 }
231
232
233
234
235
236
237
238
239
240
241
242
243 protected void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
244 AttributeQuery query = (AttributeQuery) requestContext.getInboundSAMLMessage();
245 if (query != null) {
246 Subject subject = query.getSubject();
247 if (subject == null) {
248 String msg = "Attribute query did not contain a proper subject";
249 log.warn(msg);
250 ((AttributeQueryContext) requestContext).setFailureStatus(buildStatus(StatusCode.REQUESTER_URI, null,
251 msg));
252 throw new ProfileException(msg);
253 }
254 requestContext.setSubjectNameIdentifier(subject.getNameID());
255 }
256 }
257
258
259
260
261
262
263
264
265 protected Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) {
266 Endpoint endpoint;
267
268 if (getInboundBinding().equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) {
269 endpoint = acsEndpointBuilder.buildObject();
270 endpoint.setBinding(SAMLConstants.SAML2_SOAP11_BINDING_URI);
271 } else {
272 BasicEndpointSelector endpointSelector = new BasicEndpointSelector();
273 endpointSelector.setEndpointType(AssertionConsumerService.DEFAULT_ELEMENT_NAME);
274 endpointSelector.setMetadataProvider(getMetadataProvider());
275 endpointSelector.setEntityMetadata(requestContext.getPeerEntityMetadata());
276 endpointSelector.setEntityRoleMetadata(requestContext.getPeerEntityRoleMetadata());
277 endpointSelector.setSamlRequest(requestContext.getInboundSAMLMessage());
278 endpointSelector.getSupportedIssuerBindings().addAll(getSupportedOutboundBindings());
279 endpoint = endpointSelector.selectEndpoint();
280 }
281
282 return endpoint;
283 }
284
285
286 protected NameID buildNameId(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
287 throws ProfileException {
288
289 log.debug("Reusing NameID supplied in query");
290 NameID src = requestContext.getSubjectNameIdentifier();
291 if (src != null) {
292 NameID dest = nameIDBuilder.buildObject();
293 dest.setValue(src.getValue());
294 dest.setNameQualifier(src.getNameQualifier());
295 dest.setSPNameQualifier(src.getSPNameQualifier());
296 dest.setFormat(src.getFormat());
297 dest.setSPProvidedID(src.getSPProvidedID());
298
299 if (dest.getValue() != null) {
300
301
302 BasicAttribute<String> attribute = new BasicAttribute<String>();
303 attribute.setId("outboundQueryNameID");
304 attribute.getValues().add(dest.getValue());
305 requestContext.setNameIdentifierAttribute(attribute);
306 }
307
308 return dest;
309 }
310 return null;
311 }
312
313
314
315 protected class AttributeQueryContext extends
316 BaseSAML2ProfileRequestContext<AttributeQuery, Response, AttributeQueryConfiguration> {
317
318 }
319 }