improve server-side permissions

This commit is contained in:
Davide Alberani 2016-05-31 22:26:38 +02:00
parent c2fe50de16
commit aa7250656f
4 changed files with 63 additions and 24 deletions

View file

@ -52,7 +52,8 @@
<div ng-if="logo.imgURL" class="navbar-brand"><a ng-if="logo.link" href="{{logo.link}}" target="_blank"><img src="{{logo.imgURL}}" /></a></div>
<ul class="nav navbar-nav">
<li ng-class="{active: isActive('/events') || isActive('/event')}"><a ui-sref="events">{{'Events' | translate}}</a></li>
<li ng-if="hasPermission('persons:get')" ng-class="{active: isActive('/persons') || isActive('/person') || isActive('/import/persons')}"><a ui-sref="persons">{{'Persons' | translate}}</a></li>
<!-- li ng-if="hasPermission('persons|read')" ng-class="{active: isActive('/persons') || isActive('/person') || isActive('/import/persons')}"><a ui-sref="persons">{{'Persons' | translate}}</a></li -->
<li ng-class="{active: isActive('/persons') || isActive('/person') || isActive('/import/persons')}"><a ui-sref="persons">{{'Persons' | translate}}</a></li>
</ul>
</div>
<div class="collapse navbar-collapse">

View file

