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 req, 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 req, 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(std::shared_ptr<crow::sse_socket::Connection>&, 436 const crow::Request&, 437 const std::shared_ptr<bmcweb::AsyncResp>&)> 438 openHandler; 439 std::function<void(std::shared_ptr<crow::sse_socket::Connection>&)> 440 closeHandler; 441 }; 442 443 template <typename T> 444 struct RuleParameterTraits 445 { 446 using self_t = T; 447 WebSocketRule& websocket() 448 { 449 self_t* self = static_cast<self_t*>(this); 450 WebSocketRule* p = new WebSocketRule(self->rule); 451 p->privilegesSet = self->privilegesSet; 452 self->ruleToUpgrade.reset(p); 453 return *p; 454 } 455 456 SseSocketRule& serverSentEvent() 457 { 458 self_t* self = static_cast<self_t*>(this); 459 SseSocketRule* p = new SseSocketRule(self->rule); 460 self->ruleToUpgrade.reset(p); 461 return *p; 462 } 463 464 self_t& name(std::string_view name) noexcept 465 { 466 self_t* self = static_cast<self_t*>(this); 467 self->nameStr = name; 468 return *self; 469 } 470 471 self_t& methods(boost::beast::http::verb method) 472 { 473 self_t* self = static_cast<self_t*>(this); 474 std::optional<HttpVerb> verb = httpVerbFromBoost(method); 475 if (verb) 476 { 477 self->methodsBitfield = 1U << static_cast<size_t>(*verb); 478 } 479 return *self; 480 } 481 482 template <typename... MethodArgs> 483 self_t& methods(boost::beast::http::verb method, MethodArgs... argsMethod) 484 { 485 self_t* self = static_cast<self_t*>(this); 486 methods(argsMethod...); 487 std::optional<HttpVerb> verb = httpVerbFromBoost(method); 488 if (verb) 489 { 490 self->methodsBitfield |= 1U << static_cast<size_t>(*verb); 491 } 492 return *self; 493 } 494 495 self_t& notFound() 496 { 497 self_t* self = static_cast<self_t*>(this); 498 self->methodsBitfield = 1U << notFoundIndex; 499 return *self; 500 } 501 502 self_t& methodNotAllowed() 503 { 504 self_t* self = static_cast<self_t*>(this); 505 self->methodsBitfield = 1U << methodNotAllowedIndex; 506 return *self; 507 } 508 509 self_t& privileges( 510 const std::initializer_list<std::initializer_list<const char*>>& p) 511 { 512 self_t* self = static_cast<self_t*>(this); 513 for (const std::initializer_list<const char*>& privilege : p) 514 { 515 self->privilegesSet.emplace_back(privilege); 516 } 517 return *self; 518 } 519 520 template <size_t N, typename... MethodArgs> 521 self_t& privileges(const std::array<redfish::Privileges, N>& p) 522 { 523 self_t* self = static_cast<self_t*>(this); 524 for (const redfish::Privileges& privilege : p) 525 { 526 self->privilegesSet.emplace_back(privilege); 527 } 528 return *self; 529 } 530 }; 531 532 class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule> 533 { 534 public: 535 explicit DynamicRule(const std::string& ruleIn) : BaseRule(ruleIn) {} 536 537 void validate() override 538 { 539 if (!erasedHandler) 540 { 541 throw std::runtime_error("no handler for url " + rule); 542 } 543 } 544 545 void handle(const Request& req, 546 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 547 const std::vector<std::string>& params) override 548 { 549 erasedHandler(req, asyncResp, params); 550 } 551 552 template <typename Func> 553 void operator()(Func f) 554 { 555 using boost::callable_traits::args_t; 556 constexpr size_t arity = std::tuple_size<args_t<Func>>::value; 557 constexpr auto is = std::make_integer_sequence<unsigned, arity>{}; 558 erasedHandler = wrap(std::move(f), is); 559 } 560 561 // enable_if Arg1 == request && Arg2 == Response 562 // enable_if Arg1 == request && Arg2 != response 563 // enable_if Arg1 != request 564 565 template <typename Func, unsigned... Indices> 566 std::function<void(const Request&, 567 const std::shared_ptr<bmcweb::AsyncResp>&, 568 const std::vector<std::string>&)> 569 wrap(Func f, std::integer_sequence<unsigned, Indices...> /*is*/) 570 { 571 using function_t = crow::utility::FunctionTraits<Func>; 572 573 auto ret = detail::routing_handler_call_helper::Wrapped< 574 Func, typename function_t::template arg<Indices>...>(); 575 ret.template set<typename function_t::template arg<Indices>...>( 576 std::move(f)); 577 return ret; 578 } 579 580 private: 581 std::function<void(const Request&, 582 const std::shared_ptr<bmcweb::AsyncResp>&, 583 const std::vector<std::string>&)> 584 erasedHandler; 585 }; 586 587 template <typename... Args> 588 class TaggedRule : 589 public BaseRule, 590 public RuleParameterTraits<TaggedRule<Args...>> 591 { 592 public: 593 using self_t = TaggedRule<Args...>; 594 595 explicit TaggedRule(const std::string& ruleIn) : BaseRule(ruleIn) {} 596 597 void validate() override 598 { 599 if (!handler) 600 { 601 throw std::runtime_error("no handler for url " + rule); 602 } 603 } 604 605 template <typename Func> 606 void operator()(Func&& f) 607 { 608 static_assert( 609 black_magic::CallHelper< 610 Func, black_magic::S<crow::Request, 611 std::shared_ptr<bmcweb::AsyncResp>&, 612 Args...>>::value, 613 "Handler type is mismatched with URL parameters"); 614 static_assert( 615 std::is_same< 616 void, 617 decltype(f(std::declval<crow::Request>(), 618 std::declval<std::shared_ptr<bmcweb::AsyncResp>&>(), 619 std::declval<Args>()...))>::value, 620 "Handler function with response argument should have void return type"); 621 622 handler = std::forward<Func>(f); 623 } 624 625 void handle(const Request& req, 626 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 627 const std::vector<std::string>& params) override 628 { 629 detail::routing_handler_call_helper::Call< 630 detail::routing_handler_call_helper::CallParams<decltype(handler)>, 631 0, black_magic::S<Args...>, black_magic::S<>>()( 632 detail::routing_handler_call_helper::CallParams<decltype(handler)>{ 633 handler, params, req, asyncResp}); 634 } 635 636 private: 637 std::function<void(const crow::Request&, 638 const std::shared_ptr<bmcweb::AsyncResp>&, Args...)> 639 handler; 640 }; 641 642 class Trie 643 { 644 public: 645 struct Node 646 { 647 unsigned ruleIndex{}; 648 std::array<size_t, static_cast<size_t>(ParamType::MAX)> 649 paramChildrens{}; 650 using ChildMap = boost::container::flat_map< 651 std::string, unsigned, std::less<>, 652 std::vector<std::pair<std::string, unsigned>>>; 653 ChildMap children; 654 655 bool isSimpleNode() const 656 { 657 return ruleIndex == 0 && 658 std::all_of(std::begin(paramChildrens), 659 std::end(paramChildrens), 660 [](size_t x) { return x == 0U; }); 661 } 662 }; 663 664 Trie() : nodes(1) {} 665 666 private: 667 void optimizeNode(Node* node) 668 { 669 for (size_t x : node->paramChildrens) 670 { 671 if (x == 0U) 672 { 673 continue; 674 } 675 Node* child = &nodes[x]; 676 optimizeNode(child); 677 } 678 if (node->children.empty()) 679 { 680 return; 681 } 682 bool mergeWithChild = true; 683 for (const Node::ChildMap::value_type& kv : node->children) 684 { 685 Node* child = &nodes[kv.second]; 686 if (!child->isSimpleNode()) 687 { 688 mergeWithChild = false; 689 break; 690 } 691 } 692 if (mergeWithChild) 693 { 694 Node::ChildMap merged; 695 for (const Node::ChildMap::value_type& kv : node->children) 696 { 697 Node* child = &nodes[kv.second]; 698 for (const Node::ChildMap::value_type& childKv : 699 child->children) 700 { 701 merged[kv.first + childKv.first] = childKv.second; 702 } 703 } 704 node->children = std::move(merged); 705 optimizeNode(node); 706 } 707 else 708 { 709 for (const Node::ChildMap::value_type& kv : node->children) 710 { 711 Node* child = &nodes[kv.second]; 712 optimizeNode(child); 713 } 714 } 715 } 716 717 void optimize() 718 { 719 optimizeNode(head()); 720 } 721 722 public: 723 void validate() 724 { 725 optimize(); 726 } 727 728 void findRouteIndexes(const std::string& reqUrl, 729 std::vector<unsigned>& routeIndexes, 730 const Node* node = nullptr, unsigned pos = 0) const 731 { 732 if (node == nullptr) 733 { 734 node = head(); 735 } 736 for (const Node::ChildMap::value_type& kv : node->children) 737 { 738 const std::string& fragment = kv.first; 739 const Node* child = &nodes[kv.second]; 740 if (pos >= reqUrl.size()) 741 { 742 if (child->ruleIndex != 0 && fragment != "/") 743 { 744 routeIndexes.push_back(child->ruleIndex); 745 } 746 findRouteIndexes(reqUrl, routeIndexes, child, 747 static_cast<unsigned>(pos + fragment.size())); 748 } 749 else 750 { 751 if (reqUrl.compare(pos, fragment.size(), fragment) == 0) 752 { 753 findRouteIndexes( 754 reqUrl, routeIndexes, child, 755 static_cast<unsigned>(pos + fragment.size())); 756 } 757 } 758 } 759 } 760 761 std::pair<unsigned, std::vector<std::string>> 762 find(const std::string_view reqUrl, const Node* node = nullptr, 763 size_t pos = 0, std::vector<std::string>* params = nullptr) const 764 { 765 std::vector<std::string> empty; 766 if (params == nullptr) 767 { 768 params = ∅ 769 } 770 771 unsigned found{}; 772 std::vector<std::string> matchParams; 773 774 if (node == nullptr) 775 { 776 node = head(); 777 } 778 if (pos == reqUrl.size()) 779 { 780 return {node->ruleIndex, *params}; 781 } 782 783 auto updateFound = 784 [&found, 785 &matchParams](std::pair<unsigned, std::vector<std::string>>& ret) { 786 if (ret.first != 0U && (found == 0U || found > ret.first)) 787 { 788 found = ret.first; 789 matchParams = std::move(ret.second); 790 } 791 }; 792 793 if (node->paramChildrens[static_cast<size_t>(ParamType::STRING)] != 0U) 794 { 795 size_t epos = pos; 796 for (; epos < reqUrl.size(); epos++) 797 { 798 if (reqUrl[epos] == '/') 799 { 800 break; 801 } 802 } 803 804 if (epos != pos) 805 { 806 params->emplace_back(reqUrl.substr(pos, epos - pos)); 807 std::pair<unsigned, std::vector<std::string>> ret = 808 find(reqUrl, 809 &nodes[node->paramChildrens[static_cast<size_t>( 810 ParamType::STRING)]], 811 epos, params); 812 updateFound(ret); 813 params->pop_back(); 814 } 815 } 816 817 if (node->paramChildrens[static_cast<size_t>(ParamType::PATH)] != 0U) 818 { 819 size_t epos = reqUrl.size(); 820 821 if (epos != pos) 822 { 823 params->emplace_back(reqUrl.substr(pos, epos - pos)); 824 std::pair<unsigned, std::vector<std::string>> ret = 825 find(reqUrl, 826 &nodes[node->paramChildrens[static_cast<size_t>( 827 ParamType::PATH)]], 828 epos, params); 829 updateFound(ret); 830 params->pop_back(); 831 } 832 } 833 834 for (const Node::ChildMap::value_type& kv : node->children) 835 { 836 const std::string& fragment = kv.first; 837 const Node* child = &nodes[kv.second]; 838 839 if (reqUrl.compare(pos, fragment.size(), fragment) == 0) 840 { 841 std::pair<unsigned, std::vector<std::string>> ret = 842 find(reqUrl, child, pos + fragment.size(), params); 843 updateFound(ret); 844 } 845 } 846 847 return {found, matchParams}; 848 } 849 850 void add(const std::string& url, unsigned ruleIndex) 851 { 852 size_t idx = 0; 853 854 for (unsigned i = 0; i < url.size(); i++) 855 { 856 char c = url[i]; 857 if (c == '<') 858 { 859 constexpr static std::array< 860 std::pair<ParamType, std::string_view>, 3> 861 paramTraits = {{ 862 {ParamType::STRING, "<str>"}, 863 {ParamType::STRING, "<string>"}, 864 {ParamType::PATH, "<path>"}, 865 }}; 866 867 for (const std::pair<ParamType, std::string_view>& x : 868 paramTraits) 869 { 870 if (url.compare(i, x.second.size(), x.second) == 0) 871 { 872 size_t index = static_cast<size_t>(x.first); 873 if (nodes[idx].paramChildrens[index] == 0U) 874 { 875 unsigned newNodeIdx = newNode(); 876 nodes[idx].paramChildrens[index] = newNodeIdx; 877 } 878 idx = nodes[idx].paramChildrens[index]; 879 i += static_cast<unsigned>(x.second.size()); 880 break; 881 } 882 } 883 884 i--; 885 } 886 else 887 { 888 std::string piece(&c, 1); 889 if (nodes[idx].children.count(piece) == 0U) 890 { 891 unsigned newNodeIdx = newNode(); 892 nodes[idx].children.emplace(piece, newNodeIdx); 893 } 894 idx = nodes[idx].children[piece]; 895 } 896 } 897 if (nodes[idx].ruleIndex != 0U) 898 { 899 throw std::runtime_error("handler already exists for " + url); 900 } 901 nodes[idx].ruleIndex = ruleIndex; 902 } 903 904 private: 905 void debugNodePrint(Node* n, size_t level) 906 { 907 for (size_t i = 0; i < static_cast<size_t>(ParamType::MAX); i++) 908 { 909 if (n->paramChildrens[i] != 0U) 910 { 911 BMCWEB_LOG_DEBUG << std::string( 912 2U * level, ' ') /*<< "("<<n->paramChildrens[i]<<") "*/; 913 switch (static_cast<ParamType>(i)) 914 { 915 case ParamType::STRING: 916 BMCWEB_LOG_DEBUG << "<str>"; 917 break; 918 case ParamType::PATH: 919 BMCWEB_LOG_DEBUG << "<path>"; 920 break; 921 case ParamType::MAX: 922 BMCWEB_LOG_DEBUG << "<ERROR>"; 923 break; 924 } 925 926 debugNodePrint(&nodes[n->paramChildrens[i]], level + 1); 927 } 928 } 929 for (const Node::ChildMap::value_type& kv : n->children) 930 { 931 BMCWEB_LOG_DEBUG 932 << std::string(2U * level, ' ') /*<< "(" << kv.second << ") "*/ 933 << kv.first; 934 debugNodePrint(&nodes[kv.second], level + 1); 935 } 936 } 937 938 public: 939 void debugPrint() 940 { 941 debugNodePrint(head(), 0U); 942 } 943 944 private: 945 const Node* head() const 946 { 947 return &nodes.front(); 948 } 949 950 Node* head() 951 { 952 return &nodes.front(); 953 } 954 955 unsigned newNode() 956 { 957 nodes.resize(nodes.size() + 1); 958 return static_cast<unsigned>(nodes.size() - 1); 959 } 960 961 std::vector<Node> nodes; 962 }; 963 964 class Router 965 { 966 public: 967 Router() = default; 968 969 DynamicRule& newRuleDynamic(const std::string& rule) 970 { 971 std::unique_ptr<DynamicRule> ruleObject = 972 std::make_unique<DynamicRule>(rule); 973 DynamicRule* ptr = ruleObject.get(); 974 allRules.emplace_back(std::move(ruleObject)); 975 976 return *ptr; 977 } 978 979 template <uint64_t N> 980 typename black_magic::Arguments<N>::type::template rebind<TaggedRule>& 981 newRuleTagged(const std::string& rule) 982 { 983 using RuleT = typename black_magic::Arguments<N>::type::template rebind< 984 TaggedRule>; 985 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 986 RuleT* ptr = ruleObject.get(); 987 allRules.emplace_back(std::move(ruleObject)); 988 989 return *ptr; 990 } 991 992 void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject) 993 { 994 if (ruleObject == nullptr) 995 { 996 return; 997 } 998 for (size_t method = 0, methodBit = 1; method <= methodNotAllowedIndex; 999 method++, methodBit <<= 1) 1000 { 1001 if ((ruleObject->methodsBitfield & methodBit) > 0U) 1002 { 1003 perMethods[method].rules.emplace_back(ruleObject); 1004 perMethods[method].trie.add( 1005 rule, static_cast<unsigned>( 1006 perMethods[method].rules.size() - 1U)); 1007 // directory case: 1008 // request to `/about' url matches `/about/' rule 1009 if (rule.size() > 2 && rule.back() == '/') 1010 { 1011 perMethods[method].trie.add( 1012 rule.substr(0, rule.size() - 1), 1013 static_cast<unsigned>(perMethods[method].rules.size() - 1014 1)); 1015 } 1016 } 1017 } 1018 } 1019 1020 void validate() 1021 { 1022 for (std::unique_ptr<BaseRule>& rule : allRules) 1023 { 1024 if (rule) 1025 { 1026 std::unique_ptr<BaseRule> upgraded = rule->upgrade(); 1027 if (upgraded) 1028 { 1029 rule = std::move(upgraded); 1030 } 1031 rule->validate(); 1032 internalAddRuleObject(rule->rule, rule.get()); 1033 } 1034 } 1035 for (PerMethod& perMethod : perMethods) 1036 { 1037 perMethod.trie.validate(); 1038 } 1039 } 1040 1041 struct FindRoute 1042 { 1043 BaseRule* rule = nullptr; 1044 std::vector<std::string> params; 1045 }; 1046 1047 struct FindRouteResponse 1048 { 1049 std::string allowHeader; 1050 FindRoute route; 1051 }; 1052 1053 FindRoute findRouteByIndex(std::string_view url, size_t index) const 1054 { 1055 FindRoute route; 1056 if (index >= perMethods.size()) 1057 { 1058 BMCWEB_LOG_CRITICAL << "Bad index???"; 1059 return route; 1060 } 1061 const PerMethod& perMethod = perMethods[index]; 1062 std::pair<unsigned, std::vector<std::string>> found = 1063 perMethod.trie.find(url); 1064 if (found.first >= perMethod.rules.size()) 1065 { 1066 throw std::runtime_error("Trie internal structure corrupted!"); 1067 } 1068 // Found a 404 route, switch that in 1069 if (found.first != 0U) 1070 { 1071 route.rule = perMethod.rules[found.first]; 1072 route.params = std::move(found.second); 1073 } 1074 return route; 1075 } 1076 1077 FindRouteResponse findRoute(Request& req) const 1078 { 1079 FindRouteResponse findRoute; 1080 1081 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 1082 if (!verb) 1083 { 1084 return findRoute; 1085 } 1086 size_t reqMethodIndex = static_cast<size_t>(*verb); 1087 // Check to see if this url exists at any verb 1088 for (size_t perMethodIndex = 0; perMethodIndex <= maxVerbIndex; 1089 perMethodIndex++) 1090 { 1091 // Make sure it's safe to deference the array at that index 1092 static_assert(maxVerbIndex < 1093 std::tuple_size_v<decltype(perMethods)>); 1094 FindRoute route = findRouteByIndex(req.url().encoded_path(), 1095 perMethodIndex); 1096 if (route.rule == nullptr) 1097 { 1098 continue; 1099 } 1100 if (!findRoute.allowHeader.empty()) 1101 { 1102 findRoute.allowHeader += ", "; 1103 } 1104 HttpVerb thisVerb = static_cast<HttpVerb>(perMethodIndex); 1105 findRoute.allowHeader += httpVerbToString(thisVerb); 1106 if (perMethodIndex == reqMethodIndex) 1107 { 1108 findRoute.route = route; 1109 } 1110 } 1111 return findRoute; 1112 } 1113 1114 // Populate session with user information. 1115 static bool 1116 populateUserInfo(Request& req, 1117 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1118 const dbus::utility::DBusPropertiesMap& userInfoMap) 1119 { 1120 const std::string* userRolePtr = nullptr; 1121 const bool* remoteUser = nullptr; 1122 const bool* passwordExpired = nullptr; 1123 const std::vector<std::string>* userGroups = nullptr; 1124 1125 const bool success = sdbusplus::unpackPropertiesNoThrow( 1126 redfish::dbus_utils::UnpackErrorPrinter(), userInfoMap, 1127 "UserPrivilege", userRolePtr, "RemoteUser", remoteUser, 1128 "UserPasswordExpired", passwordExpired, "UserGroups", userGroups); 1129 1130 if (!success) 1131 { 1132 BMCWEB_LOG_ERROR << "Failed to unpack user properties."; 1133 asyncResp->res.result( 1134 boost::beast::http::status::internal_server_error); 1135 return false; 1136 } 1137 1138 if (userRolePtr != nullptr) 1139 { 1140 req.session->userRole = *userRolePtr; 1141 BMCWEB_LOG_DEBUG << "userName = " << req.session->username 1142 << " userRole = " << *userRolePtr; 1143 } 1144 1145 if (remoteUser == nullptr) 1146 { 1147 BMCWEB_LOG_ERROR << "RemoteUser property missing or wrong type"; 1148 asyncResp->res.result( 1149 boost::beast::http::status::internal_server_error); 1150 return false; 1151 } 1152 bool expired = false; 1153 if (passwordExpired == nullptr) 1154 { 1155 if (!*remoteUser) 1156 { 1157 BMCWEB_LOG_ERROR 1158 << "UserPasswordExpired property is expected for" 1159 " local user but is missing or wrong type"; 1160 asyncResp->res.result( 1161 boost::beast::http::status::internal_server_error); 1162 return false; 1163 } 1164 } 1165 else 1166 { 1167 expired = *passwordExpired; 1168 } 1169 1170 // Set isConfigureSelfOnly based on D-Bus results. This 1171 // ignores the results from both pamAuthenticateUser and the 1172 // value from any previous use of this session. 1173 req.session->isConfigureSelfOnly = expired; 1174 1175 if (userGroups != nullptr) 1176 { 1177 // Populate session with user groups. 1178 for (const auto& userGroup : *userGroups) 1179 { 1180 req.session->userGroups.emplace_back(userGroup); 1181 } 1182 } 1183 1184 return true; 1185 } 1186 1187 static bool isSseRoute(Request& req) 1188 { 1189 return std::any_of(sse_socket::sseRoutes.begin(), 1190 sse_socket::sseRoutes.end(), 1191 [&req](const char* sseRoute) { 1192 return (req.url().encoded_path() == sseRoute); 1193 }); 1194 } 1195 1196 static bool 1197 isUserPrivileged(Request& req, 1198 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1199 BaseRule& rule) 1200 { 1201 // Get the user's privileges from the role 1202 redfish::Privileges userPrivileges = 1203 redfish::getUserPrivileges(*req.session); 1204 1205 // Modify privileges if isConfigureSelfOnly. 1206 if (req.session->isConfigureSelfOnly) 1207 { 1208 // Remove all privileges except ConfigureSelf 1209 userPrivileges = userPrivileges.intersection( 1210 redfish::Privileges{"ConfigureSelf"}); 1211 BMCWEB_LOG_DEBUG << "Operation limited to ConfigureSelf"; 1212 } 1213 1214 if (!rule.checkPrivileges(userPrivileges)) 1215 { 1216 asyncResp->res.result(boost::beast::http::status::forbidden); 1217 if (req.session->isConfigureSelfOnly) 1218 { 1219 redfish::messages::passwordChangeRequired( 1220 asyncResp->res, 1221 boost::urls::format( 1222 "/redfish/v1/AccountService/Accounts/{}", 1223 req.session->username)); 1224 } 1225 return false; 1226 } 1227 1228 req.userRole = req.session->userRole; 1229 return true; 1230 } 1231 1232 template <typename CallbackFn> 1233 void afterGetUserInfo(Request& req, 1234 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1235 BaseRule& rule, CallbackFn&& callback, 1236 const boost::system::error_code& ec, 1237 const dbus::utility::DBusPropertiesMap& userInfoMap) 1238 { 1239 if (ec) 1240 { 1241 BMCWEB_LOG_ERROR << "GetUserInfo failed..."; 1242 asyncResp->res.result( 1243 boost::beast::http::status::internal_server_error); 1244 return; 1245 } 1246 1247 if (!populateUserInfo(req, asyncResp, userInfoMap)) 1248 { 1249 BMCWEB_LOG_ERROR << "Failed to populate user information"; 1250 asyncResp->res.result( 1251 boost::beast::http::status::internal_server_error); 1252 return; 1253 } 1254 1255 if (!Router::isUserPrivileged(req, asyncResp, rule)) 1256 { 1257 // User is not privileged 1258 BMCWEB_LOG_ERROR << "Insufficient Privilege"; 1259 asyncResp->res.result(boost::beast::http::status::forbidden); 1260 return; 1261 } 1262 callback(req); 1263 } 1264 1265 template <typename CallbackFn> 1266 void validatePrivilege(Request& req, 1267 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1268 BaseRule& rule, CallbackFn&& callback) 1269 { 1270 if (req.session == nullptr) 1271 { 1272 return; 1273 } 1274 std::string username = req.session->username; 1275 crow::connections::systemBus->async_method_call( 1276 [this, &req, asyncResp, &rule, 1277 callback(std::forward<CallbackFn>(callback))]( 1278 const boost::system::error_code& ec, 1279 const dbus::utility::DBusPropertiesMap& userInfoMap) mutable { 1280 afterGetUserInfo(req, asyncResp, rule, 1281 std::forward<CallbackFn>(callback), ec, 1282 userInfoMap); 1283 }, 1284 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1285 "xyz.openbmc_project.User.Manager", "GetUserInfo", username); 1286 } 1287 1288 template <typename Adaptor> 1289 void handleUpgrade(Request& req, 1290 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1291 Adaptor&& adaptor) 1292 { 1293 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 1294 if (!verb || static_cast<size_t>(*verb) >= perMethods.size()) 1295 { 1296 asyncResp->res.result(boost::beast::http::status::not_found); 1297 return; 1298 } 1299 PerMethod& perMethod = perMethods[static_cast<size_t>(*verb)]; 1300 Trie& trie = perMethod.trie; 1301 std::vector<BaseRule*>& rules = perMethod.rules; 1302 1303 const std::pair<unsigned, std::vector<std::string>>& found = 1304 trie.find(req.url().encoded_path()); 1305 unsigned ruleIndex = found.first; 1306 if (ruleIndex == 0U) 1307 { 1308 BMCWEB_LOG_DEBUG << "Cannot match rules " 1309 << req.url().encoded_path(); 1310 asyncResp->res.result(boost::beast::http::status::not_found); 1311 return; 1312 } 1313 1314 if (ruleIndex >= rules.size()) 1315 { 1316 throw std::runtime_error("Trie internal structure corrupted!"); 1317 } 1318 1319 BaseRule& rule = *rules[ruleIndex]; 1320 size_t methods = rule.getMethods(); 1321 if ((methods & (1U << static_cast<size_t>(*verb))) == 0) 1322 { 1323 BMCWEB_LOG_DEBUG 1324 << "Rule found but method mismatch: " 1325 << req.url().encoded_path() << " with " << req.methodString() 1326 << "(" << static_cast<uint32_t>(*verb) << ") / " << methods; 1327 asyncResp->res.result(boost::beast::http::status::not_found); 1328 return; 1329 } 1330 1331 BMCWEB_LOG_DEBUG << "Matched rule (upgrade) '" << rule.rule << "' " 1332 << static_cast<uint32_t>(*verb) << " / " << methods; 1333 1334 // TODO(ed) This should be able to use std::bind_front, but it doesn't 1335 // appear to work with the std::move on adaptor. 1336 validatePrivilege( 1337 req, asyncResp, rule, 1338 [&rule, asyncResp, adaptor(std::forward<Adaptor>(adaptor))]( 1339 Request& thisReq) mutable { 1340 rule.handleUpgrade(thisReq, asyncResp, std::move(adaptor)); 1341 }); 1342 } 1343 1344 void handle(Request& req, 1345 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1346 { 1347 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 1348 if (!verb || static_cast<size_t>(*verb) >= perMethods.size()) 1349 { 1350 asyncResp->res.result(boost::beast::http::status::not_found); 1351 return; 1352 } 1353 1354 FindRouteResponse foundRoute = findRoute(req); 1355 1356 if (foundRoute.route.rule == nullptr) 1357 { 1358 // Couldn't find a normal route with any verb, try looking for a 404 1359 // route 1360 if (foundRoute.allowHeader.empty()) 1361 { 1362 foundRoute.route = findRouteByIndex(req.url().encoded_path(), 1363 notFoundIndex); 1364 } 1365 else 1366 { 1367 // See if we have a method not allowed (405) handler 1368 foundRoute.route = findRouteByIndex(req.url().encoded_path(), 1369 methodNotAllowedIndex); 1370 } 1371 } 1372 1373 // Fill in the allow header if it's valid 1374 if (!foundRoute.allowHeader.empty()) 1375 { 1376 asyncResp->res.addHeader(boost::beast::http::field::allow, 1377 foundRoute.allowHeader); 1378 } 1379 1380 // If we couldn't find a real route or a 404 route, return a generic 1381 // response 1382 if (foundRoute.route.rule == nullptr) 1383 { 1384 if (foundRoute.allowHeader.empty()) 1385 { 1386 asyncResp->res.result(boost::beast::http::status::not_found); 1387 } 1388 else 1389 { 1390 asyncResp->res.result( 1391 boost::beast::http::status::method_not_allowed); 1392 } 1393 return; 1394 } 1395 1396 BaseRule& rule = *foundRoute.route.rule; 1397 std::vector<std::string> params = std::move(foundRoute.route.params); 1398 1399 BMCWEB_LOG_DEBUG << "Matched rule '" << rule.rule << "' " 1400 << static_cast<uint32_t>(*verb) << " / " 1401 << rule.getMethods(); 1402 1403 if (req.session == nullptr) 1404 { 1405 rule.handle(req, asyncResp, params); 1406 return; 1407 } 1408 validatePrivilege(req, asyncResp, rule, 1409 [&rule, asyncResp, params](Request& thisReq) mutable { 1410 rule.handle(thisReq, asyncResp, params); 1411 }); 1412 } 1413 1414 void debugPrint() 1415 { 1416 for (size_t i = 0; i < perMethods.size(); i++) 1417 { 1418 BMCWEB_LOG_DEBUG << boost::beast::http::to_string( 1419 static_cast<boost::beast::http::verb>(i)); 1420 perMethods[i].trie.debugPrint(); 1421 } 1422 } 1423 1424 std::vector<const std::string*> getRoutes(const std::string& parent) 1425 { 1426 std::vector<const std::string*> ret; 1427 1428 for (const PerMethod& pm : perMethods) 1429 { 1430 std::vector<unsigned> x; 1431 pm.trie.findRouteIndexes(parent, x); 1432 for (unsigned index : x) 1433 { 1434 ret.push_back(&pm.rules[index]->rule); 1435 } 1436 } 1437 return ret; 1438 } 1439 1440 private: 1441 struct PerMethod 1442 { 1443 std::vector<BaseRule*> rules; 1444 Trie trie; 1445 // rule index 0 has special meaning; preallocate it to avoid 1446 // duplication. 1447 PerMethod() : rules(1) {} 1448 }; 1449 1450 std::array<PerMethod, methodNotAllowedIndex + 1> perMethods; 1451 std::vector<std::unique_ptr<BaseRule>> allRules; 1452 }; 1453 } // namespace crow 1454