Skip to content

Commit

Permalink
Merge pull request #2141 from sadnub/feat-reportingpermissions
Browse files Browse the repository at this point in the history
use user permissions when creating the queryset
  • Loading branch information
wh1te909 authored Feb 24, 2025
2 parents b0cef90 + 6c5a363 commit 6be6876
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 12 deletions.
20 changes: 18 additions & 2 deletions api/tacticalrmm/ee/reporting/tests/test_data_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ def test_resolve_model_no_model_key(self):
class TestBuildingQueryset:
@pytest.fixture
def setup_agents(self):
agent1 = baker.make("agents.Agent", hostname="ZAgent1", plat="windows")
agent2 = baker.make("agents.Agent", hostname="Agent2", plat="windows")
agent1 = baker.make_recipe("agents.agent", hostname="ZAgent1", plat="windows")
agent2 = baker.make_recipe("agents.agent", hostname="Agent2", plat="windows")
return [agent1, agent2]

def test_build_queryset_with_valid_model(self, mock, setup_agents):
Expand Down Expand Up @@ -405,6 +405,22 @@ def test_build_queryset_result_in_json_format(self, mock, setup_agents):

assert isinstance(parsed_result, list)

def test_build_queryset_with_restricted_user(self, mock, setup_agents):
role = baker.make(
"accounts.Role",
can_view_reports=True,
can_view_clients=[setup_agents[0].client],
)
user = baker.make("accounts.User", role=role)

data_source = {"model": Agent}
restricted_result = build_queryset(data_source=data_source, user=user)

data_source = {"model": Agent}
unrestricted_result = build_queryset(data_source=data_source)

assert len(unrestricted_result) > len(restricted_result)


@pytest.mark.django_db
class TestAddingCustomFields:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ def test_generate_report_with_dependencies(
),
variables=report_template.template_variables,
dependencies={"client": 1},
user=authenticated_client.handler._force_user,
)

def test_unauthenticated_generate_report_view(
Expand Down
53 changes: 43 additions & 10 deletions api/tacticalrmm/ee/reporting/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,36 @@
import json
import re
from enum import Enum
from typing import Any, Dict, List, Literal, Optional, Tuple, Type, Union, cast
from typing import (
TYPE_CHECKING,
Any,
Dict,
List,
Literal,
Optional,
Tuple,
Type,
Union,
cast,
)
from zoneinfo import ZoneInfo

import yaml
from django.apps import apps
from jinja2 import Environment, FunctionLoader
from rest_framework.serializers import ValidationError
from tacticalrmm.logger import logger
from tacticalrmm.utils import RE_DB_VALUE, get_db_value
from weasyprint import CSS, HTML
from weasyprint.text.fonts import FontConfiguration

from tacticalrmm.utils import get_db_value

from . import custom_filters
from .constants import REPORTING_MODELS
from .markdown.config import Markdown
from .models import ReportAsset, ReportDataQuery, ReportHTMLTemplate, ReportTemplate
from tacticalrmm.utils import RE_DB_VALUE

if TYPE_CHECKING:
from accounts import User

RE_ASSET_URL = re.compile(
r"(asset://([0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}))"
Expand Down Expand Up @@ -93,6 +106,7 @@ def generate_html(
html_template: Optional[int] = None,
variables: str = "",
dependencies: Optional[Dict[str, int]] = None,
user: Optional["User"] = None,
) -> Tuple[str, Dict[str, Any]]:
if dependencies is None:
dependencies = {}
Expand All @@ -118,7 +132,7 @@ def generate_html(
tm = env.from_string(template_string)

variables_dict = prep_variables_for_template(
variables=variables, dependencies=dependencies
variables=variables, dependencies=dependencies, user=user
)

return (tm.render(css=css, **variables_dict), variables_dict)
Expand Down Expand Up @@ -148,6 +162,7 @@ def prep_variables_for_template(
variables: str,
dependencies: Optional[Dict[str, Any]] = None,
limit_query_results: Optional[int] = None,
user: Optional["User"] = None,
) -> Dict[str, Any]:
if not dependencies:
dependencies = {}
Expand All @@ -163,7 +178,7 @@ def prep_variables_for_template(
# replace the data_sources with the actual data from DB. This will be passed to the template
# in the form of {{data_sources.data_source_name}}
variables_dict = process_data_sources(
variables=variables_dict, limit_query_results=limit_query_results
variables=variables_dict, limit_query_results=limit_query_results, user=user
)

# generate and replace charts in the variables
Expand Down Expand Up @@ -227,7 +242,12 @@ class InvalidDBOperationException(Exception):
pass


def build_queryset(*, data_source: Dict[str, Any], limit: Optional[int] = None) -> Any:
def build_queryset(
*,
data_source: Dict[str, Any],
limit: Optional[int] = None,
user: Optional["User"] = None,
) -> Any:
local_data_source = data_source
Model = local_data_source.pop("model")
count = False
Expand All @@ -242,7 +262,15 @@ def build_queryset(*, data_source: Dict[str, Any], limit: Optional[int] = None)
fields_to_add = []

# create a base reporting queryset
queryset = Model.objects.using("default")
try:
if user and hasattr(Model.objects, "filter_by_role"):
queryset = Model.objects.filter_by_role(user)
else:
queryset = Model.objects.using("default")
except Exception as e:
logger.error(str(e))
queryset = Model.objects.using("default")

model_name = Model.__name__.lower()
for operation, values in local_data_source.items():
# Usage in the build_queryset function:
Expand Down Expand Up @@ -494,7 +522,10 @@ def decode_base64_asset(asset: str) -> bytes:


def process_data_sources(
*, variables: Dict[str, Any], limit_query_results: Optional[int] = None
*,
variables: Dict[str, Any],
limit_query_results: Optional[int] = None,
user: Optional["User"] = None,
) -> Dict[str, Any]:
data_sources = variables.get("data_sources")

Expand All @@ -503,7 +534,9 @@ def process_data_sources(
if isinstance(value, dict):
modified_datasource = resolve_model(data_source=value)
queryset = build_queryset(
data_source=modified_datasource, limit=limit_query_results
data_source=modified_datasource,
limit=limit_query_results,
user=user,
)
data_sources[key] = queryset

Expand Down
2 changes: 2 additions & 0 deletions api/tacticalrmm/ee/reporting/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ def post(self, request: Request, pk: int) -> Union[FileResponse, Response]:
),
variables=template.template_variables,
dependencies=request.data["dependencies"],
user=request.user,
)

html_report = normalize_asset_url(html_report, format)
Expand Down Expand Up @@ -192,6 +193,7 @@ def post(self, request: Request) -> Union[FileResponse, Response]:
html_template=report_data.get("template_html"),
variables=report_data["template_variables"],
dependencies=report_data["dependencies"],
user=request.user,
)

if report_data["debug"]:
Expand Down

0 comments on commit 6be6876

Please sign in to comment.