https://issues.shibboleth.net/jira/browse/SSPCPP-245
authorScott Cantor <cantor.2@osu.edu>
Wed, 8 Feb 2012 16:49:49 +0000 (16:49 +0000)
committerScott Cantor <cantor.2@osu.edu>
Wed, 8 Feb 2012 16:49:49 +0000 (16:49 +0000)
17 files changed:
Shibboleth.sln
configs/Makefile.am
configs/attrChecker.html [new file with mode: 0644]
configs/example-shibboleth2.xml
configs/shibboleth2.xml
configs/win-shibboleth2.xml
doc/main.css
schemas/shibboleth-2.0-native-sp-config.xsd
shibsp/Makefile.am
shibsp/handler/Handler.h
shibsp/handler/impl/AbstractHandler.cpp
shibsp/handler/impl/AttributeCheckerHandler.cpp [new file with mode: 0644]
shibsp/impl/XMLAccessControl.cpp
shibsp/shibsp-lite.vcxproj
shibsp/shibsp-lite.vcxproj.filters
shibsp/shibsp.vcxproj
shibsp/shibsp.vcxproj.filters

index 9468764..7b29ba4 100644 (file)
@@ -36,6 +36,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Config", "Config", "{2543BC
                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
@@ -55,7 +56,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Config", "Config", "{2543BC
                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
index 5224c4e..8028341 100644 (file)
@@ -42,6 +42,7 @@ CONFIGFILES = \
        console.logger \
        syslog.logger \
        accessError.html \
+    attrChecker.html \
        sessionError.html \
        metadataError.html \
        bindingTemplate.html \
diff --git a/configs/attrChecker.html b/configs/attrChecker.html
new file mode 100644 (file)
index 0000000..c382db6
--- /dev/null
@@ -0,0 +1,47 @@
+<?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>
index cf952dc..dee3782 100644 (file)
@@ -93,6 +93,8 @@
     -->
     <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"/>
 
index 134cb66..ccea6dc 100644 (file)
@@ -66,6 +66,7 @@
         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"/>
         
index c32f66c..448f3a7 100644 (file)
         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"/>
         
index a11cebe..ee6493d 100644 (file)
@@ -29,11 +29,25 @@ h2 {
        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;
+}
index db8a688..045bf39 100644 (file)
     <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>
index d2b2358..f525df9 100644 (file)
@@ -124,6 +124,7 @@ common_sources = \
        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 \
index 5c93e1e..f64cfed 100644 (file)
@@ -158,6 +158,9 @@ namespace shibsp {
     /** 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"
 
index 9136242..c1b0f25 100644 (file)
@@ -81,6 +81,7 @@ namespace shibsp {
     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;
@@ -121,6 +122,7 @@ void SHIBSP_API shibsp::registerHandlers()
     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);
diff --git a/shibsp/handler/impl/AttributeCheckerHandler.cpp b/shibsp/handler/impl/AttributeCheckerHandler.cpp
new file mode 100644 (file)
index 0000000..da37277
--- /dev/null
@@ -0,0 +1,208 @@
+/**
+ * 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));
+}
index 059ab09..e5d49f1 100644 (file)
@@ -139,6 +139,7 @@ namespace shibsp {
     }
 
     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);
@@ -154,20 +155,23 @@ Rule::Rule(const DOMElement* e) : m_alias(XMLHelper::getAttrString(e, nullptr, r
 {
     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
@@ -219,6 +223,10 @@ AccessControl::aclresult_t Rule::authorized(const SPRequest& request, const Sess
         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();
@@ -407,8 +415,16 @@ pair<bool,DOMElement*> XMLAccessControl::background_load()
     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))
index a7449b7..6f4f036 100644 (file)
     <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
index 8d81e3d..a3c5028 100644 (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
index 23b79eb..f878453 100644 (file)
     <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
index d0bd18c..5419a76 100644 (file)
     <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