1 #pragma once 2 3 #include "async_resp.hpp" 4 #include "common.hpp" 5 #include "dbus_utility.hpp" 6 #include "error_messages.hpp" 7 #include "http_request.hpp" 8 #include "http_response.hpp" 9 #include "logging.hpp" 10 #include "privileges.hpp" 11 #include "sessions.hpp" 12 #include "utility.hpp" 13 #include "utils/dbus_utils.hpp" 14 #include "verb.hpp" 15 #include "websocket.hpp" 16 17 #include <boost/beast/ssl/ssl_stream.hpp> 18 #include <boost/container/flat_map.hpp> 19 #include <boost/url/format.hpp> 20 #include <sdbusplus/unpack_properties.hpp> 21 22 #include <cerrno> 23 #include <cstdint> 24 #include <cstdlib> 25 #include <limits> 26 #include <memory> 27 #include <optional> 28 #include <tuple> 29 #include <utility> 30 #include <vector> 31 32 namespace crow 33 { 34 35 class BaseRule 36 { 37 public: 38 explicit BaseRule(const std::string& thisRule) : rule(thisRule) {} 39 40 virtual ~BaseRule() = default; 41 42 BaseRule(const BaseRule&) = delete; 43 BaseRule(BaseRule&&) = delete; 44 BaseRule& operator=(const BaseRule&) = delete; 45 BaseRule& operator=(const BaseRule&&) = delete; 46 47 virtual void validate() = 0; 48 std::unique_ptr<BaseRule> upgrade() 49 { 50 if (ruleToUpgrade) 51 { 52 return std::move(ruleToUpgrade); 53 } 54 return {}; 55 } 56 57 virtual void handle(const Request& /*req*/, 58 const std::shared_ptr<bmcweb::AsyncResp>&, 59 const std::vector<std::string>&) = 0; 60 #ifndef BMCWEB_ENABLE_SSL 61 virtual void 62 handleUpgrade(const Request& /*req*/, 63 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 64 boost::asio::ip::tcp::socket&& /*adaptor*/) 65 { 66 asyncResp->res.result(boost::beast::http::status::not_found); 67 } 68 #else 69 virtual void handleUpgrade( 70 const Request& /*req*/, 71 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 72 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& /*adaptor*/) 73 { 74 asyncResp->res.result(boost::beast::http::status::not_found); 75 } 76 #endif 77 78 size_t getMethods() const 79 { 80 return methodsBitfield; 81 } 82 83 bool checkPrivileges(const redfish::Privileges& userPrivileges) 84 { 85 // If there are no privileges assigned, assume no privileges 86 // required 87 if (privilegesSet.empty()) 88 { 89 return true; 90 } 91 92 for (const redfish::Privileges& requiredPrivileges : privilegesSet) 93 { 94 if (userPrivileges.isSupersetOf(requiredPrivileges)) 95 { 96 return true; 97 } 98 } 99 return false; 100 } 101 102 size_t methodsBitfield{1 << static_cast<size_t>(HttpVerb::Get)}; 103 static_assert(std::numeric_limits<decltype(methodsBitfield)>::digits > 104 methodNotAllowedIndex, 105 "Not enough bits to store bitfield"); 106 107 std::vector<redfish::Privileges> privilegesSet; 108 109 std::string rule; 110 111 std::unique_ptr<BaseRule> ruleToUpgrade; 112 113 friend class Router; 114 template <typename T> 115 friend struct RuleParameterTraits; 116 }; 117 118 namespace detail 119 { 120 namespace routing_handler_call_helper 121 { 122 template <typename T, int Pos> 123 struct CallPair 124 { 125 using type = T; 126 static const int pos = Pos; 127 }; 128 129 template <typename H1> 130 struct CallParams 131 { 132 H1& handler; 133 const std::vector<std::string>& params; 134 const Request& req; 135 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp; 136 }; 137 138 template <typename F, int NString, typename S1, typename S2> 139 struct Call 140 {}; 141 142 template <typename F, int NString, typename... Args1, typename... Args2> 143 struct Call<F, NString, black_magic::S<std::string, Args1...>, 144 black_magic::S<Args2...>> 145 { 146 void operator()(F cparams) 147 { 148 using pushed = typename black_magic::S<Args2...>::template push_back< 149 CallPair<std::string, NString>>; 150 Call<F, NString + 1, black_magic::S<Args1...>, pushed>()(cparams); 151 } 152 }; 153 154 template <typename F, int NString, typename... Args1> 155 struct Call<F, NString, black_magic::S<>, black_magic::S<Args1...>> 156 { 157 void operator()(F cparams) 158 { 159 cparams.handler(cparams.req, cparams.asyncResp, 160 cparams.params[Args1::pos]...); 161 } 162 }; 163 164 template <typename Func, typename... ArgsWrapped> 165 struct Wrapped 166 { 167 template <typename... Args> 168 void set( 169 Func f, 170 typename std::enable_if< 171 !std::is_same< 172 typename std::tuple_element<0, std::tuple<Args..., void>>::type, 173 const Request&>::value, 174 int>::type /*enable*/ 175 = 0) 176 { 177 handler = [f = std::forward<Func>(f)]( 178 const Request&, 179 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 180 Args... args) { asyncResp->res.result(f(args...)); }; 181 } 182 183 template <typename Req, typename... Args> 184 struct ReqHandlerWrapper 185 { 186 explicit ReqHandlerWrapper(Func fIn) : f(std::move(fIn)) {} 187 188 void operator()(const Request& req, 189 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 190 Args... args) 191 { 192 asyncResp->res.result(f(req, args...)); 193 } 194 195 Func f; 196 }; 197 198 template <typename... Args> 199 void set( 200 Func f, 201 typename std::enable_if< 202 std::is_same< 203 typename std::tuple_element<0, std::tuple<Args..., void>>::type, 204 const Request&>::value && 205 !std::is_same<typename std::tuple_element< 206 1, std::tuple<Args..., void, void>>::type, 207 const std::shared_ptr<bmcweb::AsyncResp>&>::value, 208 int>::type /*enable*/ 209 = 0) 210 { 211 handler = ReqHandlerWrapper<Args...>(std::move(f)); 212 /*handler = ( 213 [f = std::move(f)] 214 (const Request& req, Response& res, Args... args){ 215 res.result(f(req, args...)); 216 res.end(); 217 });*/ 218 } 219 220 template <typename... Args> 221 void set( 222 Func f, 223 typename std::enable_if< 224 std::is_same< 225 typename std::tuple_element<0, std::tuple<Args..., void>>::type, 226 const Request&>::value && 227 std::is_same<typename std::tuple_element< 228 1, std::tuple<Args..., void, void>>::type, 229 const std::shared_ptr<bmcweb::AsyncResp>&>::value, 230 int>::type /*enable*/ 231 = 0) 232 { 233 handler = std::move(f); 234 } 235 236 template <typename... Args> 237 struct HandlerTypeHelper 238 { 239 using type = std::function<void( 240 const crow::Request& /*req*/, 241 const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>; 242 using args_type = black_magic::S<Args...>; 243 }; 244 245 template <typename... Args> 246 struct HandlerTypeHelper<const Request&, Args...> 247 { 248 using type = std::function<void( 249 const crow::Request& /*req*/, 250 const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>; 251 using args_type = black_magic::S<Args...>; 252 }; 253 254 template <typename... Args> 255 struct HandlerTypeHelper<const Request&, 256 const std::shared_ptr<bmcweb::AsyncResp>&, Args...> 257 { 258 using type = std::function<void( 259 const crow::Request& /*req*/, 260 const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>; 261 using args_type = black_magic::S<Args...>; 262 }; 263 264 typename HandlerTypeHelper<ArgsWrapped...>::type handler; 265 266 void operator()(const Request& req, 267 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 268 const std::vector<std::string>& params) 269 { 270 detail::routing_handler_call_helper::Call< 271 detail::routing_handler_call_helper::CallParams<decltype(handler)>, 272 0, typename HandlerTypeHelper<ArgsWrapped...>::args_type, 273 black_magic::S<>>()( 274 detail::routing_handler_call_helper::CallParams<decltype(handler)>{ 275 handler, params, req, asyncResp}); 276 } 277 }; 278 } // namespace routing_handler_call_helper 279 } // namespace detail 280 281 class WebSocketRule : public BaseRule 282 { 283 using self_t = WebSocketRule; 284 285 public: 286 explicit WebSocketRule(const std::string& ruleIn) : BaseRule(ruleIn) {} 287 288 void validate() override {} 289 290 void handle(const Request& /*req*/, 291 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 292 const std::vector<std::string>& /*params*/) override 293 { 294 asyncResp->res.result(boost::beast::http::status::not_found); 295 } 296 297 #ifndef BMCWEB_ENABLE_SSL 298 void handleUpgrade(const Request& req, 299 const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, 300 boost::asio::ip::tcp::socket&& adaptor) override 301 { 302 BMCWEB_LOG_DEBUG << "Websocket handles upgrade"; 303 std::shared_ptr< 304 crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>> 305 myConnection = std::make_shared< 306 crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>( 307 req, std::move(adaptor), openHandler, messageHandler, 308 messageExHandler, closeHandler, errorHandler); 309 myConnection->start(); 310 } 311 #else 312 void handleUpgrade(const Request& req, 313 const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, 314 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& 315 adaptor) override 316 { 317 BMCWEB_LOG_DEBUG << "Websocket handles upgrade"; 318 std::shared_ptr<crow::websocket::ConnectionImpl< 319 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>> 320 myConnection = std::make_shared<crow::websocket::ConnectionImpl< 321 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>( 322 req, std::move(adaptor), openHandler, messageHandler, 323 messageExHandler, closeHandler, errorHandler); 324 myConnection->start(); 325 } 326 #endif 327 328 template <typename Func> 329 self_t& onopen(Func f) 330 { 331 openHandler = f; 332 return *this; 333 } 334 335 template <typename Func> 336 self_t& onmessage(Func f) 337 { 338 messageHandler = f; 339 return *this; 340 } 341 342 template <typename Func> 343 self_t& onmessageex(Func f) 344 { 345 messageExHandler = f; 346 return *this; 347 } 348 349 template <typename Func> 350 self_t& onclose(Func f) 351 { 352 closeHandler = f; 353 return *this; 354 } 355 356 template <typename Func> 357 self_t& onerror(Func f) 358 { 359 errorHandler = f; 360 return *this; 361 } 362 363 protected: 364 std::function<void(crow::websocket::Connection&)> openHandler; 365 std::function<void(crow::websocket::Connection&, const std::string&, bool)> 366 messageHandler; 367 std::function<void(crow::websocket::Connection&, std::string_view, 368 crow::websocket::MessageType type, 369 std::function<void()>&& whenComplete)> 370 messageExHandler; 371 std::function<void(crow::websocket::Connection&, const std::string&)> 372 closeHandler; 373 std::function<void(crow::websocket::Connection&)> errorHandler; 374 }; 375 376 template <typename T> 377 struct RuleParameterTraits 378 { 379 using self_t = T; 380 WebSocketRule& websocket() 381 { 382 self_t* self = static_cast<self_t*>(this); 383 WebSocketRule* p = new WebSocketRule(self->rule); 384 p->privilegesSet = self->privilegesSet; 385 self->ruleToUpgrade.reset(p); 386 return *p; 387 } 388 389 self_t& methods(boost::beast::http::verb method) 390 { 391 self_t* self = static_cast<self_t*>(this); 392 std::optional<HttpVerb> verb = httpVerbFromBoost(method); 393 if (verb) 394 { 395 self->methodsBitfield = 1U << static_cast<size_t>(*verb); 396 } 397 return *self; 398 } 399 400 template <typename... MethodArgs> 401 self_t& methods(boost::beast::http::verb method, MethodArgs... argsMethod) 402 { 403 self_t* self = static_cast<self_t*>(this); 404 methods(argsMethod...); 405 std::optional<HttpVerb> verb = httpVerbFromBoost(method); 406 if (verb) 407 { 408 self->methodsBitfield |= 1U << static_cast<size_t>(*verb); 409 } 410 return *self; 411 } 412 413 self_t& notFound() 414 { 415 self_t* self = static_cast<self_t*>(this); 416 self->methodsBitfield = 1U << notFoundIndex; 417 return *self; 418 } 419 420 self_t& methodNotAllowed() 421 { 422 self_t* self = static_cast<self_t*>(this); 423 self->methodsBitfield = 1U << methodNotAllowedIndex; 424 return *self; 425 } 426 427 self_t& privileges( 428 const std::initializer_list<std::initializer_list<const char*>>& p) 429 { 430 self_t* self = static_cast<self_t*>(this); 431 for (const std::initializer_list<const char*>& privilege : p) 432 { 433 self->privilegesSet.emplace_back(privilege); 434 } 435 return *self; 436 } 437 438 template <size_t N, typename... MethodArgs> 439 self_t& privileges(const std::array<redfish::Privileges, N>& p) 440 { 441 self_t* self = static_cast<self_t*>(this); 442 for (const redfish::Privileges& privilege : p) 443 { 444 self->privilegesSet.emplace_back(privilege); 445 } 446 return *self; 447 } 448 }; 449 450 class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule> 451 { 452 public: 453 explicit DynamicRule(const std::string& ruleIn) : BaseRule(ruleIn) {} 454 455 void validate() override 456 { 457 if (!erasedHandler) 458 { 459 throw std::runtime_error("no handler for url " + rule); 460 } 461 } 462 463 void handle(const Request& req, 464 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 465 const std::vector<std::string>& params) override 466 { 467 erasedHandler(req, asyncResp, params); 468 } 469 470 template <typename Func> 471 void operator()(Func f) 472 { 473 using boost::callable_traits::args_t; 474 constexpr size_t arity = std::tuple_size<args_t<Func>>::value; 475 constexpr auto is = std::make_integer_sequence<unsigned, arity>{}; 476 erasedHandler = wrap(std::move(f), is); 477 } 478 479 // enable_if Arg1 == request && Arg2 == Response 480 // enable_if Arg1 == request && Arg2 != response 481 // enable_if Arg1 != request 482 483 template <typename Func, unsigned... Indices> 484 std::function<void(const Request&, 485 const std::shared_ptr<bmcweb::AsyncResp>&, 486 const std::vector<std::string>&)> 487 wrap(Func f, std::integer_sequence<unsigned, Indices...> /*is*/) 488 { 489 using function_t = crow::utility::FunctionTraits<Func>; 490 491 auto ret = detail::routing_handler_call_helper::Wrapped< 492 Func, typename function_t::template arg<Indices>...>(); 493 ret.template set<typename function_t::template arg<Indices>...>( 494 std::move(f)); 495 return ret; 496 } 497 498 private: 499 std::function<void(const Request&, 500 const std::shared_ptr<bmcweb::AsyncResp>&, 501 const std::vector<std::string>&)> 502 erasedHandler; 503 }; 504 505 template <typename... Args> 506 class TaggedRule : 507 public BaseRule, 508 public RuleParameterTraits<TaggedRule<Args...>> 509 { 510 public: 511 using self_t = TaggedRule<Args...>; 512 513 explicit TaggedRule(const std::string& ruleIn) : BaseRule(ruleIn) {} 514 515 void validate() override 516 { 517 if (!handler) 518 { 519 throw std::runtime_error("no handler for url " + rule); 520 } 521 } 522 523 template <typename Func> 524 void operator()(Func&& f) 525 { 526 static_assert( 527 black_magic::CallHelper< 528 Func, black_magic::S<crow::Request, 529 std::shared_ptr<bmcweb::AsyncResp>&, 530 Args...>>::value, 531 "Handler type is mismatched with URL parameters"); 532 static_assert( 533 std::is_same< 534 void, 535 decltype(f(std::declval<crow::Request>(), 536 std::declval<std::shared_ptr<bmcweb::AsyncResp>&>(), 537 std::declval<Args>()...))>::value, 538 "Handler function with response argument should have void return type"); 539 540 handler = std::forward<Func>(f); 541 } 542 543 void handle(const Request& req, 544 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 545 const std::vector<std::string>& params) override 546 { 547 detail::routing_handler_call_helper::Call< 548 detail::routing_handler_call_helper::CallParams<decltype(handler)>, 549 0, black_magic::S<Args...>, black_magic::S<>>()( 550 detail::routing_handler_call_helper::CallParams<decltype(handler)>{ 551 handler, params, req, asyncResp}); 552 } 553 554 private: 555 std::function<void(const crow::Request&, 556 const std::shared_ptr<bmcweb::AsyncResp>&, Args...)> 557 handler; 558 }; 559 560 class Trie 561 { 562 public: 563 struct Node 564 { 565 unsigned ruleIndex{}; 566 std::array<size_t, static_cast<size_t>(ParamType::MAX)> 567 paramChildrens{}; 568 using ChildMap = boost::container::flat_map< 569 std::string, unsigned, std::less<>, 570 std::vector<std::pair<std::string, unsigned>>>; 571 ChildMap children; 572 573 bool isSimpleNode() const 574 { 575 return ruleIndex == 0 && 576 std::all_of(std::begin(paramChildrens), 577 std::end(paramChildrens), 578 [](size_t x) { return x == 0U; }); 579 } 580 }; 581 582 Trie() : nodes(1) {} 583 584 private: 585 void optimizeNode(Node* node) 586 { 587 for (size_t x : node->paramChildrens) 588 { 589 if (x == 0U) 590 { 591 continue; 592 } 593 Node* child = &nodes[x]; 594 optimizeNode(child); 595 } 596 if (node->children.empty()) 597 { 598 return; 599 } 600 bool mergeWithChild = true; 601 for (const Node::ChildMap::value_type& kv : node->children) 602 { 603 Node* child = &nodes[kv.second]; 604 if (!child->isSimpleNode()) 605 { 606 mergeWithChild = false; 607 break; 608 } 609 } 610 if (mergeWithChild) 611 { 612 Node::ChildMap merged; 613 for (const Node::ChildMap::value_type& kv : node->children) 614 { 615 Node* child = &nodes[kv.second]; 616 for (const Node::ChildMap::value_type& childKv : 617 child->children) 618 { 619 merged[kv.first + childKv.first] = childKv.second; 620 } 621 } 622 node->children = std::move(merged); 623 optimizeNode(node); 624 } 625 else 626 { 627 for (const Node::ChildMap::value_type& kv : node->children) 628 { 629 Node* child = &nodes[kv.second]; 630 optimizeNode(child); 631 } 632 } 633 } 634 635 void optimize() 636 { 637 optimizeNode(head()); 638 } 639 640 public: 641 void validate() 642 { 643 optimize(); 644 } 645 646 void findRouteIndexes(const std::string& reqUrl, 647 std::vector<unsigned>& routeIndexes, 648 const Node* node = nullptr, unsigned pos = 0) const 649 { 650 if (node == nullptr) 651 { 652 node = head(); 653 } 654 for (const Node::ChildMap::value_type& kv : node->children) 655 { 656 const std::string& fragment = kv.first; 657 const Node* child = &nodes[kv.second]; 658 if (pos >= reqUrl.size()) 659 { 660 if (child->ruleIndex != 0 && fragment != "/") 661 { 662 routeIndexes.push_back(child->ruleIndex); 663 } 664 findRouteIndexes(reqUrl, routeIndexes, child, 665 static_cast<unsigned>(pos + fragment.size())); 666 } 667 else 668 { 669 if (reqUrl.compare(pos, fragment.size(), fragment) == 0) 670 { 671 findRouteIndexes( 672 reqUrl, routeIndexes, child, 673 static_cast<unsigned>(pos + fragment.size())); 674 } 675 } 676 } 677 } 678 679 std::pair<unsigned, std::vector<std::string>> 680 find(const std::string_view reqUrl, const Node* node = nullptr, 681 size_t pos = 0, std::vector<std::string>* params = nullptr) const 682 { 683 std::vector<std::string> empty; 684 if (params == nullptr) 685 { 686 params = ∅ 687 } 688 689 unsigned found{}; 690 std::vector<std::string> matchParams; 691 692 if (node == nullptr) 693 { 694 node = head(); 695 } 696 if (pos == reqUrl.size()) 697 { 698 return {node->ruleIndex, *params}; 699 } 700 701 auto updateFound = 702 [&found, 703 &matchParams](std::pair<unsigned, std::vector<std::string>>& ret) { 704 if (ret.first != 0U && (found == 0U || found > ret.first)) 705 { 706 found = ret.first; 707 matchParams = std::move(ret.second); 708 } 709 }; 710 711 if (node->paramChildrens[static_cast<size_t>(ParamType::STRING)] != 0U) 712 { 713 size_t epos = pos; 714 for (; epos < reqUrl.size(); epos++) 715 { 716 if (reqUrl[epos] == '/') 717 { 718 break; 719 } 720 } 721 722 if (epos != pos) 723 { 724 params->emplace_back(reqUrl.substr(pos, epos - pos)); 725 std::pair<unsigned, std::vector<std::string>> ret = 726 find(reqUrl, 727 &nodes[node->paramChildrens[static_cast<size_t>( 728 ParamType::STRING)]], 729 epos, params); 730 updateFound(ret); 731 params->pop_back(); 732 } 733 } 734 735 if (node->paramChildrens[static_cast<size_t>(ParamType::PATH)] != 0U) 736 { 737 size_t epos = reqUrl.size(); 738 739 if (epos != pos) 740 { 741 params->emplace_back(reqUrl.substr(pos, epos - pos)); 742 std::pair<unsigned, std::vector<std::string>> ret = 743 find(reqUrl, 744 &nodes[node->paramChildrens[static_cast<size_t>( 745 ParamType::PATH)]], 746 epos, params); 747 updateFound(ret); 748 params->pop_back(); 749 } 750 } 751 752 for (const Node::ChildMap::value_type& kv : node->children) 753 { 754 const std::string& fragment = kv.first; 755 const Node* child = &nodes[kv.second]; 756 757 if (reqUrl.compare(pos, fragment.size(), fragment) == 0) 758 { 759 std::pair<unsigned, std::vector<std::string>> ret = 760 find(reqUrl, child, pos + fragment.size(), params); 761 updateFound(ret); 762 } 763 } 764 765 return {found, matchParams}; 766 } 767 768 void add(const std::string& url, unsigned ruleIndex) 769 { 770 size_t idx = 0; 771 772 for (unsigned i = 0; i < url.size(); i++) 773 { 774 char c = url[i]; 775 if (c == '<') 776 { 777 constexpr static std::array< 778 std::pair<ParamType, std::string_view>, 3> 779 paramTraits = {{ 780 {ParamType::STRING, "<str>"}, 781 {ParamType::STRING, "<string>"}, 782 {ParamType::PATH, "<path>"}, 783 }}; 784 785 for (const std::pair<ParamType, std::string_view>& x : 786 paramTraits) 787 { 788 if (url.compare(i, x.second.size(), x.second) == 0) 789 { 790 size_t index = static_cast<size_t>(x.first); 791 if (nodes[idx].paramChildrens[index] == 0U) 792 { 793 unsigned newNodeIdx = newNode(); 794 nodes[idx].paramChildrens[index] = newNodeIdx; 795 } 796 idx = nodes[idx].paramChildrens[index]; 797 i += static_cast<unsigned>(x.second.size()); 798 break; 799 } 800 } 801 802 i--; 803 } 804 else 805 { 806 std::string piece(&c, 1); 807 if (nodes[idx].children.count(piece) == 0U) 808 { 809 unsigned newNodeIdx = newNode(); 810 nodes[idx].children.emplace(piece, newNodeIdx); 811 } 812 idx = nodes[idx].children[piece]; 813 } 814 } 815 if (nodes[idx].ruleIndex != 0U) 816 { 817 throw std::runtime_error("handler already exists for " + url); 818 } 819 nodes[idx].ruleIndex = ruleIndex; 820 } 821 822 private: 823 void debugNodePrint(Node* n, size_t level) 824 { 825 for (size_t i = 0; i < static_cast<size_t>(ParamType::MAX); i++) 826 { 827 if (n->paramChildrens[i] != 0U) 828 { 829 BMCWEB_LOG_DEBUG << std::string( 830 2U * level, ' ') /*<< "("<<n->paramChildrens[i]<<") "*/; 831 switch (static_cast<ParamType>(i)) 832 { 833 case ParamType::STRING: 834 BMCWEB_LOG_DEBUG << "<str>"; 835 break; 836 case ParamType::PATH: 837 BMCWEB_LOG_DEBUG << "<path>"; 838 break; 839 case ParamType::MAX: 840 BMCWEB_LOG_DEBUG << "<ERROR>"; 841 break; 842 } 843 844 debugNodePrint(&nodes[n->paramChildrens[i]], level + 1); 845 } 846 } 847 for (const Node::ChildMap::value_type& kv : n->children) 848 { 849 BMCWEB_LOG_DEBUG 850 << std::string(2U * level, ' ') /*<< "(" << kv.second << ") "*/ 851 << kv.first; 852 debugNodePrint(&nodes[kv.second], level + 1); 853 } 854 } 855 856 public: 857 void debugPrint() 858 { 859 debugNodePrint(head(), 0U); 860 } 861 862 private: 863 const Node* head() const 864 { 865 return &nodes.front(); 866 } 867 868 Node* head() 869 { 870 return &nodes.front(); 871 } 872 873 unsigned newNode() 874 { 875 nodes.resize(nodes.size() + 1); 876 return static_cast<unsigned>(nodes.size() - 1); 877 } 878 879 std::vector<Node> nodes; 880 }; 881 882 class Router 883 { 884 public: 885 Router() = default; 886 887 DynamicRule& newRuleDynamic(const std::string& rule) 888 { 889 std::unique_ptr<DynamicRule> ruleObject = 890 std::make_unique<DynamicRule>(rule); 891 DynamicRule* ptr = ruleObject.get(); 892 allRules.emplace_back(std::move(ruleObject)); 893 894 return *ptr; 895 } 896 897 template <uint64_t N> 898 typename black_magic::Arguments<N>::type::template rebind<TaggedRule>& 899 newRuleTagged(const std::string& rule) 900 { 901 using RuleT = typename black_magic::Arguments<N>::type::template rebind< 902 TaggedRule>; 903 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 904 RuleT* ptr = ruleObject.get(); 905 allRules.emplace_back(std::move(ruleObject)); 906 907 return *ptr; 908 } 909 910 void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject) 911 { 912 if (ruleObject == nullptr) 913 { 914 return; 915 } 916 for (size_t method = 0, methodBit = 1; method <= methodNotAllowedIndex; 917 method++, methodBit <<= 1) 918 { 919 if ((ruleObject->methodsBitfield & methodBit) > 0U) 920 { 921 perMethods[method].rules.emplace_back(ruleObject); 922 perMethods[method].trie.add( 923 rule, static_cast<unsigned>( 924 perMethods[method].rules.size() - 1U)); 925 // directory case: 926 // request to `/about' url matches `/about/' rule 927 if (rule.size() > 2 && rule.back() == '/') 928 { 929 perMethods[method].trie.add( 930 rule.substr(0, rule.size() - 1), 931 static_cast<unsigned>(perMethods[method].rules.size() - 932 1)); 933 } 934 } 935 } 936 } 937 938 void validate() 939 { 940 for (std::unique_ptr<BaseRule>& rule : allRules) 941 { 942 if (rule) 943 { 944 std::unique_ptr<BaseRule> upgraded = rule->upgrade(); 945 if (upgraded) 946 { 947 rule = std::move(upgraded); 948 } 949 rule->validate(); 950 internalAddRuleObject(rule->rule, rule.get()); 951 } 952 } 953 for (PerMethod& perMethod : perMethods) 954 { 955 perMethod.trie.validate(); 956 } 957 } 958 959 struct FindRoute 960 { 961 BaseRule* rule = nullptr; 962 std::vector<std::string> params; 963 }; 964 965 struct FindRouteResponse 966 { 967 std::string allowHeader; 968 FindRoute route; 969 }; 970 971 FindRoute findRouteByIndex(std::string_view url, size_t index) const 972 { 973 FindRoute route; 974 if (index >= perMethods.size()) 975 { 976 BMCWEB_LOG_CRITICAL << "Bad index???"; 977 return route; 978 } 979 const PerMethod& perMethod = perMethods[index]; 980 std::pair<unsigned, std::vector<std::string>> found = 981 perMethod.trie.find(url); 982 if (found.first >= perMethod.rules.size()) 983 { 984 throw std::runtime_error("Trie internal structure corrupted!"); 985 } 986 // Found a 404 route, switch that in 987 if (found.first != 0U) 988 { 989 route.rule = perMethod.rules[found.first]; 990 route.params = std::move(found.second); 991 } 992 return route; 993 } 994 995 FindRouteResponse findRoute(Request& req) const 996 { 997 FindRouteResponse findRoute; 998 999 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 1000 if (!verb) 1001 { 1002 return findRoute; 1003 } 1004 size_t reqMethodIndex = static_cast<size_t>(*verb); 1005 // Check to see if this url exists at any verb 1006 for (size_t perMethodIndex = 0; perMethodIndex <= maxVerbIndex; 1007 perMethodIndex++) 1008 { 1009 // Make sure it's safe to deference the array at that index 1010 static_assert(maxVerbIndex < 1011 std::tuple_size_v<decltype(perMethods)>); 1012 FindRoute route = findRouteByIndex(req.url().encoded_path(), 1013 perMethodIndex); 1014 if (route.rule == nullptr) 1015 { 1016 continue; 1017 } 1018 if (!findRoute.allowHeader.empty()) 1019 { 1020 findRoute.allowHeader += ", "; 1021 } 1022 HttpVerb thisVerb = static_cast<HttpVerb>(perMethodIndex); 1023 findRoute.allowHeader += httpVerbToString(thisVerb); 1024 if (perMethodIndex == reqMethodIndex) 1025 { 1026 findRoute.route = route; 1027 } 1028 } 1029 return findRoute; 1030 } 1031 1032 // Populate session with user information. 1033 static bool 1034 populateUserInfo(Request& req, 1035 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1036 const dbus::utility::DBusPropertiesMap& userInfoMap) 1037 { 1038 const std::string* userRolePtr = nullptr; 1039 const bool* remoteUser = nullptr; 1040 const bool* passwordExpired = nullptr; 1041 const std::vector<std::string>* userGroups = nullptr; 1042 1043 const bool success = sdbusplus::unpackPropertiesNoThrow( 1044 redfish::dbus_utils::UnpackErrorPrinter(), userInfoMap, 1045 "UserPrivilege", userRolePtr, "RemoteUser", remoteUser, 1046 "UserPasswordExpired", passwordExpired, "UserGroups", userGroups); 1047 1048 if (!success) 1049 { 1050 BMCWEB_LOG_ERROR << "Failed to unpack user properties."; 1051 asyncResp->res.result( 1052 boost::beast::http::status::internal_server_error); 1053 return false; 1054 } 1055 1056 if (userRolePtr != nullptr) 1057 { 1058 req.session->userRole = *userRolePtr; 1059 BMCWEB_LOG_DEBUG << "userName = " << req.session->username 1060 << " userRole = " << *userRolePtr; 1061 } 1062 1063 if (remoteUser == nullptr) 1064 { 1065 BMCWEB_LOG_ERROR << "RemoteUser property missing or wrong type"; 1066 asyncResp->res.result( 1067 boost::beast::http::status::internal_server_error); 1068 return false; 1069 } 1070 bool expired = false; 1071 if (passwordExpired == nullptr) 1072 { 1073 if (!*remoteUser) 1074 { 1075 BMCWEB_LOG_ERROR 1076 << "UserPasswordExpired property is expected for" 1077 " local user but is missing or wrong type"; 1078 asyncResp->res.result( 1079 boost::beast::http::status::internal_server_error); 1080 return false; 1081 } 1082 } 1083 else 1084 { 1085 expired = *passwordExpired; 1086 } 1087 1088 // Set isConfigureSelfOnly based on D-Bus results. This 1089 // ignores the results from both pamAuthenticateUser and the 1090 // value from any previous use of this session. 1091 req.session->isConfigureSelfOnly = expired; 1092 1093 if (userGroups != nullptr) 1094 { 1095 // Populate session with user groups. 1096 for (const auto& userGroup : *userGroups) 1097 { 1098 req.session->userGroups.emplace_back(userGroup); 1099 } 1100 } 1101 1102 return true; 1103 } 1104 1105 static bool 1106 isUserPrivileged(Request& req, 1107 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1108 BaseRule& rule) 1109 { 1110 // Get the user's privileges from the role 1111 redfish::Privileges userPrivileges = 1112 redfish::getUserPrivileges(*req.session); 1113 1114 // Modify privileges if isConfigureSelfOnly. 1115 if (req.session->isConfigureSelfOnly) 1116 { 1117 // Remove all privileges except ConfigureSelf 1118 userPrivileges = userPrivileges.intersection( 1119 redfish::Privileges{"ConfigureSelf"}); 1120 BMCWEB_LOG_DEBUG << "Operation limited to ConfigureSelf"; 1121 } 1122 1123 if (!rule.checkPrivileges(userPrivileges)) 1124 { 1125 asyncResp->res.result(boost::beast::http::status::forbidden); 1126 if (req.session->isConfigureSelfOnly) 1127 { 1128 redfish::messages::passwordChangeRequired( 1129 asyncResp->res, 1130 boost::urls::format( 1131 "/redfish/v1/AccountService/Accounts/{}", 1132 req.session->username)); 1133 } 1134 return false; 1135 } 1136 1137 req.userRole = req.session->userRole; 1138 return true; 1139 } 1140 1141 template <typename CallbackFn> 1142 void afterGetUserInfo(Request& req, 1143 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1144 BaseRule& rule, CallbackFn&& callback, 1145 const boost::system::error_code& ec, 1146 const dbus::utility::DBusPropertiesMap& userInfoMap) 1147 { 1148 if (ec) 1149 { 1150 BMCWEB_LOG_ERROR << "GetUserInfo failed..."; 1151 asyncResp->res.result( 1152 boost::beast::http::status::internal_server_error); 1153 return; 1154 } 1155 1156 if (!populateUserInfo(req, asyncResp, userInfoMap)) 1157 { 1158 BMCWEB_LOG_ERROR << "Failed to populate user information"; 1159 asyncResp->res.result( 1160 boost::beast::http::status::internal_server_error); 1161 return; 1162 } 1163 1164 if (!Router::isUserPrivileged(req, asyncResp, rule)) 1165 { 1166 // User is not privileged 1167 BMCWEB_LOG_ERROR << "Insufficient Privilege"; 1168 asyncResp->res.result(boost::beast::http::status::forbidden); 1169 return; 1170 } 1171 callback(req); 1172 } 1173 1174 template <typename CallbackFn> 1175 void validatePrivilege(Request& req, 1176 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1177 BaseRule& rule, CallbackFn&& callback) 1178 { 1179 if (req.session == nullptr) 1180 { 1181 return; 1182 } 1183 std::string username = req.session->username; 1184 crow::connections::systemBus->async_method_call( 1185 [this, &req, asyncResp, &rule, 1186 callback(std::forward<CallbackFn>(callback))]( 1187 const boost::system::error_code& ec, 1188 const dbus::utility::DBusPropertiesMap& userInfoMap) mutable { 1189 afterGetUserInfo(req, asyncResp, rule, 1190 std::forward<CallbackFn>(callback), ec, 1191 userInfoMap); 1192 }, 1193 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1194 "xyz.openbmc_project.User.Manager", "GetUserInfo", username); 1195 } 1196 1197 template <typename Adaptor> 1198 void handleUpgrade(Request& req, 1199 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1200 Adaptor&& adaptor) 1201 { 1202 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 1203 if (!verb || static_cast<size_t>(*verb) >= perMethods.size()) 1204 { 1205 asyncResp->res.result(boost::beast::http::status::not_found); 1206 return; 1207 } 1208 PerMethod& perMethod = perMethods[static_cast<size_t>(*verb)]; 1209 Trie& trie = perMethod.trie; 1210 std::vector<BaseRule*>& rules = perMethod.rules; 1211 1212 const std::pair<unsigned, std::vector<std::string>>& found = 1213 trie.find(req.url().encoded_path()); 1214 unsigned ruleIndex = found.first; 1215 if (ruleIndex == 0U) 1216 { 1217 BMCWEB_LOG_DEBUG << "Cannot match rules " 1218 << req.url().encoded_path(); 1219 asyncResp->res.result(boost::beast::http::status::not_found); 1220 return; 1221 } 1222 1223 if (ruleIndex >= rules.size()) 1224 { 1225 throw std::runtime_error("Trie internal structure corrupted!"); 1226 } 1227 1228 BaseRule& rule = *rules[ruleIndex]; 1229 size_t methods = rule.getMethods(); 1230 if ((methods & (1U << static_cast<size_t>(*verb))) == 0) 1231 { 1232 BMCWEB_LOG_DEBUG 1233 << "Rule found but method mismatch: " 1234 << req.url().encoded_path() << " with " << req.methodString() 1235 << "(" << static_cast<uint32_t>(*verb) << ") / " << methods; 1236 asyncResp->res.result(boost::beast::http::status::not_found); 1237 return; 1238 } 1239 1240 BMCWEB_LOG_DEBUG << "Matched rule (upgrade) '" << rule.rule << "' " 1241 << static_cast<uint32_t>(*verb) << " / " << methods; 1242 1243 // TODO(ed) This should be able to use std::bind_front, but it doesn't 1244 // appear to work with the std::move on adaptor. 1245 validatePrivilege( 1246 req, asyncResp, rule, 1247 [&rule, asyncResp, adaptor(std::forward<Adaptor>(adaptor))]( 1248 Request& thisReq) mutable { 1249 rule.handleUpgrade(thisReq, asyncResp, std::move(adaptor)); 1250 }); 1251 } 1252 1253 void handle(Request& req, 1254 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1255 { 1256 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 1257 if (!verb || static_cast<size_t>(*verb) >= perMethods.size()) 1258 { 1259 asyncResp->res.result(boost::beast::http::status::not_found); 1260 return; 1261 } 1262 1263 FindRouteResponse foundRoute = findRoute(req); 1264 1265 if (foundRoute.route.rule == nullptr) 1266 { 1267 // Couldn't find a normal route with any verb, try looking for a 404 1268 // route 1269 if (foundRoute.allowHeader.empty()) 1270 { 1271 foundRoute.route = findRouteByIndex(req.url().encoded_path(), 1272 notFoundIndex); 1273 } 1274 else 1275 { 1276 // See if we have a method not allowed (405) handler 1277 foundRoute.route = findRouteByIndex(req.url().encoded_path(), 1278 methodNotAllowedIndex); 1279 } 1280 } 1281 1282 // Fill in the allow header if it's valid 1283 if (!foundRoute.allowHeader.empty()) 1284 { 1285 asyncResp->res.addHeader(boost::beast::http::field::allow, 1286 foundRoute.allowHeader); 1287 } 1288 1289 // If we couldn't find a real route or a 404 route, return a generic 1290 // response 1291 if (foundRoute.route.rule == nullptr) 1292 { 1293 if (foundRoute.allowHeader.empty()) 1294 { 1295 asyncResp->res.result(boost::beast::http::status::not_found); 1296 } 1297 else 1298 { 1299 asyncResp->res.result( 1300 boost::beast::http::status::method_not_allowed); 1301 } 1302 return; 1303 } 1304 1305 BaseRule& rule = *foundRoute.route.rule; 1306 std::vector<std::string> params = std::move(foundRoute.route.params); 1307 1308 BMCWEB_LOG_DEBUG << "Matched rule '" << rule.rule << "' " 1309 << static_cast<uint32_t>(*verb) << " / " 1310 << rule.getMethods(); 1311 1312 if (req.session == nullptr) 1313 { 1314 rule.handle(req, asyncResp, params); 1315 return; 1316 } 1317 validatePrivilege(req, asyncResp, rule, 1318 [&rule, asyncResp, params](Request& thisReq) mutable { 1319 rule.handle(thisReq, asyncResp, params); 1320 }); 1321 } 1322 1323 void debugPrint() 1324 { 1325 for (size_t i = 0; i < perMethods.size(); i++) 1326 { 1327 BMCWEB_LOG_DEBUG << boost::beast::http::to_string( 1328 static_cast<boost::beast::http::verb>(i)); 1329 perMethods[i].trie.debugPrint(); 1330 } 1331 } 1332 1333 std::vector<const std::string*> getRoutes(const std::string& parent) 1334 { 1335 std::vector<const std::string*> ret; 1336 1337 for (const PerMethod& pm : perMethods) 1338 { 1339 std::vector<unsigned> x; 1340 pm.trie.findRouteIndexes(parent, x); 1341 for (unsigned index : x) 1342 { 1343 ret.push_back(&pm.rules[index]->rule); 1344 } 1345 } 1346 return ret; 1347 } 1348 1349 private: 1350 struct PerMethod 1351 { 1352 std::vector<BaseRule*> rules; 1353 Trie trie; 1354 // rule index 0 has special meaning; preallocate it to avoid 1355 // duplication. 1356 PerMethod() : rules(1) {} 1357 }; 1358 1359 std::array<PerMethod, methodNotAllowedIndex + 1> perMethods; 1360 std::vector<std::unique_ptr<BaseRule>> allRules; 1361 }; 1362 } // namespace crow 1363