XML access control plugin
authorScott Cantor <cantor.2@osu.edu>
Mon, 24 Oct 2005 04:05:17 +0000 (04:05 +0000)
committerScott Cantor <cantor.2@osu.edu>
Mon, 24 Oct 2005 04:05:17 +0000 (04:05 +0000)
xmlproviders/Makefile.am
xmlproviders/XML.cpp
xmlproviders/XMLAccessControl.cpp [new file with mode: 0644]
xmlproviders/XMLProviders.cpp
xmlproviders/internal.h

index 1b5ff77..60333ae 100644 (file)
@@ -8,13 +8,15 @@ plugin_LTLIBRARIES = xmlproviders.la
 noinst_HEADERS = internal.h
 
 xmlproviders_la_LIBADD = \
-       ../shib/libshib.la
+       $(top_builddir)/shib/libshib.la \
+       $(top_builddir)/shib-target/libshib-target.la
 
 xmlproviders_la_SOURCES = \
        CredResolvers.cpp \
        TargetedID.cpp \
        XML.cpp \
        XMLAAP.cpp \
+       XMLAccessControl.cpp \
        XMLCredentials.cpp \
        XMLMetadata.cpp \
        XMLProviders.cpp \
index 5857cf5..6f362ee 100644 (file)
@@ -24,6 +24,8 @@
 
 #include "internal.h"
 
+const char XML::XMLAccessControlType[] =    "edu.internet2.middleware.shibboleth.sp.provider.XMLAccessControl";
+
 // Namespace and schema string literals
 
 const XMLCh XML::SHIB_NS[] = // urn:mace:shibboleth:1.0
@@ -617,3 +619,23 @@ const XMLCh XML::Literals::WantAssertionsSigned[] =
   chLatin_A, chLatin_s, chLatin_s, chLatin_e, chLatin_r, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chLatin_s,
   chLatin_S, chLatin_i, chLatin_g, chLatin_n, chLatin_e, chLatin_d, chNull
 };
