Initial ADFS check in, compiles, but not tested.
[shibboleth/cpp-sp.git] / adfs / listener.cpp
1 /*
2  *  Copyright 2001-2005 Internet2
3  * 
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /*
18  * listener.cpp -- implementation of IListener functional methods that includes ADFS support
19  *
20  * Scott Cantor
21  * 10/10/05
22  *
23  */
24
25 #include "internal.h"
26
27 #include <xercesc/framework/MemBufInputSource.hpp>
28
29 using namespace std;
30 using namespace log4cpp;
31 using namespace saml;
32 using namespace shibboleth;
33 using namespace shibtarget;
34 using namespace adfs;
35
36 namespace {
37     class ADFSListener : public virtual IListener
38     {
39     public:
40         ADFSListener(const DOMElement* e) : log(&Category::getInstance(ADFS_LOGCAT".Listener")) {}
41         ~ADFSListener() {}
42
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;}
48
49         void sessionNew(
50             const IApplication* application,
51             int supported_profiles,
52             const char* recipient,
53             const char* packet,
54             const char* ip,
55             std::string& target,
56             std::string& cookie,
57             std::string& provider_id
58             ) const;
59     
60         void sessionGet(
61             const IApplication* application,
62             const char* cookie,
63             const char* ip,
64             ISessionCacheEntry** pentry
65             ) const;
66     
67         void sessionEnd(
68             const IApplication* application,
69             const char* cookie
70         ) const;
71         
72         void ping(int& i) const;
73
74     private:
75         Category* log;
76     };
77 }
78
79 IPlugIn* ADFSListenerFactory(const DOMElement* e)
80 {
81     return new ADFSListener(e);
82 }
83
84 void ADFSListener::sessionNew(
85     const IApplication* app,
86     int supported_profiles,
87     const char* recipient,
88     const char* packet,
89     const char* ip,
90     string& target,
91     string& cookie,
92     string& provider_id
93     ) const
94 {
95 #ifdef _DEBUG
96     saml::NDC ndc("sessionNew");
97 #endif
98
99     log->debug("creating session for %s", ip);
100     log->debug("recipient: %s", recipient);
101     log->debug("application: %s", app->getId());
102
103     auto_ptr_XMLCh wrecipient(recipient);
104
105     // Access the application config. It's already locked behind us.
106     ShibTargetConfig& stc=ShibTargetConfig::getConfig();
107     IConfig* conf=stc.getINI();
108
109     bool checkIPAddress=true;
110     const IPropertySet* props=app->getPropertySet("Sessions");
111     if (props) {
112         pair<bool,bool> pcheck=props->getBool("checkAddress");
113         if (pcheck.first)
114             checkIPAddress = pcheck.second;
115     }
116
117     pair<bool,bool> checkReplay=pair<bool,bool>(false,false);
118     props=app->getPropertySet("Sessions");
119     if (props)
120         checkReplay=props->getBool("checkReplay");
121  
122     const IRoleDescriptor* role=NULL;
123     Metadata m(app->getMetadataProviders());
124
125     bool bADFS = false;
126     SAMLBrowserProfile::BrowserProfileResponse bpr;
127
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")) {
134             bADFS=true;
135             param=parser.get_value("wresult");
136             if (!param)
137                 throw FatalProfileException("ADFS profile required wresult parameter not found");
138             
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;
142             try {
143                 saml::XML::Parser p;
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);
147                 rdoc=p.parse(dsrc);
148         
149                 // Process the wrapper and extract the assertion.
150                 if (saml::XML::isElementNamed(rdoc->getDocumentElement(),adfs::XML::WSTRUST_NS,ADFS_L(RequestSecurityTokenResponse))) {
151                     DOMElement* e=
152                         saml::XML::getFirstChildElement(rdoc->getDocumentElement(),adfs::XML::WSTRUST_NS,ADFS_L(RequestedSecurityToken));
153                     if (e) {
154                         e=saml::XML::getFirstChildElement(e,saml::XML::SAML_NS,L(Assertion));
155                         if (e) {
156                             auto_ptr<SAMLAssertion> assertion(new SAMLAssertion(e));
157                             
158                             // Try and map to metadata.
159                             const IEntityDescriptor* provider=m.lookup(assertion->getIssuer());
160                             if (provider)
161                                 role=provider->getIDPSSODescriptor(adfs::XML::WSFED_NS);
162                             if (!role) {
163                                 MetadataException ex("unable to locate role-specific metadata for identity provider.");
164                                 annotateException(&ex,provider); // throws it
165                             }
166                             
167                             try {
168                                 // Check over the assertion.
169                                 SAMLAuthenticationStatement* authnStatement=checkAssertionProfile(assertion.get());
170                                 
171                                 // Check signature.
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");
177                                 }
178                                 
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);
186                                 rdoc=NULL;
187                                 
188                                 // Now dummy up the SAML profile response wrapper.
189                                 param=parser.get_value("wctx");
190                                 if (param)
191                                     bpr.TARGET=param;
192                                 bpr.profile=SAMLBrowserProfile::Post;   // not really, but...
193                                 bpr.response=response.release();
194                                 bpr.assertion=response->getAssertions().next();
195                                 bpr.authnStatement=authnStatement;
196                             }
197                             catch (SAMLException& ex) {
198                                 annotateException(&ex,role); // throws it
199                             }
200                         }
201                     }
202                 }
203                 if (rdoc) {
204                     rdoc->release();
205                     rdoc=NULL;
206                 }
207             }
208             catch(...) {
209                 if (rdoc) rdoc->release();
210                 throw;
211             }
212         }
213         if (bADFS && !bpr.response)
214             throw FatalProfileException("ADFS profile was indicated, but processing was unsuccesful");
215     }
216     
217     // If ADFS wasn't used, proceed to SAML processing up until we reach a common point.
218     int minorVersion = 1;
219     try {
220         if (!bADFS) {
221             int allowed = 0;
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;
227     
228             auto_ptr<SAMLBrowserProfile::ArtifactMapper> artifactMapper(app->getArtifactMapper());
229       
230             // Try and run the profile.
231             log->debug("executing browser profile...");
232             bpr=app->getBrowserProfile()->receive(
233                 packet,
234                 wrecipient.get(),
235                 allowed,
236                 (!checkReplay.first || checkReplay.second) ? conf->getReplayCache() : NULL,
237                 artifactMapper.get(),
238                 minorVersion
239                 );
240     
241             // Blow it away to clear any locks that might be held.
242             delete artifactMapper.release();
243     
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());
250             if (provider) {
251                 const IIDPSSODescriptor* IDP=provider->getIDPSSODescriptor(
252                     minorVersion==1 ? saml::XML::SAML11_PROTOCOL_ENUM : saml::XML::SAML10_PROTOCOL_ENUM
253                     );
254                 role=IDP;
255             }
256             
257             // This isn't likely, since the profile must have found a role.
258             if (!role) {
259                 MetadataException ex("Unable to locate role-specific metadata for identity provider.");
260                 annotateException(&ex,provider); // throws it
261             }
262         }
263         
264         // At this point, we link back up and do the same work for ADFS and SAML.
265         
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();
271             if (wip && *wip) {
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.",
280                         params(1,ip)
281                         );
282                     annotateException(&ex,role); // throws it
283                 }
284             }
285         }
286       
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);
293             if (!ac) {
294                 ostringstream os;
295                 os << *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
299             }
300             else if (!ac->eval(app->getAudiences())) {
301                 ostringstream os;
302                 os << *ac;
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
306             }
307         }
308     }
309     catch (SAMLException&) {
310         bpr.clear();
311         throw;
312     }
313     catch (...) {
314         log->error("caught unknown exception");
315         bpr.clear();
316 #ifdef _DEBUG
317         throw;
318 #else
319         SAMLException e("An unexpected error occurred while creating your session.");
320         annotateException(&e,role);
321 #endif
322     }
323
324     // It passes all our tests -- create a new session.
325     log->info("creating new session");
326
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;
335         }
336     }
337
338     auto_ptr_char oname(role->getEntityDescriptor()->getId());
339     auto_ptr_char hname(bpr.authnStatement->getSubject()->getNameIdentifier()->getName());
340
341     try {
342         // Create a new session key.
343         cookie = conf->getSessionCache()->generateKey();
344
345         // Insert into cache.
346         auto_ptr<SAMLAuthenticationStatement> as(static_cast<SAMLAuthenticationStatement*>(bpr.authnStatement->clone()));
347         conf->getSessionCache()->insert(
348             cookie.c_str(),
349             app,
350             ip,
351             (bADFS ? ADFS_SSO :
352                 ((bpr.profile==SAMLBrowserProfile::Post) ?
353                     (minorVersion==1 ? SAML11_POST : SAML10_POST) : (minorVersion==1 ? SAML11_ARTIFACT : SAML10_ARTIFACT))),
354             oname.get(),
355             as.get(),
356             (attributesPushed ? bpr.response : NULL),
357             role
358             );
359         as.release();   // owned by cache now
360     }
361     catch (SAMLException&) {
362         bpr.clear();
363         throw;
364     }
365     catch (...) {
366         log->error("caught unknown exception");
367         bpr.clear();
368 #ifdef _DEBUG
369         throw;
370 #else
371         SAMLException e("An unexpected error occurred while creating your session.");
372         annotateException(&e,role);
373 #endif
374     }
375
376     target = bpr.TARGET;
377     provider_id = oname.get();
378
379     // Maybe delete the response...
380     if (!attributesPushed)
381         bpr.clear();
382
383     log->debug("new session id: %s", cookie.c_str());
384   
385     // Transaction Logging
386     Category::getInstance(SHIBTRAN_LOGCAT).infoStream() <<
387         "New session (ID: " <<
388             cookie <<
389         ") with (applicationId: " <<
390             app->getId() <<
391         ") for principal from (IdP: " <<
392             provider_id <<
393         ") at (ClientAddress: " <<
394             ip <<
395         ") with (NameIdentifier: " <<
396             hname.get() <<
397         ")";
398     //stc.releaseTransactionLog();
399 }
400
401 void ADFSListener::sessionGet(
402     const IApplication* app,
403     const char* cookie,
404     const char* ip,
405     ISessionCacheEntry** pentry
406     ) const
407 {
408 #ifdef _DEBUG
409     saml::NDC ndc("sessionGet");
410 #endif
411
412     *pentry=NULL;
413     log->debug("checking for session: %s@%s", cookie, ip);
414
415     // See if the session exists...
416
417     ShibTargetConfig& stc=ShibTargetConfig::getConfig();
418     IConfig* conf=stc.getINI();
419     log->debug("application: %s", app->getId());
420
421     bool checkIPAddress=true;
422     int lifetime=0,timeout=0;
423     const IPropertySet* props=app->getPropertySet("Sessions");
424     if (props) {
425         pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
426         if (p.first)
427             lifetime = p.second;
428         p=props->getUnsignedInt("timeout");
429         if (p.first)
430             timeout = p.second;
431         pair<bool,bool> pcheck=props->getBool("checkAddress");
432         if (pcheck.first)
433             checkIPAddress = pcheck.second;
434     }
435     
436     *pentry = conf->getSessionCache()->find(cookie,app);
437
438     // If not, leave now..
439     if (!*pentry) {
440         log->debug("session not found");
441         throw InvalidSessionException("No session exists for key value ($session_id)",namedparams(1,"session_id",cookie));
442     }
443
444     // TEST the session...
445     try {
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.",
454                     params(1,ip)
455                     );
456                 Metadata m(app->getMetadataProviders());
457                 annotateException(&ex,m.lookup((*pentry)->getProviderId())); // throws it
458             }
459         }
460
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
467         }
468     }
469     catch (SAMLException&) {
470         (*pentry)->unlock();
471         *pentry=NULL;
472         conf->getSessionCache()->remove(cookie);
473       
474         // Transaction Logging
475         Category::getInstance(SHIBTRAN_LOGCAT).infoStream() <<
476             "Destroyed invalid session (ID: " <<
477                 cookie <<
478             ") with (applicationId: " <<
479                 app->getId() <<
480             "), request was from (ClientAddress: " <<
481                 ip <<
482             ")";
483         //stc.releaseTransactionLog();
484         throw;
485     }
486     catch (...) {
487         log->error("caught unknown exception");
488 #ifndef _DEBUG
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);
492 #endif
493         (*pentry)->unlock();
494         *pentry=NULL;
495         conf->getSessionCache()->remove(cookie);
496
497         // Transaction Logging
498         Category::getInstance(SHIBTRAN_LOGCAT).infoStream() <<
499             "Destroyed invalid session (ID: " <<
500                 cookie <<
501             ") with (applicationId: " <<
502                 app->getId() <<
503             "), request was from (ClientAddress: " <<
504                 ip <<
505             ")";
506         //stc.releaseTransactionLog();
507 #ifdef _DEBUG
508         throw;
509 #else
510         ex.raise();
511 #endif
512     }
513
514     log->debug("session ok");
515 }
516
517 void ADFSListener::sessionEnd(
518     const IApplication* application,
519     const char* cookie
520     ) const
521 {
522 #ifdef _DEBUG
523     saml::NDC ndc("sessionEnd");
524 #endif
525
526     log->debug("removing session: %s", cookie);
527
528     ShibTargetConfig& stc=ShibTargetConfig::getConfig();
529     stc.getINI()->getSessionCache()->remove(cookie);
530   
531     // Transaction Logging
532     Category::getInstance(SHIBTRAN_LOGCAT).infoStream() << "Destroyed session (ID: " << cookie << ")";
533     //stc.releaseTransactionLog();
534 }
535
536 void ADFSListener::ping(int& i) const
537 {
538     i++;
539 }