https://issues.shibboleth.net/jira/browse/SSPCPP-175
[shibboleth/cpp-sp.git] / xmlproviders / XMLAccessControl.cpp
1 /*
2  *  Copyright 2001-2005 Internet2
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /* XMLAccessControl.cpp - an XML-based access control syntax
18
19    Scott Cantor
20    10/25/05
21 */
22
23 #include "internal.h"
24 #include <shib-target/shib-target.h>
25
26 #ifndef HAVE_STRCASECMP
27 # define strcasecmp stricmp
28 #endif
29
30 using namespace std;
31 using namespace saml;
32 using namespace shibboleth;
33 using namespace shibtarget;
34 using namespace xmlproviders::logging;
35
36 namespace {
37     struct IAuthz {
38         virtual ~IAuthz() {}
39         virtual bool authorized(ShibTarget* st, ISessionCacheEntry* entry) const=0;
40     };
41     
42     class Rule : public IAuthz
43     {
44     public:
45         Rule(const DOMElement* e);
46         ~Rule() {}
47         bool authorized(ShibTarget* st, ISessionCacheEntry* entry) const;
48     
49     private:
50         string m_alias;
51         vector <string> m_vals;
52     };
53     
54     class Operator : public IAuthz
55     {
56     public:
57         Operator(const DOMElement* e);
58         ~Operator();
59         bool authorized(ShibTarget* st, ISessionCacheEntry* entry) const;
60         
61     private:
62         enum operator_t { OP_NOT, OP_AND, OP_OR } m_op;
63         vector<IAuthz*> m_operands;
64     };
65
66     class XMLAccessControlImpl : public ReloadableXMLFileImpl
67     {
68     public:
69         XMLAccessControlImpl(const char* pathname) : ReloadableXMLFileImpl(pathname) { init(); }
70         XMLAccessControlImpl(const DOMElement* e) : ReloadableXMLFileImpl(e) { init(); }
71         void init();
72         ~XMLAccessControlImpl() {delete m_rootAuthz;}
73         
74         IAuthz* m_rootAuthz;
75     };
76
77     class XMLAccessControl : public IAccessControl, public ReloadableXMLFile
78     {
79     public:
80         XMLAccessControl(const DOMElement* e) : ReloadableXMLFile(e) {}
81         ~XMLAccessControl() {}
82
83         virtual bool authorized(ShibTarget* st, ISessionCacheEntry* entry) const;
84
85     protected:
86         virtual ReloadableXMLFileImpl* newImplementation(const char* pathname, bool first=true) const;
87         virtual ReloadableXMLFileImpl* newImplementation(const DOMElement* e, bool first=true) const;
88     };
89 }
90
91 IPlugIn* XMLAccessControlFactory(const DOMElement* e)
92 {
93     auto_ptr<XMLAccessControl> a(new XMLAccessControl(e));
94     a->getImplementation();
95     return a.release();
96 }
97
98 Rule::Rule(const DOMElement* e)
99 {
100     auto_ptr_char req(e->getAttributeNS(NULL,SHIB_L(require)));
101     if (!req.get() || !*req.get())
102         throw MalformedException("Access control rule missing require attribute");
103     m_alias=req.get();
104     
105     auto_ptr_char vals(e->hasChildNodes() ? e->getFirstChild()->getNodeValue() : NULL);
106 #ifdef HAVE_STRTOK_R
107     char* pos=NULL;
108     const char* token=strtok_r(const_cast<char*>(vals.get())," ",&pos);
109 #else
110     const char* token=strtok(const_cast<char*>(vals.get())," ");
111 #endif
112     while (token) {
113         m_vals.push_back(token);
114 #ifdef HAVE_STRTOK_R
115         token=strtok_r(NULL," ",&pos);
116 #else
117         token=strtok(NULL," ");
118 #endif
119     }
120 }
121
122 bool Rule::authorized(ShibTarget* st, ISessionCacheEntry* entry) const
123 {
124     // Map alias in rule to the attribute.
125     Iterator<IAAP*> provs=st->getApplication()->getAAPProviders();
126     AAP wrapper(provs,m_alias.c_str());
127     if (wrapper.fail()) {
128         st->log(ShibTarget::LogLevelWarn, string("AccessControl plugin didn't recognize rule (") + m_alias + "), check AAP for corresponding Alias");
129         return false;
130     }
131     else if (!entry) {
132         st->log(ShibTarget::LogLevelWarn, "AccessControl plugin not given a valid session to evaluate, are you using lazy sessions?");
133         return false;
134     }
135     
136     // Find the corresponding attribute. This isn't very efficient...
137     ISessionCacheEntry::CachedResponse cr=entry->getResponse();
138     Iterator<SAMLAssertion*> a_iter(cr.filtered ? cr.filtered->getAssertions() : EMPTY(SAMLAssertion*));
139     while (a_iter.hasNext()) {
140         SAMLAssertion* assert=a_iter.next();
141         Iterator<SAMLStatement*> statements=assert->getStatements();
142         while (statements.hasNext()) {
143             SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
144             if (!astate)
145                 continue;
146             Iterator<SAMLAttribute*> attrs=astate->getAttributes();
147             while (attrs.hasNext()) {
148                 SAMLAttribute* attr=attrs.next();
149                 if (!XMLString::compareString(attr->getName(),wrapper->getName()) &&
150                     !XMLString::compareString(attr->getNamespace(),wrapper->getNamespace())) {
151                     // Now we have to intersect the attribute's values against the rule's list.
152                     Iterator<string> vals=attr->getSingleByteValues();
153                     if (!vals.hasNext())
154                         return false;
155                     for (vector<string>::const_iterator ival=m_vals.begin(); ival!=m_vals.end(); ival++) {
156                         vals.reset();
157                         while (vals.hasNext()) {
158                             const string& v=vals.next();
159                             if ((wrapper->getCaseSensitive() && v == *ival) || (!wrapper->getCaseSensitive() && !strcasecmp(v.c_str(),ival->c_str()))) {
160                                 st->log(ShibTarget::LogLevelDebug, string("XMLAccessControl plugin expecting (" + *ival + "), got it, authz granted"));
161                                 return true;
162                             }
163                             else {
164                                 st->log(ShibTarget::LogLevelDebug, string("XMLAccessControl plugin expecting (" + *ival + "), got (" + v + "), authz not granted"));
165                             }
166                         }
167                     }
168                 }
169             }
170         }
171     }
172     
173     return false;
174 }
175
176 Operator::Operator(const DOMElement* e)
177 {
178     if (saml::XML::isElementNamed(e,shibtarget::XML::SHIBTARGET_NS,SHIB_L(NOT)))
179         m_op=OP_NOT;
180     else if (saml::XML::isElementNamed(e,shibtarget::XML::SHIBTARGET_NS,SHIB_L(AND)))
181         m_op=OP_AND;
182     else if (saml::XML::isElementNamed(e,shibtarget::XML::SHIBTARGET_NS,SHIB_L(OR)))
183         m_op=OP_OR;
184     else
185         throw MalformedException("Unrecognized operator in access control rule");
186     
187     try {
188         e=saml::XML::getFirstChildElement(e);
189         if (saml::XML::isElementNamed(e,shibtarget::XML::SHIBTARGET_NS,SHIB_L(Rule)))
190             m_operands.push_back(new Rule(e));
191         else
192             m_operands.push_back(new Operator(e));
193         
194         if (m_op==OP_NOT)
195             return;
196         
197         e=saml::XML::getNextSiblingElement(e);
198         while (e) {
199             if (saml::XML::isElementNamed(e,shibtarget::XML::SHIBTARGET_NS,SHIB_L(Rule)))
200                 m_operands.push_back(new Rule(e));
201             else
202                 m_operands.push_back(new Operator(e));
203             e=saml::XML::getNextSiblingElement(e);
204         }
205     }
206     catch (SAMLException&) {
207         this->~Operator();
208         throw;
209     }
210 }
211
212 Operator::~Operator()
213 {
214     for (vector<IAuthz*>::iterator i=m_operands.begin(); i!=m_operands.end(); i++)
215         delete *i;
216 }
217
218 bool Operator::authorized(ShibTarget* st, ISessionCacheEntry* entry) const
219 {
220     switch (m_op) {
221         case OP_NOT:
222             return !m_operands[0]->authorized(st,entry);
223         
224         case OP_AND:
225         {
226             for (vector<IAuthz*>::const_iterator i=m_operands.begin(); i!=m_operands.end(); i++) {
227                 if (!(*i)->authorized(st,entry))
228                     return false;
229             }
230             return true;
231         }
232         
233         case OP_OR:
234         {
235             for (vector<IAuthz*>::const_iterator i=m_operands.begin(); i!=m_operands.end(); i++) {
236                 if ((*i)->authorized(st,entry))
237                     return true;
238             }
239             return false;
240         }
241     }
242     st->log(ShibTarget::LogLevelWarn,"Unknown operation in access control policy, denying access");
243     return false;
244 }
245
246 void XMLAccessControlImpl::init()
247 {
248 #ifdef _DEBUG
249     NDC ndc("init");
250 #endif
251     Category* log=&Category::getInstance(XMLPROVIDERS_LOGCAT".AccessControl");
252
253     try {
254         // We need to move below the AccessControl root element if the policy is in a separate file.
255         // Unlike most of the plugins, an inline policy will end up handing us the first inline
256         // content element, and not the outer wrapper.
257         const DOMElement* rootElement=ReloadableXMLFileImpl::m_root;
258         if (saml::XML::isElementNamed(rootElement,shibtarget::XML::SHIBTARGET_NS,SHIB_L(AccessControl)))
259             rootElement = saml::XML::getFirstChildElement(rootElement);
260         
261         if (saml::XML::isElementNamed(rootElement,shibtarget::XML::SHIBTARGET_NS,SHIB_L(Rule)))
262             m_rootAuthz=new Rule(rootElement);
263         else
264             m_rootAuthz=new Operator(rootElement);
265     }
266     catch (SAMLException& e) {
267         log->errorStream() << "Error while parsing access control configuration: " << e.what() << xmlproviders::logging::eol;
268         throw;
269     }
270 #ifndef _DEBUG
271     catch (...)
272     {
273         log->error("Unexpected error while parsing access control configuration");
274         throw;
275     }
276 #endif
277 }
278
279 ReloadableXMLFileImpl* XMLAccessControl::newImplementation(const char* pathname, bool first) const
280 {
281     return new XMLAccessControlImpl(pathname);
282 }
283
284 ReloadableXMLFileImpl* XMLAccessControl::newImplementation(const DOMElement* e, bool first) const
285 {
286     return new XMLAccessControlImpl(e);
287 }
288
289 bool XMLAccessControl::authorized(ShibTarget* st, ISessionCacheEntry* entry) const
290 {
291     return static_cast<XMLAccessControlImpl*>(getImplementation())->m_rootAuthz->authorized(st,entry);
292 }