@ -64,12 +64,12 @@ eventManApp.run(['$rootScope', '$state', '$stateParams', '$log', 'Info',
return false;
}
var granted = false;
var splitted_permission = permission.split(':');
var main_permission = splitted_permission + ':all';
var splitted_permission = permission.split('|');
var global_permission = splitted_permission + '|all';
angular.forEach($rootScope.info.user.permissions || [],
function(value, idx) {
if (value === 'admin:all' || value === main_permission || value === permission) {
if (value === 'admin|all' || value === global_permission || value === permission) {
granted = true;
return;
}

View file

@ -26,6 +26,9 @@ eventManServices.factory('Event', ['$resource', '$rootScope',
isArray: true,
transformResponse: function(data, headers) {
data = angular.fromJson(data);
if (data.error) {
return data;
}
angular.forEach(data.events || [], function(event_, event_idx) {
convert_dates(event_);
});
@ -102,7 +105,11 @@ eventManServices.factory('Person', ['$resource', '$rootScope',
interceptor : {responseError: $rootScope.errorHandler},
isArray: true,
transformResponse: function(data, headers) {
return angular.fromJson(data).persons;
data = angular.fromJson(data);
if (data.error) {
return data;
}
return data.persons;
}
},
@ -141,7 +148,11 @@ eventManServices.factory('Setting', ['$resource', '$rootScope',
interceptor : {responseError: $rootScope.errorHandler},
isArray: true,
transformResponse: function(data, headers) {
return angular.fromJson(data).settings;
data = angular.fromJson(data);
if (data.error) {
return data;
}
return data.settings;
}
},
@ -162,7 +173,11 @@ eventManServices.factory('Info', ['$resource', '$rootScope',
interceptor : {responseError: $rootScope.errorHandler},
isArray: false,
transformResponse: function(data, headers) {
return angular.fromJson(data).info || {};
data = angular.fromJson(data);
if (data.error) {
return data;
}
return data.info || {};
}
}
});

View file

@ -62,6 +62,8 @@ def authenticated(method):
class BaseHandler(tornado.web.RequestHandler):
"""Base class for request handlers."""
permissions = {}
# A property to access the first value of each argument.
arguments = property(lambda self: dict([(k, v[0])
for k, v in self.request.arguments.iteritems()]))
@ -140,11 +142,18 @@ class BaseHandler(tornado.web.RequestHandler):
:rtype: bool
"""
user_info = self.current_user_info
user_permissions = user_info.get('permissions') or []
if not user_info:
return False
main_permission = '%s:all' % permission.split(':')[0]
return 'admin:all' in user_permissions or main_permission in user_permissions or permission in user_permissions
user_permissions = user_info.get('permissions') or []
global_permission = '%s|all' % permission.split('|')[0]
if 'admin|all' in user_permissions or global_permission in user_permissions or permission in user_permissions:
return True
collection_permission = self.permissions.get(permission)
if isinstance(collection_permission, bool):
return collection_permission
if callable(collection_permission):
return collection_permission(permission)
return False
def build_error(self, message='', status=400):
self.set_status(status)
@ -257,24 +266,27 @@ class CollectionHandler(BaseHandler):
def get(self, id_=None, resource=None, resource_id=None, **kwargs):
if resource:
# Handle access to sub-resources.
if not self.has_permission('%s:%s-read' % (self.document, resource)):
return self.build_error(status=401, message='insufficient permissions')
permission = '%s:%s|read' % (self.document, resource)
if not self.has_permission(permission):
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
method = getattr(self, 'handle_get_%s' % resource, None)
if method and callable(method):
self.write(method(id_, resource_id, **kwargs))
return
if id_ is not None:
# read a single document
if not self.has_permission('%s:read' % self.document):
return self.build_error(status=401, message='insufficient permissions')
permission = '%s|read' % self.document
if not self.has_permission(permission):
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
self.write(self.db.get(self.collection, id_))
else:
# return an object containing the list of all objects in the collection;
# e.g.: {'events': [{'_id': 'obj1-id, ...}, {'_id': 'obj2-id, ...}, ...]}
# Please, never return JSON lists that are not encapsulated into an object,
# to avoid XSS vulnerabilities.
if not self.has_permission('%s:read' % self.collection):
return self.build_error(status=401, message='insufficient permissions')
permission = '%s|read' % self.collection
if not self.has_permission(permission):
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
self.write({self.collection: self.db.query(self.collection)})
@gen.coroutine
@ -284,21 +296,23 @@ class CollectionHandler(BaseHandler):
self._clean_dict(data)
method = self.request.method.lower()
if resource:
if not self.has_permission('%s:%s-%s' % (self.document, resource,
'create' if method == 'post' else 'update')):
return self.build_error(status=401, message='insufficient permissions')
permission = '%s:%s|%s' % (self.document, resource, 'create' if method == 'post' else 'update')
if not self.has_permission(permission):
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
# Handle access to sub-resources.
handler = getattr(self, 'handle_%s_%s' % (method, resource), None)
if handler and callable(handler):
self.write(handler(id_, resource_id, data, **kwargs))
return
if id_ is None:
if not self.has_permission('%s:%s' % (self.document, 'create' if method == 'post' else 'update')):
return self.build_error(status=401, message='insufficient permissions')
permission = '%s|%s' % (self.document, 'create' if method == 'post' else 'update')
if not self.has_permission(permission):
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
newData = self.db.add(self.collection, data)
else:
if not self.has_permission('%s:%s' % (self.collection, 'create' if method == 'post' else 'update')):
return self.build_error(status=401, message='insufficient permissions')
permission = '%s|%s' % (self.collection, 'create' if method == 'post' else 'update')
if not self.has_permission(permission):
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
merged, newData = self.db.update(self.collection, id_, data)
self.write(newData)
@ -310,11 +324,17 @@ class CollectionHandler(BaseHandler):
def delete(self, id_=None, resource=None, resource_id=None, **kwargs):
if resource:
# Handle access to sub-resources.
permission = '%s:%s|delete' % (self.document, resource)
if not self.has_permission(permission):
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
method = getattr(self, 'handle_delete_%s' % resource, None)
if method and callable(method):
self.write(method(id_, resource_id, **kwargs))
return
if id_:
permission = '%s|delete' % self.document
if not self.has_permission(permission):
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
self.db.delete(self.collection, id_)
self.write({'success': True})
@ -440,6 +460,9 @@ class EventsHandler(CollectionHandler):
document = 'event'
collection = 'events'
object_id = 'event_id'
permissions = {
'events|read': True
}
def _get_person_data(self, person_id_or_query, persons):
"""Filter a list of persons returning the first item with a given person_id
@ -772,7 +795,7 @@ def run():
if not db_connector.query('users', {'username': 'admin'}):
db_connector.add('users',
{'username': 'admin', 'password': utils.hash_password('eventman'),
'permissions': ['admin:all']})
'permissions': ['admin|all']})
# If present, use the cookie_secret stored into the database.
cookie_secret = db_connector.query('settings', {'setting': 'server_cookie_secret'})