mirror of
https://github.com/Icinga/icinga2.git
synced 2026-02-03 20:40:17 -05:00
Merge commit from fork
Check for permissions when evaluating object filters
This commit is contained in:
commit
56255ac7a6
13 changed files with 647 additions and 42 deletions
|
|
@ -64,6 +64,7 @@ set(base_SOURCES
|
|||
ringbuffer.cpp ringbuffer.hpp
|
||||
scriptframe.cpp scriptframe.hpp
|
||||
scriptglobal.cpp scriptglobal.hpp
|
||||
scriptpermission.cpp scriptpermission.hpp
|
||||
scriptutils.cpp scriptutils.hpp
|
||||
serializer.cpp serializer.hpp
|
||||
shared.hpp
|
||||
|
|
|
|||
|
|
@ -44,14 +44,24 @@ INITIALIZE_ONCE_WITH_PRIORITY([]() {
|
|||
l_StatsNS->Freeze();
|
||||
}, InitializePriority::FreezeNamespaces);
|
||||
|
||||
/**
|
||||
* Construct a @c ScriptFrame that has `Self` assigned to the global namespace.
|
||||
*
|
||||
* Prefer the other constructor if possible since if misused this may leak global variables
|
||||
* without permissions or senstive variables like TicketSalt in a sandboxed context.
|
||||
*
|
||||
* @todo Remove this constructor and call the other with the global namespace in places where it's actually necessary.
|
||||
*/
|
||||
ScriptFrame::ScriptFrame(bool allocLocals)
|
||||
: Locals(allocLocals ? new Dictionary() : nullptr), Self(ScriptGlobal::GetGlobals()), Sandboxed(false), Depth(0)
|
||||
: Locals(allocLocals ? new Dictionary() : nullptr), PermChecker(new ScriptPermissionChecker),
|
||||
Self(ScriptGlobal::GetGlobals()), Sandboxed(false), Depth(0), Globals(nullptr)
|
||||
{
|
||||
InitializeFrame();
|
||||
}
|
||||
|
||||
ScriptFrame::ScriptFrame(bool allocLocals, Value self)
|
||||
: Locals(allocLocals ? new Dictionary() : nullptr), Self(std::move(self)), Sandboxed(false), Depth(0)
|
||||
: Locals(allocLocals ? new Dictionary() : nullptr), PermChecker(new ScriptPermissionChecker), Self(std::move(self)),
|
||||
Sandboxed(false), Depth(0), Globals(nullptr)
|
||||
{
|
||||
InitializeFrame();
|
||||
}
|
||||
|
|
@ -63,6 +73,8 @@ void ScriptFrame::InitializeFrame()
|
|||
if (frames && !frames->empty()) {
|
||||
ScriptFrame *frame = frames->top();
|
||||
|
||||
// See the documentation of `ScriptFrame::Globals` for why these two are inherited and Globals isn't.
|
||||
PermChecker = frame->PermChecker;
|
||||
Sandboxed = frame->Sandboxed;
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +91,41 @@ ScriptFrame::~ScriptFrame()
|
|||
#endif /* I2_DEBUG */
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sanitized copy of the global variables namespace when sandboxed.
|
||||
*
|
||||
* This filters out the TicketSalt variable specifically and any variable for which the
|
||||
* PermChecker does not return 'true'.
|
||||
*
|
||||
* However it specifically keeps the Types, System, and Icinga sub-namespaces, because they're
|
||||
* accessed through globals in ScopeExpression and the user should have access to all Values
|
||||
* contained in these namespaces.
|
||||
*
|
||||
* @return a sanitized copy of the global namespace if sandboxed, a pointer to the global namespace otherwise.
|
||||
*/
|
||||
Namespace::Ptr ScriptFrame::GetGlobals()
|
||||
{
|
||||
if (Sandboxed) {
|
||||
if (!Globals) {
|
||||
Globals = new Namespace;
|
||||
auto globals = ScriptGlobal::GetGlobals();
|
||||
ObjectLock lock{globals};
|
||||
for (auto& [key, val] : globals) {
|
||||
if (key == "TicketSalt") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == "Types" || key == "System" || key == "Icinga" || PermChecker->CanAccessGlobalVariable(key)) {
|
||||
Globals->Set(key, val.Val, val.Const);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Globals;
|
||||
}
|
||||
|
||||
return ScriptGlobal::GetGlobals();
|
||||
}
|
||||
|
||||
void ScriptFrame::IncreaseStackDepth()
|
||||
{
|
||||
if (Depth + 1 > 300)
|
||||
|
|
|
|||
|
|
@ -5,18 +5,30 @@
|
|||
|
||||
#include "base/i2-base.hpp"
|
||||
#include "base/dictionary.hpp"
|
||||
#include "base/array.hpp"
|
||||
#include "base/namespace.hpp"
|
||||
#include "base/scriptpermission.hpp"
|
||||
#include <boost/thread/tss.hpp>
|
||||
#include <stack>
|
||||
|
||||
namespace icinga
|
||||
{
|
||||
|
||||
/**
|
||||
* A frame describing the context a section of script code is executed in.
|
||||
*
|
||||
* This is implemented by each new object that is constructed getting pushed on a thread_local
|
||||
* global stack that is accessible from anywhere during script evaluation.
|
||||
*
|
||||
* Most properties in this frame, like local variables do not carry over to successive frames,
|
||||
* except the `PermChecker` and `Sandboxed` members, which get propagated to enforce access
|
||||
* control and availability of unsafe functions.
|
||||
*/
|
||||
struct ScriptFrame
|
||||
{
|
||||
Dictionary::Ptr Locals;
|
||||
ScriptPermissionChecker::Ptr PermChecker; /* inherited by next frame */
|
||||
Value Self;
|
||||
bool Sandboxed;
|
||||
bool Sandboxed; /* inherited by next frame */
|
||||
int Depth;
|
||||
|
||||
ScriptFrame(bool allocLocals);
|
||||
|
|
@ -28,7 +40,20 @@ struct ScriptFrame
|
|||
|
||||
static ScriptFrame *GetCurrentFrame();
|
||||
|
||||
Namespace::Ptr GetGlobals();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Caches a sanitized version of the global namespace for the current `ScriptFrame`.
|
||||
*
|
||||
* This is a value that is dependent on a ScriptFrame's `Sandboxed` and `CheckPerms`
|
||||
* members. These are both independent of each other and while they are inherited by
|
||||
* subsequent frames themselves, their values can be changed for new frames easily.
|
||||
* Therefore Globals can hold a different value for each ScriptFrame and is not
|
||||
* inherited.
|
||||
*/
|
||||
Namespace::Ptr Globals;
|
||||
|
||||
static boost::thread_specific_ptr<std::stack<ScriptFrame *> > m_ScriptFrames;
|
||||
|
||||
static void PushFrame(ScriptFrame *frame);
|
||||
|
|
|
|||
15
lib/base/scriptpermission.cpp
Normal file
15
lib/base/scriptpermission.cpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
|
||||
|
||||
#include "base/scriptpermission.hpp"
|
||||
|
||||
using namespace icinga;
|
||||
|
||||
bool ScriptPermissionChecker::CanAccessGlobalVariable(const String&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScriptPermissionChecker::CanAccessConfigObject(const ConfigObject::Ptr&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
28
lib/base/scriptpermission.hpp
Normal file
28
lib/base/scriptpermission.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "base/string.hpp"
|
||||
#include "base/shared-object.hpp"
|
||||
#include "base/configobject.hpp"
|
||||
|
||||
namespace icinga {
|
||||
|
||||
class ScriptPermissionChecker : public SharedObject
|
||||
{
|
||||
public:
|
||||
DECLARE_PTR_TYPEDEFS(ScriptPermissionChecker);
|
||||
|
||||
ScriptPermissionChecker() = default;
|
||||
ScriptPermissionChecker(const ScriptPermissionChecker&) = delete;
|
||||
ScriptPermissionChecker(ScriptPermissionChecker&&) = delete;
|
||||
ScriptPermissionChecker& operator=(const ScriptPermissionChecker&) = delete;
|
||||
ScriptPermissionChecker& operator=(ScriptPermissionChecker&&) = delete;
|
||||
|
||||
~ScriptPermissionChecker() override = default;
|
||||
|
||||
virtual bool CanAccessGlobalVariable(const String& varName);
|
||||
virtual bool CanAccessConfigObject(const ConfigObject::Ptr& obj);
|
||||
};
|
||||
|
||||
} // namespace icinga
|
||||
|
|
@ -35,10 +35,10 @@ REGISTER_FUNCTION(System, exit, &Application::Exit, "status");
|
|||
REGISTER_SAFE_FUNCTION(System, typeof, &ScriptUtils::TypeOf, "value");
|
||||
REGISTER_SAFE_FUNCTION(System, keys, &ScriptUtils::Keys, "value");
|
||||
REGISTER_SAFE_FUNCTION(System, random, &Utility::Random, "");
|
||||
REGISTER_SAFE_FUNCTION(System, get_template, &ScriptUtils::GetTemplate, "type:name");
|
||||
REGISTER_SAFE_FUNCTION(System, get_templates, &ScriptUtils::GetTemplates, "type");
|
||||
REGISTER_FUNCTION(System, get_template, &ScriptUtils::GetTemplate, "type:name");
|
||||
REGISTER_FUNCTION(System, get_templates, &ScriptUtils::GetTemplates, "type");
|
||||
REGISTER_SAFE_FUNCTION(System, get_object, &ScriptUtils::GetObject, "type:name");
|
||||
REGISTER_SAFE_FUNCTION(System, get_objects, &ScriptUtils::GetObjects, "type");
|
||||
REGISTER_FUNCTION(System, get_objects, &ScriptUtils::GetObjects, "type");
|
||||
REGISTER_FUNCTION(System, assert, &ScriptUtils::Assert, "value");
|
||||
REGISTER_SAFE_FUNCTION(System, string, &ScriptUtils::CastString, "value");
|
||||
REGISTER_SAFE_FUNCTION(System, number, &ScriptUtils::CastNumber, "value");
|
||||
|
|
@ -46,7 +46,7 @@ REGISTER_SAFE_FUNCTION(System, bool, &ScriptUtils::CastBool, "value");
|
|||
REGISTER_SAFE_FUNCTION(System, get_time, &Utility::GetTime, "");
|
||||
REGISTER_SAFE_FUNCTION(System, basename, &Utility::BaseName, "path");
|
||||
REGISTER_SAFE_FUNCTION(System, dirname, &Utility::DirName, "path");
|
||||
REGISTER_SAFE_FUNCTION(System, getenv, &ScriptUtils::GetEnv, "value");
|
||||
REGISTER_FUNCTION(System, getenv, &ScriptUtils::GetEnv, "value");
|
||||
REGISTER_SAFE_FUNCTION(System, msi_get_component_path, &ScriptUtils::MsiGetComponentPathShim, "component");
|
||||
REGISTER_SAFE_FUNCTION(System, escape_shell_cmd, &Utility::EscapeShellCmd, "cmd");
|
||||
REGISTER_SAFE_FUNCTION(System, escape_shell_arg, &Utility::EscapeShellArg, "arg");
|
||||
|
|
@ -473,7 +473,15 @@ ConfigObject::Ptr ScriptUtils::GetObject(const Value& vtype, const String& name)
|
|||
if (!ctype)
|
||||
return nullptr;
|
||||
|
||||
return ctype->GetObject(name);
|
||||
auto cfgObj = ctype->GetObject(name);
|
||||
if (cfgObj) {
|
||||
auto* frame = ScriptFrame::GetCurrentFrame();
|
||||
if (frame->PermChecker->CanAccessConfigObject(cfgObj)) {
|
||||
return cfgObj;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Array::Ptr ScriptUtils::GetObjects(const Type::Ptr& type)
|
||||
|
|
|
|||
|
|
@ -118,8 +118,10 @@ ExpressionResult VariableExpression::DoEvaluate(ScriptFrame& frame, DebugHint *d
|
|||
return value;
|
||||
else if (VMOps::FindVarImport(frame, m_Imports, m_Variable, &value, m_DebugInfo))
|
||||
return value;
|
||||
else
|
||||
return ScriptGlobal::Get(m_Variable);
|
||||
else if (frame.GetGlobals()->Get(m_Variable, &value))
|
||||
return value;
|
||||
|
||||
BOOST_THROW_EXCEPTION(ScriptError{"Tried to access undefined script variable '" + m_Variable + "'"});
|
||||
}
|
||||
|
||||
bool VariableExpression::GetReference(ScriptFrame& frame, bool init_dict, Value *parent, String *index, DebugHint **dhint) const
|
||||
|
|
@ -138,8 +140,8 @@ bool VariableExpression::GetReference(ScriptFrame& frame, bool init_dict, Value
|
|||
*dhint = new DebugHint((*dhint)->GetChild(m_Variable));
|
||||
} else if (VMOps::FindVarImportRef(frame, m_Imports, m_Variable, parent, m_DebugInfo)) {
|
||||
return true;
|
||||
} else if (ScriptGlobal::Exists(m_Variable)) {
|
||||
*parent = ScriptGlobal::GetGlobals();
|
||||
} else if (frame.GetGlobals()->Contains(m_Variable)) {
|
||||
*parent = frame.GetGlobals();
|
||||
|
||||
if (dhint)
|
||||
*dhint = nullptr;
|
||||
|
|
@ -546,7 +548,7 @@ ExpressionResult GetScopeExpression::DoEvaluate(ScriptFrame& frame, DebugHint *d
|
|||
else if (m_ScopeSpec == ScopeThis)
|
||||
return frame.Self;
|
||||
else if (m_ScopeSpec == ScopeGlobal)
|
||||
return ScriptGlobal::GetGlobals();
|
||||
return frame.GetGlobals();
|
||||
else
|
||||
VERIFY(!"Invalid scope.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,120 @@
|
|||
|
||||
using namespace icinga;
|
||||
|
||||
Dictionary::Ptr FilterUtility::GetTargetForVar(const String& name, const Value& value)
|
||||
{
|
||||
return new Dictionary({
|
||||
{ "name", name },
|
||||
{ "type", value.GetReflectionType()->GetName() },
|
||||
{ "value", value }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls access to an object or variable based on an ApiUser's permissions.
|
||||
*
|
||||
* This is accomplished by caching the generated filter expressions so they don't have to be
|
||||
* regenerated again and again when access is repeatedly checked in script functions and when
|
||||
* evaluating expressions.
|
||||
*/
|
||||
class FilterExprPermissionChecker : public ScriptPermissionChecker
|
||||
{
|
||||
public:
|
||||
DECLARE_PTR_TYPEDEFS(FilterExprPermissionChecker);
|
||||
|
||||
explicit FilterExprPermissionChecker(ApiUser::Ptr user) : m_User(std::move(user)) {}
|
||||
|
||||
/**
|
||||
* Check if the user has the given permission and cache the result if they do.
|
||||
*
|
||||
* This is a wrapper around FilterUtility::CheckPermission() that caches the generated
|
||||
* filter expression for later use when checking permissions inside sandboxed ScriptFrames.
|
||||
*
|
||||
* Like FilterUtility::CheckPermission() an exception is thrown if the user does not have
|
||||
* the requested permission.
|
||||
*
|
||||
* If the user has permission and there is a filter for the given permission, the filter
|
||||
* expression is generated, cached and then a pointer to it is returned, otherwise a
|
||||
* nullptr will be returned.
|
||||
*
|
||||
* Since the optionally returned pointer is a raw-pointer and this class retains ownership
|
||||
* over the expression it is only valid for the lifetime of the @c FilterExprPermissionChecker
|
||||
* object that returned it.
|
||||
*
|
||||
* @param permissionString The permission string to check against the ApiUser member of this class.
|
||||
*
|
||||
* @return a pointer to the generated permission expression if the permission has a filter, or nullptr if not.
|
||||
*/
|
||||
Expression* CheckPermission(const String& permissionString)
|
||||
{
|
||||
auto [it, inserted] = m_PermCache.try_emplace(permissionString);
|
||||
auto& [hasPermission, permissionExpr] = it->second;
|
||||
|
||||
if (inserted) {
|
||||
FilterUtility::CheckPermission(m_User, permissionString, &permissionExpr);
|
||||
} else if (!hasPermission) {
|
||||
BOOST_THROW_EXCEPTION(ScriptError("Missing permission: " + permissionString.ToLower()));
|
||||
}
|
||||
|
||||
hasPermission = true;
|
||||
return permissionExpr.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this object's ApiUser has permissions to access variable `varName`.
|
||||
*
|
||||
* @param varName The name of the variable to check for access
|
||||
*
|
||||
* @return 'true' if the variable can be accessed, 'false' if it can't.
|
||||
*/
|
||||
bool CanAccessGlobalVariable(const String& varName) override
|
||||
{
|
||||
auto obj = FilterUtility::GetTargetForVar(varName, ScriptGlobal::Get(varName));
|
||||
return CheckPermissionAndEvalFilter("variables", obj, "variable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this object's ApiUser has permissions to access ConfigObject `obj`.
|
||||
*
|
||||
* @param obj A pointer to the ConfigObject to check for access
|
||||
*
|
||||
* @return 'true' if the object can be accessed, 'false' if it can't.
|
||||
*/
|
||||
bool CanAccessConfigObject(const ConfigObject::Ptr& obj) override
|
||||
{
|
||||
ASSERT(obj);
|
||||
|
||||
String perm = "objects/query/" + obj->GetReflectionType()->GetName();
|
||||
String varName = obj->GetReflectionType()->GetName().ToLower();
|
||||
|
||||
return CheckPermissionAndEvalFilter(perm, obj, varName);
|
||||
}
|
||||
|
||||
private:
|
||||
bool CheckPermissionAndEvalFilter(const String& permissionString, const Object::Ptr& obj, const String& varName)
|
||||
{
|
||||
auto [it, inserted] = m_PermCache.try_emplace(permissionString);
|
||||
auto& [hasPermission, permissionExpr] = it->second;
|
||||
|
||||
if (inserted) {
|
||||
hasPermission = FilterUtility::HasPermission(m_User, permissionString, &permissionExpr);
|
||||
}
|
||||
|
||||
if (hasPermission && permissionExpr) {
|
||||
ScriptFrame permissionFrame(false, new Namespace());
|
||||
// Sandboxing is lifted because this only evaluates the function from the
|
||||
// ApiUser->permissions->filter
|
||||
permissionFrame.Sandboxed = false;
|
||||
return FilterUtility::EvaluateFilter(permissionFrame, permissionExpr.get(), obj, varName);
|
||||
}
|
||||
|
||||
return hasPermission;
|
||||
}
|
||||
|
||||
std::unordered_map<String, std::pair<bool, std::unique_ptr<Expression>>> m_PermCache;
|
||||
ApiUser::Ptr m_User;
|
||||
};
|
||||
|
||||
Type::Ptr FilterUtility::TypeFromPluralName(const String& pluralName)
|
||||
{
|
||||
String uname = pluralName;
|
||||
|
|
@ -211,8 +325,8 @@ std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, c
|
|||
else
|
||||
provider = new ConfigObjectTargetProvider();
|
||||
|
||||
std::unique_ptr<Expression> permissionFilter;
|
||||
CheckPermission(user, qd.Permission, &permissionFilter);
|
||||
FilterExprPermissionChecker::Ptr permissionChecker = new FilterExprPermissionChecker{user};
|
||||
auto* permissionFilter = permissionChecker->CheckPermission(qd.Permission);
|
||||
|
||||
Namespace::Ptr permissionFrameNS = new Namespace();
|
||||
ScriptFrame permissionFrame(false, permissionFrameNS);
|
||||
|
|
@ -228,7 +342,7 @@ std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, c
|
|||
String name = HttpUtility::GetLastParameter(query, attr);
|
||||
Object::Ptr target = provider->GetTargetByName(type, name);
|
||||
|
||||
if (!FilterUtility::EvaluateFilter(permissionFrame, permissionFilter.get(), target, variableName))
|
||||
if (!FilterUtility::EvaluateFilter(permissionFrame, permissionFilter, target, variableName))
|
||||
BOOST_THROW_EXCEPTION(ScriptError("Access denied to object '" + name + "' of type '" + type + "'"));
|
||||
|
||||
result.emplace_back(std::move(target));
|
||||
|
|
@ -244,7 +358,7 @@ std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, c
|
|||
for (String name : names) {
|
||||
Object::Ptr target = provider->GetTargetByName(type, name);
|
||||
|
||||
if (!FilterUtility::EvaluateFilter(permissionFrame, permissionFilter.get(), target, variableName))
|
||||
if (!FilterUtility::EvaluateFilter(permissionFrame, permissionFilter, target, variableName))
|
||||
BOOST_THROW_EXCEPTION(ScriptError("Access denied to object '" + name + "' of type '" + type + "'"));
|
||||
|
||||
result.emplace_back(std::move(target));
|
||||
|
|
@ -268,6 +382,7 @@ std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, c
|
|||
Namespace::Ptr frameNS = new Namespace();
|
||||
ScriptFrame frame(false, frameNS);
|
||||
frame.Sandboxed = true;
|
||||
frame.PermChecker = permissionChecker;
|
||||
|
||||
if (query->Contains("filter")) {
|
||||
String filter = HttpUtility::GetLastParameter(query, "filter");
|
||||
|
|
@ -322,7 +437,7 @@ std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, c
|
|||
|
||||
if (targeted) {
|
||||
for (auto& target : targets) {
|
||||
if (FilterUtility::EvaluateFilter(permissionFrame, permissionFilter.get(), target, variableName)) {
|
||||
if (FilterUtility::EvaluateFilter(permissionFrame, permissionFilter, target, variableName)) {
|
||||
result.emplace_back(std::move(target));
|
||||
}
|
||||
}
|
||||
|
|
@ -335,16 +450,16 @@ std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, c
|
|||
}
|
||||
}
|
||||
|
||||
provider->FindTargets(type, [&permissionFrame, &permissionFilter, &frame, &ufilter, &result, variableName](const Object::Ptr& target) {
|
||||
FilteredAddTarget(permissionFrame, permissionFilter.get(), frame, &*ufilter, result, variableName, target);
|
||||
provider->FindTargets(type, [&permissionFrame, permissionFilter, &frame, &ufilter, &result, variableName](const Object::Ptr& target) {
|
||||
FilteredAddTarget(permissionFrame, permissionFilter, frame, &*ufilter, result, variableName, target);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
/* Ensure to pass a nullptr as filter expression.
|
||||
* GCC 8.1.1 on F28 causes problems, see GH #6533.
|
||||
*/
|
||||
provider->FindTargets(type, [&permissionFrame, &permissionFilter, &frame, &result, variableName](const Object::Ptr& target) {
|
||||
FilteredAddTarget(permissionFrame, permissionFilter.get(), frame, nullptr, result, variableName, target);
|
||||
provider->FindTargets(type, [&permissionFrame, permissionFilter, &frame, &result, variableName](const Object::Ptr& target) {
|
||||
FilteredAddTarget(permissionFrame, permissionFilter, frame, nullptr, result, variableName, target);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ struct QueryDescription
|
|||
class FilterUtility
|
||||
{
|
||||
public:
|
||||
|
||||
static Dictionary::Ptr GetTargetForVar(const String& name, const Value& value);
|
||||
static Type::Ptr TypeFromPluralName(const String& pluralName);
|
||||
static void CheckPermission(const ApiUser::Ptr& user, const String& permission, std::unique_ptr<Expression>* filter = nullptr);
|
||||
static bool HasPermission(const ApiUser::Ptr& user, const String& permission, std::unique_ptr<Expression>* permissionFilter = nullptr);
|
||||
|
|
|
|||
|
|
@ -19,30 +19,32 @@ class VariableTargetProvider final : public TargetProvider
|
|||
public:
|
||||
DECLARE_PTR_TYPEDEFS(VariableTargetProvider);
|
||||
|
||||
static Dictionary::Ptr GetTargetForVar(const String& name, const Value& value)
|
||||
{
|
||||
return new Dictionary({
|
||||
{ "name", name },
|
||||
{ "type", value.GetReflectionType()->GetName() },
|
||||
{ "value", value }
|
||||
});
|
||||
}
|
||||
|
||||
void FindTargets(const String& type,
|
||||
const std::function<void (const Value&)>& addTarget) const override
|
||||
{
|
||||
{
|
||||
Namespace::Ptr globals = ScriptGlobal::GetGlobals();
|
||||
ObjectLock olock(globals);
|
||||
for (const Namespace::Pair& kv : globals) {
|
||||
addTarget(GetTargetForVar(kv.first, kv.second.Val));
|
||||
Namespace::Ptr globals = ScriptGlobal::GetGlobals();
|
||||
ObjectLock olock(globals);
|
||||
for (auto& [key, value] : globals) {
|
||||
/* We want wo avoid leaking the TicketSalt over the API, so we remove it here,
|
||||
* as early as possible, so it isn't possible to abuse the fact that all of the
|
||||
* global variables we return here later get checked against a user-provided
|
||||
* filter expression that can cause its content to be printed in an error message
|
||||
* or potentially access them otherwise.
|
||||
*/
|
||||
if (key == "TicketSalt") {
|
||||
continue;
|
||||
}
|
||||
|
||||
addTarget(FilterUtility::GetTargetForVar(key, value.Val));
|
||||
}
|
||||
}
|
||||
|
||||
Value GetTargetByName(const String& type, const String& name) const override
|
||||
{
|
||||
return GetTargetForVar(name, ScriptGlobal::Get(name));
|
||||
if (name == "TicketSalt") {
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument{"Access to TicketSalt via /v1/variables is not permitted."});
|
||||
}
|
||||
return FilterUtility::GetTargetForVar(name, ScriptGlobal::Get(name));
|
||||
}
|
||||
|
||||
bool IsValidType(const String& type) const override
|
||||
|
|
@ -99,9 +101,6 @@ bool VariableQueryHandler::HandleRequest(
|
|||
ArrayData results;
|
||||
|
||||
for (Dictionary::Ptr var : objs) {
|
||||
if (var->Get("name") == "TicketSalt")
|
||||
continue;
|
||||
|
||||
results.emplace_back(new Dictionary({
|
||||
{ "name", var->Get("name") },
|
||||
{ "type", var->Get("type") },
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ set(base_test_SOURCES
|
|||
icinga-perfdata.cpp
|
||||
methods-pluginnotificationtask.cpp
|
||||
remote-certificate-fixture.cpp
|
||||
remote-filterutility.cpp
|
||||
remote-configpackageutility.cpp
|
||||
remote-httpserverconnection.cpp
|
||||
remote-httpmessage.cpp
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
using namespace icinga;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(config_ops)
|
||||
BOOST_AUTO_TEST_SUITE(config_ops,
|
||||
*boost::unit_test::label("config"))
|
||||
|
||||
BOOST_AUTO_TEST_CASE(simple)
|
||||
{
|
||||
|
|
@ -243,4 +244,44 @@ BOOST_AUTO_TEST_CASE(advanced)
|
|||
BOOST_CHECK(func->Invoke() == 3);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(sandboxed_ticket_salt)
|
||||
{
|
||||
ScriptFrame frame(true, new Namespace);
|
||||
std::unique_ptr<Expression> expr;
|
||||
|
||||
auto ns = ScriptGlobal::GetGlobals();
|
||||
ns->Set("TicketSalt", "testvalue");
|
||||
|
||||
expr = ConfigCompiler::CompileText("<test>", "TicketSalt");
|
||||
BOOST_CHECK_EQUAL(expr->Evaluate(frame).GetValue(), "testvalue");
|
||||
|
||||
expr = ConfigCompiler::CompileText("<test>", "globals.TicketSalt");
|
||||
BOOST_CHECK_EQUAL(expr->Evaluate(frame).GetValue(), "testvalue");
|
||||
|
||||
expr = ConfigCompiler::CompileText("<test>", "*&TicketSalt");
|
||||
BOOST_CHECK_EQUAL(expr->Evaluate(frame).GetValue(), "testvalue");
|
||||
|
||||
expr = ConfigCompiler::CompileText("<test>", "globals.TicketSalt = {{{other}}}");
|
||||
BOOST_CHECK_NO_THROW(expr->Evaluate(frame));
|
||||
|
||||
frame.Sandboxed = true;
|
||||
ns->Set("TicketSalt", "testvalue", false);
|
||||
|
||||
// Accessing TicketSalt in a sandboxed context is like trying to access a variable that doesn't exist.
|
||||
// In case of direct access, it will throw a ScriptError.
|
||||
expr = ConfigCompiler::CompileText("<test>", "TicketSalt");
|
||||
BOOST_CHECK_THROW(expr->Evaluate(frame).GetValue(), ScriptError);
|
||||
|
||||
// In case of other ways of accessing it, like through the global scope, it evaluates to Empty
|
||||
expr = ConfigCompiler::CompileText("<test>", "globals.TicketSalt");
|
||||
BOOST_CHECK_EQUAL(expr->Evaluate(frame).GetValue(), "");
|
||||
|
||||
// Same for (the different ways of) trying to access it via a reference.
|
||||
expr = ConfigCompiler::CompileText("<test>", "*&TicketSalt");
|
||||
BOOST_CHECK_EQUAL(expr->Evaluate(frame).GetValue(), "");
|
||||
|
||||
expr = ConfigCompiler::CompileText("<test>", "globals.TicketSalt = {{{other}}}");
|
||||
BOOST_CHECK_THROW(expr->Evaluate(frame), ScriptError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
|||
321
test/remote-filterutility.cpp
Normal file
321
test/remote-filterutility.cpp
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
|
||||
|
||||
#include <BoostTestTargetConfig.h>
|
||||
#include "icinga/host.hpp"
|
||||
#include "remote/apiuser.hpp"
|
||||
#include "remote/filterutility.hpp"
|
||||
#include "test/icingaapplication-fixture.hpp"
|
||||
#include "config/configcompiler.hpp"
|
||||
|
||||
using namespace icinga;
|
||||
|
||||
// clang-format off
|
||||
BOOST_AUTO_TEST_SUITE(remote_filterutility,
|
||||
*boost::unit_test::label("config"))
|
||||
// clang-format on
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(safe_function_permissions, IcingaApplicationFixture)
|
||||
{
|
||||
auto createObjects = []() {
|
||||
String config = R"CONFIG({
|
||||
object CheckCommand "dummy" {
|
||||
command = "/bin/echo"
|
||||
}
|
||||
|
||||
object ApiUser "allPermissionsUser" {
|
||||
permissions = [ "*" ]
|
||||
}
|
||||
|
||||
object ApiUser "permissionFilterUser" {
|
||||
permissions = [
|
||||
{
|
||||
permission = "objects/query/Host"
|
||||
filter = {{ host.name == {{{host1}}} }}
|
||||
},
|
||||
{
|
||||
permission = "objects/query/Service"
|
||||
filter = {{ service.name == {{{svc1}}} }}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
object Host "host1" {
|
||||
address = "host1"
|
||||
check_command = "dummy"
|
||||
}
|
||||
|
||||
object Host "host2" {
|
||||
address = "host2"
|
||||
check_command = "dummy"
|
||||
}
|
||||
|
||||
object Service "svc1" {
|
||||
host_name = "host1"
|
||||
check_command = "dummy"
|
||||
}
|
||||
|
||||
object Service "svc2" {
|
||||
host_name = "host2"
|
||||
check_command = "dummy"
|
||||
}
|
||||
})CONFIG";
|
||||
std::unique_ptr<Expression> expr = ConfigCompiler::CompileText("<test>", config);
|
||||
expr->Evaluate(*ScriptFrame::GetCurrentFrame());
|
||||
};
|
||||
|
||||
ConfigItem::RunWithActivationContext(new Function("CreateTestObjects", createObjects));
|
||||
|
||||
auto allPermissionsUser = ApiUser::GetByName("allPermissionsUser");
|
||||
auto permissionFilterUser = ApiUser::GetByName("permissionFilterUser");
|
||||
|
||||
QueryDescription qd;
|
||||
qd.Types.insert("Host");
|
||||
qd.Types.insert("Service");
|
||||
qd.Permission = "objects/query/Host";
|
||||
|
||||
Dictionary::Ptr queryParams = new Dictionary();
|
||||
queryParams->Set("type", "Host");
|
||||
|
||||
// This is a filter that uses a get_object call on an object the permissionFilterUser
|
||||
// has access to. A second user is tested that has access to everything, to make sure
|
||||
// the filter evaluates properly in the first place.
|
||||
queryParams->Set("filter", "get_object(Host,{{{host1}}}).name == {{{host1}}}");
|
||||
|
||||
std::vector<Value> objs;
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 2);
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
|
||||
// We need to test again with querying services, while still using the get_object(Host) filter,
|
||||
// because we need to verify permissions in filters work regardless of whether the object type
|
||||
// that is queried is the same or different from the one that is checked in the filters.
|
||||
qd.Permission = "objects/query/Service";
|
||||
queryParams->Set("type", "Service");
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 2);
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
|
||||
// Now test again with a filter that always evaluates to false.
|
||||
// Both users shouldn't find objects.
|
||||
queryParams->Set("filter", "get_object(Host,{{{host2}}}).name == {{{host1}}}");
|
||||
qd.Permission = "objects/query/Host";
|
||||
queryParams->Set("type", "Host");
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK(objs.empty());
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK(objs.empty());
|
||||
|
||||
// Again, the same test with querying service objects instead of hosts.
|
||||
qd.Permission = "objects/query/Service";
|
||||
queryParams->Set("type", "Service");
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK(objs.empty());
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK(objs.empty());
|
||||
|
||||
// In the previous asserts we have established that filters work as intended with valid permissions.
|
||||
// Now test again with a valid filter that tries to access a host object the permissionFilterUser
|
||||
// doesn't have access to. It should still return an empty array.
|
||||
queryParams->Set("filter", "get_object(Host,{{{host2}}}).name == {{{host2}}}");
|
||||
qd.Permission = "objects/query/Host";
|
||||
queryParams->Set("type", "Host");
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 2);
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK(objs.empty());
|
||||
|
||||
// Again, the same test with querying service objects instead of hosts.
|
||||
qd.Permission = "objects/query/Service";
|
||||
queryParams->Set("type", "Service");
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 2);
|
||||
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK(objs.empty());
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(variable_expression_permissions, IcingaApplicationFixture)
|
||||
{
|
||||
auto createObjects = []() {
|
||||
String config = R"CONFIG({
|
||||
object CheckCommand "dummy" {
|
||||
command = "/bin/echo"
|
||||
}
|
||||
|
||||
object ApiUser "allPermissionsUser" {
|
||||
permissions = [ "*" ]
|
||||
}
|
||||
|
||||
object ApiUser "permissionFilterUser" {
|
||||
permissions = [
|
||||
"objects/query/Host",
|
||||
{
|
||||
permission = "variables"
|
||||
filter = {{ variable.name != {{{SuperSecretConstant}}} }}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
object ApiUser "noVariablePermUser" {
|
||||
permissions = [ "objects/query/Host" ]
|
||||
}
|
||||
|
||||
object Host "host1" {
|
||||
address = "host1"
|
||||
check_command = "dummy"
|
||||
}
|
||||
})CONFIG";
|
||||
std::unique_ptr<Expression> expr = ConfigCompiler::CompileText("<test>", config);
|
||||
expr->Evaluate(*ScriptFrame::GetCurrentFrame());
|
||||
};
|
||||
|
||||
ConfigItem::RunWithActivationContext(new Function("CreateTestObjects", createObjects));
|
||||
|
||||
auto allPermissionsUser = ApiUser::GetByName("allPermissionsUser");
|
||||
auto permissionFilterUser = ApiUser::GetByName("permissionFilterUser");
|
||||
auto noVariablePermUser = ApiUser::GetByName("noVariablePermUser");
|
||||
|
||||
QueryDescription qd;
|
||||
qd.Types.insert("Host");
|
||||
qd.Types.insert("Service");
|
||||
qd.Permission = "objects/query/Host";
|
||||
|
||||
ScriptGlobal::Set("VeryUsefulConstant", "Test1");
|
||||
ScriptGlobal::Set("SuperSecretConstant", "MyCleartextBankingPassword");
|
||||
ScriptGlobal::Set("TicketSalt", "Test2");
|
||||
|
||||
Dictionary::Ptr queryParams = new Dictionary();
|
||||
queryParams->Set("type", "Host");
|
||||
|
||||
std::vector<Value> objs;
|
||||
|
||||
// First test a simple variable access.
|
||||
// We expect the user with the right permissions to be able to query the object,
|
||||
// while for a user without permission a ScriptError should be thrown.
|
||||
queryParams->Set("filter", "VeryUsefulConstant == {{{Test1}}}");
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
BOOST_REQUIRE_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, noVariablePermUser), ScriptError);
|
||||
|
||||
// The variable can also be referenced and dereferenced.
|
||||
// Unlike the variable access above indirectly accessing the variable without permissions does not
|
||||
// throw a ScriptError but just returns an empty string.
|
||||
queryParams->Set("filter", "*&VeryUsefulConstant == {{{Test1}}}");
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, noVariablePermUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
|
||||
// The variable can also be referenced and dereferenced via get(), which should have the
|
||||
// same result as above.
|
||||
queryParams->Set("filter", "(&VeryUsefulConstant).get() == {{{Test1}}}");
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, noVariablePermUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
|
||||
// Global variables can also be accessed via an IndexerExpression. The result should be the same as above.
|
||||
queryParams->Set("filter", "globals[{{{VeryUsefulConstant}}}] == {{{Test1}}}");
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, noVariablePermUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
|
||||
// Now we verify that a user that isn't allowed to access a constant is not able to do so in a
|
||||
// filter expression.
|
||||
// The allPermissionsUser should be able to access the variable.
|
||||
// The permissionFilterUser should receive an exception because they are specifically
|
||||
// forbidden from reading that variable.
|
||||
// Same for the noVariablePermUser, which as before isn't allowed to read any variable.
|
||||
queryParams->Set("filter", "SuperSecretConstant == {{{MyCleartextBankingPassword}}}");
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
BOOST_REQUIRE_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser), ScriptError);
|
||||
BOOST_REQUIRE_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, noVariablePermUser), ScriptError);
|
||||
|
||||
// Repeat the other ways to access secret variables, again, only the allPermissionsUser should
|
||||
// be able to use it.
|
||||
queryParams->Set("filter", "*&SuperSecretConstant == {{{MyCleartextBankingPassword}}}");
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, noVariablePermUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
|
||||
// Repeat the other ways to access secret variables, again, only the allPermissionsUser should
|
||||
// be able to use it.
|
||||
queryParams->Set("filter", "(&SuperSecretConstant).get() == {{{MyCleartextBankingPassword}}}");
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, noVariablePermUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
|
||||
// Repeat the other ways to access secret variables, again, only the allPermissionsUser should
|
||||
// be able to use it.
|
||||
queryParams->Set("filter", "globals[{{{SuperSecretConstant}}}] == {{{MyCleartextBankingPassword}}}");
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, noVariablePermUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
|
||||
// We also need to verify that even a user with all permissions can not access the TicketSalt variable.
|
||||
// Like in the other cases above, direct access should throw.
|
||||
queryParams->Set("filter", "TicketSalt == {{{Test2}}}");
|
||||
BOOST_REQUIRE_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser), ScriptError);
|
||||
BOOST_REQUIRE_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser), ScriptError);
|
||||
BOOST_REQUIRE_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, noVariablePermUser), ScriptError);
|
||||
|
||||
// Repeat the other ways to access variables with TicketSalt.
|
||||
queryParams->Set("filter", "*&TicketSalt == {{{Test2}}}");
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, noVariablePermUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
|
||||
// Repeat the other ways to access variables with TicketSalt.
|
||||
queryParams->Set("filter", "(&TicketSalt).get() == {{{Test2}}}");
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, noVariablePermUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
|
||||
// Repeat the other ways to access variables with TicketSalt.
|
||||
queryParams->Set("filter", "globals[{{{TicketSalt}}}] == {{{Test2}}}");
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, allPermissionsUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, permissionFilterUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, noVariablePermUser));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 0);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
Loading…
Reference in a new issue