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}}
+
+
+
+
{{'Tickets' | translate}}
+
+
+
+
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'})