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