fixes #136: user pages

This commit is contained in:
Davide Alberani 2016-07-09 13:34:36 +02:00
parent 1ec1bca6fe
commit 5fc0ff0723
6 changed files with 189 additions and 31 deletions

View file

@ -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',

View file

@ -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() {

View file

@ -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);
}
},

View file

@ -0,0 +1,58 @@
<!-- show details of a User -->
<div class="container">
<h1>{{user.username}}</h1>
<div class="panel panel-success table-striped">
<div class="panel-heading">{{'Update user information' | translate}}</div>
<div class="panel-body">
<form method="POST">
<div class="input-group input-group-lg top10">
<span class="input-group-addon min150">{{'Email' | translate}}</span>
<input type="email" id="new-email" name="new-email" ng-model="updateUserInfo.email" class="form-control">
</div>
<div class="input-group input-group-lg top10">
<span class="input-group-addon min150">{{'Old password' | translate}}</span>
<input type="password" id="old-password" name="old-password" ng-model="updateUserInfo.old_password" class="form-control">
<span class="input-group-addon min150">{{'New password' | translate}}</span>
<input type="password" id="new-password" name="new-password" ng-model="updateUserInfo.new_password" class="form-control">
</div>
<button type="submit" ng-click="updateUser()" class="btn btn-success top10">{{'update' | translate}}</button>
</form>
</div>
</div>
<div class="panel panel-primary table-striped top5">
<div class="panel-heading">{{'Tickets' | translate}}</div>
<div class="panel-body">
<form class="form-inline">
<div class="form-group">
<label for="query-tickets">{{'Search:' | translate}}</label>
<input eventman-focus type="text" id="query-tickets" class="form-control" placeholder="{{'Event title' | translate}}" ng-model="query" ng-model-options="{debounce: 600}">
</div>
<div class="form-group">
<label for="tickets-order">{{'Sort by:' | translate}}</label>
<select id="tickets-order" class="form-control" ng-model="ticketsOrderProp">
<option value="title">{{'Title' | translate}}</option>
<option value="-title">{{'Title (descending)' | translate}}</option>
</select>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>{{'Event' | translate}}</th>
<th class="text-center">{{'Attended' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="ticket in (user.tickets || []) | splittedFilter:query | orderBy:ticketsOrderProp">
<td><strong><a ui-sref="event.ticket.edit({id: ticket.event_id, ticket_id: ticket._id})">{{ticket.event_title}}</a></strong></td>
<td class="text-center">
<span class="fa fa-lg {{(ticket.attended) && 'fa-check-circle text-success' || 'fa-times-circle text-danger'}}"></span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View file

@ -0,0 +1,2 @@
<!-- main view for User -->
<div ui-view></div>

View file

@ -124,6 +124,8 @@ class BaseHandler(tornado.web.RequestHandler):
'true': True
}
_re_split_salt = re.compile(r'\$(?P<salt>.+)\$(?P<hash>.+)')
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<salt>.+)\$(?P<hash>.+)')
@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'})