https://issues.shibboleth.net/jira/browse/SSPCPP-119
[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
223     // Are attributes present?
224     bool attributesPushed=false;
225     Iterator<SAMLAssertion*> assertions=bpr.response->getAssertions();
226     while (!attributesPushed && assertions.hasNext()) {
227         Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
228         while (!attributesPushed && statements.hasNext()) {
229             if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
230                 attributesPushed=true;
231         }
232     }
233
234     auto_ptr_char oname(role->getEntityDescriptor()->getId());
235     auto_ptr_char hname(bpr.authnStatement->getSubject()->getNameIdentifier()->getName());
236
237     try {
238         // Create a new session key.
239         cookie = conf->getSessionCache()->generateKey();
240
241         // Insert into cache.
242         auto_ptr<SAMLAuthenticationStatement> as(static_cast<SAMLAuthenticationStatement*>(bpr.authnStatement->clone()));
243         conf->getSessionCache()->insert(
244             cookie.c_str(),
245             app,
246             ip,
247             (bpr.profile==SAMLBrowserProfile::Post) ?
248                 (minorVersion==1 ? SAML11_POST : SAML10_POST) : (minorVersion==1 ? SAML11_ARTIFACT : SAML10_ARTIFACT),
249             oname.get(),
250             as.get(),
251             (attributesPushed ? bpr.response : NULL),
252             role
253             );
254         as.release();   // owned by cache now
255     }
256     catch (SAMLException&) {
257         bpr.clear();
258         throw;
259     }
260 #ifndef _DEBUG
261     catch (...) {
262         log->error("caught unknown exception");
263         bpr.clear();
264         SAMLException e("An unexpected error occurred while creating your session.");
265         annotateException(&e,role);
266     }
267 #endif
268
269     target = bpr.TARGET;
270     provider_id = oname.get();
271
272     // Maybe delete the response...
273     if (!attributesPushed)
274         bpr.clear();
275
276     log->debug("new session id: %s", cookie.c_str());
277   
278     // Transaction Logging
279     stc.getTransactionLog().infoStream() <<
280         "New session (ID: " <<
281             cookie <<
282         ") with (applicationId: " <<
283             app->getId() <<
284         ") for principal from (IdP: " <<
285             provider_id <<
286         ") at (ClientAddress: " <<
287             ip <<
288         ") with (NameIdentifier: " <<
289             hname.get() <<
290         ")";
291
292     stc.releaseTransactionLog();
293 }
294
295 void MemoryListener::sessionGet(
296     const IApplication* app,
297     const char* cookie,
298     const char* ip,
299     ISessionCacheEntry** pentry
300     ) const
301 {
302 #ifdef _DEBUG
303     saml::NDC ndc("sessionGet");
304 #endif
305
306     *pentry=NULL;
307     log->debug("checking for session: %s@%s", cookie, ip);
308
309     // See if the session exists...
310
311     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
312     IConfig* conf=stc.getINI();
313     log->debug("application: %s", app->getId());
314
315     bool consistentIPAddress=true;
316     int lifetime=0,timeout=0;
317     const IPropertySet* props=app->getPropertySet("Sessions");
318     if (props) {
319         pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
320         if (p.first)
321             lifetime = p.second;
322         p=props->getUnsignedInt("timeout");
323         if (p.first)
324             timeout = p.second;
325         pair<bool,bool> pcheck=props->getBool("consistentAddress");
326         if (pcheck.first)
327             consistentIPAddress = pcheck.second;
328     }
329     
330     *pentry = conf->getSessionCache()->find(cookie,app);
331
332     // If not, leave now..
333     if (!*pentry) {
334         log->debug("session not found");
335         throw InvalidSessionException("No session exists for key value ($session_id)",namedparams(1,"session_id",cookie));
336     }
337
338     // TEST the session...
339     try {
340         // Verify the address is the same
341         if (consistentIPAddress) {
342             log->debug("Checking address against %s", (*pentry)->getClientAddress());
343             if (strcmp(ip, (*pentry)->getClientAddress())) {
344                 log->debug("client address mismatch");
345                 InvalidSessionException ex(
346                     SESSION_E_ADDRESSMISMATCH,
347                     "Your IP address ($1) does not match the address recorded at the time the session was established.",
348                     params(1,ip)
349                     );
350                 Metadata m(app->getMetadataProviders());
351                 annotateException(&ex,m.lookup((*pentry)->getProviderId())); // throws it
352             }
353         }
354
355         // and that the session is still valid...
356         if (!(*pentry)->isValid(lifetime,timeout)) {
357             log->debug("session expired");
358             InvalidSessionException ex(SESSION_E_EXPIRED, "Your session has expired, and you must re-authenticate.");
359             Metadata m(app->getMetadataProviders());
360             annotateException(&ex,m.lookup((*pentry)->getProviderId())); // throws it
361         }
362     }
363     catch (SAMLException&) {
364         (*pentry)->unlock();
365         *pentry=NULL;
366         conf->getSessionCache()->remove(cookie);
367       
368         // Transaction Logging
369         stc.getTransactionLog().infoStream() <<
370             "Destroyed invalid session (ID: " <<
371                 cookie <<
372             ") with (applicationId: " <<
373                 app->getId() <<
374             "), request was from (ClientAddress: " <<
375                 ip <<
376             ")";
377         stc.releaseTransactionLog();
378         throw;
379     }
380     catch (...) {
381         log->error("caught unknown exception");
382 #ifndef _DEBUG
383         InvalidSessionException ex("An unexpected error occurred while validating your session, and you must re-authenticate.");
384         Metadata m(app->getMetadataProviders());
385         annotateException(&ex,m.lookup((*pentry)->getProviderId()),false);
386 #endif
387         (*pentry)->unlock();
388         *pentry=NULL;
389         conf->getSessionCache()->remove(cookie);
390
391         // Transaction Logging
392         stc.getTransactionLog().infoStream() <<
393             "Destroyed invalid session (ID: " <<
394                 cookie <<
395             ") with (applicationId: " <<
396                 app->getId() <<
397             "), request was from (ClientAddress: " <<
398                 ip <<
399             ")";
400         stc.releaseTransactionLog();
401
402 #ifdef _DEBUG
403         throw;
404 #else
405         ex.raise();
406 #endif
407     }
408
409     log->debug("session ok");
410 }
411
412 void MemoryListener::sessionEnd(
413     const IApplication* application,
414     const char* cookie
415     ) const
416 {
417 #ifdef _DEBUG
418     saml::NDC ndc("sessionEnd");
419 #endif
420
421     log->debug("removing session: %s", cookie);
422
423     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
424     stc.getINI()->getSessionCache()->remove(cookie);
425   
426     // Transaction Logging
427     stc.getTransactionLog().infoStream() << "Destroyed session (ID: " << cookie << ")";
428     stc.releaseTransactionLog();
429 }
430
431 void MemoryListener::ping(int& i) const
432 {
433     i++;
434 }