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      */
Privileges(std::initializer_list<const char * > privilegeList)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      */
setSinglePrivilege(std::string_view privilege)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      */
resetSinglePrivilege(const char * privilege)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>
getActivePrivilegeNames(const PrivilegeType type) const163         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      */
isSupersetOf(const Privileges & p) const195     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      */
intersection(const Privileges & p) const208     Privileges intersection(const Privileges& p) const
209     {
210         return Privileges{privilegeBitset & p.privilegeBitset};
211     }
212 
213   private:
Privileges(const std::bitset<maxPrivilegeCount> & p)214     explicit Privileges(const std::bitset<maxPrivilegeCount>& p) :
215         privilegeBitset{p}
216     {}
217     std::bitset<maxPrivilegeCount> privilegeBitset = 0;
218 };
219 
getUserPrivileges(const persistent_data::UserSession & session)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  */
isOperationAllowedWithPrivileges(const std::vector<Privileges> & operationPrivilegesRequired,const Privileges & userPrivileges)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  */
isMethodAllowedWithPrivileges(const boost::beast::http::verb method,const OperationMap & operationMap,const Privileges & userPrivileges)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