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