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