diff --git a/data/users.py b/data/users.py index f8fce77a5..bcd0fad62 100644 --- a/data/users.py +++ b/data/users.py @@ -1,7 +1,10 @@ import ldap import logging +import json +import itertools +import uuid -from flask.sessions import SecureCookieSessionInterface, BadSignature +from util.aes import AESCipher from util.validation import generate_valid_usernames from data import model @@ -107,6 +110,7 @@ class LDAPUsers(object): return found_user is not None + class UserAuthentication(object): def __init__(self, app=None): self.app = app @@ -139,23 +143,68 @@ class UserAuthentication(object): app.extensions['authentication'] = users return users + def _get_secret_key(self): + """ Returns the secret key to use for encrypting and decrypting. """ + from app import app + app_secret_key = app.config['SECRET_KEY'] + + # First try parsing the key as a float. + try: + secret_key = float(app_secret_key) + except ValueError: + secret_key = app_secret_key + + # Next try parsing it as an UUID. + try: + secret_key = uuid.UUID(app_secret_key).bytes + except ValueError: + secret_key = app_secret_key + + # Otherwise, use the bytes directly. + return ''.join(itertools.islice(itertools.cycle(secret_key), 32)) + + def encrypt_user_password(self, password): + """ Returns an encrypted version of the user's password. """ + data = { + 'password': password + } + + message = json.dumps(data) + cipher = AESCipher(self._get_secret_key()) + return cipher.encrypt(message) + + def _decrypt_user_password(self, encrypted): + """ Attempts to decrypt the given password and returns it. """ + cipher = AESCipher(self._get_secret_key()) + + try: + message = cipher.decrypt(encrypted) + except ValueError: + return None + except TypeError: + return None + + try: + data = json.loads(message) + except ValueError: + return None + + return data.get('password', encrypted) + def verify_user(self, username_or_email, password, basic_auth=False): # First try to decode the password as a signed token. if basic_auth: - from app import app import features - ser = SecureCookieSessionInterface().get_signing_serializer(app) - - try: - token_data = ser.loads(password) - password = token_data.get('password', password) - except BadSignature: + decrypted = self._decrypt_user_password(password) + if decrypted is None: # This is a normal password. if features.REQUIRE_ENCRYPTED_BASIC_AUTH: msg = ('Client login with passwords is disabled. Please generate a client token ' + 'and use it in place of your password.') return (None, msg) + else: + password = decrypted result = self.state.verify_user(username_or_email, password) if result: diff --git a/endpoints/api/user.py b/endpoints/api/user.py index d10807940..9ccb1d7aa 100644 --- a/endpoints/api/user.py +++ b/endpoints/api/user.py @@ -5,7 +5,6 @@ from random import SystemRandom from flask import request from flask.ext.login import logout_user from flask.ext.principal import identity_changed, AnonymousIdentity -from flask.sessions import SecureCookieSessionInterface from peewee import IntegrityError from app import app, billing as stripe, authentication, avatar @@ -370,15 +369,8 @@ class ClientKey(ApiResource): if not result: raise request_error(message=error_message) - ser = SecureCookieSessionInterface().get_signing_serializer(app) - data_to_sign = { - 'password': password, - 'nonce': SystemRandom().randint(0, 10000000000) - } - - encrypted = ser.dumps(data_to_sign) return { - 'key': encrypted + 'key': authentication.encrypt_user_password(password) } diff --git a/static/directives/config/config-setup-tool.html b/static/directives/config/config-setup-tool.html index 9be7ddd37..aa0c60e5d 100644 --- a/static/directives/config/config-setup-tool.html +++ b/static/directives/config/config-setup-tool.html @@ -311,13 +311,13 @@