Reduce catch all handlers, and make them optional.
[shibboleth/cpp-sp.git] / shib-target / MemoryListener.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  * MemoryListener.cpp -- An actual implementation of the IListener functional methods
19  *
20  * Scott Cantor
21  * 5/1/05
22  *
23  */
24
25 #include "internal.h"
26
27 using namespace std;
28 using namespace saml;
29 using namespace shibboleth;
30 using namespace shibtarget;
31 using namespace shibtarget::logging;
32
33 namespace {
34     class MemoryListener : public virtual IListener
35     {
36     public:
37         MemoryListener(const DOMElement* e) : log(&Category::getInstance(SHIBT_LOGCAT".Listener")) {}
38         ~MemoryListener() {}
39
40         bool create(ShibSocket& s) const {return true;}
41         bool bind(ShibSocket& s, bool force=false) const {return true;}
42         bool connect(ShibSocket& s) const {return true;}
43         bool close(ShibSocket& s) const {return true;}
44         bool accept(ShibSocket& listener, ShibSocket& s) const {return true;}
45
46         void sessionNew(
47             const IApplication* application,
48             int supported_profiles,
49             const char* recipient,
50             const char* packet,
51             const char* ip,
52             std::string& target,
53             std::string& cookie,
54             std::string& provider_id
55             ) const;
56     
57         void sessionGet(
58             const IApplication* application,
59             const char* cookie,
60             const char* ip,
61             ISessionCacheEntry** pentry
62             ) const;
63     
64         void sessionEnd(
65             const IApplication* application,
66             const char* cookie
67         ) const;
68         
69         void ping(int& i) const;
70
71     private:
72         Category* log;
73     };
74 }
75
76 IPlugIn* MemoryListenerFactory(const DOMElement* e)
77 {
78     return new MemoryListener(e);
79 }
80
81 void MemoryListener::sessionNew(
82     const IApplication* app,
83     int supported_profiles,
84     const char* recipient,
85     const char* packet,
86     const char* ip,
87     string& target,
88     string& cookie,
89     string& provider_id
90     ) const
91 {
92 #ifdef _DEBUG
93     saml::NDC ndc("sessionNew");
94 #endif
95
96     log->debug("creating session for %s", ip);
97     log->debug("recipient: %s", recipient);
98     log->debug("application: %s", app->getId());
99
100     auto_ptr_XMLCh wrecipient(recipient);
101
102     // Access the application config. It's already locked behind us.
103     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
104     IConfig* conf=stc.getINI();
105
106     bool checkIPAddress=true;
107     const IPropertySet* props=app->getPropertySet("Sessions");
108     if (props) {
109         pair<bool,bool> pcheck=props->getBool("checkAddress");
110         if (pcheck.first)
111             checkIPAddress = pcheck.second;
112     }
113
114     pair<bool,bool> checkReplay=pair<bool,bool>(false,false);
115     props=app->getPropertySet("Sessions");
116     if (props)
117         checkReplay=props->getBool("checkReplay");
118  
119     const IRoleDescriptor* role=NULL;
120     Metadata m(app->getMetadataProviders());
121     SAMLBrowserProfile::BrowserProfileResponse bpr;
122     int allowed = 0;
123     if (supported_profiles & SAML11_POST || supported_profiles & SAML10_POST)
124         allowed |= SAMLBrowserProfile::Post;
125     if (supported_profiles & SAML11_ARTIFACT || supported_profiles & SAML10_ARTIFACT)
126         allowed |= SAMLBrowserProfile::Artifact;
127     int minorVersion=(supported_profiles & SAML11_ARTIFACT || supported_profiles & SAML11_POST) ? 1 : 0;
128     
129     try {
130         auto_ptr<SAMLBrowserProfile::ArtifactMapper> artifactMapper(app->getArtifactMapper());
131       
132         // Try and run the profile.
133         log->debug("executing browser profile...");
134         bpr=app->getBrowserProfile()->receive(
135             packet,
136             wrecipient.get(),
137             allowed,
138             (!checkReplay.first || checkReplay.second) ? conf->getReplayCache() : NULL,
139             artifactMapper.get(),
140             minorVersion
141             );
142
143         // Blow it away to clear any locks that might be held.
144         delete artifactMapper.release();
145
146         // Try and map to metadata (again).
147         // Once the metadata layer is in the SAML core, the repetition should be fixed.
148         const IEntityDescriptor* provider=m.lookup(bpr.assertion->getIssuer());
149         if (!provider && bpr.authnStatement->getSubject()->getNameIdentifier() &&
150                 bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier())
151             provider=m.lookup(bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier());
152         if (provider) {
153             const IIDPSSODescriptor* IDP=provider->getIDPSSODescriptor(
154                 minorVersion==1 ? saml::XML::SAML11_PROTOCOL_ENUM : saml::XML::SAML10_PROTOCOL_ENUM
155                 );
156             role=IDP;
157         }
158         
159         // This isn't likely, since the profile must have found a role.
160         if (!role) {
161             MetadataException ex("Unable to locate role-specific metadata for identity provider.");
162             annotateException(&ex,provider); // throws it
163         }
164     
165         // Maybe verify the origin address....
166         if (checkIPAddress) {
167             log->debug("verifying client address");
168             // Verify the client address exists
169             const XMLCh* wip = bpr.authnStatement->getSubjectIP();
170             if (wip && *wip) {
171                 // Verify the client address matches authentication
172                 auto_ptr_char this_ip(wip);
173                 if (strcmp(ip, this_ip.get())) {
174                     FatalProfileException ex(
175                         SESSION_E_ADDRESSMISMATCH,
176                        "Your client's current address ($1) differs from the one used when you authenticated "
177                         "to your identity provider. To correct this problem, you may need to bypass a proxy server. "
178                         "Please contact your local support staff or help desk for assistance.",
179                         params(1,ip)
180                         );
181                     annotateException(&ex,role); // throws it
182                 }
183             }
184         }
185       
186         // Verify condition(s) on authentication assertion.
187         // Attribute assertions get filtered later, essentially just like an AAP.
188         Iterator<SAMLCondition*> conditions=bpr.assertion->getConditions();
189         while (conditions.hasNext()) {
190             SAMLCondition* cond=conditions.next();
191             const SAMLAudienceRestrictionCondition* ac=dynamic_cast<const SAMLAudienceRestrictionCondition*>(cond);
192             if (!ac) {
193                 ostringstream os;
194                 os << *cond;
195                 log->error("Unrecognized Condition in authentication assertion (%s), tossing it.",os.str().c_str());
196                 FatalProfileException ex("Unable to create session due to unrecognized condition in authentication assertion.");
197                 annotateException(&ex,role); // throws it
198             }
199             else if (!ac->eval(app->getAudiences())) {
200                 ostringstream os;
201                 os << *ac;
202                 log->error("Unacceptable AudienceRestrictionCondition in authentication assertion (%s), tossing it.",os.str().c_str());
203                 FatalProfileException ex("Unable to create session due to unacceptable AudienceRestrictionCondition in authentication assertion.");
204                 annotateException(&ex,role); // throws it
205             }
206         }
207     }
208     catch (SAMLException&) {
209         bpr.clear();
210         throw;
211     }
212 #ifndef _DEBUG
213     catch (...) {
214         log->error("caught unknown exception");
215         bpr.clear();
216         SAMLException e("An unexpected error occurred while creating your session.");
217         annotateException(&e,role);
218     }
219 #endif
220
221     // It passes all our tests -- create a new session.
222     log->info("creating new session");
223
224     // Are attributes present?
225     bool attributesPushed=false;
226     Iterator<SAMLAssertion*> assertions=bpr.response->getAssertions();
227     while (!attributesPushed && assertions.hasNext()) {
228         Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
229         while (!attributesPushed && statements.hasNext()) {
230             if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
231                 attributesPushed=true;
232         }
233     }
234
235     auto_ptr_char oname(role->getEntityDescriptor()->getId());
236     auto_ptr_char hname(bpr.authnStatement->getSubject()->getNameIdentifier()->getName());
237
238     try {
239         // Create a new session key.
240         cookie = conf->getSessionCache()->generateKey();
241
242         // Insert into cache.
243         auto_ptr<SAMLAuthenticationStatement> as(static_cast<SAMLAuthenticationStatement*>(bpr.authnStatement->clone()));
244         conf->getSessionCache()->insert(
245             cookie.c_str(),
246             app,
247             ip,
248             (bpr.profile==SAMLBrowserProfile::Post) ?
249                 (minorVersion==1 ? SAML11_POST : SAML10_POST) : (minorVersion==1 ? SAML11_ARTIFACT : SAML10_ARTIFACT),
250             oname.get(),
251             as.get(),
252             (attributesPushed ? bpr.response : NULL),
253             role
254             );
255         as.release();   // owned by cache now
256     }
257     catch (SAMLException&) {
258         bpr.clear();
259         throw;
260     }
261 #ifndef _DEBUG
262     catch (...) {
263         log->error("caught unknown exception");
264         bpr.clear();
265         SAMLException e("An unexpected error occurred while creating your session.");
266         annotateException(&e,role);
267     }
268 #endif
269
270     target = bpr.TARGET;
271     provider_id = oname.get();
272
273     // Maybe delete the response...
274     if (!attributesPushed)
275         bpr.clear();
276
277     log->debug("new session id: %s", cookie.c_str());
278   
279     // Transaction Logging
280     stc.getTransactionLog().infoStream() <<
281         "New session (ID: " <<
282             cookie <<
283         ") with (applicationId: " <<
284             app->getId() <<
285         ") for principal from (IdP: " <<
286             provider_id <<
287         ") at (ClientAddress: " <<
288             ip <<
289         ") with (NameIdentifier: " <<
290             hname.get() <<
291         ")";
292
293     stc.releaseTransactionLog();
294 }
295
296 void MemoryListener::sessionGet(
297     const IApplication* app,
298     const char* cookie,
299     const char* ip,
300     ISessionCacheEntry** pentry
301     ) const
302 {
303 #ifdef _DEBUG
304     saml::NDC ndc("sessionGet");
305 #endif
306
307     *pentry=NULL;
308     log->debug("checking for session: %s@%s", cookie, ip);
309
310     // See if the session exists...
311
312     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
313     IConfig* conf=stc.getINI();
314     log->debug("application: %s", app->getId());
315
316     bool consistentIPAddress=true;
317     int lifetime=0,timeout=0;
318     const IPropertySet* props=app->getPropertySet("Sessions");
319     if (props) {
320         pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
321         if (p.first)
322             lifetime = p.second;
323         p=props->getUnsignedInt("timeout");
324         if (p.first)
325             timeout = p.second;
326         pair<bool,bool> pcheck=props->getBool("consistentAddress");
327         if (pcheck.first)
328             consistentIPAddress = pcheck.second;
329     }
330     
331     *pentry = conf->getSessionCache()->find(cookie,app);
332
333     // If not, leave now..
334     if (!*pentry) {
335         log->debug("session not found");
336         throw InvalidSessionException("No session exists for key value ($session_id)",namedparams(1,"session_id",cookie));
337     }
338
339     // TEST the session...
340     try {
341         // Verify the address is the same
342         if (consistentIPAddress) {
343             log->debug("Checking address against %s", (*pentry)->getClientAddress());
344             if (strcmp(ip, (*pentry)->getClientAddress())) {
345                 log->debug("client address mismatch");
346                 InvalidSessionException ex(
347                     SESSION_E_ADDRESSMISMATCH,
348                     "Your IP address ($1) does not match the address recorded at the time the session was established.",
349                     params(1,ip)
350                     );
351                 Metadata m(app->getMetadataProviders());
352                 annotateException(&ex,m.lookup((*pentry)->getProviderId())); // throws it
353             }
354         }
355
356         // and that the session is still valid...
357         if (!(*pentry)->isValid(lifetime,timeout)) {
358             log->debug("session expired");
359             InvalidSessionException ex(SESSION_E_EXPIRED, "Your session has expired, and you must re-authenticate.");
360             Metadata m(app->getMetadataProviders());
361             annotateException(&ex,m.lookup((*pentry)->getProviderId())); // throws it
362         }
363     }
364     catch (SAMLException&) {
365         (*pentry)->unlock();
366         *pentry=NULL;
367         conf->getSessionCache()->remove(cookie);
368       
369         // Transaction Logging
370         stc.getTransactionLog().infoStream() <<
371             "Destroyed invalid session (ID: " <<
372                 cookie <<
373             ") with (applicationId: " <<
374                 app->getId() <<
375             "), request was from (ClientAddress: " <<
376                 ip <<
377             ")";
378         stc.releaseTransactionLog();
379         throw;
380     }
381     catch (...) {
382         log->error("caught unknown exception");
383 #ifndef _DEBUG
384         InvalidSessionException ex("An unexpected error occurred while validating your session, and you must re-authenticate.");
385         Metadata m(app->getMetadataProviders());
386         annotateException(&ex,m.lookup((*pentry)->getProviderId()),false);
387 #endif
388         (*pentry)->unlock();
389         *pentry=NULL;
390         conf->getSessionCache()->remove(cookie);
391
392         // Transaction Logging
393         stc.getTransactionLog().infoStream() <<
394             "Destroyed invalid session (ID: " <<
395                 cookie <<
396             ") with (applicationId: " <<
397                 app->getId() <<
398             "), request was from (ClientAddress: " <<
399                 ip <<
400             ")";
401         stc.releaseTransactionLog();
402
403 #ifdef _DEBUG
404         throw;
405 #else
406         ex.raise();
407 #endif
408     }
409
410     log->debug("session ok");
411 }
412
413 void MemoryListener::sessionEnd(
414     const IApplication* application,
415     const char* cookie
416     ) const
417 {
418 #ifdef _DEBUG
419     saml::NDC ndc("sessionEnd");
420 #endif
421
422     log->debug("removing session: %s", cookie);
423
424     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
425     stc.getINI()->getSessionCache()->remove(cookie);
426   
427     // Transaction Logging
428     stc.getTransactionLog().infoStream() << "Destroyed session (ID: " << cookie << ")";
429     stc.releaseTransactionLog();
430 }
431
432 void MemoryListener::ping(int& i) const
433 {
434     i++;
435 }