Skip to content

Commit

Permalink
review viewer permissions (#4556)
Browse files Browse the repository at this point in the history
* review viewer permissions

* update ubid create ali arg

* Fix note permissions

* pass menu object in notes modal

* Fixed notes permissions for viewers

* pin left permission for viewers

* Fixed column list settings permissions for viewers

* De-duplicated users list code

---------

Co-authored-by: Hannah Eslinger <[email protected]>
Co-authored-by: Ross Perry <[email protected]>
Co-authored-by: Alex Swindler <[email protected]>
  • Loading branch information
4 people authored Mar 6, 2024
1 parent 1348d7c commit b73123f
Show file tree
Hide file tree
Showing 35 changed files with 265 additions and 118 deletions.
102 changes: 96 additions & 6 deletions seed/lib/superperms/orgs/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponseForbidden, JsonResponse
from django.utils.datastructures import MultiValueDictKeyError
from rest_framework import status

from seed.data_importer.models import ImportFile, ImportRecord
Expand Down Expand Up @@ -238,12 +239,32 @@ def _wrapped(request, *args, **kwargs):
return decorator


def assert_hierarchy_access(request, property_id_kwarg=None, property_view_id_kwarg=None, param_property_view_id=None, taxlot_view_id_kwarg=None, import_file_id_kwarg=None, param_import_file_id=None, import_record_id_kwarg=None, body_ali_id=None, body_import_file_id=None, body_property_id=None, analysis_id_kwarg=None, ubid_id_kwarg=None, body_import_record_id=None, param_import_record_id=None, goal_id_kwarg=None, *args, **kwargs):
def assert_hierarchy_access(
request,
property_id_kwarg=None,
property_view_id_kwarg=None,
param_property_view_id=None,
taxlot_view_id_kwarg=None,
import_file_id_kwarg=None,
param_import_file_id=None,
import_record_id_kwarg=None,
body_ali_id=None,
body_import_file_id=None,
body_property_id=None,
analysis_id_kwarg=None,
ubid_id_kwarg=None,
body_import_record_id=None,
body_property_state_id=None,
body_taxlot_state_id=None,
param_import_record_id=None,
goal_id_kwarg=None,
*args,
**kwargs
):

"""Helper function to has_hierarchy_access"""
body = request.data
params = request.GET

try:
if property_id_kwarg and property_id_kwarg in kwargs:
property = Property.objects.get(pk=kwargs[property_id_kwarg])
Expand All @@ -253,6 +274,15 @@ def assert_hierarchy_access(request, property_id_kwarg=None, property_view_id_kw
property = Property.objects.get(pk=body[body_property_id])
requests_ali = property.access_level_instance

elif body_property_state_id and body_property_state_id in body:
# there should only be one property_view with a specific property state
property_view = PropertyView.objects.get(state_id=body[body_property_state_id])
requests_ali = property_view.property.access_level_instance

elif body_taxlot_state_id and body_taxlot_state_id in body:
taxlot_view = TaxLotView.objects.get(state_id=body[body_taxlot_state_id])
requests_ali = taxlot_view.taxlot.access_level_instance

elif property_view_id_kwarg and property_view_id_kwarg in kwargs:
property_view = PropertyView.objects.get(pk=kwargs[property_view_id_kwarg])
requests_ali = property_view.property.access_level_instance
Expand Down Expand Up @@ -319,7 +349,7 @@ def assert_hierarchy_access(request, property_id_kwarg=None, property_view_id_kw
property_view = PropertyView.objects.get(pk=request.GET['property_view_id'])
requests_ali = property_view.property.access_level_instance

except ObjectDoesNotExist:
except (ObjectDoesNotExist, MultiValueDictKeyError):
return JsonResponse({
'status': 'error',
'message': 'No such resource.'
Expand All @@ -333,17 +363,77 @@ def assert_hierarchy_access(request, property_id_kwarg=None, property_view_id_kw
}, status=status.HTTP_404_NOT_FOUND)


def has_hierarchy_access(property_id_kwarg=None, property_view_id_kwarg=None, param_property_view_id=None, taxlot_view_id_kwarg=None, import_file_id_kwarg=None, param_import_file_id=None, import_record_id_kwarg=None, body_ali_id=None, body_import_file_id=None, body_property_id=None, analysis_id_kwarg=None, ubid_id_kwarg=None, body_import_record_id=None, param_import_record_id=None, goal_id_kwarg=None):
def has_hierarchy_access(
property_id_kwarg=None,
property_view_id_kwarg=None,
param_property_view_id=None,
taxlot_view_id_kwarg=None,
import_file_id_kwarg=None,
param_import_file_id=None,
import_record_id_kwarg=None,
body_ali_id=None,
body_import_file_id=None,
body_property_id=None,
analysis_id_kwarg=None,
ubid_id_kwarg=None,
body_import_record_id=None,
body_property_state_id=None,
body_taxlot_state_id=None,
param_import_record_id=None,
goal_id_kwarg=None
):
"""Must be called after has_perm_class"""
def decorator(fn):
if 'self' in signature(fn).parameters:
@wraps(fn)
def _wrapped(self, request, *args, **kwargs):
return assert_hierarchy_access(request, property_id_kwarg, property_view_id_kwarg, param_property_view_id, taxlot_view_id_kwarg, import_file_id_kwarg, param_import_file_id, import_record_id_kwarg, body_ali_id, body_import_file_id, body_property_id, analysis_id_kwarg, ubid_id_kwarg, body_import_record_id, param_import_record_id, goal_id_kwarg, *args, **kwargs) or fn(self, request, *args, **kwargs)
return assert_hierarchy_access(
request,
property_id_kwarg,
property_view_id_kwarg,
param_property_view_id,
taxlot_view_id_kwarg,
import_file_id_kwarg,
param_import_file_id,
import_record_id_kwarg,
body_ali_id,
body_import_file_id,
body_property_id,
analysis_id_kwarg,
ubid_id_kwarg,
body_import_record_id,
body_property_state_id,
body_taxlot_state_id,
param_import_record_id,
goal_id_kwarg,
*args,
**kwargs
) or fn(self, request, *args, **kwargs)
else:
@wraps(fn)
def _wrapped(request, *args, **kwargs):
return assert_hierarchy_access(request, property_id_kwarg, property_view_id_kwarg, param_property_view_id, taxlot_view_id_kwarg, import_file_id_kwarg, param_import_file_id, import_record_id_kwarg, body_ali_id, body_import_file_id, body_property_id, analysis_id_kwarg, ubid_id_kwarg, body_import_record_id, param_import_record_id, goal_id_kwarg, *args, **kwargs) or fn(request, *args, **kwargs)
return assert_hierarchy_access(
request,
property_id_kwarg,
property_view_id_kwarg,
param_property_view_id,
taxlot_view_id_kwarg,
import_file_id_kwarg,
param_import_file_id,
import_record_id_kwarg,
body_ali_id,
body_import_file_id,
body_property_id,
analysis_id_kwarg,
ubid_id_kwarg,
body_import_record_id,
body_property_state_id,
body_taxlot_state_id,
param_import_record_id,
goal_id_kwarg,
*args,
**kwargs
) or fn(request, *args, **kwargs)

return _wrapped

Expand Down
18 changes: 15 additions & 3 deletions seed/static/seed/js/controllers/analyses_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,32 @@ angular
'cycles_payload',
'organization_payload',
'organization_service',
'users_payload',
// 'users_payload',
'auth_payload',
'messages_payload',
'urls',
'analyses_service',
'Notification',
// eslint-disable-next-line func-names
function ($scope, analyses_payload, cycles_payload, organization_payload, organization_service, users_payload, auth_payload, messages_payload, urls, analyses_service, Notification) {
function (
$scope,
analyses_payload,
cycles_payload,
organization_payload,
organization_service,
//users_payload,
auth_payload,
messages_payload,
urls,
analyses_service,
Notification
) {
$scope.org = organization_payload.organization;
$scope.auth = auth_payload.auth;
$scope.analyses = analyses_payload.analyses;
$scope.views = analyses_payload.views;
$scope.original_views = analyses_payload.original_views;
$scope.users = users_payload.users;
// $scope.users = users_payload.users;
$scope.messages = messages_payload.messages;
$scope.cycles = cycles_payload.cycles;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ angular.module('BE.seed.controller.inventory_detail_analyses', []).controller('i
'inventory_service',
'inventory_payload',
'analyses_payload',
'users_payload',
// 'users_payload',
'organization_payload',
'views_payload',
'urls',
Expand All @@ -30,7 +30,7 @@ angular.module('BE.seed.controller.inventory_detail_analyses', []).controller('i
inventory_service,
inventory_payload,
analyses_payload,
users_payload,
// users_payload,
organization_payload,
views_payload,
urls,
Expand All @@ -46,7 +46,7 @@ angular.module('BE.seed.controller.inventory_detail_analyses', []).controller('i
$scope.cycle = inventory_payload.cycle;
// WARNING: $scope.org is used by "child" controller - analysis_details_controller
$scope.org = organization_payload.organization;
$scope.users = users_payload.users;
// $scope.users = users_payload.users;
$scope.analyses = analyses_payload.analyses;
$scope.inventory = {
view_id: $stateParams.view_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ angular.module('BE.seed.controller.inventory_detail', []).controller('inventory_
'inventory_payload',
'views_payload',
'analyses_payload',
'users_payload',
// 'users_payload',
'columns',
'derived_columns_payload',
'profiles',
Expand Down Expand Up @@ -66,7 +66,7 @@ angular.module('BE.seed.controller.inventory_detail', []).controller('inventory_
inventory_payload,
views_payload,
analyses_payload,
users_payload,
// users_payload,
columns,
derived_columns_payload,
profiles,
Expand Down Expand Up @@ -162,7 +162,7 @@ angular.module('BE.seed.controller.inventory_detail', []).controller('inventory_
return 0;
})[0];
}
$scope.users = users_payload.users;
// $scope.users = users_payload.users;

// handle popovers cleared on scrolling
[document.getElementsByClassName('ui-view-container')[0], document.getElementById('pin')].forEach((el) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ angular.module('BE.seed.controller.inventory_detail_meters', []).controller('inv
'property_meter_usage',
'spinner_utility',
'urls',
'user_service',
// 'user_service',
'organization_payload',
// eslint-disable-next-line func-names
function (
Expand All @@ -35,7 +35,7 @@ angular.module('BE.seed.controller.inventory_detail_meters', []).controller('inv
property_meter_usage,
spinner_utility,
urls,
user_service,
// user_service,
organization_payload
) {
spinner_utility.show();
Expand All @@ -61,7 +61,7 @@ angular.module('BE.seed.controller.inventory_detail_meters', []).controller('inv

resetSelections();

const deleteButton = '<button type="button" class="btn-primary" style="border-radius: 4px;" ng-click="grid.appScope.open_meter_deletion_modal(row.entity)" translate>Delete</button>';
const deleteButton = '<button type="button" ng-show="grid.appScope.menu.user.organization.user_role !== \'viewer\'" class="btn-primary" style="border-radius: 4px;" ng-click="grid.appScope.open_meter_deletion_modal(row.entity)" translate>Delete</button>';

$scope.meterGridOptions = {
data: 'sorted_meters',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ angular.module('BE.seed.controller.inventory_detail_sensors', []).controller('in
enableHiding: false,
cellTemplate:
'<div style="display: flex; justify-content: space-around; align-content: center">' +
'<button type="button" class="btn-primary" style="border-radius: 4px;" ng-click="grid.appScope.open_sensors_upload_modal(row.entity)" translate>UPLOAD_SENSORS_BUTTON</button>' +
'<button type="button" class="btn-primary" style="border-radius: 4px;" ng-click="grid.appScope.open_sensor_readings_upload_modal(row.entity)" translate>UPLOAD_SENSOR_READINGS_BUTTON</button>' +
'<button type="button" ng-show="grid.appScope.menu.user.organization.user_role !== \'viewer\'" class="btn-primary" style="border-radius: 4px;" ng-click="grid.appScope.open_sensors_upload_modal(row.entity)" translate>UPLOAD_SENSORS_BUTTON</button>' +
'<button type="button" ng-show="grid.appScope.menu.user.organization.user_role !== \'viewer\'" class="btn-primary" style="border-radius: 4px;" ng-click="grid.appScope.open_sensor_readings_upload_modal(row.entity)" translate>UPLOAD_SENSOR_READINGS_BUTTON</button>' +
'</div>',
enableColumnMenu: false,
enableColumnMoving: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ angular.module('BE.seed.controller.inventory_detail_settings', []).controller('i
_.forEach($scope.gridApi.grid.rows, (row) => {
if (row.entity.visible === false) row.setSelected(false);
else row.setSelected(true);
if ($scope.menu.user.organization.user_role === 'viewer') {
row.enableSelection = false;
}
});
});
});
Expand Down Expand Up @@ -150,8 +153,8 @@ angular.module('BE.seed.controller.inventory_detail_settings', []).controller('i
const element = angular.element(selector)[0];
if (element) height += element.offsetHeight;
});
angular.element('#grid-container').css('height', `calc(100vh - ${height + 2}px)`);
angular.element('#grid-container > div').css('height', `calc(100vh - ${height + 4}px)`);
angular.element('#grid-container').css('height', `calc(100vh - ${height}px)`);
angular.element('#grid-container > div').css('height', `calc(100vh - ${height + 2}px)`);
$scope.gridApi.core.handleWindowResize();
};

Expand Down Expand Up @@ -271,7 +274,7 @@ angular.module('BE.seed.controller.inventory_detail_settings', []).controller('i
gridMenuShowHideColumns: false,
minRowsToShow: 30,
rowTemplate:
'<div grid="grid" class="ui-grid-draggable-row" draggable="true"><div ng-repeat="(colRenderIndex, col) in colContainer.renderedColumns track by col.colDef.name" class="ui-grid-cell" ng-class="{ \'ui-grid-row-header-cell\': col.isRowHeader, \'custom\': true }" ui-grid-cell></div></div>',
'<div grid="grid" class="ui-grid-draggable-row" ng-attr-draggable="{$ grid.appScope.menu.user.organization.user_role !== \'viewer\' $}"><div ng-repeat="(colRenderIndex, col) in colContainer.renderedColumns track by col.colDef.name" class="ui-grid-cell" ng-class="{ \'ui-grid-row-header-cell\': col.isRowHeader, \'custom\': true }" ui-grid-cell></div></div>',
columnDefs: [
{
name: 'displayName',
Expand All @@ -289,6 +292,7 @@ angular.module('BE.seed.controller.inventory_detail_settings', []).controller('i

gridApi.selection.on.rowSelectionChanged($scope, rowSelectionChanged);
gridApi.selection.on.rowSelectionChangedBatch($scope, rowSelectionChanged);
gridApi.dragndrop.setDragDisabled($scope.menu.user.organization.user_role === 'viewer');
gridApi.draggableRows.on.rowDropped($scope, modified_service.setModified);

_.delay($scope.updateHeight, 150);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,18 @@ angular.module('BE.seed.controller.inventory_detail_timeline', []).controller('i
'users_payload',
'organization_payload',
// eslint-disable-next-line func-names
function ($scope, $stateParams, $timeout, uiGridConstants, cycles, events, inventory_payload, urls, users_payload, organization_payload) {
function (
$scope,
$stateParams,
$timeout,
uiGridConstants,
cycles,
events,
inventory_payload,
urls,
users_payload,
organization_payload
) {
$scope.organization = organization_payload.organization;
$scope.static_url = urls.static_url;
$scope.cycleNameById = cycles.cycles.reduce((acc, curr) => ({ ...acc, [curr.id]: curr.name }), {});
Expand Down
10 changes: 9 additions & 1 deletion seed/static/seed/js/controllers/inventory_list_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -1642,7 +1642,15 @@ angular.module('BE.seed.controller.inventory_list', []).controller('inventory_li
($state, $stateParams, inventory_service) => (record.inventory_type === 'properties' ? inventory_service.get_property(record.view_id) : inventory_service.get_taxlot(record.view_id))
],
organization_payload: () => organization_payload,
notes: ['note_service', (note_service) => note_service.get_notes($scope.organization.id, record.inventory_type, record.view_id)]
notes: ['note_service', (note_service) => note_service.get_notes($scope.organization.id, record.inventory_type, record.view_id)],
auth_payload: [
'auth_service',
'user_service',
(auth_service, user_service) => {
const organization_id = user_service.get_organization().id;
return auth_service.is_authorized(organization_id, ['requires_member']);
}
]
}
})
.result.then((notes_count) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1224,7 +1224,15 @@ angular.module('BE.seed.controller.inventory_list_legacy', []).controller('inven
($state, $stateParams, inventory_service) => (record.inventory_type === 'properties' ? inventory_service.get_property(record.view_id) : inventory_service.get_taxlot(record.view_id))
],
organization_payload: () => organization_payload,
notes: ['note_service', (note_service) => note_service.get_notes($scope.organization.id, record.inventory_type, record.view_id)]
notes: ['note_service', (note_service) => note_service.get_notes($scope.organization.id, record.inventory_type, record.view_id)],
auth_payload: [
'auth_service',
'user_service',
(auth_service, user_service) => {
const organization_id = user_service.get_organization().id;
return auth_service.is_authorized(organization_id, ['requires_member']);
}
]
}
})
.result.then((notes_count) => {
Expand Down
Loading

0 comments on commit b73123f

Please sign in to comment.