From 5fc0ff0723cb27a96d33ed90c0b372f029dde27c Mon Sep 17 00:00:00 2001 From: Davide Alberani Date: Sat, 9 Jul 2016 13:34:36 +0200 Subject: [PATCH] fixes #136: user pages --- angular_app/js/app.js | 9 +++ angular_app/js/controllers.js | 42 ++++++++++++-- angular_app/js/services.js | 6 +- angular_app/user-edit.html | 58 +++++++++++++++++++ angular_app/user-main.html | 2 + eventman_server.py | 103 ++++++++++++++++++++++++++-------- 6 files changed, 189 insertions(+), 31 deletions(-) create mode 100644 angular_app/user-edit.html create mode 100644 angular_app/user-main.html diff --git a/angular_app/js/app.js b/angular_app/js/app.js index 21cbc29..b174973 100644 --- a/angular_app/js/app.js +++ b/angular_app/js/app.js @@ -164,6 +164,15 @@ eventManApp.config(['$stateProvider', '$urlRouterProvider', templateUrl: 'users-list.html', controller: 'UsersCtrl' }) + .state('user', { + url: '/user', + templateUrl: 'user-main.html' + }) + .state('user.edit', { + url: '/edit/:id', + templateUrl: 'user-edit.html', + controller: 'UsersCtrl' + }) .state('login', { url: '/login', templateUrl: 'login.html', diff --git a/angular_app/js/controllers.js b/angular_app/js/controllers.js index e285c65..bb6c764 100644 --- a/angular_app/js/controllers.js +++ b/angular_app/js/controllers.js @@ -273,6 +273,9 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event', /* Stuff to do when a ticket is added, modified or removed locally. */ $scope._localAddTicket = function(ticket, original_person) { + if (!$state.is('event.tickets')) { + return true; + } var ret = true; if (!$scope.event.persons) { $scope.event.persons = []; @@ -652,10 +655,21 @@ eventManControllers.controller('PersonDetailsCtrl', ['$scope', '$state', 'Person ); -eventManControllers.controller('UsersCtrl', ['$scope', '$rootScope', '$state', '$log', 'User', - function ($scope, $rootScope, $state, $log, User) { +eventManControllers.controller('UsersCtrl', ['$scope', '$rootScope', '$state', '$log', 'User', '$uibModal', + function ($scope, $rootScope, $state, $log, User, $uibModal) { $scope.loginData = {}; - $scope.usersOrderProp = ['username']; + $scope.user = {}; + $scope.updateUserInfo = {}; + $scope.users = []; + $scope.usersOrderProp = 'username'; + $scope.ticketsOrderProp = 'title'; + + $scope.confirm_delete = 'Do you really want to delete this user?'; + $rootScope.$on('$translateChangeSuccess', function () { + $translate('Do you really want to delete this user?').then(function (translation) { + $scope.confirm_delete = translation; + }); + }); $scope.updateUsersList = function() { if ($state.is('users')) { @@ -665,8 +679,28 @@ eventManControllers.controller('UsersCtrl', ['$scope', '$rootScope', '$state', ' $scope.updateUsersList(); + if ($state.is('user.edit') && $state.params.id) { + $scope.user = User.get({id: $state.params.id}, function() { + $scope.updateUserInfo = $scope.user; + }); + } + + $scope.updateUser = function() { + User.update($scope.updateUserInfo); + }; + $scope.deleteUser = function(user_id) { - User.delete({id: user_id}, $scope.updateUsersList); + var modalInstance = $uibModal.open({ + scope: $scope, + templateUrl: 'modal-confirm-action.html', + controller: 'ModalConfirmInstanceCtrl', + resolve: { + message: function() { return $scope.confirm_delete; } + } + }); + modalInstance.result.then(function() { + User.delete({id: user_id}, $scope.updateUsersList); + }); }; $scope.register = function() { diff --git a/angular_app/js/services.js b/angular_app/js/services.js index 2d2a46a..3fbc3a4 100644 --- a/angular_app/js/services.js +++ b/angular_app/js/services.js @@ -251,11 +251,7 @@ eventManServices.factory('User', ['$resource', '$rootScope', method: 'GET', interceptor : {responseError: $rootScope.errorHandler}, transformResponse: function(data, headers) { - data = angular.fromJson(data); - if (data.error) { - return data; - } - return data.user || {}; + return angular.fromJson(data); } }, diff --git a/angular_app/user-edit.html b/angular_app/user-edit.html new file mode 100644 index 0000000..845fe7b --- /dev/null +++ b/angular_app/user-edit.html @@ -0,0 +1,58 @@ + +
+

{{user.username}}

