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