Skip to content

Commit

Permalink
Merge pull request #34958 from dimagi/ad/allow-LRUs-to-view-location-…
Browse files Browse the repository at this point in the history
…safe-tab-reports
  • Loading branch information
AddisonDunn authored Aug 7, 2024
2 parents ba26690 + a35579b commit 77b7de6
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 12 deletions.
2 changes: 1 addition & 1 deletion corehq/apps/reports/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def for_user(cls, domain, couch_user):
items = [
viz
for viz in TableauVisualization.objects.filter(domain=domain)
if couch_user.can_view_tableau_viz(domain, f"{viz.id}")
if couch_user.can_view_tableau_viz(domain, viz)
]
return sorted(items, key=lambda v: v.name.lower())

Expand Down
11 changes: 10 additions & 1 deletion corehq/apps/reports/standard/tableau.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
from corehq.apps.reports.models import TableauVisualization
from corehq.apps.domain.decorators import login_and_domain_required
from corehq.apps.domain.views.base import BaseDomainView
from corehq.apps.locations.permissions import location_safe


@location_safe
class TableauView(BaseDomainView):
urlname = 'tableau'
template_name = 'reports/bootstrap3/tableau_template.html'
Expand Down Expand Up @@ -41,10 +43,15 @@ def page_url(self):

@method_decorator(toggles.EMBEDDED_TABLEAU.required_decorator())
def dispatch(self, request, *args, **kwargs):
if not self._authenticate_request(request):
raise Http403
if self.visualization is None:
raise Http404()
return super().dispatch(request, *args, **kwargs)

def _authenticate_request(self, request):
return request.couch_user.can_view_tableau_viz(self.domain, self.visualization)

@property
def page_context(self):
if self.visualization.server.validate_hostname == '':
Expand All @@ -69,6 +76,7 @@ def get(self, request, *args, **kwargs):

@login_and_domain_required
@require_POST
@location_safe
def tableau_visualization_ajax(request, domain):
from requests_toolbelt.adapters import host_header_ssl
visualization_data = {data: value[0] for data, value in dict(request.POST).items()}
Expand All @@ -77,7 +85,8 @@ def tableau_visualization_ajax(request, domain):
target_site = visualization_data.pop('target_site')

# Authenticate
if not request.couch_user.can_view_tableau_viz(domain, visualization_data.pop('viz_id')):
if not request.couch_user.can_view_tableau_viz(domain, TableauVisualization.objects.get(
id=visualization_data.pop('viz_id'))):
raise Http403

if toggles.EMBED_TABLEAU_REPORT_BY_USER.enabled(domain):
Expand Down
111 changes: 104 additions & 7 deletions corehq/apps/reports/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import io
import json
import unittest

from collections import namedtuple
from couchdbkit import ResourceNotFound
from django.http import QueryDict
from django.http import HttpRequest, QueryDict
from django.http.response import Http404
from django.core.exceptions import PermissionDenied
from django.test import TestCase, RequestFactory
from django.urls.base import reverse
from unittest.mock import patch

from corehq.apps.domain.shortcuts import create_domain
from corehq.apps.users.models import HqPermissions, UserRole, WebUser
from corehq.apps.saved_reports.models import ReportConfig, ReportNotification
from corehq.blobs import get_blob_db
from corehq.apps.locations.models import LocationType, make_location
from corehq.apps.reports.models import TableauServer, TableauVisualization
from corehq.apps.reports.standard.tableau import tableau_visualization_ajax, TableauView
from corehq.apps.reports.views import (
MySavedReportsView,
AddSavedReportConfigView,
soft_shift_to_domain_timezone,
soft_shift_to_server_timezone,
)
from django.urls.base import reverse
from corehq.apps.saved_reports.models import ReportConfig, ReportNotification
from corehq.apps.users.models import HqPermissions, UserRole, WebUser
from corehq.blobs import get_blob_db
from corehq.util.test_utils import flag_enabled
from .. import views

from django.http import HttpRequest


