Merge pull request #1691 from emanuelschuetze/motions-permissions

Updated motion permission checks
This commit is contained in:
Norman Jäckel 2015-11-20 23:18:54 +01:00
commit cdd1813c02
6 changed files with 91 additions and 36 deletions

View File

@ -482,6 +482,9 @@ class Motion(RESTModelMixin, models.Model):
* unsupport * unsupport
* change_state * change_state
* reset_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. # TODO: Remove this method and implement these things in the views.
actions = { actions = {

View File

@ -158,12 +158,15 @@ angular.module('OpenSlidesApp.motions', [])
'DS', 'DS',
'MotionPoll', 'MotionPoll',
'jsDataModel', 'jsDataModel',
function(DS, MotionPoll, jsDataModel) { 'gettext',
'operator',
'Config',
function(DS, MotionPoll, jsDataModel, gettext, operator, Config) {
var name = 'motions/motion' var name = 'motions/motion'
return DS.defineResource({ return DS.defineResource({
name: name, name: name,
useClass: jsDataModel, useClass: jsDataModel,
agendaSupplement: '(Motion)', agendaSupplement: '(' + gettext('Motion') + ')',
methods: { methods: {
getResourceName: function () { getResourceName: function () {
return name; return name;
@ -195,6 +198,56 @@ angular.module('OpenSlidesApp.motions', [])
value = this.identifier + ' | '; value = this.identifier + ' | ';
} }
return value + this.getTitle(); 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: { relations: {
@ -246,13 +299,14 @@ angular.module('OpenSlidesApp.motions', [])
// Provide generic motion form fields for create and update view // Provide generic motion form fields for create and update view
.factory('MotionFormFieldFactory', [ .factory('MotionFormFieldFactory', [
'gettext', 'gettext',
'operator',
'Category', 'Category',
'Config', 'Config',
'Mediafile', 'Mediafile',
'Tag', 'Tag',
'User', 'User',
'Workflow', 'Workflow',
function (gettext, Category, Config, Mediafile, Tag, User, Workflow) { function (gettext, operator, Category, Config, Mediafile, Tag, User, Workflow) {
return { return {
getFormFields: function () { getFormFields: function () {
return [ return [
@ -275,7 +329,8 @@ angular.module('OpenSlidesApp.motions', [])
valueProp: 'id', valueProp: 'id',
labelProp: 'full_name', labelProp: 'full_name',
placeholder: gettext('Select or search a submitter...') placeholder: gettext('Select or search a submitter...')
} },
hide: !operator.hasPerms('motions.can_manage')
}, },
{ {
key: 'title', key: 'title',
@ -305,7 +360,8 @@ angular.module('OpenSlidesApp.motions', [])
type: 'checkbox', type: 'checkbox',
templateOptions: { templateOptions: {
label: gettext('Show extended fields') label: gettext('Show extended fields')
} },
hide: !operator.hasPerms('motions.can_manage')
}, },
{ {
key: 'attachments_id', key: 'attachments_id',
@ -394,11 +450,12 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
.config([ .config([
'mainMenuProvider', 'mainMenuProvider',
function (mainMenuProvider) { 'gettext',
function (mainMenuProvider, gettext) {
mainMenuProvider.register({ mainMenuProvider.register({
'ui_sref': 'motions.motion.list', 'ui_sref': 'motions.motion.list',
'img_class': 'file-text', 'img_class': 'file-text',
'title': 'Motions', 'title': gettext('Motions'),
'weight': 300, 'weight': 300,
'perm': 'motions.can_see', 'perm': 'motions.can_see',
}); });
@ -657,7 +714,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
} }
$scope.unsupport = function () { $scope.unsupport = function () {
$http.delete('/rest/motions/motion/' + motion.id + '/support/'); $http.delete('/rest/motions/motion/' + motion.id + '/support/');
console.log(motion);
} }
$scope.update_state = function (state) { $scope.update_state = function (state) {
$http.put('/rest/motions/motion/' + motion.id + '/set_state/', {'state': state.id}); $http.put('/rest/motions/motion/' + motion.id + '/set_state/', {'state': state.id});
@ -704,6 +760,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
'$scope', '$scope',
'$state', '$state',
'gettext', 'gettext',
'operator',
'Motion', 'Motion',
'MotionFormFieldFactory', 'MotionFormFieldFactory',
'Category', 'Category',
@ -712,7 +769,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
'Tag', 'Tag',
'User', 'User',
'Workflow', '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'); Category.bindAll({}, $scope, 'categories');
Mediafile.bindAll({}, $scope, 'mediafiles'); Mediafile.bindAll({}, $scope, 'mediafiles');
Tag.bindAll({}, $scope, 'tags'); Tag.bindAll({}, $scope, 'tags');
@ -726,6 +783,10 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
if ($scope.formFields[i].key == "identifier") { if ($scope.formFields[i].key == "identifier") {
$scope.formFields[i].hide = true; $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") { if ($scope.formFields[i].key == "workflow_id") {
// preselect default workflow // preselect default workflow
$scope.formFields[i].defaultValue = Config.get('motions_workflow').value; $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}); $state.go('motions.motion.detail', {id: motion.id});
}) })
.catch(function(fallback) { .catch(function(fallback) {
//TODO: show error in GUI
console.log(fallback); console.log(fallback);
}); });
}; };

View File

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

View File

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

View File

@ -66,16 +66,6 @@ class MotionViewSet(ModelViewSet):
result = False result = False
return result 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): def create(self, request, *args, **kwargs):
""" """
Customized view endpoint to create a new motion. Customized view endpoint to create a new motion.

View File

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