diff options
author | Markus Krogh <markus@nordu.net> | 2017-06-02 13:19:30 +0200 |
---|---|---|
committer | Markus Krogh <markus@nordu.net> | 2017-06-02 13:19:30 +0200 |
commit | 934702f61f1cbdbf001ebb598c22c75efa247645 (patch) | |
tree | b0c6725a8c8a682b421aa35eea9662d7fff31bd6 /src/apps | |
parent | 41afbaae97384968df6312cbe570305208b2216e (diff) |
Django 1.11 compatible and cleaned up
Diffstat (limited to 'src/apps')
-rw-r--r-- | src/apps/__init__.py | 0 | ||||
-rw-r--r-- | src/apps/changepw/__init__.py | 0 | ||||
-rw-r--r-- | src/apps/changepw/eduroam.mobileconfig | 68 | ||||
-rw-r--r-- | src/apps/changepw/models.py | 34 | ||||
-rw-r--r-- | src/apps/changepw/nordunet_change_password.py | 277 | ||||
-rw-r--r-- | src/apps/changepw/templates/changepw/change_other.html | 37 | ||||
-rw-r--r-- | src/apps/changepw/templates/changepw/change_password.html | 54 | ||||
-rw-r--r-- | src/apps/changepw/templates/changepw/change_public_ssh_key.html | 45 | ||||
-rw-r--r-- | src/apps/changepw/templates/changepw/index.html | 41 | ||||
-rw-r--r-- | src/apps/changepw/templates/changepw/reset_password.html | 44 | ||||
-rw-r--r-- | src/apps/changepw/tests.py | 23 | ||||
-rw-r--r-- | src/apps/changepw/urls.py | 13 | ||||
-rw-r--r-- | src/apps/changepw/views.py | 183 | ||||
-rwxr-xr-x | src/apps/fedlogin/__init__.py | 0 | ||||
-rw-r--r-- | src/apps/fedlogin/middleware.py | 6 | ||||
-rwxr-xr-x | src/apps/fedlogin/models.py | 1 | ||||
-rwxr-xr-x | src/apps/fedlogin/tests.py | 23 | ||||
-rwxr-xr-x | src/apps/fedlogin/views.py | 40 |
18 files changed, 889 insertions, 0 deletions
diff --git a/src/apps/__init__.py b/src/apps/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/apps/__init__.py diff --git a/src/apps/changepw/__init__.py b/src/apps/changepw/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/apps/changepw/__init__.py diff --git a/src/apps/changepw/eduroam.mobileconfig b/src/apps/changepw/eduroam.mobileconfig new file mode 100644 index 0000000..952c924 --- /dev/null +++ b/src/apps/changepw/eduroam.mobileconfig @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>PayloadContent</key> + <array> + <dict> + <key>EAPClientConfiguration</key> + <dict> + <key>AcceptEAPTypes</key> + <array> + <integer>25</integer> + </array> + <key>EAPFASTProvisionPAC</key> + <false/> + <key>EAPFASTProvisionPACAnonymously</key> + <false/> + <key>EAPFASTUsePAC</key> + <false/> + <key>OuterIdentity</key> + <string>nordu-user@nordu.net</string> + <key>TLSAllowTrustExceptions</key> + <true/> + <key>TLSTrustedServerNames</key> + <array> + <string>eduroam1.nordu.net</string> + </array> + <key>UserName</key> + <string>eduroam-user</string> + </dict> + <key>EncryptionType</key> + <string>WPA</string> + <key>HIDDEN_NETWORK</key> + <false/> + <key>PayloadDescription</key> + <string>Configures wireless connectivity settings.</string> + <key>PayloadDisplayName</key> + <string>Wi-Fi (eduroam)</string> + <key>PayloadIdentifier</key> + <string>net.nordu.iphone.profile.wifi</string> + <key>PayloadOrganization</key> + <string>NORDUnet</string> + <key>PayloadType</key> + <string>com.apple.wifi.managed</string> + <key>PayloadUUID</key> + <string>63622959-0DCB-413A-9C3C-515833E38891</string> + <key>PayloadVersion</key> + <integer>1</integer> + <key>SSID_STR</key> + <string>eduroam</string> + </dict> + </array> + <key>PayloadDescription</key> + <string>Configuration for eduroam at NORDUnet</string> + <key>PayloadDisplayName</key> + <string>Eduroam Profile</string> + <key>PayloadIdentifier</key> + <string>net.nordu.iphone.profile</string> + <key>PayloadOrganization</key> + <string>NORDUnet</string> + <key>PayloadType</key> + <string>Configuration</string> + <key>PayloadUUID</key> + <string>4AD09534-1BC6-41CA-BC88-56149799781B</string> + <key>PayloadVersion</key> + <integer>1</integer> +</dict> +</plist> diff --git a/src/apps/changepw/models.py b/src/apps/changepw/models.py new file mode 100644 index 0000000..24e0bec --- /dev/null +++ b/src/apps/changepw/models.py @@ -0,0 +1,34 @@ +from django import forms +import re + +class ChangePasswordForm(forms.Form): + new_password = forms.CharField(widget=forms.PasswordInput) + new_password_again = forms.CharField(widget=forms.PasswordInput) + + def clean(self): + ''' + Validate the password submitted. + ''' + cleaned_data = self.cleaned_data + # The two submitted strings need to match. + new_password = cleaned_data.get('new_password') + new_password_again = cleaned_data.get('new_password_again') + if new_password != new_password_again: + raise forms.ValidationError('The typed passwords do not \ +match.') + # Check that the length is at least 10 characters. + if not len(new_password) >= 10: + raise forms.ValidationError('Your password needs to be at \ +least 10 characters long. Currently %d characters.' % len(new_password)) + # The password needs to contain at least one upper and one lower case + # letter and three numbers or special characters. + if not re.search('[a-z]', new_password) or not re.search( + '[A-Z]', new_password): + raise forms.ValidationError('You need at least one upper \ +case letter and one lower case letter in your password.') + numbers = re.findall('\d', new_password) + specials = re.findall('[,.\[\]!@#$%^&*?_\(\)-]', new_password) + if (len(numbers)+len(specials)) < 3: + raise forms.ValidationError('You need at least three numbers or \ +special characters i.e. 1234567890,.][!@#$%^&*?_()-') + return cleaned_data diff --git a/src/apps/changepw/nordunet_change_password.py b/src/apps/changepw/nordunet_change_password.py new file mode 100644 index 0000000..8149483 --- /dev/null +++ b/src/apps/changepw/nordunet_change_password.py @@ -0,0 +1,277 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon May 9 14:20:31 2011 + +@author: lundberg +Used in django-changepw (http://git.nordu.net/?p=django-changepw.git;a=summary). +""" + +from subprocess import call, Popen, PIPE +import pexpect +import ldap +from django.conf import settings + +SUFFIXES = ['', '/ppp', '/net', '/vpn'] + + +def _normalize_whitespace(s): + """ + Removes leading and ending whitespace from a string. + """ + return ' '.join(s.split()) + + +def check_kerberos_password(username, password): + """ + Tries to kinit with the username and password. + Returns True and kdestroys the ticket if the kinit succeded and returns + False otherwise. + """ + child = pexpect.spawn('kinit %s' % username) + result = child.expect(['Password', 'not found']) + if result is 0: + child.sendline(password) + result = child.expect([ + 'kinit: Password incorrect', + 'kinit: krb5_get_init_creds: salt type 3 not supported', # Missmatch of kerberos version between client and server + 'unknown', + pexpect.EOF]) + if result == 3: + call('kdestroy') + return True + return False + + +def duplicated_kerberos_password(suffix, _username, password): + """ + Checks all suffixes except the one provided, if the password can unlock + any pricipal True is returned else False. + """ + kerberos_uid = _username.split('@') + kerberos_uid[1] = kerberos_uid[1].upper() + suffixes = list(SUFFIXES) + suffixes.remove(suffix) + for suff in suffixes: + username = '%s@' % suff + if check_kerberos_password(username.join(kerberos_uid), password): + return True + return False + + +def change_nordunet_sso_pw(user, new_password): + """ + Changes the Kerberos and LDAP password for the user. + """ + ret = _change_kerberos_pw('', user.username, new_password) + if not ret: + ret = set_nordunet_ldap_pw_sasl(user) + return ret + + +def set_nordunet_ldap_pw_sasl(user): + """ + Sets the users ldap password to a pointer to a Kerberos principal. + """ + username = user.username.split('@')[0] + ldap_dn = 'uid=%s,ou=People,dc=nordu,dc=net' % username + l = _connect_ldap(user=settings.LDAP_USER, password=settings.LDAP_PASSWORD) + if l: + try: + mod_attrs = [(ldap.MOD_REPLACE, 'userPassword', str('{SASL}%s@NORDU.NET' % username))] + l.modify_s(ldap_dn, mod_attrs) + except ldap.LDAPError, e: + l.unbind() + return e.message + l.unbind() + else: + return 'Invalid LDAP credentials in settings.' + return 0 + + +def change_nordunet_ppp_pw(user, new_password): + """ + Uses a third party script to change a Kerberos password. + Returns the return value from the third party script. + + User needs to be employee at NORDUnet to run this. User has + affiliation employee@nordu.net. + """ + if user.is_staff: + return _change_kerberos_pw('/ppp', user.username, new_password) + else: + return 'You need to be a NORDUnet employee or member to use this.' + + +def change_nordunet_net_pw(user, new_password): + """ + Uses a third party script to change a Kerberos password. + Returns the return value from the third party script. + + User needs to be employee at NORDUnet to run this. If user has + affiliation employee@nordu.net is_staff flag is True. + """ + if user.is_staff: + return _change_kerberos_pw('/net', user.username, new_password) + else: + return 'You need to be a NORDUnet employee to use this.' + + +def change_nordunet_vpn_pw(user, new_password): + """ + Uses a third party script to change a Kerberos password. + Returns the return value from the third party script. + + User needs to be employee at NORDUnet to run this. If user has + affiliation employee@nordu.net is_staff flag is True. + """ + if user.is_staff: + return _change_kerberos_pw('/vpn', user.username, new_password) + else: + return 'You need to be a NORDUnet employee to use this.' + + +def _change_kerberos_pw(suffix, username, new_password): + kerberos_uid = username.split('@') + kerberos_uid[1] = kerberos_uid[1].upper() + if not duplicated_kerberos_password(suffix, username, new_password): + kerberos_uid = '%s%s@%s' % (kerberos_uid[0], suffix, kerberos_uid[1]) + p = Popen([settings.KERBEROS_SCRIPT], stdin=PIPE) + p.communicate('%s %s' % (kerberos_uid, new_password)) + return p.wait() + return 'You can\'t set the same password as your %s password.' % _pretty_suffixes(without=suffix) + + +def _pretty_suffixes(without=None): + if without is None: + suffixes = [s for s in SUFFIXES if s is not without] + else: + suffixes = list(SUFFIXES) + if '' in suffixes: + suffixes.remove('') + suffixes.append('SSO') + return ', '.join([s.upper().replace('/', '') for s in suffixes]) + + +def _validate_ssh_key(s): + """ + Tries to validate a string against the public ssh key format as in + RFC4253 and RFC4716. + + Checks that the string is in three parts separated by whitespace and that + the first part is in public_key_formats and the second part is a base64 + encoded string. + + Returns True if the string validates. + """ + import base64 + public_key_formats = ['ssh-dss', 'ssh-rsa', 'pgp-sign-rsa', 'pgp-sign-dss', 'ssh-ed25519'] + three_parts = s.split() + if three_parts[0] in public_key_formats and len(three_parts) in [2,3]: + try: + base64.b64decode(three_parts[1]) + except TypeError: + return False + else: + return False + return True + + +def _connect_ldap(server=None, user=None, password=None): + """ + Connects to an ldap server and binds with supplied user and password. + """ + _server = server or settings.LDAP_URL + l = ldap.initialize(_server) + if not _server.startswith("ldaps"): + l.start_tls_s() + try: + if user is None: + l.simple_bind_s() + else: + l.bind_s(user, password) + except ldap.INVALID_CREDENTIALS: + return False + return l + + +def set_public_ssh_key(user, ssh_keys): + """ + Sets the provided string(s) as the sshPublicKey attribute for the user. + User need to have affiliation employee@nordu.net to use this function. + """ + if user.is_staff: + valid_keys = [] + for ssh_key in ssh_keys.split('\n'): + ssh_key = _normalize_whitespace(ssh_key) + if ssh_key: + if _validate_ssh_key(ssh_key): + valid_keys.append(ssh_key) + else: + return '%s is not a valid SSH key.' % ssh_key + if valid_keys: + ldap_dn = user.username.split('@') + ldap_dn = 'uid=%s,ou=People,dc=nordu,dc=net' % ldap_dn[0] + l = _connect_ldap(user=settings.LDAP_USER, password=settings.LDAP_PASSWORD) + if l: + try: + # Ensure that objectClass ldapPublicKey is added to the user + mod_attrs = [(ldap.MOD_ADD, 'objectClass', 'ldapPublicKey')] + l.modify_s(ldap_dn, mod_attrs) + except ldap.TYPE_OR_VALUE_EXISTS: + pass + try: + # Add the new ssh keys + for key in valid_keys: + mod_attrs = [(ldap.MOD_ADD, 'sshPublicKey', str(key))] + l.modify_s(ldap_dn, mod_attrs) + except ldap.LDAPError, e: + l.unbind() + return e.message + l.unbind() + else: + return 'Invalid LDAP credentials in settings.' + else: + return 'You need to be a NORDUnet employee to use this.' + return 0 + + +def get_public_ssh_keys(user): + l = _connect_ldap() + if l: + uid = user.username.split('@')[0] + dn = "uid=%s,ou=People,dc=nordu,dc=net" % uid + try: + res = l.search_s(dn, ldap.SCOPE_SUBTREE, "(objectClass=person)")[0][1] + return res.get('sshPublicKey') + except (ldap.LDAPError, TypeError): + pass + return None + + +def del_public_ssh_key(user, ssh_key): + """ + Sets the provided string(s) as the sshPublicKey attribute for the user. + User need to have affiliation employee@nordu.net to use this function. + """ + if user.is_staff: + ldap_dn = user.username.split('@') + ldap_dn = 'uid=%s,ou=People,dc=nordu,dc=net' % ldap_dn[0] + l = _connect_ldap(user=settings.LDAP_USER, + password=settings.LDAP_PASSWORD) + if l: + try: + # Remove all previous ssh keys + try: + mod_attrs = [(ldap.MOD_DELETE, 'sshPublicKey', ssh_key)] + l.modify_s(ldap_dn, mod_attrs) + except ldap.NO_SUCH_ATTRIBUTE: + pass + except ldap.LDAPError, e: + l.unbind() + return e.message + l.unbind() + else: + return 'Invalid LDAP credentials in settings.' + else: + return 'You need to be a NORDUnet employee to use this.' + return 0 diff --git a/src/apps/changepw/templates/changepw/change_other.html b/src/apps/changepw/templates/changepw/change_other.html new file mode 100644 index 0000000..0979e84 --- /dev/null +++ b/src/apps/changepw/templates/changepw/change_other.html @@ -0,0 +1,37 @@ +{% extends "base.html" %} +{% block js %} +{% endblock %} +{% block title %}Update public SSH keys{% endblock %} +{% block content %} +<h2>Update your public SSH keys</h2> +{% if return_value == None %} + <form action="{% url changeother %}" method="post" autocomplete="off">{% csrf_token %} + <p>When pasting multiple ssh public keys remember to use new line or a blank line between keys.</p> + <table> + <tr> + <td class="formlabel">Paste your SSH public keys:</td> + </tr> + <tr> + <td class="formfield"> + <textarea name="ssh_key" cols="50" rows="10"></textarea> + </td> + </tr> + <tr> + <td class="formbutton"><input type="submit" value="Submit" /></td> + </tr> + </table> + </form> +{% else %} + {% if return_value == 0 %} + <p>Your public SSH keys was updated successfully.</p> + {% else %} + <p>Something went wrong. Please contact an administrator.</p> + <p>Return code: {{ return_value }}</p> + {% endif %} +{% endif %} +<p> + <a href="{% url index %}">Back</a><br /> + <a href="{% url logout %}">Log out</a> +</p> +{% endblock %} + diff --git a/src/apps/changepw/templates/changepw/change_password.html b/src/apps/changepw/templates/changepw/change_password.html new file mode 100644 index 0000000..5017b4c --- /dev/null +++ b/src/apps/changepw/templates/changepw/change_password.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} +{% load static %} +{% block js %} + <script type="text/javascript" src="{% static 'js/jquery/jquery-1.4.4.min.js' %}"></script> + <script type="text/javascript" src="{% static 'js/jquery/password_strength.js' %}"></script> +{% endblock %} +{% block title %}Change {{ pwtype }} password{% endblock %} +{% block content %} +<h2>Change {{ pwtype }} password</h2> +{% if form %} +<p>When thinking of a new password you need to remember to use:</p> +<ul> + <li>no fewer than ten characters</li> + <li>at least one upper case and one lower case letter</li> + <li>three or more numbers or special characters</li> +</ul> + + <p class="error"> + {{ form.non_field_errors }} + </p> + <form action="{% url 'changepw' pwtype %}" method="post" autocomplete="off">{% csrf_token %} + <table> + <tr> + <th class="formlabel">Username:</th><td>{{ username }}{% if pwtype == "ppp" %}/ppp{% endif %}</td> + </tr> + {% for field in form %} + <tr> + <td class="fielderrors">{{ field.errors }}</td> + </tr> + <tr> + <th class="formlabel">{{ field.label_tag }}:</th><td class="formfield">{{ field }}</td><td><span class="password_strength"></span></td> + </tr> + {% endfor %} + </table> + <input type="submit" value="Submit" /> + </form> + <script type="text/javascript"> + $('form').attr('autocomplete', 'off'); + $('#id_new_password').password_strength(); + $('#id_new_password_again').password_strength(); + </script> +{% else %} + {% if return_value == 0 %} + <p>Your {{ pwtype }} password was changed successfully.</p> + {% else %} + <p>Something went wrong. Please contact an administrator.</p> + <p>Return code: {{ return_value }}</p> + {% endif %} +{% endif %} +<p> + <a href="{% url 'index' %}">Back</a><br /> + <a href="{% url 'logout' %}">Log out</a> +</p> +{% endblock %} diff --git a/src/apps/changepw/templates/changepw/change_public_ssh_key.html b/src/apps/changepw/templates/changepw/change_public_ssh_key.html new file mode 100644 index 0000000..0ad6533 --- /dev/null +++ b/src/apps/changepw/templates/changepw/change_public_ssh_key.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} +{% block js %} +{% endblock %} +{% block title %}Update public SSH keys{% endblock %} +{% block content %} + +{% if return_value == 0 or return_value == None %} + {% if return_value == 0 %} + <p>Your public SSH keys where updated successfully.</p> + {% endif %} + + <h2>Your existing public SSH keys</h2> + <table border="1px"> + {% for key in ssh_keys %} + <tr> + <td><div style="width:500px;word-wrap:break-word;">{{ key }}</div></td> + <td><a href="{% url 'deletepublicsshkey' key_number=forloop.counter0 %}">Delete</a></td> + </tr> + {% endfor %} + </table> + <h2>Update your public SSH keys</h2> + <form action="{% url 'changepublicsshkeys' %}" method="post" autocomplete="off">{% csrf_token %} + <table> + <tr> + <td class="formlabel">Paste your SSH public keys (one key per line):</td> + </tr> + <tr> + <td class="formfield"> + <textarea name="ssh_key" cols="70" rows="10"></textarea> + </td> + </tr> + <tr> + <td class="formbutton"><input type="submit" value="Submit" /></td> + </tr> + </table> + </form> +{% else %} + <p>Something went wrong. Please contact an administrator.</p> + <p>Return code: {{ return_value }}</p> +{% endif %} +<p> + <a href="{% url 'index' %}">Back</a><br /> + <a href="{% url 'logout' %}">Log out</a> +</p> +{% endblock %} diff --git a/src/apps/changepw/templates/changepw/index.html b/src/apps/changepw/templates/changepw/index.html new file mode 100644 index 0000000..12df371 --- /dev/null +++ b/src/apps/changepw/templates/changepw/index.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} +{% block js %} +{% endblock %} +{% block title %}SSO Password Manager{% endblock %} +{% block content %} +<h2>SSO Password Manager</h2> +<p> + Hello {{ full_name|capfirst }},<br /> + Welcome to the single sign on password manager site. +</p> + +<table> + <tr> + <th>Your usernames</th><th></th> + </tr> + <tr> + <td>SSO username:</td><td>{{ username }}</td> + </tr> + <tr> + <td><!-- VPN and -->eduroam username:</td><td>{{ username }}/ppp</td> + </tr> +</table> + +<p> +Available actions:<br /> +<a href="{% url 'changepw' "sso" %}">Change single sign on (SSO) password</a><br /> +{% if user.is_staff %} + <a href="{% url 'changepw' "net" %}">Change TACACS password</a><br /> +{% endif %} +{% if user.is_active or user.is_staff %} + <a href="{% url 'changepw' "ppp" %}">Change <!-- VPN and -->eduroam password</a><br /> +{% endif %} +{% if user.is_staff %} + <a href="{% url 'changepublicsshkeys' %}">Update your public SSH keys</a><br /> + <a href="{% url 'ideviceconf' %}" rel="external">Configure eduroam on your iDevice</a> +{% endif %} +</p> + +<p><a href="{% url 'logout' %}">Log out</a></p> +{% endblock %} + diff --git a/src/apps/changepw/templates/changepw/reset_password.html b/src/apps/changepw/templates/changepw/reset_password.html new file mode 100644 index 0000000..c56b920 --- /dev/null +++ b/src/apps/changepw/templates/changepw/reset_password.html @@ -0,0 +1,44 @@ +{% extends "base.html" %} +{% block js %} +{% endblock %} +{% block title %}Reset <!--VPN and -->eduroam password{% endblock %} +{% block content %} +<h2>Reset <!--VPN and -->eduroam password</h2> +{% if not return_value %} + <table> + <tr> + <th>Username:</th><td>{{ username }}/ppp</td> + </tr> + {% if new_password %} + <tr> + <th>Password:</th><td>{{ new_password }}</td> + </tr> + {% else %} + <tr> + <th>Password:</th> + <td> + <form action="{% url resetpw %}" method="post"> + {% csrf_token %} + <input type="submit" value="Reset" /> + </form> + </td> + </tr> + {% endif %} + </table> + <p> +<!-- + <a href="https://portal.nordu.net/display/nordunet/VPN+Access" target="_blank">Guide to VPN setup</a><br /> +--> + <a href="https://portal.nordu.net/display/nordunet/Wireless+roaming+via+eduroam" target="_blank">Guide to eduroam setup</a> + </p> +{% else %} + <p>Something went wrong. Please contact an administrator.</p> + <p>Return code: {{ return_value }}</p> +{% endif %} + +<p> + <a href="{% url index %}">Back</a><br /> + <a href="{% url logout %}">Log out</a> +</p> +{% endblock %} + diff --git a/src/apps/changepw/tests.py b/src/apps/changepw/tests.py new file mode 100644 index 0000000..2247054 --- /dev/null +++ b/src/apps/changepw/tests.py @@ -0,0 +1,23 @@ +""" +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/src/apps/changepw/urls.py b/src/apps/changepw/urls.py new file mode 100644 index 0000000..8983950 --- /dev/null +++ b/src/apps/changepw/urls.py @@ -0,0 +1,13 @@ +# This also imports the include function +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url(r'^$', views.index, name='index'), + url(r'^changepw/(?P<pwtype>[-\w]+)$', views.change_password, name='changepw'), + url(r'^changeother$', views.change_other, name='changeother'), + url(r'^ideviceconf$', views.ideviceconf, name='ideviceconf'), + url(r'^changepublicsshkeys$', views.change_public_ssh_keys, name='changepublicsshkeys'), + url(r'^changepublicsshkeys/deletekey/(?P<key_number>\d+)$', views.delete_public_ssh_key, name='deletepublicsshkey'), +] diff --git a/src/apps/changepw/views.py b/src/apps/changepw/views.py new file mode 100644 index 0000000..c3fb090 --- /dev/null +++ b/src/apps/changepw/views.py @@ -0,0 +1,183 @@ +from django.contrib.auth.decorators import login_required +from apps.changepw.models import ChangePasswordForm +from django.http import HttpResponse +from django.shortcuts import render +import random +import nordunet_change_password as pw + + +def _change_password(pwtype, user, new_password): + ''' + Use this to call your change password function. + ''' + if pwtype == 'sso': + ret = pw.change_nordunet_sso_pw(user, new_password) + elif pwtype == 'ppp': + ret = pw.change_nordunet_ppp_pw(user, new_password) + elif pwtype == 'net': + ret = pw.change_nordunet_net_pw(user, new_password) + elif pwtype == 'vpn': + ret = pw.change_nordunet_vpn_pw(user, new_password) + else: + ret = 'Could not change that password type.' + return ret + + +def _change_other(request, *args): + ''' + Use this to call your change function. + ''' + user = request.user + ssh_key = request.POST.get('ssh_key', None) + if ssh_key: + ret = pw.set_public_ssh_key(user, ssh_key) + else: + return 1 + return ret + + +def _get_username(request): + ''' + Returns the actual username from the Shibboleth uid. + request.user.username == username@domain.com + ''' + return request.user.username.split('@')[0] + + +def _generate_password(n, z=3): + ''' + Returns a psudo random string of lenght n in accordance to the NORDUnet + security standard. z is the number of non-letters to include. + ''' + letters = 'abcdefghijklmnopqrstuvwxyz' + others = '1234567890!#%&?+*-_.<>' + pw = [] + for i in range(0, n//2): + pw.append(random.choice(letters)) + pw.append(random.choice(letters.upper())) + random.shuffle(pw) + pw = pw[:n] + for i in random.sample(range(0, n-1), z): + pw[i] = random.choice(others) + return ''.join(pw) + + +@login_required() +def index(request): + ''' + Greets the user and presents the choices available. + ''' + username = _get_username(request) + try: + full_name = request.user.get_full_name() + except AttributeError: + full_name = username + return render(request, + 'changepw/index.html', + {'full_name': full_name, 'username': username}) + + +@login_required() +def change_password(request, pwtype): + ''' + If the user is authenticated and the form is valid the password + changing script will be run with the username and new password. + The function that changes the password has to be provided as func. + ''' + username = _get_username(request) + form = ChangePasswordForm(request.POST or None) + return_value = -1 + if request.method == 'POST': + if form.is_valid(): + new_password = form.cleaned_data['new_password'] + return_value = _change_password(pwtype, request.user, new_password) + form = None + return render(request, + 'changepw/change_password.html', + {'form': form, + 'username': username, + 'pwtype': pwtype, + 'return_value': return_value}) + + +@login_required() +def change_other(request, *args): + ''' + Just passes along the request so that something can be done for that user. + ''' + username = _get_username(request) + return_value = None + if request.method == 'POST': + return_value = _change_other(request, *args) + return render(request, + 'changepw/change_other.html', + {'username': username, 'return_value': return_value}) + + +@login_required() +def change_public_ssh_keys(request): + """ + Lets the user remove or add public SSH keys. + """ + if request.POST: + ssh_key = request.POST.get('ssh_key', None) + if ssh_key: + ret = pw.set_public_ssh_key(request.user, ssh_key) + else: + ret = 'No SSH key to add.' + ssh_keys = pw.get_public_ssh_keys(request.user) + return render(request, + 'changepw/change_public_ssh_key.html', + {'username': request.user.username, + 'ssh_keys': ssh_keys, + 'return_value': ret}) + else: + ssh_keys = pw.get_public_ssh_keys(request.user) + return render(request, + 'changepw/change_public_ssh_key.html', + {'username': request.user.username, + 'ssh_keys': ssh_keys, + 'return_value': None}) + + +@login_required() +def delete_public_ssh_key(request, key_number): + """ + Delete a public SSH key. + """ + ssh_keys = pw.get_public_ssh_keys(request.user) + ret = pw.del_public_ssh_key(request.user, ssh_keys[int(key_number)]) + ssh_keys = pw.get_public_ssh_keys(request.user) + return render(request, + 'changepw/change_public_ssh_key.html', + { + 'username': request.user.username, + 'ssh_keys': ssh_keys, + 'return_value': ret}) + +def _create_ieduroam_conf(user): + """ + Creates an xml config (http://www.apple.com/DTDs/PropertyList-1.0.dtd) for + iPhone, iPod Touch or Ipad that can be set by surfing to the URL. + + Should ultimately returned with + HttpResponse(conf, mimetype='application/x-apple-aspen-config') + """ + try: + f = open('/var/lib/django/sso/apps/changepw/eduroam.mobileconfig') + except IOError: + return 'Could not open boilerplate configuration.' + uid = user.username.split('@')[0] + s = ''.join(f.readlines()) + s = s.replace('nordu-user', '%s-pwman' % uid) + conf = s.replace('eduroam-user', '%s/ppp' % uid) + return conf + + +def ideviceconf(request): + """ + HACK + """ + user = request.user + conf = _create_ieduroam_conf(user) + return HttpResponse(conf, content_type='application/x-apple-aspen-config') diff --git a/src/apps/fedlogin/__init__.py b/src/apps/fedlogin/__init__.py new file mode 100755 index 0000000..e69de29 --- /dev/null +++ b/src/apps/fedlogin/__init__.py diff --git a/src/apps/fedlogin/middleware.py b/src/apps/fedlogin/middleware.py new file mode 100644 index 0000000..00f5ba4 --- /dev/null +++ b/src/apps/fedlogin/middleware.py @@ -0,0 +1,6 @@ +#MK: 2017-04-27 hack to make it work double proxied... +from django.contrib.auth.middleware import PersistentRemoteUserMiddleware + +class CustomHeaderMiddleware(PersistentRemoteUserMiddleware): + header = 'HTTP_X_REMOTE_USER' +#END MK hack diff --git a/src/apps/fedlogin/models.py b/src/apps/fedlogin/models.py new file mode 100755 index 0000000..137941f --- /dev/null +++ b/src/apps/fedlogin/models.py @@ -0,0 +1 @@ +from django.db import models diff --git a/src/apps/fedlogin/tests.py b/src/apps/fedlogin/tests.py new file mode 100755 index 0000000..2247054 --- /dev/null +++ b/src/apps/fedlogin/tests.py @@ -0,0 +1,23 @@ +""" +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/src/apps/fedlogin/views.py b/src/apps/fedlogin/views.py new file mode 100755 index 0000000..f122ccd --- /dev/null +++ b/src/apps/fedlogin/views.py @@ -0,0 +1,40 @@ +from django.contrib.auth import logout +from django.http import HttpResponseRedirect +from django.conf import settings +from django.core.urlresolvers import reverse +from django.contrib.auth.views import login + + +def fedlogin(request): + user = request.user + + if user.is_authenticated(): + + first_name = request.META.get('HTTP_GIVENNAME') + last_name = request.META.get('HTTP_SN') + email = request.META.get('HTTP_MAIL') + affiliations = request.META.get('HTTP_AFFILIATION', '').split(';') + + if first_name: + user.first_name = first_name + if last_name: + user.last_name = last_name + if email: + user.email = email + user.is_staff = 'employee@nordu.net' in affiliations + user.is_active = 'employee@nordu.net' in affiliations or 'member@nordu.net' in affiliations + if user.password == "": + user.password = "(not used for federated logins)" + user.save() + + _next = request.GET.get('next') + if _next: + return HttpResponseRedirect(_next) + else: + return HttpResponseRedirect(reverse(login)) + + +def fedlogout(request): + logout(request) + url = getattr(settings, 'FEDERATE_LOGOUT_URL', '/Shibboleth.sso/Logout') + return HttpResponseRedirect(url) |