Add regex support to acl plugin.
[shibboleth/sp.git] / shibsp / impl / XMLAccessControl.cpp
index e1f1383..939ea68 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
- *  Copyright 2001-2005 Internet2\r
+ *  Copyright 2001-2007 Internet2\r
  * \r
  * Licensed under the Apache License, Version 2.0 (the "License");\r
  * you may not use this file except in compliance with the License.\r
 #include "exceptions.h"\r
 #include "AccessControl.h"\r
 #include "SessionCache.h"\r
+#include "SPRequest.h"\r
+#include "attribute/Attribute.h"\r
 \r
 #include <xmltooling/util/ReloadableXMLFile.h>\r
 #include <xmltooling/util/XMLHelper.h>\r
 #include <xercesc/util/XMLUniDefs.hpp>\r
+#include <xercesc/util/regx/RegularExpression.hpp>\r
 \r
 #ifndef HAVE_STRCASECMP\r
 # define strcasecmp _stricmp\r
@@ -48,13 +51,32 @@ namespace {
         Lockable* lock() {return this;}\r
         void unlock() {}\r
 \r
-        bool authorized(const SPRequest& request, const Session* session) const;\r
+        aclresult_t authorized(const SPRequest& request, const Session* session) const;\r
     \r
     private:\r
         string m_alias;\r
         vector <string> m_vals;\r
     };\r
     \r
