Archived
Major work
- Django 1.7 and other mode upgrades - Move code for each service to it's own app - Password decrypt should now work (RIJNDAEL-256 !#%!@)
This commit is contained in:
1 parent
2fe608a33b
commit
3462e2865c
32 files changed
+893
-353
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
Whitespace-only changes.
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,54 @@
|
||||
import datetime
|
||||
from django.conf import settings
|
||||
import logging
|
||||
import re
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_kerberos_principal(username):
|
||||
principal = "{0}@{1}".format(username, settings.KERBEROS_REALM)
|
||||
kadmin_query = " -p '{0}' -w {1} -q 'get_principal {2}'".format(
|
||||
settings.KERBEROS_PASSWORD_CHANGING_PRINCIPAL,
|
||||
settings.KERBEROS_PASSWORD,
|
||||
principal)
|
||||
p = Popen('kadmin' + kadmin_query, shell=True, stdout=PIPE, stderr=PIPE)
|
||||
output, error = p.communicate()
|
||||
if error:
|
||||
# Can not init with KDC?
|
||||
# KADM5_UNK_PRINC (principal does not exist)
|
||||
# KADM5_AUTH_GET (requires the get (inquire) privilege)
|
||||
logger.error(error)
|
||||
return None
|
||||
|
||||
cleaned_output = {}
|
||||
match = re.findall(r'([\w ]*): (.+)', output)
|
||||
for key, value in match:
|
||||
cleaned_output[key] = value
|
||||
return cleaned_output
|
||||
|
||||
|
||||
def set_kerberos_password(username, raw_password):
|
||||
principal = "{0}@{1}".format(username, settings.KERBEROS_REALM)
|
||||
kadmin_query = " -p {0} -w {1} -q 'change_password -pw {2} {3}'".format(
|
||||
settings.KERBEROS_PASSWORD_CHANGING_PRINCIPAL,
|
||||
settings.KERBEROS_PASSWORD,
|
||||
raw_password,
|
||||
principal)
|
||||
# FIXME: Running this shell command needs escaping: http://qntm.org/bash
|
||||
p = Popen('kadmin' + kadmin_query, shell=True, stdout=PIPE, stderr=PIPE)
|
||||
output, error = p.communicate()
|
||||
if error:
|
||||
# Can not init with KDC?
|
||||
# KADM5_AUTH_MODIFY (requires the modify privilege)
|
||||
logger.error(error)
|
||||
|
||||
|
||||
def format_krb5_date(date):
|
||||
try:
|
||||
formatted_date = datetime.datetime.strptime(date, '%a %b %d %H:%M:%S %Z %Y')
|
||||
formatted_date = formatted_date.strftime('%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
formatted_date = 'never'
|
||||
return formatted_date
|
||||
@@ -0,0 +1,20 @@
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import JsonResponse
|
||||
|
||||
from neuf_kerberos.utils import get_kerberos_principal, format_krb5_date
|
||||
|
||||
|
||||
@login_required
|
||||
def client_status(request):
|
||||
krb5_principal = get_kerberos_principal(request.GET.get('username'))
|
||||
if not krb5_principal:
|
||||
return JsonResponse({'active': False})
|
||||
|
||||
last_succ_auth = format_krb5_date(krb5_principal['Last successful authentication'])
|
||||
status = {
|
||||
'active': True,
|
||||
'last_successful_auth': last_succ_auth,
|
||||
'last_modified': krb5_principal['Last modified']
|
||||
}
|
||||
|
||||
return JsonResponse(status)
|
||||
Whitespace-only changes.
@@ -0,0 +1,17 @@
|
||||
from django.contrib import admin
|
||||
from neuf_ldap.models import LdapGroup, LdapUser
|
||||
|
||||
|
||||
class LdapGroupAdmin(admin.ModelAdmin):
|
||||
exclude = ['dn', 'usernames']
|
||||
list_display = ['name', 'gid']
|
||||
search_fields = ['name']
|
||||
|
||||
|
||||
class LdapUserAdmin(admin.ModelAdmin):
|
||||
exclude = ['dn', 'password', 'photo']
|
||||
list_display = ['username', 'first_name', 'last_name', 'email', 'id']
|
||||
search_fields = ['first_name', 'last_name', 'full_name', 'username']
|
||||
|
||||
admin.site.register(LdapGroup, LdapGroupAdmin)
|
||||
admin.site.register(LdapUser, LdapUserAdmin)
|
||||
Whitespace-only changes.
@@ -0,0 +1,70 @@
|
||||
import datetime
|
||||
import ldapdb.models
|
||||
from ldapdb.models.fields import CharField, ImageField, IntegerField, ListField
|
||||
|
||||
from neuf_ldap.utils import ldap_create, ldap_validate
|
||||
|
||||
|
||||
class LdapUser(ldapdb.models.Model):
|
||||
""" Represents an LDAP posixAccount,inetOrgPerson entry. """
|
||||
|
||||
connection_name = 'ldap'
|
||||
|
||||
# LDAP meta-data
|
||||
base_dn = "ou=People,dc=neuf,dc=no" # FXIME: CONFIG
|
||||
object_classes = ['inetOrgPerson', 'posixAccount', 'shadowAccount']
|
||||
|
||||
# inetOrgPerson
|
||||
first_name = CharField(db_column='givenName')
|
||||
last_name = CharField(db_column='sn')
|
||||
full_name = CharField(db_column='cn')
|
||||
email = CharField(db_column='mail')
|
||||
phone = CharField(db_column='telephoneNumber', blank=True)
|
||||
mobile_phone = CharField(db_column='mobile', blank=True)
|
||||
photo = ImageField(db_column='jpegPhoto')
|
||||
|
||||
# posixAccount
|
||||
id = IntegerField(db_column='uidNumber', unique=True) # referenced in reset password form
|
||||
group = IntegerField(db_column='gidNumber')
|
||||
gecos = CharField(db_column='gecos')
|
||||
home_directory = CharField(db_column='homeDirectory')
|
||||
login_shell = CharField(db_column='loginShell', default='/bin/bash')
|
||||
username = CharField(db_column='uid', primary_key=True)
|
||||
password = CharField(db_column='userPassword')
|
||||
|
||||
# TODO shadowAccount
|
||||
|
||||
# ugly hack to use django internals for password reset
|
||||
last_login = datetime.datetime(2001, 1, 1)
|
||||
|
||||
def set_password(self, raw_password):
|
||||
if raw_password is None:
|
||||
self.password = "!"
|
||||
return
|
||||
|
||||
self.password = ldap_create(raw_password)
|
||||
self.save()
|
||||
|
||||
def check_password(self, raw_password):
|
||||
return ldap_validate(raw_password, self.password)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.username
|
||||
|
||||
|
||||
class LdapGroup(ldapdb.models.Model):
|
||||
""" Represents an LDAP posixGroup entry. """
|
||||
|
||||
connection_name = 'ldap'
|
||||
|
||||
# LDAP meta-data
|
||||
base_dn = "ou=Groups,dc=neuf,dc=no" # CONFIG
|
||||
object_classes = ['posixGroup']
|
||||
|
||||
# posixGroup attributes
|
||||
gid = IntegerField(db_column='gidNumber', unique=True)
|
||||
name = CharField(db_column='cn', max_length=200, primary_key=True)
|
||||
usernames = ListField(db_column='memberUid')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -2,14 +2,17 @@ import hashlib
|
||||
import os
|
||||
from base64 import encodestring as encode
|
||||
from base64 import decodestring as decode
|
||||
import os.path
|
||||
|
||||
# Ref:
|
||||
# - http://www.openldap.org/faq/data/cache/347.html
|
||||
# - http://www.openldap.org/doc/admin24/security.html
|
||||
"""
|
||||
Ref:
|
||||
- http://www.openldap.org/faq/data/cache/347.html
|
||||
- http://www.openldap.org/doc/admin24/security.html
|
||||
"""
|
||||
|
||||
|
||||
def ldap_create(raw_password, hash_type='ssha'):
|
||||
# Generate a salted SHA hash
|
||||
# Generate a salted SHA1 hash
|
||||
salt = os.urandom(4)
|
||||
h = hashlib.sha1(raw_password)
|
||||
h.update(salt)
|
||||
@@ -27,7 +30,11 @@ def ldap_validate(raw_password, challenge_password):
|
||||
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
|
||||
def set_ldap_password(username, raw_password):
|
||||
from neuf_ldap.models import LdapUser
|
||||
try:
|
||||
# LDAP: Lookup the Ldap user with the identical username (1-to-1).
|
||||
user = LdapUser.objects.get(username=username)
|
||||
user.set_password(raw_password)
|
||||
except LdapUser.DoesNotExist:
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
Whitespace-only changes.
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
Whitespace-only changes.
@@ -0,0 +1,106 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
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'
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,28 @@
|
||||
import hashlib
|
||||
from neuf_radius.models import Radcheck
|
||||
|
||||
|
||||
def radius_create(raw_password):
|
||||
# found in the wild.
|
||||
nt_password = hashlib.new('md4', raw_password.encode('utf-16le')).hexdigest()
|
||||
return nt_password
|
||||
|
||||
|
||||
def set_radius_password(username, raw_password):
|
||||
"""
|
||||
Set password in Radius mysql backend.
|
||||
Create user if one with an NT-Password does not exist.
|
||||
"""
|
||||
pw_type = 'NT-Password'
|
||||
radius_defaults = {'attribute': pw_type, 'op': ':='}
|
||||
radius_user, created = Radcheck.objects.get_or_create(
|
||||
username=username,
|
||||
defaults=radius_defaults)
|
||||
|
||||
# Some have clear text passwords!
|
||||
if radius_user.attribute != pw_type:
|
||||
radius_user.attribute = pw_type
|
||||
|
||||
radius_user.value = radius_create(raw_password)
|
||||
radius_user.save()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import JsonResponse
|
||||
from neuf_radius.models import Radcheck, Radpostauth
|
||||
|
||||
|
||||
@login_required
|
||||
def wireless_status(request):
|
||||
username = request.GET.get('username')
|
||||
try:
|
||||
radius_user = Radcheck.objects.get(username=username)
|
||||
except Radcheck.DoesNotExist:
|
||||
return JsonResponse({'active': False})
|
||||
|
||||
# get last authentication
|
||||
last_auth = Radpostauth.objects.filter(username__iexact=username, reply='Access-Accept').order_by('authdate')
|
||||
status = {'active': True, 'hash': radius_user.attribute}
|
||||
if len(last_auth) != 0:
|
||||
status['last_successful_auth'] = last_auth[0].authdate.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
return JsonResponse(status)
|
||||
@@ -1,17 +1 @@
|
||||
from django.contrib import admin
|
||||
from neuf_userinfo.models import LdapGroup, LdapUser
|
||||
|
||||
|
||||
class LdapGroupAdmin(admin.ModelAdmin):
|
||||
exclude = ['dn', 'usernames']
|
||||
list_display = ['name', 'gid']
|
||||
search_fields = ['name']
|
||||
|
||||
|
||||
class LdapUserAdmin(admin.ModelAdmin):
|
||||
exclude = ['dn', 'password', 'photo']
|
||||
list_display = ['username', 'first_name', 'last_name', 'email', 'id']
|
||||
search_fields = ['first_name', 'last_name', 'full_name', 'username']
|
||||
|
||||
admin.site.register(LdapGroup, LdapGroupAdmin)
|
||||
admin.site.register(LdapUser, LdapUserAdmin)
|
||||
+41
-22
@@ -1,6 +1,6 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
from django.contrib.auth.forms import SetPasswordForm, PasswordResetForm
|
||||
from django.contrib.auth.forms import SetPasswordForm
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.sites.models import get_current_site
|
||||
from django.core.validators import MinLengthValidator
|
||||
@@ -9,32 +9,40 @@ from django.template import loader
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
from django.utils.translation import ugettext as _
|
||||
import sys
|
||||
|
||||
from inside.models import InsideUser
|
||||
from inside.utils import set_inside_password
|
||||
from neuf_userinfo.models import LdapUser
|
||||
from neuf_userinfo.utils import set_kerberos_password, set_radius_password
|
||||
|
||||
from neuf_kerberos.utils import set_kerberos_password
|
||||
from neuf_radius.utils import set_radius_password
|
||||
from neuf_ldap.utils import set_ldap_password
|
||||
from neuf_userinfo.validators import PasswordValidator
|
||||
|
||||
|
||||
class NeufSetPasswordForm(SetPasswordForm):
|
||||
"""
|
||||
Saves a user password in each of the following services:
|
||||
- Inside (all)
|
||||
- Kerberos
|
||||
- LDAP
|
||||
- RADIUS
|
||||
|
||||
Note: self.user is a local Django User object (which does not have a usable password)
|
||||
"""
|
||||
def save(self, commit=True):
|
||||
|
||||
username = self.user.username
|
||||
password = self.cleaned_data['new_password1']
|
||||
|
||||
# Membership database
|
||||
set_inside_password(self.user.username, self.cleaned_data['new_password1'])
|
||||
set_inside_password(username, password)
|
||||
|
||||
# Active services
|
||||
set_kerberos_password(self.user.username, self.cleaned_data['new_password1'])
|
||||
set_radius_password(self.user.username, self.cleaned_data['new_password1'])
|
||||
set_kerberos_password(username, password)
|
||||
set_radius_password(username, password)
|
||||
set_ldap_password(username, password)
|
||||
|
||||
# LDAP: 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
|
||||
return self.user # Local Django User
|
||||
|
||||
def clean_new_password1(self):
|
||||
raw_password = self.cleaned_data.get('new_password1')
|
||||
@@ -49,12 +57,13 @@ class NeufPasswordChangeForm(NeufSetPasswordForm):
|
||||
pass
|
||||
|
||||
|
||||
class NeufPasswordResetForm(PasswordResetForm):
|
||||
class NeufPasswordResetForm(forms.Form):
|
||||
EMAIL_ERROR_MSG = "That e-mail address doesn't have an associated user account. Are you sure you've registered?"
|
||||
email = forms.EmailField(label=_("Email"), max_length=254)
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data["email"].lower()
|
||||
self.users_cache = InsideUser.objects.filter(email=email)
|
||||
self.users_cache = InsideUser.objects.filter(email=email) # From Membership database (Inside)
|
||||
if len(self.users_cache) == 0:
|
||||
raise forms.ValidationError(_(self.EMAIL_ERROR_MSG))
|
||||
return email
|
||||
@@ -63,15 +72,24 @@ class NeufPasswordResetForm(PasswordResetForm):
|
||||
subject_template_name='registration/password_reset_subject.txt',
|
||||
email_template_name='registration/password_reset_email.html',
|
||||
use_https=False, token_generator=default_token_generator,
|
||||
from_email=None, request=None):
|
||||
from_email=None, request=None, html_email_template_name=None):
|
||||
"""
|
||||
Generates a one-use only link for resetting password and sends to the user.
|
||||
Generates a one-use only link for resetting password and sends to the
|
||||
user.
|
||||
|
||||
Note: this is the same form as django.contrib.auth.forms.PasswordResetForm
|
||||
with some changes for looking up a custom user object.
|
||||
"""
|
||||
from django.core.mail import send_mail
|
||||
# UserModel = get_user_model()
|
||||
# email = self.cleaned_data["email"]
|
||||
# active_users = UserModel._default_manager.filter(
|
||||
# email__iexact=email, is_active=True)
|
||||
for user in self.users_cache:
|
||||
# Make sure that no email is sent to a user that actually has
|
||||
# a password marked as unusable
|
||||
# if not user.has_usable_password():
|
||||
# continue
|
||||
if not domain_override:
|
||||
current_site = get_current_site(request)
|
||||
site_name = current_site.name
|
||||
@@ -91,8 +109,9 @@ class NeufPasswordResetForm(PasswordResetForm):
|
||||
# Email subject *must not* contain newlines
|
||||
subject = ''.join(subject.splitlines())
|
||||
email = loader.render_to_string(email_template_name, c)
|
||||
if sys.version_info < (2, 6, 6):
|
||||
# Workaround for http://bugs.python.org/issue1368247
|
||||
email = email.encode('utf-8')
|
||||
|
||||
send_mail(subject, email, from_email, [user.email])
|
||||
if html_email_template_name:
|
||||
html_email = loader.render_to_string(html_email_template_name, c)
|
||||
else:
|
||||
html_email = None
|
||||
send_mail(subject, email, from_email, [user.email], html_message=html_email)
|
||||
+1
-195
@@ -1,195 +1 @@
|
||||
import datetime
|
||||
from django.db import models
|
||||
import ldapdb.models
|
||||
from ldapdb.models.fields import CharField, ImageField, IntegerField, ListField
|
||||
import passwd
|
||||
|
||||
|
||||
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" # FXIME: CONFIG
|
||||
object_classes = ['inetOrgPerson', 'posixAccount', 'shadowAccount']
|
||||
|
||||
# inetOrgPerson
|
||||
first_name = CharField(db_column='givenName')
|
||||
last_name = CharField(db_column='sn')
|
||||
full_name = CharField(db_column='cn')
|
||||
email = CharField(db_column='mail')
|
||||
phone = CharField(db_column='telephoneNumber', blank=True)
|
||||
mobile_phone = CharField(db_column='mobile', blank=True)
|
||||
photo = ImageField(db_column='jpegPhoto')
|
||||
|
||||
# posixAccount
|
||||
id = IntegerField(db_column='uidNumber', unique=True) # referenced in reset password form
|
||||
group = IntegerField(db_column='gidNumber')
|
||||
gecos = CharField(db_column='gecos')
|
||||
home_directory = CharField(db_column='homeDirectory')
|
||||
login_shell = CharField(db_column='loginShell', default='/bin/bash')
|
||||
username = CharField(db_column='uid', primary_key=True)
|
||||
password = CharField(db_column='userPassword')
|
||||
|
||||
# ugly hack to use django internals for password reset
|
||||
last_login = datetime.datetime(2001, 1, 1)
|
||||
|
||||
def set_password(self, raw_password):
|
||||
if raw_password is None:
|
||||
self.password = "!"
|
||||
else:
|
||||
self.password = passwd.ldap_create(raw_password)
|
||||
self.save()
|
||||
|
||||
def check_password(self, raw_password):
|
||||
"""
|
||||
Returns a boolean of whether the raw_password was correct.
|
||||
"""
|
||||
return passwd.ldap_validate(raw_password, self.password)
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
def __unicode__(self):
|
||||
return self.full_name
|
||||
|
||||
|
||||
class LdapGroup(ldapdb.models.Model):
|
||||
"""
|
||||
Class for representing an LDAP group entry.
|
||||
"""
|
||||
|
||||
connection_name = 'ldap'
|
||||
|
||||
# LDAP meta-data
|
||||
base_dn = "ou=Groups,dc=neuf,dc=no" # CONFIG
|
||||
object_classes = ['posixGroup']
|
||||
|
||||
# posixGroup attributes
|
||||
gid = IntegerField(db_column='gidNumber', unique=True)
|
||||
name = CharField(db_column='cn', max_length=200, primary_key=True)
|
||||
usernames = ListField(db_column='memberUid')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
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'
|
||||
|
||||
from django.db import models
|
||||
@@ -0,0 +1,378 @@
|
||||
"""
|
||||
Ref: http://wiki.birth-online.de/_media/snippets/python/rijndael.py
|
||||
|
||||
A pure python (slow) implementation of rijndael with a decent interface
|
||||
|
||||
To include -
|
||||
|
||||
from rijndael import rijndael
|
||||
|
||||
To do a key setup -
|
||||
|
||||
r = rijndael(key, block_size = 16)
|
||||
|
||||
key must be a string of length 16, 24, or 32
|
||||
blocksize must be 16, 24, or 32. Default is 16
|
||||
|
||||
To use -
|
||||
|
||||
ciphertext = r.encrypt(plaintext)
|
||||
plaintext = r.decrypt(ciphertext)
|
||||
|
||||
If any strings are of the wrong length a ValueError is thrown
|
||||
"""
|
||||
|
||||
# ported from the Java reference code by Bram Cohen, April 2001
|
||||
# this code is public domain, unless someone makes
|
||||
# an intellectual property claim against the reference
|
||||
# code, in which case it can be made public domain by
|
||||
# deleting all the comments and renaming all the variables
|
||||
|
||||
import copy
|
||||
import string
|
||||
|
||||
shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]],
|
||||
[[0, 0], [1, 5], [2, 4], [3, 3]],
|
||||
[[0, 0], [1, 7], [3, 5], [4, 4]]]
|
||||
|
||||
# [keysize][block_size]
|
||||
num_rounds = {16: {16: 10, 24: 12, 32: 14}, 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}}
|
||||
|
||||
A = [[1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 1, 1, 1, 1],
|
||||
[1, 1, 0, 0, 0, 1, 1, 1],
|
||||
[1, 1, 1, 0, 0, 0, 1, 1],
|
||||
[1, 1, 1, 1, 0, 0, 0, 1]]
|
||||
|
||||
# produce log and alog tables, needed for multiplying in the
|
||||
# field GF(2^m) (generator = 3)
|
||||
alog = [1]
|
||||
for i in range(255):
|
||||
j = (alog[-1] << 1) ^ alog[-1]
|
||||
if j & 0x100 != 0:
|
||||
j ^= 0x11B
|
||||
alog.append(j)
|
||||
|
||||
log = [0] * 256
|
||||
for i in range(1, 255):
|
||||
log[alog[i]] = i
|
||||
|
||||
# multiply two elements of GF(2^m)
|
||||
def mul(a, b):
|
||||
if a == 0 or b == 0:
|
||||
return 0
|
||||
return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255]
|
||||
|
||||
# substitution box based on F^{-1}(x)
|
||||
box = [[0] * 8 for i in range(256)]
|
||||
box[1][7] = 1
|
||||
for i in range(2, 256):
|
||||
j = alog[255 - log[i]]
|
||||
for t in range(8):
|
||||
box[i][t] = (j >> (7 - t)) & 0x01
|
||||
|
||||
B = [0, 1, 1, 0, 0, 0, 1, 1]
|
||||
|
||||
# affine transform: box[i] <- B + A*box[i]
|
||||
cox = [[0] * 8 for i in range(256)]
|
||||
for i in range(256):
|
||||
for t in range(8):
|
||||
cox[i][t] = B[t]
|
||||
for j in range(8):
|
||||
cox[i][t] ^= A[t][j] * box[i][j]
|
||||
|
||||
# S-boxes and inverse S-boxes
|
||||
S = [0] * 256
|
||||
Si = [0] * 256
|
||||
for i in range(256):
|
||||
S[i] = cox[i][0] << 7
|
||||
for t in range(1, 8):
|
||||
S[i] ^= cox[i][t] << (7-t)
|
||||
Si[S[i] & 0xFF] = i
|
||||
|
||||
# T-boxes
|
||||
G = [[2, 1, 1, 3],
|
||||
[3, 2, 1, 1],
|
||||
[1, 3, 2, 1],
|
||||
[1, 1, 3, 2]]
|
||||
|
||||
AA = [[0] * 8 for i in range(4)]
|
||||
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
AA[i][j] = G[i][j]
|
||||
AA[i][i+4] = 1
|
||||
|
||||
for i in range(4):
|
||||
pivot = AA[i][i]
|
||||
if pivot == 0:
|
||||
t = i + 1
|
||||
while AA[t][i] == 0 and t < 4:
|
||||
t += 1
|
||||
assert t != 4, 'G matrix must be invertible'
|
||||
for j in range(8):
|
||||
AA[i][j], AA[t][j] = AA[t][j], AA[i][j]
|
||||
pivot = AA[i][i]
|
||||
for j in range(8):
|
||||
if AA[i][j] != 0:
|
||||
AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255]
|
||||
for t in range(4):
|
||||
if i != t:
|
||||
for j in range(i+1, 8):
|
||||
AA[t][j] ^= mul(AA[i][j], AA[t][i])
|
||||
AA[t][i] = 0
|
||||
|
||||
iG = [[0] * 4 for i in range(4)]
|
||||
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
iG[i][j] = AA[i][j + 4]
|
||||
|
||||
def mul4(a, bs):
|
||||
if a == 0:
|
||||
return 0
|
||||
r = 0
|
||||
for b in bs:
|
||||
r <<= 8
|
||||
if b != 0:
|
||||
r = r | mul(a, b)
|
||||
return r
|
||||
|
||||
T1 = []
|
||||
T2 = []
|
||||
T3 = []
|
||||
T4 = []
|
||||
T5 = []
|
||||
T6 = []
|
||||
T7 = []
|
||||
T8 = []
|
||||
U1 = []
|
||||
U2 = []
|
||||
U3 = []
|
||||
U4 = []
|
||||
|
||||
for t in range(256):
|
||||
s = S[t]
|
||||
T1.append(mul4(s, G[0]))
|
||||
T2.append(mul4(s, G[1]))
|
||||
T3.append(mul4(s, G[2]))
|
||||
T4.append(mul4(s, G[3]))
|
||||
|
||||
s = Si[t]
|
||||
T5.append(mul4(s, iG[0]))
|
||||
T6.append(mul4(s, iG[1]))
|
||||
T7.append(mul4(s, iG[2]))
|
||||
T8.append(mul4(s, iG[3]))
|
||||
|
||||
U1.append(mul4(t, iG[0]))
|
||||
U2.append(mul4(t, iG[1]))
|
||||
U3.append(mul4(t, iG[2]))
|
||||
U4.append(mul4(t, iG[3]))
|
||||
|
||||
# round constants
|
||||
rcon = [1]
|
||||
r = 1
|
||||
for t in range(1, 30):
|
||||
r = mul(2, r)
|
||||
rcon.append(r)
|
||||
|
||||
del A
|
||||
del AA
|
||||
del pivot
|
||||
del B
|
||||
del G
|
||||
del box
|
||||
del log
|
||||
del alog
|
||||
del i
|
||||
del j
|
||||
del r
|
||||
del s
|
||||
del t
|
||||
del mul
|
||||
del mul4
|
||||
del cox
|
||||
del iG
|
||||
|
||||
class rijndael:
|
||||
def __init__(self, key, block_size = 16):
|
||||
if block_size != 16 and block_size != 24 and block_size != 32:
|
||||
raise ValueError('Invalid block size: ' + str(block_size))
|
||||
if len(key) != 16 and len(key) != 24 and len(key) != 32:
|
||||
raise ValueError('Invalid key size: ' + str(len(key)))
|
||||
self.block_size = block_size
|
||||
|
||||
ROUNDS = num_rounds[len(key)][block_size]
|
||||
BC = block_size // 4
|
||||
# encryption round keys
|
||||
Ke = [[0] * BC for i in range(ROUNDS + 1)]
|
||||
# decryption round keys
|
||||
Kd = [[0] * BC for i in range(ROUNDS + 1)]
|
||||
ROUND_KEY_COUNT = (ROUNDS + 1) * BC
|
||||
KC = len(key) // 4
|
||||
|
||||
# copy user material bytes into temporary ints
|
||||
tk = []
|
||||
for i in range(0, KC):
|
||||
tk.append((ord(key[i * 4]) << 24) | (ord(key[i * 4 + 1]) << 16) |
|
||||
(ord(key[i * 4 + 2]) << 8) | ord(key[i * 4 + 3]))
|
||||
|
||||
# copy values into round key arrays
|
||||
t = 0
|
||||
j = 0
|
||||
while j < KC and t < ROUND_KEY_COUNT:
|
||||
Ke[t // BC][t % BC] = tk[j]
|
||||
Kd[ROUNDS - (t // BC)][t % BC] = tk[j]
|
||||
j += 1
|
||||
t += 1
|
||||
tt = 0
|
||||
rconpointer = 0
|
||||
while t < ROUND_KEY_COUNT:
|
||||
# extrapolate using phi (the round key evolution function)
|
||||
tt = tk[KC - 1]
|
||||
tk[0] ^= (S[(tt >> 16) & 0xFF] & 0xFF) << 24 ^ \
|
||||
(S[(tt >> 8) & 0xFF] & 0xFF) << 16 ^ \
|
||||
(S[ tt & 0xFF] & 0xFF) << 8 ^ \
|
||||
(S[(tt >> 24) & 0xFF] & 0xFF) ^ \
|
||||
(rcon[rconpointer] & 0xFF) << 24
|
||||
rconpointer += 1
|
||||
if KC != 8:
|
||||
for i in range(1, KC):
|
||||
tk[i] ^= tk[i-1]
|
||||
else:
|
||||
for i in range(1, KC // 2):
|
||||
tk[i] ^= tk[i-1]
|
||||
tt = tk[KC // 2 - 1]
|
||||
tk[KC // 2] ^= (S[ tt & 0xFF] & 0xFF) ^ \
|
||||
(S[(tt >> 8) & 0xFF] & 0xFF) << 8 ^ \
|
||||
(S[(tt >> 16) & 0xFF] & 0xFF) << 16 ^ \
|
||||
(S[(tt >> 24) & 0xFF] & 0xFF) << 24
|
||||
for i in range(KC // 2 + 1, KC):
|
||||
tk[i] ^= tk[i-1]
|
||||
# copy values into round key arrays
|
||||
j = 0
|
||||
while j < KC and t < ROUND_KEY_COUNT:
|
||||
Ke[t // BC][t % BC] = tk[j]
|
||||
Kd[ROUNDS - (t // BC)][t % BC] = tk[j]
|
||||
j += 1
|
||||
t += 1
|
||||
# inverse MixColumn where needed
|
||||
for r in range(1, ROUNDS):
|
||||
for j in range(BC):
|
||||
tt = Kd[r][j]
|
||||
Kd[r][j] = U1[(tt >> 24) & 0xFF] ^ \
|
||||
U2[(tt >> 16) & 0xFF] ^ \
|
||||
U3[(tt >> 8) & 0xFF] ^ \
|
||||
U4[ tt & 0xFF]
|
||||
self.Ke = Ke
|
||||
self.Kd = Kd
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
if len(plaintext) != self.block_size:
|
||||
raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext)))
|
||||
Ke = self.Ke
|
||||
|
||||
BC = self.block_size // 4
|
||||
ROUNDS = len(Ke) - 1
|
||||
if BC == 4:
|
||||
SC = 0
|
||||
elif BC == 6:
|
||||
SC = 1
|
||||
else:
|
||||
SC = 2
|
||||
s1 = shifts[SC][1][0]
|
||||
s2 = shifts[SC][2][0]
|
||||
s3 = shifts[SC][3][0]
|
||||
a = [0] * BC
|
||||
# temporary work array
|
||||
t = []
|
||||
# plaintext to ints + key
|
||||
for i in range(BC):
|
||||
t.append((ord(plaintext[i * 4 ]) << 24 |
|
||||
ord(plaintext[i * 4 + 1]) << 16 |
|
||||
ord(plaintext[i * 4 + 2]) << 8 |
|
||||
ord(plaintext[i * 4 + 3]) ) ^ Ke[0][i])
|
||||
# apply round transforms
|
||||
for r in range(1, ROUNDS):
|
||||
for i in range(BC):
|
||||
a[i] = (T1[(t[ i ] >> 24) & 0xFF] ^
|
||||
T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^
|
||||
T3[(t[(i + s2) % BC] >> 8) & 0xFF] ^
|
||||
T4[ t[(i + s3) % BC] & 0xFF] ) ^ Ke[r][i]
|
||||
t = copy.copy(a)
|
||||
# last round is special
|
||||
result = []
|
||||
for i in range(BC):
|
||||
tt = Ke[ROUNDS][i]
|
||||
result.append((S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
|
||||
result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
|
||||
result.append((S[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF)
|
||||
result.append((S[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF)
|
||||
return ''.join(map(chr, result))
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
if len(ciphertext) != self.block_size:
|
||||
raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(ciphertext)))
|
||||
Kd = self.Kd
|
||||
|
||||
BC = self.block_size // 4
|
||||
ROUNDS = len(Kd) - 1
|
||||
if BC == 4:
|
||||
SC = 0
|
||||
elif BC == 6:
|
||||
SC = 1
|
||||
else:
|
||||
SC = 2
|
||||
s1 = shifts[SC][1][1]
|
||||
s2 = shifts[SC][2][1]
|
||||
s3 = shifts[SC][3][1]
|
||||
a = [0] * BC
|
||||
# temporary work array
|
||||
t = [0] * BC
|
||||
# ciphertext to ints + key
|
||||
for i in range(BC):
|
||||
t[i] = (ord(ciphertext[i * 4 ]) << 24 |
|
||||
ord(ciphertext[i * 4 + 1]) << 16 |
|
||||
ord(ciphertext[i * 4 + 2]) << 8 |
|
||||
ord(ciphertext[i * 4 + 3]) ) ^ Kd[0][i]
|
||||
# apply round transforms
|
||||
for r in range(1, ROUNDS):
|
||||
for i in range(BC):
|
||||
a[i] = (T5[(t[ i ] >> 24) & 0xFF] ^
|
||||
T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^
|
||||
T7[(t[(i + s2) % BC] >> 8) & 0xFF] ^
|
||||
T8[ t[(i + s3) % BC] & 0xFF] ) ^ Kd[r][i]
|
||||
t = copy.copy(a)
|
||||
# last round is special
|
||||
result = []
|
||||
for i in range(BC):
|
||||
tt = Kd[ROUNDS][i]
|
||||
result.append((Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
|
||||
result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
|
||||
result.append((Si[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF)
|
||||
result.append((Si[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF)
|
||||
return ''.join(map(chr, result))
|
||||
|
||||
def encrypt(key, block):
|
||||
return rijndael(key, len(block)).encrypt(block)
|
||||
|
||||
def decrypt(key, block):
|
||||
return rijndael(key, len(block)).decrypt(block)
|
||||
|
||||
def test():
|
||||
def t(kl, bl):
|
||||
b = 'b' * bl
|
||||
r = rijndael('a' * kl, bl)
|
||||
assert r.decrypt(r.encrypt(b)) == b
|
||||
t(16, 16)
|
||||
t(16, 24)
|
||||
t(16, 32)
|
||||
t(24, 16)
|
||||
t(24, 24)
|
||||
t(24, 32)
|
||||
t(32, 16)
|
||||
t(32, 24)
|
||||
t(32, 32)
|
||||
+36
-63
@@ -1,78 +1,51 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
import datetime
|
||||
from django.conf import settings
|
||||
import base64
|
||||
import rijndael
|
||||
import logging
|
||||
import passwd
|
||||
import re
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from models import Radcheck
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
Rijndael stuff, thank you SO!
|
||||
Ref: http://stackoverflow.com/questions/8217269/decrypting-strings-in-python-that-were-encrypted-with-mcrypt-rijndael-256-in-php#answers-header
|
||||
"""
|
||||
|
||||
def get_kerberos_principal(username):
|
||||
principal = "{0}@{1}".format(username, settings.KERBEROS_REALM)
|
||||
kadmin_query = " -p '{0}' -w {1} -q 'get_principal {2}'".format(
|
||||
settings.KERBEROS_PASSWORD_CHANGING_PRINCIPAL,
|
||||
settings.KERBEROS_PASSWORD,
|
||||
principal)
|
||||
p = Popen('kadmin' + kadmin_query, shell=True, stdout=PIPE, stderr=PIPE)
|
||||
output, error = p.communicate()
|
||||
if error:
|
||||
# Can not init with KDC?
|
||||
# KADM5_UNK_PRINC (principal does not exist)
|
||||
# KADM5_AUTH_GET (requires the get (inquire) privilege)
|
||||
logger.error(error)
|
||||
return None
|
||||
else:
|
||||
cleaned_output = {}
|
||||
match = re.findall(r'([\w ]*): (.+)', output)
|
||||
for key, value in match:
|
||||
cleaned_output[key] = value
|
||||
return cleaned_output
|
||||
KEY_SIZE = 16
|
||||
BLOCK_SIZE = 32
|
||||
|
||||
|
||||
def set_kerberos_password(username, raw_password):
|
||||
principal = "{0}@{1}".format(username, settings.KERBEROS_REALM)
|
||||
kadmin_query = " -p {0} -w {1} -q 'change_password -pw {2} {3}'".format(
|
||||
settings.KERBEROS_PASSWORD_CHANGING_PRINCIPAL,
|
||||
settings.KERBEROS_PASSWORD,
|
||||
raw_password,
|
||||
principal)
|
||||
# FIXME: Running this shell command needs escaping: http://qntm.org/bash
|
||||
p = Popen('kadmin' + kadmin_query, shell=True, stdout=PIPE, stderr=PIPE)
|
||||
output, error = p.communicate()
|
||||
if error:
|
||||
# Can not init with KDC?
|
||||
# KADM5_AUTH_MODIFY (requires the modify privilege)
|
||||
logger.error(error)
|
||||
def encrypt_rijndael(key, plaintext):
|
||||
padded_key = key.ljust(KEY_SIZE, '\0')
|
||||
padded_text = plaintext + (BLOCK_SIZE - len(plaintext) % BLOCK_SIZE) * '\0'
|
||||
|
||||
# could also be one of
|
||||
#if len(plaintext) % BLOCK_SIZE != 0:
|
||||
# padded_text = plaintext.ljust((len(plaintext) / BLOCK_SIZE) + 1 * BLOCKSIZE), '\0')
|
||||
# -OR-
|
||||
#padded_text = plaintext.ljust((len(plaintext) + (BLOCK_SIZE - len(plaintext) % BLOCK_SIZE)), '\0')
|
||||
|
||||
r = rijndael.rijndael(padded_key, BLOCK_SIZE)
|
||||
|
||||
ciphertext = ''
|
||||
for start in range(0, len(padded_text), BLOCK_SIZE):
|
||||
ciphertext += r.encrypt(padded_text[start:start+BLOCK_SIZE])
|
||||
|
||||
encoded = base64.b64encode(ciphertext)
|
||||
|
||||
return encoded
|
||||
|
||||
|
||||
def set_radius_password(username, raw_password):
|
||||
"""
|
||||
Set password in Radius mysql backend.
|
||||
Create user if one with an NT-Password does not exist.
|
||||
"""
|
||||
pw_type = 'NT-Password'
|
||||
radius_defaults = {'attribute': pw_type, 'op': ':='}
|
||||
radius_user, created = Radcheck.objects.get_or_create(
|
||||
username=username,
|
||||
defaults=radius_defaults)
|
||||
def decrypt_rijndael(key, encoded):
|
||||
padded_key = key.ljust(KEY_SIZE, '\0')
|
||||
|
||||
# Some have clear text passwords!
|
||||
if radius_user.attribute != pw_type:
|
||||
radius_user.attribute = pw_type
|
||||
ciphertext = base64.b64decode(encoded)
|
||||
|
||||
radius_user.value = passwd.radius_create(raw_password)
|
||||
radius_user.save()
|
||||
r = rijndael.rijndael(padded_key, BLOCK_SIZE)
|
||||
|
||||
padded_text = ''
|
||||
for start in range(0, len(ciphertext), BLOCK_SIZE):
|
||||
padded_text += r.decrypt(ciphertext[start:start+BLOCK_SIZE])
|
||||
|
||||
def format_krb5_date(date):
|
||||
try:
|
||||
formatted_date = datetime.datetime.strptime(date, '%a %b %d %H:%M:%S %Z %Y')
|
||||
formatted_date = formatted_date.strftime('%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
formatted_date = 'never'
|
||||
return formatted_date
|
||||
plaintext = padded_text.split('\x00', 1)[0]
|
||||
return plaintext
|
||||
+53
-37
@@ -1,21 +1,22 @@
|
||||
# coding: utf-8
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.contrib.auth.forms import SetPasswordForm, AuthenticationForm
|
||||
from django.contrib.auth import login, logout as auth_logout
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
|
||||
from django.shortcuts import render_to_response, resolve_url, render
|
||||
from django.template import RequestContext
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.http import urlsafe_base64_decode
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
import json
|
||||
from inside.models import InsideUser
|
||||
from django.views.generic import View
|
||||
|
||||
from models import *
|
||||
import utils
|
||||
from inside.models import InsideUser
|
||||
from neuf_ldap.models import LdapGroup, LdapUser
|
||||
from neuf_userinfo.utils import decrypt_rijndael
|
||||
|
||||
|
||||
def index(request):
|
||||
@@ -35,6 +36,7 @@ def index(request):
|
||||
|
||||
@login_required
|
||||
def profile(request):
|
||||
# FIXME: Allmost identical to below
|
||||
username = request.user.username
|
||||
groups = request.user.groups.all()
|
||||
ldap_user = None
|
||||
@@ -63,38 +65,6 @@ def user_profile(request, username):
|
||||
return render_to_response('private/profile.html', locals(), context_instance=RequestContext(request))
|
||||
|
||||
|
||||
# TODO security
|
||||
def client_status(request):
|
||||
krb5_principal = utils.get_kerberos_principal(request.GET.get('username'))
|
||||
if krb5_principal:
|
||||
last_succ_auth = utils.format_krb5_date(krb5_principal['Last successful authentication'])
|
||||
status = {
|
||||
'active': True,
|
||||
'last_successful_auth': last_succ_auth,
|
||||
'last_modified': krb5_principal['Last modified']
|
||||
}
|
||||
else:
|
||||
status = {'active': False}
|
||||
return HttpResponse(json.dumps(status), content_type='application/javascript; charset=utf8')
|
||||
|
||||
|
||||
# TODO security
|
||||
def wireless_status(request):
|
||||
username = request.GET.get('username')
|
||||
try:
|
||||
radius_user = Radcheck.objects.get(username=username)
|
||||
except Radcheck.ObjectDoesNotExist:
|
||||
return HttpResponse(json.dumps({'active': False}), content_type='application/javascript; charset=utf8')
|
||||
|
||||
# get last authentication
|
||||
last_auth = Radpostauth.objects.filter(username__iexact=username, reply='Access-Accept').order_by('authdate')
|
||||
status = {'active': True, 'hash': radius_user.attribute}
|
||||
if len(last_auth) != 0:
|
||||
status['last_successful_auth'] = last_auth[0].authdate.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
return HttpResponse(json.dumps(status), content_type='application/javascript; charset=utf8')
|
||||
|
||||
|
||||
def logout(request):
|
||||
auth_logout(request)
|
||||
return render(request, 'registration/logout.html')
|
||||
@@ -148,3 +118,49 @@ def password_reset_confirm(request, uidb64=None, token=None,
|
||||
context.update(extra_context)
|
||||
|
||||
return TemplateResponse(request, template_name, context, current_app=current_app)
|
||||
|
||||
|
||||
class AddNewUserView(View):
|
||||
ATTRS = ['username', 'firstname', 'lastname', 'email', 'password', 'groups']
|
||||
|
||||
def get(self, request):
|
||||
"""
|
||||
- Create new user in LDAP (username, first_name, last_name, email)
|
||||
- add to groups (usergroup, dns-alle), it not exists, create
|
||||
- Set LDAP password
|
||||
- Create automount entry
|
||||
- Create kerberos principal and set password
|
||||
- Create homedir on wii
|
||||
- Set RADIUS password
|
||||
"""
|
||||
if not self._validate_api_key(request.GET.get('api_key', '')):
|
||||
return HttpResponse('Invalid api_key')
|
||||
if not self._validate_attributes(request):
|
||||
return HttpResponse('Missing at least one required attribute: '.format(', '.join(self.ATTRS)))
|
||||
|
||||
user_data = self._get_user(request.GET)
|
||||
|
||||
# TODO: you are here
|
||||
|
||||
result = {'yousir': 'are a scholar!'}
|
||||
return JsonResponse(result)
|
||||
|
||||
def _validate_api_key(self, api_key):
|
||||
if api_key == '' or api_key != settings.INSIDE_USERSYNC_API_KEY:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _validate_attributes(self, request):
|
||||
for a in self.ATTRS:
|
||||
if a not in request.GET:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _get_user(self, params):
|
||||
user = {a: params[a] for a in self.ATTRS}
|
||||
user['password'] = decrypt_rijndael(settings.INSIDE_USERSYNC_ENC_KEY, user['password'])
|
||||
user['groups'] = user['groups'].strip().split(',')
|
||||
|
||||
return user
|
||||
+5
-6
@@ -1,9 +1,8 @@
|
||||
Django==1.6.5
|
||||
Django==1.7.4
|
||||
django-auth-ldap==1.2.0
|
||||
django-extensions==1.3.8
|
||||
django-extensions==1.5.0
|
||||
django-ldapdb==0.3.2
|
||||
dj-static==0.0.5
|
||||
MySQL-python==1.2.5
|
||||
dj-static==0.0.6
|
||||
mysqlclient==1.3.5
|
||||
passlib==1.6.2
|
||||
python-ldap==2.4.15
|
||||
six==1.7.3
|
||||
South==1.0
|
||||
@@ -1,2 +1,5 @@
|
||||
Noen har bedt om at passordet for brukeren {{ user.username }} med epostadresse {{ email }} skal endres. Vennligst følg lenken nedenfor for å sette et nytt passord:
|
||||
{{ protocol }}://{{ domain }}{% url "neuf_userinfo.views.password_reset_confirm" uidb64=uid token=token %}
|
||||
{% comment %}
|
||||
ldap_username is actually just the Inside username [sic].
|
||||
{% endcomment %}Noen har bedt om at passordet for brukeren {{ user.ldap_username }} med epostadresse {{ email }} skal endres.<br>
|
||||
<br>
|
||||
<a href="{{ protocol }}://{{ domain }}{% url "neuf_userinfo.views.password_reset_confirm" uidb64=uid token=token %}">Klikk her for å sette et nytt passord</a>.
|
||||
@@ -0,0 +1,4 @@
|
||||
{% comment %}
|
||||
ldap_username is actually just the Inside username [sic].
|
||||
{% endcomment %}Noen har bedt om at passordet for brukeren {{ user.ldap_username }} med epostadresse {{ email }} skal endres. Vennligst følg lenken nedenfor for å sette et nytt passord:
|
||||
{{ protocol }}://{{ domain }}{% url "neuf_userinfo.views.password_reset_confirm" uidb64=uid token=token %}
|
||||
@@ -36,10 +36,12 @@ INSTALLED_APPS = (
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django_extensions',
|
||||
'south',
|
||||
)
|
||||
INSTALLED_APPS += (
|
||||
'neuf_userinfo',
|
||||
'neuf_ldap',
|
||||
'neuf_radius',
|
||||
'neuf_kerberos',
|
||||
'inside',
|
||||
)
|
||||
|
||||
@@ -194,6 +196,8 @@ KERBEROS_PASSWORD = ''
|
||||
|
||||
# Inside
|
||||
INSIDE_GROUPS_SYNC_DELETE = True # When user logs in, groups are synced (locally in Django), this deletes aswell
|
||||
INSIDE_USERSYNC_API_KEY = ''
|
||||
INSIDE_USERSYNC_ENC_KEY = ''
|
||||
|
||||
try:
|
||||
from local_settings import *
|
||||
|
||||
+14
-3
@@ -1,6 +1,8 @@
|
||||
from django.conf.urls import include, patterns, url
|
||||
from django.contrib import admin
|
||||
from neuf_userinfo.forms import NeufPasswordChangeForm, NeufSetPasswordForm, NeufPasswordResetForm
|
||||
from neuf_userinfo.views import AddNewUserView
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
import settings
|
||||
@@ -10,8 +12,8 @@ urlpatterns = patterns(
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
|
||||
url(r'^userstatus/client/$', 'neuf_userinfo.views.client_status'),
|
||||
url(r'^userstatus/wireless/$', 'neuf_userinfo.views.wireless_status'),
|
||||
url(r'^userstatus/client/$', 'neuf_kerberos.views.client_status'),
|
||||
url(r'^userstatus/wireless/$', 'neuf_radius.views.wireless_status'),
|
||||
url(r'^$', 'neuf_userinfo.views.index'),
|
||||
)
|
||||
|
||||
@@ -30,11 +32,20 @@ urlpatterns += patterns(
|
||||
|
||||
# Reset password (forgot password)
|
||||
url(r'^accounts/password/reset$', 'django.contrib.auth.views.password_reset',
|
||||
{'password_reset_form': NeufPasswordResetForm, 'from_email': settings.DEFAULT_FROM_EMAIL}),
|
||||
{'password_reset_form': NeufPasswordResetForm,
|
||||
'from_email': settings.DEFAULT_FROM_EMAIL,
|
||||
'email_template_name': 'registration/password_reset_email.txt',
|
||||
'html_email_template_name': 'registration/password_reset_email.html'}),
|
||||
url(r'^accounts/password/reset/done$', 'django.contrib.auth.views.password_reset_done', name='password_reset_done'),
|
||||
# Set password
|
||||
url(r'^accounts/password/reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
|
||||
'neuf_userinfo.views.password_reset_confirm',
|
||||
{'set_password_form': NeufSetPasswordForm}),
|
||||
url(r'^accounts/password/reset/complete$', 'django.contrib.auth.views.password_reset_complete', name='password_reset_complete'),
|
||||
)
|
||||
|
||||
# Rewrite of usersync.neuf.no
|
||||
urlpatterns += patterns(
|
||||
'',
|
||||
url(r'^usersync/$', AddNewUserView.as_view(), name='usersync'),
|
||||
)
|
||||
Reference in new issue
Block a user