configs\apache.config.in = configs\apache.config.in
configs\apache2.config.in = configs\apache2.config.in
configs\apache22.config.in = configs\apache22.config.in
+ configs\attrChecker.html = configs\attrChecker.html
configs\attribute-map.xml = configs\attribute-map.xml
configs\attribute-policy.xml = configs\attribute-policy.xml
configs\bindingTemplate.html = configs\bindingTemplate.html
configs\postTemplate.html = configs\postTemplate.html
configs\protocols.xml = configs\protocols.xml
configs\security-policy.xml = configs\security-policy.xml
- configs\sessionError.html = configs\sessionError.html
configs\shibboleth2.xml = configs\shibboleth2.xml
configs\shibd-debian.in = configs\shibd-debian.in
configs\shibd-osx.plist.in = configs\shibd-osx.plist.in
console.logger \
syslog.logger \
accessError.html \
+ attrChecker.html \
sessionError.html \
metadataError.html \
bindingTemplate.html \
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="stylesheet" type="text/css" href="<shibmlp styleSheet/>" />
+ <title>Insufficient Information</title>
+</head>
+
+<body>
+
+<img src="<shibmlp logoLocation/>" alt="Logo" />
+<h3>We're sorry, but you cannot access this service at this time.</h3>
+
+<p>This service requires information about you that your identity provider
+<shibmlpif Meta-displayName>(<shibmlp Meta-displayName />)</shibmlpif>
+did not release. To gain access to this service, your identity provider
+must release the required information.</p>
+
+<shibmlpif Meta-errorURL>
+<p>
+<div class="boxed-content">
+Please visit
+<a href="<shibmlp Meta-errorURL />">
+<shibmlpif Meta-displayName>the <shibmlp Meta-displayName /> support page</shibmlpif>
+<shibmlpifnot Meta-displayName>this support page</shibmlpifnot>
+</a>
+for further instructions.
+</div>
+</p>
+</shibmlpif>
+
+<p>
+You were trying to access the following URL:
+<blockquote><p><shibmlp target /></p></blockquote>
+</p>
+
+<shibmlpif helpLocation>
+<p>For more information about this service, including what user information is required for access,
+please visit <a href="<shibmlp helpLocation />">our information page</a>.</p>
+</shibmlpif>
+
+</body>
+</html>
-->
<ApplicationDefaults entityID="https://sp.example.org/shibboleth"
REMOTE_USER="eppn persistent-id targeted-id"
+ metadataAttributePrefix="Meta-"
+ sessionHook="/Shibboleth.sso/AttrChecker"
signing="false" encryption="false">
<!--
<!-- JSON feed of discovery information. -->
<Handler type="DiscoveryFeed" Location="/DiscoFeed"/>
+
+ <!-- Checks for required attribute(s) before login completes. -->
+ <Handler type="AttributeChecker" Location="/AttrChecker" template="attrChecker.html"
+ attributes="eppn" flushSession="true"/>
</Sessions>
<!--
also add attributes with values that can be plugged into the templates.
-->
<Errors supportContact="root@localhost"
+ helpLocation="/about.html"
logoLocation="/shibboleth-sp/logo.jpg"
styleSheet="/shibboleth-sp/main.css"/>
<!-- Map to extract attributes from SAML assertions. -->
<AttributeExtractor type="XML" validate="true" path="attribute-map.xml"/>
-
+
+ <!-- Extracts support information for IdP from its metadata. -->
+ <AttributeExtractor type="Metadata" errorURL="errorURL" DisplayName="displayName"/>
+
<!-- Use a SAML query if no attributes are supplied during SSO. -->
<AttributeResolver type="Query" subjectMatch="true"/>
also add attributes with values that can be plugged into the templates.
-->
<Errors supportContact="root@localhost"
+ helpLocation="/about.html"
logoLocation="/shibboleth-sp/logo.jpg"
styleSheet="/shibboleth-sp/main.css"/>
also add attributes with values that can be plugged into the templates.
-->
<Errors supportContact="root@localhost"
+ helpLocation="/about.html"
logoLocation="/shibboleth-sp/logo.jpg"
styleSheet="/shibboleth-sp/main.css"/>
font-weight: bold;
}
+img {
+ margin-bottom: 15px;
+}
+
.error {
font-size: 10pt;
font-weight: bold;
}
-img {
- margin-bottom: 15px;
-}
\ No newline at end of file
+.boxed-content {
+ margin-top: 2ex;
+ margin-right: 3em;
+ margin-bottom: 2ex;
+ margin-left: 3em;
+ padding-top: 0.5ex;
+ padding-right: 1em;
+ padding-bottom: 0.5ex;
+ padding-left: 1em;
+ border-width: 1px;
+ border-style: solid;
+ border-color: #999999;
+}
<attribute name="partialLogout" type="anyURI"/>
<attribute name="supportContact" type="conf:string"/>
<attribute name="logoLocation" type="anyURI"/>
+ <attribute name="helpLocation" type="anyURI"/>
<attribute name="styleSheet" type="anyURI"/>
<anyAttribute namespace="##any" processContents="lax"/>
</complexType>
handler/impl/AbstractHandler.cpp \
handler/impl/AssertionConsumerService.cpp \
handler/impl/AssertionLookup.cpp \
+ handler/impl/AttributeCheckerHandler.cpp \
handler/impl/ChainingLogoutInitiator.cpp \
handler/impl/ChainingSessionInitiator.cpp \
handler/impl/CookieSessionInitiator.cpp \
/** Handler for SAML 2.0 Artifact Resolution. */
#define SAML20_ARTIFACT_RESOLUTION_SERVICE "SAML2"
+ /** Handler for hooking new sessions with attribute checking. */
+ #define ATTR_CHECKER_HANDLER "AttributeChecker"
+
/** Handler for metadata generation. */
#define DISCOVERY_FEED_HANDLER "DiscoveryFeed"
SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2LogoutFactory;
SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2NameIDMgmtFactory;
SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory AssertionLookupFactory;
+ SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory AttributeCheckerFactory;
SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory DiscoveryFeedFactory;
SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory MetadataGeneratorFactory;
SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory StatusHandlerFactory;
conf.ArtifactResolutionServiceManager.registerFactory(SAML20_BINDING_SOAP, SAML2ArtifactResolutionFactory);
conf.HandlerManager.registerFactory(SAML20_BINDING_URI, AssertionLookupFactory);
+ conf.HandlerManager.registerFactory(ATTR_CHECKER_HANDLER, AttributeCheckerFactory);
conf.HandlerManager.registerFactory(DISCOVERY_FEED_HANDLER, DiscoveryFeedFactory);
conf.HandlerManager.registerFactory(METADATA_GENERATOR_HANDLER, MetadataGeneratorFactory);
conf.HandlerManager.registerFactory(STATUS_HANDLER, StatusHandlerFactory);
--- /dev/null
+/**
+ * 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.
+ *
+ * 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
+ *
+ * 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.
+ */
+
+/**
+ * AttributeCheckerHandler.cpp
+ *
+ * Handler for checking a session for required attributes.
+ */
+
+#include "internal.h"
+#include "AccessControl.h"
+#include "Application.h"
+#include "exceptions.h"
+#include "ServiceProvider.h"
+#include "SessionCache.h"
+#include "SPRequest.h"
+#include "attribute/Attribute.h"
+#include "handler/AbstractHandler.h"
+#include "util/TemplateParameters.h"
+
+#include <fstream>
+#include <sstream>
+#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/algorithm/string.hpp>
+#include <xmltooling/XMLToolingConfig.h>
+#include <xmltooling/util/PathResolver.h>
+#include <xmltooling/util/XMLHelper.h>
+
+using namespace shibsp;
+using namespace xmltooling;
+using namespace boost;
+using namespace std;
+
+namespace shibsp {
+
+#if defined (_MSC_VER)
+ #pragma warning( push )
+ #pragma warning( disable : 4250 )
+#endif
+
+ class SHIBSP_DLLLOCAL Blocker : public DOMNodeFilter
+ {
+ public:
+#ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE
+ short
+#else
+ FilterAction
+#endif
+ acceptNode(const DOMNode* node) const {
+ return FILTER_REJECT;
+ }
+ };
+
+ static SHIBSP_DLLLOCAL Blocker g_Blocker;
+
+ class SHIBSP_API AttributeCheckerHandler : public AbstractHandler
+ {
+ public:
+ AttributeCheckerHandler(const DOMElement* e, const char* appId);
+ virtual ~AttributeCheckerHandler() {}
+
+ pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+
+ private:
+ void flushSession(SPRequest& request) const {
+ try {
+ request.getApplication().getServiceProvider().getSessionCache()->remove(request.getApplication(), request, &request);
+ }
+ catch (std::exception&) {
+ }
+ }
+
+ string m_template;
+ bool m_flushSession;
+ vector<string> m_attributes;
+ scoped_ptr<AccessControl> m_acl;
+ };
+
+#if defined (_MSC_VER)
+ #pragma warning( pop )
+#endif
+
+ Handler* SHIBSP_DLLLOCAL AttributeCheckerFactory(const pair<const DOMElement*,const char*>& p)
+ {
+ return new AttributeCheckerHandler(p.first, p.second);
+ }
+
+ static const XMLCh attributes[] = UNICODE_LITERAL_10(a,t,t,r,i,b,u,t,e,s);
+ static const XMLCh _flushSession[] = UNICODE_LITERAL_12(f,l,u,s,h,S,e,s,s,i,o,n);
+ static const XMLCh _template[] = UNICODE_LITERAL_8(t,e,m,p,l,a,t,e);
+};
+
+AttributeCheckerHandler::AttributeCheckerHandler(const DOMElement* e, const char* appId)
+ : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".AttributeCheckerHandler"), &g_Blocker)
+{
+ if (!SPConfig::getConfig().isEnabled(SPConfig::InProcess))
+ return;
+ m_template = XMLHelper::getAttrString(e, nullptr, _template);
+ if (m_template.empty())
+ throw ConfigurationException("AttributeChecker missing required template setting.");
+ XMLToolingConfig::getConfig().getPathResolver()->resolve(m_template, PathResolver::XMLTOOLING_CFG_FILE);
+
+ m_flushSession = XMLHelper::getAttrBool(e, false, _flushSession);
+
+ string attrs(XMLHelper::getAttrString(e, nullptr, attributes));
+ if (!attrs.empty()) {
+ split(m_attributes, attrs, is_space(), algorithm::token_compress_on);
+ if (m_attributes.empty())
+ throw ConfigurationException("AttributeChecker unable to parse attributes setting.");
+ }
+ else {
+ m_acl.reset(SPConfig::getConfig().AccessControlManager.newPlugin(XML_ACCESS_CONTROL, e));
+ }
+}
+
+pair<bool,long> AttributeCheckerHandler::run(SPRequest& request, bool isHandler) const
+{
+ // If the checking passes, we route to the return URL, target URL, or homeURL in that order.
+ const char* returnURL = request.getParameter("return");
+ const char* target = request.getParameter("target");
+ if (!returnURL)
+ returnURL = target;
+ if (returnURL)
+ request.getApplication().limitRedirect(request, returnURL);
+ else
+ returnURL = request.getApplication().getString("homeURL").second;
+ if (!returnURL)
+ returnURL = "/";
+
+ Session* session = nullptr;
+ try {
+ session = request.getSession(true, false, false);
+ if (!session)
+ request.log(SPRequest::SPWarn, "AttributeChecker found session unavailable immediately after creation");
+ }
+ catch (std::exception& ex) {
+ request.log(SPRequest::SPWarn, string("AttributeChecker caught exception accessing session immediately after creation: ") + ex.what());
+ }
+
+ Locker sessionLocker(session, false);
+
+ bool checked = false;
+ if (session) {
+ if (!m_attributes.empty()) {
+ typedef multimap<string,const Attribute*> indexed_t;
+ static indexed_t::const_iterator (indexed_t::* fn)(const string&) const = &indexed_t::find;
+ const indexed_t& indexed = session->getIndexedAttributes();
+ // Look for an attribute in the list that is not in the session multimap.
+ // If that fails, the check succeeds.
+ checked = (
+ find_if(m_attributes.begin(), m_attributes.end(),
+ boost::bind(fn, boost::cref(indexed), _1) == indexed.end()) == m_attributes.end()
+ );
+ }
+ else {
+ checked = (m_acl && m_acl->authorized(request, session) == AccessControl::shib_acl_true);
+ }
+ }
+
+ if (checked) {
+ string loc(returnURL);
+ request.absolutize(loc);
+ return make_pair(true, request.sendRedirect(loc.c_str()));
+ }
+
+ request.setContentType("text/html; charset=UTF-8");
+ request.setResponseHeader("Expires","Wed, 01 Jan 1997 12:00:00 GMT");
+ request.setResponseHeader("Cache-Control","private,no-store,no-cache,max-age=0");
+
+ ifstream infile(m_template);
+ if (infile) {
+ TemplateParameters tp(nullptr, request.getApplication().getPropertySet("Errors"), session);
+ tp.m_request = &request;
+ stringstream str;
+ XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
+ if (m_flushSession) {
+ sessionLocker.assign(); // unlock the session
+ flushSession(request);
+ }
+ return make_pair(true, request.sendError(str));
+ }
+
+ if (m_flushSession) {
+ sessionLocker.assign(); // unlock the session
+ flushSession(request);
+ }
+ m_log.error("could not process error template (%s)", m_template.c_str());
+ istringstream msg("Internal Server Error. Please contact the site administrator.");
+ return make_pair(true, request.sendResponse(msg));
+}
}
static const XMLCh _AccessControl[] = UNICODE_LITERAL_13(A,c,c,e,s,s,C,o,n,t,r,o,l);
+ static const XMLCh _Handler[] = UNICODE_LITERAL_7(H,a,n,d,l,e,r);
static const XMLCh ignoreCase[] = UNICODE_LITERAL_10(i,g,n,o,r,e,C,a,s,e);
static const XMLCh ignoreOption[] = UNICODE_LITERAL_1(i);
static const XMLCh _list[] = UNICODE_LITERAL_4(l,i,s,t);
{
if (m_alias.empty())
throw ConfigurationException("Access control rule missing require attribute");
+ if (!e->hasChildNodes())
+ return; // empty rule
- auto_arrayptr<char> vals(toUTF8(e->hasChildNodes() ? e->getFirstChild()->getNodeValue() : nullptr));
- if (!vals.get())
- return;
+ auto_arrayptr<char> vals(toUTF8(e->getTextContent()));
+ if (!vals.get() || !*vals.get())
+ throw ConfigurationException("Unable to convert Rule content into UTF-8.");
bool listflag = XMLHelper::getAttrBool(e, true, _list);
if (!listflag) {
- if (*vals.get())
- m_vals.insert(vals.get());
+ m_vals.insert(vals.get());
return;
}
string temp(vals.get());
split(m_vals, temp, boost::is_space(), algorithm::token_compress_on);
+ if (m_vals.empty())
+ throw ConfigurationException("Rule did not contain any usable values.");
}
AccessControl::aclresult_t Rule::authorized(const SPRequest& request, const Session* session) const
request.log(SPRequest::SPWarn, string("rule requires attribute (") + m_alias + "), not found in session");
return shib_acl_false;
}
+ else if (m_vals.empty()) {
+ request.log(SPRequest::SPDebug, string("AccessControl plugin requires presence of attribute (") + m_alias + "), authz granted");
+ return shib_acl_true;
+ }
for (; attrs.first != attrs.second; ++attrs.first) {
bool caseSensitive = attrs.first->second->isCaseSensitive();
XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : nullptr);
// Check for AccessControl wrapper and drop a level.
- if (XMLString::equals(raw.second->getLocalName(),_AccessControl))
+ if (XMLString::equals(raw.second->getLocalName(),_AccessControl)) {
raw.second = XMLHelper::getFirstChildElement(raw.second);
+ if (!raw.second)
+ throw ConfigurationException("No child element found in AccessControl parent element.");
+ }
+ else if (XMLString::equals(raw.second->getLocalName(),_Handler)) {
+ raw.second = XMLHelper::getFirstChildElement(raw.second);
+ if (!raw.second)
+ throw ConfigurationException("No child element found in Handler parent element.");
+ }
scoped_ptr<AccessControl> authz;
if (XMLString::equals(raw.second->getLocalName(),_Rule))
<ClCompile Include="AbstractSPRequest.cpp" />\r
<ClCompile Include="Application.cpp" />\r
<ClCompile Include="binding\impl\XMLProtocolProvider.cpp" />\r
+ <ClCompile Include="handler\impl\AttributeCheckerHandler.cpp" />\r
<ClCompile Include="handler\impl\DiscoveryFeed.cpp" />\r
<ClCompile Include="handler\impl\LogoutInitiator.cpp" />\r
<ClCompile Include="ServiceProvider.cpp" />\r
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
<ImportGroup Label="ExtensionTargets">\r
</ImportGroup>\r
-</Project>
+</Project>
\ No newline at end of file
<ClCompile Include="attribute\BinaryAttribute.cpp">\r
<Filter>Source Files\attribute</Filter>\r
</ClCompile>\r
+ <ClCompile Include="handler\impl\AttributeCheckerHandler.cpp">\r
+ <Filter>Source Files\handler\impl</Filter>\r
+ </ClCompile>\r
</ItemGroup>\r
<ItemGroup>\r
<ClInclude Include="remoting\impl\SocketListener.h">\r
<ClCompile Include="attribute\resolver\impl\AssertionAttributeExtractor.cpp" />\r
<ClCompile Include="attribute\resolver\impl\MetadataAttributeExtractor.cpp" />\r
<ClCompile Include="binding\impl\XMLProtocolProvider.cpp" />\r
+ <ClCompile Include="handler\impl\AttributeCheckerHandler.cpp" />\r
<ClCompile Include="handler\impl\DiscoveryFeed.cpp" />\r
<ClCompile Include="handler\impl\LogoutInitiator.cpp" />\r
<ClCompile Include="impl\XMLSecurityPolicyProvider.cpp" />\r
<ClCompile Include="attribute\resolver\impl\MetadataAttributeExtractor.cpp">\r
<Filter>Source Files\attribute\resolver\impl</Filter>\r
</ClCompile>\r
+ <ClCompile Include="handler\impl\AttributeCheckerHandler.cpp">\r
+ <Filter>Source Files\handler\impl</Filter>\r
+ </ClCompile>\r
</ItemGroup>\r
<ItemGroup>\r
<ClInclude Include="remoting\impl\SocketListener.h">\r