From 29d12cd3917c1a792c3a891e39ab15f99e8b380d Mon Sep 17 00:00:00 2001 From: Keith Yang Date: Sat, 16 Jul 2016 16:28:25 +0800 Subject: Add one-way password hash to security example in Quick Tutorial. --- docs/quick_tutorial/authentication.rst | 23 ++++++++++++++++++++++ docs/quick_tutorial/authentication/setup.py | 3 ++- .../authentication/tutorial/security.py | 16 +++++++++++++-- .../authentication/tutorial/views.py | 7 +++++-- docs/quick_tutorial/authorization/setup.py | 3 ++- .../authorization/tutorial/security.py | 16 +++++++++++++-- .../quick_tutorial/authorization/tutorial/views.py | 7 +++++-- 7 files changed, 65 insertions(+), 10 deletions(-) diff --git a/docs/quick_tutorial/authentication.rst b/docs/quick_tutorial/authentication.rst index acff97f3b..c28958b33 100644 --- a/docs/quick_tutorial/authentication.rst +++ b/docs/quick_tutorial/authentication.rst @@ -34,6 +34,17 @@ Steps .. code-block:: bash $ cd ..; cp -r view_classes authentication; cd authentication + +#. This step depends on bcrypt_, so add it as a dependency in + ``authentication/setup.py``: + + .. literalinclude:: authentication/setup.py + :linenos: + +#. Now we can activate the development-mode distribution: + + .. code-block:: bash + $ $VENV/bin/pip install -e . #. Put the security hash in the ``authentication/development.ini`` @@ -103,6 +114,11 @@ In this example we chose to use the bundled :ref:`AuthTktAuthenticationPolicy ` policy. We enabled it in our configuration and provided a ticket-signing secret in our INI file. +The function ``hash_password`` hashes user's password by bcrypt_ instead of +storing password in plain text directly as a best practice [1]_. And function +``check_password`` will compare the hashed value of the submitted password +against the hashed value of the user's password. + Our view class grew a login view. When you reached it via a ``GET`` request, it returned a login form. When reached via ``POST``, it processed the submitted username and password against the "groupfinder" callable that we registered in @@ -126,3 +142,10 @@ Extra credit .. seealso:: See also :ref:`security_chapter`, :ref:`AuthTktAuthenticationPolicy `. + +.. _bcrypt: https://pypi.python.org/pypi/bcrypt + +.. [1] We are using the bcrypt_ package from PyPI to hash our passwords + securely. There are other one-way hash algorithms for passwords if + bcrypt is an issue on your system. Just make sure that it's an + algorithm approved for storing passwords versus a generic one-way hash. diff --git a/docs/quick_tutorial/authentication/setup.py b/docs/quick_tutorial/authentication/setup.py index 2221b72e9..7a6ff4226 100644 --- a/docs/quick_tutorial/authentication/setup.py +++ b/docs/quick_tutorial/authentication/setup.py @@ -2,7 +2,8 @@ from setuptools import setup requires = [ 'pyramid', - 'pyramid_chameleon' + 'pyramid_chameleon', + 'bcrypt' ] setup(name='tutorial', diff --git a/docs/quick_tutorial/authentication/tutorial/security.py b/docs/quick_tutorial/authentication/tutorial/security.py index ab90bab2c..e585e2642 100644 --- a/docs/quick_tutorial/authentication/tutorial/security.py +++ b/docs/quick_tutorial/authentication/tutorial/security.py @@ -1,5 +1,17 @@ -USERS = {'editor': 'editor', - 'viewer': 'viewer'} +import bcrypt + + +def hash_password(pw): + pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt()) + return pwhash.decode('utf8') + +def check_password(pw, hashed_pw): + expected_hash = hashed_pw.encode('utf8') + return bcrypt.checkpw(pw.encode('utf8'), expected_hash) + + +USERS = {'editor': hash_password('editor'), + 'viewer': hash_password('viewer')} GROUPS = {'editor': ['group:editors']} diff --git a/docs/quick_tutorial/authentication/tutorial/views.py b/docs/quick_tutorial/authentication/tutorial/views.py index ab46eb2dd..b07538d5e 100644 --- a/docs/quick_tutorial/authentication/tutorial/views.py +++ b/docs/quick_tutorial/authentication/tutorial/views.py @@ -9,7 +9,10 @@ from pyramid.view import ( view_defaults ) -from .security import USERS +from .security import ( + USERS, + check_password +) @view_defaults(renderer='home.pt') @@ -40,7 +43,7 @@ class TutorialViews: if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] - if USERS.get(login) == password: + if check_password(password, USERS.get(login)): headers = remember(request, login) return HTTPFound(location=came_from, headers=headers) diff --git a/docs/quick_tutorial/authorization/setup.py b/docs/quick_tutorial/authorization/setup.py index 2221b72e9..7a6ff4226 100644 --- a/docs/quick_tutorial/authorization/setup.py +++ b/docs/quick_tutorial/authorization/setup.py @@ -2,7 +2,8 @@ from setuptools import setup requires = [ 'pyramid', - 'pyramid_chameleon' + 'pyramid_chameleon', + 'bcrypt' ] setup(name='tutorial', diff --git a/docs/quick_tutorial/authorization/tutorial/security.py b/docs/quick_tutorial/authorization/tutorial/security.py index ab90bab2c..e585e2642 100644 --- a/docs/quick_tutorial/authorization/tutorial/security.py +++ b/docs/quick_tutorial/authorization/tutorial/security.py @@ -1,5 +1,17 @@ -USERS = {'editor': 'editor', - 'viewer': 'viewer'} +import bcrypt + + +def hash_password(pw): + pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt()) + return pwhash.decode('utf8') + +def check_password(pw, hashed_pw): + expected_hash = hashed_pw.encode('utf8') + return bcrypt.checkpw(pw.encode('utf8'), expected_hash) + + +USERS = {'editor': hash_password('editor'), + 'viewer': hash_password('viewer')} GROUPS = {'editor': ['group:editors']} diff --git a/docs/quick_tutorial/authorization/tutorial/views.py b/docs/quick_tutorial/authorization/tutorial/views.py index 43d14455a..b2dc905c0 100644 --- a/docs/quick_tutorial/authorization/tutorial/views.py +++ b/docs/quick_tutorial/authorization/tutorial/views.py @@ -10,7 +10,10 @@ from pyramid.view import ( forbidden_view_config ) -from .security import USERS +from .security import ( + USERS, + check_password +) @view_defaults(renderer='home.pt') @@ -42,7 +45,7 @@ class TutorialViews: if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] - if USERS.get(login) == password: + if check_password(password, USERS.get(login)): headers = remember(request, login) return HTTPFound(location=came_from, headers=headers) -- cgit v1.2.3