Просмотр исходного кода

Security app was split into smaller apps.

Ralfp 12 лет назад
Родитель
Сommit
a2fd43f5fc

+ 4 - 3
misago/activation/views.py

@@ -4,10 +4,11 @@ from misago.banning.models import check_ban
 from misago.banning.decorators import block_banned
 from misago.banning.views import error_banned
 from misago.forms.layouts import FormLayout
-from misago.messages import Message
-from misago.security.auth import sign_user_in
-from misago.security.decorators import *
+from misago.auth.means import sign_user_in
+from misago.auth.decorators import block_authenticated
 from misago.activation.forms import UserSendActivationMailForm
+from misago.bruteforce.decorators import block_jammed
+from misago.messages import Message
 from misago.users.models import User
 from misago.views import redirect_message, error404
 

+ 19 - 0
misago/auth/decorators.py

@@ -0,0 +1,19 @@
+from django.utils.translation import ugettext as _
+from misago.views import error403
+
+def block_authenticated(f):
+    def decorator(*args, **kwargs):
+        request = args[0]
+        if not request.firewall.admin and request.user.is_authenticated():
+            return error403(request, _("%{username}s, this page is not available to signed in users.") % {'username': request.user.username})
+        return f(*args, **kwargs)
+    return decorator
+
+
+def block_guest(f):
+    def decorator(*args, **kwargs):
+        request = args[0]
+        if not request.user.is_authenticated():
+            return error403(request, _("Dear Guest, only signed in members are allowed to access this page. Please sign in or register and try again."))
+        return f(*args, **kwargs)
+    return decorator

+ 30 - 0
misago/auth/forms.py

@@ -0,0 +1,30 @@
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+from misago.forms import Form
+
+class SignInForm(Form):
+    user_email = forms.EmailField(max_length=255, label=_("Your email"))
+    user_password = forms.CharField(widget=forms.PasswordInput, max_length=255, label=_("Your password"))
+    user_remember_me = forms.BooleanField(label=_("Stay Signed In"), help_text=_("Sign me In automatically next time"), required=False)
+    
+    layout = [
+              (
+               None,
+               (
+                ('user_email', {'attrs': {'placeholder': _("Enter your e-mail")}}),
+                ('user_password', {'has_value': False, 'placeholder': _("Enter your password")}),
+                )
+               ),
+              (
+               None,
+               ['user_remember_me'],
+               ),
+              ]
+    
+    def __init__(self, *args, **kwargs):
+        show_remember_me = kwargs['show_remember_me']
+        del kwargs['show_remember_me']
+        
+        super(SignInForm, self).__init__(*args, **kwargs)
+        if not show_remember_me:
+            del self.fields['user_remember_me']

+ 1 - 1
misago/security/auth.py → misago/auth/methods.py

@@ -3,7 +3,7 @@ from django.conf import settings
 from django.utils import timezone
 from django.utils.translation import ugettext_lazy as _
 from misago.banning.models import check_ban
-from misago.security.models import SignInAttempt
+from misago.bruteforce.models import SignInAttempt
 from misago.sessions.models import Token
 from misago.users.models import User
     

+ 13 - 0
misago/auth/urls.py

@@ -0,0 +1,13 @@
+from django.conf.urls import patterns, url
+from misago.admin import ADMIN_PATH
+
+urlpatterns = patterns('misago.auth.views',
+    url(r'^signin/$', 'signin', name="sign_in"),
+    url(r'^signout/$', 'signout', name="sign_out"),
+)
+
+# Include admin patterns
+if ADMIN_PATH:
+    urlpatterns += patterns('misago.auth.views',
+        url(r'^' + ADMIN_PATH + 'signout/$', 'signout', name="admin_sign_out"),
+    )

+ 110 - 0
misago/auth/views.py

