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