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