From 79a14fca0209065234713ee7e9a90fe7c6f6914c Mon Sep 17 00:00:00 2001 From: Nikolai R Kristiansen Date: Sat, 14 Feb 2015 22:54:36 +0100 Subject: [PATCH] Major update on the way to less users in LDAP - Adds new app inside with models and moves related logic. - Working InsideAuthBackend with group sync - use passlib (<3) for MySQL hashes - Renames main app to neuf_userinfo - introduced local_settings.py --- .gitignore | 2 +- README | 3 +- {main => inside}/__init__.py | 0 inside/admin.py | 3 + inside/authentication.py | 78 +++ inside/models.py | 471 ++++++++++++++++++ inside/tests.py | 3 + inside/utils.py | 39 ++ inside/views.py | 3 + main/authentication.py | 77 --- main/tests.py | 23 - main/utils.py | 102 ---- neuf_userinfo/__init__.py | 0 {main => neuf_userinfo}/admin.py | 4 +- {main => neuf_userinfo}/backends.py | 2 +- {main => neuf_userinfo}/forms.py | 45 +- {main => neuf_userinfo}/models.py | 22 +- {main => neuf_userinfo}/passwd.py | 3 + {main => neuf_userinfo}/router.py | 2 +- .../static}/js/bootstrap-collapse.js | 0 .../static}/js/bootstrap-popover.js | 0 .../static}/js/bootstrap-tooltip.js | 0 .../static}/js/jquery-1.7.1.min.js | 0 .../static}/style/config.rb | 0 .../static}/style/img/DNSlogo.png | Bin .../static}/style/img/check.png | Bin .../static}/style/img/edb_brick.png | Bin .../static}/style/img/exclamation_mark.png | Bin .../style/img/glyphicons-halflings-white.png | Bin .../style/img/glyphicons-halflings.png | Bin .../static}/style/sass/ie.scss | 0 .../static}/style/sass/print.scss | 0 .../static}/style/sass/screen.scss | 0 .../static}/style/stylesheets/ie.css | 0 .../static}/style/stylesheets/print.css | 0 .../static}/style/stylesheets/screen.css | 0 neuf_userinfo/tests.py | 1 + neuf_userinfo/utils.py | 78 +++ {main => neuf_userinfo}/validators.py | 0 {main => neuf_userinfo}/views.py | 64 +-- scripts/push_active_to_wordpress.py | 15 +- templates/nav.html | 2 +- templates/registration/logout.html | 2 +- .../registration/password_reset_email.html | 2 +- test/testdata.ldif | 4 +- .../{settings-sample.py => settings.py} | 68 ++- userinfosite/urls.py | 39 +- 47 files changed, 828 insertions(+), 329 deletions(-) rename {main => inside}/__init__.py (100%) create mode 100644 inside/admin.py create mode 100644 inside/authentication.py create mode 100644 inside/models.py create mode 100644 inside/tests.py create mode 100644 inside/utils.py create mode 100644 inside/views.py delete mode 100644 main/authentication.py delete mode 100644 main/tests.py delete mode 100644 main/utils.py create mode 100644 neuf_userinfo/__init__.py rename {main => neuf_userinfo}/admin.py (89%) rename {main => neuf_userinfo}/backends.py (93%) rename {main => neuf_userinfo}/forms.py (74%) rename {main => neuf_userinfo}/models.py (95%) rename {main => neuf_userinfo}/passwd.py (99%) rename {main => neuf_userinfo}/router.py (100%) rename {static => neuf_userinfo/static}/js/bootstrap-collapse.js (100%) rename {static => neuf_userinfo/static}/js/bootstrap-popover.js (100%) rename {static => neuf_userinfo/static}/js/bootstrap-tooltip.js (100%) rename {static => neuf_userinfo/static}/js/jquery-1.7.1.min.js (100%) rename {static => neuf_userinfo/static}/style/config.rb (100%) rename {static => neuf_userinfo/static}/style/img/DNSlogo.png (100%) rename {static => neuf_userinfo/static}/style/img/check.png (100%) rename {static => neuf_userinfo/static}/style/img/edb_brick.png (100%) rename {static => neuf_userinfo/static}/style/img/exclamation_mark.png (100%) rename {static => neuf_userinfo/static}/style/img/glyphicons-halflings-white.png (100%) rename {static => neuf_userinfo/static}/style/img/glyphicons-halflings.png (100%) rename {static => neuf_userinfo/static}/style/sass/ie.scss (100%) rename {static => neuf_userinfo/static}/style/sass/print.scss (100%) rename {static => neuf_userinfo/static}/style/sass/screen.scss (100%) rename {static => neuf_userinfo/static}/style/stylesheets/ie.css (100%) rename {static => neuf_userinfo/static}/style/stylesheets/print.css (100%) rename {static => neuf_userinfo/static}/style/stylesheets/screen.css (100%) create mode 100644 neuf_userinfo/tests.py create mode 100644 neuf_userinfo/utils.py rename {main => neuf_userinfo}/validators.py (100%) rename {main => neuf_userinfo}/views.py (75%) rename userinfosite/{settings-sample.py => settings.py} (74%) diff --git a/.gitignore b/.gitignore index b208663..32004b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ *.swp *.pyc -settings.py db.sqlite3 static/style/.sass-cache/ +local_settings.py venv .idea diff --git a/README b/README index 3e59389..568355a 100644 --- a/README +++ b/README @@ -12,9 +12,10 @@ ## LDAP sudo docker run -e LDAP_DOMAIN=neuf.no -e LDAP_ORGANISATION="Neuf" -e LDAP_ROOTPASS="toor" -p 389:389 -d osixia/openldap - ldapadd -D "cn=admin,dc=neuf,dc=no" -w "toor" -f testdata.ldif + ldapadd -D "cn=admin,dc=neuf,dc=no" -w "toor" -f test/testdata.ldif # Verify import ldapsearch -x -b dc=neuf,dc=no + # Login with test@example.com:test # External dependencies: - LDAP: Read the tutorial on [Howto setup a LDAP server with Docker](https://edb.neuf.no/wiki/index.php?title=Howto_setup_a_LDAP_server_with_Docker) diff --git a/main/__init__.py b/inside/__init__.py similarity index 100% rename from main/__init__.py rename to inside/__init__.py diff --git a/inside/admin.py b/inside/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/inside/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/inside/authentication.py b/inside/authentication.py new file mode 100644 index 0000000..945e387 --- /dev/null +++ b/inside/authentication.py @@ -0,0 +1,78 @@ +from django.conf import settings +from django.contrib.auth.models import User, Group +from django.db.models import Q +import logging + +from inside import utils +from inside.models import InsideUser, InsideGroup, AuthLog + +logger = logging.getLogger(__name__) + + +class InsideBackend(object): + """ + Authenticate against the inside database + """ + + def authenticate(self, username=None, password=None): + password_hash = utils.mysql_create(password) + inside_user = InsideUser.objects.filter( + Q(password=password_hash) & (Q(ldap_username=username) | Q(email=username)) + ) + # AuthLog.objects.create() + + if not len(inside_user) == 1: + return None + + inside_user = inside_user[0] + + try: + user = User.objects.get(username=inside_user.ldap_username) + except User.DoesNotExist: + # Create a new user. + user = User(username=inside_user.ldap_username, password=password) + + # Sync detail and groups from Inside to local Django install (for display and permissions) + user = self._update_user_details(user, inside_user) + user.save() + + user = self._update_groups(user, inside_user) + + return user + + def get_user(self, user_id): + try: + return User.objects.get(pk=user_id) + except User.DoesNotExist: + return None + + def _update_user_details(self, user, inside_user): + sync_attrs = { + 'firstname': 'first_name', + 'lastname': 'last_name', + 'email': 'email' + } + for inside_attr, local_attr in sync_attrs.iteritems(): + setattr(user, local_attr, getattr(inside_user, inside_attr)) + + return user + + def _update_groups(self, user, inside_user): + existing_groups = list(user.groups.all()) + inside_groups = InsideGroup.objects.filter(user_rels__user=inside_user).exclude(posix_group='') + inside_groups = inside_groups.values_list('posix_group', flat=True) + + # Add new relationships + for group in inside_groups: + g, created = Group.objects.get_or_create(name=group) + user.groups.add(g) + + if not settings.INSIDE_GROUPS_SYNC_DELETE: + return user + + # Remove old relationships + for g in existing_groups: + if g.name not in inside_groups: + user.groups.remove(g) + + return user diff --git a/inside/models.py b/inside/models.py new file mode 100644 index 0000000..f4e2c83 --- /dev/null +++ b/inside/models.py @@ -0,0 +1,471 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from django.db import models + + +class Action(models.Model): + connection_name = 'inside' + + name = models.CharField(unique=True, max_length=50) + text = models.CharField(max_length=255) + + class Meta: + managed = False + db_table = 'din_action' + + +class ActionGroupRelationship(models.Model): + connection_name = 'inside' + + action = models.ForeignKey(Action) + group = models.ForeignKey('inside.InsideGroup') + + class Meta: + managed = False + db_table = 'din_actiongrouprelationship' + + +class BugReport(models.Model): + connection_name = 'inside' + + date = models.DateTimeField() + type = models.CharField(max_length=45) + filename = models.CharField(max_length=45) + query = models.TextField() + referer = models.TextField() + useragent = models.CharField(max_length=255) + user_id = models.IntegerField(blank=True, null=True) + comment = models.TextField() + title = models.CharField(max_length=50) + active = models.IntegerField() + status_comment = models.TextField() + + class Meta: + managed = False + db_table = 'din_bugreport' + + +class Division(models.Model): + connection_name = 'inside' + + name = models.CharField(unique=True, max_length=50) + nicename = models.CharField(max_length=20) + text = models.TextField() + phone = models.CharField(max_length=8) + email = models.CharField(max_length=120) + office = models.CharField(max_length=3) + user_id_contact = models.ForeignKey('inside.InsideUser', db_column='user_id_contact') + url = models.CharField(max_length=120) + divisioncategory = models.ForeignKey('inside.DivisionCategory') + picture = models.CharField(max_length=7) + hidden = models.IntegerField() + updated = models.DateTimeField(blank=True, null=True) + + class Meta: + managed = False + db_table = 'din_division' + + +class DivisionCategory(models.Model): + connection_name = 'inside' + + title = models.CharField(unique=True, max_length=50) + text = models.TextField() + + class Meta: + managed = False + db_table = 'din_divisioncategory' + + +class Document(models.Model): + connection_name = 'inside' + + type = models.CharField(max_length=60) + name = models.CharField(max_length=120) + size = models.BigIntegerField() + date = models.DateTimeField() + documentcategory = models.ForeignKey('inside.DocumentCategory') + tags = models.CharField(max_length=255) + + class Meta: + managed = False + db_table = 'din_document' + + +class DocumentCategory(models.Model): + connection_name = 'inside' + + title = models.CharField(unique=True, max_length=50) + text = models.TextField() + + class Meta: + managed = False + db_table = 'din_documentcategory' + + +class DocumentData(models.Model): + connection_name = 'inside' + + data = models.TextField() + document = models.ForeignKey(Document) + + class Meta: + managed = False + db_table = 'din_documentdata' + + +class InsideGroup(models.Model): + connection_name = 'inside' + + name = models.CharField(unique=True, max_length=50) + text = models.TextField() + division = models.ForeignKey(Division, blank=True, null=True) + admin = models.CharField(max_length=1) + mailinglist = models.CharField(max_length=50) + posix_group = models.CharField(max_length=128) + + class Meta: + managed = False + db_table = 'din_group' + + +class MemberCard(models.Model): + connection_name = 'inside' + + userid = models.IntegerField(db_column='userId', blank=True, null=True) # Field name made lowercase. + ordered = models.DateTimeField(blank=True, null=True) + produced = models.DateTimeField(blank=True, null=True) + delivered = models.DateTimeField(blank=True, null=True) + active = models.IntegerField() + + class Meta: + managed = False + db_table = 'din_membercard' + + +class Order(models.Model): + connection_name = 'inside' + + user = models.ForeignKey('inside.InsideUser') + timestamp = models.DateTimeField() + order_status = models.ForeignKey('inside.OrderStatus') + order_deliverystatus_id = models.IntegerField() + comment = models.TextField() + + class Meta: + managed = False + db_table = 'din_order' + + +class OrderDeliveryStatus(models.Model): + connection_name = 'inside' + + order_deliverystatus_id = models.IntegerField(primary_key=True) + value = models.CharField(max_length=45) + + class Meta: + managed = False + db_table = 'din_order_deliverystatus' + + +class OrderItem(models.Model): + connection_name = 'inside' + + product = models.ForeignKey('inside.Product') + quantity = models.IntegerField() + order = models.ForeignKey(Order) + discount = models.IntegerField() + comment = models.TextField() + + class Meta: + managed = False + db_table = 'din_order_item' + + +class OrderStatus(models.Model): + connection_name = 'inside' + + value = models.CharField(max_length=45) + + class Meta: + managed = False + db_table = 'din_order_status' + + +class Position(models.Model): + connection_name = 'inside' + + name = models.CharField(max_length=50) + division = models.ForeignKey(Division) + text = models.TextField() + + class Meta: + managed = False + db_table = 'din_position' + + +class InsidePostalCode(models.Model): + connection_name = 'inside' + + postnummer = models.CharField(primary_key=True, max_length=4) + poststed = models.CharField(max_length=32) + kommunenr = models.CharField(max_length=4) + kommunenavn = models.CharField(max_length=30) + kategori = models.CharField(max_length=1) + + class Meta: + managed = False + db_table = 'din_postnummer' + + +class Product(models.Model): + connection_name = 'inside' + + title = models.CharField(max_length=255) + description = models.TextField() + price = models.IntegerField() + allow_comment = models.IntegerField() + display_in_shop = models.IntegerField() + picture = models.CharField(max_length=10) + + class Meta: + managed = False + db_table = 'din_product' + + +class InsideSettings(models.Model): + connection_name = 'inside' + + name = models.CharField(primary_key=True, max_length=45) + value = models.CharField(max_length=45) + + class Meta: + managed = False + db_table = 'din_settings' + + +class SmsReceived(models.Model): + connection_name = 'inside' + + smsid = models.IntegerField(primary_key=True) + userid = models.IntegerField(blank=True, null=True) + gsm = models.CharField(max_length=15) + codeword = models.CharField(max_length=20) + message = models.CharField(max_length=160) + operator = models.CharField(max_length=20) + shortno = models.CharField(max_length=15) + action = models.CharField(max_length=30) + ip = models.CharField(db_column='IP', max_length=15) # Field name made lowercase. + date = models.DateTimeField() + simulation = models.IntegerField() + + class Meta: + managed = False + db_table = 'din_sms_received' + + +class SmsSent(models.Model): + connection_name = 'inside' + + smsid = models.IntegerField(primary_key=True) + response_to = models.IntegerField() + msgid = models.IntegerField() + sender = models.CharField(max_length=15) + receiver = models.CharField(max_length=15) + countrycode = models.IntegerField() + message = models.CharField(max_length=160) + operator = models.CharField(max_length=20, blank=True) + codeword = models.CharField(max_length=20, blank=True) + billing_price = models.IntegerField() + use_dlr = models.IntegerField() + date = models.DateTimeField() + simulation = models.IntegerField() + activation_code = models.CharField(max_length=10, blank=True) + + class Meta: + managed = False + db_table = 'din_sms_sent' + + +class Transaction(models.Model): + connection_name = 'inside' + + id_string = models.CharField(max_length=25) + user = models.ForeignKey('inside.InsideUser') + order = models.ForeignKey(Order) + timestamp = models.DateTimeField() + status = models.CharField(max_length=8, blank=True) + amount = models.IntegerField() + + class Meta: + managed = False + db_table = 'din_transaction' + + +class TransactionCheck(models.Model): + connection_name = 'inside' + + order_id = models.IntegerField(primary_key=True) + inserted = models.DateTimeField() + order_ref = models.CharField(max_length=255) + order_ref2 = models.CharField(max_length=255, blank=True) + + class Meta: + managed = False + db_table = 'din_transaction_check' + + +class UsedCardNo(models.Model): + connection_name = 'inside' + + date = models.DateTimeField() + + class Meta: + managed = False + db_table = 'din_usedcardno' + + +class InsideUser(models.Model): + connection_name = 'inside' + + cardno = models.IntegerField(unique=True, blank=True, null=True) + username = models.CharField(unique=True, max_length=12) + password = models.CharField(max_length=50) + firstname = models.CharField(max_length=120) + lastname = models.CharField(max_length=120) + addresstype = models.CharField(max_length=3) + valid_address = models.IntegerField() + email = models.CharField(unique=True, max_length=120) + birthdate = models.DateField() + placeofstudy = models.IntegerField(db_column='placeOfStudy') # Field name made lowercase. + passwordreset = models.CharField(db_column='passwordReset', max_length=1) # Field name made lowercase. + expires = models.DateField(blank=True, null=True) + division_id_request = models.ForeignKey(Division, db_column='division_id_request', blank=True, null=True) + cardproduced = models.IntegerField(db_column='cardProduced') # Field name made lowercase. + carddelivered = models.IntegerField(db_column='cardDelivered') # Field name made lowercase. + laststicker = models.CharField(db_column='lastSticker', max_length=9) # Field name made lowercase. + migrated = models.DateTimeField(blank=True, null=True) + ldap_username = models.CharField(unique=True, max_length=20, blank=True) + source = models.CharField(max_length=255) + registration_status = models.CharField(max_length=255) + created = models.DateTimeField(blank=True, null=True) + membership_trial = models.CharField(max_length=255) + + def __unicode__(self): + if not self.ldap_username: + return 'User {}'.format(self.pk) + + return '{} ({})'.format(self.ldap_username, self.pk) + + class Meta: + managed = False + db_table = 'din_user' + + +class UserAddressInt(models.Model): + connection_name = 'inside' + + user = models.ForeignKey('inside.InsideUser', primary_key=True) + street = models.CharField(max_length=255) + zipcode = models.CharField(max_length=10) + city = models.CharField(max_length=50) + state = models.CharField(max_length=50) + country = models.CharField(max_length=50) + + class Meta: + managed = False + db_table = 'din_useraddressint' + + +class UserAddressNo(models.Model): + connection_name = 'inside' + + user = models.ForeignKey('inside.InsideUser', primary_key=True) + street = models.CharField(max_length=255) + zipcode = models.ForeignKey('inside.PostalCode', db_column='zipcode') + + class Meta: + managed = False + db_table = 'din_useraddressno' + + +class UserGroupRelationship(models.Model): + connection_name = 'inside' + + user = models.ForeignKey('inside.InsideUser', related_name='group_rels') + group = models.ForeignKey('inside.InsideGroup', related_name='user_rels') + + class Meta: + managed = False + db_table = 'din_usergrouprelationship' + + +class UserPhoneNumber(models.Model): + connection_name = 'inside' + + user = models.ForeignKey('inside.InsideUser', primary_key=True) + number = models.CharField(max_length=16) + validated = models.IntegerField() + + class Meta: + managed = False + db_table = 'din_userphonenumber' + + +class UserUpdate(models.Model): + connection_name = 'inside' + + date = models.DateTimeField(auto_now_add=True) + user_updated = models.ForeignKey( + 'inside.InsideUser', + db_column='user_id_updated', + related_name='updates') + comment = models.CharField(max_length=255) + user_updated_by = models.ForeignKey( + 'inside.InsideUser', + db_column='user_id_updated_by', + blank=True, + null=True, + related_name='admin_updates') + + class Meta: + managed = False + db_table = 'din_userupdate' + + +class AuthLog(models.Model): + connection_name = 'inside' + + username = models.CharField(max_length=80) + password = models.IntegerField() + time = models.DateTimeField() + error = models.CharField(max_length=100) + + class Meta: + managed = False + db_table = 'inside_auth_log' + + +class PostalCode(models.Model): + connection_name = 'inside' + + postnummer = models.CharField(primary_key=True, max_length=4) + poststed = models.CharField(max_length=32) + kommunenr = models.CharField(max_length=4) + kommunenavn = models.CharField(max_length=30) + kategori = models.CharField(max_length=1) + + class Meta: + managed = False + db_table = 'postnummer' + + +class PlaceOfStudy(models.Model): + connection_name = 'inside' + + navn = models.CharField(unique=True, max_length=120) + + class Meta: + managed = False + db_table = 'studiesteder' diff --git a/inside/tests.py b/inside/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/inside/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/inside/utils.py b/inside/utils.py new file mode 100644 index 0000000..0878eed --- /dev/null +++ b/inside/utils.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from django.db import connections +import logging +from passlib.hash import mysql41 + +from inside.models import InsideUser, UserUpdate + + +logger = logging.getLogger(__name__) + + +def mysql_create(raw_password): + return mysql41.encrypt(raw_password) + + +def log_inside_userupdate(username, message): + try: + user = InsideUser.objects.get(ldap_username=username) + except InsideUser.DoesNotExist: + logger.warning('Could not log update in Inside for user with username {}'.format(username)) + return + + UserUpdate.objects.create(user_updated=user, comment=message, user_updated_by=user) + + +def set_inside_password(username, raw_password): + hashed_password = mysql_create(raw_password) + + try: + user = InsideUser.objects.get(ldap_username=username) + except InsideUser.DoesNotExist: + logger.warning('Could not set password in Inside for user with username {}'.format(username)) + return + + user.password = hashed_password + user.save() + + log_inside_userupdate(username, "Satt passord på nytt via brukerinfo.neuf.no.") diff --git a/inside/views.py b/inside/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/inside/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/main/authentication.py b/main/authentication.py deleted file mode 100644 index fa4c237..0000000 --- a/main/authentication.py +++ /dev/null @@ -1,77 +0,0 @@ -from django.contrib.auth.models import User, Group -from django.db import connections - - -class InsideBackend(object): - """ - Authenticate against the inside database - """ - - def authenticate(self, username=None, password=None): - row = None - cur = connections['inside'].cursor() - try: - cur.execute("SELECT id,ldap_username,firstname,lastname,email FROM din_user WHERE password=PASSWORD(%s) AND (ldap_username=%s OR email=%s)", [password, username, username]) - row = cur.fetchone() - except Exception as e: - # FIXME errorhandling - print "Auth ERROR: {}".format(e) - raise e - finally: - cur.close() - - if row: - username = row[1] - try: - user = User.objects.get(username=username) - except User.DoesNotExist: - # Create a new user. - user = User(username=username, password=password) - - user = self._update_user_details(user, row) # Saves user object - user = self._update_groups(user, row) - - return user - - return None - - def get_user(self, user_id): - try: - return User.objects.get(pk=user_id) - except User.DoesNotExist: - return None - - def _update_user_details(self, user, row): - uid, username, first_name, last_name, email = row - user.first_name = first_name - user.last_name = last_name - user.email = email - user.save() - return user - - def _update_groups(self, user, user_row): - groups = None - cur = connections['inside'].cursor() - user_id = user_row[0] - - existing_groups = list(user.groups.all()) - try: - cur.execute("SELECT g.posix_group FROM din_user AS u LEFT JOIN din_usergrouprelationship AS ugr ON u.id = ugr.user_id LEFT JOIN din_group AS g ON ugr.group_id = g.id WHERE u.id = %s AND g.posix_group != ''", (int(user_row[0]),)) - groups = map(lambda x: x[0], cur.fetchall()) - for group in groups: - g, created = Group.objects.get_or_create(name=group) - user.groups.add(g) - - # Remove old relationships - # TODO: if setting is set. not tested - for g in existing_groups: - if g.name not in groups: - user.groups.remove(g) - - except Exception as e: - # FIXME errorhandling - print "ERROR: {}".format(e) - finally: - cur.close() - - return user diff --git a/main/tests.py b/main/tests.py deleted file mode 100644 index 2247054..0000000 --- a/main/tests.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -This file demonstrates two different styles of tests (one doctest and one -unittest). These will both pass when you run "manage.py test". - -Replace these with more appropriate tests for your application. -""" - -from django.test import TestCase - -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.failUnlessEqual(1 + 1, 2) - -__test__ = {"doctest": """ -Another way to test that 1 + 1 is equal to 2. - ->>> 1 + 1 == 2 -True -"""} - diff --git a/main/utils.py b/main/utils.py deleted file mode 100644 index 5c67947..0000000 --- a/main/utils.py +++ /dev/null @@ -1,102 +0,0 @@ -# coding: utf-8 -from django.conf import settings -from django.core.exceptions import ObjectDoesNotExist -from subprocess import Popen, PIPE -import datetime -import re - -from models import Radcheck -import passwd - -def get_kerberos_principal(username): - principal = u"{0}@{1}".format(username, settings.KERBEROS_REALM) - kadmin_query = u" -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) - return None - else: - 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 = u"{0}@{1}".format(username, settings.KERBEROS_REALM) - kadmin_query = u" -p {0} -w {1} -q 'change_password -pw {2} {3}'".format(settings.KERBEROS_PASSWORD_CHANGING_PRINCIPAL, - settings.KERBEROS_PASSWORD, - raw_password, - principal) - # Note: Running this shell command could need 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) - return False - else: - return True - -def set_radius_password(username, raw_password): - """ - Set password in Radius mysql backend. - Create user if one with an NT-Password does not exist. - """ - try: - radius_user = Radcheck.objects.get(username=username) - except ObjectDoesNotExist: - radius_user = Radcheck(username=username, attribute='NT-Password', op=':=') - - # Some have clear text passwords! - if radius_user.attribute != 'NT-Password': - radius_user.attribute = 'NT-Password' - - radius_user.value = passwd.radius_create(raw_password) - radius_user.save() - return True - -def log_inside_userupdate(username, message): - from django.db import connections - c = connections['inside'].cursor() - - # TODO errorhandling - try: - c.execute("SELECT id FROM din_user WHERE username= %s", [username]) - row = c.fetchone() - if row is not None: - user_id = row[0] - c.execute( - "INSERT INTO din_userupdate (date, user_id_updated, comment, user_id_updated_by) VALUES (NOW(), %s, %s, %s)", - [user_id, message, user_id]) - finally: - c.close() - - return True - -def set_inside_password(username, raw_password): - from django.db import connections - c = connections['inside'].cursor() - - # TODO errorhandling - try: - c.execute("UPDATE din_user SET password=PASSWORD(%s) WHERE username=%s", [raw_password, username]) - finally: - c.close() - - log_inside_userupdate(username, u"Satt passord på nytt via brukerinfo.neuf.no.") - - return True - -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 diff --git a/neuf_userinfo/__init__.py b/neuf_userinfo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main/admin.py b/neuf_userinfo/admin.py similarity index 89% rename from main/admin.py rename to neuf_userinfo/admin.py index 4bb8507..b1b498f 100644 --- a/main/admin.py +++ b/neuf_userinfo/admin.py @@ -1,11 +1,13 @@ from django.contrib import admin -from main.models import LdapGroup, LdapUser +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'] diff --git a/main/backends.py b/neuf_userinfo/backends.py similarity index 93% rename from main/backends.py rename to neuf_userinfo/backends.py index 860e1cd..0600b98 100644 --- a/main/backends.py +++ b/neuf_userinfo/backends.py @@ -1,4 +1,4 @@ -from django_auth_ldap.backend import LDAPBackend, _LDAPUser +from django_auth_ldap.backend import LDAPBackend class LDAPUsernameBackend(LDAPBackend): diff --git a/main/forms.py b/neuf_userinfo/forms.py similarity index 74% rename from main/forms.py rename to neuf_userinfo/forms.py index ed6dbef..ed94db4 100644 --- a/main/forms.py +++ b/neuf_userinfo/forms.py @@ -1,4 +1,5 @@ # coding: utf-8 +from __future__ import unicode_literals from django.contrib.auth.forms import SetPasswordForm, PasswordResetForm from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.models import get_current_site @@ -9,50 +10,53 @@ 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 models import * from validators import PasswordValidator -import utils +from neuf_userinfo.utils import set_kerberos_password, set_radius_password -class LDAPSetPasswordForm(SetPasswordForm): + +class NeufSetPasswordForm(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']) - # set inside password - set_inside = utils.set_inside_password(self.user.username, self.cleaned_data['new_password1']) + # Membership database + set_inside_password(self.user.username, self.cleaned_data['new_password1']) - # Lookup the Ldap user with the identical username (1-to-1). + # Active services + set_kerberos_password(self.user.username, self.cleaned_data['new_password1']) + set_radius_password(self.user.username, self.cleaned_data['new_password1']) + + # LDAP: Lookup the Ldap user with the identical username (1-to-1). self.user = LdapUser.objects.get(username=self.user.username) - - # set ldap password self.user.set_password(self.cleaned_data['new_password1']) if commit: self.user.save() - # TODO: Log the result of the above. - return self.user def clean_new_password1(self): raw_password = self.cleaned_data.get('new_password1') - # Validation here + MinLengthValidator(8)(raw_password) PasswordValidator(raw_password) + return raw_password -class LDAPPasswordChangeForm(LDAPSetPasswordForm): + +class NeufPasswordChangeForm(NeufSetPasswordForm): pass -class LDAPPasswordResetForm(PasswordResetForm): + +class NeufPasswordResetForm(PasswordResetForm): + EMAIL_ERROR_MSG = "That e-mail address doesn't have an associated user account. Are you sure you've registered?" + def clean_email(self): email = self.cleaned_data["email"].lower() - self.users_cache = LdapUser.objects.filter(email=email) + self.users_cache = InsideUser.objects.filter(email=email) if len(self.users_cache) == 0: - raise forms.ValidationError(_("That e-mail address doesn't have an associated user account. Are you sure you've registered?")) + raise forms.ValidationError(_(self.EMAIL_ERROR_MSG)) return email def save(self, domain_override=None, @@ -88,8 +92,7 @@ class LDAPPasswordResetForm(PasswordResetForm): 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 ? + # Workaround for http://bugs.python.org/issue1368247 email = email.encode('utf-8') send_mail(subject, email, from_email, [user.email]) - diff --git a/main/models.py b/neuf_userinfo/models.py similarity index 95% rename from main/models.py rename to neuf_userinfo/models.py index e5355a4..1a288d1 100644 --- a/main/models.py +++ b/neuf_userinfo/models.py @@ -1,8 +1,9 @@ -from ldapdb.models.fields import CharField, ImageField, IntegerField, ListField +import datetime from django.db import models import ldapdb.models +from ldapdb.models.fields import CharField, ImageField, IntegerField, ListField import passwd -import datetime + class LdapUser(ldapdb.models.Model): """ @@ -12,7 +13,7 @@ class LdapUser(ldapdb.models.Model): connection_name = 'ldap' # LDAP meta-data - base_dn = "ou=People,dc=neuf,dc=no" # CONFIG + base_dn = "ou=People,dc=neuf,dc=no" # FXIME: CONFIG object_classes = ['inetOrgPerson', 'posixAccount', 'shadowAccount'] # inetOrgPerson @@ -25,9 +26,9 @@ class LdapUser(ldapdb.models.Model): photo = ImageField(db_column='jpegPhoto') # posixAccount - id = IntegerField(db_column='uidNumber', unique=True) # referenced in reset password form + id = IntegerField(db_column='uidNumber', unique=True) # referenced in reset password form group = IntegerField(db_column='gidNumber') - gecos = CharField(db_column='gecos') + 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) @@ -55,6 +56,7 @@ class LdapUser(ldapdb.models.Model): def __unicode__(self): return self.full_name + class LdapGroup(ldapdb.models.Model): """ Class for representing an LDAP group entry. @@ -63,7 +65,7 @@ class LdapGroup(ldapdb.models.Model): connection_name = 'ldap' # LDAP meta-data - base_dn = "ou=Groups,dc=neuf,dc=no" # CONFIG + base_dn = "ou=Groups,dc=neuf,dc=no" # CONFIG object_classes = ['posixGroup'] # posixGroup attributes @@ -116,9 +118,11 @@ class Radacct(models.Model): 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' @@ -126,9 +130,11 @@ class Radcheck(models.Model): 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' @@ -140,6 +146,7 @@ class Radgroupcheck(models.Model): class Meta: db_table = u'radgroupcheck' + class Radgroupreply(models.Model): connection_name = 'radius' @@ -151,6 +158,7 @@ class Radgroupreply(models.Model): class Meta: db_table = u'radgroupreply' + class Radpostauth(models.Model): connection_name = 'radius' @@ -162,6 +170,7 @@ class Radpostauth(models.Model): class Meta: db_table = u'radpostauth' + class Radreply(models.Model): connection_name = 'radius' @@ -173,6 +182,7 @@ class Radreply(models.Model): class Meta: db_table = u'radreply' + class Radusergroup(models.Model): connection_name = 'radius' diff --git a/main/passwd.py b/neuf_userinfo/passwd.py similarity index 99% rename from main/passwd.py rename to neuf_userinfo/passwd.py index d9c5ab2..88a4d75 100644 --- a/main/passwd.py +++ b/neuf_userinfo/passwd.py @@ -7,6 +7,7 @@ from base64 import decodestring as decode # - 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 salt = os.urandom(4) @@ -14,6 +15,7 @@ def ldap_create(raw_password, hash_type='ssha'): h.update(salt) return "{SSHA}" + encode(h.digest() + salt)[:-1] + def ldap_validate(raw_password, challenge_password): # challenge_password is hash from db # raw_password is the cleartext password. @@ -24,6 +26,7 @@ def ldap_validate(raw_password, challenge_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() diff --git a/main/router.py b/neuf_userinfo/router.py similarity index 100% rename from main/router.py rename to neuf_userinfo/router.py index fe18f74..9d7c7fb 100644 --- a/main/router.py +++ b/neuf_userinfo/router.py @@ -1,6 +1,7 @@ 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. @@ -28,4 +29,3 @@ class Router(object): if is_radius_model(model): return model.connection_name return None - diff --git a/static/js/bootstrap-collapse.js b/neuf_userinfo/static/js/bootstrap-collapse.js similarity index 100% rename from static/js/bootstrap-collapse.js rename to neuf_userinfo/static/js/bootstrap-collapse.js diff --git a/static/js/bootstrap-popover.js b/neuf_userinfo/static/js/bootstrap-popover.js similarity index 100% rename from static/js/bootstrap-popover.js rename to neuf_userinfo/static/js/bootstrap-popover.js diff --git a/static/js/bootstrap-tooltip.js b/neuf_userinfo/static/js/bootstrap-tooltip.js similarity index 100% rename from static/js/bootstrap-tooltip.js rename to neuf_userinfo/static/js/bootstrap-tooltip.js diff --git a/static/js/jquery-1.7.1.min.js b/neuf_userinfo/static/js/jquery-1.7.1.min.js similarity index 100% rename from static/js/jquery-1.7.1.min.js rename to neuf_userinfo/static/js/jquery-1.7.1.min.js diff --git a/static/style/config.rb b/neuf_userinfo/static/style/config.rb similarity index 100% rename from static/style/config.rb rename to neuf_userinfo/static/style/config.rb diff --git a/static/style/img/DNSlogo.png b/neuf_userinfo/static/style/img/DNSlogo.png similarity index 100% rename from static/style/img/DNSlogo.png rename to neuf_userinfo/static/style/img/DNSlogo.png diff --git a/static/style/img/check.png b/neuf_userinfo/static/style/img/check.png similarity index 100% rename from static/style/img/check.png rename to neuf_userinfo/static/style/img/check.png diff --git a/static/style/img/edb_brick.png b/neuf_userinfo/static/style/img/edb_brick.png similarity index 100% rename from static/style/img/edb_brick.png rename to neuf_userinfo/static/style/img/edb_brick.png diff --git a/static/style/img/exclamation_mark.png b/neuf_userinfo/static/style/img/exclamation_mark.png similarity index 100% rename from static/style/img/exclamation_mark.png rename to neuf_userinfo/static/style/img/exclamation_mark.png diff --git a/static/style/img/glyphicons-halflings-white.png b/neuf_userinfo/static/style/img/glyphicons-halflings-white.png similarity index 100% rename from static/style/img/glyphicons-halflings-white.png rename to neuf_userinfo/static/style/img/glyphicons-halflings-white.png diff --git a/static/style/img/glyphicons-halflings.png b/neuf_userinfo/static/style/img/glyphicons-halflings.png similarity index 100% rename from static/style/img/glyphicons-halflings.png rename to neuf_userinfo/static/style/img/glyphicons-halflings.png diff --git a/static/style/sass/ie.scss b/neuf_userinfo/static/style/sass/ie.scss similarity index 100% rename from static/style/sass/ie.scss rename to neuf_userinfo/static/style/sass/ie.scss diff --git a/static/style/sass/print.scss b/neuf_userinfo/static/style/sass/print.scss similarity index 100% rename from static/style/sass/print.scss rename to neuf_userinfo/static/style/sass/print.scss diff --git a/static/style/sass/screen.scss b/neuf_userinfo/static/style/sass/screen.scss similarity index 100% rename from static/style/sass/screen.scss rename to neuf_userinfo/static/style/sass/screen.scss diff --git a/static/style/stylesheets/ie.css b/neuf_userinfo/static/style/stylesheets/ie.css similarity index 100% rename from static/style/stylesheets/ie.css rename to neuf_userinfo/static/style/stylesheets/ie.css diff --git a/static/style/stylesheets/print.css b/neuf_userinfo/static/style/stylesheets/print.css similarity index 100% rename from static/style/stylesheets/print.css rename to neuf_userinfo/static/style/stylesheets/print.css diff --git a/static/style/stylesheets/screen.css b/neuf_userinfo/static/style/stylesheets/screen.css similarity index 100% rename from static/style/stylesheets/screen.css rename to neuf_userinfo/static/style/stylesheets/screen.css diff --git a/neuf_userinfo/tests.py b/neuf_userinfo/tests.py new file mode 100644 index 0000000..0fabb9f --- /dev/null +++ b/neuf_userinfo/tests.py @@ -0,0 +1 @@ +from django.test import TestCase \ No newline at end of file diff --git a/neuf_userinfo/utils.py b/neuf_userinfo/utils.py new file mode 100644 index 0000000..50f3702 --- /dev/null +++ b/neuf_userinfo/utils.py @@ -0,0 +1,78 @@ +# coding: utf-8 +from __future__ import unicode_literals +import datetime +from django.conf import settings +import logging +import passwd +import re +from subprocess import Popen, PIPE + +from models import Radcheck + +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 + else: + 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 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 = passwd.radius_create(raw_password) + radius_user.save() + + +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 diff --git a/main/validators.py b/neuf_userinfo/validators.py similarity index 100% rename from main/validators.py rename to neuf_userinfo/validators.py diff --git a/main/views.py b/neuf_userinfo/views.py similarity index 75% rename from main/views.py rename to neuf_userinfo/views.py index ed61c5f..1f3d77e 100644 --- a/main/views.py +++ b/neuf_userinfo/views.py @@ -1,26 +1,23 @@ # coding: utf-8 -import json - 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.models import User from django.contrib.auth.tokens import default_token_generator -from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse from django.http import HttpResponse, HttpResponseRedirect -from django.shortcuts import render_to_response, get_object_or_404, resolve_url +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.csrf import csrf_protect from django.views.decorators.debug import sensitive_post_parameters +import json +from inside.models import InsideUser -from forms import LDAPPasswordResetForm from models import * import utils + def index(request): if request.method == "POST": form = AuthenticationForm(data=request.POST) @@ -29,12 +26,13 @@ def index(request): login(request, form.get_user()) else: if request.user.is_authenticated(): - return HttpResponseRedirect( reverse('main.views.profile') ) + return HttpResponseRedirect(reverse('neuf_userinfo.views.profile')) else: form = AuthenticationForm(request) return render_to_response('public/index.html', locals(), context_instance=RequestContext(request)) + @login_required def profile(request): username = request.user.username @@ -42,7 +40,7 @@ def profile(request): ldap_user = None try: ldap_user = LdapUser.objects.get(username=username) - except: + except LdapUser.DoesNotExist: return render_to_response('private/profile.html', locals(), context_instance=RequestContext(request)) ldap_groups = LdapGroup.objects.filter(usernames__contains=username) @@ -50,12 +48,13 @@ def profile(request): return render_to_response('private/profile.html', locals(), context_instance=RequestContext(request)) + @permission_required('main.is_superuser') def user_profile(request, username): ldap_user = None try: ldap_user = LdapUser.objects.get(username=username) - except: + except LdapUser.DoesNotExist: return render_to_response('private/profile.html', locals(), context_instance=RequestContext(request)) groups = LdapGroup.objects.filter(usernames__contains=username) @@ -63,37 +62,42 @@ def user_profile(request, username): return render_to_response('private/profile.html', locals(), context_instance=RequestContext(request)) -#todo security + +# 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'] } + status = { + 'active': True, + 'last_successful_auth': last_succ_auth, + 'last_modified': krb5_principal['Last modified'] + } else: - status = { 'active' : False } + status = {'active': False} return HttpResponse(json.dumps(status), content_type='application/javascript; charset=utf8') -#todo security + +# TODO security def wireless_status(request): + username = request.GET.get('username') try: - radius_user = Radcheck.objects.get(username=request.GET.get('username')) - # get last authentication - last_auth = Radpostauth.objects.filter(username__iexact=request.GET.get('username'), reply='Access-Accept').order_by('authdate') - if len(last_auth) != 0: - status = { 'active' : True, - 'last_successful_auth': last_auth[0].authdate.strftime('%Y-%m-%d %H:%M:%S'), - 'hash': radius_user.attribute } - else: - status = { 'active' : True, 'hash': radius_user.attribute } - except ObjectDoesNotExist: - status = { 'active' : False } + 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_to_response('registration/logout.html', locals(), context_instance=RequestContext(request)) + return render(request, 'registration/logout.html') # Doesn't need csrf_protect since no-one can guess the URL @@ -119,8 +123,8 @@ def password_reset_confirm(request, uidb64=None, token=None, post_reset_redirect = resolve_url(post_reset_redirect) try: uid = urlsafe_base64_decode(uidb64) - user = LdapUser.objects.get(pk=uid) # Diff line vs internal django view - except (TypeError, ValueError, OverflowError, LdapUser.DoesNotExist): + user = InsideUser.objects.get(pk=uid) # Diff line vs internal django view + except (TypeError, ValueError, OverflowError, InsideUser.DoesNotExist): user = None if user is not None and token_generator.check_token(user, token): diff --git a/scripts/push_active_to_wordpress.py b/scripts/push_active_to_wordpress.py index 8695489..29a958e 100644 --- a/scripts/push_active_to_wordpress.py +++ b/scripts/push_active_to_wordpress.py @@ -1,15 +1,17 @@ -import json, codecs, subprocess, os +import json +import codecs +import subprocess +import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "userinfosite.settings") -from django.conf import settings -from main.models import * +from neuf_userinfo.models import LdapGroup, LdapUser PHP_SCRIPT_PATH = '/var/www/neuf.no/userinfo/scripts' -OUT_FILENAME = os.path.join(PHP_SCRIPT_PATH,"users_in_group_active.json") +OUT_FILENAME = os.path.join(PHP_SCRIPT_PATH, "users_in_group_active.json") -usernames = LdapGroup.objects.get(name="dns-aktiv").usernames +usernames = LdapGroup.objects.get(posix_name="dns-aktiv").usernames users = LdapUser.objects.filter(username__in=usernames) -users_out = [[u.username,u.first_name,u.last_name,u.email] for u in users] +users_out = [[u.username, u.first_name, u.last_name, u.email] for u in users] out_file = codecs.open(OUT_FILENAME, "w", encoding="utf-8") @@ -29,4 +31,3 @@ for load_path in wp_load_paths: proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) script_response = proc.stdout.read() print script_response, - diff --git a/templates/nav.html b/templates/nav.html index 49cb711..4e5064a 100644 --- a/templates/nav.html +++ b/templates/nav.html @@ -13,7 +13,7 @@ {% if active %}
  • Din konto
  • Endre passord
  • -
  • Logg ut
  • +
  • Logg ut
  • {% if request.user.is_superuser %}
  • Administrasjon
  • {% endif %} diff --git a/templates/registration/logout.html b/templates/registration/logout.html index 55a440c..db30e6b 100644 --- a/templates/registration/logout.html +++ b/templates/registration/logout.html @@ -7,5 +7,5 @@ {%endblock%} {% block content %} - Du har blitt logget ut, vil du logge inn igjen? + Du har blitt logget ut, vil du logge inn igjen? {% endblock %} diff --git a/templates/registration/password_reset_email.html b/templates/registration/password_reset_email.html index 8cf48b1..ce4a7b0 100644 --- a/templates/registration/password_reset_email.html +++ b/templates/registration/password_reset_email.html @@ -1,2 +1,2 @@ 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 "main.views.password_reset_confirm" uidb64=uid token=token %} +{{ protocol }}://{{ domain }}{% url "neuf_userinfo.views.password_reset_confirm" uidb64=uid token=token %} diff --git a/test/testdata.ldif b/test/testdata.ldif index 472a6ee..eefe4f1 100644 --- a/test/testdata.ldif +++ b/test/testdata.ldif @@ -9,8 +9,8 @@ dn: uid=test,ou=People,dc=neuf,dc=no objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount -uid: testesen -gecos: testesen +uid: test +gecos: test loginShell: /bin/bash shadowExpire: -1 shadowFlag: 0 diff --git a/userinfosite/settings-sample.py b/userinfosite/settings.py similarity index 74% rename from userinfosite/settings-sample.py rename to userinfosite/settings.py index 72f201a..6737fc8 100644 --- a/userinfosite/settings-sample.py +++ b/userinfosite/settings.py @@ -35,12 +35,12 @@ INSTALLED_APPS = ( 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', -) -INSTALLED_APPS += ( 'django_extensions', 'south', - # Our app - 'main', +) +INSTALLED_APPS += ( + 'neuf_userinfo', + 'inside', ) MIDDLEWARE_CLASSES = ( @@ -67,38 +67,29 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - # Example PostgreSQL config: - #'ENGINE': 'django.db.backends.postgresql_psycopg2', - #'NAME': 'dusken_test', - #'USER': 'postgres', - #'PASSWORD': '', - #'HOST': '127.0.0.1', - #'PORT': '', }, 'ldap': { 'ENGINE': 'ldapdb.backends.ldap', 'NAME': 'ldap://localhost/', - 'USER': 'uid=admin,ou=People,dc=neuf,dc=no', - 'PASSWORD': '', + 'USER': 'uid=test,ou=People,dc=neuf,dc=no', + 'PASSWORD': 'test', }, '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. + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'radius', + 'USER': 'radius', + 'PASSWORD': '', + 'HOST': 'localhost', }, 'inside': { - 'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': 'dev_inside', # Or path to database file if using sqlite3. - 'USER': 'dev', # Not used with sqlite3. - 'PASSWORD': 'dev', # 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. + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'dev_inside', + 'USER': 'dev', + 'PASSWORD': 'dev', + 'HOST': 'localhost', } } -DATABASE_ROUTERS = ['main.router.Router'] +DATABASE_ROUTERS = ['neuf_userinfo.router.Router'] # Internationalization # https://docs.djangoproject.com/en/1.6/topics/i18n/ @@ -110,14 +101,10 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.6/howto/static-files/ -STATIC_ROOT = 'staticfiles' +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') STATIC_URL = '/static/' -STATICFILES_DIRS = ( - os.path.join(BASE_DIR, 'static'), -) # Uploads -# TODO S3 MEDIA_ROOT = os.path.join(BASE_DIR, "media") MEDIA_URL = "/media/" @@ -126,18 +113,19 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SITE_ID = 1 -EMAIL_HOST = 'snes.neuf.no' +EMAIL_HOST = 'mx.neuf.no' EMAIL_PORT = 25 DEFAULT_FROM_EMAIL = 'noreply@neuf.no' AUTHENTICATION_BACKENDS = ( - 'main.backends.LDAPEmailBackend', - 'main.backends.LDAPUsernameBackend', + # 'inside.authentication.InsideBackend', + 'neuf_userinfo.backends.LDAPEmailBackend', + 'neuf_userinfo.backends.LDAPUsernameBackend', 'django.contrib.auth.backends.ModelBackend', ) # No cleaningladies. SESSION_EXPIRE_AT_BROWSER_CLOSE = True -SESSION_COOKIE_AGE = 60 * 15 # in seconds +SESSION_COOKIE_AGE = 60 * 15 # in seconds # LDAP server URI and BIND_DN, same as db-settings AUTH_LDAP_U_SERVER_URI = DATABASES['ldap']['NAME'] @@ -162,7 +150,7 @@ AUTH_LDAP_U_USER_FLAGS_BY_GROUP = { } # Group to profile flag mappings, not used. AUTH_LDAP_U_PROFILE_FLAGS_BY_GROUP = { - #"is_edb": "cn=edb,ou=Groups,dc=neuf,dc=no" + # "is_edb": "cn=edb,ou=Groups,dc=neuf,dc=no" } # User attribute mappings AUTH_LDAP_U_USER_ATTR_MAP = { @@ -203,3 +191,11 @@ logger.setLevel(logging.DEBUG) KERBEROS_REALM = "NEUF.NO" KERBEROS_PASSWORD_CHANGING_PRINCIPAL = 'brukerinfo' KERBEROS_PASSWORD = '' + +# Inside +INSIDE_GROUPS_SYNC_DELETE = True # When user logs in, groups are synced (locally in Django), this deletes aswell + +try: + from local_settings import * +except ImportError: + pass \ No newline at end of file diff --git a/userinfosite/urls.py b/userinfosite/urls.py index 97bda2c..fc07715 100644 --- a/userinfosite/urls.py +++ b/userinfosite/urls.py @@ -1,35 +1,40 @@ from django.conf.urls import include, patterns, url from django.contrib import admin -from main.forms import LDAPPasswordChangeForm, LDAPSetPasswordForm, LDAPPasswordResetForm +from neuf_userinfo.forms import NeufPasswordChangeForm, NeufSetPasswordForm, NeufPasswordResetForm admin.autodiscover() import settings -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^admin/', include(admin.site.urls)), url(r'^admin/doc/', include('django.contrib.admindocs.urls')), - url(r'^accounts/profile/$', 'main.views.profile'), - url(r'^accounts/profile/(?P\w+)$', 'main.views.user_profile'), + + url(r'^userstatus/client/$', 'neuf_userinfo.views.client_status'), + url(r'^userstatus/wireless/$', 'neuf_userinfo.views.wireless_status'), + url(r'^$', 'neuf_userinfo.views.index'), +) + +# User account views +urlpatterns += patterns( + '', + url(r'^accounts/profile/$', 'neuf_userinfo.views.profile'), + url(r'^accounts/profile/(?P\w+)$', 'neuf_userinfo.views.user_profile'), url(r'^accounts/login/$', 'django.contrib.auth.views.login'), - url(r'^accounts/logout/$', 'main.views.logout'), + url(r'^accounts/logout/$', 'neuf_userinfo.views.logout'), + # Change password url(r'^accounts/password/change$', 'django.contrib.auth.views.password_change', - { 'password_change_form': LDAPPasswordChangeForm }), + {'password_change_form': NeufPasswordChangeForm}), url(r'^accounts/password/change/done$', 'django.contrib.auth.views.password_change_done', name='password_change_done'), + # Reset password (forgot password) url(r'^accounts/password/reset$', 'django.contrib.auth.views.password_reset', - { 'password_reset_form' : LDAPPasswordResetForm, - 'from_email' : settings.DEFAULT_FROM_EMAIL }), + {'password_reset_form': NeufPasswordResetForm, 'from_email': settings.DEFAULT_FROM_EMAIL}), 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[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', - 'main.views.password_reset_confirm', - { 'set_password_form': LDAPSetPasswordForm }), + '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'), - url(r'^userstatus/client/$', 'main.views.client_status'), - url(r'^userstatus/wireless/$', 'main.views.wireless_status'), - url(r'^$', 'main.views.index'), -) - -from django.contrib.staticfiles.urls import staticfiles_urlpatterns # for dev -urlpatterns += staticfiles_urlpatterns() +) \ No newline at end of file