+
+const XMLCh XML::Literals::AccessControl[] =
+{ chLatin_A, chLatin_c, chLatin_c, chLatin_e, chLatin_s, chLatin_s,
+  chLatin_C, chLatin_o, chLatin_n, chLatin_t, chLatin_r, chLatin_o, chLatin_l, chNull
+};
+
+const XMLCh XML::Literals::AND[] =
+{ chLatin_A, chLatin_N, chLatin_D, chNull };
+
+const XMLCh XML::Literals::NOT[] =
+{ chLatin_N, chLatin_O, chLatin_T, chNull };
+
+const XMLCh XML::Literals::OR[] =
+{ chLatin_O, chLatin_R, chNull };
+
+const XMLCh XML::Literals::require[] =
+{ chLatin_r, chLatin_e, chLatin_q, chLatin_u, chLatin_i, chLatin_r, chLatin_e, chNull };
+
+const XMLCh XML::Literals::Rule[] =
+{ chLatin_R, chLatin_u, chLatin_l, chLatin_e, chNull };
diff --git a/xmlproviders/XMLAccessControl.cpp b/xmlproviders/XMLAccessControl.cpp
new file mode 100644 (file)
index 0000000..be0e744
--- /dev/null
@@ -0,0 +1,287 @@
+/*
+ *  Copyright 2001-2005 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.
+ */
+
+/* XMLAccessControl.cpp - an XML-based access control syntax
+
+   Scott Cantor
+   10/25/05
+*/
+
+#include "internal.h"
+#include <shib-target/shib-target.h>
+
+#include <log4cpp/Category.hh>
+
+#ifndef HAVE_STRCASECMP
+# define strcasecmp stricmp
+#endif
+
+using namespace std;
+using namespace log4cpp;
+using namespace saml;
+using namespace shibboleth;
+using namespace shibtarget;
+
+namespace {
+    struct IAuthz {
+        virtual ~IAuthz() {}
+        virtual bool authorized(ShibTarget* st, ISessionCacheEntry* entry) const=0;
+    };
+    
+    class Rule : public IAuthz
+    {
+    public:
+        Rule(const DOMElement* e);
+        ~Rule() {}
+        bool authorized(ShibTarget* st, ISessionCacheEntry* entry) const;
+    
+    private:
+        string m_alias;
+        vector <string> m_vals;
+    };
+    
+    class Operator : public IAuthz
+    {
+    public:
+        Operator(const DOMElement* e);
+        ~Operator();
+        bool authorized(ShibTarget* st, ISessionCacheEntry* entry) const;
+        
+    private:
+        enum operator_t { OP_NOT, OP_AND, OP_OR } m_op;
+        vector<IAuthz*> m_operands;
+    };
+
+    class XMLAccessControlImpl : public ReloadableXMLFileImpl
+    {
+    public:
+        XMLAccessControlImpl(const char* pathname) : ReloadableXMLFileImpl(pathname) { init(); }
+        XMLAccessControlImpl(const DOMElement* e) : ReloadableXMLFileImpl(e) { init(); }
+        void init();
+        ~XMLAccessControlImpl() {delete m_rootAuthz;}
+        
+        IAuthz* m_rootAuthz;
+    };
+
+    class XMLAccessControl : public IAccessControl, public ReloadableXMLFile
+    {
+    public:
+        XMLAccessControl(const DOMElement* e) : ReloadableXMLFile(e) {}
+        ~XMLAccessControl() {}
+
+        virtual bool authorized(ShibTarget* st, ISessionCacheEntry* entry) const;
+
+    protected:
+        virtual ReloadableXMLFileImpl* newImplementation(const char* pathname, bool first=true) const;
+        virtual ReloadableXMLFileImpl* newImplementation(const DOMElement* e, bool first=true) const;
+    };
+}
+
+IPlugIn* XMLAccessControlFactory(const DOMElement* e)
+{
+    auto_ptr<XMLAccessControl> a(new XMLAccessControl(e));
+    a->getImplementation();
+    return a.release();
+}
+
+Rule::Rule(const DOMElement* e)
+{
+    auto_ptr_char req(e->getAttributeNS(NULL,SHIB_L(require)));
+    if (!req.get() || !*req.get())
+        throw MalformedException("Access control rule missing require attribute");
+    m_alias=req.get();
+    
+    auto_ptr_char vals(e->hasChildNodes() ? e->getFirstChild()->getNodeValue() : NULL);
+#ifdef HAVE_STRTOK_R
+    char* pos=NULL;
+    const char* token=strtok_r(const_cast<char*>(vals.get()),"/",&pos);
+#else
+    const char* token=strtok(const_cast<char*>(vals.get()),"/");
+#endif
+    while (token) {
+        m_vals.push_back(token);
+#ifdef HAVE_STRTOK_R
+        token=strtok_r(NULL,"/",&pos);
+#else
+        token=strtok(NULL,"/");
+#endif
+    }
+}
+
+bool Rule::authorized(ShibTarget* st, ISessionCacheEntry* entry) const
+{
+    // Map alias in rule to the attribute.
+    Iterator<IAAP*> provs=st->getApplication()->getAAPProviders();
+    AAP wrapper(provs,m_alias.c_str());
+    if (wrapper.fail()) {
+        st->log(ShibTarget::LogLevelWarn, string("AccessControl plugin didn't recognize rule (") + m_alias + "), check AAP for corresponding Alias");
+        return false;
+    }
+    
+    // Find the corresponding attribute. This isn't very efficient...
+    ISessionCacheEntry::CachedResponse cr=entry->getResponse();
+    Iterator<SAMLAssertion*> a_iter(cr.filtered ? cr.filtered->getAssertions() : EMPTY(SAMLAssertion*));
+    while (a_iter.hasNext()) {
+        SAMLAssertion* assert=a_iter.next();
+        Iterator<SAMLStatement*> statements=assert->getStatements();
+        while (statements.hasNext()) {
+            SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
+            if (!astate)
+                continue;
+            Iterator<SAMLAttribute*> attrs=astate->getAttributes();
+            while (attrs.hasNext()) {
+                SAMLAttribute* attr=attrs.next();
+                if (!XMLString::compareString(attr->getName(),wrapper->getName()) &&
+                    !XMLString::compareString(attr->getNamespace(),wrapper->getNamespace())) {
+                    // Now we have to intersect the attribute's values against the rule's list.
+                    Iterator<string> vals=attr->getSingleByteValues();
+                    if (!vals.hasNext())
+                        return false;
+                    for (vector<string>::const_iterator ival=m_vals.begin(); ival!=m_vals.end(); ival++) {
+                        vals.reset();
+                        while (vals.hasNext()) {
+                            const string& v=vals.next();
+                            if ((wrapper->getCaseSensitive() && v == *ival) || (!wrapper->getCaseSensitive() && !strcasecmp(v.c_str(),ival->c_str()))) {
+                                st->log(ShibTarget::LogLevelDebug, string("XMLAccessControl plugin expecting " + *ival + ", authz granted"));
+                                return true;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    
+    return false;
+}
+
+Operator::Operator(const DOMElement* e)
+{
+    if (saml::XML::isElementNamed(e,shibtarget::XML::SHIBTARGET_NS,SHIB_L(NOT)))
+        m_op=OP_NOT;
+    else if (saml::XML::isElementNamed(e,shibtarget::XML::SHIBTARGET_NS,SHIB_L(AND)))
+        m_op=OP_AND;
+    else if (saml::XML::isElementNamed(e,shibtarget::XML::SHIBTARGET_NS,SHIB_L(OR)))
+        m_op=OP_OR;
+    else
+        throw MalformedException("Unrecognized operator in access control rule");
+    
+    try {
+        e=saml::XML::getFirstChildElement(e);
+        if (saml::XML::isElementNamed(e,shibtarget::XML::SHIBTARGET_NS,SHIB_L(Rule)))
+            m_operands.push_back(new Rule(e));
+        else
+            m_operands.push_back(new Operator(e));
+        
+        if (m_op==OP_NOT)
+            return;
+        
+        e=saml::XML::getNextSiblingElement(e);
+        while (e) {
+            if (saml::XML::isElementNamed(e,shibtarget::XML::SHIBTARGET_NS,SHIB_L(Rule)))
+                m_operands.push_back(new Rule(e));
+            else
+                m_operands.push_back(new Operator(e));
+            e=saml::XML::getNextSiblingElement(e);
+        }
+    }
+    catch (SAMLException&) {
+        this->~Operator();
+        throw;
+    }
+}
+
+Operator::~Operator()
+{
+    for (vector<IAuthz*>::iterator i=m_operands.begin(); i!=m_operands.end(); i++)
+        delete *i;
+}
+
+bool Operator::authorized(ShibTarget* st, ISessionCacheEntry* entry) const
+{
+    switch (m_op) {
+        case OP_NOT:
+            return !m_operands[0]->authorized(st,entry);
+        
+        case OP_AND:
+        {
+            for (vector<IAuthz*>::const_iterator i=m_operands.begin(); i!=m_operands.end(); i++) {
+                if (!(*i)->authorized(st,entry))
+                    return false;
+            }
+            return true;
+        }
+        
+        case OP_OR:
+        {
+            for (vector<IAuthz*>::const_iterator i=m_operands.begin(); i!=m_operands.end(); i++) {
+                if ((*i)->authorized(st,entry))
+                    return true;
+            }
+            return false;
+        }
+    }
+    st->log(ShibTarget::LogLevelWarn,"Unknown operation in access control policy, denying access");
+    return false;
+}
+
+void XMLAccessControlImpl::init()
+{
+#ifdef _DEBUG
+    NDC ndc("init");
+#endif
+    Category* log=&Category::getInstance(XMLPROVIDERS_LOGCAT".AccessControl");
+
+    try {
+        // We need to move below the AccessControl root element if the policy is in a separate file.
+        // Unlike most of the plugins, an inline policy will end up handing us the first inline
+        // content element, and not the outer wrapper.
+        const DOMElement* rootElement=ReloadableXMLFileImpl::m_root;
+        if (saml::XML::isElementNamed(rootElement,shibtarget::XML::SHIBTARGET_NS,SHIB_L(AccessControl)))
+            rootElement = saml::XML::getFirstChildElement(rootElement);
+        
+        if (saml::XML::isElementNamed(rootElement,shibtarget::XML::SHIBTARGET_NS,SHIB_L(Rule)))
+            m_rootAuthz=new Rule(rootElement);
+        else
+            m_rootAuthz=new Operator(rootElement);
+    }
+    catch (SAMLException& e) {
+        log->errorStream() << "Error while parsing access control configuration: " << e.what() << CategoryStream::ENDLINE;
+        throw;
+    }
+#ifndef _DEBUG
+    catch (...)
+    {
+        log->error("Unexpected error while parsing access control configuration");
+        throw;
+    }
+#endif
+}
+
+ReloadableXMLFileImpl* XMLAccessControl::newImplementation(const char* pathname, bool first) const
+{
+    return new XMLAccessControlImpl(pathname);
+}
+
+ReloadableXMLFileImpl* XMLAccessControl::newImplementation(const DOMElement* e, bool first) const
+{
+    return new XMLAccessControlImpl(e);
+}
+
+bool XMLAccessControl::authorized(ShibTarget* st, ISessionCacheEntry* entry) const
+{
+    return static_cast<XMLAccessControlImpl*>(getImplementation())->m_rootAuthz->authorized(st,entry);
+}
index 89a573a..e7ea636 100644 (file)
@@ -45,6 +45,7 @@ PlugManager::Factory XMLTrustFactory;
 PlugManager::Factory XMLCredentialsFactory;
 PlugManager::Factory XMLAAPFactory;
 PlugManager::Factory FileCredResolverFactory;
+PlugManager::Factory XMLAccessControlFactory;
 
 extern "C" int XML_EXPORTS saml_extension_init(void*)
 {
@@ -68,6 +69,7 @@ extern "C" int XML_EXPORTS saml_extension_init(void*)
     conf.getPlugMgr().regFactory("edu.internet2.middleware.shibboleth.common.Credentials.FileCredentialResolver",&FileCredResolverFactory);
     conf.getPlugMgr().regFactory("edu.internet2.middleware.shibboleth.aap.provider.XMLAAP",&XMLAAPFactory);
     conf.getPlugMgr().regFactory("edu.internet2.middleware.shibboleth.target.provider.XMLAAP",&XMLAAPFactory);
+    conf.getPlugMgr().regFactory(::XML::XMLAccessControlType,&XMLAccessControlFactory);
 
     return 0;
 }
@@ -85,6 +87,7 @@ extern "C" void XML_EXPORTS saml_extension_term()
     conf.getPlugMgr().unregFactory("edu.internet2.middleware.shibboleth.common.Credentials.FileCredentialResolver");
     conf.getPlugMgr().unregFactory("edu.internet2.middleware.shibboleth.aap.provider.XMLAAP");
     conf.getPlugMgr().unregFactory("edu.internet2.middleware.shibboleth.target.provider.XMLAAP");
+    conf.getPlugMgr().unregFactory(::XML::XMLAccessControlType);
 }
 
 void log_openssl()
index d9b38ac..f46c682 100644 (file)
@@ -65,6 +65,8 @@ public:
     static const XMLCh XMLSIG_RETMETHOD_RAWX509[];  // http://www.w3.org/2000/09/xmldsig#rawX509Certificate
     static const XMLCh XMLSIG_RETMETHOD_RAWX509CRL[]; // http://www.w3.org/2000/09/xmldsig-more#rawX509CRL
 
+    static const char XMLAccessControlType[];   // Proprietary but portable XML authz syntax
+
     struct Literals
     {
         // old metadata constants
@@ -202,6 +204,14 @@ public:
         static const XMLCh validUntil[];
         static const XMLCh WantAuthnRequestsSigned[];
         static const XMLCh WantAssertionsSigned[];
+
+        // access control constants
+        static const XMLCh AccessControl[];
+        static const XMLCh AND[];
+        static const XMLCh NOT[];
+        static const XMLCh OR[];
+        static const XMLCh require[];
+        static const XMLCh Rule[];
     };
 };