-/*
- * Copyright 2001-2010 Internet2
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
*
- * 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
+ * UCAID licenses this file to you 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
+ * 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.
+ * 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.
*/
/**
#include <shibsp/SessionCache.h>
#include <shibsp/SPConfig.h>
#include <shibsp/SPRequest.h>
+#include <shibsp/TransactionLog.h>
#include <shibsp/handler/AssertionConsumerService.h>
#include <shibsp/handler/LogoutInitiator.h>
#include <shibsp/handler/SessionInitiator.h>
using namespace xmltooling::logging;
using namespace xmltooling;
using namespace xercesc;
+using namespace boost;
using namespace std;
#define WSFED_NS "http://schemas.xmlsoap.org/ws/2003/07/secext"
return m_binding.get();
}
+#ifndef SHIBSP_LITE
+ void generateMetadata(saml2md::SPSSODescriptor& role, const char* handlerURL) const {
+ doGenerateMetadata(role, handlerURL);
+ }
+#endif
+
private:
pair<bool,long> doRequest(
const Application& application,
{
// We have to know the IdP to function.
if (entityID.empty() || !checkCompatibility(request, isHandler))
- return make_pair(false,0L);
+ return make_pair(false, 0L);
string target;
pair<bool,const char*> prop;
pair<bool,const char*> acClass;
- const Handler* ACS=nullptr;
- const Application& app=request.getApplication();
+ const Handler* ACS = nullptr;
+ const Application& app = request.getApplication();
if (isHandler) {
prop.second = request.getParameter("acsIndex");
// Since we're passing the ACS by value, we need to compute the return URL,
// so we'll need the target resource for real.
recoverRelayState(app, request, request, target, false);
+ app.limitRedirect(request, target.c_str());
acClass = getString("authnContextClassRef", request);
}
// Since we're not passing by index, we need to fully compute the return URL.
// Compute the ACS URL. We add the ACS location to the base handlerURL.
- string ACSloc=request.getHandlerURL(target.c_str());
+ string ACSloc = request.getHandlerURL(target.c_str());
prop = ACS->getString("Location");
if (prop.first)
ACSloc += prop.second;
void ADFSSessionInitiator::receive(DDF& in, ostream& out)
{
// Find application.
- const char* aid=in["application_id"].string();
- const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
+ const char* aid = in["application_id"].string();
+ const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
if (!app) {
// Something's horribly wrong.
m_log.error("couldn't find application (%s) to generate ADFS request", aid ? aid : "(missing)");
DDFJanitor jout(ret);
// Wrap the outgoing object with a Response facade.
- auto_ptr<HTTPResponse> http(getResponse(ret));
+ scoped_ptr<HTTPResponse> http(getResponse(ret));
string relayState(in["RelayState"].string() ? in["RelayState"].string() : "");
// 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, nullptr, *http.get(), entityID, acsLocation, in["authnContextClassRef"].string(), relayState);
+ doRequest(*app, nullptr, *http, entityID, acsLocation, in["authnContextClassRef"].string(), relayState);
if (!ret.isstruct())
ret.structure();
ret.addmember("RelayState").unsafe_string(relayState.c_str());
{
#ifndef SHIBSP_LITE
// Use metadata to invoke the SSO service directly.
- MetadataProvider* m=app.getMetadataProvider();
+ MetadataProvider* m = app.getMetadataProvider();
Locker locker(m);
MetadataProviderCriteria mc(app, entityID, &IDPSSODescriptor::ELEMENT_QNAME, m_binding.get());
- pair<const EntityDescriptor*,const RoleDescriptor*> entity=m->getEntityDescriptor(mc);
+ pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(mc);
if (!entity.first) {
m_log.warn("unable to locate metadata for provider (%s)", entityID);
throw MetadataException("Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", entityID));
else if (!entity.second) {
m_log.log(getParent() ? Priority::INFO : Priority::WARN, "unable to locate ADFS-aware identity provider role for provider (%s)", entityID);
if (getParent())
- return make_pair(false,0L);
+ return make_pair(false, 0L);
throw MetadataException("Unable to locate ADFS-aware identity provider role for provider ($entityID)", namedparams(1, "entityID", entityID));
}
const EndpointType* ep = EndpointManager<SingleSignOnService>(
if (!ep) {
m_log.warn("unable to locate compatible SSO service for provider (%s)", entityID);
if (getParent())
- return make_pair(false,0L);
+ return make_pair(false, 0L);
throw MetadataException("Unable to locate compatible SSO service for provider ($entityID)", namedparams(1, "entityID", entityID));
}
preserveRelayState(app, httpResponse, relayState);
+ scoped_ptr<AuthnRequestEvent> ar_event(newAuthnRequestEvent(app, httpRequest));
+ if (ar_event.get()) {
+ ar_event->m_binding = WSFED_NS;
+ ar_event->m_protocol = WSFED_NS;
+ ar_event->m_peer = entity.first;
+ app.getServiceProvider().getTransactionLog()->write(*ar_event);
+ }
+
// UTC timestamp
time_t epoch=time(nullptr);
#ifndef HAVE_GMTIME_R
return make_pair(true, httpResponse.sendRedirect(req.c_str()));
#else
- return make_pair(false,0L);
+ return make_pair(false, 0L);
#endif
}
if (!policy.isAuthenticated())
throw SecurityPolicyException("Unable to establish security of incoming assertion.");
- saml1::NameIdentifier* saml1name=nullptr;
- saml2::NameID* saml2name=nullptr;
+ const saml1::NameIdentifier* saml1name=nullptr;
+ const saml1::AuthenticationStatement* saml1statement=nullptr;
+ const saml2::NameID* saml2name=nullptr;
+ const saml2::AuthnStatement* saml2statement=nullptr;
const XMLCh* authMethod=nullptr;
const XMLCh* authInstant=nullptr;
time_t now = time(nullptr), sessionExp = 0;
// authnskew allows rejection of SSO if AuthnInstant is too old.
pair<bool,unsigned int> authnskew = sessionProps ? sessionProps->getUnsignedInt("maxTimeSinceAuthn") : pair<bool,unsigned int>(false,0);
- const saml1::AuthenticationStatement* ssoStatement=saml1token->getAuthenticationStatements().front();
- if (authnskew.first && authnskew.second &&
- ssoStatement->getAuthenticationInstant() && (now - ssoStatement->getAuthenticationInstantEpoch() > authnskew.second))
- throw FatalProfileException("The gap between now and the time you logged into your identity provider exceeds the limit.");
+ saml1statement = saml1token->getAuthenticationStatements().front();
+ if (saml1statement->getAuthenticationInstant()) {
+ if (saml1statement->getAuthenticationInstantEpoch() - XMLToolingConfig::getConfig().clock_skew_secs > now) {
+ throw FatalProfileException("The login time at your identity provider was future-dated.");
+ }
+ else if (authnskew.first && authnskew.second && saml1statement->getAuthenticationInstantEpoch() <= now &&
+ (now - saml1statement->getAuthenticationInstantEpoch() > authnskew.second)) {
+ throw FatalProfileException("The gap between now and the time you logged into your identity provider exceeds the allowed limit.");
+ }
+ }
+ else if (authnskew.first && authnskew.second) {
+ throw FatalProfileException("Your identity provider did not supply a time of login, violating local policy.");
+ }
// Address checking.
- saml1::SubjectLocality* locality = ssoStatement->getSubjectLocality();
+ saml1::SubjectLocality* locality = saml1statement->getSubjectLocality();
if (locality && locality->getIPAddress()) {
auto_ptr_char ip(locality->getIPAddress());
checkAddress(application, httpRequest, ip.get());
}
- saml1name = ssoStatement->getSubject()->getNameIdentifier();
- authMethod = ssoStatement->getAuthenticationMethod();
- if (ssoStatement->getAuthenticationInstant())
- authInstant = ssoStatement->getAuthenticationInstant()->getRawData();
+ saml1name = saml1statement->getSubject()->getNameIdentifier();
+ authMethod = saml1statement->getAuthenticationMethod();
+ if (saml1statement->getAuthenticationInstant())
+ authInstant = saml1statement->getAuthenticationInstant()->getRawData();
// Session expiration.
pair<bool,unsigned int> lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair<bool,unsigned int>(true,28800);
// authnskew allows rejection of SSO if AuthnInstant is too old.
pair<bool,unsigned int> authnskew = sessionProps ? sessionProps->getUnsignedInt("maxTimeSinceAuthn") : pair<bool,unsigned int>(false,0);
- const saml2::AuthnStatement* ssoStatement=saml2token->getAuthnStatements().front();
+ saml2statement = saml2token->getAuthnStatements().front();
if (authnskew.first && authnskew.second &&
- ssoStatement->getAuthnInstant() && (now - ssoStatement->getAuthnInstantEpoch() > authnskew.second))
+ saml2statement->getAuthnInstant() && (now - saml2statement->getAuthnInstantEpoch() > authnskew.second))
throw FatalProfileException("The gap between now and the time you logged into your identity provider exceeds the limit.");
// Address checking.
- saml2::SubjectLocality* locality = ssoStatement->getSubjectLocality();
+ saml2::SubjectLocality* locality = saml2statement->getSubjectLocality();
if (locality && locality->getAddress()) {
auto_ptr_char ip(locality->getAddress());
checkAddress(application, httpRequest, ip.get());
}
saml2name = saml2token->getSubject() ? saml2token->getSubject()->getNameID() : nullptr;
- if (ssoStatement->getAuthnContext() && ssoStatement->getAuthnContext()->getAuthnContextClassRef())
- authMethod = ssoStatement->getAuthnContext()->getAuthnContextClassRef()->getReference();
- if (ssoStatement->getAuthnInstant())
- authInstant = ssoStatement->getAuthnInstant()->getRawData();
+ if (saml2statement->getAuthnContext() && saml2statement->getAuthnContext()->getAuthnContextClassRef())
+ authMethod = saml2statement->getAuthnContext()->getAuthnContextClassRef()->getReference();
+ if (saml2statement->getAuthnInstant())
+ authInstant = saml2statement->getAuthnInstant()->getRawData();
// Session expiration for SAML 2.0 is jointly IdP- and SP-driven.
- sessionExp = ssoStatement->getSessionNotOnOrAfter() ? ssoStatement->getSessionNotOnOrAfterEpoch() : 0;
+ sessionExp = saml2statement->getSessionNotOnOrAfter() ? saml2statement->getSessionNotOnOrAfterEpoch() : 0;
pair<bool,unsigned int> lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair<bool,unsigned int>(true,28800);
if (!lifetime.first || lifetime.second == 0)
lifetime.second = 28800;
// To complete processing, we need to extract and resolve attributes and then create the session.
// Normalize a SAML 1.x NameIdentifier...
- auto_ptr<saml2::NameID> nameid(saml1name ? saml2::NameIDBuilder::buildNameID() : nullptr);
+ scoped_ptr<saml2::NameID> nameid(saml1name ? saml2::NameIDBuilder::buildNameID() : nullptr);
if (saml1name) {
nameid->setName(saml1name->getName());
nameid->setFormat(saml1name->getFormat());
// The context will handle deleting attributes and new tokens.
vector<const Assertion*> tokens(1,token);
- auto_ptr<ResolutionContext> ctx(
+ scoped_ptr<ResolutionContext> ctx(
resolveAttributes(
application,
+ &httpRequest,
policy.getIssuerMetadata(),
m_protocol.get(),
+ nullptr,
saml1name,
+ saml1statement,
(saml1name ? nameid.get() : saml2name),
+ saml2statement,
authMethod,
nullptr,
&tokens
tokens.insert(tokens.end(), ctx->getResolvedAssertions().begin(), ctx->getResolvedAssertions().end());
}
+ string session_id;
application.getServiceProvider().getSessionCache()->insert(
+ session_id,
application,
httpRequest,
httpResponse,
authMethod,
nullptr,
&tokens,
- ctx.get() ? &ctx->getResolvedAttributes() : nullptr
+ ctx ? &ctx->getResolvedAttributes() : nullptr
);
+
+ scoped_ptr<LoginEvent> login_event(newLoginEvent(application, httpRequest));
+ if (login_event) {
+ login_event->m_sessionID = session_id.c_str();
+ login_event->m_peer = entity;
+ login_event->m_protocol = WSFED_NS;
+ login_event->m_binding = WSFED_NS;
+ login_event->m_saml1AuthnStatement = saml1statement;
+ login_event->m_nameID = (saml1name ? nameid.get() : saml2name);
+ login_event->m_saml2AuthnStatement = saml2statement;
+ if (ctx)
+ login_event->m_attributes = &ctx->getResolvedAttributes();
+ application.getServiceProvider().getTransactionLog()->write(*login_event);
+ }
}
#endif
try {
session = request.getSession(false, true, false); // don't cache it and ignore all checks
if (!session)
- return make_pair(false,0L);
+ return make_pair(false, 0L);
// We only handle ADFS sessions.
if (!XMLString::equals(session->getProtocol(), WSFED_NS) || !session->getEntityID()) {
session->unlock();
- return make_pair(false,0L);
+ return make_pair(false, 0L);
}
}
- catch (exception& ex) {
+ catch (std::exception& ex) {
m_log.error("error accessing current session: %s", ex.what());
return make_pair(false,0L);
}
- string entityID(session->getEntityID());
- session->unlock();
-
if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
// When out of process, we run natively.
return doRequest(request.getApplication(), request, request, session);
// When not out of process, we remote the request.
session->unlock();
vector<string> headers(1,"Cookie");
- DDF out,in = wrap(request,&headers);
+ headers.push_back("User-Agent");
+ DDF out,in = wrap(request, &headers);
DDFJanitor jin(in), jout(out);
out=request.getServiceProvider().getListenerService()->send(in);
return unwrap(request, out);
return LogoutHandler::receive(in, out);
// Find application.
- const char* aid=in["application_id"].string();
- const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
+ const char* aid = in["application_id"].string();
+ const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
if (!app) {
// Something's horribly wrong.
m_log.error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
}
// Unpack the request.
- auto_ptr<HTTPRequest> req(getRequest(in));
+ scoped_ptr<HTTPRequest> req(getRequest(in));
// Set up a response shim.
DDF ret(nullptr);
DDFJanitor jout(ret);
- auto_ptr<HTTPResponse> resp(getResponse(ret));
+ scoped_ptr<HTTPResponse> resp(getResponse(ret));
Session* session = nullptr;
try {
- session = app->getServiceProvider().getSessionCache()->find(*app, *req.get(), nullptr, nullptr);
+ session = app->getServiceProvider().getSessionCache()->find(*app, *req, nullptr, nullptr);
}
- catch (exception& ex) {
+ catch (std::exception& ex) {
m_log.error("error accessing current session: %s", ex.what());
}
// 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, *req.get(), *resp.get(), session);
+ doRequest(*app, *req, *resp, session);
}
else {
- m_log.error("no issuing entityID found in session");
- session->unlock();
- app->getServiceProvider().getSessionCache()->remove(*app, *req.get(), resp.get());
+ m_log.error("no issuing entityID found in session");
+ session->unlock();
+ app->getServiceProvider().getSessionCache()->remove(*app, *req, resp.get());
}
}
out << ret;
const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse, Session* session
) const
{
+ Locker sessionLocker(session, false);
+
// Do back channel notification.
vector<string> sessions(1, session->getID());
if (!notifyBackChannel(application, httpRequest.getRequestURL(), sessions, false)) {
- session->unlock();
+#ifndef SHIBSP_LITE
+ scoped_ptr<LogoutEvent> logout_event(newLogoutEvent(application, &httpRequest, session));
+ if (logout_event) {
+ logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_PARTIAL;
+ application.getServiceProvider().getTransactionLog()->write(*logout_event);
+ }
+#endif
+ sessionLocker.assign();
+ session = nullptr;
application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
return sendLogoutPage(application, httpRequest, httpResponse, "partial");
}
#ifndef SHIBSP_LITE
- pair<bool,long> ret = make_pair(false,0L);
+ pair<bool,long> ret = make_pair(false, 0L);
try {
// With a session in hand, we can create a request message, if we can find a compatible endpoint.
- MetadataProvider* m=application.getMetadataProvider();
+ MetadataProvider* m = application.getMetadataProvider();
Locker metadataLocker(m);
MetadataProviderCriteria mc(application, session->getEntityID(), &IDPSSODescriptor::ELEMENT_QNAME, m_binding.get());
pair<const EntityDescriptor*,const RoleDescriptor*> entity=m->getEntityDescriptor(mc);
);
}
- const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
const char* returnloc = httpRequest.getParameter("return");
+ if (returnloc)
+ application.limitRedirect(httpRequest, returnloc);
+
+ // Log the request.
+ scoped_ptr<LogoutEvent> logout_event(newLogoutEvent(application, &httpRequest, session));
+ if (logout_event) {
+ logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_UNKNOWN;
+ application.getServiceProvider().getTransactionLog()->write(*logout_event);
+ }
+
auto_ptr_char dest(ep->getLocation());
string req=string(dest.get()) + (strchr(dest.get(),'?') ? '&' : '?') + "wa=wsignout1.0";
- if (returnloc)
- req += "&wreply=" + urlenc->encode(returnloc);
+ if (returnloc) {
+ req += "&wreply=";
+ if (*returnloc == '/') {
+ string s(returnloc);
+ httpRequest.absolutize(s);
+ req += XMLToolingConfig::getConfig().getURLEncoder()->encode(s.c_str());
+ }
+ else {
+ req += XMLToolingConfig::getConfig().getURLEncoder()->encode(returnloc);
+ }
+ }
ret.second = httpResponse.sendRedirect(req.c_str());
ret.first = true;
+
+ if (session) {
+ sessionLocker.assign();
+ session = nullptr;
+ application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
+ }
}
- catch (exception& ex) {
- m_log.error("error issuing ADFS logout request: %s", ex.what());
+ catch (MetadataException& mex) {
+ // Less noise for IdPs that don't support logout
+ m_log.info("unable to issue ADFS logout request: %s", mex.what());
}
-
- if (session) {
- session->unlock();
- session = nullptr;
- application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
+ catch (std::exception& ex) {
+ m_log.error("error issuing ADFS logout request: %s", ex.what());
}
return ret;
try {
app.getServiceProvider().getSessionCache()->remove(app, request, &request);
}
- catch (exception& ex) {
+ catch (std::exception& ex) {
m_log.error("error removing session (%s): %s", session_id.c_str(), ex.what());
}
}
- if (param)
- return make_pair(true, request.sendRedirect(param));
+ if (param) {
+ if (*param == '/') {
+ string p(param);
+ request.absolutize(p);
+ return make_pair(true, request.sendRedirect(p.c_str()));
+ }
+ else {
+ app.limitRedirect(request, param);
+ return make_pair(true, request.sendRedirect(param));
+ }
+ }
return sendLogoutPage(app, request, request, "global");
}