Updated motion permission checks.

- Add permission checks in motion form (Fixes #1677)
- Implement get_allowed_actions in motions JavaScript (Fixes #1668)
This commit is contained in:
Emanuel Schuetze 2015-11-19 20:51:39 +01:00
parent 2458cc97fb
commit c93e20b1b3
6 changed files with 91 additions and 36 deletions

View File

@ -482,6 +482,9 @@ class Motion(RESTModelMixin, models.Model):
* unsupport
* change_state
* reset_state
NOTE: If you update this function please also update the
'isAllowed' function on client side in motions/site.js.
"""
# TODO: Remove this method and implement these things in the views.
actions = {

View File

@ -158,12 +158,15 @@ angular.module('OpenSlidesApp.motions', [])
'DS',
'MotionPoll',
'jsDataModel',
function(DS, MotionPoll, jsDataModel) {
'gettext',
'operator',
'Config',
function(DS, MotionPoll, jsDataModel, gettext, operator, Config) {
var name = 'motions/motion'
return DS.defineResource({
name: name,
useClass: jsDataModel,
agendaSupplement: '(Motion)',
agendaSupplement: '(' + gettext('Motion') + ')',
methods: {
getResourceName: function () {
return name;
@ -195,6 +198,56 @@ angular.module('OpenSlidesApp.motions', [])
value = this.identifier + ' | ';
}
return value + this.getTitle();
},
isAllowed: function (action) {
/*
* Return true if the requested user is allowed to do the specific action.
* There are the following possible actions.
* - see
* - update
* - delete
* - create_poll
* - support
* - unsupport
* - change_state
* - reset_state
*
* NOTE: If you update this function please also update the
* 'get_allowed_actions' function on server side in motions/models.py.
*/
switch (action) {
case 'see':
return (operator.hasPerms('motions.can_see') &&
(!this.state.required_permission_to_see ||
operator.hasPerms(this.state.required_permission_to_see) ||
(operator.user in this.submitters)));
case 'update':
return (operator.hasPerms('motions.can_manage') ||
(($.inArray(operator.user, this.submitters) != -1) &&
this.state.allow_submitter_edit));
case 'quickedit':
return operator.hasPerms('motions.can_manage');
case 'delete':
return operator.hasPerms('motions.can_manage');
case 'create_poll':
return (operator.hasPerms('motions.can_manage') &&
this.state.allow_create_poll);
case 'support':
return (operator.hasPerms('motions.can_support') &&
this.state.allow_support &&
Config.get('motions_min_supporters').value > 0 &&
!($.inArray(operator.user, this.submitters) != -1) &&
!($.inArray(operator.user, this.supporters) != -1));
case 'unsupport':
return (this.state.allow_support &&
($.inArray(operator.user, this.supporters) != -1));
case 'change_state':
return operator.hasPerms('motions.can_manage');
case 'reset_state':
return operator.hasPerms('motions.can_manage');
default:
return false;
}
}
},
relations: {
@ -246,13 +299,14 @@ angular.module('OpenSlidesApp.motions', [])
// Provide generic motion form fields for create and update view
.factory('MotionFormFieldFactory', [
'gettext',
'operator',
'Category',
'Config',
'Mediafile',
'Tag',
'User',
'Workflow',
function (gettext, Category, Config, Mediafile, Tag, User, Workflow) {
function (gettext, operator, Category, Config, Mediafile, Tag, User, Workflow) {
return {
getFormFields: function () {
return [
@ -275,7 +329,8 @@ angular.module('OpenSlidesApp.motions', [])
valueProp: 'id',
labelProp: 'full_name',
placeholder: gettext('Select or search a submitter...')
}
},
hide: !operator.hasPerms('motions.can_manage')
},
{
key: 'title',
@ -305,7 +360,8 @@ angular.module('OpenSlidesApp.motions', [])
type: 'checkbox',
templateOptions: {
label: gettext('Show extended fields')
}
},
hide: !operator.hasPerms('motions.can_manage')
},
{
key: 'attachments_id',
@ -394,11 +450,12 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
.config([
'mainMenuProvider',
function (mainMenuProvider) {
'gettext',
function (mainMenuProvider, gettext) {
mainMenuProvider.register({
'ui_sref': 'motions.motion.list',
'img_class': 'file-text',
'title': 'Motions',
'title': gettext('Motions'),
'weight': 300,
'perm': 'motions.can_see',
});
@ -657,7 +714,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
}
$scope.unsupport = function () {
$http.delete('/rest/motions/motion/' + motion.id + '/support/');
console.log(motion);
}
$scope.update_state = function (state) {
$http.put('/rest/motions/motion/' + motion.id + '/set_state/', {'state': state.id});
@ -704,6 +760,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
'$scope',
'$state',
'gettext',
'operator',
'Motion',
'MotionFormFieldFactory',
'Category',
@ -712,7 +769,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
'Tag',
'User',
'Workflow',
function($scope, $state, gettext, Motion, MotionFormFieldFactory, Category, Config, Mediafile, Tag, User, Workflow) {
function($scope, $state, gettext, operator, Motion, MotionFormFieldFactory, Category, Config, Mediafile, Tag, User, Workflow) {
Category.bindAll({}, $scope, 'categories');
Mediafile.bindAll({}, $scope, 'mediafiles');
Tag.bindAll({}, $scope, 'tags');
@ -726,6 +783,10 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
if ($scope.formFields[i].key == "identifier") {
$scope.formFields[i].hide = true;
}
if ($scope.formFields[i].key == "text") {
// set preamble config value as default text
$scope.formFields[i].defaultValue = Config.get('motions_preamble').value;
}
if ($scope.formFields[i].key == "workflow_id") {
// preselect default workflow
$scope.formFields[i].defaultValue = Config.get('motions_workflow').value;
@ -738,7 +799,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
}
);
};
}
])
@ -798,6 +858,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
$state.go('motions.motion.detail', {id: motion.id});
})
.catch(function(fallback) {
//TODO: show error in GUI
console.log(fallback);
});
};

View File

@ -25,7 +25,7 @@
<i class="fa fa-video-camera"></i>
</a>
<!-- edit -->
<a ng-if="motion.allowed_actions.update" ui-sref="motions.motion.detail.update({id: motion.id })"
<a ng-if="motion.isAllowed('update')" ui-sref="motions.motion.detail.update({id: motion.id })"
class="btn btn-default btn-sm"
title="{{ 'Edit' | translate}}">
<i class="fa fa-pencil"></i>
@ -79,12 +79,12 @@
{{ supporters.get_full_name() }}
</ol>
<!-- support button -->
<button ng-if="motion.allowed_actions.support" ng-click="support()" class="btn btn-primary btn-sm">
<button ng-if="motion.isAllowed('support')" ng-click="support()" class="btn btn-default btn-sm">
<i class="fa fa-heart"></i>
<translate>Support motion</translate>
</button>
<!-- unsupport button -->
<button ng-if="motion.allowed_actions.unsupport" ng-click="unsupport()" class="btn btn-default btn-sm">
<button ng-if="motion.isAllowed('unsupport')" ng-click="unsupport()" class="btn btn-default btn-sm">
<i class="fa fa-heart-o"></i>
<translate>Unsupport motion</translate>
</button>
@ -94,13 +94,13 @@
<span class="label" ng-class="'label-'+motion.state.css_class">
{{ motion.state.name | translate }}
</span>
<div ng-if="motion.allowed_actions.change_state" class="spacer">
<div ng-if="motion.isAllowed('change_state')" class="spacer">
<div class="btn-group-vertical spacer" role="group">
<button ng-repeat="state in motion.state.getNextStates()" ng-click="update_state(state)"
class="btn btn-default btn-sm">
{{state.action_word}}
</button>
<button ng-if="motion.allowed_actions.reset_state" ng-click="reset_state()"
<button ng-if="motion.isAllowed('reset_state')" ng-click="reset_state()"
class="btn btn-danger btn-xs">
<i class="fa fa-exclamation-triangle"></i>
<translate>Reset state</translate>
@ -227,7 +227,7 @@
</div>
</div>
</ol>
<button ng-if="motion.allowed_actions.create_poll" ng-click="create_poll()" class="btn btn-default btn-sm">
<button ng-if="motion.isAllowed('create_poll')" ng-click="create_poll()" class="btn btn-default btn-sm">
<i class="fa fa-bar-chart fa-lg"></i>
<translate>New poll</translate>
</button>

View File

@ -107,14 +107,14 @@
<td ng-if="!motion.quickEdit">{{ motion.identifier }}
<td ng-if="!motion.quickEdit" ng-mouseover="motion.hover=true" ng-mouseleave="motion.hover=false">
<strong><a ui-sref="motions.motion.detail({id: motion.id})">{{ motion.getTitle() }}</a></strong>
<div class="hoverActions" ng-class="{'hiddenDiv': !motion.hover}">
<span ng-if="motion.allowed_actions.update">
<a ui-sref="motions.motion.detail.update({ id: motion.id })" translate>Edit</a> |
<div ng-if="motion.isAllowed('update')" class="hoverActions" ng-class="{'hiddenDiv': !motion.hover}">
<span ng-if="motion.isAllowed('update')">
<a ui-sref="motions.motion.detail.update({ id: motion.id })" translate>Edit</a>
</span>
<span ng-if="motion.allowed_actions.update">
<a href="" os-perms="motions.can_manage" ng-click="motion.quickEdit=true" translate>QuickEdit</a> |
<span ng-if="motion.isAllowed('quickedit')">
| <a href="" ng-click="motion.quickEdit=true" translate>QuickEdit</a> |
</span>
<span ng-if="motion.allowed_actions.delete">
<span ng-if="motion.isAllowed('delete')">
<!-- TODO: translate confirm message -->
<a href="" class="text-danger"
ng-bootbox-confirm="Are you sure you want to delete <b>{{ motion.getTitle() }}</b>?"
@ -132,7 +132,7 @@
{{ motion.state.name | translate }}
</span>
<!-- quickEdit columns -->
<td ng-if="motion.quickEdit && motion.allowed_actions.update" colspan="5">
<td ng-if="motion.quickEdit && motion.isAllowed('quickedit')" colspan="5">
<h4>{{ motion.getTitle() }} <span class="text-muted">&ndash; Quick Edit</span></h4>
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
{{alert.msg}}
@ -193,10 +193,10 @@
<button ng-click="motion.quickEdit=false" class="btn btn-default pull-left" translate>
Cancel
</button> &nbsp;
<button ng-if="motion.allowed_actions.update" ng-click="update(motion)" class="btn btn-primary" translate>
<button ng-if="motion.isAllowed('update')" ng-click="update(motion)" class="btn btn-primary" translate>
Update
</button>
<a ng-if="motion.allowed_actions.update" ui-sref="motions.motion.detail.update({id: motion.id })"
<a ng-if="motion.isAllowed('update')" ui-sref="motions.motion.detail.update({id: motion.id })"
class="pull-right" translate>Edit motion...</a>
</div>
</table>

View File

@ -66,16 +66,6 @@ class MotionViewSet(ModelViewSet):
result = False
return result
def retrieve(self, request, *args, **kwargs):
"""
Customized view endpoint to retrieve a motion.
Adds the allowed actions for the motion.
"""
response = super().retrieve(request, *args, **kwargs)
response.data['allowed_actions'] = self.get_object().get_allowed_actions(request.user)
return response
def create(self, request, *args, **kwargs):
"""
Customized view endpoint to create a new motion.

View File

@ -172,7 +172,8 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
'operator',
'$rootScope',
'$http',
function(operator, $rootScope, $http) {
'Group',
function(operator, $rootScope, $http, Group) {
// Put the operator into the root scope
$http.get('/users/whoami/').success(function(data) {
operator.setUser(data.user_id);