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