@@ -0,0 +1,110 @@
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.template import RequestContext
+from django.utils import timezone
+from django.utils.translation import ugettext as _
+from misago.admin import site
+from misago.csrf.decorators import check_csrf
+from misago.banning.decorators import block_banned
+from misago.forms.layouts import FormLayout
+from misago.messages import Message
+from misago.auth.methods import auth
+from misago.auth.decorators import block_authenticated, block_guest
+from misago.auth.forms import SignInForm
+from misago.auth.methods import AuthException, auth_admin, auth_forum, sign_user_in
+from misago.bruteforce.decorators import block_jammed
+from misago.bruteforce.models import SignInAttempt
+from misago.sessions.models import Token
+from misago.utils import get_random_string
+
+@block_banned
+@block_authenticated
+@block_jammed
+def signin(request):
+    message = request.messages.get_message('security')
+    bad_password = False
+    not_active = False
+    banned_account = False   
+    
+    if request.method == 'POST':
+        form = SignInForm(
+                          request.POST,
+                          show_remember_me=not request.firewall.admin and request.settings['remember_me_allow'],
+                          request=request
+                          )
+        
+        if form.is_valid():
+            try:
+                # Configure correct auth and redirect links
+                if request.firewall.admin:
+                    auth_method = auth_admin
+                    success_redirect = reverse(site.get_admin_index())
+                else:
+                    auth_method = auth_forum
+                    success_redirect = reverse('index')
+                
+                # Authenticate user
+                user = auth_method(
+                                  request,
+                                  form.cleaned_data['user_email'],
+                                  form.cleaned_data['user_password'],
+                                  )
+                
+                sign_user_in(request, user)     
+                remember_me_token = False
+                
+                if not request.firewall.admin and request.settings['remember_me_allow'] and form.cleaned_data['user_remember_me']:
+                    remember_me_token = get_random_string(42)
+                    remember_me = Token(
+                                        id=remember_me_token,
+                                        user=user,
+                                        created=timezone.now(),
+                                        accessed=timezone.now(),
+                                        )
+                    remember_me.save()
+                if remember_me_token:
+                    request.cookie_jar.set('TOKEN', remember_me_token, True)
+                request.messages.set_flash(Message(_("Welcome back, %(username)s!") % {'username': user.username}), 'success', 'security')
+                return redirect(success_redirect)
+            except AuthException as e:
+                message = Message(e.error, 'error')
+                bad_password = e.password
+                banned_account = e.ban
+                not_active = e.activation
+                
+                # If not in Admin, register failed attempt
+                if not request.firewall.admin and e.type == auth.CREDENTIALS:
+                    SignInAttempt.objects.register_attempt(request.session.get_ip(request))
+                    
+                    # Have we jammed our account?
+                    if SignInAttempt.objects.is_jammed(request.settings, request.session.get_ip(request)):
+                        request.jam.expires = timezone.now()
+                        return redirect(reverse('sign_in'))
+        else:
+            message = Message(form.non_field_errors()[0], 'error')
+    else:
+        form = SignInForm(
+                          show_remember_me=not request.firewall.admin and request.settings['remember_me_allow'],
+                          request=request
+                          )
+    return request.theme.render_to_response('signin.html',
+                                            {
+                                             'message': message,
+                                             'bad_password': bad_password,
+                                             'banned_account': banned_account,
+                                             'not_active': not_active,
+                                             'form': FormLayout(form),
+                                             'hide_signin': True, 
+                                             },
+                                            context_instance=RequestContext(request));
+
+
+@block_guest
+@check_csrf
+def signout(request):
+    user = request.user
+    request.session.sign_out(request)
+    request.messages.set_flash(Message(_("You have been signed out.")), 'info', 'security')
+    if request.firewall.admin:
+        return redirect(reverse(site.get_admin_index()))
+    return redirect(reverse('index'))

+ 0 - 0
misago/security/management/__init__.py → misago/bruteforce/__init__.py


+ 1 - 1
misago/security/context_processors.py → misago/bruteforce/context_processors.py

