https://issues.shibboleth.net/jira/browse/SSPCPP-624
[shibboleth/cpp-sp.git] / plugins / TimeAccessControl.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * TimeAccessControl.cpp
23  *
24  * Access control plugin for time-based policies.
25  */
26
27 #include "internal.h"
28
29 #include <shibsp/exceptions.h>
30 #include <shibsp/AccessControl.h>
31 #include <shibsp/SessionCache.h>
32 #include <shibsp/SPRequest.h>
33
34 #include <boost/lexical_cast.hpp>
35 #include <boost/algorithm/string.hpp>
36 #include <boost/ptr_container/ptr_vector.hpp>
37 #include <xmltooling/unicode.h>
38 #include <xmltooling/util/DateTime.h>
39 #include <xmltooling/util/XMLHelper.h>
40 #include <xercesc/util/XMLUniDefs.hpp>
41
42 using namespace shibsp;
43 using namespace xmltooling;
44 using namespace xercesc;
45 using namespace boost;
46 using namespace std;
47
48 namespace shibsp {
49
50     class Rule : public AccessControl
51     {
52     public:
53         Rule(const DOMElement* e);
54         ~Rule() {}
55
56         Lockable* lock() {return this;}
57         void unlock() {}
58
59         aclresult_t authorized(const SPRequest& request, const Session* session) const;
60
61     private:
62         enum { TM_AUTHN, TM_TIME, TM_YEAR, TM_MONTH, TM_DAY, TM_HOUR, TM_MINUTE, TM_SECOND, TM_WDAY } m_type;
63         enum { OP_LT, OP_LE, OP_EQ, OP_GE, OP_GT } m_op;
64         time_t m_value;
65     };
66
67     class TimeAccessControl : public AccessControl
68     {
69     public:
70         TimeAccessControl(const DOMElement* e);
71         ~TimeAccessControl() {}
72
73         Lockable* lock() {
74             return this;
75         }
76         void unlock() {
77         }
78
79         aclresult_t authorized(const SPRequest& request, const Session* session) const;
80
81     private:
82         enum { OP_AND, OP_OR } m_op;
83         ptr_vector<Rule> m_rules;
84     };
85
86     AccessControl* SHIBSP_DLLLOCAL TimeAccessControlFactory(const DOMElement* const & e)
87     {
88         return new TimeAccessControl(e);
89     }
90
91     static const XMLCh _operator[] =        UNICODE_LITERAL_8(o,p,e,r,a,t,o,r);
92     static const XMLCh AND[] =              UNICODE_LITERAL_3(A,N,D);
93     static const XMLCh OR[] =               UNICODE_LITERAL_2(O,R);
94     static const XMLCh Day[] =              UNICODE_LITERAL_3(D,a,y);
95     static const XMLCh DayOfWeek[] =        UNICODE_LITERAL_9(D,a,y,O,f,W,e,e,k);
96     static const XMLCh Hour[] =             UNICODE_LITERAL_4(H,o,u,r);
97     static const XMLCh Minute[] =           UNICODE_LITERAL_6(M,i,n,u,t,e);
98     static const XMLCh Month[] =            UNICODE_LITERAL_5(M,o,n,t,h);
99     static const XMLCh Second[] =           UNICODE_LITERAL_6(S,e,c,o,n,d);
100     static const XMLCh Time[] =             UNICODE_LITERAL_4(T,i,m,e);
101     static const XMLCh TimeSinceAuthn[] =   UNICODE_LITERAL_14(T,i,m,e,S,i,n,c,e,A,u,t,h,n);
102     static const XMLCh Year[] =             UNICODE_LITERAL_4(Y,e,a,r);
103 }
104
105 Rule::Rule(const DOMElement* e)
106 {
107     if (XMLString::equals(e->getLocalName(), TimeSinceAuthn)) {
108         m_type = TM_AUTHN;
109         DateTime dur(e->getTextContent());
110         dur.parseDuration();
111         m_value = dur.getEpoch(true);
112         return;
113     }
114     
115     auto_ptr_char temp(e->getTextContent());
116     string s(temp.get() ? temp.get() : "");
117     trim(s);
118     vector<string> tokens;
119     if (split(tokens, s, is_space(), algorithm::token_compress_on).size() != 2)
120         throw ConfigurationException("Time-based rule requires element content of the form \"LT|LE|EQ|GE|GT value\".");
121     string& op = tokens.front();
122     if (op == "LT")         m_op = OP_LT;
123     else if (op == "LE")    m_op = OP_LE;
124     else if (op == "EQ")    m_op = OP_EQ;
125     else if (op == "GE")    m_op = OP_GE;
126     else if (op == "GT")    m_op = OP_GT;
127     else
128         throw ConfigurationException("First component of time-based rule must be one of LT, LE, EQ, GE, GT.");
129
130     if (XMLString::equals(e->getLocalName(), Time)) {
131         m_type = TM_TIME;
132         auto_ptr_XMLCh widen(tokens.back().c_str());
133         DateTime dt(widen.get());
134         dt.parseDateTime();
135         m_value = dt.getEpoch();
136         return;
137     }
138
139     m_value = lexical_cast<time_t>(tokens.back());
140
141     if (XMLString::equals(e->getLocalName(), Year))             m_type = TM_YEAR;
142     else if (XMLString::equals(e->getLocalName(), Month))       m_type = TM_MONTH;
143     else if (XMLString::equals(e->getLocalName(), Day))         m_type = TM_DAY;
144     else if (XMLString::equals(e->getLocalName(), Hour))        m_type = TM_HOUR;
145     else if (XMLString::equals(e->getLocalName(), Minute))      m_type = TM_MINUTE;
146     else if (XMLString::equals(e->getLocalName(), Second))      m_type = TM_SECOND;
147     else if (XMLString::equals(e->getLocalName(), DayOfWeek))   m_type = TM_WDAY;
148     else
149         throw ConfigurationException("Unrecognized time-based rule.");
150 }
151
152 /*
153 <AccessControlProvider type="Time" operator="AND|OR">
154     <TimeSinceAuthn>PT1H</TimeSinceAuthn>
155     <Time> LT|LE|EQ|GE|GT ISO </Time>
156     <Year> LT|LE|EQ|GE|GT nn </Year>
157     <Month> LT|LE|EQ|GE|GT nn </Month>
158     <Day> LT|LE|EQ|GE|GT nn </Day>
159     <Hour> LT|LE|EQ|GE|GT nn </Hour>
160     <Minute> LT|LE|EQ|GE|GT nn </Minute>
161     <Second> LT|LE|EQ|GE|GT nn </Second>
162     <DayOfWeek> LT|LE|EQ|GE|GT 0-6 </DayOfWeek>
163 </AccessControlProvider>
164 */
165
166 AccessControl::aclresult_t Rule::authorized(const SPRequest& request, const Session* session) const
167 {
168     time_t operand = 0;
169
170     if (m_type == TM_AUTHN) {
171         if (session) {
172             auto_ptr_XMLCh atime(session->getAuthnInstant());
173             if (atime.get()) {
174                 try {
175                     DateTime dt(atime.get());
176                     dt.parseDateTime();
177                     if (time(nullptr) - dt.getEpoch() <= m_value)
178                         return shib_acl_true;
179                     request.log(SPRequest::SPDebug, "elapsed time since authentication exceeds limit");
180                     return shib_acl_false;
181                 }
182                 catch (std::exception& e) {
183                     request.log(SPRequest::SPError, e.what());
184                 }
185             }
186         }
187         request.log(SPRequest::SPDebug, "session or authentication time unavailable");
188         return shib_acl_false;
189     }
190
191     // Extract value from tm struct or time directly.
192     operand = time(nullptr);
193     if (m_type != TM_TIME) {
194 #ifndef HAVE_LOCALTIME_R
195         struct tm* ptime = localtime(&operand);
196 #else
197         struct tm res;
198         struct tm* ptime = localtime_r(&operand, &res);
199 #endif
200         switch (m_type) {
201             case TM_YEAR:
202                 operand = ptime->tm_year + 1900;
203                 break;
204             case TM_MONTH:
205                 operand = ptime->tm_mon + 1;
206                 break;
207             case TM_DAY:
208                 operand = ptime->tm_mday;
209                 break;
210             case TM_HOUR:
211                 operand = ptime->tm_hour;
212                 break;
213             case TM_MINUTE:
214                 operand = ptime->tm_min;
215                 break;
216             case TM_SECOND:
217                 operand = ptime->tm_sec;
218                 break;
219             case TM_WDAY:
220                 operand = ptime->tm_wday;
221                 break;
222         }
223     }
224
225     // Compare operand to test value in rule using rule operator.
226     switch (m_op) {
227         case OP_LT:
228             return (operand < m_value) ? shib_acl_true : shib_acl_false;
229         case OP_LE:
230             return (operand <= m_value) ? shib_acl_true : shib_acl_false;
231         case OP_EQ:
232             return (operand == m_value) ? shib_acl_true : shib_acl_false;
233         case OP_GE:
234             return (operand >= m_value) ? shib_acl_true : shib_acl_false;
235         case OP_GT:
236             return (operand > m_value) ? shib_acl_true : shib_acl_false;
237     }
238     return shib_acl_false;
239 }
240
241 TimeAccessControl::TimeAccessControl(const DOMElement* e) : m_op(OP_AND)
242 {
243     const XMLCh* op = e ? e->getAttributeNS(nullptr, _operator) : nullptr;
244     if (XMLString::equals(op, OR))
245         m_op = OP_OR;
246     else if (op && *op && !XMLString::equals(op, AND))
247         throw ConfigurationException("Unrecognized operator in Time AccessControl configuration.");
248
249     e = XMLHelper::getFirstChildElement(e);
250     while (e) {
251         auto_ptr<Rule> np(new Rule(e));
252         m_rules.push_back(np.get());
253         np.release();
254         e = XMLHelper::getNextSiblingElement(e);
255     }
256     if (m_rules.empty())
257         throw ConfigurationException("Time AccessControl plugin requires at least one rule.");
258 }
259
260 AccessControl::aclresult_t TimeAccessControl::authorized(const SPRequest& request, const Session* session) const
261 {
262     switch (m_op) {
263         case OP_AND:
264         {
265             for (ptr_vector<Rule>::const_iterator i = m_rules.begin(); i != m_rules.end(); ++i) {
266                 if (i->authorized(request, session) != shib_acl_true) {
267                     request.log(SPRequest::SPDebug, "time-based rule unsuccessful, denying access");
268                     return shib_acl_false;
269                 }
270             }
271             return shib_acl_true;
272         }
273
274         case OP_OR:
275         {
276             for (ptr_vector<Rule>::const_iterator i = m_rules.begin(); i != m_rules.end(); ++i) {
277                 if (i->authorized(request,session) == shib_acl_true)
278                     return shib_acl_true;
279             }
280             request.log(SPRequest::SPDebug, "all time-based rules unsuccessful, denying access");
281             return shib_acl_false;
282         }
283     }
284     request.log(SPRequest::SPWarn, "unknown operator in access control policy, denying access");
285     return shib_acl_false;
286 }