From: cantor Date: Tue, 17 Jul 2007 22:22:45 +0000 (+0000) Subject: First cut at logout race detection in cache. X-Git-Tag: 2.4~839 X-Git-Url: http://www.project-moonshot.org/gitweb/?a=commitdiff_plain;h=dcd7501fe51a7eb419e05898c42c146188c4d838;hp=ccaf4502e44d16c590e0990e4404bb7ea5593edb;p=shibboleth%2Fsp.git First cut at logout race detection in cache. Back-channel logout notifier using SOAP. git-svn-id: https://svn.middleware.georgetown.edu/cpp-sp/trunk@2362 cb58f699-b61c-0410-a6fe-9272a202ed29 --- diff --git a/schemas/Makefile.am b/schemas/Makefile.am index 7a52a98..3fd6989 100644 --- a/schemas/Makefile.am +++ b/schemas/Makefile.am @@ -12,6 +12,7 @@ schemafiles = \ shibboleth-2.0-afp-mf-basic.xsd \ shibboleth-2.0-afp-mf-saml.xsd \ shibboleth-2.0-attribute-map.xsd \ + shibboleth-2.0-sp-notify.xsd \ metadata_v12_to_v13.xsl \ metadata_v13_to_v12.xsl \ trust_v13_to_v12.xsl diff --git a/schemas/catalog.xml.in b/schemas/catalog.xml.in index e633dd3..115473e 100644 --- a/schemas/catalog.xml.in +++ b/schemas/catalog.xml.in @@ -6,5 +6,6 @@ + diff --git a/schemas/shibboleth-2.0-sp-notify.xsd b/schemas/shibboleth-2.0-sp-notify.xsd new file mode 100644 index 0000000..8160ed6 --- /dev/null +++ b/schemas/shibboleth-2.0-sp-notify.xsd @@ -0,0 +1,42 @@ + + + + + + Defines local application notification protocol used by SP. + + + + + + + + + + + + + + + + + + Notifies application of a logout event. + + + + + + + + + + + + + + + diff --git a/shibsp/Application.h b/shibsp/Application.h index ae6e76f..118467f 100644 --- a/shibsp/Application.h +++ b/shibsp/Application.h @@ -156,12 +156,12 @@ namespace shibsp { /** * Returns the designated notification URL, or an empty string if no more locations are specified. * - * @param request SP request to use to fill in missing pieces of URL + * @param request requested URL to use to fill in missing pieces of notification URL * @param front true iff front channel notification is desired, false iff back channel is desired * @param index zero-based index of URL to return * @return the designated URL, or an empty string */ - virtual std::string getNotificationURL(const xmltooling::HTTPRequest& request, bool front, unsigned int index) const=0; + virtual std::string getNotificationURL(const char* request, bool front, unsigned int index) const=0; /** * Returns a set of attribute IDs to use as a REMOTE_USER value. diff --git a/shibsp/handler/LogoutHandler.h b/shibsp/handler/LogoutHandler.h index 8bcf05e..8c79666 100644 --- a/shibsp/handler/LogoutHandler.h +++ b/shibsp/handler/LogoutHandler.h @@ -109,10 +109,14 @@ namespace shibsp { * Perform back-channel logout notifications for an Application. * * @param application the Application to notify + * @param requestURL requestURL that resulted in method call * @param sessions array of session keys being logged out + * @param local true iff the logout operation is local to the SP, false iff global * @return true iff all notifications succeeded */ - bool notifyBackChannel(const Application& application, const std::vector& sessions) const; + bool notifyBackChannel( + const Application& application, const char* requestURL, const std::vector& sessions, bool local + ) const; }; #if defined (_MSC_VER) diff --git a/shibsp/handler/impl/LocalLogoutInitiator.cpp b/shibsp/handler/impl/LocalLogoutInitiator.cpp index 687dd32..aa51435 100644 --- a/shibsp/handler/impl/LocalLogoutInitiator.cpp +++ b/shibsp/handler/impl/LocalLogoutInitiator.cpp @@ -99,7 +99,7 @@ pair LocalLogoutInitiator::run(SPRequest& request, bool isHandler) co if (session_id) { // Do back channel notification. vector sessions(1, session_id); - if (!notifyBackChannel(request.getApplication(), sessions)) { + if (!notifyBackChannel(request.getApplication(), request.getRequestURL(), sessions, true)) { request.getApplication().getServiceProvider().getSessionCache()->remove(session_id, request.getApplication()); return sendLogoutPage(request.getApplication(), request, true, "Partial logout failure."); } diff --git a/shibsp/handler/impl/LogoutHandler.cpp b/shibsp/handler/impl/LogoutHandler.cpp index e363c05..901e45b 100644 --- a/shibsp/handler/impl/LogoutHandler.cpp +++ b/shibsp/handler/impl/LogoutHandler.cpp @@ -33,13 +33,34 @@ #include #include -#ifndef SHIBSP_LITE -#endif - using namespace shibsp; using namespace xmltooling; using namespace std; +pair LogoutHandler::sendLogoutPage(const Application& application, HTTPResponse& response, bool local, const char* status) const +{ + pair prop = application.getString(local ? "localLogout" : "globalLogout"); + if (prop.first) { + response.setContentType("text/html"); + response.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT"); + response.setResponseHeader("Cache-Control","private,no-store,no-cache"); + ifstream infile(prop.second); + if (!infile) + throw ConfigurationException("Unable to access $1 HTML template.", params(1,local ? "localLogout" : "globalLogout")); + TemplateParameters tp; + tp.setPropertySet(application.getPropertySet("Errors")); + if (status) + tp.m_map["logoutStatus"] = status; + stringstream str; + XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp); + return make_pair(true,response.sendResponse(str)); + } + prop = application.getString("homeURL"); + if (!prop.first) + prop.second = "/"; + return make_pair(true,response.sendRedirect(prop.second)); +} + pair LogoutHandler::run(SPRequest& request, bool isHandler) const { // If we're inside a chain, so do nothing. @@ -80,7 +101,7 @@ void LogoutHandler::receive(DDF& in, ostream& out) while (temp.isstring()) { sessions.push_back(temp.string()); temp = s.next(); - if (notifyBackChannel(*app, sessions)) + if (notifyBackChannel(*app, in["url"].string(), sessions, in["local"].integer()==1)) ret.integer(1); } @@ -101,7 +122,7 @@ pair LogoutHandler::notifyFrontChannel( index = atoi(param); // Fetch the next front notification URL and bump the index for the next round trip. - string loc = application.getNotificationURL(request, true, index++); + string loc = application.getNotificationURL(request.getRequestURL(), true, index++); if (loc.empty()) return make_pair(false,0); @@ -137,11 +158,71 @@ pair LogoutHandler::notifyFrontChannel( return make_pair(true,response.sendRedirect(loc.c_str())); } -bool LogoutHandler::notifyBackChannel(const Application& application, const vector& sessions) const +#ifndef SHIBSP_LITE +#include "util/SPConstants.h" +#include +#include +#include +using namespace soap11; +namespace { + static const XMLCh LogoutNotification[] = UNICODE_LITERAL_18(L,o,g,o,u,t,N,o,t,i,f,i,c,a,t,i,o,n); + static const XMLCh SessionID[] = UNICODE_LITERAL_9(S,e,s,s,i,o,n,I,D); + static const XMLCh _type[] = UNICODE_LITERAL_4(t,y,p,e); + static const XMLCh _local[] = UNICODE_LITERAL_5(l,o,c,a,l); + static const XMLCh _global[] = UNICODE_LITERAL_6(g,l,o,b,a,l); + + class SHIBSP_DLLLOCAL SOAPNotifier : public soap11::SOAPClient + { + public: + SOAPNotifier() {} + virtual ~SOAPNotifier() {} + private: + void prepareTransport(SOAPTransport& transport) { + transport.setVerifyHost(false); + } + }; +}; +#endif + +bool LogoutHandler::notifyBackChannel( + const Application& application, const char* requestURL, const vector& sessions, bool local + ) const { + unsigned int index = 0; + string endpoint = application.getNotificationURL(requestURL, false, index++); + if (endpoint.empty()) + return true; + if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) { #ifndef SHIBSP_LITE - return true; + auto_ptr env(EnvelopeBuilder::buildEnvelope()); + Body* body = BodyBuilder::buildBody(); + env->setBody(body); + ElementProxy* msg = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, LogoutNotification); + body->getUnknownXMLObjects().push_back(msg); + msg->setAttribute(QName(NULL, _type), local ? _local : _global); + for (vector::const_iterator s = sessions.begin(); s!=sessions.end(); ++s) { + auto_ptr_XMLCh temp(s->c_str()); + ElementProxy* child = new AnyElementImpl(shibspconstants::SHIB2SPNOTIFY_NS, SessionID); + child->setTextContent(temp.get()); + msg->getUnknownXMLObjects().push_back(child); + } + + bool result = true; + SOAPNotifier soaper; + while (!endpoint.empty()) { + try { + soaper.send(*env.get(), application.getId(), endpoint.c_str()); + delete soaper.receive(); + } + catch (exception& ex) { + log4cpp::Category::getInstance(SHIBSP_LOGCAT".Logout").error("error notifying application of logout event: %s", ex.what()); + result = false; + } + soaper.reset(); + endpoint = application.getNotificationURL(requestURL, false, index++); + } + return result; #else return false; #endif @@ -152,6 +233,9 @@ bool LogoutHandler::notifyBackChannel(const Application& application, const vect DDFJanitor jin(in), jout(out); in.addmember("notify").integer(1); in.addmember("application_id").string(application.getId()); + in.addmember("url").string(requestURL); + if (local) + in.addmember("local").integer(1); DDF s = in.addmember("sessions").list(); for (vector::const_iterator i = sessions.begin(); i!=sessions.end(); ++i) { DDF temp = DDF(NULL).string(i->c_str()); @@ -160,27 +244,3 @@ bool LogoutHandler::notifyBackChannel(const Application& application, const vect out=application.getServiceProvider().getListenerService()->send(in); return (out.integer() == 1); } - -pair LogoutHandler::sendLogoutPage(const Application& application, HTTPResponse& response, bool local, const char* status) const -{ - pair prop = application.getString(local ? "localLogout" : "globalLogout"); - if (prop.first) { - response.setContentType("text/html"); - response.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT"); - response.setResponseHeader("Cache-Control","private,no-store,no-cache"); - ifstream infile(prop.second); - if (!infile) - throw ConfigurationException("Unable to access $1 HTML template.", params(1,local ? "localLogout" : "globalLogout")); - TemplateParameters tp; - tp.setPropertySet(application.getPropertySet("Errors")); - if (status) - tp.m_map["logoutStatus"] = status; - stringstream str; - XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp); - return make_pair(true,response.sendResponse(str)); - } - prop = application.getString("homeURL"); - if (!prop.first) - prop.second = "/"; - return make_pair(true,response.sendRedirect(prop.second)); -} diff --git a/shibsp/handler/impl/SAML2Logout.cpp b/shibsp/handler/impl/SAML2Logout.cpp index 906b9b8..6bd06cc 100644 --- a/shibsp/handler/impl/SAML2Logout.cpp +++ b/shibsp/handler/impl/SAML2Logout.cpp @@ -252,7 +252,7 @@ pair SAML2Logout::doRequest( bool worked1 = false,worked2 = false; if (session_id) { vector sessions(1,session_id); - worked1 = notifyBackChannel(application, sessions); + worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false); try { cache->remove(session_id, application); worked2 = true; @@ -452,7 +452,7 @@ pair SAML2Logout::doRequest( // For back-channel requests, or if no front-channel notification is needed... bool worked1 = false,worked2 = false; - worked1 = notifyBackChannel(application, sessions); + worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false); if (session_id) { // One last session to yoink... try { diff --git a/shibsp/handler/impl/SAML2LogoutInitiator.cpp b/shibsp/handler/impl/SAML2LogoutInitiator.cpp index c8b410b..dac3946 100644 --- a/shibsp/handler/impl/SAML2LogoutInitiator.cpp +++ b/shibsp/handler/impl/SAML2LogoutInitiator.cpp @@ -73,7 +73,7 @@ namespace shibsp { pair run(SPRequest& request, bool isHandler=true) const; private: - pair doRequest(const Application& application, Session* session_id, HTTPResponse& httpResponse) const; + pair doRequest(const Application& application, const char* requestURL, Session* session_id, HTTPResponse& httpResponse) const; string m_appId; #ifndef SHIBSP_LITE @@ -194,7 +194,7 @@ pair SAML2LogoutInitiator::run(SPRequest& request, bool isHandler) co if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) { // When out of process, we run natively. - return doRequest(request.getApplication(), session, request); + return doRequest(request.getApplication(), request.getRequestURL(), session, request); } else { // When not out of process, we remote the request. @@ -203,6 +203,7 @@ pair SAML2LogoutInitiator::run(SPRequest& request, bool isHandler) co DDFJanitor jin(in), jout(out); in.addmember("application_id").string(request.getApplication().getId()); in.addmember("session_id").string(session->getID()); + in.addmember("url").string(request.getRequestURL()); out=request.getServiceProvider().getListenerService()->send(in); return unwrap(request, out); } @@ -243,7 +244,7 @@ void SAML2LogoutInitiator::receive(DDF& in, ostream& out) // Since we're remoted, the result should either be a throw, which we pass on, // a false/0 return, which we just return as an empty structure, or a response/redirect, // which we capture in the facade and send back. - doRequest(*app, session, *resp.get()); + doRequest(*app, in["url"].string(), session, *resp.get()); } else { m_log.error("no NameID or issuing entityID found in session"); @@ -258,11 +259,13 @@ void SAML2LogoutInitiator::receive(DDF& in, ostream& out) #endif } -pair SAML2LogoutInitiator::doRequest(const Application& application, Session* session, HTTPResponse& response) const +pair SAML2LogoutInitiator::doRequest( + const Application& application, const char* requestURL, Session* session, HTTPResponse& response + ) const { // Do back channel notification. vector sessions(1, session->getID()); - if (!notifyBackChannel(application, sessions)) { + if (!notifyBackChannel(application, requestURL, sessions, false)) { session->unlock(); application.getServiceProvider().getSessionCache()->remove(sessions.front().c_str(), application); return sendLogoutPage(application, response, true, "Partial logout failure."); diff --git a/shibsp/impl/StorageServiceSessionCache.cpp b/shibsp/impl/StorageServiceSessionCache.cpp index b64f7d3..c41f599 100644 --- a/shibsp/impl/StorageServiceSessionCache.cpp +++ b/shibsp/impl/StorageServiceSessionCache.cpp @@ -487,7 +487,7 @@ void SSCache::insert(const char* key, time_t expires, const char* name, const ch // Since we can't guarantee uniqueness, check for an existing record. string record; time_t recordexp; - int ver = m_storage->readText("Logout", name, &record, &recordexp); + int ver = m_storage->readText("NameID", name, &record, &recordexp); if (ver > 0) { // Existing record, so we need to unmarshall it. istringstream in(record); @@ -512,13 +512,13 @@ void SSCache::insert(const char* key, time_t expires, const char* name, const ch // Try and store it back... if (ver > 0) { - ver = m_storage->updateText("Logout", name, out.str().c_str(), max(expires, recordexp), ver); + ver = m_storage->updateText("NameID", name, out.str().c_str(), max(expires, recordexp), ver); if (ver <= 0) { // Out of sync, or went missing, so retry. return insert(key, expires, name, index); } } - else if (!m_storage->createText("Logout", name, out.str().c_str(), expires)) { + else if (!m_storage->createText("NameID", name, out.str().c_str(), expires)) { // Hit a dup, so just retry, hopefully hitting the other branch. return insert(key, expires, name, index); } @@ -545,6 +545,38 @@ string SSCache::insert( m_log.debug("creating new session"); + time_t now = time(NULL); + auto_ptr_char index(session_index); + auto_ptr_char entity_id(issuer ? issuer->getEntityID() : NULL); + auto_ptr_char name(nameid ? nameid->getName() : NULL); + + if (nameid) { + // Check for a pending logout. + if (strlen(name.get()) > 255) + const_cast(name.get())[255] = 0; + string pending; + int ver = m_storage->readText("Logout", name.get(), &pending); + if (ver > 0) { + DDF pendobj; + DDFJanitor jpend(pendobj); + istringstream pstr(pending); + pstr >> pendobj; + // IdP.SP.index contains logout expiration, if any. + DDF deadmenwalking = pendobj[issuer ? entity_id.get() : "_shibnull"][application.getString("entityID").second]; + const char* logexpstr = deadmenwalking[session_index ? index.get() : "_shibnull"].string(); + if (!logexpstr && session_index) // we tried an exact session match, now try for NULL + logexpstr = deadmenwalking["_shibnull"].string(); + if (logexpstr) { + auto_ptr_XMLCh dt(logexpstr); + DateTime dtobj(dt.get()); + dtobj.parseDateTime(); + time_t logexp = dtobj.getEpoch(); + if (now - XMLToolingConfig::getConfig().clock_skew_secs < logexp) + throw FatalProfileException("A logout message from your identity provider has blocked your login attempt."); + } + } + } + auto_ptr_char key(SAMLConfig::getConfig().generateIdentifier()); // Store session properties in DDF. @@ -552,7 +584,7 @@ string SSCache::insert( obj.addmember("version").integer(1); obj.addmember("application_id").string(application.getId()); - // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps. + // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps. #ifndef HAVE_GMTIME_R struct tm* ptime=gmtime(&expires); #else @@ -565,10 +597,8 @@ string SSCache::insert( if (client_addr) obj.addmember("client_addr").string(client_addr); - if (issuer) { - auto_ptr_char entity_id(issuer->getEntityID()); + if (issuer) obj.addmember("entity_id").string(entity_id.get()); - } if (protocol) { auto_ptr_char prot(protocol); obj.addmember("protocol").string(prot.get()); @@ -577,7 +607,6 @@ string SSCache::insert( auto_ptr_char instant(authn_instant); obj.addmember("authn_instant").string(instant.get()); } - auto_ptr_char index(session_index); if (session_index) obj.addmember("session_index").string(index.get()); if (authncontext_class) { @@ -617,14 +646,12 @@ string SSCache::insert( record << obj; m_log.debug("storing new session..."); - time_t now = time(NULL); if (!m_storage->createText(key.get(), "session", record.str().c_str(), now + m_cacheTimeout)) throw FatalProfileException("Attempted to create a session with a duplicate key."); // Store the reverse mapping for logout. - auto_ptr_char name(nameid ? nameid->getName() : NULL); try { - if (name.get()) + if (nameid) insert(key.get(), expires, name.get(), index.get()); } catch (exception& ex) { @@ -662,7 +689,7 @@ string SSCache::insert( ") at (ClientAddress: " << (client_addr ? client_addr : "none") << ") with (NameIdentifier: " << - (name.get() ? name.get() : "none") << + (nameid ? name.get() : "none") << ")"; if (attributes) { @@ -840,16 +867,70 @@ vector::size_type SSCache::logout( if (strlen(name.get()) > 255) const_cast(name.get())[255] = 0; - // Read in potentially matching sessions. + DDF obj; + DDFJanitor jobj(obj); string record; - int ver = m_storage->readText("Logout", name.get(), &record); + int ver; + + if (expires) { + // Record the logout to prevent post-delivered assertions. + // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps. +#ifndef HAVE_GMTIME_R + struct tm* ptime=gmtime(&expires); +#else + struct tm res; + struct tm* ptime=gmtime_r(&expires,&res); +#endif + char timebuf[32]; + strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime); + + time_t oldexp = 0; + ver = m_storage->readText("Logout", name.get(), &record, &oldexp); + if (ver > 0) { + istringstream lin(record); + lin >> obj; + } + else { + obj = DDF(NULL).structure(); + } + + // Structure is keyed by the IdP and SP, with a member per session index containing the expiration. + DDF root = obj.addmember(issuer ? entityID.get() : "_shibnull").addmember(application.getString("entityID").second); + if (indexes) { + for (set::const_iterator x = indexes->begin(); x!=indexes->end(); ++x) + root.addmember(x->c_str()).string(timebuf); + } + else { + root.addmember("_shibnull").string(timebuf); + } + + // Write it back. + ostringstream lout; + lout << obj; + + if (ver > 0) { + ver = m_storage->updateText("Logout", name.get(), lout.str().c_str(), max(expires, oldexp), ver); + if (ver <= 0) { + // Out of sync, or went missing, so retry. + return logout(issuer, nameid, indexes, expires, application, sessionsKilled); + } + } + else if (!m_storage->createText("Logout", name.get(), lout.str().c_str(), expires)) { + // Hit a dup, so just retry, hopefully hitting the other branch. + return logout(issuer, nameid, indexes, expires, application, sessionsKilled); + } + + obj.destroy(); + record.erase(); + } + + // Read in potentially matching sessions. + ver = m_storage->readText("NameID", name.get(), &record); if (ver == 0) { m_log.debug("no active sessions to logout for supplied issuer and subject"); return 0; } - DDF obj; - DDFJanitor jobj(obj); istringstream in(record); in >> obj; @@ -907,12 +988,12 @@ vector::size_type SSCache::logout( // If possible, write back the mapping record (this isn't crucial). try { if (obj.isnull()) { - m_storage->deleteText("Logout", name.get()); + m_storage->deleteText("NameID", name.get()); } else if (!sessionsKilled.empty()) { ostringstream out; out << obj; - if (m_storage->updateText("Logout", name.get(), out.str().c_str(), 0, ver) <= 0) + if (m_storage->updateText("NameID", name.get(), out.str().c_str(), 0, ver) <= 0) m_log.warn("logout mapping record changed behind us, leaving it alone"); } } diff --git a/shibsp/impl/XMLServiceProvider.cpp b/shibsp/impl/XMLServiceProvider.cpp index 39c1df4..cdbbefc 100644 --- a/shibsp/impl/XMLServiceProvider.cpp +++ b/shibsp/impl/XMLServiceProvider.cpp @@ -129,7 +129,7 @@ namespace { return (m_audiences.empty() && m_base) ? m_base->getAudiences() : m_audiences; } #endif - string getNotificationURL(const HTTPRequest& request, bool front, unsigned int index) const; + string getNotificationURL(const char* resource, bool front, unsigned int index) const; const set& getRemoteUserAttributeIds() const { return (m_remoteUsers.empty() && m_base) ? m_base->getRemoteUserAttributeIds() : m_remoteUsers; @@ -949,15 +949,14 @@ const PropertySet* XMLApplication::getRelyingParty(const EntityDescriptor* provi #endif -string XMLApplication::getNotificationURL(const HTTPRequest& request, bool front, unsigned int index) const +string XMLApplication::getNotificationURL(const char* resource, bool front, unsigned int index) const { const vector& locs = front ? m_frontLogout : m_backLogout; if (locs.empty()) - return m_base ? m_base->getNotificationURL(request, front, index) : string(); + return m_base ? m_base->getNotificationURL(resource, front, index) : string(); else if (index >= locs.size()) return string(); - const char* resource = request.getRequestURL(); #ifdef HAVE_STRCASECMP if (!resource || (strncasecmp(resource,"http://",7) && strncasecmp(resource,"https://",8))) #else diff --git a/shibsp/util/SPConstants.cpp b/shibsp/util/SPConstants.cpp index 48d991c..78c140a 100644 --- a/shibsp/util/SPConstants.cpp +++ b/shibsp/util/SPConstants.cpp @@ -56,6 +56,13 @@ const XMLCh shibspconstants::SHIB2ATTRIBUTEMAP_NS[] = // urn:mace:shibboleth:2.0 chLatin_m, chLatin_a, chLatin_p, chNull }; +const XMLCh shibspconstants::SHIB2SPNOTIFY_NS[] = // urn:mace:shibboleth:2.0:sp:notify +{ chLatin_u, chLatin_r, chLatin_n, chColon, chLatin_m, chLatin_a, chLatin_c, chLatin_e, chColon, + chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chColon, + chDigit_2, chPeriod, chDigit_0, chColon, chLatin_s, chLatin_p, chColon, + chLatin_n, chLatin_o, chLatin_t, chLatin_i, chLatin_f, chLatin_y, chNull +}; + const XMLCh shibspconstants::SHIB2ATTRIBUTEFILTER_NS[] = // urn:mace:shibboleth:2.0:afp { chLatin_u, chLatin_r, chLatin_n, chColon, chLatin_m, chLatin_a, chLatin_c, chLatin_e, chColon, chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chColon, diff --git a/shibsp/util/SPConstants.h b/shibsp/util/SPConstants.h index 31c2097..92689a0 100644 --- a/shibsp/util/SPConstants.h +++ b/shibsp/util/SPConstants.h @@ -47,6 +47,9 @@ namespace shibspconstants { /** Shibboleth 2.0 attribute mapping namespace ("urn:mace:shibboleth:2.0:attribute-map") */ extern SHIBSP_API const XMLCh SHIB2ATTRIBUTEMAP_NS[]; + /** Shibboleth 2.0 notification namespace ("urn:mace:shibboleth:2.0:sp:notify") */ + extern SHIBSP_API const XMLCh SHIB2SPNOTIFY_NS[]; + /** Shibboleth 2.0 attribute filter policy namespace ("urn:mace:shibboleth:2.0:afp") */ extern SHIBSP_API const XMLCh SHIB2ATTRIBUTEFILTER_NS[];