Move advanced "back-channel" methods to an Ex subinterface.
}
application.getServiceProvider().getSessionCache()->insert(
- now + lifetime.second,
application,
httpRequest,
httpResponse,
+ now + lifetime.second,
policy.getIssuerMetadata() ? dynamic_cast<const EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : NULL,
m_protocol.get(),
nameid.get(),
}
// Best effort on back channel and to remove the user agent's session.
- string session_id = app.getServiceProvider().getSessionCache()->active(request, app);
+ string session_id = app.getServiceProvider().getSessionCache()->active(app, request);
if (!session_id.empty()) {
vector<string> sessions(1,session_id);
notifyBackChannel(app, request.getRequestURL(), sessions, false);
try {
- app.getServiceProvider().getSessionCache()->remove(request, &request, app);
+ app.getServiceProvider().getSessionCache()->remove(app, request, &request);
}
catch (exception& ex) {
m_log.error("error removing session (%s): %s", session_id.c_str(), ex.what());
// The cache will either silently pass a session or NULL back, or throw an exception out.
Session* session = getServiceProvider().getSessionCache()->find(
- *this, getApplication(), ignoreAddress ? NULL : getRemoteAddr().c_str(), checkTimeout ? &timeout : NULL
+ getApplication(), *this, ignoreAddress ? NULL : getRemoteAddr().c_str(), checkTimeout ? &timeout : NULL
);
if (cache)
m_session = session;
RequestMapper.h \
ServiceProvider.h \
SessionCache.h \
+ SessionCacheEx.h \
SPConfig.h \
SPRequest.h \
TransactionLog.h \
class SHIBSP_API Application;
class SHIBSP_API Attribute;
+ /**
+ * Encapsulates access to a user's security session.
+ *
+ * <p>The SessionCache does not itself require locking to manage
+ * concurrency, but access to each Session is generally exclusive
+ * or at least controlled, and the caller must unlock a Session
+ * to dispose of it.
+ */
class SHIBSP_API Session : public virtual xmltooling::Lockable
{
MAKE_NONCOPYABLE(Session);
*
* <p>The SSO tokens and Attributes remain owned by the caller and are copied by the cache.
*
- * @param expires expiration time of session
* @param application reference to Application that owns the Session
* @param httpRequest request that initiated session
* @param httpResponse current response to client
+ * @param expires expiration time of session
* @param issuer issuing metadata of assertion issuer, if known
* @param protocol protocol family used to initiate the session
* @param nameid principal identifier, normalized to SAML 2, if any
* @param attributes optional array of resolved Attributes to cache with session
*/
virtual void insert(
- time_t expires,
const Application& application,
const xmltooling::HTTPRequest& httpRequest,
xmltooling::HTTPResponse& httpResponse,
+ time_t expires,
const opensaml::saml2md::EntityDescriptor* issuer=NULL,
const XMLCh* protocol=NULL,
const opensaml::saml2::NameID* nameid=NULL,
)=0;
/**
- * Returns active sessions that match particular parameters and records the logout
- * to prevent race conditions.
- *
- * <p>On exit, the mapping between these sessions and the associated information MAY be
- * removed by the cache, so subsequent calls to this method may not return anything.
- *
- * <p>Until logout expiration, any attempt to create a session with the same parameters
- * will be blocked by the cache.
- *
- * @param issuer source of session(s)
- * @param nameid name identifier associated with the session(s) to terminate
- * @param indexes indexes of sessions, or NULL for all sessions associated with other parameters
- * @param expires logout expiration
- * @param application reference to Application that owns the session(s)
- * @param sessions on exit, contains the IDs of the matching sessions found
- */
- virtual std::vector<std::string>::size_type logout(
- const opensaml::saml2md::EntityDescriptor* issuer,
- const opensaml::saml2::NameID& nameid,
- const std::set<std::string>* indexes,
- time_t expires,
- const Application& application,
- std::vector<std::string>& sessions
- )=0;
-
- /**
* Determines whether the Session bound to a client request matches a set of input criteria.
*
+ * @param application reference to Application that owns the Session
* @param request request in which to locate Session
* @param issuer required source of session(s)
* @param nameid required name identifier
* @param indexes session indexes
- * @param application reference to Application that owns the Session
* @return true iff the Session exists and matches the input criteria
*/
virtual bool matches(
+ const Application& application,
const xmltooling::HTTPRequest& request,
const opensaml::saml2md::EntityDescriptor* issuer,
const opensaml::saml2::NameID& nameid,
- const std::set<std::string>* indexes,
- const Application& application
+ const std::set<std::string>* indexes
)=0;
/**
/**
* Returns the ID of the session bound to the specified client request, if possible.
*
- * @param request request from client containing session, or a reference to it
* @param application reference to Application that owns the Session
+ * @param request request from client containing session, or a reference to it
* @return ID of session, if any known, or an empty string
*/
- virtual std::string active(const xmltooling::HTTPRequest& request, const Application& application) const=0;
-
- /**
- * Locates an existing session by ID.
- *
- * <p>If the client address is supplied, then a check will be performed against
- * the address recorded in the record.
- *
- * @param key session key
- * @param application reference to Application that owns the Session
- * @param client_addr network address of client (if known)
- * @param timeout inactivity timeout to enforce (0 for none, NULL to bypass check/update of last access)
- * @return pointer to locked Session, or NULL
- */
- virtual Session* find(
- const char* key, const Application& application, const char* client_addr=NULL, time_t* timeout=NULL
- )=0;
+ virtual std::string active(const Application& application, const xmltooling::HTTPRequest& request)=0;
/**
* Locates an existing session bound to a request.
* <p>If the client address is supplied, then a check will be performed against
* the address recorded in the record.
*
- * @param request request from client containing session, or a reference to it
* @param application reference to Application that owns the Session
+ * @param request request from client containing session, or a reference to it
* @param client_addr network address of client (if known)
* @param timeout inactivity timeout to enforce (0 for none, NULL to bypass check/update of last access)
* @return pointer to locked Session, or NULL
*/
virtual Session* find(
- const xmltooling::HTTPRequest& request, const Application& application, const char* client_addr=NULL, time_t* timeout=NULL
+ const Application& application, const xmltooling::HTTPRequest& request, const char* client_addr=NULL, time_t* timeout=NULL
)=0;
/**
- * Deletes an existing session.
- *
- * @param key session key
- * @param application reference to Application that owns the Session
- */
- virtual void remove(const char* key, const Application& application)=0;
-
- /**
* Deletes an existing session bound to a request.
*
+ * @param application reference to Application that owns the Session
* @param request request from client containing session, or a reference to it
* @param response optional response to client enabling removal of session or reference
- * @param application reference to Application that owns the Session
*/
- virtual void remove(const xmltooling::HTTPRequest& request, xmltooling::HTTPResponse* response, const Application& application)=0;
+ virtual void remove(const Application& application, const xmltooling::HTTPRequest& request, xmltooling::HTTPResponse* response=NULL)=0;
};
/** SessionCache implementation backed by a StorageService. */
--- /dev/null
+/*
+ * Copyright 2001-2007 Internet2
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file shibsp/SessionCacheEx.h
+ *
+ * Extended SessionCache API with additional capabilities
+ */
+
+#ifndef __shibsp_sessioncacheex_h__
+#define __shibsp_sessioncacheex_h__
+
+#include <shibsp/SessionCache.h>
+
+namespace shibsp {
+
+ /**
+ * Extended SessionCache API with additional capabilities
+ */
+ class SHIBSP_API SessionCacheEx : public SessionCache
+ {
+ protected:
+ SessionCacheEx() {}
+ public:
+ virtual ~SessionCacheEx() {}
+
+#ifndef SHIBSP_LITE
+ /**
+ * Returns active sessions that match particular parameters and records the logout
+ * to prevent race conditions.
+ *
+ * <p>On exit, the mapping between these sessions and the associated information MAY be
+ * removed by the cache, so subsequent calls to this method may not return anything.
+ *
+ * <p>Until logout expiration, any attempt to create a session with the same parameters
+ * will be blocked by the cache.
+ *
+ * @param application reference to Application that owns the session(s)
+ * @param issuer source of session(s)
+ * @param nameid name identifier associated with the session(s) to terminate
+ * @param indexes indexes of sessions, or NULL for all sessions associated with other parameters
+ * @param expires logout expiration
+ * @param sessions on exit, contains the IDs of the matching sessions found
+ */
+ virtual std::vector<std::string>::size_type logout(
+ const Application& application,
+ const opensaml::saml2md::EntityDescriptor* issuer,
+ const opensaml::saml2::NameID& nameid,
+ const std::set<std::string>* indexes,
+ time_t expires,
+ std::vector<std::string>& sessions
+ )=0;
+#endif
+
+ /**
+ * Locates an existing session by ID.
+ *
+ * <p>If the client address is supplied, then a check will be performed against
+ * the address recorded in the record.
+ *
+ * @param application reference to Application that owns the Session
+ * @param key session key
+ * @param client_addr network address of client (if known)
+ * @param timeout inactivity timeout to enforce (0 for none, NULL to bypass check/update of last access)
+ * @return pointer to locked Session, or NULL
+ */
+ virtual Session* find(const Application& application, const char* key, const char* client_addr=NULL, time_t* timeout=NULL)=0;
+
+ /**
+ * Deletes an existing session.
+ *
+ * @param application reference to Application that owns the Session
+ * @param key session key
+ */
+ virtual void remove(const Application& application, const char* key)=0;
+ };
+};
+
+#endif /* __shibsp_sessioncacheex_h__ */
#include "Application.h"
#include "exceptions.h"
#include "ServiceProvider.h"
-#include "SessionCache.h"
+#include "SessionCacheEx.h"
#include "handler/AbstractHandler.h"
#include "handler/RemotedHandler.h"
#include "util/SPConstants.h"
m_log.debug("processing assertion lookup request (session: %s, assertion: %s)", key, ID);
+ SessionCacheEx* cache = dynamic_cast<SessionCacheEx*>(application.getServiceProvider().getSessionCache());
+ if (!cache) {
+ m_log.error("session cache does not support extended API");
+ throw FatalProfileException("Session cache does not support assertion lookup.");
+ }
+
// The cache will either silently pass a session or NULL back, or throw an exception out.
- Session* session = application.getServiceProvider().getSessionCache()->find(httpRequest, application);
+ Session* session = cache->find(application, ID);
if (!session) {
m_log.error("valid session (%s) not found for assertion lookup", key);
throw FatalProfileException("Session key not found.");
return ret;
const Application& app = request.getApplication();
- string session_id = app.getServiceProvider().getSessionCache()->active(request, app);
+ string session_id = app.getServiceProvider().getSessionCache()->active(app, request);
if (!session_id.empty()) {
// Do back channel notification.
vector<string> sessions(1, session_id);
if (!notifyBackChannel(app, request.getRequestURL(), sessions, true)) {
- app.getServiceProvider().getSessionCache()->remove(request, &request, app);
+ app.getServiceProvider().getSessionCache()->remove(app, request, &request);
return sendLogoutPage(app, request, true, "Partial logout failure.");
}
- request.getServiceProvider().getSessionCache()->remove(request, &request, app);
+ request.getServiceProvider().getSessionCache()->remove(app, request, &request);
}
return sendLogoutPage(app, request, true, "Logout was successful.");
tokens.insert(tokens.end(), badtokens.begin(), badtokens.end());
application.getServiceProvider().getSessionCache()->insert(
- now + lifetime.second,
application,
httpRequest,
httpResponse,
+ now + lifetime.second,
policy.getIssuerMetadata() ? dynamic_cast<const EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : NULL,
(!response->getMinorVersion().first || response->getMinorVersion().second==1) ?
samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM,
tokens.insert(tokens.end(), badtokens.begin(), badtokens.end());
application.getServiceProvider().getSessionCache()->insert(
- sessionExp,
application,
httpRequest,
httpResponse,
+ sessionExp,
policy.getIssuerMetadata() ? dynamic_cast<const EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : NULL,
samlconstants::SAML20P_NS,
ssoName,
#include "util/SPConstants.h"
#ifndef SHIBSP_LITE
-# include "SessionCache.h"
+# include "SessionCacheEx.h"
# include "security/SecurityPolicy.h"
# include "util/TemplateParameters.h"
# include <fstream>
#ifndef SHIBSP_LITE
// First capture the active session ID.
SessionCache* cache = application.getServiceProvider().getSessionCache();
- string session_id = cache->active(request, application);
+ SessionCacheEx* cacheex = dynamic_cast<SessionCacheEx*>(cache);
+ string session_id = cache->active(application, request);
if (!strcmp(request.getMethod(),"GET") && request.getParameter("notifying")) {
// This is returning from a front-channel notification, so we have to do the back-channel and then
// respond. To do that, we need state from the original request.
if (!request.getParameter("entityID")) {
- cache->remove(request, &response, application);
+ cache->remove(application, request, &response);
throw FatalProfileException("Application notification loop did not return entityID for LogoutResponse.");
}
vector<string> sessions(1,session_id);
worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
try {
- cache->remove(request, &response, application);
+ cache->remove(application, request, &response);
worked2 = true;
}
catch (exception& ex) {
// For a front-channel LogoutRequest, we have to match the information in the request
// against the current session.
if (!session_id.empty()) {
- if (!cache->matches(request, entity, *nameid, &indexes, application)) {
+ if (!cache->matches(application, request, entity, *nameid, &indexes)) {
return sendResponse(
logoutRequest->getID(),
StatusCode::REQUESTER, StatusCode::REQUEST_DENIED, "Active session did not match logout request.",
// Now we perform "logout" by finding the matching sessions.
vector<string> sessions;
try {
- time_t expires = logoutRequest->getNotOnOrAfter() ? logoutRequest->getNotOnOrAfterEpoch() : 0;
- cache->logout(entity, *nameid, &indexes, expires, application, sessions);
-
- // Now we actually terminate everything except for the active session,
- // if this is front-channel, for notification purposes.
- for (vector<string>::const_iterator sit = sessions.begin(); sit != sessions.end(); ++sit)
- if (*sit != session_id)
- cache->remove(sit->c_str(), application); // using the ID-based removal operation
+ if (cacheex) {
+ time_t expires = logoutRequest->getNotOnOrAfter() ? logoutRequest->getNotOnOrAfterEpoch() : 0;
+ cacheex->logout(application, entity, *nameid, &indexes, expires, sessions);
+
+ // Now we actually terminate everything except for the active session,
+ // if this is front-channel, for notification purposes.
+ for (vector<string>::const_iterator sit = sessions.begin(); sit != sessions.end(); ++sit)
+ if (*sit != session_id)
+ cacheex->remove(application, sit->c_str()); // using the ID-based removal operation
+ }
+ else {
+ m_log.warn("session cache does not support extended API, can't implement indirect logout of sessions");
+ if (!session_id.empty())
+ sessions.push_back(session_id);
+ }
}
catch (exception& ex) {
m_log.error("error while logging out matching sessions: %s", ex.what());
if (!session_id.empty()) {
// One last session to yoink...
try {
- cache->remove(request, &response, application);
+ cache->remove(application, request, &response);
worked2 = true;
}
catch (exception& ex) {
Session* session = NULL;
try {
- session = app->getServiceProvider().getSessionCache()->find(*req.get(), *app, NULL, NULL);
+ session = app->getServiceProvider().getSessionCache()->find(*app, *req.get(), NULL, NULL);
}
catch (exception& ex) {
m_log.error("error accessing current session: %s", ex.what());
else {
m_log.error("no NameID or issuing entityID found in session");
session->unlock();
- app->getServiceProvider().getSessionCache()->remove(*req.get(), resp.get(), *app);
+ app->getServiceProvider().getSessionCache()->remove(*app, *req.get(), resp.get());
}
}
out << ret;
vector<string> sessions(1, session->getID());
if (!notifyBackChannel(application, httpRequest.getRequestURL(), sessions, false)) {
session->unlock();
- application.getServiceProvider().getSessionCache()->remove(httpRequest, &httpResponse, application);
+ application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
return sendLogoutPage(application, httpResponse, true, "Partial logout failure.");
}
if (session) {
session->unlock();
session = NULL;
- application.getServiceProvider().getSessionCache()->remove(httpRequest, &httpResponse, application);
+ application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
}
return ret;
}
if (session) {
session->unlock();
session = NULL;
- application.getServiceProvider().getSessionCache()->remove(httpRequest, &httpResponse, application);
+ application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
}
return ret;
#else
session->unlock();
- application.getServiceProvider().getSessionCache()->remove(httpRequest, &httpResponse, application);
+ application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
throw ConfigurationException("Cannot perform logout using lite version of shibsp library.");
#endif
}
// Message from IdP to change or terminate a NameID.
// If this is front-channel, we have to have a session_id to use already.
- string session_id = cache->active(request, application);
+ string session_id = cache->active(application, request);
if (m_decoder->isUserAgentPresent() && session_id.empty()) {
m_log.error("no active session");
return sendResponse(
// against the current session.
EntityDescriptor* entity = policy.getIssuerMetadata() ? dynamic_cast<EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : NULL;
if (!session_id.empty()) {
- if (!cache->matches(request, entity, *nameid, NULL, application)) {
+ if (!cache->matches(application, request, entity, *nameid, NULL)) {
return sendResponse(
mgmtRequest->getID(),
StatusCode::REQUESTER, StatusCode::REQUEST_DENIED, "Active session did not match NameID mgmt request.",
#include "Application.h"
#include "exceptions.h"
#include "ServiceProvider.h"
-#include "SessionCache.h"
+#include "SessionCacheEx.h"
#include "TransactionLog.h"
#include "attribute/Attribute.h"
#include "remoting/ListenerService.h"
namespace shibsp {
class StoredSession;
- class SSCache : public SessionCache
+ class SSCache : public SessionCacheEx
#ifndef SHIBSP_LITE
,public virtual Remoted
#endif
void receive(DDF& in, ostream& out);
void insert(
- time_t expires,
const Application& application,
const HTTPRequest& httpRequest,
HTTPResponse& httpResponse,
+ time_t expires,
const saml2md::EntityDescriptor* issuer=NULL,
const XMLCh* protocol=NULL,
const saml2::NameID* nameid=NULL,
const vector<Attribute*>* attributes=NULL
);
vector<string>::size_type logout(
+ const Application& application,
const saml2md::EntityDescriptor* issuer,
const saml2::NameID& nameid,
const set<string>* indexes,
time_t expires,
- const Application& application,
vector<string>& sessions
);
bool matches(
+ const Application& application,
const xmltooling::HTTPRequest& request,
const saml2md::EntityDescriptor* issuer,
const saml2::NameID& nameid,
- const set<string>* indexes,
- const Application& application
+ const set<string>* indexes
);
#endif
- Session* find(const char* key, const Application& application, const char* client_addr=NULL, time_t* timeout=NULL);
- void remove(const char* key, const Application& application);
+ Session* find(const Application& application, const char* key, const char* client_addr=NULL, time_t* timeout=NULL);
+ void remove(const Application& application, const char* key);
void test();
- string active(const xmltooling::HTTPRequest& request, const Application& application) const {
+ string active(const Application& application, const xmltooling::HTTPRequest& request) {
pair<string,const char*> shib_cookie = application.getCookieNameProps("_shibsession_");
const char* session_id = request.getCookie(shib_cookie.first.c_str());
return (session_id ? session_id : "");
}
- Session* find(const HTTPRequest& request, const Application& application, const char* client_addr=NULL, time_t* timeout=NULL) {
- string id = active(request, application);
+ Session* find(const Application& application, const HTTPRequest& request, const char* client_addr=NULL, time_t* timeout=NULL) {
+ string id = active(application, request);
if (!id.empty())
- return find(id.c_str(), application, client_addr, timeout);
+ return find(application, id.c_str(), client_addr, timeout);
return NULL;
}
- void remove(const HTTPRequest& request, HTTPResponse* response, const Application& application) {
+ void remove(const Application& application, const HTTPRequest& request, HTTPResponse* response=NULL) {
pair<string,const char*> shib_cookie = application.getCookieNameProps("_shibsession_");
const char* session_id = request.getCookie(shib_cookie.first.c_str());
if (session_id && *session_id) {
if (response)
response->setCookie(shib_cookie.first.c_str(), shib_cookie.second);
- remove(session_id, application);
+ remove(application, session_id);
}
}
}
void SSCache::insert(
- time_t expires,
const Application& application,
const HTTPRequest& httpRequest,
HTTPResponse& httpResponse,
+ time_t expires,
const saml2md::EntityDescriptor* issuer,
const XMLCh* protocol,
const saml2::NameID* nameid,
}
pair<string,const char*> shib_cookie = application.getCookieNameProps("_shibsession_");
- string k(key.get());\r
+ string k(key.get());
k += shib_cookie.second;
httpResponse.setCookie(shib_cookie.first.c_str(), k.c_str());
}
bool SSCache::matches(
+ const Application& application,
const xmltooling::HTTPRequest& request,
const saml2md::EntityDescriptor* issuer,
const saml2::NameID& nameid,
- const set<string>* indexes,
- const Application& application
+ const set<string>* indexes
)
{
auto_ptr_char entityID(issuer ? issuer->getEntityID() : NULL);
try {
- Session* session = find(request, application);
+ Session* session = find(application, request);
if (session) {
Locker locker(session, false);
if (XMLString::equals(session->getEntityID(), entityID.get()) && session->getNameID() &&
}
vector<string>::size_type SSCache::logout(
+ const Application& application,
const saml2md::EntityDescriptor* issuer,
const saml2::NameID& nameid,
const set<string>* indexes,
time_t expires,
- const Application& application,
vector<string>& sessionsKilled
)
{
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);
+ return logout(application, issuer, nameid, indexes, expires, 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);
+ return logout(application, issuer, nameid, indexes, expires, sessionsKilled);
}
obj.destroy();
// Fetch the session for comparison.
Session* session = NULL;
try {
- session = find(key.string(), application);
+ session = find(application, key.string());
}
catch (exception& ex) {
m_log.error("error locating session (%s): %s", key.string(), ex.what());
#endif
-Session* SSCache::find(const char* key, const Application& application, const char* client_addr, time_t* timeout)
+Session* SSCache::find(const Application& application, const char* key, const char* client_addr, time_t* timeout)
{
#ifdef _DEBUG
xmltooling::NDC ndc("find");
if (timeout && *timeout > 0 && now - lastAccess >= *timeout) {
m_log.info("session timed out (ID: %s)", key);
- remove(key, application);
+ remove(application, key);
RetryableProfileException ex("Your session has expired, and you must re-authenticate.");
const char* eid = obj["entity_id"].string();
if (!eid) {
}
catch (...) {
session->unlock();
- remove(key, application);
+ remove(application, key);
throw;
}
return session;
}
-void SSCache::remove(const char* key, const Application& application)
+void SSCache::remove(const Application& application, const char* key)
{
#ifdef _DEBUG
xmltooling::NDC ndc("remove");
if (timeout > 0 && now - lastAccess >= timeout) {
m_log.info("session timed out (ID: %s)", key);
- remove(key,*app);
+ remove(*app, key);
throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
}
if (!app)
throw ConfigurationException("Application not found, check configuration?");
- remove(key,*app);
+ remove(*app, key);
DDF ret(NULL);
DDFJanitor jan(ret);
out << ret;
>\r
</File>\r
<File\r
+ RelativePath=".\SessionCacheEx.h"\r
+ >\r
+ </File>\r
+ <File\r
RelativePath=".\SPConfig.h"\r
>\r
</File>\r
>\r
</File>\r
<File\r
+ RelativePath=".\SessionCacheEx.h"\r
+ >\r
+ </File>\r
+ <File\r
RelativePath=".\SPConfig.h"\r
>\r
</File>\r