diff options
Diffstat (limited to 'src/apps/changepw/nordunet_change_password.py')
-rw-r--r-- | src/apps/changepw/nordunet_change_password.py | 277 |
1 files changed, 277 insertions, 0 deletions
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 |