1 /*
2 // Copyright (c) 2018 Intel Corporation
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 #pragma once
17 
18 #include "logging.hpp"
19 
20 #include <boost/beast/http/verb.hpp>
21 #include <boost/container/flat_map.hpp>
22 #include <boost/container/vector.hpp>
23 #include <boost/move/algo/move.hpp>
24 
25 #include <array>
26 #include <bitset>
27 #include <cstddef>
28 #include <functional>
29 #include <initializer_list>
30 #include <string>
31 #include <string_view>
32 #include <utility>
33 #include <vector>
34 
35 // IWYU pragma: no_include <stddef.h>
36 
37 namespace redfish
38 {
39 
40 enum class PrivilegeType
41 {
42     BASE,
43     OEM
44 };
45 
46 /** @brief A fixed array of compile time privileges  */
47 constexpr std::array<const char*, 5> basePrivileges{
48     "Login", "ConfigureManager", "ConfigureComponents", "ConfigureSelf",
49     "ConfigureUsers"};
50 
51 constexpr const size_t basePrivilegeCount = basePrivileges.size();
52 
53 /** @brief Max number of privileges per type  */
54 constexpr const size_t maxPrivilegeCount = 32;
55 
56 /** @brief A vector of all privilege names and their indexes */
57 static const std::array<std::string, maxPrivilegeCount> privilegeNames{
58     "Login", "ConfigureManager", "ConfigureComponents", "ConfigureSelf",
59     "ConfigureUsers"};
60 
61 /**
62  * @brief Redfish privileges
63  *
64  *        This implements a set of Redfish privileges.  These directly represent
65  *        user privileges and help represent entity privileges.
66  *
67  *        Each incoming Connection requires a comparison between privileges held
68  *        by the user issuing a request and the target entity's privileges.
69  *
70  *        To ensure best runtime performance of this comparison, privileges
71  *        are represented as bitsets. Each bit in the bitset corresponds to a
72  *        unique privilege name.
73  *
74  *        A bit is set if the privilege is required (entity domain) or granted
75  *        (user domain) and false otherwise.
76  *
77  */
78 class Privileges
79 {
80   public:
81     /**
82      * @brief Constructs object without any privileges active
83      *
84      */
85     Privileges() = default;
86 
87     /**
88      * @brief Constructs object with given privileges active
89      *
90      * @param[in] privilegeList  List of privileges to be activated
91      *
92      */
93     Privileges(std::initializer_list<const char*> privilegeList)
94     {
95         for (const char* privilege : privilegeList)
96         {
97             if (!setSinglePrivilege(privilege))
98             {
99                 BMCWEB_LOG_CRITICAL << "Unable to set privilege " << privilege
100                                     << "in constructor";
101             }
102         }
103     }
104 
105     /**
106      * @brief Sets given privilege in the bitset
107      *
108      * @param[in] privilege  Privilege to be set
109      *
110      * @return               None
111      *
112      */
113     bool setSinglePrivilege(const std::string_view privilege)
114     {
115         for (size_t searchIndex = 0; searchIndex < privilegeNames.size();
116              searchIndex++)
117         {
118             if (privilege == privilegeNames[searchIndex])
119             {
120                 privilegeBitset.set(searchIndex);
121                 return true;
122             }
123         }
124 
125         return false;
126     }
127 
128     /**
129      * @brief Resets the given privilege in the bitset
130      *
131      * @param[in] privilege  Privilege to be reset
132      *
133      * @return               None
134      *
135      */
136     bool resetSinglePrivilege(const char* privilege)
137     {
138         for (size_t searchIndex = 0; searchIndex < privilegeNames.size();
139              searchIndex++)
140         {
141             if (privilege == privilegeNames[searchIndex])
142             {
143                 privilegeBitset.reset(searchIndex);
144                 return true;
145             }
146         }
147         return false;
148     }
149 
150     /**
151      * @brief Retrieves names of all active privileges for a given type
152      *
153      * @param[in] type    Base or OEM
154      *
155      * @return            Vector of active privileges.  Pointers are valid until
156      * the setSinglePrivilege is called, or the Privilege structure is destroyed
157      *
158      */
159     std::vector<std::string>
160         getActivePrivilegeNames(const PrivilegeType type) const
161     {
162         std::vector<std::string> activePrivileges;
163 
164         size_t searchIndex = 0;
165         size_t endIndex = basePrivilegeCount;
166         if (type == PrivilegeType::OEM)
167         {
168             searchIndex = basePrivilegeCount;
169             endIndex = privilegeNames.size();
170         }
171 
172         for (; searchIndex < endIndex; searchIndex++)
173         {
174             if (privilegeBitset.test(searchIndex))
175             {
176                 activePrivileges.emplace_back(privilegeNames[searchIndex]);
177             }
178         }
179 
180         return activePrivileges;
181     }
182 
183     /**
184      * @brief Determines if this Privilege set is a superset of the given
185      * privilege set
186      *
187      * @param[in] privilege  Privilege to be checked
188      *
189      * @return               None
190      *
191      */
192     bool isSupersetOf(const Privileges& p) const
193     {
194         return (privilegeBitset & p.privilegeBitset) == p.privilegeBitset;
195     }
196 
197     /**
198      * @brief Returns the intersection of two Privilege sets.
199      *
200      * @param[in] privilege  Privilege set to intersect with.
201      *
202      * @return               The new Privilege set.
203      *
204      */
205     Privileges intersection(const Privileges& p) const
206     {
207         return Privileges{privilegeBitset & p.privilegeBitset};
208     }
209 
210   private:
211     explicit Privileges(const std::bitset<maxPrivilegeCount>& p) :
212         privilegeBitset{p}
213     {}
214     std::bitset<maxPrivilegeCount> privilegeBitset = 0;
215 };
216 
217 inline const Privileges& getUserPrivileges(const std::string& userRole)
218 {
219     // Redfish privilege : Administrator
220     if (userRole == "priv-admin")
221     {
222         static Privileges admin{"Login", "ConfigureManager", "ConfigureSelf",
223                                 "ConfigureUsers", "ConfigureComponents"};
224         return admin;
225     }
226     if (userRole == "priv-operator")
227     {
228         // Redfish privilege : Operator
229         static Privileges op{"Login", "ConfigureSelf", "ConfigureComponents"};
230         return op;
231     }
232     if (userRole == "priv-user")
233     {
234         // Redfish privilege : Readonly
235         static Privileges readOnly{"Login", "ConfigureSelf"};
236         return readOnly;
237     }
238     // Redfish privilege : NoAccess
239     static Privileges noaccess;
240     return noaccess;
241 }
242 
243 /**
244  * @brief The OperationMap represents the privileges required for a
245  * single entity (URI).  It maps from the allowable verbs to the
246  * privileges required to use that operation.
247  *
248  * This represents the Redfish "Privilege AND and OR syntax" as given
249  * in the spec and shown in the Privilege Registry.  This does not
250  * implement any Redfish property overrides, subordinate overrides, or
251  * resource URI overrides.  This does not implement the limitation of
252  * the ConfigureSelf privilege to operate only on your own account or
253  * session.
254  **/
255 using OperationMap = boost::container::flat_map<boost::beast::http::verb,
256                                                 std::vector<Privileges>>;
257 
258 /* @brief Checks if user is allowed to call an operation
259  *
260  * @param[in] operationPrivilegesRequired   Privileges required
261  * @param[in] userPrivileges                Privileges the user has
262  *
263  * @return                 True if operation is allowed, false otherwise
264  */
265 inline bool isOperationAllowedWithPrivileges(
266     const std::vector<Privileges>& operationPrivilegesRequired,
267     const Privileges& userPrivileges)
268 {
269     // If there are no privileges assigned, there are no privileges required
270     if (operationPrivilegesRequired.empty())
271     {
272         return true;
273     }
274     for (const auto& requiredPrivileges : operationPrivilegesRequired)
275     {
276         BMCWEB_LOG_DEBUG << "Checking operation privileges...";
277         if (userPrivileges.isSupersetOf(requiredPrivileges))
278         {
279             BMCWEB_LOG_DEBUG << "...success";
280             return true;
281         }
282     }
283     return false;
284 }
285 
286 /**
287  * @brief Checks if given privileges allow to call an HTTP method
288  *
289  * @param[in] method       HTTP method
290  * @param[in] user         Privileges
291  *
292  * @return                 True if method allowed, false otherwise
293  *
294  */
295 inline bool isMethodAllowedWithPrivileges(const boost::beast::http::verb method,
296                                           const OperationMap& operationMap,
297                                           const Privileges& userPrivileges)
298 {
299     const auto& it = operationMap.find(method);
300     if (it == operationMap.end())
301     {
302         return false;
303     }
304 
305     return isOperationAllowedWithPrivileges(it->second, userPrivileges);
306 }
307 
308 } // namespace redfish
309