From f3790b6920b949617bf38e542cd482b252a80b12 Mon Sep 17 00:00:00 2001 From: William Brown Date: Feb 04 2020 04:50:33 +0000 Subject: Ticket 1 - use ds sso tokens Bug Description: The proof of concept used an encrypted token containing the users password to allow the bind. This is not exactly optimal. Fix Description: Now that sso tokens have been merged to 389-ds we can rely on those for rebinding instead. https://pagure.io/389-ds-portal/issue/1 Author: William Brown Review by: ??? --- diff --git a/requirements.txt b/requirements.txt index f1ad0a2..4fdf6a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ flask -cryptography python-ldap python-dateutil diff --git a/server.py b/server.py index daf8f4d..a7284f1 100644 --- a/server.py +++ b/server.py @@ -4,15 +4,10 @@ import configparser import copy import sys -from cryptography.fernet import Fernet -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC - from flask import Flask, session, redirect, url_for, escape, request, render_template from lib389 import DirSrv -from lib389.idm.account import Accounts +from lib389.idm.account import Account, Accounts from lib389.idm.user import nsUserAccounts from lib389._mapped_object import _gen_or, _gen_filter, _term_gen @@ -28,9 +23,6 @@ if not config.has_option('dsportal', 'ldapurl'): if not config.has_option('dsportal', 'basedn'): app.logger.error("Missing config.ini option dsportal:basedn\n") raise Exception("Missing config.ini option dsportal:basedn") -if not config.has_option('dsportal', 'fernet_cookie_key'): - app.logger.error("Missing config.ini option dsportal:fernet_cookie_key\n") - raise Exception("Missing config.ini option dsportal:fernet_cookie_key") if not config.has_option('dsportal', 'cookie_signing_key'): app.logger.error("Missing config.ini option dsportal:cookie_signing_key\n") raise Exception("Missing config.ini option dsportal:cookie_signing_key") @@ -41,19 +33,8 @@ app.secret_key = base64.b64decode(config['dsportal']['cookie_signing_key']) CONFIG = { 'ldapurl': config['dsportal']['ldapurl'], 'basedn': config['dsportal']['basedn'], - 'cookie_key': base64.b64decode(config['dsportal']['fernet_cookie_key']) } -KDF = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=b'', - iterations=100000, - backend=default_backend() -) -COOKIE_KEY = base64.urlsafe_b64encode(KDF.derive(CONFIG['cookie_key'])) -FERNET = Fernet(COOKIE_KEY) - ACCOUNT = { 'class': config['account']['class'], 'name_attr': config['account']['name_attr'], @@ -98,9 +79,14 @@ def _get_name_to_dn(ldapurl, name, basedn, binddn=None, password=None): def _bind_dn(ldapurl, binddn, password): inst = _get_ds_instance(ldapurl, binddn, password) - inst.open() - inst.unbind_s() - return True + token = None + try: + inst.open() + acct = Account(inst, binddn) + token = acct.request_sso_token() + finally: + inst.unbind_s() + return token @app.errorhandler(404) @app.route('/notfound') @@ -117,9 +103,7 @@ def account_dn_update(req_dn): # the main page. We may need to return a status code the ajax can interpret try: dn = session['dn'] - enc_token = session['token'] - app.logger.debug(f'{enc_token}') - token = FERNET.decrypt(enc_token).decode() + token = session['token'] except Exception as e: app.logger.debug(e) app.logger.error('Failed to decrypt auth token or get account details.') @@ -168,9 +152,7 @@ def account_dn_update(req_dn): def account_dn_password_update(req_dn): try: dn = session['dn'] - enc_token = session['token'] - app.logger.debug(f'{enc_token}') - token = FERNET.decrypt(enc_token).decode() + token = session['token'] except Exception as e: app.logger.debug(e) app.logger.error('Failed to decrypt auth token or get account details.') @@ -213,12 +195,6 @@ def account_dn_password_update(req_dn): acct = nsaccts.get(dn=req_dn) acct.change_password(cur_pw, new_pw) - # WARNING: Because we current have the pw in the token, we need to re-issue it here else - # we'd log the user out. In the futur version when fernet comes from 389-ds, this won't - # be needed! - token = FERNET.encrypt(str.encode(new_pw)) - session['token'] = token - except Exception as e: app.logger.debug(e) return '', 500 @@ -231,15 +207,22 @@ def account_dn_password_update(req_dn): def index_password(): try: dn = session['dn'] - enc_token = session['token'] - app.logger.debug(f'{enc_token}') - token = FERNET.decrypt(enc_token).decode() + token = session['token'] except Exception as e: app.logger.debug(e) app.logger.error('Failed to decrypt auth token or get account details.') # TODO: Put a session invalid message here. return redirect(url_for('login')) + inst = _get_ds_instance(CONFIG['ldapurl'], dn, token) + try: + inst.open() + except Exception as e: + app.logger.debug(e) + return '', 500 + finally: + inst.unbind_s() + app.logger.debug(f'{dn}, {token}') return render_template('password.html', dn=dn) @@ -248,16 +231,13 @@ def index(): # Are they authenticated? try: dn = session['dn'] - enc_token = session['token'] - app.logger.debug(f'{enc_token}') - token = FERNET.decrypt(enc_token).decode() + token = session['token'] except Exception as e: app.logger.debug(e) app.logger.error('Failed to decrypt auth token or get account details.') # TODO: Put a session invalid message here. return redirect(url_for('login')) - app.logger.debug(f'{dn}, {token}') inst = _get_ds_instance(CONFIG['ldapurl'], dn, token) @@ -303,10 +283,9 @@ def login(): app.logger.debug(f'authenticating as {dn}') # TODO: Handle incorrect password differently. - if _bind_dn(CONFIG['ldapurl'], dn, pw): + token = _bind_dn(CONFIG['ldapurl'], dn, pw) + if token is not None: session['dn'] = dn - # Enc the pw - token = FERNET.encrypt(str.encode(pw)) session['token'] = token return redirect(url_for('index')) except Exception as e: