# -*- coding: utf-8 -*- import hmac import ldap import pickle import re from Crypto.Cipher import AES from functools import wraps from flask import flash, g, redirect, render_template, request, session, url_for from hashlib import sha1 from random import randint from werkzeug.exceptions import Forbidden _username_re = re.compile(r'^[a-z]{2,16}') # using http://flask.pocoo.org/docs/patterns/viewdecorators/ def templated(template=None): def templated_(f): @wraps(f) def templated__(*args, **kwargs): template_name = template if template_name is None: template_name = request.endpoint \ .replace('.', '/') + '.html' ctx = f(*args, **kwargs) if ctx is None: ctx = {} elif not isinstance(ctx, dict): return ctx return render_template(template_name, **ctx) return templated__ return templated_ def login_required(f): @wraps(f) def login_required_(*args, **kwargs): if not g.user: raise Forbidden return f(*args, **kwargs) return login_required_ def login_user(username, password): try: g.user = g.ldap.auth(username, password) except ldap.INVALID_CREDENTIALS: return False session['username'] = username session['password'] = encrypt_password(password) return True def logout_user(): session.pop('username', None) session.pop('password', None) g.user = None def pad(s, numbytes=32, padding='\0'): return s + (numbytes - len(s) % numbytes) * padding def encrypt_password(password): """ Encrypt the given password with `config.PASSWORD_ENCRYPTION_KEY`. The key must be 32 bytes long. """ assert len(app.config['PASSWORD_ENCRYPTION_KEY']) == 32 if isinstance(password, unicode): password = password.encode('utf8') iv = ''.join(chr(randint(0, 0xff)) for i in range(16)) encryptor = AES.new(app.config['PASSWORD_ENCRYPTION_KEY'], AES.MODE_CBC, iv) return iv + encryptor.encrypt(pad(password)) def decrypt_password(ciphertext): """ Decrypt the given password with `config.PASSWORD_ENCRYPTION_KEY`. """ iv = ciphertext[:16] encryptor = AES.new(app.config['PASSWORD_ENCRYPTION_KEY'], AES.MODE_CBC, iv) return encryptor.decrypt(ciphertext[16:]).rstrip('\0') def create_confirmation(realm, data): """ Create a confirmation token e.g. for confirmation mails. Expects as input a realm to distinguish data for several applications and some data (pickle-able). """ key = '\0'.join((app.config['SECRET_KEY'], realm)) payload = pickle.dumps(data) mac = hmac.new(key, payload, sha1) return ''.join((mac.digest(), payload)).encode('base64').strip() class InvalidConfirmation(ValueError): """Raised by `verify_confirmation` on invalid input data""" def verify_confirmation(realm, token): """ Verify a confirmation created by `create_confirmation` and, if it is valid, return the original data. """ key = '\0'.join((app.config['SECRET_KEY'], realm)) token = token.decode('base64') mac = token[:20] payload = token[20:] if mac != hmac.new(key, payload, sha1).digest(): raise InvalidConfirmation('MAC does not match') return pickle.loads(payload) # circular import from app import app