+    class RuleRegex : public AccessControl\r
+    {\r
+    public:\r
+        RuleRegex(const DOMElement* e);\r
+        ~RuleRegex() {\r
+            delete m_re;\r
+        }\r
+        \r
+        Lockable* lock() {return this;}\r
+        void unlock() {}\r
+\r
+        aclresult_t authorized(const SPRequest& request, const Session* session) const;\r
+        \r
+    private:\r
+        string m_alias;\r
+        auto_arrayptr<char> m_exp;\r
+        RegularExpression* m_re;\r
+    };\r
+    \r
     class Operator : public AccessControl\r
     {\r
     public:\r
@@ -64,7 +86,7 @@ namespace {
         Lockable* lock() {return this;}\r
         void unlock() {}\r
 \r
-        bool authorized(const SPRequest& request, const Session* session) const;\r
+        aclresult_t authorized(const SPRequest& request, const Session* session) const;\r
         \r
     private:\r
         enum operator_t { OP_NOT, OP_AND, OP_OR } m_op;\r
@@ -79,7 +101,8 @@ namespace {
     class XMLAccessControl : public AccessControl, public ReloadableXMLFile\r
     {\r
     public:\r
-        XMLAccessControl(const DOMElement* e) : ReloadableXMLFile(e), m_rootAuthz(NULL) {\r
+        XMLAccessControl(const DOMElement* e)\r
+                : ReloadableXMLFile(e, Category::getInstance(SHIBSP_LOGCAT".AccessControl")), m_rootAuthz(NULL) {\r
             load(); // guarantees an exception or the policy is loaded\r
         }\r
         \r
@@ -87,7 +110,7 @@ namespace {
             delete m_rootAuthz;\r
         }\r
 \r
-        bool authorized(const SPRequest& request, const Session* session) const;\r
+        aclresult_t authorized(const SPRequest& request, const Session* session) const;\r
 \r
     protected:\r
         pair<bool,DOMElement*> load();\r
@@ -105,94 +128,215 @@ namespace {
         return new XMLAccessControl(e);\r
     }\r
 \r
-    static const XMLCh _AccessControl[] =    UNICODE_LITERAL_13(A,c,c,e,s,s,C,o,n,t,r,o,l);\r
+    static const XMLCh _AccessControl[] =   UNICODE_LITERAL_13(A,c,c,e,s,s,C,o,n,t,r,o,l);\r
+    static const XMLCh ignoreCase[] =       UNICODE_LITERAL_10(i,g,n,o,r,e,C,a,s,e);\r
+    static const XMLCh ignoreOption[] =     UNICODE_LITERAL_1(i);\r
+    static const XMLCh _list[] =            UNICODE_LITERAL_4(l,i,s,t);\r
     static const XMLCh require[] =          UNICODE_LITERAL_7(r,e,q,u,i,r,e);\r
     static const XMLCh NOT[] =              UNICODE_LITERAL_3(N,O,T);\r
     static const XMLCh AND[] =              UNICODE_LITERAL_3(A,N,D);\r
     static const XMLCh OR[] =               UNICODE_LITERAL_2(O,R);\r
     static const XMLCh _Rule[] =            UNICODE_LITERAL_4(R,u,l,e);\r
+    static const XMLCh _RuleRegex[] =       UNICODE_LITERAL_9(R,u,l,e,R,e,g,e,x);\r
 }\r
 \r
 void SHIBSP_API shibsp::registerAccessControls()\r
 {\r
-    SPConfig::getConfig().AccessControlManager.registerFactory(XML_ACCESS_CONTROL, XMLAccessControlFactory);\r
+    SPConfig& conf=SPConfig::getConfig();\r
+    conf.AccessControlManager.registerFactory(XML_ACCESS_CONTROL, XMLAccessControlFactory);\r
+    conf.AccessControlManager.registerFactory("edu.internet2.middleware.shibboleth.sp.provider.XMLAccessControl", XMLAccessControlFactory);\r
 }\r
 \r
 Rule::Rule(const DOMElement* e)\r
 {\r
-    xmltooling::auto_ptr_char req(e->getAttributeNS(NULL,require));\r
+    auto_ptr_char req(e->getAttributeNS(NULL,require));\r
     if (!req.get() || !*req.get())\r
         throw ConfigurationException("Access control rule missing require attribute");\r
     m_alias=req.get();\r
+\r
+    auto_arrayptr<char> vals(toUTF8(e->hasChildNodes() ? e->getFirstChild()->getNodeValue() : NULL));\r
+    if (!vals.get())\r
+        return;\r
+    \r
+    const XMLCh* flag = e->getAttributeNS(NULL,_list);\r
+    if (flag && (*flag == chLatin_f || *flag == chDigit_0)) {\r
+        if (*vals.get())\r
+            m_vals.push_back(vals.get());\r
+        return;\r
+    }\r
     \r
-    xmltooling::auto_ptr_char vals(e->hasChildNodes() ? e->getFirstChild()->getNodeValue() : NULL);\r
 #ifdef HAVE_STRTOK_R\r
     char* pos=NULL;\r
-    const char* token=strtok_r(const_cast<char*>(vals.get()),"/",&pos);\r
+    const char* token=strtok_r(const_cast<char*>(vals.get())," ",&pos);\r
 #else\r
-    const char* token=strtok(const_cast<char*>(vals.get()),"/");\r
+    const char* token=strtok(const_cast<char*>(vals.get())," ");\r
 #endif\r
     while (token) {\r
         m_vals.push_back(token);\r
 #ifdef HAVE_STRTOK_R\r
-        token=strtok_r(NULL,"/",&pos);\r
+        token=strtok_r(NULL," ",&pos);\r
 #else\r
-        token=strtok(NULL,"/");\r
+        token=strtok(NULL," ");\r
 #endif\r
     }\r
 }\r
 \r
-bool Rule::authorized(const SPRequest& request, const Session* session) const\r
+AccessControl::aclresult_t Rule::authorized(const SPRequest& request, const Session* session) const\r
 {\r
-    /*\r
-    TODO: port...\r
+    // We can make this more complex later using pluggable comparison functions,\r
+    // but for now, just a straight port to the new Attribute API.\r
+\r
     // Map alias in rule to the attribute.\r
-    Iterator<IAAP*> provs=st->getApplication()->getAAPProviders();\r
-    AAP wrapper(provs,m_alias.c_str());\r
-    if (wrapper.fail()) {\r
-        st->log(ShibTarget::LogLevelWarn, string("AccessControl plugin didn't recognize rule (") + m_alias + "), check AAP for corresponding Alias");\r
-        return false;\r
+    if (!session) {\r
+        request.log(SPRequest::SPWarn, "AccessControl plugin not given a valid session to evaluate, are you using lazy sessions?");\r
+        return shib_acl_false;\r
     }\r
-    else if (!entry) {\r
-        st->log(ShibTarget::LogLevelWarn, "AccessControl plugin not given a valid session to evaluate, are you using lazy sessions?");\r
-        return false;\r
+    \r
+    if (m_alias == "valid-user") {\r
+        if (session) {\r
+            request.log(SPRequest::SPDebug,"AccessControl plugin accepting valid-user based on active session");\r
+            return shib_acl_true;\r
+        }\r
+        return shib_acl_false;\r
+    }\r
+    if (m_alias == "user") {\r
+        for (vector<string>::const_iterator i=m_vals.begin(); i!=m_vals.end(); ++i) {\r
+            if (*i == request.getRemoteUser()) {\r
+                request.log(SPRequest::SPDebug, string("AccessControl plugin expecting REMOTE_USER (") + *i + "), authz granted");\r
+                return shib_acl_true;\r
+            }\r
+        }\r
+        return shib_acl_false;\r
+    }\r
+    else if (m_alias == "authnContextClassRef") {\r
+        const char* ref = session->getAuthnContextClassRef();\r
+        for (vector<string>::const_iterator i=m_vals.begin(); ref && i!=m_vals.end(); ++i) {\r
+            if (!strcmp(i->c_str(),ref)) {\r
+                request.log(SPRequest::SPDebug, string("AccessControl plugin expecting authnContextClassRef (") + *i + "), authz granted");\r
+                return shib_acl_true;\r
+            }\r
+        }\r
+        return shib_acl_false;\r
+    }\r
+    else if (m_alias == "authnContextDeclRef") {\r
+        const char* ref = session->getAuthnContextDeclRef();\r
+        for (vector<string>::const_iterator i=m_vals.begin(); ref && i!=m_vals.end(); ++i) {\r
+            if (!strcmp(i->c_str(),ref)) {\r
+                request.log(SPRequest::SPDebug, string("AccessControl plugin expecting authnContextDeclRef (") + *i + "), authz granted");\r
+                return shib_acl_true;\r
+            }\r
+        }\r
+        return shib_acl_false;\r
+    }\r
+\r
+    // Find the attribute(s) matching the require rule.\r
+    pair<multimap<string,const Attribute*>::const_iterator, multimap<string,const Attribute*>::const_iterator> attrs =\r
+        session->getIndexedAttributes().equal_range(m_alias);\r
+    if (attrs.first == attrs.second) {\r
+        request.log(SPRequest::SPWarn, string("rule requires attribute (") + m_alias + "), not found in session");\r
+        return shib_acl_false;\r
+    }\r
+\r
+    for (; attrs.first != attrs.second; ++attrs.first) {\r
+        bool caseSensitive = attrs.first->second->isCaseSensitive();\r
+\r
+        // Now we have to intersect the attribute's values against the rule's list.\r
+        const vector<string>& vals = attrs.first->second->getSerializedValues();\r
+        for (vector<string>::const_iterator i=m_vals.begin(); i!=m_vals.end(); ++i) {\r
+            for (vector<string>::const_iterator j=vals.begin(); j!=vals.end(); ++j) {\r
+                if ((caseSensitive && *i == *j) || (!caseSensitive && !strcasecmp(i->c_str(),j->c_str()))) {\r
+                    request.log(SPRequest::SPDebug, string("AccessControl plugin expecting (") + *j + "), authz granted");\r
+                    return shib_acl_true;\r
+                }\r
+            }\r
+        }\r
+    }\r
+\r
+    return shib_acl_false;\r
+}\r
+\r
+RuleRegex::RuleRegex(const DOMElement* e) : m_exp(toUTF8(e->hasChildNodes() ? e->getFirstChild()->getNodeValue() : NULL))\r
+{\r
+    auto_ptr_char req(e->getAttributeNS(NULL,require));\r
+    if (!req.get() || !*req.get() || !m_exp.get() || !*m_exp.get())\r
+        throw ConfigurationException("Access control rule missing require attribute or element content.");\r
+    m_alias=req.get();\r
+    \r
+    const XMLCh* flag = e->getAttributeNS(NULL,ignoreCase);\r
+    bool ignore = (flag && (*flag == chLatin_t || *flag == chDigit_1));\r
+    try {\r
+        m_re = new RegularExpression(e->getFirstChild()->getNodeValue(), (ignore ? ignoreOption : &chNull)); \r
+    }\r
+    catch (XMLException& ex) {\r
+        auto_ptr_char tmp(ex.getMessage());\r
+        throw ConfigurationException("Caught exception while parsing RuleRegex regular expression: $1", params(1,tmp.get()));\r
+    }\r
+}\r
+\r
+AccessControl::aclresult_t RuleRegex::authorized(const SPRequest& request, const Session* session) const\r
+{\r
+    // Map alias in rule to the attribute.\r
+    if (!session) {\r
+        request.log(SPRequest::SPWarn, "AccessControl plugin not given a valid session to evaluate, are you using lazy sessions?");\r
+        return shib_acl_false;\r
     }\r
     \r
-    // Find the corresponding attribute. This isn't very efficient...\r
-    pair<const char*,const SAMLResponse*> filtered=entry->getFilteredTokens(false,true);\r
-    Iterator<SAMLAssertion*> a_iter(filtered.second ? filtered.second->getAssertions() : EMPTY(SAMLAssertion*));\r
-    while (a_iter.hasNext()) {\r
-        SAMLAssertion* assert=a_iter.next();\r
-        Iterator<SAMLStatement*> statements=assert->getStatements();\r
-        while (statements.hasNext()) {\r
-            SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());\r
-            if (!astate)\r
-                continue;\r
-            Iterator<SAMLAttribute*> attrs=astate->getAttributes();\r
-            while (attrs.hasNext()) {\r
-                SAMLAttribute* attr=attrs.next();\r
-                if (!XMLString::compareString(attr->getName(),wrapper->getName()) &&\r
-                    !XMLString::compareString(attr->getNamespace(),wrapper->getNamespace())) {\r
-                    // Now we have to intersect the attribute's values against the rule's list.\r
-                    Iterator<string> vals=attr->getSingleByteValues();\r
-                    if (!vals.hasNext())\r
-                        return false;\r
-                    for (vector<string>::const_iterator ival=m_vals.begin(); ival!=m_vals.end(); ival++) {\r
-                        vals.reset();\r
-                        while (vals.hasNext()) {\r
-                            const string& v=vals.next();\r
-                            if ((wrapper->getCaseSensitive() && v == *ival) || (!wrapper->getCaseSensitive() && !strcasecmp(v.c_str(),ival->c_str()))) {\r
-                                st->log(ShibTarget::LogLevelDebug, string("XMLAccessControl plugin expecting " + *ival + ", authz granted"));\r
-                                return true;\r
-                            }\r
-                        }\r
-                    }\r
+    if (m_alias == "valid-user") {\r
+        if (session) {\r
+            request.log(SPRequest::SPDebug,"AccessControl plugin accepting valid-user based on active session");\r
+            return shib_acl_true;\r
+        }\r
+        return shib_acl_false;\r
+    }\r
+\r
+    try {\r
+        if (m_alias == "user") {\r
+            if (m_re->matches(request.getRemoteUser().c_str())) {\r
+                request.log(SPRequest::SPDebug, string("AccessControl plugin expecting REMOTE_USER (") + m_exp.get() + "), authz granted");\r
+                return shib_acl_true;\r
+            }\r
+            return shib_acl_false;\r
+        }\r
+        else if (m_alias == "authnContextClassRef") {\r
+            if (session->getAuthnContextClassRef() && m_re->matches(session->getAuthnContextClassRef())) {\r
+                request.log(SPRequest::SPDebug, string("AccessControl plugin expecting authnContextClassRef (") + m_exp.get() + "), authz granted");\r
+                return shib_acl_true;\r
+            }\r
+            return shib_acl_false;\r
+        }\r
+        else if (m_alias == "authnContextDeclRef") {\r
+            if (session->getAuthnContextDeclRef() && m_re->matches(session->getAuthnContextDeclRef())) {\r
+                request.log(SPRequest::SPDebug, string("AccessControl plugin expecting authnContextDeclRef (") + m_exp.get() + "), authz granted");\r
+                return shib_acl_true;\r
+            }\r
+            return shib_acl_false;\r
+        }\r
+\r
+        // Find the attribute(s) matching the require rule.\r
+        pair<multimap<string,const Attribute*>::const_iterator, multimap<string,const Attribute*>::const_iterator> attrs =\r
+            session->getIndexedAttributes().equal_range(m_alias);\r
+        if (attrs.first == attrs.second) {\r
+            request.log(SPRequest::SPWarn, string("rule requires attribute (") + m_alias + "), not found in session");\r
+            return shib_acl_false;\r
+        }\r
+\r
+        for (; attrs.first != attrs.second; ++attrs.first) {\r
+            // Now we have to intersect the attribute's values against the regular expression.\r
+            const vector<string>& vals = attrs.first->second->getSerializedValues();\r
+            for (vector<string>::const_iterator j=vals.begin(); j!=vals.end(); ++j) {\r
+                if (m_re->matches(j->c_str())) {\r
+                    request.log(SPRequest::SPDebug, string("AccessControl plugin expecting (") + m_exp.get() + "), authz granted");\r
+                    return shib_acl_true;\r
                 }\r
             }\r
         }\r
     }\r
-    */\r
-    return true;\r
+    catch (XMLException& ex) {\r
+        auto_ptr_char tmp(ex.getMessage());\r
+        request.log(SPRequest::SPError, string("caught exception while parsing RuleRegex regular expression: ") + tmp.get());\r
+    }\r
+    \r
+    return shib_acl_false;\r
 }\r
 \r
 Operator::Operator(const DOMElement* e)\r
@@ -210,6 +354,8 @@ Operator::Operator(const DOMElement* e)
         e=XMLHelper::getFirstChildElement(e);\r
         if (XMLString::equals(e->getLocalName(),_Rule))\r
             m_operands.push_back(new Rule(e));\r
+        else if (XMLString::equals(e->getLocalName(),_RuleRegex))\r
+            m_operands.push_back(new RuleRegex(e));\r
         else\r
             m_operands.push_back(new Operator(e));\r
         \r
@@ -220,6 +366,8 @@ Operator::Operator(const DOMElement* e)
         while (e) {\r
             if (XMLString::equals(e->getLocalName(),_Rule))\r
                 m_operands.push_back(new Rule(e));\r
+            else if (XMLString::equals(e->getLocalName(),_RuleRegex))\r
+                m_operands.push_back(new RuleRegex(e));\r
             else\r
                 m_operands.push_back(new Operator(e));\r
             e=XMLHelper::getNextSiblingElement(e);\r
@@ -236,32 +384,39 @@ Operator::~Operator()
     for_each(m_operands.begin(),m_operands.end(),xmltooling::cleanup<AccessControl>());\r
 }\r
 \r
-bool Operator::authorized(const SPRequest& request, const Session* session) const\r
+AccessControl::aclresult_t Operator::authorized(const SPRequest& request, const Session* session) const\r
 {\r
     switch (m_op) {\r
         case OP_NOT:\r
-            return !m_operands[0]->authorized(request,session);\r
+            switch (m_operands.front()->authorized(request,session)) {\r
+                case shib_acl_true:\r
+                    return shib_acl_false;\r
+                case shib_acl_false:\r
+                    return shib_acl_true;\r
+                default:\r
+                    return shib_acl_indeterminate;\r
+            }\r
         \r
         case OP_AND:\r
         {\r
             for (vector<AccessControl*>::const_iterator i=m_operands.begin(); i!=m_operands.end(); i++) {\r
-                if (!(*i)->authorized(request,session))\r
-                    return false;\r
+                if ((*i)->authorized(request,session) != shib_acl_true)\r
+                    return shib_acl_false;\r
             }\r
-            return true;\r
+            return shib_acl_true;\r
         }\r
         \r
         case OP_OR:\r
         {\r
             for (vector<AccessControl*>::const_iterator i=m_operands.begin(); i!=m_operands.end(); i++) {\r
-                if ((*i)->authorized(request,session))\r
-                    return true;\r
+                if ((*i)->authorized(request,session) == shib_acl_true)\r
+                    return shib_acl_true;\r
             }\r
-            return false;\r
+            return shib_acl_false;\r
         }\r
     }\r
-    //st->log(ShibTarget::LogLevelWarn,"Unknown operation in access control policy, denying access");\r
-    return false;\r
+    request.log(SPRequest::SPWarn,"unknown operation in access control policy, denying access");\r
+    return shib_acl_false;\r
 }\r
 \r
 pair<bool,DOMElement*> XMLAccessControl::load()\r
@@ -279,6 +434,8 @@ pair<bool,DOMElement*> XMLAccessControl::load()
     AccessControl* authz;\r
     if (XMLString::equals(raw.second->getLocalName(),_Rule))\r
         authz=new Rule(raw.second);\r
+    else if (XMLString::equals(raw.second->getLocalName(),_RuleRegex))\r
+        authz=new RuleRegex(raw.second);\r
     else\r
         authz=new Operator(raw.second);\r
 \r
@@ -287,7 +444,7 @@ pair<bool,DOMElement*> XMLAccessControl::load()
     return make_pair(false,(DOMElement*)NULL);\r
 }\r
 \r
-bool XMLAccessControl::authorized(const SPRequest& request, const Session* session) const\r
+AccessControl::aclresult_t XMLAccessControl::authorized(const SPRequest& request, const Session* session) const\r
 {\r
-    return m_rootAuthz ? m_rootAuthz->authorized(request,session) : false;\r
+    return m_rootAuthz ? m_rootAuthz->authorized(request,session) : shib_acl_false;\r
 }\r