diff --git a/README.md b/README.md index f6b37d2..d201e99 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,9 @@ See [https://ibt2.ismito.it:3002/](https://ibt2.ismito.it:3002/) To install it: ``` bash wget https://bootstrap.pypa.io/get-pip.py -sudo python get-pip.py -sudo pip install tornado -sudo pip install pymongo -sudo pip install python-dateutil +sudo python3 get-pip.py +sudo pip3 install tornado +sudo pip3 install pymongo git clone https://github.com/raspibo/ibt2 cd ibt2 ``` @@ -62,7 +61,7 @@ npm run devserver & npm run dev # only when the devserver is running, you can also run the testsuite -python ./tests/ibt2_tests.py +python3 ./tests/ibt2_tests.py ``` Your browser will automatically open [http://localhost:8080/](http://localhost:8080/) (that's the server for development) diff --git a/ibt2.py b/ibt2.py index 05b3cd1..e7259ec 100755 --- a/ibt2.py +++ b/ibt2.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """I'll Be There, 2 (ibt2) - an oversimplified attendees registration system. Copyright 2016-2017 Davide Alberani @@ -63,8 +63,8 @@ class BaseHandler(tornado.web.RequestHandler): collection = None # 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()])) + arguments = property(lambda self: dict([(k, v[0].decode('utf-8')) + for k, v in self.request.arguments.items()])) # Arguments suitable for a query on MongoDB. clean_arguments = property(lambda self: self._clean_dict(self.arguments)) @@ -86,8 +86,8 @@ class BaseHandler(tornado.web.RequestHandler): :param data: dictionary to clean :type data: dict""" if isinstance(data, dict): - for key in data.keys(): - if (isinstance(key, (str, unicode)) and key.startswith('$')) or key in ('_id', 'created_by', + for key in list(data.keys()): + if (isinstance(key, str) and key.startswith('$')) or key in ('_id', 'created_by', 'created_at', 'updated_by', 'updated_at'): del data[key] @@ -109,13 +109,16 @@ class BaseHandler(tornado.web.RequestHandler): def initialize(self, **kwargs): """Add every passed (key, value) as attributes of the instance.""" - for key, value in kwargs.iteritems(): + for key, value in kwargs.items(): setattr(self, key, value) @property def current_user(self): """Retrieve current user ID from the secure cookie.""" - return self.get_secure_cookie("user") + current_user = self.get_secure_cookie("user") + if isinstance(current_user, bytes): + current_user = current_user.decode('utf-8') + return current_user @property def current_user_info(self): @@ -381,8 +384,7 @@ class GroupsHandler(BaseHandler): if not (day and group): return self.build_error(status=404, message='unable to access the resource') if not self.current_user_info.get('isAdmin'): - self.build_error(status=401, message='insufficient permissions: must be admin') - return False + return self.build_error(status=401, message='insufficient permissions: must be admin') query = {'day': day, 'group': group} howMany = self.db.delete('attendees', query) self.db.delete('groups', query) diff --git a/monco.py b/monco.py index d3d3b0b..2ee8076 100644 --- a/monco.py +++ b/monco.py @@ -56,7 +56,7 @@ def convert(seq): """ if isinstance(seq, dict): d = {} - for key, item in seq.iteritems(): + for key, item in seq.items(): if key in _force_conversion: try: d[key] = _force_conversion[key](item) @@ -254,7 +254,7 @@ class Monco(object): operator = self._operations.get(operation) if updateList: newData = {} - for key, value in data.iteritems(): + for key, value in data.items(): newData['%s.$.%s' % (updateList, key)] = value data = newData res = db[collection].find_and_modify(query=_id_or_query, diff --git a/package.json b/package.json index f77120e..3db81c0 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ }, "scripts": { "dev": "node build/dev-server.js", - "devserver": "python ibt2.py --debug --db_name=ibt2_test", - "server": "python ibt2.py --debug", + "devserver": "python3 ibt2.py --debug --db_name=ibt2_test", + "server": "python3 ibt2.py --debug", "build": "node build/build.js" }, "dependencies": { diff --git a/tests/ibt2_tests.py b/tests/ibt2_tests.py index 860fa15..a893091 100755 --- a/tests/ibt2_tests.py +++ b/tests/ibt2_tests.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """I'll Be There, 2 (ibt2) - tests Copyright 2016-2017 Davide Alberani @@ -23,7 +23,7 @@ BASE_URL = 'http://localhost:3000/v1.1/' DB_NAME = 'ibt2_test' def dictInDict(d, dContainer): - for k, v in d.viewitems(): + for k, v in d.items(): if k not in dContainer: return False if v != dContainer[k]: @@ -39,15 +39,15 @@ class Ibt2Tests(unittest.TestCase): self.db['attendees'].drop() self.db['days'].drop() self.db['groups'].drop() - self.db['users'].remove({'username': 'newuser'}) - self.db['users'].remove({'username': 'newuser2'}) + self.db['users'].delete_one({'username': 'newuser'}) + self.db['users'].delete_one({'username': 'newuser2'}) def tearDown(self): self.db['attendees'].drop() self.db['days'].drop() self.db['groups'].drop() - self.db['users'].remove({'username': 'newuser'}) - self.db['users'].remove({'username': 'newuser2'}) + self.db['users'].delete_one({'username': 'newuser'}) + self.db['users'].delete_one({'username': 'newuser2'}) def add_attendee(self, attendee): r = requests.post('%sattendees' % BASE_URL, json=attendee) @@ -85,9 +85,11 @@ class Ibt2Tests(unittest.TestCase): attendee = {'name': 'A Name', 'day': '2017-01-15', 'group': 'A group'} r = self.add_attendee(attendee) id_ = r.json().get('_id') + r.connection.close() r = requests.delete(BASE_URL + 'attendees/' + id_) r.raise_for_status() self.assertTrue(r.json().get('success')) + r.connection.close() def test_get_days(self): self.add_attendee({'day': '2017-01-15', 'name': 'A name', 'group': 'group A'}) @@ -118,9 +120,11 @@ class Ibt2Tests(unittest.TestCase): def test_create_user(self): r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'}) r.raise_for_status() + r.connection.close() s = self.login('newuser', 'ibt2') r = s.get(BASE_URL + 'users/current') r.raise_for_status() + r.connection.close() def test_update_user(self): r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'}) @@ -130,17 +134,20 @@ class Ibt2Tests(unittest.TestCase): r.raise_for_status() id2_ = r.json()['_id'] r = requests.put(BASE_URL + 'users/' + id_, json={'email': 't@example.com'}) - self.assertRaises(r.raise_for_status) + self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status) s = self.login('newuser', 'ibt2') r = s.put(BASE_URL + 'users/' + id_, json={'email': 'test@example.com'}) r.raise_for_status() self.assertEqual(r.json().get('email'), 'test@example.com') + r.connection.close() r = s.put(BASE_URL + 'users/' + id2_, json={'email': 'test@example.com'}) - self.assertRaises(r.raise_for_status) + self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status) + r.connection.close() s = self.login('admin', 'ibt2') r = s.put(BASE_URL + 'users/' + id_, json={'email': 'test2@example.com'}) r.raise_for_status() self.assertEqual(r.json().get('email'), 'test2@example.com') + r.connection.close() def test_delete_user(self): r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'}) @@ -150,26 +157,31 @@ class Ibt2Tests(unittest.TestCase): r.raise_for_status() id2_ = r.json()['_id'] r = requests.delete(BASE_URL + 'users/' + id_) - self.assertRaises(r.raise_for_status) + self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status) + r.connection.close() s = self.login('newuser', 'ibt2') r = s.delete(BASE_URL + 'users/' + id_) r.raise_for_status() + r.connection.close() r = s.delete(BASE_URL + 'users/' + id2_) - self.assertRaises(r.raise_for_status) + self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status) + r.connection.close() s = self.login('admin', 'ibt2') r = s.delete(BASE_URL + 'users/' + id2_) r.raise_for_status() + r.connection.close() def test_duplicate_user(self): r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'}) r.raise_for_status() r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt3'}) - self.assertRaises(r.raise_for_status) + self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status) def login(self, username, password): s = requests.Session() r = s.post(BASE_URL + 'login', json={'username': username, 'password': password}) r.raise_for_status() + r.connection.close() return s def test_created_by(self): @@ -177,12 +189,14 @@ class Ibt2Tests(unittest.TestCase): r = s.get(BASE_URL + 'users/current') r.raise_for_status() user_id = r.json()['_id'] + r.connection.close() attendee = {'day': '2017-01-15', 'name': 'A name', 'group': 'group A'} r = s.post('%sattendees' % BASE_URL, json=attendee) r.raise_for_status() rj = r.json() self.assertEqual(user_id, rj['created_by']) self.assertEqual(user_id, rj['updated_by']) + r.connection.close() def test_put_day(self): day = {'day': '2017-01-16', 'notes': 'A day note'} @@ -214,10 +228,12 @@ class Ibt2Tests(unittest.TestCase): r = s.delete(BASE_URL + 'days/2017-01-16/groups/A group', params={'day': '2017-01-16', 'group': 'A group'}) r.raise_for_status() rj = r.json() + r.connection.close() r = requests.get(BASE_URL + 'days/2017-01-16') r.raise_for_status() rj = r.json() self.assertTrue(rj == {}) + r.connection.close() if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/utils.py b/utils.py index b1d7107..a6b0835 100644 --- a/utils.py +++ b/utils.py @@ -36,15 +36,22 @@ def hash_password(password, salt=None): :rtype: str""" if salt is None: salt_pool = string.ascii_letters + string.digits - salt = ''.join(random.choice(salt_pool) for x in xrange(32)) - hash_ = hashlib.sha512('%s%s' % (salt, password)) + salt = ''.join(random.choice(salt_pool) for x in range(32)) + pass_and_salt = '%s%s' % (salt, password) + pass_and_salt = pass_and_salt.encode('utf-8', 'ignore') + hash_ = hashlib.sha512(pass_and_salt) return '$%s$%s' % (salt, hash_.hexdigest()) class ImprovedEncoder(json.JSONEncoder): """Enhance the default JSON encoder to serialize datetime and ObjectId instances.""" def default(self, o): - if isinstance(o, (datetime.datetime, datetime.date, + if isinstance(o, bytes): + try: + return o.decode('utf-8') + except: + pass + elif isinstance(o, (datetime.datetime, datetime.date, datetime.time, datetime.timedelta, ObjectId)): try: return str(o)