| #include "redfish_entity_trie.h" | |
| #include <algorithm> | |
| #include <cstddef> | |
| #include <iterator> | |
| #include <limits> | |
| #include <memory> | |
| #include <string> | |
| #include <string_view> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/container/flat_hash_set.h" | |
| #include "absl/log/log.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_split.h" | |
| #include "absl/strings/substitute.h" | |
| #include "absl/synchronization/mutex.h" | |
| #include "authorizer_enums.h" | |
| #include "redfish_entity_trie_node.h" | |
| #include "redfish_privileges.h" | |
| #include "redfish_subtree_privileges_trie_node.h" | |
| namespace milotic::authz { | |
| using ::milotic::authz::internal::RedfishEntityTrieNode; | |
| using ::milotic::authz::internal::RedfishSubtreePrivilegesTrieNode; | |
| namespace { | |
| // When authorizing we can ignore URI fragments. Authorizing /uri#fragment is | |
| // the same as authorizing /uri | |
| std::string_view IgnoreFragment(std::string_view uri) { | |
| return uri.substr(0, uri.find('#')); | |
| } | |
| bool ValidUri(std::string_view uri) { | |
| return !uri.empty() && uri.front() == '/'; | |
| } | |
| absl::StatusOr<std::vector<std::string>> SplitUri(std::string_view uri) { | |
| if (!ValidUri(uri)) { | |
| return absl::InvalidArgumentError(absl::StrCat("Invalid URI: ", uri)); | |
| } | |
| // Depending on if uri ends with /, then the last element of uri_split could | |
| // be empty | |
| std::vector<std::string> uri_split = absl::StrSplit(IgnoreFragment(uri), '/'); | |
| if (!uri_split.empty() && uri_split.back().empty()) { | |
| uri_split.pop_back(); | |
| } | |
| return uri_split; | |
| } | |
| } // namespace | |
| RedfishEntityTrie::RedfishEntityTrie() | |
| : entity_trie_root_(std::make_unique<RedfishEntityTrieNode>()), | |
| subtree_privileges_trie_root_( | |
| std::make_unique<RedfishSubtreePrivilegesTrieNode>()) {} | |
| void RedfishEntityTrie::InsertUri(std::string_view uri, | |
| ecclesia::ResourceEntity entity_tag, | |
| std::size_t node_index_in_pattern_array) { | |
| absl::StatusOr<std::vector<std::string>> uri_split = SplitUri(uri); | |
| if (!uri_split.ok()) { | |
| return; | |
| } | |
| RedfishEntityTrieNode* cur = entity_trie_root_.get(); | |
| // Every URI starts with a / so the first element is always empty | |
| for (size_t i = 1; i < uri_split->size(); ++i) { | |
| cur = cur->GetOrDefaultChild((*uri_split)[i]); | |
| } | |
| cur->SetEntityTag(entity_tag); | |
| cur->SetNodeIndexInPatternArray(node_index_in_pattern_array); | |
| } | |
| const RedfishEntityTrieNode* RedfishEntityTrie::GetTrieNode( | |
| std::string_view uri) const { | |
| absl::StatusOr<std::vector<std::string>> uri_split = SplitUri(uri); | |
| if (!uri_split.ok()) { | |
| return nullptr; | |
| } | |
| const RedfishEntityTrieNode* cur = entity_trie_root_.get(); | |
| for (size_t i = 1; i < uri_split->size(); ++i) { | |
| const RedfishEntityTrieNode* child = cur->GetChild((*uri_split)[i]); | |
| if (child == nullptr) { | |
| return nullptr; | |
| } | |
| cur = child; | |
| } | |
| return cur; | |
| } | |
| ecclesia::ResourceEntity RedfishEntityTrie::GetEntityType( | |
| std::string_view uri) const { | |
| const RedfishEntityTrieNode* cur = GetTrieNode(uri); | |
| if (cur == nullptr) { | |
| return ecclesia::ResourceEntity::kUndefined; | |
| } | |
| return cur->GetEntityTag(); | |
| } | |
| std::size_t RedfishEntityTrie::GetNodeIndexInPatternArray( | |
| std::string_view uri) const { | |
| const RedfishEntityTrieNode* cur = GetTrieNode(uri); | |
| if (cur == nullptr) { | |
| return std::numeric_limits<std::size_t>::max(); | |
| } | |
| return cur->GetNodeIndexInPatternArray(); | |
| } | |
| absl::Status RedfishEntityTrie::AddSubtreeMapping( | |
| SubtreePrivilegeMapping&& subtree_privilege_mapping) { | |
| absl::StatusOr<std::vector<std::string>> uri_split = | |
| SplitUri(subtree_privilege_mapping.url); | |
| if (!uri_split.ok()) { | |
| return uri_split.status(); | |
| } | |
| RedfishSubtreePrivilegesTrieNode* cur = subtree_privileges_trie_root_.get(); | |
| // Every URI starts with a / so the first element is always empty | |
| for (size_t i = 1; i < uri_split->size(); ++i) { | |
| RedfishSubtreePrivilegesTrieNode* child = | |
| cur->GetOrDefaultChild((*uri_split)[i]); | |
| if (child->ContainsPrivilegeForSubtree( | |
| subtree_privilege_mapping.operation, | |
| subtree_privilege_mapping.privileges)) { | |
| return absl::AlreadyExistsError(absl::Substitute( | |
| "URI: $0 already exists in the trie", subtree_privilege_mapping.url)); | |
| } | |
| cur = child; | |
| } | |
| cur->AddPrivilegeForSubtree(subtree_privilege_mapping.operation, | |
| std::move(subtree_privilege_mapping.privileges)); | |
| return absl::OkStatus(); | |
| } | |
| void RedfishEntityTrie::ReplaceSubtreePrivilegeMappings( | |
| std::vector<SubtreePrivilegeMapping>&& subtree_privilege_mappings) { | |
| // Sort based on strings as order of addition of subtree mappings into the | |
| // trie matters. | |
| // TODO: Lazy propagation can be done to eliminate the need for this sort | |
| std::sort(subtree_privilege_mappings.begin(), | |
| subtree_privilege_mappings.end()); | |
| absl::MutexLock lock(subtree_privileges_mutex_); | |
| // Clear the subtree_privileges trie | |
| subtree_privileges_trie_root_->Clear(); | |
| for (auto& mapping : subtree_privilege_mappings) { | |
| // Have to get mapping string first before moving struct. | |
| std::string mapping_string = mapping.ToString(); | |
| absl::Status status = AddSubtreeMapping(std::move(mapping)); | |
| // Log all failures but continue to add mappings | |
| if (!status.ok()) { | |
| LOG(ERROR) << mapping_string << " was not added due to error: " << status; | |
| } | |
| } | |
| } | |
| absl::flat_hash_set<RedfishPrivileges> | |
| RedfishEntityTrie::GetAdditionalSubtreePrivileges( | |
| std::string_view uri, const ecclesia::Operation& operation) const { | |
| absl::flat_hash_set<RedfishPrivileges> privileges_list; | |
| absl::StatusOr<std::vector<std::string>> uri_split = SplitUri(uri); | |
| if (!uri_split.ok()) { | |
| return privileges_list; | |
| } | |
| absl::MutexLock lock(subtree_privileges_mutex_); | |
| RedfishSubtreePrivilegesTrieNode* cur = subtree_privileges_trie_root_.get(); | |
| // increment cur_uri_len to incorporate the / per for loop iteration | |
| for (size_t i = 1; i < uri_split->size(); ++i) { | |
| RedfishSubtreePrivilegesTrieNode* child = cur->GetChild((*uri_split)[i]); | |
| // If the URI does not exist in the trie, return current privileges we | |
| // have gotten | |
| if (child == nullptr) { | |
| return privileges_list; | |
| } | |
| std::vector<RedfishPrivileges> privileges_for_subtree = | |
| child->GetPrivilegesForSubtree(operation); | |
| privileges_list.insert( | |
| std::make_move_iterator(privileges_for_subtree.begin()), | |
| std::make_move_iterator(privileges_for_subtree.end())); | |
| cur = child; | |
| } | |
| return privileges_list; | |
| } | |
| } // namespace milotic::authz |