Merge pull request #9408 from Icinga/bugfix/match-api-permissions-against-join-relations

ObjectQueryHandler: Check user permissions on joined relations
This commit is contained in:
Alexander Aleksandrovič Klimov 2022-10-11 13:42:27 +02:00 committed by GitHub
commit 363f4d3fde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 3 deletions

View File

@ -124,13 +124,27 @@ static void FilteredAddTarget(ScriptFrame& permissionFrame, Expression *permissi
}
}
void FilterUtility::CheckPermission(const ApiUser::Ptr& user, const String& permission, Expression **permissionFilter)
/**
* Checks whether the given API user is granted the given permission
*
* When you desire an exception to be raised when the given user doesn't have the given permission,
* you need to use FilterUtility::CheckPermission().
*
* @param user ApiUser pointer to the user object you want to check the permission of
* @param permission The actual permission you want to check the user permission against
* @param permissionFilter Expression pointer that is used as an output buffer for all the filter expressions of the
* individual permissions of the given user to be evaluated. It's up to the caller to delete
* this pointer when it's not needed any more.
*
* @return bool
*/
bool FilterUtility::HasPermission(const ApiUser::Ptr& user, const String& permission, Expression **permissionFilter)
{
if (permissionFilter)
*permissionFilter = nullptr;
if (permission.IsEmpty())
return;
return true;
bool foundPermission = false;
String requiredPermission = permission.ToLower();
@ -172,8 +186,15 @@ void FilterUtility::CheckPermission(const ApiUser::Ptr& user, const String& perm
if (!foundPermission) {
Log(LogWarning, "FilterUtility")
<< "Missing permission: " << requiredPermission;
}
BOOST_THROW_EXCEPTION(ScriptError("Missing permission: " + requiredPermission));
return foundPermission;
}
void FilterUtility::CheckPermission(const ApiUser::Ptr& user, const String& permission, Expression **permissionFilter)
{
if (!HasPermission(user, permission, permissionFilter)) {
BOOST_THROW_EXCEPTION(ScriptError("Missing permission: " + permission.ToLower()));
}
}

View File

@ -52,6 +52,7 @@ class FilterUtility
public:
static Type::Ptr TypeFromPluralName(const String& pluralName);
static void CheckPermission(const ApiUser::Ptr& user, const String& permission, Expression **filter = nullptr);
static bool HasPermission(const ApiUser::Ptr& user, const String& permission, Expression **permissionFilter = nullptr);
static std::vector<Value> GetFilterTargets(const QueryDescription& qd, const Dictionary::Ptr& query,
const ApiUser::Ptr& user, const String& variableName = String());
static bool EvaluateFilter(ScriptFrame& frame, Expression *filter,

View File

@ -8,6 +8,7 @@
#include "base/configtype.hpp"
#include <boost/algorithm/string/case_conv.hpp>
#include <set>
#include <unordered_map>
using namespace icinga;
@ -189,6 +190,9 @@ bool ObjectQueryHandler::HandleRequest(
joinAttrs.insert(field.Name);
}
std::unordered_map<Type*, std::pair<bool, Expression::Ptr>> typePermissions;
std::unordered_map<Object*, bool> objectAccessAllowed;
for (const ConfigObject::Ptr& obj : objs) {
DictionaryData result1{
{ "name", obj->GetName() },
@ -257,6 +261,51 @@ bool ObjectQueryHandler::HandleRequest(
if (!joinedObj)
continue;
Type::Ptr reflectionType = joinedObj->GetReflectionType();
Expression::Ptr permissionFilter;
auto it = typePermissions.find(reflectionType.get());
bool granted;
if (it == typePermissions.end()) {
String permission = "objects/query/" + reflectionType->GetName();
Expression *filter = nullptr;
granted = FilterUtility::HasPermission(user, permission, &filter);
permissionFilter = filter;
typePermissions.insert({reflectionType.get(), std::make_pair(granted, permissionFilter)});
} else {
std::tie(granted, permissionFilter) = it->second;
}
if (!granted) {
// Not authorized
continue;
}
auto relation = objectAccessAllowed.find(joinedObj.get());
bool accessAllowed;
if (relation == objectAccessAllowed.end()) {
ScriptFrame permissionFrame(false, new Namespace());
try {
accessAllowed = FilterUtility::EvaluateFilter(permissionFrame, permissionFilter.get(), joinedObj);
} catch (const ScriptError& err) {
accessAllowed = false;
}
objectAccessAllowed.insert({joinedObj.get(), accessAllowed});
} else {
accessAllowed = relation->second;
}
if (!accessAllowed) {
// Access denied
continue;
}
String prefix = field.NavigationName;
try {