Source code for crate_anon.nlp_webserver.security

r"""
crate_anon/nlp_webserver/security.py

===============================================================================

    Copyright (C) 2015, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CRATE.

    CRATE is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    CRATE is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with CRATE. If not, see <https://www.gnu.org/licenses/>.

===============================================================================

Security functions for CRATE implementation of an NLPRP server.

"""

import bcrypt
import binascii
import base64
from typing import Optional

from cryptography.fernet import Fernet

# noinspection PyUnresolvedReferences
from paste.httpheaders import AUTHORIZATION
from pyramid.request import Request

from crate_anon.nlp_webserver.constants import NlpServerConfigKeys
from crate_anon.nlp_webserver.settings import SETTINGS


[docs]def generate_encryption_key() -> None: """ Generates a key to be used for reversible encryption of passwords and prints it to screen. The key should then be put in the config file. To be called via the command line. """ key = Fernet.generate_key() print(key.decode())
[docs]def encrypt_password(password: str) -> bytes: """ Encrypts a password using the configured key. """ key = SETTINGS[NlpServerConfigKeys.ENCRYPTION_KEY] # Turn key into bytes object key = key.encode() cipher_suite = Fernet(key) # Turn password into bytes object password_bytes = password.encode() return cipher_suite.encrypt(password_bytes)
[docs]def decrypt_password(encrypted_pw: bytes, cipher_suite: Fernet) -> str: """ Decrypts a password using the specified cipher suite. Args: encrypted_pw: the encrypted password, as bytes cipher_suite: a Python object with the method ``decrypt(encrypted_pw)`` Returns: the decrypted password as a string """ # Get the password as bytes password_bytes = cipher_suite.decrypt(encrypted_pw) # Return the password as a string return password_bytes.decode()
[docs]def hash_password(pw: str) -> str: """ Encrypts a password using bcrypt. """ pwhash = bcrypt.hashpw(pw.encode("utf8"), bcrypt.gensalt()) return pwhash.decode("utf8")
[docs]def check_password(pw: str, hashed_pw: str) -> bool: """ Checks a password against its hash. Args: pw: the clear-text password hashed_pw: the stored hashed version Returns: do they match? """ expected_hash = hashed_pw.encode("utf8") return bcrypt.checkpw(pw.encode("utf8"), expected_hash)
[docs]class Credentials: """ Represents username/password credentials sent by the user to us. """
[docs] def __init__(self, username: str, password: str) -> None: self.username = username self.password = password
[docs]def get_auth_credentials(request: Request) -> Optional[Credentials]: """ Gets username and password as a :class:`Credentials` obejct, from an HTTP request. Returns ``None`` if there is a problem. """ authorization = AUTHORIZATION(request.environ) try: authmeth, auth = authorization.split(" ", 1) except ValueError: # not enough values to unpack return None if authmeth.lower() == "basic": # ensure rquest is using basicauth try: # auth = auth.strip().decode('base64') auth = base64.b64decode(auth.strip()) except binascii.Error: # can't decode return None # Turn it back into a string auth = "".join(chr(x) for x in auth) try: username, password = auth.split(":", 1) except ValueError: # not enough values to unpack return None return Credentials(username, password) return None