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