Commit 8a1e7d91 authored by David Matamoros's avatar David Matamoros
Browse files

MDL-72588 reportbuilder: add report access tab.



This tab includes a system report that provides a listing of all
users who can access the report, taking into account all audiences
that have been created for it. Update page of users available
reports to obey audience configuration.

Fix filters JS form to ensure it is only loaded once.

Co-authored-by: Mikel Martín Corrales's avatarMikel Martín <mikel@moodle.com>
parent 144084a2
......@@ -22,6 +22,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['access'] = 'Access';
$string['actions'] = 'Actions';
$string['addaudience'] = 'Add audience \'{$a}\'';
$string['addaudiences'] = 'Add an audience to this report';
......
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
......@@ -59,6 +59,13 @@ const setFilterButtonCount = async(reportElement, filterCount) => {
export const init = (reportId, contextId) => {
const reportElement = document.querySelector(reportSelectors.forReport(reportId));
const filterFormContainer = reportElement.querySelector(reportSelectors.regions.filtersForm);
// Ensure we only add our listeners once (can be called multiple times by mustache template).
if (filterFormContainer.dataset.initialized) {
return;
}
filterFormContainer.dataset.initialized = true;
const filterForm = new DynamicForm(filterFormContainer, '\\core_reportbuilder\\form\\filter');
// Submit report filters.
......
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_reportbuilder\local\systemreports;
use core_reportbuilder\local\models\audience;
use core_reportbuilder\local\models\report;
use core_reportbuilder\permission;
use core_reportbuilder\system_report;
use core_reportbuilder\local\entities\user;
use core_reportbuilder\local\helpers\audience as audience_helper;
/**
* Report access list
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_access_list extends system_report {
/**
* Initialise the report
*/
protected function initialise(): void {
$userentity = new user();
$userentityalias = $userentity->get_table_alias('user');
$this->set_main_table('user', $userentityalias);
$this->add_entity($userentity);
$reportid = $this->get_parameter('id', 0, PARAM_INT);
// Find users allowed to view the report thru the report audiences.
[$wheres, $params] = self::get_users_by_audience_sql($reportid, $userentityalias);
if (!empty($wheres)) {
// Wrap each OR condition into brackets.
$allwheres = '(' . implode(') OR (', $wheres) . ')';
} else {
$allwheres = "1=0";
}
$this->add_base_condition_sql("($allwheres)", $params);
$this->add_column_from_entity('user:fullnamewithpicturelink');
$this->add_filter_from_entity('user:fullname');
$this->set_downloadable(false);
}
/**
* Ensure we can view the report
*
* @return bool
*/
protected function can_view(): bool {
$reportid = $this->get_parameter('id', 0, PARAM_INT);
$reportpersistent = new report($reportid);
return permission::can_edit_report($reportpersistent);
}
/**
* Find users who can access this report based on the audience and add them to the report.
*
* @param int $reportid
* @param string $usertablealias
* @return array
*/
protected static function get_users_by_audience_sql(int $reportid, string $usertablealias): array {
$audiences = audience::get_records(['reportid' => $reportid]);
return audience_helper::user_audience_sql($audiences, $usertablealias);
}
}
......@@ -18,6 +18,9 @@ declare(strict_types=1);
namespace core_reportbuilder\local\systemreports;
use context_system;
use core_reportbuilder\local\helpers\audience;
use core_reportbuilder\local\helpers\database;
use html_writer;
use lang_string;
use moodle_url;
......@@ -65,6 +68,12 @@ class reports_list extends system_report {
$this->add_base_condition_simple('rb.type', self::TYPE_CUSTOM_REPORT);
$this->add_base_fields('rb.id, rb.name, rb.source, rb.type, rb.usercreated'); // Necessary for actions/row class.
// If user can't view all reports, limit the returned list to those reports they can see.
[$where, $params] = $this->filter_by_allowed_reports_sql();
if (!empty($where)) {
$this->add_base_condition_sql($where, $params);
}
// Join user entity for "User modified" column.
$entityuser = new user();
$entityuseralias = $entityuser->get_table_alias('user');
......@@ -297,4 +306,53 @@ class reports_list extends system_report {
'usercreated' => $row->usercreated,
]);
}
/**
* Filters the list of reports to return only the ones the user has access to
*
* - A user with 'editall' capability will have access to all reports.
* - A user with 'edit' capability will have access to:
* - Those reports this user has created.
* - Those reports this user is in audience of.
* - A user with 'view' capability will have access to:
* - Those reports this user is in audience of.
*
* @return array
*/
private function filter_by_allowed_reports_sql(): array {
global $DB, $USER;
// If user can't view all reports, limit the returned list to those reports they can see.
if (!has_capability('moodle/reportbuilder:editall', context_system::instance())) {
$reports = audience::user_reports_list();
if (has_capability('moodle/reportbuilder:edit', context_system::instance())) {
// User can always see own reports and also those reports user is in audience of.
$paramuserid = database::generate_param_name();
if (empty($reports)) {
return ["rb.usercreated = :{$paramuserid}", [$paramuserid => $USER->id]];
}
$prefix = database::generate_param_name() . '_';
[$where, $params] = $DB->get_in_or_equal($reports, SQL_PARAMS_NAMED, $prefix);
$params = array_merge($params, [$paramuserid => $USER->id]);
return ["(rb.usercreated = :{$paramuserid} OR rb.id {$where})", $params];
}
// User has view capability. User can only see those reports user is in audience of.
if (empty($reports)) {
return ['1=2', []];
}
$prefix = database::generate_param_name() . '_';
[$where, $params] = $DB->get_in_or_equal($reports, SQL_PARAMS_NAMED, $prefix);
return ["rb.id {$where}", $params];
}
return ['', []];
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_reportbuilder\output\dynamictabs;
use context_system;
use core\output\dynamic_tabs\base;
use core_reportbuilder\local\models\report;
use core_reportbuilder\local\systemreports\report_access_list;
use core_reportbuilder\permission;
use core_reportbuilder\system_report_factory;
use renderer_base;
/**
* Access dynamic tab
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class access extends base {
/**
* Export this for use in a mustache template context.
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output): array {
$report = system_report_factory::create(report_access_list::class, context_system::instance(), '', '', 0,
['id' => $this->data['reportid']]);
$data['report'] = $report->output();
return $data;
}
/**
* The label to be displayed on the tab
*
* @return string
*/
public function get_tab_label(): string {
return get_string('access', 'core_reportbuilder');
}
/**
* Check permission of the current user to access this tab
*
* @return bool
*/
public function is_available(): bool {
$reportpersistent = new report((int)$this->data['reportid']);
return permission::can_edit_report($reportpersistent);
}
/**
* Template to use to display tab contents
*
* @return string
*/
public function get_template(): string {
return 'core_reportbuilder/local/dynamictabs/access';
}
}
......@@ -19,6 +19,7 @@ declare(strict_types=1);
namespace core_reportbuilder;
use context_system;
use core_reportbuilder\local\helpers\audience;
use core_reportbuilder\local\models\report;
use core_reportbuilder\local\report\base;
......@@ -50,7 +51,11 @@ class permission {
* @return bool
*/
public static function can_view_reports_list(?int $userid = null): bool {
return has_capability('moodle/reportbuilder:view', context_system::instance(), $userid);
return has_any_capability([
'moodle/reportbuilder:editall',
'moodle/reportbuilder:edit',
'moodle/reportbuilder:view',
], context_system::instance(), $userid);
}
/**
......@@ -74,11 +79,16 @@ class permission {
* @return bool
*/
public static function can_view_report(report $report, ?int $userid = null): bool {
if (!static::can_view_reports_list()) {
if (!static::can_view_reports_list($userid)) {
return false;
}
return true; // TODO: Audience.
if (self::can_edit_report($report, $userid)) {
return true;
}
$reports = audience::user_reports_list($userid);
return in_array($report->get('id'), $reports);
}
/**
......
......@@ -27,6 +27,7 @@ declare(strict_types=1);
use core\output\dynamic_tabs;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\output\dynamictabs\access;
use core_reportbuilder\output\dynamictabs\audience;
use core_reportbuilder\output\dynamictabs\editor;
......@@ -54,6 +55,7 @@ $tabdata = ['reportid' => $reportid];
$tabs = [
new editor($tabdata),
new audience($tabdata),
new access($tabdata),
];
echo $OUTPUT->render_from_template('core/dynamic_tabs',
......
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_reportbuilder/local/dynamictabs/access
Template for Access report
Example context (json):
{
"report": "The report content"
}
}}
<div class="reportbuilder-access-container">
{{{ report }}}
</div>
\ No newline at end of file
@core_reportbuilder @javascript
Feature: Configure access to reports based on intended audience
As a manager
I want to restrict which users have access to a report
Background:
Given the following "users" exist:
| username | firstname | lastname |
| user1 | User | 1 |
| user2 | User | 2 |
| user3 | User | 3 |
And the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_user\reportbuilder\datasource\users | 0 |
Scenario: Configure report audience with manually added users audience type
Given the following "users" exist:
| username | firstname | lastname |
| manager1 | Manager | 1 |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| manager1 | manager | System | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/reportbuilder:editall | Allow | manager | System | |
And I log in as "manager1"
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Access" dynamic tab
And I should see "Nothing to display"
And I click on the "Audience" dynamic tab
And I should see "Add an audience to this report"
Then I click on "Add audience 'Manually added users'" "link"
And I set the field "Add users manually" to "User 1,User 3"
And I press "Save changes"
And I should see "User 1"
And I should not see "User 2"
And I should see "User 3"
And I should not see "Add an audience to this report"
And I click on the "Access" dynamic tab
And I should see "User 1"
And I should not see "User 2"
And I should see "User 3"
And I log out
Scenario: Configure report audience with has system role audience type
Given the following "roles" exist:
| shortname | name | archetype |
| testrole | Test role | |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user2 | testrole | System | |
And I log in as "admin"
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Audience" dynamic tab
Then I click on "Add audience 'Assigned system role'" "link"
And I set the field "Select a role" to "Test role"
And I press "Save changes"
And I should see "Test role"
And I log out
Scenario: Configure report audience with Member of cohort audience type
And the following "cohorts" exist:
| name | idnumber |
| Cohort1 | cohort1 |
And I log in as "admin"
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Audience" dynamic tab
Then I click on "Add audience 'Member of cohort'" "link"
And I set the field "Select members from cohort" to "Cohort1"
And I press "Save changes"
And I should see "Cohort1"
And I log out
Scenario: Configure report audience with Member of cohort audience type with no cohorts available
Given I log in as "admin"
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I click on the "Audience" dynamic tab
Then "Add audience 'All users'" "link" should exist
# This audience type should be disabled because there are no cohorts available.
And "Add audience 'Member of cohort'" "link" should not exist
Scenario: View report as a user with no edit capability and set in the report audience
Given the following "core_reportbuilder > Reports" exist:
| name | source |
| My second report | core_user\reportbuilder\datasource\users |
And the following "roles" exist:
| shortname | name | archetype |
| viewreportsrole | Test role | |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user1 | viewreportsrole | System | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/reportbuilder:editall | Prohibit | viewreportsrole | System | |
| moodle/reportbuilder:edit | Prohibit | viewreportsrole | System | |
| moodle/reportbuilder:view | Allow | viewreportsrole | System | |
| moodle/site:configview | Allow | viewreportsrole | System | |
When I log in as "user1"
And I navigate to "Reports > Report builder > Custom reports" in site administration
And I should see "Custom reports"
And I should not see "My report"
And I should not see "My second report"
And I log out
And I log in as "admin"
And I navigate to "Reports > Report builder > Custom reports" in site administration
And I click on "My report" "link" in the "My report" "table_row"
And I click on the "Audience" dynamic tab
And I should see "Add an audience to this report"
Then I click on "Add audience 'Manually added users'" "link"
And I set the field "Add users manually" to "User 1"
And I press "Save changes"
And I log out
And I log in as "user1"
And I navigate to "Reports > Report builder > Custom reports" in site administration
And I should not see "My second report"
And I click on "My report" "link" in the "My report" "table_row"
And I log out
Scenario: View report as a user with edit capability
Given the following "core_reportbuilder > Reports" exist:
| name | source |
| My second report | core_user\reportbuilder\datasource\users |
And the following "roles" exist:
| shortname | name | archetype |
| viewreportsrole | Test role | |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user1 | viewreportsrole | System | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/reportbuilder:editall | Prohibit | viewreportsrole | System | |
| moodle/reportbuilder:edit | Allow | viewreportsrole | System | |
| moodle/reportbuilder:view | Prohibit | viewreportsrole | System | |
| moodle/site:configview | Allow | viewreportsrole | System | |
When I log in as "user1"
And I navigate to "Reports > Report builder > Custom reports" in site administration
And I should see "Custom reports"
And I should not see "My report"
And I should not see "My second report"
And I click on "New report" "button"
And I set the following fields in the "New report" "dialogue" to these values:
| Name | My user1 report |
| Report source | Users |
| Include default setup | 1 |
And I click on "Save" "button" in the "New report" "dialogue"
And I navigate to "Reports > Report builder > Custom reports" in site administration
And I should see "My user1 report"
And I log out
And I log in as "admin"
And I navigate to "Reports > Report builder > Custom reports" in site administration
And I click on "My report" "link" in the "My report" "table_row"
And I click on the "Audience" dynamic tab
And I should see "Add an audience to this report"
Then I click on "Add audience 'Manually added users'" "link"
And I set the field "Add users manually" to "User 1"
And I press "Save changes"
And I log out
And I log in as "user1"
And I navigate to "Reports > Report builder > Custom reports" in site administration
And I should not see "My second report"
And I should see "My user1 report"
And I click on "My report" "link" in the "My report" "table_row"
And I log out
Scenario: View report as a user with editall capability
Given the following "core_reportbuilder > Reports" exist:
| name | source |
| My second report | core_user\reportbuilder\datasource\users |
And the following "roles" exist:
| shortname | name | archetype |
| viewreportsrole | Test role | |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user1 | viewreportsrole | System | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/reportbuilder:editall | Allow | viewreportsrole | System | |
| moodle/reportbuilder:edit | Prohibit | viewreportsrole | System | |
| moodle/reportbuilder:view | Prohibit | viewreportsrole | System | |
| moodle/site:configview | Allow | viewreportsrole | System | |
When I log in as "user1"
And I navigate to "Reports > Report builder > Custom reports" in site administration
And I should see "Custom reports"
And I should see "My report"
Then I click on "My second report" "link" in the "My second report" "table_row"
And I should see "Email address"
And I log out
......@@ -131,22 +131,7 @@ Feature: Manage custom reports
Given the following "core_reportbuilder > Reports" exist:
| name | source |
| My report | core_user\reportbuilder\datasource\users |
Given the following "users" exist:
| username | firstname | lastname | email |
| user1 | User | 1 | user1@example.com |
And the following "roles" exist:
| shortname | name | archetype |
| viewreportsrole | Test role | |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user1 | viewreportsrole | System | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/reportbuilder:editall | Prohibit | viewreportsrole | System | |
| moodle/reportbuilder:edit | Prohibit | viewreportsrole | System | |
| moodle/reportbuilder:view | Allow | viewreportsrole | System | |
| moodle/site:configview | Allow | viewreportsrole | System | |
When I log in as "<user>"
When I log in as "admin"
And I navigate to "Reports > Report builder > Custom reports" in site administration
And I click on "<link>" "link" in the "My report" "table_row"
Then <previewvisible> "Preview" in the "[data-region='core_reportbuilder/report-header']" "css_element"
......@@ -154,7 +139,6 @@ Feature: Manage custom reports
And <settingsvisible> "Settings" in the "[data-region='core_reportbuilder/report-header']" "css_element"
And <filtersvisible> "Filters" in the "[data-region='core_reportbuilder/report-header']" "css_element"
Examples:
| user | link | previewvisible | editvisible | settingsvisible | filtersvisible |
| admin | My report | I should see | I should not see | I should see | I should not see |
| admin | View | I should not see | I should not see | I should not see | I should see |
| user1 | My report | I should not see | I should not see | I should not see | I should see |
| link | previewvisible | editvisible | settingsvisible | filtersvisible |
| My report | I should see | I should not see | I should see | I should not see |
| View | I should not see | I should not see | I should not see | I should see |
......@@ -23,6 +23,7 @@ use context_system;
use core_reportbuilder_generator;
use Throwable;
use core_user\reportbuilder\datasource\users;
use core_reportbuilder\reportbuilder\audience\manual;
/**
* Unit tests for the report permission class
......@@ -64,8 +65,6 @@ class permission_test extends advanced_testcase {
/**
* Test whether user can view specific report
*
* TODO: audiences
*/
public function test_require_can_view_report(): void {
global $DB;
......@@ -96,6 +95,40 @@ class permission_test extends advanced_testcase {
permission::require_can_view_report($report);