561bd621826f6e32b139e6113543ac58be6991cf
[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 <log4cpp/FixedContextCategory.hh>
28 #include <xercesc/framework/MemBufInputSource.hpp>
29
30 using namespace std;
31 using namespace log4cpp;
32 using namespace saml;
33 using namespace shibboleth;
34 using namespace shibtarget;
35 using namespace adfs;
36
37 namespace {
38     class ADFSListener : public virtual IListener
39     {
40     public:
41         ADFSListener(const DOMElement* e) : log(&Category::getInstance(ADFS_LOGCAT".Listener")) {}
42         ~ADFSListener() {}
43
44         bool create(ShibSocket& s) const {return true;}
45         bool bind(ShibSocket& s, bool force=false) const {return true;}
46         bool connect(ShibSocket& s) const {return true;}
47         bool close(ShibSocket& s) const {return true;}
48         bool accept(ShibSocket& listener, ShibSocket& s) const {return true;}
49
50         void sessionNew(
51             const IApplication* application,
52             int supported_profiles,
53             const char* recipient,
54             const char* packet,
55             const char* ip,
56             std::string& target,
57             std::string& cookie,
58             std::string& provider_id
59             ) const;
60     
61         void sessionGet(
62             const IApplication* application,
63             const char* cookie,
64             const char* ip,
65             ISessionCacheEntry** pentry
66             ) const;
67     
68         void sessionEnd(
69             const IApplication* application,
70             const char* cookie
71         ) const;
72         
73         void ping(int& i) const;
74
75     private:
76         Category* log;
77     };
78 }
79
80 IPlugIn* ADFSListenerFactory(const DOMElement* e)
81 {
82     return new ADFSListener(e);
83 }
84
85 void ADFSListener::sessionNew(
86     const IApplication* app,
87     int supported_profiles,
88     const char* recipient,
89     const char* packet,
90     const char* ip,
91     string& target,
92     string& cookie,
93     string& provider_id
94     ) const
95 {
96 #ifdef _DEBUG
97     saml::NDC ndc("sessionNew");
98 #endif
99
100     log->debug("creating session for %s", ip);
101     log->debug("recipient: %s", recipient);
102     log->debug("application: %s", app->getId());
103
104     auto_ptr_XMLCh wrecipient(recipient);
105
106     // Access the application config. It's already locked behind us.
107     ShibTargetConfig& stc=ShibTargetConfig::getConfig();
108     IConfig* conf=stc.getINI();
109
110     bool checkIPAddress=true;
111     const IPropertySet* props=app->getPropertySet("Sessions");
112     if (props) {
113         pair<bool,bool> pcheck=props->getBool("checkAddress");
114         if (pcheck.first)
115             checkIPAddress = pcheck.second;
116     }
117
118     pair<bool,bool> checkReplay=pair<bool,bool>(false,false);
119     props=app->getPropertySet("Sessions");
120     if (props)
121         checkReplay=props->getBool("checkReplay");
122  
123     const IRoleDescriptor* role=NULL;
124     Metadata m(app->getMetadataProviders());
125
126     bool bADFS = false;
127     SAMLBrowserProfile::BrowserProfileResponse bpr;
128
129     // For now, just branch off to handle ADFS inline, I'll wrap all this up later.
130     if (supported_profiles & ADFS_SSO) {
131         log->debug("executing ADFS profile...");
132         CgiParse parser(packet,strlen(packet));
133         const char* param=parser.get_value("wa");
134         if (param && !strcmp(param,"wsignin1.0")) {
135             bADFS=true;
136             param=parser.get_value("wresult");
137             if (!param)
138                 throw FatalProfileException("ADFS profile required wresult parameter not found");
139             
140             log->debug("decoded ADFS Token response:\n%s",param);
141             // wresult should carry an wst:RequestSecurityTokenResponse message so we parse it manually
142             DOMDocument* rdoc=NULL;
143             try {
144                 saml::XML::Parser p;
145                 static const XMLCh systemId[]={chLatin_W, chLatin_S, chDash, chLatin_T, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chNull};
146                 MemBufInputSource membufsrc(reinterpret_cast<const XMLByte*>(param),strlen(param),systemId,false);
147                 Wrapper4InputSource dsrc(&membufsrc,false);
148                 rdoc=p.parse(dsrc);
149         
150                 // Process the wrapper and extract the assertion.
151                 if (saml::XML::isElementNamed(rdoc->getDocumentElement(),adfs::XML::WSTRUST_NS,ADFS_L(RequestSecurityTokenResponse))) {
152                     DOMElement* e=
153                         saml::XML::getFirstChildElement(rdoc->getDocumentElement(),adfs::XML::WSTRUST_NS,ADFS_L(RequestedSecurityToken));
154                     if (e) {
155                         e=saml::XML::getFirstChildElement(e,saml::XML::SAML_NS,L(Assertion));
156                         if (e) {
157
158                             // Wrap the assertion DOM in a dummy samlp:Response for subsequent processing.
159                             // We have to manually create the Response DOM first in order to avoid
160                             // corrupting the namespace declarations in the Assertion.
161
162                             static const XMLCh One[]={chDigit_1, chNull};
163                             static const XMLCh dummyID[] = {chLatin_A, chLatin_D, chLatin_F, chLatin_S, chNull};
164                             static const XMLCh samlp_Success[]=
165                             { chLatin_s, chLatin_a, chLatin_m, chLatin_l, chLatin_p, chColon,
166                               chLatin_S, chLatin_u, chLatin_c, chLatin_c, chLatin_e, chLatin_s, chLatin_s, chNull };
167                             DOMElement* rdom=rdoc->createElementNS(saml::XML::SAMLP_NS,L(Response));
168                             rdom->setAttributeNS(saml::XML::XMLNS_NS,L_QNAME(xmlns,samlp),saml::XML::SAMLP_NS);
169                             rdom->setAttributeNS(saml::XML::XMLNS_NS,L(xmlns),saml::XML::SAMLP_NS);
170                             rdom->setAttributeNS(NULL,L(MajorVersion),One);
171                             rdom->setAttributeNS(NULL,L(MinorVersion),One);
172                             rdom->setAttributeNS(NULL,L(ResponseID),dummyID);
173                             SAMLDateTime issued(time(NULL));
174                             issued.parseDateTime();
175                             rdom->setAttributeNS(NULL,L(IssueInstant),issued.getRawData());
176                             DOMElement* status=rdoc->createElementNS(saml::XML::SAMLP_NS,L(Status));
177                             rdom->appendChild(status);
178                             DOMElement* code=rdoc->createElementNS(saml::XML::SAMLP_NS,L(StatusCode));
179                             code->setAttributeNS(NULL,L(Value),samlp_Success);
180                             status->appendChild(code);
181                             rdom->appendChild(e);   // append the assertion
182                             auto_ptr<SAMLResponse> response(new SAMLResponse(rdom));
183                             response->setDocument(rdoc);    // give the Document to the response object
184                             // root the response in the document so the signature will verify
185                             rdoc->replaceChild(response->toDOM(rdoc,false),rdoc->getDocumentElement());
186                             rdoc=NULL;
187                             
188                             // Try and map to metadata.
189                             SAMLAssertion* assertion=response->getAssertions().next();
190                             const IEntityDescriptor* provider=m.lookup(assertion->getIssuer());
191                             if (provider)
192                                 role=provider->getIDPSSODescriptor(adfs::XML::WSFED_NS);
193                             if (!role) {
194                                 MetadataException ex("unable to locate role-specific metadata for identity provider.");
195                                 annotateException(&ex,provider); // throws it
196                             }
197                             
198                             try {
199                                 // Check over the assertion.
200                                 SAMLAuthenticationStatement* authnStatement=checkAssertionProfile(assertion);
201
202                                 if (!checkReplay.first || checkReplay.second) {
203                                     auto_ptr_char id(assertion->getId());
204                                     string key(id.get());
205                                     key="P_" + key;
206                                     if (!conf->getReplayCache()->check(key.c_str(),assertion->getNotOnOrAfter()->getEpoch()))
207                                         throw ReplayedAssertionException(string("Rejecting replayed assertion ID (") + id.get() + ")");
208                                 }
209                                 
210                                 // Check signature.
211                                 log->debug("passing signed ADFS assertion to trust layer");
212                                 Trust t(app->getTrustProviders());
213                                 if (!t.validate(*assertion,role)) {
214                                     log->error("unable to verify signed authentication assertion");
215                                     throw TrustException("unable to verify signed authentication assertion");
216                                 }
217                                 
218                                 // Now dummy up the SAML profile response wrapper.
219                                 param=parser.get_value("wctx");
220                                 if (param)
221                                     bpr.TARGET=param;
222                                 bpr.profile=SAMLBrowserProfile::Post;   // not really, but...
223                                 bpr.response=response.release();
224                                 bpr.assertion=assertion;
225                                 bpr.authnStatement=authnStatement;
226                             }
227                             catch (SAMLException& ex) {
228                                 annotateException(&ex,role); // throws it
229                             }
230                         }
231                     }
232                 }
233                 if (rdoc) {
234                     rdoc->release();
235                     rdoc=NULL;
236                 }
237             }
238             catch(...) {
239                 if (rdoc) rdoc->release();
240                 throw;
241             }
242         }
243         if (bADFS && !bpr.response)
244             throw FatalProfileException("ADFS profile was indicated, but processing was unsuccesful");
245     }
246     
247     // If ADFS wasn't used, proceed to SAML processing up until we reach a common point.
248     int minorVersion = 1;
249     try {
250         if (!bADFS) {
251             int allowed = 0;
252             if (supported_profiles & SAML11_POST || supported_profiles & SAML10_POST)
253                 allowed |= SAMLBrowserProfile::Post;
254             if (supported_profiles & SAML11_ARTIFACT || supported_profiles & SAML10_ARTIFACT)
255                 allowed |= SAMLBrowserProfile::Artifact;
256             minorVersion=(supported_profiles & SAML11_ARTIFACT || supported_profiles & SAML11_POST) ? 1 : 0;
257     
258             auto_ptr<SAMLBrowserProfile::ArtifactMapper> artifactMapper(app->getArtifactMapper());
259       
260             // Try and run the profile.
261             log->debug("executing browser profile...");
262             bpr=app->getBrowserProfile()->receive(
263                 packet,
264                 wrecipient.get(),
265                 allowed,
266                 (!checkReplay.first || checkReplay.second) ? conf->getReplayCache() : NULL,
267                 artifactMapper.get(),
268                 minorVersion
269                 );
270     
271             // Blow it away to clear any locks that might be held.
272             delete artifactMapper.release();
273     
274             // Try and map to metadata (again).
275             // Once the metadata layer is in the SAML core, the repetition should be fixed.
276             const IEntityDescriptor* provider=m.lookup(bpr.assertion->getIssuer());
277             if (!provider && bpr.authnStatement->getSubject()->getNameIdentifier() &&
278                     bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier())
279                 provider=m.lookup(bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier());
280             if (provider) {
281                 const IIDPSSODescriptor* IDP=provider->getIDPSSODescriptor(
282                     minorVersion==1 ? saml::XML::SAML11_PROTOCOL_ENUM : saml::XML::SAML10_PROTOCOL_ENUM
283                     );
284                 role=IDP;
285             }
286             
287             // This isn't likely, since the profile must have found a role.
288             if (!role) {
289                 MetadataException ex("Unable to locate role-specific metadata for identity provider.");
290                 annotateException(&ex,provider); // throws it
291             }
292         }
293         
294         // At this point, we link back up and do the same work for ADFS and SAML.
295         
296         // Maybe verify the origin address....
297         if (checkIPAddress) {
298             log->debug("verifying client address");
299             // Verify the client address exists
300             const XMLCh* wip = bpr.authnStatement->getSubjectIP();
301             if (wip && *wip) {
302                 // Verify the client address matches authentication
303                 auto_ptr_char this_ip(wip);
304                 if (strcmp(ip, this_ip.get())) {
305                     FatalProfileException ex(
306                         SESSION_E_ADDRESSMISMATCH,
307                        "Your client's current address ($1) differs from the one used when you authenticated "
308                         "to your identity provider. To correct this problem, you may need to bypass a proxy server. "
309                         "Please contact your local support staff or help desk for assistance.",
310                         params(1,ip)
311                         );
312                     annotateException(&ex,role); // throws it
313                 }
314             }
315         }
316       
317         // Verify condition(s) on authentication assertion.
318         // Attribute assertions get filtered later by the AAP.
319         Iterator<SAMLCondition*> conditions=bpr.assertion->getConditions();
320         while (conditions.hasNext()) {
321             SAMLCondition* cond=conditions.next();
322             const SAMLAudienceRestrictionCondition* ac=dynamic_cast<const SAMLAudienceRestrictionCondition*>(cond);
323             if (!ac) {
324                 ostringstream os;
325                 os << *cond;
326                 log->error("Unrecognized Condition in authentication assertion (%s), tossing it.",os.str().c_str());
327                 FatalProfileException ex("unable to create session due to unrecognized condition in authentication assertion.");
328                 annotateException(&ex,role); // throws it
329             }
330             else if (!ac->eval(app->getAudiences())) {
331                 ostringstream os;
332                 os << *ac;
333                 log->error("Unacceptable AudienceRestrictionCondition in authentication assertion (%s), tossing it.",os.str().c_str());
334                 FatalProfileException ex("unable to create session due to unacceptable AudienceRestrictionCondition in authentication assertion.");
335                 annotateException(&ex,role); // throws it
336             }
337         }
338     }
339     catch (SAMLException&) {
340         bpr.clear();
341         throw;
342     }
343     catch (...) {
344         log->error("caught unknown exception");
345         bpr.clear();
346 #ifdef _DEBUG
347         throw;
348 #else
349         SAMLException e("An unexpected error occurred while creating your session.");
350         annotateException(&e,role);
351 #endif
352     }
353
354     // It passes all our tests -- create a new session.
355     log->info("creating new session");
356
357     // Are attributes present?
358     bool attributesPushed=false;
359     Iterator<SAMLAssertion*> assertions=bpr.response->getAssertions();
360     while (!attributesPushed && assertions.hasNext()) {
361         Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
362         while (!attributesPushed && statements.hasNext()) {
363             if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
364                 attributesPushed=true;
365         }
366     }
367
368     auto_ptr_char oname(role->getEntityDescriptor()->getId());
369     auto_ptr_char hname(bpr.authnStatement->getSubject()->getNameIdentifier()->getName());
370
371     try {
372         // Create a new session key.
373         cookie = conf->getSessionCache()->generateKey();
374
375         // Insert into cache.
376         auto_ptr<SAMLAuthenticationStatement> as(static_cast<SAMLAuthenticationStatement*>(bpr.authnStatement->clone()));
377         conf->getSessionCache()->insert(
378             cookie.c_str(),
379             app,
380             ip,
381             (bADFS ? ADFS_SSO :
382                 ((bpr.profile==SAMLBrowserProfile::Post) ?
383                     (minorVersion==1 ? SAML11_POST : SAML10_POST) : (minorVersion==1 ? SAML11_ARTIFACT : SAML10_ARTIFACT))),
384             oname.get(),
385             as.get(),
386             (attributesPushed ? bpr.response : NULL),
387             role
388             );
389         as.release();   // owned by cache now
390     }
391     catch (SAMLException&) {
392         bpr.clear();
393         throw;
394     }
395     catch (...) {
396         log->error("caught unknown exception");
397         bpr.clear();
398 #ifdef _DEBUG
399         throw;
400 #else
401         SAMLException e("An unexpected error occurred while creating your session.");
402         annotateException(&e,role);
403 #endif
404     }
405
406     target = bpr.TARGET;
407     provider_id = oname.get();
408
409     // Maybe delete the response...
410     if (!attributesPushed)
411         bpr.clear();
412
413     log->debug("new session id: %s", cookie.c_str());
414   
415     // Transaction Logging
416     FixedContextCategory tranLog(SHIBTRAN_LOGCAT);
417     tranLog.infoStream() <<
418         "New session (ID: " <<
419             cookie <<
420         ") with (applicationId: " <<
421             app->getId() <<
422         ") for principal from (IdP: " <<
423             provider_id <<
424         ") at (ClientAddress: " <<
425             ip <<
426         ") with (NameIdentifier: " <<
427             hname.get() <<
428         ")";
429     //stc.releaseTransactionLog();
430 }
431
432 void ADFSListener::sessionGet(
433     const IApplication* app,
434     const char* cookie,
435     const char* ip,
436     ISessionCacheEntry** pentry
437     ) const
438 {
439     g_MemoryListener->sessionGet(app,cookie,ip,pentry);
440 }
441
442 void ADFSListener::sessionEnd(
443     const IApplication* application,
444     const char* cookie
445     ) const
446 {
447     g_MemoryListener->sessionEnd(application,cookie);
448 }
449
450 void ADFSListener::ping(int& i) const
451 {
452     g_MemoryListener->ping(i);
453 }