diff --git a/ldapdb/router.py b/ldapdb/router.py index ba00631..a195e54 100644 --- a/ldapdb/router.py +++ b/ldapdb/router.py @@ -43,6 +43,7 @@ class Router(object): NOTE: if you have more than one LDAP database, you will need to write your own router. + Yes. """ def __init__(self): diff --git a/main/forms.py b/main/forms.py index cae42c9..391cc43 100644 --- a/main/forms.py +++ b/main/forms.py @@ -3,16 +3,24 @@ from django.core.validators import MinLengthValidator from validators import PasswordValidator from models import * +import utils # not needed? class LDAPSetPasswordForm(SetPasswordForm): def save(self, commit=True): + # set kerberos password + set_krb5 = utils.set_kerberos_password(self.user.username, self.cleaned_data['new_password1']) + + # set radius password + set_radius = utils.set_radius_password(self.user.username, self.cleaned_data['new_password1']) + # Lookup the Ldap user with the identical username (1-to-1). self.user = LdapUser.objects.get(username=self.user.username) self.user.set_password(self.cleaned_data['new_password1']) if commit: self.user.save() + return self.user def clean_new_password1(self): diff --git a/main/models.py b/main/models.py index 44035f8..1c81789 100644 --- a/main/models.py +++ b/main/models.py @@ -1,11 +1,16 @@ from ldapdb.models.fields import CharField, ImageField, IntegerField, ListField +from django.db import models import ldapdb.models import passwd import datetime + class LdapUser(ldapdb.models.Model): """ Class for representing an LDAP user entry. """ + + connection_name = 'ldap' + # LDAP meta-data base_dn = "ou=People,dc=neuf,dc=no" # CONFIG object_classes = ['inetOrgPerson', 'posixAccount', 'shadowAccount'] @@ -68,3 +73,111 @@ class LdapGroup(ldapdb.models.Model): def __unicode__(self): return self.name + + +# This is an auto-generated Django model module. +# You'll have to do the following manually to clean this up: +# * Rearrange models' order +# * Make sure each model has one field with primary_key=True +# Feel free to rename the models, but don't rename db_table values or field names. +# +# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]' +# into your database. + +class Radacct(models.Model): + connection_name = 'radius' + + radacctid = models.BigIntegerField(primary_key=True) + acctsessionid = models.CharField(max_length=192) + acctuniqueid = models.CharField(max_length=96) + username = models.CharField(max_length=192) + groupname = models.CharField(max_length=192) + realm = models.CharField(max_length=192, blank=True) + nasipaddress = models.CharField(max_length=45) + nasportid = models.CharField(max_length=45, blank=True) + nasporttype = models.CharField(max_length=96, blank=True) + acctstarttime = models.DateTimeField(null=True, blank=True) + acctstoptime = models.DateTimeField(null=True, blank=True) + acctsessiontime = models.IntegerField(null=True, blank=True) + acctauthentic = models.CharField(max_length=96, blank=True) + connectinfo_start = models.CharField(max_length=150, blank=True) + connectinfo_stop = models.CharField(max_length=150, blank=True) + acctinputoctets = models.BigIntegerField(null=True, blank=True) + acctoutputoctets = models.BigIntegerField(null=True, blank=True) + calledstationid = models.CharField(max_length=150) + callingstationid = models.CharField(max_length=150) + acctterminatecause = models.CharField(max_length=96) + servicetype = models.CharField(max_length=96, blank=True) + framedprotocol = models.CharField(max_length=96, blank=True) + framedipaddress = models.CharField(max_length=45) + acctstartdelay = models.IntegerField(null=True, blank=True) + acctstopdelay = models.IntegerField(null=True, blank=True) + xascendsessionsvrkey = models.CharField(max_length=30, blank=True) + class Meta: + db_table = u'radacct' + +class Radcheck(models.Model): + connection_name = 'radius' + + username = models.CharField(max_length=192) + attribute = models.CharField(max_length=192) + op = models.CharField(max_length=6) + value = models.CharField(max_length=759) + class Meta: + db_table = u'radcheck' + +class Radgroupcheck(models.Model): + connection_name = 'radius' + + groupname = models.CharField(max_length=192) + attribute = models.CharField(max_length=192) + op = models.CharField(max_length=6) + value = models.CharField(max_length=759) + + class Meta: + db_table = u'radgroupcheck' + +class Radgroupreply(models.Model): + connection_name = 'radius' + + groupname = models.CharField(max_length=192) + attribute = models.CharField(max_length=192) + op = models.CharField(max_length=6) + value = models.CharField(max_length=759) + + class Meta: + db_table = u'radgroupreply' + +class Radpostauth(models.Model): + connection_name = 'radius' + + username = models.CharField(max_length=192) + pass_field = models.CharField(max_length=192, db_column='pass') + reply = models.CharField(max_length=96) + authdate = models.DateTimeField() + + class Meta: + db_table = u'radpostauth' + +class Radreply(models.Model): + connection_name = 'radius' + + username = models.CharField(max_length=192) + attribute = models.CharField(max_length=192) + op = models.CharField(max_length=6) + value = models.CharField(max_length=759) + + class Meta: + db_table = u'radreply' + +class Radusergroup(models.Model): + connection_name = 'radius' + + username = models.CharField(max_length=192) + groupname = models.CharField(max_length=192) + priority = models.IntegerField() + + class Meta: + db_table = u'radusergroup' + + diff --git a/main/passwd.py b/main/passwd.py index 8287416..d9c5ab2 100644 --- a/main/passwd.py +++ b/main/passwd.py @@ -23,3 +23,8 @@ def ldap_validate(raw_password, challenge_password): hr = hashlib.sha1(raw_password) hr.update(salt) return digest == hr.digest() + +def radius_create(raw_password): + # found in the wild. + nt_password = hashlib.new('md4', raw_password.encode('utf-16le')).hexdigest() + return nt_password diff --git a/main/router.py b/main/router.py new file mode 100644 index 0000000..fe18f74 --- /dev/null +++ b/main/router.py @@ -0,0 +1,31 @@ +def is_radius_model(model): + return hasattr(model, 'connection_name') + +class Router(object): + """ + A router to point database operations on models to the right db. + Using the attribute connection_name referring to the + DATABASES-label in settings.py. + + Ref: https://docs.djangoproject.com/en/dev/topics/db/multi-db/ + """ + + def allow_syncdb(self, db, model): + """Do not create tables on the default db for models on $./manage.py sync, + but this function maybe used know which db a model is on""" + if is_radius_model(model): + return db == model.connection_name + return db == 'default' + + def db_for_read(self, model, **hints): + "Point all operations on models connection_name to db=connection_name" + if is_radius_model(model): + return model.connection_name + return None + + def db_for_write(self, model, **hints): + "Point all operations on models connection_name to db=connection_name" + if is_radius_model(model): + return model.connection_name + return None + diff --git a/main/utils.py b/main/utils.py new file mode 100644 index 0000000..b55d276 --- /dev/null +++ b/main/utils.py @@ -0,0 +1,27 @@ +# coding: utf-8 +from subprocess import Popen, PIPE +from django.core.exceptions import ObjectDoesNotExist + +from models import Radcheck +import passwd + +def set_kerberos_password(username, raw_password): + principal = u"{0}@NEUF.NO".format(username) + kadmin_query = u"-q change_password {0} -pw {1}".format(principal, raw_password) + p = Popen(['kadmin', kadmin_query], stdout=PIPE) + output, error = p.communicate() + if error: + # Can not init with KDC? + # KADM5_AUTH_MODIFY (requires the modify privilege) + return False + else: + return True + +def set_radius_password(username, raw_password): + try: + radius_user = Radcheck.objects.get(username=username) + except ObjectDoesNotExist: + return False + radius_user.value = passwd.radius_create(raw_password) + radius_user.save() + return True diff --git a/main/views.py b/main/views.py index bb991c7..0e35a5a 100644 --- a/main/views.py +++ b/main/views.py @@ -37,12 +37,14 @@ def profile(request): try: ldap_user = LdapUser.objects.get(username=request.user) except: - pass + return render_to_response('private/profile.html', locals(), context_instance=RequestContext(request)) + groups = LdapGroup.objects.filter(usernames__contains=request.user) private_group = LdapGroup.objects.filter(gid=ldap_user.group)[0] return render_to_response('private/profile.html', locals(), context_instance=RequestContext(request)) + def radius_info(): return {'active': True} diff --git a/settings-sample.py b/settings-sample.py index 497d5f5..d380c47 100644 --- a/settings-sample.py +++ b/settings-sample.py @@ -28,9 +28,17 @@ DATABASES = { 'NAME': 'ldap://localhost/', 'USER': 'uid=admin,ou=People,dc=neuf,dc=no', 'PASSWORD': '', + }, + 'radius': { + 'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': 'radius', # Or path to database file if using sqlite3. + 'USER': 'radius', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': 'localhost', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. } } -DATABASE_ROUTERS = ['ldapdb.router.Router'] +DATABASE_ROUTERS = ['main.router.Router'] # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name diff --git a/templates/private/profile.html b/templates/private/profile.html index 7aaf19f..5e63765 100644 --- a/templates/private/profile.html +++ b/templates/private/profile.html @@ -21,10 +21,14 @@

Dine grupper

Dine tjenester