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 org.joda.time.DateTime;
21 import org.joda.time.chrono.ISOChronology;
22 import org.opensaml.Configuration;
23 import org.opensaml.common.IdentifierGenerator;
24 import org.opensaml.common.SAMLObjectBuilder;
25 import org.opensaml.common.binding.BasicEndpointSelector;
26 import org.opensaml.common.binding.SAMLMessageContext;
27 import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
28 import org.opensaml.common.xml.SAMLConstants;
29 import org.opensaml.saml2.binding.decoding.BaseSAML2MessageDecoder;
30 import org.opensaml.saml2.core.AuthnRequest;
31 import org.opensaml.saml2.core.Issuer;
32 import org.opensaml.saml2.core.NameIDPolicy;
33 import org.opensaml.saml2.metadata.AssertionConsumerService;
34 import org.opensaml.saml2.metadata.Endpoint;
35 import org.opensaml.saml2.metadata.SPSSODescriptor;
36 import org.opensaml.saml2.metadata.provider.MetadataProvider;
37 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
38 import org.opensaml.ws.message.MessageContext;
39 import org.opensaml.ws.message.decoder.MessageDecodingException;
40 import org.opensaml.ws.transport.http.HTTPInTransport;
41 import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
42 import org.opensaml.xml.XMLObjectBuilderFactory;
43 import org.opensaml.xml.util.DatatypeHelper;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 import edu.internet2.middleware.shibboleth.idp.profile.saml2.SSOProfileHandler.SSORequestContext;
48
49
50
51
52
53
54
55
56
57
58
59
60 public class UnsolicitedSSODecoder extends BaseSAML2MessageDecoder implements SAMLMessageDecoder {
61
62
63 private final Logger log = LoggerFactory.getLogger(UnsolicitedSSODecoder.class);
64
65
66 public String defaultBinding;
67
68
69 private SAMLObjectBuilder<AuthnRequest> authnRequestBuilder;
70
71
72 private SAMLObjectBuilder<Issuer> issuerBuilder;
73
74
75 private SAMLObjectBuilder<NameIDPolicy> nipBuilder;
76
77
78 private IdentifierGenerator idGenerator;
79
80
81
82
83
84
85 @SuppressWarnings("unchecked")
86 public UnsolicitedSSODecoder(IdentifierGenerator identifierGenerator) {
87 super();
88
89 XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory();
90
91 authnRequestBuilder =
92 (SAMLObjectBuilder<AuthnRequest>) builderFactory.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
93 issuerBuilder =
94 (SAMLObjectBuilder<Issuer>) builderFactory.getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
95 nipBuilder =
96 (SAMLObjectBuilder<NameIDPolicy>) builderFactory.getBuilder(NameIDPolicy.DEFAULT_ELEMENT_NAME);
97
98 idGenerator = identifierGenerator;
99 defaultBinding = SAMLConstants.SAML2_POST_BINDING_URI;
100 }
101
102
103 public String getBindingURI() {
104 return "urn:mace:shibboleth:2.0:profiles:AuthnRequest";
105 }
106
107
108 @SuppressWarnings("unchecked")
109 protected boolean isIntendedDestinationEndpointURIRequired(SAMLMessageContext samlMsgCtx) {
110 return false;
111 }
112
113
114 @SuppressWarnings("unchecked")
115 protected String getIntendedDestinationEndpointURI(SAMLMessageContext samlMsgCtx) throws MessageDecodingException {
116
117
118 return null;
119 }
120
121
122
123
124
125 public String getDefaultBinding() {
126 return defaultBinding;
127 }
128
129
130
131
132
133 public void setDefaultBinding(String binding) {
134 defaultBinding = binding;
135 }
136
137
138 @SuppressWarnings("unchecked")
139 protected void doDecode(MessageContext messageContext) throws MessageDecodingException {
140 if (!(messageContext instanceof SSORequestContext)) {
141 log.warn("Invalid message context type, this decoder only supports SSORequestContext");
142 throw new MessageDecodingException(
143 "Invalid message context type, this decoder only supports SSORequestContext");
144 }
145
146 if (!(messageContext.getInboundMessageTransport() instanceof HTTPInTransport)) {
147 log.warn("Invalid inbound message transport type, this decoder only support HTTPInTransport");
148 throw new MessageDecodingException(
149 "Invalid inbound message transport type, this decoder only support HTTPInTransport");
150 }
151
152 SSORequestContext requestContext = (SSORequestContext) messageContext;
153 HTTPInTransport transport = (HTTPInTransport) messageContext.getInboundMessageTransport();
154
155 String providerId = DatatypeHelper.safeTrimOrNullString(transport.getParameterValue("providerId"));
156 if (providerId == null) {
157 log.warn("No providerId parameter given in unsolicited SSO authentication request.");
158 throw new MessageDecodingException(
159 "No providerId parameter given in unsolicited SSO authentication request.");
160 }
161
162 requestContext.setRelayState(DatatypeHelper.safeTrimOrNullString(transport.getParameterValue("target")));
163
164 String timeStr = DatatypeHelper.safeTrimOrNullString(transport.getParameterValue("time"));
165 String sessionID = ((HttpServletRequestAdapter) transport).getWrappedRequest().getRequestedSessionId();
166
167 String binding = null;
168 String acsURL = DatatypeHelper.safeTrimOrNullString(transport.getParameterValue("shire"));
169 if (acsURL == null) {
170 acsURL = lookupACSURL(requestContext.getMetadataProvider(), providerId);
171 if (acsURL == null) {
172 log.warn("Unable to resolve SP ACS URL for AuthnRequest construction for entityID: {}",
173 providerId);
174 throw new MessageDecodingException("Unable to resolve SP ACS URL for AuthnRequest construction");
175 }
176 binding = defaultBinding;
177 }
178
179 AuthnRequest authnRequest = buildAuthnRequest(providerId, acsURL, binding, timeStr, sessionID);
180 requestContext.setInboundMessage(authnRequest);
181 requestContext.setInboundSAMLMessage(authnRequest);
182 log.debug("Mocked up SAML message");
183
184 populateMessageContext(requestContext);
185
186 requestContext.setUnsolicited(true);
187 }
188
189
190
191
192
193
194
195
196
197
198
199 @SuppressWarnings("unchecked")
200 private AuthnRequest buildAuthnRequest(String entityID, String acsURL, String acsBinding, String timeStr, String sessionID) {
201
202 AuthnRequest authnRequest = authnRequestBuilder.buildObject();
203 authnRequest.setAssertionConsumerServiceURL(acsURL);
204 if (acsBinding != null) {
205 authnRequest.setProtocolBinding(acsBinding);
206 }
207
208 Issuer issuer = issuerBuilder.buildObject();
209 issuer.setValue(entityID);
210 authnRequest.setIssuer(issuer);
211
212
213 NameIDPolicy nip = nipBuilder.buildObject();
214 nip.setAllowCreate(true);
215 authnRequest.setNameIDPolicy(nip);
216
217 if (timeStr != null) {
218 authnRequest.setIssueInstant(
219 new DateTime(Long.parseLong(timeStr) * 1000, ISOChronology.getInstanceUTC()));
220 if (sessionID != null) {
221
222
223
224 authnRequest.setID('_' + sessionID + '!' + timeStr);
225 } else {
226 authnRequest.setID(idGenerator.generateIdentifier());
227 }
228 } else {
229 authnRequest.setID(idGenerator.generateIdentifier());
230 authnRequest.setIssueInstant(new DateTime());
231 }
232
233 return authnRequest;
234 }
235
236
237
238
239
240
241
242
243
244 @SuppressWarnings("unchecked")
245 private String lookupACSURL(MetadataProvider mdProvider, String entityId)
246 throws MessageDecodingException {
247 SPSSODescriptor spssoDesc = null;
248 try {
249 spssoDesc = (SPSSODescriptor) mdProvider.getRole(entityId, SPSSODescriptor.DEFAULT_ELEMENT_NAME,
250 SAMLConstants.SAML20P_NS);
251 } catch (MetadataProviderException e) {
252 throw new MessageDecodingException("Error resolving metadata role for SP entityId: " + entityId, e);
253 }
254
255 if (spssoDesc == null) {
256 throw new MessageDecodingException(
257 "SAML 2 SPSSODescriptor could not be resolved from metadata for SP entityID: " + entityId);
258 }
259
260 BasicEndpointSelector selector = new BasicEndpointSelector();
261 selector.setEntityRoleMetadata(spssoDesc);
262 selector.setEndpointType(AssertionConsumerService.DEFAULT_ELEMENT_NAME);
263 selector.getSupportedIssuerBindings().add(defaultBinding);
264
265 Endpoint endpoint = selector.selectEndpoint();
266 if (endpoint == null || endpoint.getLocation() == null) {
267 throw new MessageDecodingException(
268 "SAML 2 ACS endpoint could not be resolved from metadata for SP entityID and binding: " + entityId
269 + " -- " + defaultBinding);
270 }
271
272 return endpoint.getLocation();
273 }
274
275 }