REPORT_NAME_LOOKUP = {
'worker_activity': 'corehq.apps.reports.standard.monitoring.WorkerActivityReport'
Expand Down Expand Up @@ -980,3 +985,95 @@ def test_convert_to_domain_timezone(self):

self.assertEqual(report_notification.hour, 22)
self.assertEqual(report_notification.stop_hour, 6)


@flag_enabled('EMBEDDED_TABLEAU')
class TestTableauView(TestReportsBase):

@classmethod
def setUpClass(cls):
super(TestTableauView, cls).setUpClass()
cls.server = TableauServer.objects.create(
domain=cls.DOMAIN,
server_type='server',
server_name='a_server',
target_site='a_site',
)
cls.viz_1 = TableauVisualization.objects.create(
domain=cls.DOMAIN,
server=cls.server,
view_url='a_url',
location_safe=False
)
cls.viz_2 = TableauVisualization.objects.create(
domain=cls.DOMAIN,
server=cls.server,
view_url='a_url2',
location_safe=True
)
cls.role = UserRole.create(cls.DOMAIN, 'Full Tab Access', HqPermissions(
access_all_locations=False, view_tableau=True
))
cls.non_admin_user.set_role(cls.DOMAIN, cls.role.get_qualified_id())
LocationType.objects.get_or_create(
domain=cls.DOMAIN,
name='loc_type',
)
# Need at least one location to access any view
location = make_location(domain=cls.DOMAIN, name='loc', parent=None, location_type='loc_type')
location.save()
cls.non_admin_user.set_location(cls.DOMAIN, location)
cls.non_admin_user.save()

def _get_tableau_view_response(self, viz):
self.log_user_in(self.non_admin_user.username)
response = self.client.get(reverse(TableauView.urlname, args=[self.DOMAIN, viz.id]))
return response

def test_location_restricted_user_cant_access_location_unsafe_report(self):
response = self._get_tableau_view_response(self.viz_1)
self.assertEqual(response.status_code, 403)

def test_no_permission_to_tableau_report(self):
self.role.set_permissions(HqPermissions(
access_all_locations=False, view_tableau=False, view_tableau_list=[str(self.viz_1.id)]).to_list())
response = self._get_tableau_view_response(self.viz_2)
self.assertEqual(response.status_code, 403)

def test_location_restricted_user_can_access_location_safe_report(self):
self.role.set_permissions(HqPermissions(
access_all_locations=False, view_tableau=False, view_tableau_list=[str(self.viz_2.id)]).to_list())
response = self._get_tableau_view_response(self.viz_2)
self.assertEqual(response.status_code, 200)

def _get_ajax_view_response(self, viz):
self.log_user_in(self.non_admin_user.username)
request_data = {
'validate_hostname': '',
'server_name': ['dimagi-tableau-test.us'],
'target_site': ['Test1234'],
'viz_id': [viz.id]
}
response = self.client.post(reverse(tableau_visualization_ajax, args=[self.DOMAIN]), request_data)
return response

def test_ajax_view_location_restricted_user_cant_access_location_unsafe_report(self):
self.role.set_permissions(HqPermissions(
access_all_locations=False, view_tableau=True).to_list())
response = self._get_ajax_view_response(self.viz_1)
self.assertEqual(response.status_code, 403)

def test_ajax_view_no_permission_to_tableau_report(self):
self.role.set_permissions(HqPermissions(
access_all_locations=False, view_tableau=False, view_tableau_list=[str(self.viz_1.id)]).to_list())
response = self._get_ajax_view_response(self.viz_2)
self.assertEqual(response.status_code, 403)

@patch('corehq.apps.reports.standard.tableau.requests.post')
def test_ajax_view_location_restricted_user_can_access_location_safe_report(self, mock_request):
MockResponse = namedtuple('MockResponse', ['status_code', 'content'])
mock_request.return_value = MockResponse(status_code=200, content='1234'.encode('utf-8'))
self.role.set_permissions(HqPermissions(
access_all_locations=False, view_tableau=False, view_tableau_list=[str(self.viz_2.id)]).to_list())
response = self._get_ajax_view_response(self.viz_2)
self.assertEqual(response.status_code, 200)
6 changes: 3 additions & 3 deletions corehq/apps/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,10 +354,10 @@ def access_web_app(self, app_id):
def access_profile(self, profile_id):
return self.edit_user_profile or profile_id in self.edit_user_profile_list

def view_tableau_viz(self, viz_id):
if not self.access_all_locations:
def view_tableau_viz(self, viz):
if not viz.location_safe and not self.access_all_locations:
return False
return self.view_tableau or viz_id in self.view_tableau_list
return self.view_tableau or str(viz.id) in self.view_tableau_list

def has(self, permission, data=None):
if data:
Expand Down

0 comments on commit 77b7de6

Please sign in to comment.