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