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