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