Fixes IndexError on save with removed member forwarding email (#11)

* Fixed IndexError if a non existent field gets queried for a value. Using dict.get to either get a value or provide a reasonable default
* Removed list construction around new_key value
* Clean up source code with 80x25 terminal and vim (complies with PEP-8)
* Added some docstrings
This commit is contained in:
Matthias 2020-12-05 23:23:19 +01:00 committed by GitHub
parent 4f093c0899
commit 09af25761c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 409 additions and 194 deletions

View file

@ -1,12 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import ldap
import copy
from django.conf import settings
from account.password_encryption import get_ldap_password
""" """
Example configuration: Example configuration:
@ -14,12 +8,30 @@ CBASE_LDAP_URL = 'ldap://lea.cbrp3.c-base.org:389/'
CBASE_BASE_DN = 'ou=crew,dc=c-base,dc=org' CBASE_BASE_DN = 'ou=crew,dc=c-base,dc=org'
""" """
import copy
import logging
from django.conf import settings
import ldap
from account.password_encryption import get_ldap_password
LOGGER = logging.getLogger(__name__)
def retrieve_member(request): def retrieve_member(request):
"""
Gets a MemberValues object by its user name bound to a request object.
:param request:
:return: A MemberValues object
"""
ldap_password = get_ldap_password(request) ldap_password = get_ldap_password(request)
session = dict(request.session) request_session = dict(request.session)
print("session:", session)
print("cookies:", request.COOKIES) LOGGER.info("session: %s", request_session)
LOGGER.info("cookies: %s", request.COOKIES)
return MemberValues(request.user.username, ldap_password) return MemberValues(request.user.username, ldap_password)
@ -29,6 +41,13 @@ class MemberValues(object):
""" """
def __init__(self, username, password): def __init__(self, username, password):
"""
Initializes a MemberValues object with all necessary parameters to
synchronize with an LDAP server.
:param username:
:param password:
"""
self._username = username self._username = username
self._password = password self._password = password
self._old = self._get_user_values() self._old = self._get_user_values()
@ -38,6 +57,12 @@ class MemberValues(object):
self._new = copy.deepcopy(self._old) self._new = copy.deepcopy(self._old)
def get(self, key, default=None): def get(self, key, default=None):
"""
Gets a member attribute value by key.
:param key:
:param default:
:return:
"""
value_list = self._new.get(key, default) value_list = self._new.get(key, default)
if value_list: if value_list:
value = value_list[0] value = value_list[0]
@ -56,13 +81,19 @@ class MemberValues(object):
return value return value
def set(self, key, value): def set(self, key, value):
if value == None: """
Sets a member attribute value for a key replacing the old value
:param key:
:param value:
:return:
"""
if value is None:
self._new[key] = [None] self._new[key] = [None]
return return
converted_value = value converted_value = value
if isinstance(value, bool): if isinstance(value, bool):
if value == True: if value:
converted_value = 'TRUE' converted_value = 'TRUE'
else: else:
converted_value = 'FALSE' converted_value = 'FALSE'
@ -73,12 +104,13 @@ class MemberValues(object):
""" """
Save the values back to the LDAP server. Save the values back to the LDAP server.
""" """
dn = "uid=%s,ou=crew,dc=c-base,dc=org" % self._username user_dn = "uid=%s,ou=crew,dc=c-base,dc=org" % self._username
l = ldap.initialize(settings.CBASE_LDAP_URL) session = ldap.initialize(settings.CBASE_LDAP_URL)
l.simple_bind_s(dn, self._password) session.simple_bind_s(user_dn, self._password)
mod_attrs = [] mod_attrs = []
action = None
for new_key, new_value in self._new.items(): for new_key, new_value in self._new.items():
# Replace is the default. # Replace is the default.
action = ldap.MOD_REPLACE action = ldap.MOD_REPLACE
@ -86,36 +118,41 @@ class MemberValues(object):
action = ldap.MOD_ADD action = ldap.MOD_ADD
mod_attrs.append((action, '%s' % new_key, new_value)) mod_attrs.append((action, '%s' % new_key, new_value))
continue continue
if self._old[new_key][0] != None and new_value == [None]: if self._old.get(new_key, [None])[0] is not None \
and new_value == [None]:
action = ldap.MOD_DELETE action = ldap.MOD_DELETE
mod_attrs.append((action, '%s' % new_key, [])) mod_attrs.append((action, '%s' % new_key, []))
# Set the attribute and wait for the LDAP server to complete.
continue continue
# Set the attribute and wait for the LDAP server to complete. if self._old.get(new_key, [None])[0] != new_value[0]:
if self._old[new_key][0] != new_value[0]:
action = ldap.MOD_REPLACE action = ldap.MOD_REPLACE
mod_attrs.append((action, '%s' % new_key, new_value)) mod_attrs.append((action, '%s' % new_key, new_value))
continue continue
print("modattrs: ", mod_attrs) LOGGER.debug("action: %s modattrs: %s", action, mod_attrs)
result = l.modify_s(dn, mod_attrs) result = session.modify_s(user_dn, mod_attrs)
# LOGGER.debug("result is: %s", result)
# print("result is: ", result) session.unbind_s()
l.unbind_s() return result # does not harm any1
def change_password(self, new_password): def change_password(self, new_password):
""" """
Change the password of the member. Change the password of the member.
You do not need to call save() after calling change_password(). You do not need to call save() after calling change_password().
""" """
l = ldap.initialize(settings.CBASE_LDAP_URL) session = ldap.initialize(settings.CBASE_LDAP_URL)
user_dn = self._get_bind_dn() user_dn = self._get_bind_dn()
l.simple_bind_s(user_dn, self._password) session.simple_bind_s(user_dn, self._password)
l.passwd_s(user_dn, self._password, new_password) session.passwd_s(user_dn, self._password, new_password)
l.unbind_s() session.unbind_s()
def to_dict(self): def to_dict(self):
"""
Converts a MembersValue object to a dict representation.
:return:
"""
result = {} result = {}
for key, value in self._new.items(): for key, _ in self._new.items():
result[key] = self.get(key) result[key] = self.get(key)
return result return result
@ -139,17 +176,22 @@ class MemberValues(object):
session.simple_bind_s(self._get_bind_dn(), self._password) session.simple_bind_s(self._get_bind_dn(), self._password)
# Set the attribute and wait for the LDAP server to complete. # Set the attribute and wait for the LDAP server to complete.
searchScope = ldap.SCOPE_SUBTREE search_scope = ldap.SCOPE_SUBTREE
# retrieve all attributes # retrieve all attributes
retrieveAttributes = None retrieve_attributes = None
searchFilter = "uid=%s" % self._username search_filter = "uid=%s" % self._username
dn = settings.CBASE_BASE_DN base_dn = settings.CBASE_BASE_DN
result = session.search_s( result = session.search_s(
dn, searchScope, searchFilter, retrieveAttributes) base_dn,
search_scope,
search_filter,
retrieve_attributes
)
# TODO: latin1 # TODO: latin1
print("result is: ", result) LOGGER.info("result is: %s", result)
# TODO: if len(result)==0 # TODO: if len(result)==0
session.unbind_s() session.unbind_s()
return result[0][1] return result[0][1]
@ -159,11 +201,11 @@ class MemberValues(object):
Change the password of the member. Change the password of the member.
You do not need to call save() after calling change_password(). You do not need to call save() after calling change_password().
""" """
l = ldap.initialize(settings.CBASE_LDAP_URL) session = ldap.initialize(settings.CBASE_LDAP_URL)
user_dn = self._get_bind_dn() user_dn = self._get_bind_dn()
l.simple_bind_s(user_dn, self._password) session.simple_bind_s(user_dn, self._password)
l.passwd_s(self._get_bind_dn(username), None, new_password) session.passwd_s(self._get_bind_dn(username), None, new_password)
l.unbind_s() session.unbind_s()
def get_number_of_members(self): def get_number_of_members(self):
""" """
@ -176,22 +218,26 @@ class MemberValues(object):
Returns a list of strings with all usernames in the group 'crew'. Returns a list of strings with all usernames in the group 'crew'.
The list is sorted alphabetically. The list is sorted alphabetically.
""" """
l = ldap.initialize(settings.CBASE_LDAP_URL) session = ldap.initialize(settings.CBASE_LDAP_URL)
user_dn = self._get_bind_dn() user_dn = self._get_bind_dn()
l.simple_bind_s(user_dn, self._password) session.simple_bind_s(user_dn, self._password)
try: try:
result_id = l.search(settings.CBASE_BASE_DN, ldap.SCOPE_SUBTREE, result_id = session.search(
"memberOf=cn=crew,ou=groups,dc=c-base,dc=org", None) settings.CBASE_BASE_DN,
ldap.SCOPE_SUBTREE,
"memberOf=cn=crew,ou=groups,dc=c-base,dc=org",
None
)
result_set = [] result_set = []
while True: while True:
result_type, result_data = l.result(result_id, 0) result_type, result_data = session.result(result_id, 0)
if (result_data == []): if not result_data:
break break
else: if result_type == ldap.RES_SEARCH_ENTRY:
if result_type == ldap.RES_SEARCH_ENTRY: result_set.append(result_data)
result_set.append(result_data)
# list comprehension to get a list of user tupels in the format ("nickname", "nickname (real name)") # list comprehension to get a list of user tupels in the
# format ("nickname", "nickname (real name)")
userlist = [( userlist = [(
x[0][1]['uid'][0].decode(), x[0][1]['uid'][0].decode(),
'%s (%s, %s)' % ( '%s (%s, %s)' % (
@ -202,4 +248,5 @@ class MemberValues(object):
) for x in result_set] ) for x in result_set]
return sorted(userlist) return sorted(userlist)
except Exception: except Exception:
LOGGER.exception('list_users failed')
return [] return []

View file

@ -7,13 +7,15 @@ from django import forms
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
class UsernameField(forms.CharField): class UsernameField(forms.CharField):
""" """
The username field makes sure that usernames are always entered in lower-case. The username field makes sure that usernames are always entered in
If we do not convert the username to lower-case, Django will create more than lower-case. If we do not convert the username to lower-case, Django will
one user object in the database. If we then try to login again, the Django auth create more than one user object in the database. If we then try to login
subsystem will do an query that looks like this: username__iexact="username". The again, the Django auth subsystem will do an query that looks like this:
result is an error, because iexact returns the objects for "username" and "Username". username__iexact="username". The result is an error, because iexact returns
the objects for "username" and "Username".
""" """
def to_python(self, value): def to_python(self, value):
@ -24,7 +26,8 @@ class UsernameField(forms.CharField):
class LoginForm(forms.Form): class LoginForm(forms.Form):
username = UsernameField(max_length=255) username = UsernameField(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.')) help_text=_('Cookies must be enabled.'))
def clean(self): def clean(self):
@ -32,8 +35,13 @@ class LoginForm(forms.Form):
password = self.cleaned_data.get('password') password = self.cleaned_data.get('password')
user = authenticate(username=username, password=password) user = authenticate(username=username, password=password)
if not user or not user.is_active: if not user or not user.is_active:
raise forms.ValidationError(_('Sorry, that login was invalid. ' raise forms.ValidationError(
'Please try again.'), code='invalid_login') _(
'Sorry, that login was invalid. '
'Please try again.'
),
code='invalid_login'
)
return self.cleaned_data return self.cleaned_data
def login(self, request): def login(self, request):
@ -45,6 +53,7 @@ class LoginForm(forms.Form):
class GastroPinField(forms.CharField): class GastroPinField(forms.CharField):
widget = forms.PasswordInput widget = forms.PasswordInput
def validate(self, value): def validate(self, value):
""" """
Check if the value is all numeric and 4 - 8 chars long. Check if the value is all numeric and 4 - 8 chars long.
@ -56,7 +65,8 @@ class GastroPinField(forms.CharField):
class GastroPinForm(forms.Form): class GastroPinForm(forms.Form):
gastropin1 = GastroPinField(label=_('New Gastro-PIN')) gastropin1 = GastroPinField(label=_('New Gastro-PIN'))
gastropin2 = GastroPinField(label=_('Repeat Gastro-PIN'), gastropin2 = GastroPinField(
label=_('Repeat Gastro-PIN'),
help_text=_('Numerical only, 4 to 8 digits')) help_text=_('Numerical only, 4 to 8 digits'))
def clean(self): def clean(self):
@ -67,24 +77,33 @@ class GastroPinForm(forms.Form):
if gastropin1 != gastropin2: if gastropin1 != gastropin2:
raise forms.ValidationError( raise forms.ValidationError(
_('The PINs entered were not identical.'), _('The PINs entered were not identical.'),
code='not_identical') code='not_identical'
)
return cleaned_data return cleaned_data
class WlanPresenceForm(forms.Form): class WlanPresenceForm(forms.Form):
# Boolean fields must never be required. # Boolean fields must never be required.
presence = forms.BooleanField(required=False, presence = forms.BooleanField(
label=_('Enable WiFi presence')) required=False,
label=_('Enable WiFi presence')
)
class PasswordForm(forms.Form): class PasswordForm(forms.Form):
old_password = forms.CharField(max_length=255, widget=forms.PasswordInput, old_password = forms.CharField(
max_length=255, widget=forms.PasswordInput,
label=_('Old password'), label=_('Old password'),
help_text=_('Enter your current password here.')) help_text=_(
password1 = forms.CharField(max_length=255, widget=forms.PasswordInput, 'Enter your current password here.'))
label=_('New password')) password1 = forms.CharField(
password2 = forms.CharField(max_length=255, widget=forms.PasswordInput, max_length=255, widget=forms.PasswordInput,
label=_('Repeat password')) label=_('New password')
)
password2 = forms.CharField(
max_length=255, widget=forms.PasswordInput,
label=_('Repeat password')
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self._request = kwargs.pop('request', None) self._request = kwargs.pop('request', None)
@ -97,8 +116,12 @@ class PasswordForm(forms.Form):
user = authenticate(username=username, password=old_password) user = authenticate(username=username, password=old_password)
if not user or not user.is_active: if not user or not user.is_active:
raise forms.ValidationError(_('The old password was incorrect.'), raise forms.ValidationError(
code='old_password_wrong') _(
'The old password was incorrect.'
),
code='old_password_wrong'
)
password1 = cleaned_data.get('password1') password1 = cleaned_data.get('password1')
password2 = cleaned_data.get('password2') password2 = cleaned_data.get('password2')
@ -115,9 +138,14 @@ class PasswordForm(forms.Form):
class RFIDForm(forms.Form): class RFIDForm(forms.Form):
rfid = forms.CharField(max_length=255, label=_('Your RFID'), rfid = forms.CharField(
help_text=_('Find out your RFID by holding your RFID tag to the ' max_length=255,
'reader in the airlock.')) label=_('Your RFID'),
help_text=_(
'Find out your RFID by holding your RFID tag to the '
'reader in the airlock.'
)
)
class SIPPinForm(forms.Form): class SIPPinForm(forms.Form):
@ -135,30 +163,44 @@ class SIPPinForm(forms.Form):
class NRF24Form(forms.Form): class NRF24Form(forms.Form):
nrf24 = forms.CharField(max_length=255, nrf24 = forms.CharField(
label = _('NRF24-ID'), max_length=255,
help_text=_("Your r0ket's NRF24 identification")) label=_('NRF24-ID'),
help_text=_("Your r0ket's NRF24 identification")
)
class CLabPinForm(forms.Form): class CLabPinForm(forms.Form):
c_lab_pin1 = GastroPinField(label=_('New indoor PIN')) c_lab_pin1 = GastroPinField(label=_('New indoor PIN'))
c_lab_pin2 = GastroPinField(label=_('Repeat indoor PIN'), c_lab_pin2 = GastroPinField(
help_text=_('Numerical only, 4 to 8 digits')) label=_('Repeat indoor PIN'),
help_text=_('Numerical only, 4 to 8 digits')
)
class PreferredEmailForm(forms.Form): class PreferredEmailForm(forms.Form):
preferred_email = forms.EmailField(max_length=255, required=False, preferred_email = forms.EmailField(
label = _('Preferred e-mail'), max_length=255, required=False,
help_text=_("Forward my mail to this address. Leave empty to use the c-base IMAP and SMTP servers.")) label=_('Preferred e-mail'),
help_text=_(
"Forward my mail to this address. Leave empty to use the c-base "
"IMAP and SMTP servers."
)
)
class AdminForm(forms.Form): class AdminForm(forms.Form):
username = forms.ChoiceField(choices=[]) username = forms.ChoiceField(choices=[])
password1 = forms.CharField(max_length=255, widget=forms.PasswordInput, password1 = forms.CharField(
label=_('New password')) max_length=255,
password2 = forms.CharField(max_length=255, widget=forms.PasswordInput, widget=forms.PasswordInput,
label=_('Repeat password')) label=_('New password')
)
password2 = forms.CharField(
max_length=255,
widget=forms.PasswordInput,
label=_('Repeat password')
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self._request = kwargs.pop('request', None) self._request = kwargs.pop('request', None)
@ -166,10 +208,23 @@ class AdminForm(forms.Form):
choices = [x for x in self._users] choices = [x for x in self._users]
choices.insert(0, ('', 'Select username ...')) choices.insert(0, ('', 'Select username ...'))
super(AdminForm, self).__init__(*args, **kwargs) super(AdminForm, self).__init__(*args, **kwargs)
#self.fields.insert(0, 'username', forms.ChoiceField(choices=choices, # self.fields.insert(
#help_text=_('Select the username for whom you want to reset the password.'))) # 0,
self.fields['username'] = forms.ChoiceField(choices=choices, # 'username',
help_text=_('Select the username for whom you want to reset the password.')) # forms.ChoiceField(
# choices=choices,
# help_text=_(
# 'Select the username for whom you want '
# 'to reset the password.'
# )
# )
# )
self.fields['username'] = forms.ChoiceField(
choices=choices,
help_text=_(
'Select the username for whom you want to reset the password.'
)
)
def clean(self): def clean(self):
cleaned_data = super(AdminForm, self).clean() cleaned_data = super(AdminForm, self).clean()
@ -179,11 +234,13 @@ class AdminForm(forms.Form):
if password1 != password2: if password1 != password2:
raise forms.ValidationError( raise forms.ValidationError(
_('The new passwords were not identical.'), _('The new passwords were not identical.'),
code='not_identical') code='not_identical'
)
if len(password1) < 6: if len(password1) < 6:
raise forms.ValidationError( raise forms.ValidationError(
_('Password must be at least 6 characters long'), _('Password must be at least 6 characters long'),
code='to_short') code='to_short'
)
return cleaned_data return cleaned_data

View file

@ -1,43 +1,73 @@
from django.db import models #!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Django models
"""
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models
from django.db.models import signals from django.db.models import signals
from account.signals import create_profile, delete_profile from account.signals import create_profile, delete_profile
class UserProfile(models.Model): class UserProfile(models.Model):
user = models.OneToOneField(User, editable=False, on_delete=models.CASCADE) """
uid = models.CharField(verbose_name="User-ID", UserProfile to be attached to a django User model.
max_length=8, """
null=True, user = models.OneToOneField(
default=None) User,
sippin = models.CharField(verbose_name="SIP PIN", editable=False,
max_length=255, on_delete=models.CASCADE
null=True, )
blank=True, uid = models.CharField(
default=None) verbose_name="User-ID",
gastropin = models.CharField(verbose_name="Gastro PIN", max_length=8,
max_length=255, null=True,
null=True, default=None
blank=True, )
default=None) sippin = models.CharField(
rfid = models.CharField(verbose_name="RFID", verbose_name="SIP PIN",
max_length=255, max_length=255,
null=True, null=True,
blank=True, blank=True,
default=None) default=None
macaddress = models.CharField(verbose_name="MAC-Address", )
max_length=255, gastropin = models.CharField(
null=True, verbose_name="Gastro PIN",
blank=True, max_length=255,
default=None) null=True,
clabpin = models.CharField(verbose_name="c-lab PIN", blank=True,
max_length=255, default=None
null=True, )
blank=True, rfid = models.CharField(
default=None) verbose_name="RFID",
preferred_email = models.CharField(verbose_name="preferred e-mail address", max_length=255,
max_length=1024, null=True,
null=True, blank=True,
default=None) default=None
)
macaddress = models.CharField(
verbose_name="MAC-Address",
max_length=255,
null=True,
blank=True,
default=None
)
clabpin = models.CharField(
verbose_name="c-lab PIN",
max_length=255,
null=True,
blank=True,
default=None
)
preferred_email = models.CharField(
verbose_name="preferred e-mail address",
max_length=1024,
null=True,
default=None
)
is_member = models.BooleanField(default=False, editable=False) is_member = models.BooleanField(default=False, editable=False)
is_ldap_admin = models.BooleanField(default=False, editable=False) is_ldap_admin = models.BooleanField(default=False, editable=False)
is_circle_member = models.BooleanField(default=False, editable=False) is_circle_member = models.BooleanField(default=False, editable=False)
@ -49,6 +79,7 @@ class UserProfile(models.Model):
def __unicode__(self): def __unicode__(self):
return 'Profile: %s' % self.user.username return 'Profile: %s' % self.user.username
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0]) User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
signals.post_save.connect(create_profile, sender=User) signals.post_save.connect(create_profile, sender=User)
signals.pre_delete.connect(delete_profile, sender=User) signals.pre_delete.connect(delete_profile, sender=User)

View file

@ -8,6 +8,7 @@ from Crypto.Cipher import AES
ENCRYPTED_LDAP_PASSWORD = 'encrypted_ldap_password' ENCRYPTED_LDAP_PASSWORD = 'encrypted_ldap_password'
def encrypt_ldap_password(cleartext_pw): def encrypt_ldap_password(cleartext_pw):
""" """
Encrypts the cleartext_pw with a randomly generated key. Encrypts the cleartext_pw with a randomly generated key.
@ -28,6 +29,7 @@ def encrypt_ldap_password(cleartext_pw):
message = iv + aes.encrypt(cleartext_pw) message = iv + aes.encrypt(cleartext_pw)
return base64.b64encode(message).decode(), base64.b64encode(key).decode() return base64.b64encode(message).decode(), base64.b64encode(key).decode()
def decrypt_ldap_password(message, key): def decrypt_ldap_password(message, key):
""" """
Takes an encrypted, base64 encoded password and the base64 encoded key. Takes an encrypted, base64 encoded password and the base64 encoded key.
@ -47,6 +49,7 @@ def decrypt_ldap_password(message, key):
cleartext_pw = aes.decrypt(ciphertext) cleartext_pw = aes.decrypt(ciphertext)
return cleartext_pw return cleartext_pw
def store_ldap_password(request, password): def store_ldap_password(request, password):
""" """
Stores the password in an encrypted session storage and returns the key. Stores the password in an encrypted session storage and returns the key.
@ -56,6 +59,7 @@ def store_ldap_password(request, password):
request.session.save() request.session.save()
return key return key
def get_ldap_password(request): def get_ldap_password(request):
cookies = request.COOKIES cookies = request.COOKIES
key = cookies.get('sessionkey', None) key = cookies.get('sessionkey', None)

View file

@ -4,6 +4,7 @@ def create_profile(sender, instance, signal, created, **kwargs):
if created: if created:
UserProfile(user=instance).save() UserProfile(user=instance).save()
def delete_profile(sender, instance, signal, **kwargs): def delete_profile(sender, instance, signal, **kwargs):
from account.models import UserProfile from account.models import UserProfile
UserProfile(user=instance).delete() UserProfile(user=instance).delete()

View file

@ -6,7 +6,9 @@ Replace this with more appropriate tests for your application.
""" """
from django.test import TestCase from django.test import TestCase
from password_encryption import encrypt_ldap_password, decrypt_ldap_password from account.password_encryption import encrypt_ldap_password, \
decrypt_ldap_password
class PasswordEncryptionTest(TestCase): class PasswordEncryptionTest(TestCase):
""" """
@ -22,7 +24,6 @@ class PasswordEncryptionTest(TestCase):
print('key:', key) print('key:', key)
print('message:', message) print('message:', message)
def test_decrypt_ldap_password(self): def test_decrypt_ldap_password(self):
message, key = self.encrypt_it() message, key = self.encrypt_it()
decrypted = decrypt_ldap_password(message, key) decrypted = decrypt_ldap_password(message, key)

View file

@ -1,29 +1,30 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os
import hashlib
import smbpasswd
import requests
import collections import collections
import hashlib
import os
import requests
from django.conf import settings from django.conf import settings
from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth import login, logout
from django.shortcuts import render_to_response
from django.template.context import RequestContext
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.auth.models import User
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.shortcuts import render from django.shortcuts import render
from django.shortcuts import render_to_response
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from account.forms import GastroPinForm, WlanPresenceForm, LoginForm, PasswordForm, \ import smbpasswd
RFIDForm, NRF24Form, SIPPinForm, CLabPinForm, AdminForm, PreferredEmailForm
from account.cbase_members import retrieve_member, MemberValues from account.cbase_members import retrieve_member, MemberValues
from account.forms import GastroPinForm, WlanPresenceForm, LoginForm, \
PasswordForm, RFIDForm, NRF24Form, SIPPinForm, CLabPinForm, AdminForm, \
PreferredEmailForm
from account.password_encryption import * from account.password_encryption import *
def landingpage(request): def landingpage(request):
if request.user.is_authenticated: if request.user.is_authenticated:
return HttpResponseRedirect('/account') return HttpResponseRedirect('/account')
@ -31,7 +32,7 @@ def landingpage(request):
try: try:
# just in case the group hasn't yet been synced # just in case the group hasn't yet been synced
admins = Group.objects.get(name="ldap_admins").user_set.all() admins = Group.objects.get(name="ldap_admins").user_set.all()
except: except Exception:
# else provide an emtpy list # else provide an emtpy list
admins = [] admins = []
@ -42,7 +43,7 @@ def landingpage(request):
try: try:
user = User.objects.get(username=check_nickname) user = User.objects.get(username=check_nickname)
check_nickname = True check_nickname = True
except: except Exception:
check_nickname = False check_nickname = False
# output as text if requested # output as text if requested
@ -51,6 +52,7 @@ def landingpage(request):
return render(request, 'base.html', locals()) return render(request, 'base.html', locals())
def auth_login(request): def auth_login(request):
redirect_to = request.GET.get('next', '') or '/' redirect_to = request.GET.get('next', '') or '/'
if request.method == 'POST': if request.method == 'POST':
@ -76,6 +78,7 @@ def auth_login(request):
form = LoginForm() form = LoginForm()
return render(request, 'login.html', {'form': form}) return render(request, 'login.html', {'form': form})
@login_required @login_required
def home(request): def home(request):
member = retrieve_member(request) member = retrieve_member(request)
@ -85,17 +88,23 @@ def home(request):
url = "https://vorstand.c-base.org/cteward-api/legacy/member/%s" % username url = "https://vorstand.c-base.org/cteward-api/legacy/member/%s" % username
cteward = None cteward = None
try: try:
r = requests.get(url, verify=False, auth=(username, password)) r = requests.get(
url,
verify=False,
auth=(username, password)
)
cteward = r.json() cteward = r.json()
except Exception: except Exception:
pass pass
context = {'member': member.to_dict(), context = {
'member': member.to_dict(),
'groups': list(request.user.groups.all().order_by('name')), 'groups': list(request.user.groups.all().order_by('name')),
'number_of_members': number_of_members, 'number_of_members': number_of_members,
'cteward': cteward, 'cteward': cteward,
} }
return render(request, 'home.html', context) return render(request, 'home.html', context)
@login_required @login_required
def auth_logout(request): def auth_logout(request):
request.session.pop(ENCRYPTED_LDAP_PASSWORD) request.session.pop(ENCRYPTED_LDAP_PASSWORD)
@ -105,7 +114,8 @@ def auth_logout(request):
response.delete_cookie('sessionkey') response.delete_cookie('sessionkey')
return response return response
@login_required(redirect_field_name="/" ,login_url="/account/login/")
@login_required(redirect_field_name="/", login_url="/account/login/")
def groups_list(request, group_name): def groups_list(request, group_name):
group = get_object_or_404(Group, name=group_name) group = get_object_or_404(Group, name=group_name)
groups = Group.objects.all() groups = Group.objects.all()
@ -115,13 +125,19 @@ def groups_list(request, group_name):
is_admin = True is_admin = True
return render_to_response("group_list.html", locals()) return render_to_response("group_list.html", locals())
@login_required @login_required
def sippin(request): def sippin(request):
return set_ldap_field(request, SIPPinForm, [('sippin', 'sippin')], return set_ldap_field(
'sippin.html') request,
SIPPinForm,
[('sippin', 'sippin')],
'sippin.html'
)
def set_hash_field(request, form_type, in_field, out_field, hash_func, def set_hash_field(request, form_type, in_field, out_field, hash_func,
template_name): template_name):
""" """
Abstract view for changing LDAP attributes that need to be hashed. Abstract view for changing LDAP attributes that need to be hashed.
Takes a function that converts the value into the hashed_value. Takes a function that converts the value into the hashed_value.
@ -136,16 +152,35 @@ def set_hash_field(request, form_type, in_field, out_field, hash_func,
member.set(out_field, hashed_value) member.set(out_field, hashed_value)
member.save() member.save()
new_form = form_type(initial=initial) new_form = form_type(initial=initial)
return render(request, template_name, return render(
{'message': _('Your changes have been saved. Thank you!'), request,
'form': new_form, 'member': member.to_dict()}) template_name,
{
'message': _('Your changes have been saved. Thank you!'),
'form': new_form,
'member': member.to_dict()
}
)
else: else:
return render(request, template_name, return render(
{'form': form, 'member': member.to_dict()}) request,
template_name,
{
'form': form,
'member': member.to_dict()
}
)
else: else:
form = form_type(initial=initial) form = form_type(initial=initial)
return render(request, template_name, return render(
{'form': form, 'member': member.to_dict()}) request,
template_name,
{
'form': form,
'member': member.to_dict()
}
)
@login_required @login_required
def gastropin(request): def gastropin(request):
@ -154,22 +189,31 @@ def gastropin(request):
bla = '%s%s' % (key, pin) bla = '%s%s' % (key, pin)
return hashlib.sha256(bla.encode()).hexdigest() return hashlib.sha256(bla.encode()).hexdigest()
return set_hash_field(request, GastroPinForm, return set_hash_field(
'gastropin1', 'gastroPIN', calculate_gastro_hash, 'gastropin.html') request,
GastroPinForm,
'gastropin1',
'gastroPIN',
calculate_gastro_hash,
'gastropin.html'
)
@login_required @login_required
def clabpin(request): def clabpin(request):
# if len(request.user.groups.filter(name__in=['cey-c-lab', 'cey-schleuse', 'cey-soundlab'])) < 1: # names = ['cey-c-lab', 'cey-schleuse', 'cey-soundlab']
# return render(request, 'access_denied.html') # if len(request.user.groups.filter(name__in=names)) < 1:
# return render(request, 'access_denied.html')
def calculate_clab_hash(pin): def calculate_clab_hash(pin):
salt = os.urandom(12) salt = os.urandom(12)
digest = hashlib.sha1(bytearray(pin, 'UTF-8')+salt).digest() digest = hashlib.sha1(bytearray(pin, 'UTF-8') + salt).digest()
pin_hash = '{SSHA}%s' % base64.b64encode(digest + salt).decode() pin_hash = '{SSHA}%s' % base64.b64encode(digest + salt).decode()
return pin_hash return pin_hash
return set_hash_field(request, CLabPinForm, 'c_lab_pin1', 'c-labPIN', return set_hash_field(request, CLabPinForm, 'c_lab_pin1', 'c-labPIN',
calculate_clab_hash, 'clabpin.html') calculate_clab_hash, 'clabpin.html')
@login_required @login_required
def password(request): def password(request):
@ -196,17 +240,19 @@ def password(request):
request.session.save() request.session.save()
new_form = PasswordForm() new_form = PasswordForm()
response = render(request, 'password.html', response = render(request, 'password.html',
{'message': _('Your password was changed. Thank you!'), {'message': _(
'form': new_form, 'member': member.to_dict()}) 'Your password was changed. Thank you!'),
'form': new_form, 'member': member.to_dict()})
response.set_cookie('sessionkey', key) response.set_cookie('sessionkey', key)
return response return response
else: else:
return render(request, 'password.html', return render(request, 'password.html',
{'form': form, 'member': member.to_dict()}) {'form': form, 'member': member.to_dict()})
else: else:
form = PasswordForm() form = PasswordForm()
return render(request, 'password.html', return render(request, 'password.html',
{'form': form, 'member': member.to_dict()}) {'form': form, 'member': member.to_dict()})
def set_ldap_field(request, form_type, field_names, template_name): def set_ldap_field(request, form_type, field_names, template_name):
""" """
@ -230,36 +276,44 @@ def set_ldap_field(request, form_type, field_names, template_name):
member.save() member.save()
new_form = form_type(initial=initial) new_form = form_type(initial=initial)
return render(request, template_name, return render(request, template_name,
{'message': _('Your changes have been saved. Thank you!'), {'message': _(
'form': new_form, 'member': member.to_dict()}) 'Your changes have been saved. Thank you!'),
'form': new_form, 'member': member.to_dict()})
else: else:
return render(request, template_name, return render(request, template_name,
{'form': form, 'member': member.to_dict()}) {'form': form, 'member': member.to_dict()})
else: else:
for form_field, ldap_field in field_names: for form_field, ldap_field in field_names:
initial[form_field] = member.get(ldap_field) initial[form_field] = member.get(ldap_field)
form = form_type(initial=initial) form = form_type(initial=initial)
return render(request, template_name, return render(request, template_name,
{'form': form, 'member': member.to_dict()}) {'form': form, 'member': member.to_dict()})
@login_required @login_required
def wlan_presence(request): def wlan_presence(request):
return set_ldap_field(request, WlanPresenceForm, return set_ldap_field(request, WlanPresenceForm,
[('presence', 'wlanPresence')], 'wlan_presence.html') [('presence', 'wlanPresence')], 'wlan_presence.html')
@login_required @login_required
def rfid(request): def rfid(request):
return set_ldap_field(request, RFIDForm, [('rfid', 'rfid')], 'rfid.html') return set_ldap_field(request, RFIDForm, [('rfid', 'rfid')], 'rfid.html')
@login_required @login_required
def nrf24(request): def nrf24(request):
return set_ldap_field(request, NRF24Form, [('nrf24', 'nrf24')], 'nrf24.html') return set_ldap_field(request, NRF24Form, [('nrf24', 'nrf24')],
'nrf24.html')
@login_required @login_required
def preferred_email(request): def preferred_email(request):
return set_ldap_field(request, PreferredEmailForm, [('preferred_email', 'preferredEmail')], return set_ldap_field(request, PreferredEmailForm,
[('preferred_email', 'preferredEmail')],
'preferred_email.html') 'preferred_email.html')
@login_required @login_required
def admin(request): def admin(request):
admin_member = retrieve_member(request) admin_member = retrieve_member(request)
@ -271,7 +325,8 @@ def admin(request):
if form.is_valid(): if form.is_valid():
new_password = form.cleaned_data['password1'] new_password = form.cleaned_data['password1']
admin_member.admin_change_password(form.cleaned_data['username'], new_password) admin_member.admin_change_password(form.cleaned_data['username'],
new_password)
member = MemberValues(form.cleaned_data['username'], new_password) member = MemberValues(form.cleaned_data['username'], new_password)
member.set('sambaLMPassword', smbpasswd.lmhash(new_password)) member.set('sambaLMPassword', smbpasswd.lmhash(new_password))
@ -280,42 +335,61 @@ def admin(request):
new_form = AdminForm(request=request, users=users) new_form = AdminForm(request=request, users=users)
return render(request, 'admin.html', return render(request, 'admin.html',
{'message': _('The password for %s was changed. Thank you!' % form.cleaned_data['username']), {'message': _(
'form': new_form}) 'The password for %s was changed. Thank you!' %
form.cleaned_data['username']),
'form': new_form})
else: else:
return render(request, 'admin.html', return render(request, 'admin.html',
{'form': form}) {'form': form})
else: else:
form = AdminForm(request=request, users=users) form = AdminForm(request=request, users=users)
return render(request, 'admin.html', return render(request, 'admin.html',
{'form': form}) {'form': form})
# username = cleaned_data.get('username')
# admin_username = self._request.user.username
# admin_password = self._request.session['ldap_password']
#username = cleaned_data.get('username')
#admin_username = self._request.user.username
#admin_password = self._request.session['ldap_password']
def hammertime(request): def hammertime(request):
return render(request, 'hammertime.html', {}) return render(request, 'hammertime.html', {})
@login_required @login_required
def memberstatus(request): def memberstatus(request):
#url = baseurl + route_operation_mapping['SessionCreate']['Route'] # url = baseurl + route_operation_mapping['SessionCreate']['Route']
#data = json.dumps({'UserLogin': username, 'Password': password}) # data = json.dumps({'UserLogin': username, 'Password': password})
password = get_ldap_password(request) password = get_ldap_password(request)
username = request.user.username username = request.user.username
url = "https://vorstand.c-base.org/cteward-api/legacy/member/%s/contributions" % username url = "https://vorstand.c-base.org" \
r = requests.get(url, verify=False, auth=(username, password)) "/cteward-api/legacy/member/%s/contributions" % username
r = requests.get(
url,
verify=False,
auth=(username, password)
)
contributions = r.json() contributions = r.json()
try: try:
years = collections.OrderedDict(sorted(contributions['years'].items(), reverse=True)) years = collections.OrderedDict(
sorted(contributions['years'].items(), reverse=True))
contributions['years'] = years.items() contributions['years'] = years.items()
except: pass except:
pass
url = "https://vorstand.c-base.org/cteward-api/legacy/member/%s" % username url = "https://vorstand.c-base.org/cteward-api/legacy/member/%s" % username
r = requests.get(url, verify=False, auth=(username, password)) r = requests.get(
url,
verify=False,
auth=(username, password)
)
cteward = r.json() cteward = r.json()
return render(request, 'memberstatus.html', {'contributions': contributions, 'cteward': cteward}) return render(
request, 'memberstatus.html',
{
'contributions': contributions,
'cteward': cteward
}
)