From 5e29478516ba377a18e7df9a83c37b4e268926b8 Mon Sep 17 00:00:00 2001 From: Uwe Kamper Date: Sun, 27 Oct 2013 21:13:41 +0100 Subject: [PATCH 1/3] #11 passwords are now stored with aes-128 encryption --- account/cbase_members.py | 11 ++++-- account/forms.py | 3 +- account/password_encryption.py | 64 ++++++++++++++++++++++++++++++++++ account/tests.py | 25 +++++++++---- account/views.py | 17 ++++----- requirements.txt | 1 + 6 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 account/password_encryption.py diff --git a/account/cbase_members.py b/account/cbase_members.py index b3fe08b..2a51617 100644 --- a/account/cbase_members.py +++ b/account/cbase_members.py @@ -5,6 +5,7 @@ import ldap import copy from django.conf import settings +from password_encryption import get_ldap_password """ Example configuration: @@ -14,8 +15,11 @@ CBASE_BASE_DN = 'ou=crew,dc=c-base,dc=org' """ def retrieve_member(request): - # TODO: Put password in encrypted session storage - return MemberValues(request.user.username, request.session['ldap_password']) + ldap_password = get_ldap_password(request) + session = dict(request.session) + print "session:", session + print "cookies:", request.COOKIES + return MemberValues(request.user.username, ldap_password) class MemberValues(object): @@ -80,7 +84,8 @@ class MemberValues(object): print "modattrs: ",mod_attrs result = l.modify_s(dn, mod_attrs) - print "result is: ", result + # + # print "result is: ", result l.unbind_s() def change_password(self, new_password): diff --git a/account/forms.py b/account/forms.py index 84f4fc1..6f957eb 100644 --- a/account/forms.py +++ b/account/forms.py @@ -10,7 +10,8 @@ from django.utils.translation import ugettext as _ class LoginForm(forms.Form): username = forms.CharField(max_length=255) - password = forms.CharField(max_length=255, widget=forms.PasswordInput) + password = forms.CharField(max_length=255, widget=forms.PasswordInput, + help_text=_('Cookies must be enabled.')) def clean(self): username = self.cleaned_data.get('username') diff --git a/account/password_encryption.py b/account/password_encryption.py new file mode 100644 index 0000000..0c4138c --- /dev/null +++ b/account/password_encryption.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import base64 + +from Crypto import Random +from Crypto.Cipher import AES + +ENCRYPTED_LDAP_PASSWORD = 'encrypted_ldap_password' + +def encrypt_ldap_password(cleartext_pw): + """ + Encrypts the cleartext_pw with a randomly generated key. + + Returns the key and the encrypted message containing the password. + The key is supposed to be stored into the 'session_key' cookie field we can + later use it to decrypt the password and connect to the LDAP server with it. + """ + # 16 bytes of key => AES-128 + random = Random.new() + key = random.read(16) + + # initialization vector + iv = random.read(16) + + # do the encryption + aes = AES.new(key, AES.MODE_CFB, iv) + message = iv + aes.encrypt(cleartext_pw) + return base64.b64encode(message), base64.b64encode(key) + +def decrypt_ldap_password(message, key): + """ + Takes an encrypted, base64 encoded password and the base64 encoded key. + Returns the cleartext password. + """ + decoded_message = base64.b64decode(message) + decoded_key = base64.b64decode(key) + + # first 16 bytes of the message are the initialization vector + iv = decoded_message[:16] + + # the rest is the encrypted password + ciphertext = decoded_message[16:] + + # decrypt it + aes = AES.new(decoded_key, AES.MODE_CFB, iv) + cleartext_pw = aes.decrypt(ciphertext) + return cleartext_pw + +def store_ldap_password(request, password): + """ + Stores the password in an encrypted session storage and returns the key. + """ + encrypted_pw, key = encrypt_ldap_password(password) + request.session[ENCRYPTED_LDAP_PASSWORD] = encrypted_pw + request.session.save() + return key + +def get_ldap_password(request): + cookies = request.COOKIES + key = cookies.get('sessionkey', None) + if not key: + raise Exception('sessionkey not found in cookies.') + return decrypt_ldap_password(request.session[ENCRYPTED_LDAP_PASSWORD], key) \ No newline at end of file diff --git a/account/tests.py b/account/tests.py index 501deb7..03a89f4 100644 --- a/account/tests.py +++ b/account/tests.py @@ -6,11 +6,24 @@ Replace this with more appropriate tests for your application. """ from django.test import TestCase +from account.views import encrypt_ldap_password, decrypt_ldap_password + +class CbmiTest(TestCase): + """ + Test for the cbmi apps. + """ + TEST_LDAP_PASSWD = 'correcthorsebatterystaple' + + def encrypt_it(self): + return encrypt_ldap_password(self.TEST_LDAP_PASSWD) + + def test_encrypt_ldap_password(self): + message, key = self.encrypt_it() + print 'key:', key + print 'message:', message -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.assertEqual(1 + 1, 2) + def test_decrypt_ldap_password(self): + message, key = self.encrypt_it() + decrypted = decrypt_ldap_password(message, key) + self.assertEqual(self.TEST_LDAP_PASSWD, decrypted) \ No newline at end of file diff --git a/account/views.py b/account/views.py index e5b68b6..ca2f34b 100644 --- a/account/views.py +++ b/account/views.py @@ -5,6 +5,7 @@ import os import base64 import hashlib + from django.conf import settings from django.http import HttpResponseRedirect from django.shortcuts import render_to_response @@ -20,6 +21,7 @@ from django.utils.translation import ugettext as _ from forms import GastroPinForm, WlanPresenceForm, LoginForm, PasswordForm, \ RFIDForm, NRF24Form, SIPPinForm, CLabPinForm, AdminForm from cbase_members import retrieve_member +from password_encryption import * def landingpage(request): if request.user.is_authenticated(): @@ -36,8 +38,6 @@ def landingpage(request): except: admins = [] - # values = get_user_values(request.user.username, request.session['ldap_password']) - #return render_to_response("dashboard.html", locals()) return render(request, 'base.html', {'form': form, 'admins': admins}) def auth_login(request): @@ -57,11 +57,9 @@ def auth_login(request): member.save() # save password in the session for later use with LDAP - request.session['ldap_password'] = password - # TODO: Change the - + key = store_ldap_password(request, password) response = HttpResponseRedirect(redirect_to) - response.set_cookie('sessionkey', 'bla') + response.set_cookie('sessionkey', key) return response else: return render(request, 'login.html', {'form': form}) @@ -79,6 +77,7 @@ def home(request): @login_required def auth_logout(request): + request.session.pop(ENCRYPTED_LDAP_PASSWORD) redirect_to = request.GET.get('next', '') or '/' logout(request) response = HttpResponseRedirect(redirect_to) @@ -154,6 +153,7 @@ def clabpin(request): @login_required def password(request): """ + View that changes the password on the LDAP server. """ member = retrieve_member(request) @@ -163,12 +163,13 @@ def password(request): if form.is_valid(): new_password = form.cleaned_data['password1'] member.change_password(new_password) - request.session['ldap_password'] = new_password + key = store_ldap_password(request, new_password) request.session.save() new_form = PasswordForm() - return render(request, 'password.html', + response = render(request, 'password.html', {'message': _('Your password was changed. Thank you!'), 'form': new_form, 'member': member.to_dict()}) + response.set_cookie('sessionkey', key) else: return render(request, 'password.html', {'form': form, 'member': member.to_dict()}) diff --git a/requirements.txt b/requirements.txt index e5d0614..942ca60 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ MySQL-python==1.2.4 django-auth-ldap==1.1.4 django-json-rpc==0.6.1 django-crispy-forms==1.4.0 +pycrypto==2.6.1 From 2b0407eaf6c60e38d115a024efbd463554870f3f Mon Sep 17 00:00:00 2001 From: Uwe Kamper Date: Sun, 27 Oct 2013 21:14:30 +0100 Subject: [PATCH 2/3] changed testcases to use the functions from password_encryption module --- account/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/account/tests.py b/account/tests.py index 03a89f4..078a96a 100644 --- a/account/tests.py +++ b/account/tests.py @@ -6,9 +6,9 @@ Replace this with more appropriate tests for your application. """ from django.test import TestCase -from account.views import encrypt_ldap_password, decrypt_ldap_password +from password_encryption import encrypt_ldap_password, decrypt_ldap_password -class CbmiTest(TestCase): +class PasswordEncryptionTest(TestCase): """ Test for the cbmi apps. """ From e61e2bda91f47c515f635988fe34773b1ea7b029 Mon Sep 17 00:00:00 2001 From: Uwe Kamper Date: Tue, 29 Oct 2013 04:20:52 +0100 Subject: [PATCH 3/3] #14 Number of total active crew members is now on homepage after the login. --- account/cbase_members.py | 17 ++++++++++++++--- account/templates/home.html | 4 +++- account/views.py | 4 +++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/account/cbase_members.py b/account/cbase_members.py index 2a51617..23e5489 100644 --- a/account/cbase_members.py +++ b/account/cbase_members.py @@ -150,15 +150,26 @@ class MemberValues(object): l.passwd_s(self._get_bind_dn(username), None, new_password) l.unbind_s() + def get_number_of_members(self): + """ + Returns the total number of c-base members with active user accounts. + """ + return len(self.list_users()) + def list_users(self): + """ + Returns a list of strings with all usernames in the group 'crew'. + The list is sorted alphabetically. + """ l = ldap.initialize(settings.CBASE_LDAP_URL) user_dn = self._get_bind_dn() l.simple_bind_s(user_dn, self._password) try: - ldap_result_id = l.search(settings.CBASE_BASE_DN, ldap.SCOPE_SUBTREE, "memberOf=cn=crew,ou=groups,dc=c-base,dc=org", None) + result_id = l.search(settings.CBASE_BASE_DN, ldap.SCOPE_SUBTREE, + "memberOf=cn=crew,ou=groups,dc=c-base,dc=org", None) result_set = [] - while 1: - result_type, result_data = l.result(ldap_result_id, 0) + while True: + result_type, result_data = l.result(result_id, 0) if (result_data == []): break else: diff --git a/account/templates/home.html b/account/templates/home.html index f8f2aaa..1532f92 100644 --- a/account/templates/home.html +++ b/account/templates/home.html @@ -5,7 +5,9 @@

{% trans "Welcome to the c-base member interface" %}

-

{% blocktrans %}Here you can change +

You are one of currently {{ number_of_members }} + c-base members.

+

{% blocktrans %}Here you can change some parameters of your c-base member account.{% endblocktrans %}

{% trans "Basic information about your account" %}

diff --git a/account/views.py b/account/views.py index ca2f34b..2f02fc6 100644 --- a/account/views.py +++ b/account/views.py @@ -72,7 +72,9 @@ def auth_login(request): @login_required def home(request): member = retrieve_member(request) - context = {'member': member.to_dict(), 'groups': request.user.groups.all()} + number_of_members = member.get_number_of_members() + context = {'member': member.to_dict(), 'groups': request.user.groups.all(), + 'number_of_members': number_of_members} return render(request, 'home.html', context) @login_required