+
+
{{'Update user information' | translate}}
+
+
+
+ {{'Email' | translate}} + +
+
+ {{'Old password' | translate}} + + {{'New password' | translate}} + +
+ +
+
+
+
+
{{'Tickets' | translate}}
+
+ +
+
+ + +
+
+ + +
+
+ + + + + + + + + + + + + +
{{'Event' | translate}}{{'Attended' | translate}}
{{ticket.event_title}} + +
+
+
+
+ diff --git a/angular_app/user-main.html b/angular_app/user-main.html new file mode 100644 index 0000000..ca36251 --- /dev/null +++ b/angular_app/user-main.html @@ -0,0 +1,2 @@ + +
diff --git a/eventman_server.py b/eventman_server.py index 70477d1..3f764b8 100755 --- a/eventman_server.py +++ b/eventman_server.py @@ -124,6 +124,8 @@ class BaseHandler(tornado.web.RequestHandler): 'true': True } + _re_split_salt = re.compile(r'\$(?P.+)\$(?P.+)') + def write_error(self, status_code, **kwargs): """Default error handler.""" if isinstance(kwargs.get('exc_info', (None, None))[1], BaseException): @@ -200,6 +202,32 @@ class BaseHandler(tornado.web.RequestHandler): return collection_permission(permission) return False + def user_authorized(self, username, password): + """Check if a combination of username/password is valid. + + :param username: username or email + :type username: str + :param password: password + :type password: str + + :returns: tuple like (bool_user_is_authorized, dict_user_info) + :rtype: dict""" + query = [{'username': username}, {'email': username}] + res = self.db.query('users', query) + if not res: + return (False, {}) + user = res[0] + db_password = user.get('password') or '' + if not db_password: + return (False, {}) + match = self._re_split_salt.match(db_password) + if not match: + return (False, {}) + salt = match.group('salt') + if utils.hash_password(password, salt=salt) == db_password: + return (True, user) + return (False, {}) + def build_error(self, message='', status=400): """Build and write an error message.""" self.set_status(status) @@ -733,6 +761,23 @@ class UsersHandler(CollectionHandler): document = 'user' collection = 'users' + def filter_get(self, data): + if 'password' in data: + del data['password'] + if '_id' in data: + tickets = [] + events = self.db.query('events', {'persons.created_by': data['_id']}) + for event in events: + event_title = event.get('title') or '' + event_id = str(event.get('_id')) + evt_tickets = self._filter_results(event.get('persons') or [], {'created_by': data['_id']}) + for evt_ticket in evt_tickets: + evt_ticket['event_title'] = event_title + evt_ticket['event_id'] = event_id + tickets.extend(evt_tickets) + data['tickets'] = tickets + return data + def filter_get_all(self, data): if 'users' not in data: return data @@ -741,6 +786,14 @@ class UsersHandler(CollectionHandler): del user['password'] return data + @gen.coroutine + @authenticated + def get(self, id_=None, resource=None, resource_id=None, acl=True, **kwargs): + if id_ is not None: + if (self.has_permission('user|read') or str(self.current_user_info.get('_id')) == id_): + acl = False + super(UsersHandler, self).get(id_, resource, resource_id, acl=acl, **kwargs) + def filter_input_post_all(self, data): username = (data.get('username') or '').strip() password = (data.get('password') or '').strip() @@ -753,6 +806,31 @@ class UsersHandler(CollectionHandler): return {'username': username, 'password': utils.hash_password(password), 'email': email, '_id': self.gen_id()} + def filter_input_put(self, data): + old_pwd = data.get('old_password') + new_pwd = data.get('new_password') + if old_pwd is not None: + del data['old_password'] + if new_pwd is not None: + del data['new_password'] + authorized, user = self.user_authorized(data['username'], old_pwd) + if not (self.has_permission('user|update') or (authorized and self.current_user == data['username'])): + raise InputException('not authorized to change password') + data['password'] = utils.hash_password(new_pwd) + if '_id' in data: + # Avoid overriding _id + del data['_id'] + return data + + @gen.coroutine + @authenticated + def put(self, id_=None, resource=None, resource_id=None, **kwargs): + if id_ is None: + return self.build_error(status=404, message='unable to access the resource') + if not (self.has_permission('user|update') or str(self.current_user_info.get('_id')) == id_): + return self.build_error(status=401, message='insufficient permissions: user|update or current user') + super(UsersHandler, self).put(id_, resource, resource_id, **kwargs) + class EbCSVImportPersonsHandler(BaseHandler): """Importer for CSV files exported from eventbrite.""" @@ -870,7 +948,6 @@ class WebSocketEventUpdatesHandler(tornado.websocket.WebSocketHandler): class LoginHandler(BaseHandler): """Handle user authentication requests.""" - re_split_salt = re.compile(r'\$(?P.+)\$(?P.+)') @gen.coroutine def get(self, **kwds): @@ -883,26 +960,6 @@ class LoginHandler(BaseHandler): with open(self.angular_app_path + "/login.html", 'r') as fd: self.write(fd.read()) - def _authorize(self, username, password, email=None): - """Return True is this username/password is valid.""" - query = [{'username': username}] - if email is not None: - query.append({'email': email}) - res = self.db.query('users', query) - if not res: - return False - user = res[0] - db_password = user.get('password') or '' - if not db_password: - return False - match = self.re_split_salt.match(db_password) - if not match: - return False - salt = match.group('salt') - if utils.hash_password(password, salt=salt) == db_password: - return True - return False - @gen.coroutine def post(self, *args, **kwargs): # authenticate a user @@ -917,7 +974,9 @@ class LoginHandler(BaseHandler): self.set_status(401) self.write({'error': True, 'message': 'missing username or password'}) return - if self._authorize(username, password): + authorized, user = self.user_authorized(username, password) + if authorized and user.get('username'): + username = user['username'] logging.info('successful login for user %s' % username) self.set_secure_cookie("user", username) self.write({'error': False, 'message': 'successful login'})