Browse files

adding bcrypt support, thanks osantana and hvdklauw

  • Loading branch information...
1 parent 19bc1ed commit dc8004d28900baa523db372682f162d231076334 @sorl committed Mar 17, 2011
Showing with 74 additions and 8 deletions.
  1. +24 −5 README.rst
  2. +2 −1 primate/admin.py
  3. +1 −1 primate/auth/base.py
  4. +16 −0 primate/auth/forms.py
  5. +29 −0 primate/auth/mixins.py
  6. +1 −0 primate/models.py
  7. +1 −1 setup.py
View
29 README.rst
@@ -53,7 +53,7 @@ Using
-----
After installing this patch you effectively have no User model at all. You have
to create one on your own and define it in your settings. I will give you an
-example on how to do this.
+example on how to do this using the provided UserBase class.
``project/users/models.py``::
@@ -81,7 +81,7 @@ It's simple
- To add a field just add a field to the model as you would normally.
- To override a field just override the field name and it will be used instead
- of the one defined in UserBase.
+ of the one defined in ``UserBase``.
The overriding feature is something special not available in normal Django
model abstract classes and is done in the custom metaclass. You can also remove
@@ -95,7 +95,7 @@ To make the admin work I have made the monkey patch ``primate.patch`` patch the
``admin.autodiscover`` so that it does not register the default admin class for
``django.contrib.auth.User``. This means that you will need to register that
your self. The easiest way to do that is to first add ``users`` to your
-``INSTALLED_APPS`` and then add something like this to ``ùsers.admin``::
+``INSTALLED_APPS`` and then add something like this to ``ùsers/admin.py``::

A small typo in directory name: s/ùsers/users/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
from primate.admin import UserAdminBase
from django.contrib import admin
@@ -109,8 +109,8 @@ your self. The easiest way to do that is to first add ``users`` to your
admin.site.register(User, UserAdmin)
-What's new in the default user model?
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+What's now in ``UserBase`` compared to ``django.contrib.auth.models.User``?
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I have made some minor changes:
1. Removed ``first_name`` and ``last_name``
@@ -144,3 +144,22 @@ So the time has come, just add this to your settings::
}
+Alternative password hashing
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+SHA-1 is the default django hashing algorithm for passwords. Some may not agree
+that this is the best choice. ``django-primate`` makes it simple for you to use
+alternative hashing as you can just override the ``check_password`` and
+``set_password`` methods in your custom user model. Since bcrypt is a good
+choice there is a simple way for you to implement hashing using this::
+
+ ``project/users/models.py``::
+
+ from primate.models import UserBase, UserMeta, BcryptMixin
+ from django.db import models
+
+ class CustomUser(BcryptMixin, UserBase):
+ __metaclass__ = UserMeta
+
+
+Note that this will update all passwords on authorization success to use bcrypt.
+
View
3 primate/admin.py
@@ -1,6 +1,6 @@
from django.conf import settings
from django.contrib.admin.sites import site
-from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AdminPasswordChangeForm
+from django.contrib.auth.forms import UserCreationForm, AdminPasswordChangeForm
from django.contrib.auth.models import Group
from django.contrib import admin
from django.contrib import messages
@@ -13,6 +13,7 @@
from django.utils.html import escape
from django.utils.translation import ugettext, ugettext_lazy as _
from django.views.decorators.csrf import csrf_protect
+from primate.auth.forms import UserChangeForm
csrf_protect_m = method_decorator(csrf_protect)
View
2 primate/auth/base.py
@@ -88,7 +88,7 @@ class UserBase(models.Model):
username = models.CharField(_('username'), max_length=50, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
name = models.CharField(_('name'), max_length=100, blank=True)
email = models.EmailField(_('e-mail address'), blank=True, null=True, unique=True)
- password = models.CharField(_('password'), max_length=128, help_text=_("Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."))
+ password = models.CharField(_('password'), max_length=128)
is_staff = models.BooleanField(_('staff status'), default=False, help_text=_("Designates whether the user can log into this admin site."))
is_active = models.BooleanField(_('active'), default=True, help_text=_("Designates whether this user should be treated as active. Unselect this instead of deleting accounts."))
is_superuser = models.BooleanField(_('superuser status'), default=False, help_text=_("Designates that this user has all permissions without explicitly assigning them."))
View
16 primate/auth/forms.py
@@ -0,0 +1,16 @@
+from django.contrib import auth
+from django import forms
+from django.utils.safestring import mark_safe
+from django.utils.translation import ugettext_lazy as _
+
+
+class PassWidget(forms.Widget):
+ def render(self, name, value, attrs=None):
+ return mark_safe(u'<input type="button" value="%s" onclick="window.location.href=\'password/\'">' % _('Change Password'))
+
+
+class UserChangeForm(auth.forms.UserChangeForm):
+ def __init__(self, *args, **kwargs):
+ super(UserChangeForm, self).__init__(*args, **kwargs)
+ self.fields['password'].widget = PassWidget()
+
View
29 primate/auth/mixins.py
@@ -0,0 +1,29 @@
+from django.utils.crypto import constant_time_compare as ctcmp
+from django.utils.encoding import smart_str
+
+
+class BcryptMixin(object):
+ rounds = 12
+
+ def check_password(self, raw_password):
+ import bcrypt
+ if self.password.startswith('bcrypt$'):
+ hash_ = self.password[6:] # remove bcrypt prefix
+ salt = hash_[:29]
+ return ctcmp(hash_, bcrypt.hashpw(raw_password, salt))
+ if super(BcryptMixin, self).check_password(raw_password):
+ # Convert the password to the new, more secure format.
+ self.set_password(raw_password)
+ self.save()
+ return True
+ return False
+
+ def set_password(self, raw_password):
+ import bcrypt
+ if raw_password is None:
+ self.set_unusable_password()
+ else:
+ raw_password = smart_str(raw_password)
+ salt = bcrypt.gensalt(self.rounds)
+ self.password = 'bcrypt%s' % bcrypt.hashpw(raw_password, salt)
+
View
1 primate/models.py
@@ -1,2 +1,3 @@
from primate.auth.base import UserBase, UserMeta
+from primate.auth.mixins import BcryptMixin
View
2 setup.py
@@ -3,7 +3,7 @@
setup(
name='django-primate',
- version='0.1',
+ version='0.1.1',
description='A modular django user',
long_description=open('README.rst').read(),
author='Mikko Hellsing',

0 comments on commit dc8004d

Please sign in to comment.