summaryrefslogtreecommitdiff
path: root/src/apps
diff options
context:
space:
mode:
authorMarkus Krogh <markus@nordu.net>2017-06-02 13:19:30 +0200
committerMarkus Krogh <markus@nordu.net>2017-06-02 13:19:30 +0200
commit934702f61f1cbdbf001ebb598c22c75efa247645 (patch)
treeb0c6725a8c8a682b421aa35eea9662d7fff31bd6 /src/apps
parent41afbaae97384968df6312cbe570305208b2216e (diff)
Django 1.11 compatible and cleaned up
Diffstat (limited to 'src/apps')
-rw-r--r--src/apps/__init__.py0
-rw-r--r--src/apps/changepw/__init__.py0
-rw-r--r--src/apps/changepw/eduroam.mobileconfig68
-rw-r--r--src/apps/changepw/models.py34
-rw-r--r--src/apps/changepw/nordunet_change_password.py277
-rw-r--r--src/apps/changepw/templates/changepw/change_other.html37
-rw-r--r--src/apps/changepw/templates/changepw/change_password.html54
-rw-r--r--src/apps/changepw/templates/changepw/change_public_ssh_key.html45
-rw-r--r--src/apps/changepw/templates/changepw/index.html41
-rw-r--r--src/apps/changepw/templates/changepw/reset_password.html44
-rw-r--r--src/apps/changepw/tests.py23
-rw-r--r--src/apps/changepw/urls.py13
-rw-r--r--src/apps/changepw/views.py183
-rwxr-xr-xsrc/apps/fedlogin/__init__.py0
-rw-r--r--src/apps/fedlogin/middleware.py6
-rwxr-xr-xsrc/apps/fedlogin/models.py1
-rwxr-xr-xsrc/apps/fedlogin/tests.py23
-rwxr-xr-xsrc/apps/fedlogin/views.py40
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)