Coverage for nlp_webserver/security.py: 57%
49 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-08-27 10:34 -0500
« prev ^ index » next coverage.py v7.8.0, created at 2025-08-27 10:34 -0500
1r"""
2crate_anon/nlp_webserver/security.py
4===============================================================================
6 Copyright (C) 2015, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CRATE.
11 CRATE is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
16 CRATE is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with CRATE. If not, see <https://www.gnu.org/licenses/>.
24===============================================================================
26Security functions for CRATE implementation of an NLPRP server.
28"""
30import bcrypt
31import binascii
32import base64
33from typing import Optional
35from cryptography.fernet import Fernet
37# noinspection PyUnresolvedReferences
38from paste.httpheaders import AUTHORIZATION
39from pyramid.request import Request
41from crate_anon.nlp_webserver.constants import NlpServerConfigKeys
42from crate_anon.nlp_webserver.settings import SETTINGS
45def generate_encryption_key() -> None:
46 """
47 Generates a key to be used for reversible encryption of passwords and
48 prints it to screen. The key should then be put in the config file.
50 To be called via the command line.
51 """
52 key = Fernet.generate_key()
53 print(key.decode())
56def encrypt_password(password: str) -> bytes:
57 """
58 Encrypts a password using the configured key.
59 """
60 key = SETTINGS[NlpServerConfigKeys.ENCRYPTION_KEY]
61 # Turn key into bytes object
62 key = key.encode()
63 cipher_suite = Fernet(key)
64 # Turn password into bytes object
65 password_bytes = password.encode()
66 return cipher_suite.encrypt(password_bytes)
69def decrypt_password(encrypted_pw: bytes, cipher_suite: Fernet) -> str:
70 """
71 Decrypts a password using the specified cipher suite.
73 Args:
74 encrypted_pw: the encrypted password, as bytes
75 cipher_suite: a Python object with the method ``decrypt(encrypted_pw)``
77 Returns:
78 the decrypted password as a string
79 """
80 # Get the password as bytes
81 password_bytes = cipher_suite.decrypt(encrypted_pw)
82 # Return the password as a string
83 return password_bytes.decode()
86def hash_password(pw: str) -> str:
87 """
88 Encrypts a password using bcrypt.
89 """
90 pwhash = bcrypt.hashpw(pw.encode("utf8"), bcrypt.gensalt())
91 return pwhash.decode("utf8")
94def check_password(pw: str, hashed_pw: str) -> bool:
95 """
96 Checks a password against its hash.
98 Args:
99 pw: the clear-text password
100 hashed_pw: the stored hashed version
102 Returns:
103 do they match?
105 """
106 expected_hash = hashed_pw.encode("utf8")
107 return bcrypt.checkpw(pw.encode("utf8"), expected_hash)
110class Credentials:
111 """
112 Represents username/password credentials sent by the user to us.
113 """
115 def __init__(self, username: str, password: str) -> None:
116 self.username = username
117 self.password = password
120def get_auth_credentials(request: Request) -> Optional[Credentials]:
121 """
122 Gets username and password as a :class:`Credentials` obejct, from an HTTP
123 request. Returns ``None`` if there is a problem.
124 """
125 authorization = AUTHORIZATION(request.environ)
126 try:
127 authmeth, auth = authorization.split(" ", 1)
128 except ValueError: # not enough values to unpack
129 return None
130 if authmeth.lower() == "basic": # ensure rquest is using basicauth
131 try:
132 # auth = auth.strip().decode('base64')
133 auth = base64.b64decode(auth.strip())
134 except binascii.Error: # can't decode
135 return None
136 # Turn it back into a string
137 auth = "".join(chr(x) for x in auth)
138 try:
139 username, password = auth.split(":", 1)
140 except ValueError: # not enough values to unpack
141 return None
142 return Credentials(username, password)
144 return None