From 7cf4bf5c8cec05cb10a8c6a90c4040b1dbb33049 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 22 Sep 2015 11:46:50 +1000 Subject: [PATCH 01/10] Fix bug in serialization of logging.LogRecord to show formatted message. --- dddp/logging.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dddp/logging.py b/dddp/logging.py index c4de8c9..2560558 100644 --- a/dddp/logging.py +++ b/dddp/logging.py @@ -1,6 +1,7 @@ """Django DDP logging helpers.""" from __future__ import absolute_import, print_function +import datetime import logging from dddp import THREAD_LOCAL as this, meteor_random_id, ADDED @@ -18,6 +19,7 @@ class DDPHandler(logging.Handler): 'collection': 'logs', 'id': meteor_random_id('/collection/logs'), 'fields': { + 'created': datetime.datetime.fromtimestamp(record.created), 'name': record.name, 'levelno': record.levelno, 'levelname': record.levelname, From ef414f50c4aa0a4e7cd3a21974ecbce6448d6281 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 22 Sep 2015 11:48:24 +1000 Subject: [PATCH 02/10] Don't rely on HTTP_REFERER header which may not be present. --- dddp/main.py | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/dddp/main.py b/dddp/main.py index 7fa9416..babbc36 100644 --- a/dddp/main.py +++ b/dddp/main.py @@ -18,22 +18,29 @@ import geventwebsocket.handler Addr = collections.namedtuple('Addr', ['host', 'port']) +def common_headers(environ, **kwargs): + """Return list of common headers for SockJS HTTP responses.""" + return [ + # DDP doesn't use cookies or HTTP level auth, so CSRF attacks are + # ineffective. We can safely allow cross-domain DDP connections and + # developers may choose to allow anonymous access to publications and + # RPC methods as they see fit. More to the point, developers should + # restrict access to publications and RPC endpoints as appropriate. + ('Access-Control-Allow-Origin', '*'), + ('Access-Control-Allow-Credentials', 'false'), + ('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'), + ('Connection', 'keep-alive'), + ('Vary', 'Origin'), + ] + + def ddpp_sockjs_xhr(environ, start_response): """Dummy method that doesn't handle XHR requests.""" start_response( '404 Not found', [ ('Content-Type', 'text/plain; charset=UTF-8'), - ( - 'Access-Control-Allow-Origin', - '/'.join(environ['HTTP_REFERER'].split('/')[:3]), - ), - ('Access-Control-Allow-Credentials', 'true'), - # ('access-control-allow-credentials', 'true'), - ('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'), - ('Connection', 'keep-alive'), - ('Vary', 'Origin'), - ], + ] + common_headers(environ), ) yield 'No.' @@ -47,16 +54,7 @@ def ddpp_sockjs_info(environ, start_response): '200 OK', [ ('Content-Type', 'application/json; charset=UTF-8'), - ( - 'Access-Control-Allow-Origin', - '/'.join(environ['HTTP_REFERER'].split('/')[:3]), - ), - ('Access-Control-Allow-Credentials', 'true'), - # ('access-control-allow-credentials', 'true'), - ('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'), - ('Connection', 'keep-alive'), - ('Vary', 'Origin'), - ], + ] + common_headers(environ), ) yield ejson.dumps(collections.OrderedDict([ ('websocket', True), From 9a9e119c36646c1a934d4364e8b2749160e938da Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 22 Sep 2015 11:49:56 +1000 Subject: [PATCH 03/10] Fix error in logging call during exception handling. --- dddp/websocket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dddp/websocket.py b/dddp/websocket.py index cf4bdfd..508ec15 100644 --- a/dddp/websocket.py +++ b/dddp/websocket.py @@ -308,7 +308,7 @@ class DDPWebSocketApplication(geventwebsocket.WebSocketApplication): } if record['exc_info'] == (None, None, None): del record['exc_info'] - self.logger.error('! %s %r', self, data, exc_info=exc_info, **record) + self.logger.error('! %s %r', self, data, **record) self.reply(msg, **data) def recv_connect(self, version=None, support=None, session=None): From 5e545ee7c25c1ba5c837fb62cacedaa4ed998381 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 22 Sep 2015 11:51:06 +1000 Subject: [PATCH 04/10] Stop printing path to stderr by default when handling GET on Meteor URLs. --- dddp/views.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/dddp/views.py b/dddp/views.py index 76ebfac..083582e 100644 --- a/dddp/views.py +++ b/dddp/views.py @@ -1,7 +1,8 @@ """Django DDP Server views.""" -from __future__ import print_function, absolute_import, unicode_literals +from __future__ import absolute_import, unicode_literals import io +import logging import mimetypes import os.path @@ -36,6 +37,7 @@ class MeteorView(View): """Django DDP Meteor server view.""" + logger = logging.getLogger(__name__) http_method_names = ['get', 'head'] json_path = None @@ -190,6 +192,12 @@ class MeteorView(View): def get(self, request, path): """Return HTML (or other related content) for Meteor.""" + self.logger.debug( + '[%s:%s] %s %s %s', + request.META['REMOTE_ADDR'], request.META['REMOTE_PORT'], + request.method, request.path, + request.META['SERVER_PROTOCOL'], + ) if path == '/meteor_runtime_config.js': config = { 'DDP_DEFAULT_CONNECTION_URL': request.build_absolute_uri('/'), @@ -217,6 +225,4 @@ class MeteorView(View): content_type=content_type, ) except KeyError: - print(path) return HttpResponse(self.html) - # raise Http404 From d7ea54539ca9a8db2c5c49d8d3c94534982789b6 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 22 Sep 2015 11:55:35 +1000 Subject: [PATCH 05/10] Show API endpoints when `dddp` command run with verbosity above `1`. --- dddp/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dddp/main.py b/dddp/main.py index babbc36..66c6f22 100644 --- a/dddp/main.py +++ b/dddp/main.py @@ -191,8 +191,9 @@ class DDPLauncher(object): self.logger.debug('PostgresGreenlet start') self._stop_event.clear() self.print('=> Discovering DDP endpoints...') - for api_path in sorted(self.api.api_path_map()): - self.logger.debug(' %s', api_path) + if self.verbosity > 1: + for api_path in sorted(self.api.api_path_map()): + print(' %s' % api_path) # start greenlets self.pgworker.start() From 9cf2ec3a0b687c893492c80fc621d1349a1c835e Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 22 Sep 2015 16:58:19 +1000 Subject: [PATCH 06/10] Correctly handle serving app content from the root path of a domain. --- CHANGES.rst | 4 ++++ dddp/views.py | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 81c1d9e..4cb975e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,10 @@ Change Log ========== +0.14.0 +------ +* Correctly handle serving app content from the root path of a domain. + 0.13.0 ------ * Abstract DDPLauncher out from dddp.main.serve to permit use from other contexts. diff --git a/dddp/views.py b/dddp/views.py index 083582e..eeb5dbf 100644 --- a/dddp/views.py +++ b/dddp/views.py @@ -46,7 +46,6 @@ class MeteorView(View): manifest = None program_json = None program_json_path = None - runtime_config = None star_json = None # top level layout @@ -62,6 +61,7 @@ class MeteorView(View): def __init__(self, **kwargs): """Initialisation for Django DDP server view.""" + self.runtime_config = {} # super(...).__init__ assigns kwargs to instance. super(MeteorView, self).__init__(**kwargs) @@ -148,6 +148,8 @@ class MeteorView(View): if item['where'] == 'client': if '?' in item['url']: item['url'] = item['url'].split('?', 1)[0] + if item['url'].startswith('/'): + item['url'] = item['url'][1:] self.client_map[item['url']] = item self.url_map[item['url']] = ( item['path_full'], @@ -198,7 +200,7 @@ class MeteorView(View): request.method, request.path, request.META['SERVER_PROTOCOL'], ) - if path == '/meteor_runtime_config.js': + if path == 'meteor_runtime_config.js': config = { 'DDP_DEFAULT_CONNECTION_URL': request.build_absolute_uri('/'), 'ROOT_URL': request.build_absolute_uri( From 81824e5bfe42020df019c66261819eb06472bcd8 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 22 Sep 2015 17:00:03 +1000 Subject: [PATCH 07/10] Fix dddp.test.test_project URL config and options for MiniMongo collections. --- dddp/test/meteor_todos/meteor_todos.js | 12 +++++++++--- dddp/test/test_project/urls.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/dddp/test/meteor_todos/meteor_todos.js b/dddp/test/meteor_todos/meteor_todos.js index 7e44016..10d00ca 100644 --- a/dddp/test/meteor_todos/meteor_todos.js +++ b/dddp/test/meteor_todos/meteor_todos.js @@ -1,8 +1,14 @@ if (Meteor.isClient) { // This code only runs on the client - Django = DDP.connect('http://'+window.location.hostname+':8000/'); - Tasks = new Mongo.Collection("django_todos.task", {"connection": Django}); - Django.subscribe('Tasks'); + options = {}; + if (__meteor_runtime_config__.hasOwnProperty('DDP_DEFAULT_CONNECTION_URL')) { + Django = Meteor; + } else { + Django = DDP.connect(window.location.protocol + '//'+window.location.hostname+':8000/'); + options.connection = Django; + } + Tasks = new Mongo.Collection("django_todos.task", options); + TaskSub = Django.subscribe('Tasks'); Template.body.helpers({ tasks: function () { return Tasks.find({}); diff --git a/dddp/test/test_project/urls.py b/dddp/test/test_project/urls.py index fc92be3..6547d18 100644 --- a/dddp/test/test_project/urls.py +++ b/dddp/test/test_project/urls.py @@ -1,6 +1,18 @@ +"""Django DDP test project - URL configuraiton.""" +import os.path + from django.conf import settings from django.conf.urls import patterns, include, url from django.contrib import admin +from dddp.views import MeteorView +import dddp.test + +app = MeteorView.as_view( + json_path=os.path.join( + os.path.dirname(dddp.test.__file__), + 'build', 'bundle', 'star.json' + ), +) urlpatterns = patterns('', # Examples: @@ -16,4 +28,6 @@ urlpatterns = patterns('', 'show_indexes': False, }, ), + # all remaining URLs routed to Meteor app. + url(r'^(?P.*)$', app), ) From 56c850948fba1fcf5b83f73cd696650a8b8764c4 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 22 Sep 2015 17:01:18 +1000 Subject: [PATCH 08/10] Update dddp/test/meteor_todos/ app to Meteor 1.2.0 (still works fine with older versions though). --- .../meteor_todos/.meteor/.finished-upgraders | 4 + dddp/test/meteor_todos/.meteor/packages | 17 ++- dddp/test/meteor_todos/.meteor/release | 2 +- dddp/test/meteor_todos/.meteor/versions | 111 ++++++++++-------- 4 files changed, 82 insertions(+), 52 deletions(-) diff --git a/dddp/test/meteor_todos/.meteor/.finished-upgraders b/dddp/test/meteor_todos/.meteor/.finished-upgraders index 8a76103..61ee313 100644 --- a/dddp/test/meteor_todos/.meteor/.finished-upgraders +++ b/dddp/test/meteor_todos/.meteor/.finished-upgraders @@ -6,3 +6,7 @@ notices-for-0.9.0 notices-for-0.9.1 0.9.4-platform-file notices-for-facebook-graph-api-2 +1.2.0-standard-minifiers-package +1.2.0-meteor-platform-split +1.2.0-cordova-changes +1.2.0-breaking-changes diff --git a/dddp/test/meteor_todos/.meteor/packages b/dddp/test/meteor_todos/.meteor/packages index 99704e0..ccb3468 100644 --- a/dddp/test/meteor_todos/.meteor/packages +++ b/dddp/test/meteor_todos/.meteor/packages @@ -4,6 +4,17 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. -meteor-platform -autopublish -insecure +standard-minifiers +meteor-base +mobile-experience +blaze-html-templates +session +jquery +tracker +logging +reload +random +ejson +spacebars +check +mongo diff --git a/dddp/test/meteor_todos/.meteor/release b/dddp/test/meteor_todos/.meteor/release index dab6b55..712ef79 100644 --- a/dddp/test/meteor_todos/.meteor/release +++ b/dddp/test/meteor_todos/.meteor/release @@ -1 +1 @@ -METEOR@1.1.0.2 +METEOR@1.2 diff --git a/dddp/test/meteor_todos/.meteor/versions b/dddp/test/meteor_todos/.meteor/versions index 410e1d9..40a8d86 100644 --- a/dddp/test/meteor_todos/.meteor/versions +++ b/dddp/test/meteor_todos/.meteor/versions @@ -1,48 +1,63 @@ -autopublish@1.0.3 -autoupdate@1.2.1 -base64@1.0.3 -binary-heap@1.0.3 -blaze@2.1.2 -blaze-tools@1.0.3 -boilerplate-generator@1.0.3 -callback-hook@1.0.3 -check@1.0.5 -ddp@1.1.0 -deps@1.0.7 -ejson@1.0.6 -fastclick@1.0.3 -geojson-utils@1.0.3 -html-tools@1.0.4 -htmljs@1.0.4 -http@1.1.0 -id-map@1.0.3 -insecure@1.0.3 -jquery@1.11.3_2 -json@1.0.3 -launch-screen@1.0.2 -livedata@1.0.13 -logging@1.0.7 -meteor@1.1.6 -meteor-platform@1.2.2 -minifiers@1.1.5 -minimongo@1.0.8 -mobile-status-bar@1.0.3 -mongo@1.1.0 -observe-sequence@1.0.6 -ordered-dict@1.0.3 -random@1.0.3 -reactive-dict@1.1.0 -reactive-var@1.0.5 -reload@1.1.3 -retry@1.0.3 -routepolicy@1.0.5 -session@1.1.0 -spacebars@1.0.6 -spacebars-compiler@1.0.6 -templating@1.1.1 -tracker@1.0.7 -ui@1.0.6 -underscore@1.0.3 -url@1.0.4 -webapp@1.2.0 -webapp-hashing@1.0.3 +autoupdate@1.2.3 +babel-compiler@5.8.24 +babel-runtime@0.1.4 +base64@1.0.4 +binary-heap@1.0.4 +blaze@2.1.3 +blaze-html-templates@1.0.1 +blaze-tools@1.0.4 +boilerplate-generator@1.0.4 +caching-compiler@1.0.0 +caching-html-compiler@1.0.1 +callback-hook@1.0.4 +check@1.0.6 +ddp@1.2.1 +ddp-client@1.2.1 +ddp-common@1.2.1 +ddp-server@1.2.1 +deps@1.0.8 +diff-sequence@1.0.1 +ecmascript@0.1.3 +ecmascript-collections@0.1.6 +ejson@1.0.7 +fastclick@1.0.7 +geojson-utils@1.0.4 +hot-code-push@1.0.0 +html-tools@1.0.5 +htmljs@1.0.5 +http@1.1.1 +id-map@1.0.4 +jquery@1.11.4 +launch-screen@1.0.3 +livedata@1.0.14 +logging@1.0.8 +meteor@1.1.7 +meteor-base@1.0.1 +minifiers@1.1.6 +minimongo@1.0.9 +mobile-experience@1.0.1 +mobile-status-bar@1.0.6 +mongo@1.1.1 +mongo-id@1.0.1 +npm-mongo@1.4.39_1 +observe-sequence@1.0.7 +ordered-dict@1.0.4 +promise@0.4.8 +random@1.0.4 +reactive-dict@1.1.1 +reactive-var@1.0.6 +reload@1.1.4 +retry@1.0.4 +routepolicy@1.0.6 +session@1.1.1 +spacebars@1.0.7 +spacebars-compiler@1.0.7 +standard-minifiers@1.0.0 +templating@1.1.2 +templating-tools@1.0.0 +tracker@1.0.8 +ui@1.0.7 +underscore@1.0.4 +url@1.0.5 +webapp@1.2.2 +webapp-hashing@1.0.4 From cefe6fe62d597e2ba31c916dc23d4379767235eb Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 22 Sep 2015 18:11:45 +1000 Subject: [PATCH 09/10] Account security tokens are now calculated for each minute allowing for finer grained token expiry. --- CHANGES.rst | 4 ++++ dddp/accounts/ddp.py | 50 ++++++++++++++++++++++---------------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4cb975e..748d66d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,10 @@ Change Log 0.14.0 ------ * Correctly handle serving app content from the root path of a domain. +* Account security tokens are now calculated for each minute allowing + for finer grained token expiry. +* DDP_PASSWORD_RESET_DAYS_VALID becomes DDP_PASSWORD_RESET_MINUTES_VALID. +* DDP_LOGIN_RESUME_DAYS_VALID becomes DDP_LOGIN_RESUME_MINUTES_VALID. 0.13.0 ------ diff --git a/dddp/accounts/ddp.py b/dddp/accounts/ddp.py index d49b0e8..8896f1c 100644 --- a/dddp/accounts/ddp.py +++ b/dddp/accounts/ddp.py @@ -45,36 +45,36 @@ class HashPurpose(object): RESUME_LOGIN = 'resume_login' -HASH_DAYS_VALID = { +HASH_MINUTES_VALID = { HashPurpose.PASSWORD_RESET: int( getattr( # keep possible attack window short to reduce chance of account # takeover through later discovery of password reset email message. - settings, 'DDP_PASSWORD_RESET_DAYS_VALID', '1', + settings, 'DDP_PASSWORD_RESET_MINUTES_VALID', '1440', # 24 hours ) ), HashPurpose.RESUME_LOGIN: int( getattr( # balance security and useability by allowing users to resume their # logins within a reasonable time, but not forever. - settings, 'DDP_LOGIN_RESUME_DAYS_VALID', '10', + settings, 'DDP_LOGIN_RESUME_MINUTES_VALID', '240', # 4 hours ) ), } -def iter_auth_hashes(user, purpose, days_valid): +def iter_auth_hashes(user, purpose, minutes_valid): """ Generate auth tokens tied to user and specified purpose. - The hash expires at midnight on the day of today + days_valid, such that - when days_valid=1 you get *at least* 24 hours to use the token. + The hash expires at midnight on the minute of now + minutes_valid, such that + when minutes_valid=1 you get *at least* 1 minute to use the token. """ - today = timezone.now().date() - for day in range(days_valid + 1): + now = timezone.now().replace(microsecond=0, second=0) + for minute in range(minutes_valid + 1): yield hashlib.sha1( '%s:%s:%s:%s:%s' % ( - today - datetime.timedelta(days=day), + now - datetime.timedelta(minutes=minute), user.password, purpose, user.pk, @@ -85,17 +85,17 @@ def iter_auth_hashes(user, purpose, days_valid): def get_auth_hash(user, purpose): """Generate a user hash for a particular purpose.""" - return iter_auth_hashes(user, purpose, days_valid=1).next() + return iter_auth_hashes(user, purpose, minutes_valid=1).next() -def calc_expiry_time(days_valid): +def calc_expiry_time(minutes_valid): """Return specific time an auth_hash will expire.""" return ( - timezone.now() + datetime.timedelta(days=days_valid + 1) - ).replace(hour=0, minute=0, second=0, microsecond=0) + timezone.now() + datetime.timedelta(minutes=minutes_valid + 1) + ).replace(second=0, microsecond=0) -def get_user_token(user, purpose, days_valid): +def get_user_token(user, purpose, minutes_valid): """Return login token info for given user.""" token = ''.join( dumps([ @@ -106,7 +106,7 @@ def get_user_token(user, purpose, days_valid): return { 'id': get_meteor_id(user), 'token': token, - 'tokenExpires': calc_expiry_time(days_valid), + 'tokenExpires': calc_expiry_time(minutes_valid), } @@ -309,7 +309,7 @@ class Auth(APIMixin): raise MeteorError(403, 'Authentication failed.') @classmethod - def validated_user(cls, token, purpose, days_valid): + def validated_user(cls, token, purpose, minutes_valid): """Resolve and validate auth token, returns user object.""" try: username, auth_hash = loads(token.decode('base64')) @@ -323,7 +323,7 @@ class Auth(APIMixin): user.backend = 'django.contrib.auth.backends.ModelBackend' except cls.user_model.DoesNotExist: cls.auth_failed(username=username, token=token) - if auth_hash not in iter_auth_hashes(user, purpose, days_valid): + if auth_hash not in iter_auth_hashes(user, purpose, minutes_valid): cls.auth_failed(username=username, token=token) return user @@ -415,7 +415,7 @@ class Auth(APIMixin): self.do_login(user) return get_user_token( user=user, purpose=HashPurpose.RESUME_LOGIN, - days_valid=HASH_DAYS_VALID[HashPurpose.RESUME_LOGIN], + minutes_valid=HASH_MINUTES_VALID[HashPurpose.RESUME_LOGIN], ) def do_login(self, user): @@ -472,7 +472,7 @@ class Auth(APIMixin): self.do_login(user) return get_user_token( user=user, purpose=HashPurpose.RESUME_LOGIN, - days_valid=HASH_DAYS_VALID[HashPurpose.RESUME_LOGIN], + minutes_valid=HASH_MINUTES_VALID[HashPurpose.RESUME_LOGIN], ) # Call to `authenticate` was unable to verify the username and password. @@ -495,13 +495,13 @@ class Auth(APIMixin): # pull the username and auth_hash from the token user = self.validated_user( params['resume'], purpose=HashPurpose.RESUME_LOGIN, - days_valid=HASH_DAYS_VALID[HashPurpose.RESUME_LOGIN], + minutes_valid=HASH_MINUTES_VALID[HashPurpose.RESUME_LOGIN], ) self.do_login(user) return get_user_token( user=user, purpose=HashPurpose.RESUME_LOGIN, - days_valid=HASH_DAYS_VALID[HashPurpose.RESUME_LOGIN], + minutes_valid=HASH_MINUTES_VALID[HashPurpose.RESUME_LOGIN], ) @api_endpoint('changePassword') @@ -538,10 +538,10 @@ class Auth(APIMixin): except self.user_model.DoesNotExist: self.auth_failed() - days_valid = HASH_DAYS_VALID[HashPurpose.PASSWORD_RESET] + minutes_valid = HASH_MINUTES_VALID[HashPurpose.PASSWORD_RESET] token = get_user_token( user=user, purpose=HashPurpose.PASSWORD_RESET, - days_valid=days_valid, + minutes_valid=minutes_valid, ) forgot_password.send( @@ -549,7 +549,7 @@ class Auth(APIMixin): user=user, token=token, request=this.request, - expiry_date=calc_expiry_time(days_valid), + expiry_date=calc_expiry_time(minutes_valid), ) @api_endpoint('resetPassword') @@ -557,7 +557,7 @@ class Auth(APIMixin): """Reset password using a token received in email then logs user in.""" user = self.validated_user( token, purpose=HashPurpose.PASSWORD_RESET, - days_valid=HASH_DAYS_VALID[HashPurpose.PASSWORD_RESET], + minutes_valid=HASH_MINUTES_VALID[HashPurpose.PASSWORD_RESET], ) user.set_password(new_password) user.save() From 3e8dd2f26ba3673a56a6b7224016f62674bc73bb Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Tue, 22 Sep 2015 18:21:14 +1000 Subject: [PATCH 10/10] Update CHANGES.rst, bump version number. --- CHANGES.rst | 14 ++++++++++---- setup.py | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 748d66d..97e9a89 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,10 +4,16 @@ Change Log 0.14.0 ------ * Correctly handle serving app content from the root path of a domain. -* Account security tokens are now calculated for each minute allowing - for finer grained token expiry. -* DDP_PASSWORD_RESET_DAYS_VALID becomes DDP_PASSWORD_RESET_MINUTES_VALID. -* DDP_LOGIN_RESUME_DAYS_VALID becomes DDP_LOGIN_RESUME_MINUTES_VALID. +* Account security tokens are now calculated for each minute allowing for finer grained token expiry. +* Fix bug in error handling where invalid arguments were being passed to `logging.error()`. +* Change setting names (and implied meanings): + - DDP_PASSWORD_RESET_DAYS_VALID becomes + DDP_PASSWORD_RESET_MINUTES_VALID. + - DDP_LOGIN_RESUME_DAYS_VALID becomes DDP_LOGIN_RESUME_MINUTES_VALID. +* Include `created` field in logs collection. +* Stop depending on `Referrer` HTTP header which is optional. +* Honour `--verbosity` in `dddp` command, now showing API endpoints in more verbose modes. +* Updated `dddp.test` to Meteor 1.2 and also showing example of URL config to serve Meteor files from Python. 0.13.0 ------ diff --git a/setup.py b/setup.py index 82bba90..9f4282b 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ CLASSIFIERS = [ setup( name='django-ddp', - version='0.13.0', + version='0.14.0', description=__doc__, long_description=open('README.rst').read(), author='Tyson Clugg',