| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | __filename__ = "auth.py" | 
					
						
							|  |  |  | __author__ = "Bob Mottram" | 
					
						
							|  |  |  | __license__ = "AGPL3+" | 
					
						
							| 
									
										
										
										
											2022-02-03 13:58:20 +00:00
										 |  |  | __version__ = "1.3.0" | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | __maintainer__ = "Bob Mottram" | 
					
						
							| 
									
										
										
										
											2021-09-10 16:14:50 +00:00
										 |  |  | __email__ = "bob@libreserver.org" | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | __status__ = "Production" | 
					
						
							| 
									
										
										
										
											2021-06-15 15:08:12 +00:00
										 |  |  | __module_group__ = "Security" | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | import base64 | 
					
						
							|  |  |  | import hashlib | 
					
						
							|  |  |  | import binascii | 
					
						
							|  |  |  | import os | 
					
						
							| 
									
										
										
										
											2020-09-03 18:13:29 +00:00
										 |  |  | import secrets | 
					
						
							| 
									
										
										
										
											2021-06-09 15:19:30 +00:00
										 |  |  | import datetime | 
					
						
							| 
									
										
										
										
											2021-12-27 15:41:04 +00:00
										 |  |  | from utils import is_system_account | 
					
						
							| 
									
										
										
										
											2021-12-26 12:19:00 +00:00
										 |  |  | from utils import has_users_path | 
					
						
							| 
									
										
										
										
											2022-06-10 09:24:11 +00:00
										 |  |  | from utils import text_in_file | 
					
						
							| 
									
										
										
										
											2022-06-21 11:58:50 +00:00
										 |  |  | from utils import remove_eol | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  | def _hash_password(password: str) -> str: | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     """Hash a password for storing
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii') | 
					
						
							|  |  |  |     pwdhash = hashlib.pbkdf2_hmac('sha512', | 
					
						
							|  |  |  |                                   password.encode('utf-8'), | 
					
						
							|  |  |  |                                   salt, 100000) | 
					
						
							|  |  |  |     pwdhash = binascii.hexlify(pwdhash) | 
					
						
							|  |  |  |     return (salt + pwdhash).decode('ascii') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  | def _get_password_hash(salt: str, provided_password: str) -> str: | 
					
						
							| 
									
										
										
										
											2020-09-03 18:07:02 +00:00
										 |  |  |     """Returns the hash of a password
 | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     pwdhash = hashlib.pbkdf2_hmac('sha512', | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |                                   provided_password.encode('utf-8'), | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |                                   salt.encode('ascii'), | 
					
						
							|  |  |  |                                   100000) | 
					
						
							| 
									
										
										
										
											2020-09-03 18:07:02 +00:00
										 |  |  |     return binascii.hexlify(pwdhash).decode('ascii') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 18:13:29 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  | def constant_time_string_check(string1: str, string2: str) -> bool: | 
					
						
							| 
									
										
										
										
											2020-09-03 18:48:32 +00:00
										 |  |  |     """Compares two string and returns if they are the same
 | 
					
						
							|  |  |  |     using a constant amount of time | 
					
						
							|  |  |  |     See https://sqreen.github.io/DevelopersSecurityBestPractices/ | 
					
						
							|  |  |  |     timing-attack/python | 
					
						
							| 
									
										
										
										
											2020-09-03 18:07:02 +00:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-09-03 18:48:32 +00:00
										 |  |  |     # strings must be of equal length | 
					
						
							|  |  |  |     if len(string1) != len(string2): | 
					
						
							| 
									
										
										
										
											2020-09-03 18:07:02 +00:00
										 |  |  |         return False | 
					
						
							|  |  |  |     ctr = 0 | 
					
						
							|  |  |  |     matched = True | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     for char in string1: | 
					
						
							|  |  |  |         if char != string2[ctr]: | 
					
						
							| 
									
										
										
										
											2020-09-03 18:07:02 +00:00
										 |  |  |             matched = False | 
					
						
							| 
									
										
										
										
											2020-09-03 18:13:29 +00:00
										 |  |  |         else: | 
					
						
							|  |  |  |             # this is to make the timing more even | 
					
						
							|  |  |  |             # and not provide clues | 
					
						
							|  |  |  |             matched = matched | 
					
						
							| 
									
										
										
										
											2020-09-03 18:07:02 +00:00
										 |  |  |         ctr += 1 | 
					
						
							|  |  |  |     return matched | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  | def _verify_password(stored_password: str, provided_password: str) -> bool: | 
					
						
							| 
									
										
										
										
											2020-09-03 18:48:32 +00:00
										 |  |  |     """Verify a stored password against one provided by user
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     if not stored_password: | 
					
						
							| 
									
										
										
										
											2020-09-03 18:48:32 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     if not provided_password: | 
					
						
							| 
									
										
										
										
											2020-09-03 18:48:32 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     salt = stored_password[:64] | 
					
						
							|  |  |  |     stored_password = stored_password[64:] | 
					
						
							|  |  |  |     pw_hash = _get_password_hash(salt, provided_password) | 
					
						
							|  |  |  |     return constant_time_string_check(pw_hash, stored_password) | 
					
						
							| 
									
										
										
										
											2020-09-03 18:48:32 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-28 21:36:27 +00:00
										 |  |  | def create_basic_auth_header(nickname: str, password: str) -> str: | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     """This is only used by tests
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     auth_str = \ | 
					
						
							| 
									
										
										
										
											2022-06-21 11:58:50 +00:00
										 |  |  |         remove_eol(nickname) + \ | 
					
						
							| 
									
										
										
										
											2020-05-22 11:32:38 +00:00
										 |  |  |         ':' + \ | 
					
						
							| 
									
										
										
										
											2022-06-21 11:58:50 +00:00
										 |  |  |         remove_eol(password) | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     return 'Basic ' + \ | 
					
						
							|  |  |  |         base64.b64encode(auth_str.encode('utf-8')).decode('utf-8') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  | def authorize_basic(base_dir: str, path: str, auth_header: str, | 
					
						
							| 
									
										
										
										
											2021-12-28 21:36:27 +00:00
										 |  |  |                     debug: bool) -> bool: | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     """HTTP basic auth
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     if ' ' not in auth_header: | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         if debug: | 
					
						
							| 
									
										
										
										
											2021-07-25 21:18:38 +00:00
										 |  |  |             print('DEBUG: basic auth - Authorisation header does not ' + | 
					
						
							| 
									
										
										
										
											2020-03-30 19:09:45 +00:00
										 |  |  |                   'contain a space character') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-12-26 12:19:00 +00:00
										 |  |  |     if not has_users_path(path): | 
					
						
							| 
									
										
										
										
											2022-02-24 16:55:37 +00:00
										 |  |  |         if not path.startswith('/calendars/'): | 
					
						
							|  |  |  |             if debug: | 
					
						
							|  |  |  |                 print('DEBUG: basic auth - ' + | 
					
						
							|  |  |  |                       'path for Authorization does not contain a user') | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  |     if path.startswith('/calendars/'): | 
					
						
							|  |  |  |         path_users_section = path.split('/calendars/')[1] | 
					
						
							|  |  |  |         nickname_from_path = path_users_section | 
					
						
							|  |  |  |         if '/' in nickname_from_path: | 
					
						
							|  |  |  |             nickname_from_path = nickname_from_path.split('/')[0] | 
					
						
							|  |  |  |         if '?' in nickname_from_path: | 
					
						
							|  |  |  |             nickname_from_path = nickname_from_path.split('?')[0] | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         path_users_section = path.split('/users/')[1] | 
					
						
							|  |  |  |         if '/' not in path_users_section: | 
					
						
							|  |  |  |             if debug: | 
					
						
							|  |  |  |                 print('DEBUG: basic auth - this is not a users endpoint') | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  |         nickname_from_path = path_users_section.split('/')[0] | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     if is_system_account(nickname_from_path): | 
					
						
							| 
									
										
										
										
											2020-11-23 09:51:26 +00:00
										 |  |  |         print('basic auth - attempted login using system account ' + | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |               nickname_from_path + ' in path') | 
					
						
							| 
									
										
										
										
											2020-11-23 09:51:26 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2022-06-21 11:58:50 +00:00
										 |  |  |     base64_str1 = auth_header.split(' ')[1] | 
					
						
							|  |  |  |     base64_str = remove_eol(base64_str1) | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     plain = base64.b64decode(base64_str).decode('utf-8') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     if ':' not in plain: | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         if debug: | 
					
						
							| 
									
										
										
										
											2020-11-23 10:18:52 +00:00
										 |  |  |             print('DEBUG: basic auth header does not contain a ":" ' + | 
					
						
							| 
									
										
										
										
											2020-03-30 19:09:45 +00:00
										 |  |  |                   'separator for username:password') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     nickname = plain.split(':')[0] | 
					
						
							| 
									
										
										
										
											2021-12-27 15:41:04 +00:00
										 |  |  |     if is_system_account(nickname): | 
					
						
							| 
									
										
										
										
											2020-11-23 09:51:26 +00:00
										 |  |  |         print('basic auth - attempted login using system account ' + nickname + | 
					
						
							|  |  |  |               ' in Auth header') | 
					
						
							|  |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     if nickname != nickname_from_path: | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         if debug: | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |             print('DEBUG: Nickname given in the path (' + nickname_from_path + | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |                   ') does not match the one in the Authorization header (' + | 
					
						
							|  |  |  |                   nickname + ')') | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     password_file = base_dir + '/accounts/passwords' | 
					
						
							|  |  |  |     if not os.path.isfile(password_file): | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         if debug: | 
					
						
							|  |  |  |             print('DEBUG: passwords file missing') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     provided_password = plain.split(':')[1] | 
					
						
							| 
									
										
										
										
											2021-11-26 12:28:20 +00:00
										 |  |  |     try: | 
					
						
							| 
									
										
										
										
											2022-06-09 14:46:30 +00:00
										 |  |  |         with open(password_file, 'r', encoding='utf-8') as passfile: | 
					
						
							| 
									
										
										
										
											2021-11-26 12:28:20 +00:00
										 |  |  |             for line in passfile: | 
					
						
							|  |  |  |                 if not line.startswith(nickname + ':'): | 
					
						
							|  |  |  |                     continue | 
					
						
							| 
									
										
										
										
											2022-06-21 11:58:50 +00:00
										 |  |  |                 stored_password_base = line.split(':')[1] | 
					
						
							|  |  |  |                 stored_password = remove_eol(stored_password_base) | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |                 success = _verify_password(stored_password, provided_password) | 
					
						
							| 
									
										
										
										
											2021-11-26 12:28:20 +00:00
										 |  |  |                 if not success: | 
					
						
							|  |  |  |                     if debug: | 
					
						
							|  |  |  |                         print('DEBUG: Password check failed for ' + nickname) | 
					
						
							|  |  |  |                 return success | 
					
						
							|  |  |  |     except OSError: | 
					
						
							|  |  |  |         print('EX: failed to open password file') | 
					
						
							|  |  |  |         return False | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     print('DEBUG: Did not find credentials for ' + nickname + | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |           ' in ' + password_file) | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-28 21:36:27 +00:00
										 |  |  | def store_basic_credentials(base_dir: str, | 
					
						
							|  |  |  |                             nickname: str, password: str) -> bool: | 
					
						
							| 
									
										
										
										
											2019-07-05 09:51:58 +00:00
										 |  |  |     """Stores login credentials to a file
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     if ':' in nickname or ':' in password: | 
					
						
							|  |  |  |         return False | 
					
						
							| 
									
										
										
										
											2022-06-21 11:58:50 +00:00
										 |  |  |     nickname = remove_eol(nickname).strip() | 
					
						
							|  |  |  |     password = remove_eol(password).strip() | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-25 16:17:53 +00:00
										 |  |  |     if not os.path.isdir(base_dir + '/accounts'): | 
					
						
							|  |  |  |         os.mkdir(base_dir + '/accounts') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     password_file = base_dir + '/accounts/passwords' | 
					
						
							|  |  |  |     store_str = nickname + ':' + _hash_password(password) | 
					
						
							|  |  |  |     if os.path.isfile(password_file): | 
					
						
							| 
									
										
										
										
											2022-06-10 09:24:11 +00:00
										 |  |  |         if text_in_file(nickname + ':', password_file): | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |             try: | 
					
						
							| 
									
										
										
										
											2022-06-09 14:46:30 +00:00
										 |  |  |                 with open(password_file, 'r', encoding='utf-8') as fin: | 
					
						
							|  |  |  |                     with open(password_file + '.new', 'w+', | 
					
						
							|  |  |  |                               encoding='utf-8') as fout: | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |                         for line in fin: | 
					
						
							|  |  |  |                             if not line.startswith(nickname + ':'): | 
					
						
							|  |  |  |                                 fout.write(line) | 
					
						
							|  |  |  |                             else: | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |                                 fout.write(store_str + '\n') | 
					
						
							| 
									
										
										
										
											2021-12-25 15:28:52 +00:00
										 |  |  |             except OSError as ex: | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |                 print('EX: unable to save password ' + password_file + | 
					
						
							| 
									
										
										
										
											2021-12-25 15:28:52 +00:00
										 |  |  |                       ' ' + str(ex)) | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |                 return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             try: | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |                 os.rename(password_file + '.new', password_file) | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |             except OSError: | 
					
						
							| 
									
										
										
										
											2021-11-25 22:22:54 +00:00
										 |  |  |                 print('EX: unable to save password 2') | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |                 return False | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |         else: | 
					
						
							|  |  |  |             # append to password file | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |             try: | 
					
						
							| 
									
										
										
										
											2022-06-09 14:46:30 +00:00
										 |  |  |                 with open(password_file, 'a+', encoding='utf-8') as passfile: | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |                     passfile.write(store_str + '\n') | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |             except OSError: | 
					
						
							| 
									
										
										
										
											2021-11-25 22:22:54 +00:00
										 |  |  |                 print('EX: unable to append password') | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |                 return False | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2022-06-09 14:46:30 +00:00
										 |  |  |             with open(password_file, 'w+', encoding='utf-8') as passfile: | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |                 passfile.write(store_str + '\n') | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |         except OSError: | 
					
						
							| 
									
										
										
										
											2021-11-25 22:22:54 +00:00
										 |  |  |             print('EX: unable to create password file') | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |             return False | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     return True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 21:55:09 +00:00
										 |  |  | def remove_password(base_dir: str, nickname: str) -> None: | 
					
						
							| 
									
										
										
										
											2019-07-05 09:51:58 +00:00
										 |  |  |     """Removes the password entry for the given nickname
 | 
					
						
							|  |  |  |     This is called during account removal | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     password_file = base_dir + '/accounts/passwords' | 
					
						
							|  |  |  |     if os.path.isfile(password_file): | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2022-06-09 14:46:30 +00:00
										 |  |  |             with open(password_file, 'r', encoding='utf-8') as fin: | 
					
						
							|  |  |  |                 with open(password_file + '.new', 'w+', | 
					
						
							|  |  |  |                           encoding='utf-8') as fout: | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |                     for line in fin: | 
					
						
							|  |  |  |                         if not line.startswith(nickname + ':'): | 
					
						
							|  |  |  |                             fout.write(line) | 
					
						
							| 
									
										
										
										
											2021-12-25 15:28:52 +00:00
										 |  |  |         except OSError as ex: | 
					
						
							|  |  |  |             print('EX: unable to remove password from file ' + str(ex)) | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |             os.rename(password_file + '.new', password_file) | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |         except OSError: | 
					
						
							| 
									
										
										
										
											2021-11-25 22:22:54 +00:00
										 |  |  |             print('EX: unable to remove password from file 2') | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |             return | 
					
						
							| 
									
										
										
										
											2019-07-05 09:49:57 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  | def authorize(base_dir: str, path: str, auth_header: str, debug: bool) -> bool: | 
					
						
							| 
									
										
										
										
											2019-07-05 09:51:58 +00:00
										 |  |  |     """Authorize using http header
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     if auth_header.lower().startswith('basic '): | 
					
						
							|  |  |  |         return authorize_basic(base_dir, path, auth_header, debug) | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     return False | 
					
						
							| 
									
										
										
										
											2019-07-05 11:27:18 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-28 21:36:27 +00:00
										 |  |  | def create_password(length: int): | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     valid_chars = 'abcdefghijklmnopqrstuvwxyz' + \ | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |         'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     return ''.join((secrets.choice(valid_chars) for i in range(length))) | 
					
						
							| 
									
										
										
										
											2021-06-09 14:27:35 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  | def record_login_failure(base_dir: str, ip_address: str, | 
					
						
							|  |  |  |                          count_dict: {}, fail_time: int, | 
					
						
							|  |  |  |                          log_to_file: bool) -> None: | 
					
						
							| 
									
										
										
										
											2021-06-09 14:27:35 +00:00
										 |  |  |     """Keeps ip addresses and the number of times login failures
 | 
					
						
							|  |  |  |     occured for them in a dict | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     if not count_dict.get(ip_address): | 
					
						
							|  |  |  |         while len(count_dict.items()) > 100: | 
					
						
							|  |  |  |             oldest_time = 0 | 
					
						
							|  |  |  |             oldest_ip = None | 
					
						
							|  |  |  |             for ip_addr, ip_item in count_dict.items(): | 
					
						
							|  |  |  |                 if oldest_time == 0 or ip_item['time'] < oldest_time: | 
					
						
							|  |  |  |                     oldest_time = ip_item['time'] | 
					
						
							|  |  |  |                     oldest_ip = ip_addr | 
					
						
							|  |  |  |             if oldest_ip: | 
					
						
							|  |  |  |                 del count_dict[oldest_ip] | 
					
						
							|  |  |  |         count_dict[ip_address] = { | 
					
						
							| 
									
										
										
										
											2021-06-09 14:27:35 +00:00
										 |  |  |             "count": 1, | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |             "time": fail_time | 
					
						
							| 
									
										
										
										
											2021-06-09 14:27:35 +00:00
										 |  |  |         } | 
					
						
							|  |  |  |     else: | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |         count_dict[ip_address]['count'] += 1 | 
					
						
							|  |  |  |         count_dict[ip_address]['time'] = fail_time | 
					
						
							|  |  |  |         fail_count = count_dict[ip_address]['count'] | 
					
						
							|  |  |  |         if fail_count > 4: | 
					
						
							|  |  |  |             print('WARN: ' + str(ip_address) + ' failed to log in ' + | 
					
						
							|  |  |  |                   str(fail_count) + ' times') | 
					
						
							| 
									
										
										
										
											2021-06-09 15:19:30 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     if not log_to_file: | 
					
						
							| 
									
										
										
										
											2021-06-09 15:19:30 +00:00
										 |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     failure_log = base_dir + '/accounts/loginfailures.log' | 
					
						
							|  |  |  |     write_type = 'a+' | 
					
						
							|  |  |  |     if not os.path.isfile(failure_log): | 
					
						
							|  |  |  |         write_type = 'w+' | 
					
						
							| 
									
										
										
										
											2021-12-26 13:17:46 +00:00
										 |  |  |     curr_time = datetime.datetime.utcnow() | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |     curr_time_str = curr_time.strftime("%Y-%m-%d %H:%M:%SZ") | 
					
						
							| 
									
										
										
										
											2021-06-21 22:53:04 +00:00
										 |  |  |     try: | 
					
						
							| 
									
										
										
										
											2022-06-09 14:46:30 +00:00
										 |  |  |         with open(failure_log, write_type, encoding='utf-8') as fp_fail: | 
					
						
							| 
									
										
										
										
											2021-06-21 22:53:04 +00:00
										 |  |  |             # here we use a similar format to an ssh log, so that | 
					
						
							|  |  |  |             # systems such as fail2ban can parse it | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |             fp_fail.write(curr_time_str + ' ' + | 
					
						
							|  |  |  |                           'ip-127-0-0-1 sshd[20710]: ' + | 
					
						
							|  |  |  |                           'Disconnecting invalid user epicyon ' + | 
					
						
							|  |  |  |                           ip_address + ' port 443: ' + | 
					
						
							|  |  |  |                           'Too many authentication failures [preauth]\n') | 
					
						
							| 
									
										
										
										
											2021-11-25 18:42:38 +00:00
										 |  |  |     except OSError: | 
					
						
							| 
									
										
										
										
											2021-12-29 23:10:55 +00:00
										 |  |  |         print('EX: record_login_failure failed ' + str(failure_log)) |