2 * Copyright 2001-2005 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * listener.cpp -- implementation of IListener functional methods that includes ADFS support
27 #include <xercesc/framework/MemBufInputSource.hpp>
30 using namespace log4cpp;
32 using namespace shibboleth;
33 using namespace shibtarget;
37 class ADFSListener : public virtual IListener
40 ADFSListener(const DOMElement* e) : log(&Category::getInstance(ADFS_LOGCAT".Listener")) {}
43 bool create(ShibSocket& s) const {return true;}
44 bool bind(ShibSocket& s, bool force=false) const {return true;}
45 bool connect(ShibSocket& s) const {return true;}
46 bool close(ShibSocket& s) const {return true;}
47 bool accept(ShibSocket& listener, ShibSocket& s) const {return true;}
50 const IApplication* application,
51 int supported_profiles,
52 const char* recipient,
57 std::string& provider_id
61 const IApplication* application,
64 ISessionCacheEntry** pentry
68 const IApplication* application,
72 void ping(int& i) const;
79 IPlugIn* ADFSListenerFactory(const DOMElement* e)
81 return new ADFSListener(e);
84 void ADFSListener::sessionNew(
85 const IApplication* app,
86 int supported_profiles,
87 const char* recipient,
96 saml::NDC ndc("sessionNew");
99 log->debug("creating session for %s", ip);
100 log->debug("recipient: %s", recipient);
101 log->debug("application: %s", app->getId());
103 auto_ptr_XMLCh wrecipient(recipient);
105 // Access the application config. It's already locked behind us.
106 ShibTargetConfig& stc=ShibTargetConfig::getConfig();
107 IConfig* conf=stc.getINI();
109 bool checkIPAddress=true;
110 const IPropertySet* props=app->getPropertySet("Sessions");
112 pair<bool,bool> pcheck=props->getBool("checkAddress");
114 checkIPAddress = pcheck.second;
117 pair<bool,bool> checkReplay=pair<bool,bool>(false,false);
118 props=app->getPropertySet("Sessions");
120 checkReplay=props->getBool("checkReplay");
122 const IRoleDescriptor* role=NULL;
123 Metadata m(app->getMetadataProviders());
126 SAMLBrowserProfile::BrowserProfileResponse bpr;
128 // For now, just branch off to handle ADFS inline, I'll wrap all this up later.
129 if (supported_profiles & ADFS_SSO) {
130 log->debug("executing ADFS profile...");
131 CgiParse parser(packet,strlen(packet));
132 const char* param=parser.get_value("wa");
133 if (param && !strcmp(param,"wsignin1.0")) {
135 param=parser.get_value("wresult");
137 throw FatalProfileException("ADFS profile required wresult parameter not found");
139 log->debug("decoded ADFS Token response:\n%s",param);
140 // wresult should carry an wst:RequestSecurityTokenResponse message so we parse it manually
141 DOMDocument* rdoc=NULL;
144 static const XMLCh systemId[]={chLatin_W, chLatin_S, chDash, chLatin_T, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chNull};
145 MemBufInputSource membufsrc(reinterpret_cast<const XMLByte*>(param),strlen(param),systemId,false);
146 Wrapper4InputSource dsrc(&membufsrc,false);
149 // Process the wrapper and extract the assertion.
150 if (saml::XML::isElementNamed(rdoc->getDocumentElement(),adfs::XML::WSTRUST_NS,ADFS_L(RequestSecurityTokenResponse))) {
152 saml::XML::getFirstChildElement(rdoc->getDocumentElement(),adfs::XML::WSTRUST_NS,ADFS_L(RequestedSecurityToken));
154 e=saml::XML::getFirstChildElement(e,saml::XML::SAML_NS,L(Assertion));
156 auto_ptr<SAMLAssertion> assertion(new SAMLAssertion(e));
158 // Try and map to metadata.
159 const IEntityDescriptor* provider=m.lookup(assertion->getIssuer());
161 role=provider->getIDPSSODescriptor(adfs::XML::WSFED_NS);
163 MetadataException ex("unable to locate role-specific metadata for identity provider.");
164 annotateException(&ex,provider); // throws it
168 // Check over the assertion.
169 SAMLAuthenticationStatement* authnStatement=checkAssertionProfile(assertion.get());
172 log->debug("passing signed ADFS assertion to trust layer");
173 Trust t(app->getTrustProviders());
174 if (!t.validate(*(assertion.get()),role)) {
175 log->error("unable to verify signed authentication assertion");
176 throw TrustException("unable to verify signed authentication assertion");
179 // Wrap the assertion in a dummy samlp:Response for subsequent processing.
180 // Generate the Response DOM using the assertion's document and then
181 // transfer ownership of the tree to the Response.
182 auto_ptr<SAMLResponse> response(new SAMLResponse());
183 response->addAssertion(assertion.release());
184 response->toDOM(rdoc);
185 response->setDocument(rdoc);
188 // Now dummy up the SAML profile response wrapper.
189 param=parser.get_value("wctx");
192 bpr.profile=SAMLBrowserProfile::Post; // not really, but...
193 bpr.response=response.release();
194 bpr.assertion=response->getAssertions().next();
195 bpr.authnStatement=authnStatement;
197 catch (SAMLException& ex) {
198 annotateException(&ex,role); // throws it
209 if (rdoc) rdoc->release();
213 if (bADFS && !bpr.response)
214 throw FatalProfileException("ADFS profile was indicated, but processing was unsuccesful");
217 // If ADFS wasn't used, proceed to SAML processing up until we reach a common point.
218 int minorVersion = 1;
222 if (supported_profiles & SAML11_POST || supported_profiles & SAML10_POST)
223 allowed |= SAMLBrowserProfile::Post;
224 if (supported_profiles & SAML11_ARTIFACT || supported_profiles & SAML10_ARTIFACT)
225 allowed |= SAMLBrowserProfile::Artifact;
226 minorVersion=(supported_profiles & SAML11_ARTIFACT || supported_profiles & SAML11_POST) ? 1 : 0;
228 auto_ptr<SAMLBrowserProfile::ArtifactMapper> artifactMapper(app->getArtifactMapper());
230 // Try and run the profile.
231 log->debug("executing browser profile...");
232 bpr=app->getBrowserProfile()->receive(
236 (!checkReplay.first || checkReplay.second) ? conf->getReplayCache() : NULL,
237 artifactMapper.get(),
241 // Blow it away to clear any locks that might be held.
242 delete artifactMapper.release();
244 // Try and map to metadata (again).
245 // Once the metadata layer is in the SAML core, the repetition should be fixed.
246 const IEntityDescriptor* provider=m.lookup(bpr.assertion->getIssuer());
247 if (!provider && bpr.authnStatement->getSubject()->getNameIdentifier() &&
248 bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier())
249 provider=m.lookup(bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier());
251 const IIDPSSODescriptor* IDP=provider->getIDPSSODescriptor(
252 minorVersion==1 ? saml::XML::SAML11_PROTOCOL_ENUM : saml::XML::SAML10_PROTOCOL_ENUM
257 // This isn't likely, since the profile must have found a role.
259 MetadataException ex("Unable to locate role-specific metadata for identity provider.");
260 annotateException(&ex,provider); // throws it
264 // At this point, we link back up and do the same work for ADFS and SAML.
266 // Maybe verify the origin address....
267 if (checkIPAddress) {
268 log->debug("verifying client address");
269 // Verify the client address exists
270 const XMLCh* wip = bpr.authnStatement->getSubjectIP();
272 // Verify the client address matches authentication
273 auto_ptr_char this_ip(ip);
274 if (strcmp(ip, this_ip.get())) {
275 FatalProfileException ex(
276 SESSION_E_ADDRESSMISMATCH,
277 "Your client's current address ($1) differs from the one used when you authenticated "
278 "to your identity provider. To correct this problem, you may need to bypass a proxy server. "
279 "Please contact your local support staff or help desk for assistance.",
282 annotateException(&ex,role); // throws it
287 // Verify condition(s) on authentication assertion.
288 // Attribute assertions get filtered later by the AAP.
289 Iterator<SAMLCondition*> conditions=bpr.assertion->getConditions();
290 while (conditions.hasNext()) {
291 SAMLCondition* cond=conditions.next();
292 const SAMLAudienceRestrictionCondition* ac=dynamic_cast<const SAMLAudienceRestrictionCondition*>(cond);
296 log->error("Unrecognized Condition in authentication assertion (%s), tossing it.",os.str().c_str());
297 FatalProfileException ex("unable to create session due to unrecognized condition in authentication assertion.");
298 annotateException(&ex,role); // throws it
300 else if (!ac->eval(app->getAudiences())) {
303 log->error("Unacceptable AudienceRestrictionCondition in authentication assertion (%s), tossing it.",os.str().c_str());
304 FatalProfileException ex("unable to create session due to unacceptable AudienceRestrictionCondition in authentication assertion.");
305 annotateException(&ex,role); // throws it
309 catch (SAMLException&) {
314 log->error("caught unknown exception");
319 SAMLException e("An unexpected error occurred while creating your session.");
320 annotateException(&e,role);
324 // It passes all our tests -- create a new session.
325 log->info("creating new session");
327 // Are attributes present?
328 bool attributesPushed=false;
329 Iterator<SAMLAssertion*> assertions=bpr.response->getAssertions();
330 while (!attributesPushed && assertions.hasNext()) {
331 Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
332 while (!attributesPushed && statements.hasNext()) {
333 if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
334 attributesPushed=true;
338 auto_ptr_char oname(role->getEntityDescriptor()->getId());
339 auto_ptr_char hname(bpr.authnStatement->getSubject()->getNameIdentifier()->getName());
342 // Create a new session key.
343 cookie = conf->getSessionCache()->generateKey();
345 // Insert into cache.
346 auto_ptr<SAMLAuthenticationStatement> as(static_cast<SAMLAuthenticationStatement*>(bpr.authnStatement->clone()));
347 conf->getSessionCache()->insert(
352 ((bpr.profile==SAMLBrowserProfile::Post) ?
353 (minorVersion==1 ? SAML11_POST : SAML10_POST) : (minorVersion==1 ? SAML11_ARTIFACT : SAML10_ARTIFACT))),
356 (attributesPushed ? bpr.response : NULL),
359 as.release(); // owned by cache now
361 catch (SAMLException&) {
366 log->error("caught unknown exception");
371 SAMLException e("An unexpected error occurred while creating your session.");
372 annotateException(&e,role);
377 provider_id = oname.get();
379 // Maybe delete the response...
380 if (!attributesPushed)
383 log->debug("new session id: %s", cookie.c_str());
385 // Transaction Logging
386 Category::getInstance(SHIBTRAN_LOGCAT).infoStream() <<
387 "New session (ID: " <<
389 ") with (applicationId: " <<
391 ") for principal from (IdP: " <<
393 ") at (ClientAddress: " <<
395 ") with (NameIdentifier: " <<
398 //stc.releaseTransactionLog();
401 void ADFSListener::sessionGet(
402 const IApplication* app,
405 ISessionCacheEntry** pentry
409 saml::NDC ndc("sessionGet");
413 log->debug("checking for session: %s@%s", cookie, ip);
415 // See if the session exists...
417 ShibTargetConfig& stc=ShibTargetConfig::getConfig();
418 IConfig* conf=stc.getINI();
419 log->debug("application: %s", app->getId());
421 bool checkIPAddress=true;
422 int lifetime=0,timeout=0;
423 const IPropertySet* props=app->getPropertySet("Sessions");
425 pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
428 p=props->getUnsignedInt("timeout");
431 pair<bool,bool> pcheck=props->getBool("checkAddress");
433 checkIPAddress = pcheck.second;
436 *pentry = conf->getSessionCache()->find(cookie,app);
438 // If not, leave now..
440 log->debug("session not found");
441 throw InvalidSessionException("No session exists for key value ($session_id)",namedparams(1,"session_id",cookie));
444 // TEST the session...
446 // Verify the address is the same
447 if (checkIPAddress) {
448 log->debug("Checking address against %s", (*pentry)->getClientAddress());
449 if (strcmp(ip, (*pentry)->getClientAddress())) {
450 log->debug("client address mismatch");
451 InvalidSessionException ex(
452 SESSION_E_ADDRESSMISMATCH,
453 "Your IP address (%1) does not match the address recorded at the time the session was established.",
456 Metadata m(app->getMetadataProviders());
457 annotateException(&ex,m.lookup((*pentry)->getProviderId())); // throws it
461 // and that the session is still valid...
462 if (!(*pentry)->isValid(lifetime,timeout)) {
463 log->debug("session expired");
464 InvalidSessionException ex(SESSION_E_EXPIRED, "Your session has expired, and you must re-authenticate.");
465 Metadata m(app->getMetadataProviders());
466 annotateException(&ex,m.lookup((*pentry)->getProviderId())); // throws it
469 catch (SAMLException&) {
472 conf->getSessionCache()->remove(cookie);
474 // Transaction Logging
475 Category::getInstance(SHIBTRAN_LOGCAT).infoStream() <<
476 "Destroyed invalid session (ID: " <<
478 ") with (applicationId: " <<
480 "), request was from (ClientAddress: " <<
483 //stc.releaseTransactionLog();
487 log->error("caught unknown exception");
489 InvalidSessionException ex("An unexpected error occurred while validating your session, and you must re-authenticate.");
490 Metadata m(app->getMetadataProviders());
491 annotateException(&ex,m.lookup((*pentry)->getProviderId()),false);
495 conf->getSessionCache()->remove(cookie);
497 // Transaction Logging
498 Category::getInstance(SHIBTRAN_LOGCAT).infoStream() <<
499 "Destroyed invalid session (ID: " <<
501 ") with (applicationId: " <<
503 "), request was from (ClientAddress: " <<
506 //stc.releaseTransactionLog();
514 log->debug("session ok");
517 void ADFSListener::sessionEnd(
518 const IApplication* application,
523 saml::NDC ndc("sessionEnd");
526 log->debug("removing session: %s", cookie);
528 ShibTargetConfig& stc=ShibTargetConfig::getConfig();
529 stc.getINI()->getSessionCache()->remove(cookie);
531 // Transaction Logging
532 Category::getInstance(SHIBTRAN_LOGCAT).infoStream() << "Destroyed session (ID: " << cookie << ")";
533 //stc.releaseTransactionLog();
536 void ADFSListener::ping(int& i) const