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