Guest User

Untitled

a guest
Jul 7th, 2018
158
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.79 KB | None | 0 0
  1. from user_interface import *
  2.  
  3. if __name__ == '__main__':
  4. main_menu()
  5.  
  6. import bcrypt
  7.  
  8.  
  9. def encrypt_password(password: str) -> str:
  10. """
  11. Encrypt a password with a randomly generated salt then a hash.
  12. Cost rounds added to slow down the process in case of rainbow table/brute force attack.
  13. :param password: The password to encrypt in clear text.
  14. :return: The encrypted password as a unicode string.
  15. """
  16. encoded_password = password.encode('utf8')
  17. cost_rounds = 12
  18. random_salt = bcrypt.gensalt(cost_rounds)
  19. hashed_password = bcrypt.hashpw(encoded_password, random_salt).decode('utf8', 'strict')
  20. return hashed_password
  21.  
  22.  
  23. def check_password(password: str, password_hash: str) -> bool:
  24. """
  25. Check a password against its encrypted hash for a match.
  26. :param password: the password to check in clear text. (Unicode)
  27. :param password_hash: The encrypted hash to check against. (Unicode)
  28. :return: Whether the password and the hash match
  29. """
  30. encoded_password = password.encode('utf8')
  31. encoded_password_hash = password_hash.encode('utf8')
  32. password_matches = bcrypt.checkpw(encoded_password, encoded_password_hash)
  33. return password_matches
  34.  
  35.  
  36. if __name__ == '__main__':
  37. test_password = 'Password1'
  38. hashed_test_password = encrypt_password(test_password)
  39. print(f'hashed_password: {hashed_test_password}')
  40. password_matches_hash = check_password(test_password, hashed_test_password)
  41. print(f'password matches hash? {password_matches_hash}')
  42.  
  43. import json
  44. from pathlib import Path
  45. from typing import List, Union
  46.  
  47.  
  48. def create_file_if_not_exists(file_path: str) -> None:
  49. """
  50. Checks if a file exists at the given path, and creates it if it doesn't.
  51. :param file_path: the path of the file to check/create, which can be relative or absolute.
  52. :return: None
  53. """
  54. Path(file_path).touch()
  55.  
  56.  
  57. def get_json_file_contents(file_path: str) -> Union[List, None]:
  58. """
  59. Reads and return the contents of a JSON file.
  60. :param file_path: The path where the JSON file is located.
  61. :return: The contents of the file, or None if there if the file is empty or not found.
  62. """
  63. try:
  64. json_file = open(file_path)
  65. except IOError:
  66. return None
  67. try:
  68. file_contents = json.load(json_file)
  69. except ValueError:
  70. file_contents = None
  71. json_file.close()
  72. return file_contents
  73.  
  74. import datetime
  75. from typing import Dict
  76. from encryption import *
  77. from json_handling import *
  78.  
  79.  
  80. DEFAULT_FILE_PATH = 'data/users.json'
  81.  
  82.  
  83. def prepare_new_user_data(username: str, password: str) -> Dict:
  84. """
  85. Return user data ready for storage.
  86. :param username: The username for this user.
  87. :param password: The password for this user, in clear text.
  88. :return: A Dict containing user data ready to store, including encrypted password.
  89. """
  90. new_user = {
  91. 'username': username,
  92. 'password': encrypt_password(password),
  93. 'created': str(datetime.datetime.now()),
  94. 'active': True
  95. }
  96. return new_user
  97.  
  98.  
  99. def check_if_user_already_exists(username: str, json_file_path: str=DEFAULT_FILE_PATH) -> bool:
  100. """
  101. Queries a JSON file and returns whether it already exists.
  102. :param username: The username to check for duplication.
  103. :param json_file_path: The path where the JSON file is located.
  104. :return: Whether the username already exists.
  105. """
  106. all_users = get_json_file_contents(json_file_path)
  107. if not all_users:
  108. return False
  109. for user in all_users:
  110. if user['username'] == username:
  111. return True
  112. return False
  113.  
  114.  
  115. def add_user(username: str, password: str, json_file_path: str=DEFAULT_FILE_PATH) -> None:
  116. """
  117. Adds a user to a JSON file, unless it is a duplicate, in which case it raises a ValueError.
  118. :param username: The username of the user to add.
  119. :param password: The password of the user to add, in clear text.
  120. :param json_file_path: The path where the JSON file to add the user to is located.
  121. :return: None
  122. """
  123. create_file_if_not_exists(json_file_path)
  124. is_duplicate_user = check_if_user_already_exists(username, json_file_path)
  125. if is_duplicate_user:
  126. raise ValueError(f'Username "{username}" already exists.')
  127. new_user = prepare_new_user_data(username, password)
  128. all_users = get_json_file_contents(json_file_path)
  129. if not all_users:
  130. all_users = []
  131. all_users.append(new_user)
  132. with open(json_file_path, 'w') as users_file:
  133. json.dump(all_users, users_file, indent=2)
  134.  
  135.  
  136. def retrieve_user(username: str, json_filepath: str=DEFAULT_FILE_PATH) -> Union[Dict, None]:
  137. """
  138. Returns a single user record from the target JSON file.
  139. :param username: the username to search for.
  140. :param json_filepath: The path where the JSON file to retrieve the user from is located.
  141. :return: The user record as a Dict, or None if it is not found.
  142. """
  143. all_users = get_json_file_contents(json_filepath)
  144. for user in all_users:
  145. if user['username'] == username:
  146. return user
  147. return None
  148.  
  149.  
  150. def authenticate_username_and_password(username: str, password: str) -> bool:
  151. """
  152. Verify that the provided username and password match what is stored in the user data,
  153. for authentication purposes.
  154. :param username: The user's username.
  155. :param password: The user's password, in clear text.
  156. :return: Whether the authentication was successful.
  157. """
  158. user = retrieve_user(username)
  159. password_hash = user['password']
  160. if not user:
  161. return False
  162. if not check_password(password, password_hash):
  163. return False
  164. return True
  165.  
  166.  
  167. if __name__ == '__main__':
  168. test_username = 'test1'
  169. test_password = 'Password1'
  170. print(prepare_new_user_data(test_username, test_password))
  171. test_file_path = 'data/test_database.json'
  172. create_file_if_not_exists(test_file_path)
  173. add_user(test_username, test_password, test_file_path)
  174. print(get_json_file_contents(test_file_path))
  175.  
  176. import getpass
  177. import re
  178. from user_storage import *
  179.  
  180.  
  181. def main_menu() -> None:
  182. """
  183. Displays the main menu of the application.
  184. :return: None
  185. """
  186. menu = 'n'.join([
  187. 'Select an option by entering its number and pressing Enter.',
  188. '1. Create a user account',
  189. '2. Log in to existing account',
  190. '---'
  191. ])
  192. print(menu)
  193. valid_selections = [1, 2]
  194. input_is_valid = False
  195. selection = None
  196. while not input_is_valid:
  197. try:
  198. selection = int(input('Selection: '))
  199. if selection in valid_selections:
  200. input_is_valid = True
  201. else:
  202. print('The number you entered is not a valid selection.')
  203. except ValueError:
  204. print('The value you entered is not a number.')
  205. handle_main_menu_selection(selection)
  206.  
  207.  
  208. def handle_main_menu_selection(selection: int) -> None:
  209. """
  210. Calls the function related to the selection the user made.
  211. :param selection: The user's selection.
  212. :return: None
  213. """
  214. if selection == 1:
  215. create_new_user_menu()
  216. elif selection == 2:
  217. user_login_menu()
  218. else:
  219. raise ValueError(f'Selection {selection} is invalid.')
  220.  
  221.  
  222. def create_new_user_menu() -> None:
  223. """
  224. Displays the account creation menu, including asking the user for username and password.
  225. :return: None
  226. """
  227. menu = 'n'.join([
  228. '---',
  229. 'Account creation',
  230. 'Username must...',
  231. 't- be at least 3 characters long',
  232. 't- contain only letters, numbers, and underscores',
  233. 'Password must...',
  234. 't- be at least 8 characters long',
  235. '---'
  236. ])
  237. print(menu)
  238. user_added_successfully = False
  239. username = ''
  240. while not user_added_successfully:
  241. try:
  242. username = get_username_input()
  243. password = get_password_input()
  244. user_added_successfully = try_adding_user(username, password)
  245. if not user_added_successfully:
  246. print(f'Username "{username}" already exists.')
  247. except ValueError as error:
  248. print(str(error))
  249.  
  250.  
  251. def try_adding_user(username: str, password: str) -> bool:
  252. """
  253. Attempts to add a user to the user database file.
  254. :param username: The username provided by the user.
  255. :param password: The password provided to the user, in clear text.
  256. :return: Whether the user was added successfully.
  257. """
  258. try:
  259. add_user(username, password)
  260. return True
  261. except ValueError:
  262. return False
  263.  
  264.  
  265. def user_login_menu() -> None:
  266. menu = 'n'.join([
  267. '---',
  268. 'User login',
  269. '---'
  270. ])
  271. print(menu)
  272. login_successful = False
  273. while not login_successful:
  274. username = get_username_input()
  275. password = get_password_input()
  276. login_successful = authenticate_username_and_password(username, password)
  277. if not login_successful:
  278. print('Incorrect username or password.')
  279. print('Login successful.')
  280.  
  281.  
  282. def get_username_input() -> str:
  283. """
  284. Request username input from the user.
  285. :return: The username entered by the user.
  286. """
  287. minimum_length = 3
  288. username = input('Enter username: ')
  289. if len(username) < minimum_length:
  290. raise ValueError('Username must be at least 3 characters.')
  291. # match upper & lower case letters, numbers, and underscores
  292. pattern = re.compile('^([a-zA-Z0-9_]+)$')
  293. if not pattern.match(username):
  294. raise ValueError('Username must consist of only letters, numbers, and underscores.')
  295. return username
  296.  
  297.  
  298. def get_password_input() -> str:
  299. """
  300. Request password input from the user.
  301. :return: The password entered by the user.
  302. """
  303. minimum_length = 8
  304. password = getpass.getpass('Enter password: ')
  305. if len(password) < minimum_length:
  306. raise ValueError('Password must be at least 8 characters.')
  307. return password
  308.  
  309.  
  310. if __name__ == '__main__':
  311. main_menu()
Add Comment
Please, Sign In to add comment