@@ -1,4 +1,4 @@
-def security(request):
+def is_jammed(request):
     if request.user.is_crawler():
         return {}
     return {

+ 10 - 0
misago/bruteforce/decorators.py

@@ -0,0 +1,10 @@
+from django.utils.translation import ugettext as _
+from misago.views import error403
+
+def block_jammed(f):
+    def decorator(*args, **kwargs):
+        request = args[0]
+        if not request.firewall.admin and request.jam.is_jammed():
+            return error403(request, _("You have used up allowed attempts quota and we temporarily banned you from accessing this page."))
+        return f(*args, **kwargs)
+    return decorator

+ 0 - 0
misago/security/management/commands/__init__.py → misago/bruteforce/management/__init__.py


+ 0 - 0
misago/bruteforce/management/commands/__init__.py


+ 1 - 3
misago/security/management/commands/clearattempts.py → misago/bruteforce/management/commands/clearattempts.py

@@ -1,8 +1,6 @@
 from datetime import timedelta
-from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
-from django.core.management.base import BaseCommand, CommandError
 from django.utils import timezone
-from misago.security.models import SignInAttempt
+from misago.bruteforce.models import SignInAttempt
 
 class Command(BaseCommand):
     """

+ 13 - 0
misago/bruteforce/middleware.py

@@ -0,0 +1,13 @@
+from misago.bruteforce.models import JamCache
+
+class JamMiddleware(object):
+    def process_request(self, request):
+        if request.user.is_crawler():
+            return None
+        try:
+            request.jam = request.session['jam']
+        except KeyError:
+            request.jam = JamCache()
+            request.session['jam'] = request.jam
+        if not request.firewall.admin:
+            request.jam.check_for_updates(request)

+ 0 - 0
misago/security/models.py → misago/bruteforce/models.py


+ 10 - 0
misago/csrf/decorators.py

@@ -0,0 +1,10 @@
+from django.utils.translation import ugettext as _
+from misago.views import error403
+
+def check_csrf(f):
+    def decorator(*args, **kwargs):
+        request = args[0]
+        if not request.csrf.request_secure(request):
+            return error403(request, _("Request authorization is invalid. Please try again."))
+        return f(*args, **kwargs)
+    return decorator

+ 1 - 1
misago/csrf/middleware.py

@@ -1,5 +1,5 @@
-from misago.security import get_random_string
 from misago.csrf import CSRFProtection
+from misago.utils import get_random_string
 
 class CSRFMiddleware(object):
     def process_request(self, request):

+ 0 - 0
misago/firewalls/__init__.py


+ 1 - 1
misago/security/firewalls.py → misago/firewalls/firewalls.py

@@ -1,7 +1,7 @@
 from django.conf import settings
 from misago.admin import ADMIN_PATH
 from misago.views import error403, error404
-from misago.security.views import signin
+from misago.auth.views import signin
 
 class FirewallForum(object):
     """

+ 3 - 16
misago/security/middleware.py → misago/firewalls/middleware.py

@@ -1,13 +1,12 @@
 from django.conf import settings
-from misago.security.firewalls import *
-from misago.security.models import JamCache
+from misago.firewalls.firewalls import *
 from misago.themes.theme import Theme
 
 class FirewallMiddleware(object):
     firewall_admin = FirewallAdmin()
     firewall_forum = FirewallForum()
+    
     def process_request(self, request):
-        # Admin firewall test
         if settings.ADMIN_PATH and self.firewall_admin.behind_firewall(request.path_info):
             request.firewall = self.firewall_admin
             request.theme.set_theme('admin')
@@ -15,16 +14,4 @@ class FirewallMiddleware(object):
             request.firewall = self.firewall_forum
 
     def process_view(self, request, callback, callback_args, callback_kwargs):
-        return request.firewall.process_view(request, callback, callback_args, callback_kwargs)
-
-class JamMiddleware(object):
-    def process_request(self, request):
-        if request.user.is_crawler():
-            return None
-        try:
-            request.jam = request.session['jam']
-        except KeyError:
-            request.jam = JamCache()
-            request.session['jam'] = request.jam
-        if not request.firewall.admin:
-            request.jam.check_for_updates(request)
+        return request.firewall.process_view(request, callback, callback_args, callback_kwargs)

+ 1 - 1
misago/newsletters/models.py

@@ -1,5 +1,5 @@
 from django.db import models
-from misago.security import get_random_string
+from misago.utils import get_random_string
 
 class Newsletter(models.Model):
     name = models.CharField(max_length=255)

+ 3 - 2
misago/register/views.py

@@ -4,10 +4,11 @@ from django.template import RequestContext
 from django.utils import timezone
 from django.utils.translation import ugettext as _
 from misago.banning.decorators import block_banned
+from misago.bruteforce.decorators import block_jammed
 from misago.forms.layouts import FormLayout
 from misago.messages import Message
-from misago.security.auth import sign_user_in
-from misago.security.decorators import *
+from misago.auth.decorators import block_authenticated
+from misago.auth.methods import sign_user_in
 from misago.register.forms import UserRegisterForm
 from misago.users.models import User
 from misago.views import redirect_message

+ 3 - 2
misago/resetpswd/views.py

@@ -3,13 +3,14 @@ from django.utils.translation import ugettext as _
 from misago.banning.models import check_ban
 from misago.banning.decorators import block_banned
 from misago.banning.views import error_banned
+from misago.bruteforce.decorators import block_jammed
 from misago.forms.layouts import FormLayout
 from misago.messages import Message
-from misago.security import get_random_string
-from misago.security.decorators import *
+from misago.auth.decorators import block_authenticated
 from misago.resetpswd.forms import UserResetPasswordForm
 from misago.users.models import User
 from misago.views import redirect_message, error404
+from misago.utils import get_random_string
 
 
 @block_banned

+ 0 - 4
misago/security/__init__.py

@@ -1,4 +0,0 @@
-from django.utils import crypto
-    
-def get_random_string(length):
-    return crypto.get_random_string(length, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM")

+ 0 - 38
misago/security/decorators.py

@@ -1,38 +0,0 @@
-from django.utils.translation import ugettext_lazy as _
-from misago.security.models import SignInAttempt
-from misago.views import error403
-
-def block_authenticated(f):
-    def decorator(*args, **kwargs):
-        request = args[0]
-        if not request.firewall.admin and request.user.is_authenticated():
-            return error403(request, _("%{username}s, this page is not available to signed in users.") % {'username': request.user.username})
-        return f(*args, **kwargs)
-    return decorator
-
-
-def block_jammed(f):
-    def decorator(*args, **kwargs):
-        request = args[0]
-        if not request.firewall.admin and request.jam.is_jammed():
-            return error403(request, _("You have used up allowed sign-in attempts quota and we temporarily banned you from signing in."))
-        return f(*args, **kwargs)
-    return decorator
-
-
-def block_guest(f):
-    def decorator(*args, **kwargs):
-        request = args[0]
-        if not request.user.is_authenticated():
-            return error403(request, _("Dear Guest, only signed in members are allowed to access this page. Please sign in or register and try again."))
-        return f(*args, **kwargs)
-    return decorator
-
-
-def check_csrf(f):
-    def decorator(*args, **kwargs):
-        request = args[0]
-        if not request.csrf.request_secure(request):
-            return error403(request, _("Request authorization is invalid. Please try again."))
-        return f(*args, **kwargs)
-    return decorator

+ 0 - 30
misago/security/forms.py

@@ -1,30 +0,0 @@
-from django import forms
-from django.utils.translation import ugettext_lazy as _
-from misago.forms import Form
-
-class SignInForm(Form):
-    user_email = forms.EmailField(max_length=255, label=_("Your email"))
-    user_password = forms.CharField(widget=forms.PasswordInput, max_length=255, label=_("Your password"))
-    user_remember_me = forms.BooleanField(label=_("Stay Signed In"), help_text=_("Sign me In automatically next time"), required=False)
-    
-    layout = [
-              (
-               None,
-               (
-                ('user_email', {'attrs': {'placeholder': _("Enter your e-mail")}}),
-                ('user_password', {'has_value': False, 'placeholder': _("Enter your password")}),
-                )
-               ),
-              (
-               None,
-               ['user_remember_me'],
-               ),
-              ]
-    
-    def __init__(self, *args, **kwargs):
-        show_remember_me = kwargs['show_remember_me']
-        del kwargs['show_remember_me']
-        
-        super(SignInForm, self).__init__(*args, **kwargs)
-        if not show_remember_me:
-            del self.fields['user_remember_me']

+ 0 - 13
misago/security/urls.py

@@ -1,13 +0,0 @@
-from django.conf.urls import patterns, url, include
-from misago.admin import ADMIN_PATH
-
-urlpatterns = patterns('misago.security.views',
-    url(r'^signin/$', 'signin', name="sign_in"),
-    url(r'^signout/$', 'signout', name="sign_out"),
-)
-
-# Include admin patterns
-if ADMIN_PATH:
-    urlpatterns += patterns('misago.security.views',
-        url(r'^' + ADMIN_PATH + 'signout/$', 'signout', name="admin_sign_out"),
-    )

+ 0 - 109
misago/security/views.py

@@ -1,109 +0,0 @@
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.template import RequestContext
-from django.utils import timezone
-from django.utils.translation import ugettext as _
-from misago.admin import site
-from misago.banning.decorators import block_banned
-from misago.forms.layouts import FormLayout
-from misago.messages import Message
-from misago.security import get_random_string
-import misago.security.auth as auth
-from misago.security.auth import AuthException, auth_admin, auth_forum, sign_user_in
-from misago.security.decorators import *
-from misago.security.models import SignInAttempt
-from misago.sessions.models import Token
-from forms import SignInForm
-
-@block_banned
-@block_authenticated
-@block_jammed
-def signin(request):
-    message = request.messages.get_message('security')
-    bad_password = False
-    not_active = False
-    banned_account = False   
-    
-    if request.method == 'POST':
-        form = SignInForm(
-                          request.POST,
-                          show_remember_me=not request.firewall.admin and request.settings['remember_me_allow'],
-                          request=request
-                          )
-        
-        if form.is_valid():
-            try:
-                # Configure correct auth and redirect links
-                if request.firewall.admin:
-                    auth_method = auth_admin
-                    success_redirect = reverse(site.get_admin_index())
-                else:
-                    auth_method = auth_forum
-                    success_redirect = reverse('index')
-                
-                # Authenticate user
-                user = auth_method(
-                                  request,
-                                  form.cleaned_data['user_email'],
-                                  form.cleaned_data['user_password'],
-                                  )
-                
-                sign_user_in(request, user)     
-                remember_me_token = False
-                
-                if not request.firewall.admin and request.settings['remember_me_allow'] and form.cleaned_data['user_remember_me']:
-                    remember_me_token = get_random_string(42)
-                    remember_me = Token(
-                                        id=remember_me_token,
-                                        user=user,
-                                        created=timezone.now(),
-                                        accessed=timezone.now(),
-                                        )
-                    remember_me.save()
-                if remember_me_token:
-                    request.cookie_jar.set('TOKEN', remember_me_token, True)
-                request.messages.set_flash(Message(_("Welcome back, %(username)s!") % {'username': user.username}), 'success', 'security')
-                return redirect(success_redirect)
-            except AuthException as e:
-                message = Message(e.error, 'error')
-                bad_password = e.password
-                banned_account = e.ban
-                not_active = e.activation
-                
-                # If not in Admin, register failed attempt
-                if not request.firewall.admin and e.type == auth.CREDENTIALS:
-                    SignInAttempt.objects.register_attempt(request.session.get_ip(request))
-                    
-                    # Have we jammed our account?
-                    if SignInAttempt.objects.is_jammed(request.settings, request.session.get_ip(request)):
-                        request.jam.expires = timezone.now()
-                        return redirect(reverse('sign_in'))
-        else:
-            message = Message(form.non_field_errors()[0], 'error')
-    else:
-        form = SignInForm(
-                          show_remember_me=not request.firewall.admin and request.settings['remember_me_allow'],
-                          request=request
-                          )
-    return request.theme.render_to_response('signin.html',
-                                            {
-                                             'message': message,
-                                             'bad_password': bad_password,
-                                             'banned_account': banned_account,
-                                             'not_active': not_active,
-                                             'form': FormLayout(form),
-                                             'hide_signin': True, 
-                                             },
-                                            context_instance=RequestContext(request));
-
-
-@block_guest
-@check_csrf
-def signout(request):
-    user = request.user
-    request.session.sign_out(request)
-    request.messages.set_flash(Message(_("You have been signed out.")), 'info', 'security')
-    if request.firewall.admin:
-        return redirect(reverse(site.get_admin_index()))
-    return redirect(reverse('index'))
-    

+ 3 - 3
misago/sessions/sessions.py

@@ -5,10 +5,10 @@ from django.db.models.loading import cache as model_cache
 from django.utils import timezone
 from django.utils.crypto import salted_hmac
 from django.utils.encoding import force_unicode
-from misago.security import get_random_string
-from misago.security.auth import auth_remember, AuthException
-from misago.users.models import Guest, User
+from misago.auth.methods import auth_remember, AuthException
 from misago.sessions.models import *
+from misago.users.models import Guest, User
+from misago.utils import get_random_string
 
 # Assert models are loaded
 if not model_cache.loaded:

+ 6 - 5
misago/settings_base.py

@@ -55,7 +55,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
     'misago.messages.context_processors.messages',
     'misago.monitor.context_processors.monitor',
     'misago.settings.context_processors.settings',
-    'misago.security.context_processors.security',
+    'misago.bruteforce.context_processors.is_jammed',
     'misago.csrf.context_processors.csrf',
     'misago.users.context_processors.user',
 )
@@ -72,11 +72,11 @@ MIDDLEWARE_CLASSES = (
     'misago.settings.middleware.SettingsMiddleware',
     'misago.monitor.middleware.MonitorMiddleware',
     'misago.themes.middleware.ThemeMiddleware',
-    'misago.security.middleware.FirewallMiddleware',
+    'misago.firewalls.middleware.FirewallMiddleware',
     'misago.crawlers.middleware.DetectCrawlerMiddleware',
     'misago.sessions.middleware.SessionMiddleware',
-    'misago.security.middleware.JamMiddleware',
-    'misago.security.middleware.CSRFMiddleware',
+    'misago.bruteforce.middleware.JamMiddleware',
+    'misago.csrf.middleware.CSRFMiddleware',
     'misago.banning.middleware.BanningMiddleware',
     'misago.messages.middleware.MessagesMiddleware',
     'misago.users.middleware.UserMiddleware',
@@ -107,8 +107,9 @@ INSTALLED_APPS = (
     'misago.messages', # Messages and Flashes
     'misago.newsletters', # Send newsletters to members from Admin
     'misago.stats', # Admin statistics generator
-    'misago.security', # Security: CSRF, Firewall, etc ect
     'misago.sessions', # Sessions
+    'misago.auth', # User authentication
+    'misago.bruteforce', # Brute-Force protection
     'misago.csrf', # Cross Site Request Forgery protection
     'misago.setup', # Installation/update tool
     'misago.template', # Templates extensions

+ 1 - 1
misago/urls.py

@@ -5,7 +5,7 @@ from misago.admin import ADMIN_PATH, site
 
 # Include frontend patterns
 urlpatterns = patterns('',
-    (r'^', include('misago.security.urls')),
+    (r'^', include('misago.auth.urls')),
     (r'^users/', include('misago.profiles.urls')),
     (r'^usercp/', include('misago.usercp.urls')),
     (r'^register/', include('misago.register.urls')),

+ 1 - 1
misago/usercp/views.py

@@ -4,7 +4,7 @@ from django.template import RequestContext
 from django.utils.translation import ugettext as _
 from misago.forms import FormLayout
 from misago.messages import Message
-from misago.security.decorators import *
+from misago.auth.decorators import block_guest
 from misago.usercp.forms import UserForumOptionsForm
 
 

+ 1 - 2
misago/users/models.py

@@ -12,10 +12,9 @@ from django.template import RequestContext
 from django.utils import timezone as tz_util
 from django.utils.translation import ugettext_lazy as _
 from misago.monitor.monitor import Monitor
-from misago.security import get_random_string
 from misago.settings.settings import Settings as DBSettings
 from misago.users.validators import validate_username, validate_password, validate_email
-from misago.utils import slugify
+from misago.utils import get_random_string, slugify
 
 class UserManager(models.Manager):
     """

+ 1 - 1
misago/users/views.py

@@ -3,9 +3,9 @@ from django.db.models import Q
 from django.utils.translation import ugettext as _
 from misago.admin import site
 from misago.admin.widgets import *
-from misago.security import get_random_string
 from misago.users.forms import UserForm, NewUserForm, SearchUsersForm
 from misago.users.models import User
+from misago.utils import get_random_string
 
 def reverse(route, target=None):
     if target:

+ 9 - 0
misago/utils/__init__.py

@@ -32,6 +32,15 @@ def get_msgid(gettext):
 
 
 """
+Random string
+"""
+from django.utils import crypto
+    
+def get_random_string(length):
+    return crypto.get_random_string(length, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM")
+
+
+"""
 Date formats
 """               
 from django.utils.formats import get_format