blob: b3595867e310c9b9b138b2894c4c64da46feac02 [file] [log] [blame]
#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) {
RedfishEntityTrieNode* child = cur->GetOrDefaultChild((*uri_split)[i]);
// If the child is a wildcard path, set cur to IsCollection
// Collection must be set after because collection is checked in
// GetOrDefaultChild
cur->SetCollection((*uri_split)[i].front() == '{');
cur = child;
}
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