├── 0x00-personal_data ├── 0-main.py ├── 1-main.py ├── 2-main.py ├── 3-main.py ├── 3-main.sql ├── 4-main.sql ├── 5-main.py ├── 6-main.py ├── README.md ├── encrypt_password.py ├── filtered_logger.py └── user_data.csv ├── 0x01-Basic_authentication ├── .db_User.json ├── .gitignore ├── README.md ├── SimpleAPI │ ├── README.md │ ├── api │ │ ├── __init__.py │ │ └── v1 │ │ │ ├── __init__.py │ │ │ ├── app.py │ │ │ └── views │ │ │ ├── __init__.py │ │ │ ├── index.py │ │ │ └── users.py │ ├── models │ │ ├── __init__.py │ │ ├── base.py │ │ └── user.py │ └── requirements.txt ├── __MACOSX │ ├── ._SimpleAPI │ └── SimpleAPI │ │ ├── ._README.md │ │ ├── ._api │ │ ├── ._models │ │ ├── ._requirements.txt │ │ ├── api │ │ ├── ._.DS_Store │ │ ├── .___init__.py │ │ ├── ._v1 │ │ └── v1 │ │ │ ├── ._.DS_Store │ │ │ ├── .___init__.py │ │ │ ├── ._app.py │ │ │ ├── ._views │ │ │ └── views │ │ │ ├── ._.DS_Store │ │ │ ├── .___init__.py │ │ │ ├── ._index.py │ │ │ └── ._users.py │ │ └── models │ │ ├── ._.DS_Store │ │ ├── .___init__.py │ │ ├── ._base.py │ │ └── ._user.py ├── api │ ├── __init__.py │ └── v1 │ │ ├── __init__.py │ │ ├── app.py │ │ ├── auth │ │ ├── __init__.py │ │ ├── auth.py │ │ └── basic_auth.py │ │ └── views │ │ ├── __init__.py │ │ ├── index.py │ │ └── users.py ├── archive.zip ├── images │ └── meme.png ├── main_0.py ├── main_1.py ├── main_100.py ├── main_2.py ├── main_3.py ├── main_4.py ├── main_5.py ├── main_6.py ├── models │ ├── .DS_Store │ ├── __init__.py │ ├── base.py │ └── user.py └── requirements.txt ├── 0x02-Session_authentication ├── .db_User.json ├── .gitignore ├── README.md ├── api │ ├── .DS_Store │ ├── __init__.py │ └── v1 │ │ ├── .DS_Store │ │ ├── __init__.py │ │ ├── app.py │ │ ├── auth │ │ ├── __init__.py │ │ ├── auth.py │ │ ├── basic_auth.py │ │ ├── session_auth.py │ │ ├── session_db_auth.py │ │ └── session_exp_auth.py │ │ └── views │ │ ├── .DS_Store │ │ ├── __init__.py │ │ ├── index.py │ │ ├── session_auth.py │ │ └── users.py ├── main_0.py ├── main_1.py ├── main_2.py ├── main_3.py ├── main_4.py ├── main_5.py ├── main_6.py ├── models │ ├── .DS_Store │ ├── __init__.py │ ├── base.py │ ├── user.py │ └── user_session.py └── requirements.txt ├── 0x03-user_authentication_service ├── README.md ├── app.py ├── auth.py ├── db.py ├── main.py └── user.py └── README.md /0x00-personal_data/0-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Main file 4 | """ 5 | 6 | filter_datum = __import__('filtered_logger').filter_datum 7 | 8 | fields = ["password", "date_of_birth"] 9 | messages = ["name=egg;email=eggmin@eggsample.com;password=eggcellent;date_of_birth=12/12/1986;", "name=bob;email=bob@dylan.com;password=bobbycool;date_of_birth=03/04/1993;"] 10 | 11 | for message in messages: 12 | print(filter_datum(fields, 'xxx', message, ';')) 13 | 14 | -------------------------------------------------------------------------------- /0x00-personal_data/1-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Main file 4 | """ 5 | 6 | import logging 7 | import re 8 | 9 | RedactingFormatter = __import__('filtered_logger').RedactingFormatter 10 | 11 | message = "name=Bob;email=bob@dylan.com;ssn=000-123-0000;password=bobby2019;" 12 | log_record = logging.LogRecord("my_logger", logging.INFO, None, None, message, None, None) 13 | formatter = RedactingFormatter(fields=("email", "ssn", "password")) 14 | print(formatter.format(log_record)) 15 | -------------------------------------------------------------------------------- /0x00-personal_data/2-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Main file 4 | """ 5 | 6 | import logging 7 | 8 | get_logger = __import__('filtered_logger').get_logger 9 | PII_FIELDS = __import__('filtered_logger').PII_FIELDS 10 | 11 | print(get_logger.__annotations__.get('return')) 12 | print("PII_FIELDS: {}".format(len(PII_FIELDS))) 13 | -------------------------------------------------------------------------------- /0x00-personal_data/3-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Main file 4 | """ 5 | 6 | get_db = __import__('filtered_logger').get_db 7 | 8 | db = get_db() 9 | cursor = db.cursor() 10 | cursor.execute("SELECT COUNT(*) FROM users;") 11 | for row in cursor: 12 | print(row[0]) 13 | cursor.close() 14 | db.close() 15 | -------------------------------------------------------------------------------- /0x00-personal_data/3-main.sql: -------------------------------------------------------------------------------- 1 | -- setup mysql server 2 | -- configure permissions 3 | CREATE DATABASE IF NOT EXISTS my_db; 4 | CREATE USER IF NOT EXISTS root@localhost IDENTIFIED BY 'root'; 5 | GRANT ALL PRIVILEGES ON my_db.* TO 'root'@'localhost'; 6 | 7 | USE my_db; 8 | 9 | DROP TABLE IF EXISTS users; 10 | CREATE TABLE users ( 11 | email VARCHAR(256) 12 | ); 13 | 14 | INSERT INTO users(email) VALUES ("bob@dylan.com"); 15 | INSERT INTO users(email) VALUES ("bib@dylan.com"); 16 | -------------------------------------------------------------------------------- /0x00-personal_data/4-main.sql: -------------------------------------------------------------------------------- 1 | -- setup mysql server 2 | -- configure permissions 3 | CREATE DATABASE IF NOT EXISTS my_db; 4 | CREATE USER IF NOT EXISTS root@localhost IDENTIFIED BY 'root'; 5 | GRANT ALL PRIVILEGES ON my_db.* TO root@localhost; 6 | 7 | USE my_db; 8 | 9 | DROP TABLE IF EXISTS users; 10 | CREATE TABLE users ( 11 | name VARCHAR(256), 12 | email VARCHAR(256), 13 | phone VARCHAR(16), 14 | ssn VARCHAR(16), 15 | password VARCHAR(256), 16 | ip VARCHAR(64), 17 | last_login TIMESTAMP, 18 | user_agent VARCHAR(512) 19 | ); 20 | 21 | INSERT INTO users(name, email, phone, ssn, password, ip, last_login, user_agent) VALUES ("Marlene Wood","hwestiii@att.net","(473) 401-4253","261-72-6780","K5?BMNv","60ed:c396:2ff:244:bbd0:9208:26f2:93ea","2019-11-14 06:14:24","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36"); 22 | INSERT INTO users(name, email, phone, ssn, password, ip, last_login, user_agent) VALUES ("Belen Bailey","bcevc@yahoo.com","(539) 233-4942","203-38-5395","^3EZ~TkX","f724:c5d1:a14d:c4c5:bae2:9457:3769:1969","2019-11-14 06:16:19","Mozilla/5.0 (Linux; U; Android 4.1.2; de-de; GT-I9100 Build/JZO54K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30"); 23 | -------------------------------------------------------------------------------- /0x00-personal_data/5-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Main file 4 | """ 5 | 6 | hash_password = __import__('encrypt_password').hash_password 7 | 8 | password = "MyAmazingPassw0rd" 9 | print(hash_password(password)) 10 | print(hash_password(password)) 11 | -------------------------------------------------------------------------------- /0x00-personal_data/6-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Main file 4 | """ 5 | 6 | hash_password = __import__('encrypt_password').hash_password 7 | is_valid = __import__('encrypt_password').is_valid 8 | 9 | password = "MyAmazingPassw0rd" 10 | encrypted_password = hash_password(password) 11 | print(encrypted_password) 12 | print(is_valid(encrypted_password, password)) 13 | -------------------------------------------------------------------------------- /0x00-personal_data/README.md: -------------------------------------------------------------------------------- 1 | # 0x05. Personal data 2 | :open_file_folder: Specializations - Web Stack programming ― Back-end 3 | :bust_in_silhouette: by Emmanuel Turlay, Staff Software Engineer at Cruise 4 | :copyright: **[Holberton School](https://www.holbertonschool.com/)** 5 | :bookmark: database access | log filter | password encryption | personal data | personally identifiable information | pii | python | user data protection 6 | 7 | ## Resources 8 | ### Read or watch: 9 | * [What Is PII, non-PII, and Personal Data?](https://piwik.pro/blog/what-is-pii-personal-data/) 10 | * [logging documentation](https://docs.python.org/3/library/logging.html) 11 | * [bcrypt package](https://github.com/pyca/bcrypt/) 12 | * [Logging to Files, Setting Levels, and Formatting](https://www.youtube.com/watch?v=-ARI4Cz-awo) 13 | * [user_data.csv](https://holbertonintranet.s3.amazonaws.com/uploads/misc/2019/11/a2e00974ce6b41460425.csv?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIARDDGGGOUWMNL5ANN%2F20201102%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20201102T202422Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=2ab6e9982734a98964a9979d6e3d4c5cd37b68ebe0c75f476265a3f035972881) 14 | * [Uncovering Password Habits](https://digitalguardian.com/blog/uncovering-password-habits-are-users-password-security-habits-improving-infographic) 15 | 16 | ## Learning Objectives 17 | At the end of this project, you are expected to be able to [explain to anyone](https://fs.blog/2012/04/feynman-technique/), without the help of Google: 18 | ### General 19 | * Examples of Personally Identifiable Information (PII) 20 | * How to implement a log filter that will obfuscate PII fields 21 | * How to encrypt a password and check the validity of an input password 22 | * How to authenticate to a database using environment variables 23 | 24 | ## Requirements 25 | * A ```README.md``` file. 26 | 27 | ## Tasks 28 | * [x] 0. Regex-ing 29 | * [x] 1. Log formatter 30 | * [x] 2. Create logger 31 | * [x] 3. Connect to secure database 32 | * [x] 4. Read and filter data 33 | * [x] 5. Encrypting passwords 34 | * [x] 6. Check valid password 35 | 36 | ## Software engineer 37 | Javier Andrés Garzón Patarroyo 38 | :octocat: [GitHub](https://github.com/javierandresgp/) 39 | -------------------------------------------------------------------------------- /0x00-personal_data/encrypt_password.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Encrypting passwords """ 3 | import bcrypt 4 | 5 | 6 | def hash_password(password: str) -> bytes: 7 | """ expects one string argument name password and returns a salted, 8 | hashed password, which is a byte string. """ 9 | return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()) 10 | 11 | 12 | def is_valid(hashed_password: bytes, password: str) -> bool: 13 | """ expects 2 arguments and returns a boolean. """ 14 | return bcrypt.checkpw(password.encode('utf-8'), hashed_password) 15 | -------------------------------------------------------------------------------- /0x00-personal_data/filtered_logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Regex-ing, Log formatter, Create logger, Connect to secure database, 3 | Read and filter data """ 4 | from typing import List 5 | import re 6 | import logging 7 | import os 8 | import mysql.connector 9 | PII_FIELDS = ('name', 'email', 'phone', 'ssn', 'password') 10 | """ containing the fields from user_data.csv that are considered PII. """ 11 | 12 | 13 | class RedactingFormatter(logging.Formatter): 14 | """ Redacting Formatter class 15 | """ 16 | 17 | REDACTION = "***" 18 | FORMAT = "[HOLBERTON] %(name)s %(levelname)s %(asctime)-15s: %(message)s" 19 | SEPARATOR = ";" 20 | 21 | def __init__(self, fields: List[str]): 22 | """ constructor """ 23 | self.fields = fields 24 | super(RedactingFormatter, self).__init__(self.FORMAT) 25 | 26 | def format(self, record: logging.LogRecord) -> str: 27 | """ filter values in incoming log records """ 28 | return filter_datum(self.fields, self.REDACTION, 29 | super().format(record), self.SEPARATOR) 30 | 31 | 32 | def filter_datum(fields: List[str], 33 | redaction: str, 34 | message: str, 35 | separator: str) -> str: 36 | """ returns the log message obfuscated """ 37 | for item in fields: 38 | message = re.sub(fr'{item}=.+?{separator}', 39 | f'{item}={redaction}{separator}', message) 40 | return message 41 | 42 | 43 | def get_logger() -> logging.Logger: 44 | """ returns a logging.Logger object """ 45 | logger = logging.getLogger("user_data") 46 | logger.setLevel(logging.INFO) 47 | logger.propagate = False 48 | handler = logging.StreamHandler() 49 | handler.setFormatter(RedactingFormatter(PII_FIELDS)) 50 | logger.addHandler(handler) 51 | return logger 52 | 53 | 54 | def get_db() -> mysql.connector.connection.MySQLConnection: 55 | """ returns a connector to the database """ 56 | return mysql.connector.connect( 57 | host=os.environ.get('PERSONAL_DATA_DB_HOST', 'localhost'), 58 | database=os.environ.get('PERSONAL_DATA_DB_NAME', 'root'), 59 | user=os.environ.get('PERSONAL_DATA_DB_USERNAME'), 60 | password=os.environ.get('PERSONAL_DATA_DB_PASSWORD', '')) 61 | 62 | 63 | def main(): 64 | """ obtain a database connection using get_db and retrieve all rows in the 65 | users table and display each row under a filtered format """ 66 | db = get_db() 67 | cursor = db.cursor() 68 | cursor.execute("SELECT * FROM users;") 69 | result = cursor.fetchall() 70 | for row in result: 71 | message = f"name={row[0]}; " + \ 72 | f"email={row[1]}; " + \ 73 | f"phone={row[2]}; " + \ 74 | f"ssn={row[3]}; " + \ 75 | f"password={row[4]};" 76 | print(message) 77 | log_record = logging.LogRecord("my_logger", logging.INFO, 78 | None, None, message, None, None) 79 | formatter = RedactingFormatter(PII_FIELDS) 80 | formatter.format(log_record) 81 | cursor.close() 82 | db.close() 83 | 84 | 85 | if __name__ == "__main__": 86 | main() 87 | -------------------------------------------------------------------------------- /0x00-personal_data/user_data.csv: -------------------------------------------------------------------------------- 1 | name,email,phone,ssn,password,ip,last_login,user_agent 2 | "Marlene Wood","hwestiii@att.net","(473) 401-4253","261-72-6780","K5?BMNv","60ed:c396:2ff:244:bbd0:9208:26f2:93ea","2019-11-14 06:14:24","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" 3 | "Rhianna Barrera","hampton@me.com","(473) 310-1175","494-07-2341","cj22zAH&","6131:10bb:3bcf:6ce5:497c:fdcb:5c7a:dd5d","2019-11-14 06:14:44","Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36" 4 | "Ruth Trevino","valdez@verizon.net","(731) 915-5892","450-76-5961","EwV9tC9#","e501:2103:c25:ab98:394a:494:5da8:731d","2019-11-14 06:15:00","Mozilla/5.0 (Linux; Android 8.1.0; motorola one Build/OPKS28.63-18-3; wv AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 Instagram 72.0.0.21.98 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 132081645)" 5 | "Kallie Lewis","lushe@hotmail.com","(706) 241-9332","174-40-2889","%9xwUUpt","630:145e:8acc:d793:5a10:11d8:768:50c0","2019-11-14 06:15:09","Mozilla/5.0 (Linux; U; Android 4.1.2; de-de; GT-I9100 Build/JZO54K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30" 6 | "Jake Wu","ramollin@comcast.net","(637) 605-9257","247-52-6862","2>jET?7q","6479:e3ae:d70c:c75b:a029:7b69:cda0:e593","2019-11-14 06:15:17","Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/69.0.3497.100 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/193.0.0.45.101;]" 7 | "Salvatore Pollard","drezet@hotmail.com","(886) 802-0292","512-74-7870","3c~Luj%?","6ccd:834f:c8c9:22e4:b815:ea2c:a350:74c2","2019-11-14 06:15:28","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36 OPR/36.0.2130.32" 8 | "Zackery Ford","jshearer@live.com","(997) 600-9698","579-03-7675","S+4DvgmW","a7ab:2c8:4a44:978f:4eec:e64e:53e5:b21b","2019-11-14 06:15:36","Mozilla/5.0 (Windows NT 5.1; rv:36.0) Gecko/20100101 Firefox/36.0" 9 | "Trystan Baker","dkeeler@comcast.net","(804) 777-8345","548-65-0641","Dd~Ep5M3","ab22:4193:dd2c:a632:2ca0:f520:3941:fa65","2019-11-14 06:15:57","Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; KTXN)" 10 | "Davon Gay","geoffr@mac.com","(644) 566-1838","358-18-2062","L4D3&5jq","d29c:1b76:cd05:6e7c:1920:959e:66e1:e490","2019-11-14 06:16:04","Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [FBAN/FBIOS;FBDV/iPhone10,2;FBMD/iPhone;FBSN/iOS;FBSV/12.3.2;FBSS/3;FBCR/Verizon;FBID/phone;FBLC/en_US;FBOP/5]" 11 | "Eliza Burke","satishr@yahoo.com","(212) 237-9866","472-42-0884","7xM6XD$T","d160:9a44:b8c2:72d1:4c10:1d3c:950b:d060","2019-11-14 06:16:10","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0" 12 | "Belen Bailey","bcevc@yahoo.com","(539) 233-4942","203-38-5395","^3EZ~TkX","f724:c5d1:a14d:c4c5:bae2:9457:3769:1969","2019-11-14 06:16:19","Mozilla/5.0 (Linux; U; Android 4.1.2; de-de; GT-I9100 Build/JZO54K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30" 13 | "Lilly Stuart","slaff@mac.com","(404) 269-6404","297-98-5726","Dc9+NZ>H","e848:e856:4e0b:a056:54ad:1e98:8110:ce1b","2019-11-14 06:16:24","Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; KTXN)" 14 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/.db_User.json: -------------------------------------------------------------------------------- 1 | {"4252f481-2fc4-468c-af52-78e733b24189": {"id": "4252f481-2fc4-468c-af52-78e733b24189", "created_at": "2023-03-08T01:04:42", "updated_at": "2023-03-08T01:04:42", "email": "bob@hbtn.io", "_password": "a5c904771b8617de27d3511d1f538094e26c120da663363b3f760f7b894f9d69", "first_name": null, "last_name": null}} -------------------------------------------------------------------------------- /0x01-Basic_authentication/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/README.md: -------------------------------------------------------------------------------- 1 | # 0x06. Basic authentication 2 | :open_file_folder: Specializations - Web Stack programming ― Back-end 3 | :bust_in_silhouette: by Guillaume, CTO at Holberton School 4 | :copyright: **[Holberton School](https://www.holbertonschool.com/)** 5 | :bookmark: 6 | 7 | ## Background Context 8 | In this project, you will learn what the authentication process means and implement a Basic Authentication on a simple API. 9 | 10 | ## Resources 11 | ### Read or watch: 12 | * [REST API Authentication Mechanisms](https://www.youtube.com/watch?v=501dpx2IjGY) 13 | * [Base64 in Python](https://docs.python.org/3.7/library/base64.html) 14 | * [HTTP header Authorization](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) 15 | * [Flask](https://palletsprojects.com/p/flask/) 16 | * [Base64 - concept](https://en.wikipedia.org/wiki/Base64) 17 | * [Custom Error Pages](https://flask.palletsprojects.com/en/1.1.x/patterns/errorpages/) 18 | * [before_request](https://flask.palletsprojects.com/en/1.1.x/api/#flask.Blueprint.before_request) 19 | * [Download and start your project from this archive](https://intranet.hbtn.io/rltoken/scy2k-OPTBy-DI90EyQ0uw) 20 | # Simple API 21 | 22 | Simple HTTP API for playing with `User` model. 23 | 24 | 25 | ## Files 26 | 27 | ### `models/` 28 | 29 | - `base.py`: base of all models of the API - handle serialization to file 30 | - `user.py`: user model 31 | 32 | ### `api/v1` 33 | 34 | - `app.py`: entry point of the API 35 | - `views/index.py`: basic endpoints of the API: `/status` and `/stats` 36 | - `views/users.py`: all users endpoints 37 | 38 | 39 | ## Setup 40 | 41 | ``` 42 | $ pip3 install -r requirements.txt 43 | ``` 44 | 45 | 46 | ## Run 47 | 48 | ``` 49 | $ API_HOST=0.0.0.0 API_PORT=5000 python3 -m api.v1.app 50 | ``` 51 | 52 | 53 | ## Routes 54 | 55 | - `GET /api/v1/status`: returns the status of the API 56 | - `GET /api/v1/stats`: returns some stats of the API 57 | - `GET /api/v1/users`: returns the list of users 58 | - `GET /api/v1/users/:id`: returns an user based on the ID 59 | - `DELETE /api/v1/users/:id`: deletes an user based on the ID 60 | - `POST /api/v1/users`: creates a new user (JSON parameters: `email`, `password`, `last_name` (optional) and `first_name` (optional)) 61 | - `PUT /api/v1/users/:id`: updates an user based on the ID (JSON parameters: `last_name` and `first_name`) 62 | 63 | ## Learning Objectives 64 | At the end of this project, you are expected to be able to [explain to anyone](https://fs.blog/2012/04/feynman-technique/), without the help of Google: 65 | ### General 66 | * What authentication means 67 | * What Base64 is 68 | * How to encode a string in Base64 69 | * What Basic authentication means 70 | * How to send the Authorization header 71 | 72 | ## Requirements 73 | * A ```README.md``` file. 74 | 75 | ## Tasks 76 | * [x] 0. Simple-basic-API 77 | * [x] 1. Error handler: Unauthorized 78 | * [x] 2. Error handler: Forbidden 79 | * [x] 3. Auth class 80 | * [x] 4. Define which routes don't need authentication 81 | * [x] 5. Request validation! 82 | * [x] 6. Basic auth 83 | * [x] 7. Basic - Base64 part 84 | * [x] 8. Basic - Base64 decode 85 | * [x] 9. Basic - User credentials 86 | * [x] 10. Basic - User object 87 | * [x] 11. Basic - Overload current_user - and BOOM! 88 | * [x] 12. Basic - Allow password with ":" 89 | * [x] 13. Require auth with stars 90 | 91 | ## Software engineer 92 | Javier Andrés Garzón Patarroyo 93 | :octocat: [GitHub](https://github.com/javierandresgp/) 94 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/SimpleAPI/README.md: -------------------------------------------------------------------------------- 1 | # Simple API 2 | 3 | Simple HTTP API for playing with `User` model. 4 | 5 | 6 | ## Files 7 | 8 | ### `models/` 9 | 10 | - `base.py`: base of all models of the API - handle serialization to file 11 | - `user.py`: user model 12 | 13 | ### `api/v1` 14 | 15 | - `app.py`: entry point of the API 16 | - `views/index.py`: basic endpoints of the API: `/status` and `/stats` 17 | - `views/users.py`: all users endpoints 18 | 19 | 20 | ## Setup 21 | 22 | ``` 23 | $ pip3 install -r requirements.txt 24 | ``` 25 | 26 | 27 | ## Run 28 | 29 | ``` 30 | $ API_HOST=0.0.0.0 API_PORT=5000 python3 -m api.v1.app 31 | ``` 32 | 33 | 34 | ## Routes 35 | 36 | - `GET /api/v1/status`: returns the status of the API 37 | - `GET /api/v1/stats`: returns some stats of the API 38 | - `GET /api/v1/users`: returns the list of users 39 | - `GET /api/v1/users/:id`: returns an user based on the ID 40 | - `DELETE /api/v1/users/:id`: deletes an user based on the ID 41 | - `POST /api/v1/users`: creates a new user (JSON parameters: `email`, `password`, `last_name` (optional) and `first_name` (optional)) 42 | - `PUT /api/v1/users/:id`: updates an user based on the ID (JSON parameters: `last_name` and `first_name`) 43 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/SimpleAPI/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/SimpleAPI/api/__init__.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/SimpleAPI/api/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/SimpleAPI/api/v1/__init__.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/SimpleAPI/api/v1/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Route module for the API 4 | """ 5 | from os import getenv 6 | from api.v1.views import app_views 7 | from flask import Flask, jsonify, abort, request 8 | from flask_cors import (CORS, cross_origin) 9 | import os 10 | 11 | 12 | app = Flask(__name__) 13 | app.register_blueprint(app_views) 14 | CORS(app, resources={r"/api/v1/*": {"origins": "*"}}) 15 | 16 | 17 | @app.errorhandler(404) 18 | def not_found(error) -> str: 19 | """ Not found handler 20 | """ 21 | return jsonify({"error": "Not found"}), 404 22 | 23 | 24 | if __name__ == "__main__": 25 | host = getenv("API_HOST", "0.0.0.0") 26 | port = getenv("API_PORT", "5000") 27 | app.run(host=host, port=port) 28 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/SimpleAPI/api/v1/views/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ DocDocDocDocDocDoc 3 | """ 4 | from flask import Blueprint 5 | 6 | app_views = Blueprint("app_views", __name__, url_prefix="/api/v1") 7 | 8 | from api.v1.views.index import * 9 | from api.v1.views.users import * 10 | 11 | User.load_from_file() 12 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/SimpleAPI/api/v1/views/index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Module of Index views 3 | """ 4 | from flask import jsonify, abort 5 | from api.v1.views import app_views 6 | 7 | 8 | @app_views.route('/status', methods=['GET'], strict_slashes=False) 9 | def status() -> str: 10 | """ GET /api/v1/status 11 | Return: 12 | - the status of the API 13 | """ 14 | return jsonify({"status": "OK"}) 15 | 16 | 17 | @app_views.route('/stats/', strict_slashes=False) 18 | def stats() -> str: 19 | """ GET /api/v1/stats 20 | Return: 21 | - the number of each objects 22 | """ 23 | from models.user import User 24 | stats = {} 25 | stats['users'] = User.count() 26 | return jsonify(stats) 27 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/SimpleAPI/api/v1/views/users.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Module of Users views 3 | """ 4 | from api.v1.views import app_views 5 | from flask import abort, jsonify, request 6 | from models.user import User 7 | 8 | 9 | @app_views.route('/users', methods=['GET'], strict_slashes=False) 10 | def view_all_users() -> str: 11 | """ GET /api/v1/users 12 | Return: 13 | - list of all User objects JSON represented 14 | """ 15 | all_users = [user.to_json() for user in User.all()] 16 | return jsonify(all_users) 17 | 18 | 19 | @app_views.route('/users/', methods=['GET'], strict_slashes=False) 20 | def view_one_user(user_id: str = None) -> str: 21 | """ GET /api/v1/users/:id 22 | Path parameter: 23 | - User ID 24 | Return: 25 | - User object JSON represented 26 | - 404 if the User ID doesn't exist 27 | """ 28 | if user_id is None: 29 | abort(404) 30 | user = User.get(user_id) 31 | if user is None: 32 | abort(404) 33 | return jsonify(user.to_json()) 34 | 35 | 36 | @app_views.route('/users/', methods=['DELETE'], strict_slashes=False) 37 | def delete_user(user_id: str = None) -> str: 38 | """ DELETE /api/v1/users/:id 39 | Path parameter: 40 | - User ID 41 | Return: 42 | - empty JSON is the User has been correctly deleted 43 | - 404 if the User ID doesn't exist 44 | """ 45 | if user_id is None: 46 | abort(404) 47 | user = User.get(user_id) 48 | if user is None: 49 | abort(404) 50 | user.remove() 51 | return jsonify({}), 200 52 | 53 | 54 | @app_views.route('/users', methods=['POST'], strict_slashes=False) 55 | def create_user() -> str: 56 | """ POST /api/v1/users/ 57 | JSON body: 58 | - email 59 | - password 60 | - last_name (optional) 61 | - first_name (optional) 62 | Return: 63 | - User object JSON represented 64 | - 400 if can't create the new User 65 | """ 66 | rj = None 67 | error_msg = None 68 | try: 69 | rj = request.get_json() 70 | except Exception as e: 71 | rj = None 72 | if rj is None: 73 | error_msg = "Wrong format" 74 | if error_msg is None and rj.get("email", "") == "": 75 | error_msg = "email missing" 76 | if error_msg is None and rj.get("password", "") == "": 77 | error_msg = "password missing" 78 | if error_msg is None: 79 | try: 80 | user = User() 81 | user.email = rj.get("email") 82 | user.password = rj.get("password") 83 | user.first_name = rj.get("first_name") 84 | user.last_name = rj.get("last_name") 85 | user.save() 86 | return jsonify(user.to_json()), 201 87 | except Exception as e: 88 | error_msg = "Can't create User: {}".format(e) 89 | return jsonify({'error': error_msg}), 400 90 | 91 | 92 | @app_views.route('/users/', methods=['PUT'], strict_slashes=False) 93 | def update_user(user_id: str = None) -> str: 94 | """ PUT /api/v1/users/:id 95 | Path parameter: 96 | - User ID 97 | JSON body: 98 | - last_name (optional) 99 | - first_name (optional) 100 | Return: 101 | - User object JSON represented 102 | - 404 if the User ID doesn't exist 103 | - 400 if can't update the User 104 | """ 105 | if user_id is None: 106 | abort(404) 107 | user = User.get(user_id) 108 | if user is None: 109 | abort(404) 110 | rj = None 111 | try: 112 | rj = request.get_json() 113 | except Exception as e: 114 | rj = None 115 | if rj is None: 116 | return jsonify({'error': "Wrong format"}), 400 117 | if rj.get('first_name') is not None: 118 | user.first_name = rj.get('first_name') 119 | if rj.get('last_name') is not None: 120 | user.last_name = rj.get('last_name') 121 | user.save() 122 | return jsonify(user.to_json()), 200 123 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/SimpleAPI/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/SimpleAPI/models/__init__.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/SimpleAPI/models/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Base module 3 | """ 4 | from datetime import datetime 5 | from typing import TypeVar, List, Iterable 6 | from os import path 7 | import json 8 | import uuid 9 | 10 | 11 | TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S" 12 | DATA = {} 13 | 14 | 15 | class Base(): 16 | """ Base class 17 | """ 18 | 19 | def __init__(self, *args: list, **kwargs: dict): 20 | """ Initialize a Base instance 21 | """ 22 | s_class = str(self.__class__.__name__) 23 | if DATA.get(s_class) is None: 24 | DATA[s_class] = {} 25 | 26 | self.id = kwargs.get('id', str(uuid.uuid4())) 27 | if kwargs.get('created_at') is not None: 28 | self.created_at = datetime.strptime(kwargs.get('created_at'), 29 | TIMESTAMP_FORMAT) 30 | else: 31 | self.created_at = datetime.utcnow() 32 | if kwargs.get('updated_at') is not None: 33 | self.updated_at = datetime.strptime(kwargs.get('updated_at'), 34 | TIMESTAMP_FORMAT) 35 | else: 36 | self.updated_at = datetime.utcnow() 37 | 38 | def __eq__(self, other: TypeVar('Base')) -> bool: 39 | """ Equality 40 | """ 41 | if type(self) != type(other): 42 | return False 43 | if not isinstance(self, Base): 44 | return False 45 | return (self.id == other.id) 46 | 47 | def to_json(self, for_serialization: bool = False) -> dict: 48 | """ Convert the object a JSON dictionary 49 | """ 50 | result = {} 51 | for key, value in self.__dict__.items(): 52 | if not for_serialization and key[0] == '_': 53 | continue 54 | if type(value) is datetime: 55 | result[key] = value.strftime(TIMESTAMP_FORMAT) 56 | else: 57 | result[key] = value 58 | return result 59 | 60 | @classmethod 61 | def load_from_file(cls): 62 | """ Load all objects from file 63 | """ 64 | s_class = cls.__name__ 65 | file_path = ".db_{}.json".format(s_class) 66 | DATA[s_class] = {} 67 | if not path.exists(file_path): 68 | return 69 | 70 | with open(file_path, 'r') as f: 71 | objs_json = json.load(f) 72 | for obj_id, obj_json in objs_json.items(): 73 | DATA[s_class][obj_id] = cls(**obj_json) 74 | 75 | @classmethod 76 | def save_to_file(cls): 77 | """ Save all objects to file 78 | """ 79 | s_class = cls.__name__ 80 | file_path = ".db_{}.json".format(s_class) 81 | objs_json = {} 82 | for obj_id, obj in DATA[s_class].items(): 83 | objs_json[obj_id] = obj.to_json(True) 84 | 85 | with open(file_path, 'w') as f: 86 | json.dump(objs_json, f) 87 | 88 | def save(self): 89 | """ Save current object 90 | """ 91 | s_class = self.__class__.__name__ 92 | self.updated_at = datetime.utcnow() 93 | DATA[s_class][self.id] = self 94 | self.__class__.save_to_file() 95 | 96 | def remove(self): 97 | """ Remove object 98 | """ 99 | s_class = self.__class__.__name__ 100 | if DATA[s_class].get(self.id) is not None: 101 | del DATA[s_class][self.id] 102 | self.__class__.save_to_file() 103 | 104 | @classmethod 105 | def count(cls) -> int: 106 | """ Count all objects 107 | """ 108 | s_class = cls.__name__ 109 | return len(DATA[s_class].keys()) 110 | 111 | @classmethod 112 | def all(cls) -> Iterable[TypeVar('Base')]: 113 | """ Return all objects 114 | """ 115 | return cls.search() 116 | 117 | @classmethod 118 | def get(cls, id: str) -> TypeVar('Base'): 119 | """ Return one object by ID 120 | """ 121 | s_class = cls.__name__ 122 | return DATA[s_class].get(id) 123 | 124 | @classmethod 125 | def search(cls, attributes: dict = {}) -> List[TypeVar('Base')]: 126 | """ Search all objects with matching attributes 127 | """ 128 | s_class = cls.__name__ 129 | def _search(obj): 130 | if len(attributes) == 0: 131 | return True 132 | for k, v in attributes.items(): 133 | if (getattr(obj, k) != v): 134 | return False 135 | return True 136 | 137 | return list(filter(_search, DATA[s_class].values())) 138 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/SimpleAPI/models/user.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ User module 3 | """ 4 | import hashlib 5 | from models.base import Base 6 | 7 | 8 | class User(Base): 9 | """ User class 10 | """ 11 | 12 | def __init__(self, *args: list, **kwargs: dict): 13 | """ Initialize a User instance 14 | """ 15 | super().__init__(*args, **kwargs) 16 | self.email = kwargs.get('email') 17 | self._password = kwargs.get('_password') 18 | self.first_name = kwargs.get('first_name') 19 | self.last_name = kwargs.get('last_name') 20 | 21 | @property 22 | def password(self) -> str: 23 | """ Getter of the password 24 | """ 25 | return self._password 26 | 27 | @password.setter 28 | def password(self, pwd: str): 29 | """ Setter of a new password: encrypt in SHA256 30 | """ 31 | if pwd is None or type(pwd) is not str: 32 | self._password = None 33 | else: 34 | self._password = hashlib.sha256(pwd.encode()).hexdigest().lower() 35 | 36 | def is_valid_password(self, pwd: str) -> bool: 37 | """ Validate a password 38 | """ 39 | if pwd is None or type(pwd) is not str: 40 | return False 41 | if self.password is None: 42 | return False 43 | pwd_e = pwd.encode() 44 | return hashlib.sha256(pwd_e).hexdigest().lower() == self.password 45 | 46 | def display_name(self) -> str: 47 | """ Display User name based on email/first_name/last_name 48 | """ 49 | if self.email is None and self.first_name is None \ 50 | and self.last_name is None: 51 | return "" 52 | if self.first_name is None and self.last_name is None: 53 | return "{}".format(self.email) 54 | if self.last_name is None: 55 | return "{}".format(self.first_name) 56 | if self.first_name is None: 57 | return "{}".format(self.last_name) 58 | else: 59 | return "{} {}".format(self.first_name, self.last_name) 60 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/SimpleAPI/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.1.2 2 | Flask-Cors==3.0.8 3 | Jinja2==2.11.2 4 | requests==2.18.4 5 | pycodestyle==2.6.0 6 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/._SimpleAPI: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/._SimpleAPI -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/._README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/._README.md -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/._api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/._api -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/._models: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/._models -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/._requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/._requirements.txt -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/api/._.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/api/._.DS_Store -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/api/.___init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/api/.___init__.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/api/._v1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/api/._v1 -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/._.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/._.DS_Store -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/.___init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/.___init__.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/._app.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/._app.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/._views: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/._views -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/views/._.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/views/._.DS_Store -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/views/.___init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/views/.___init__.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/views/._index.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/views/._index.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/views/._users.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/api/v1/views/._users.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/models/._.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/models/._.DS_Store -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/models/.___init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/models/.___init__.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/models/._base.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/models/._base.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/__MACOSX/SimpleAPI/models/._user.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/__MACOSX/SimpleAPI/models/._user.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/api/__init__.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/api/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/api/v1/__init__.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/api/v1/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Route module for the API 4 | """ 5 | from os import getenv 6 | from api.v1.views import app_views 7 | from flask import Flask, jsonify, abort, request 8 | from flask_cors import (CORS, cross_origin) 9 | import os 10 | 11 | 12 | app = Flask(__name__) 13 | app.register_blueprint(app_views) 14 | CORS(app, resources={r"/api/v1/*": {"origins": "*"}}) 15 | auth = None 16 | AUTH_TYPE = getenv("AUTH_TYPE") 17 | if AUTH_TYPE == 'auth': 18 | from api.v1.auth.auth import Auth 19 | auth = Auth() 20 | elif AUTH_TYPE == 'basic_auth': 21 | from api.v1.auth.basic_auth import BasicAuth 22 | auth = BasicAuth() 23 | 24 | 25 | @app.errorhandler(401) 26 | def unauthorized(error) -> str: 27 | """ error handler for (unauthorized) 401 status code """ 28 | return jsonify({"error": "Unauthorized"}), 401 29 | 30 | 31 | @app.errorhandler(403) 32 | def forbidden(error) -> str: 33 | """ error handler for (forbidden) 403 status code """ 34 | return jsonify({"error": "Forbidden"}), 403 35 | 36 | 37 | @app.errorhandler(404) 38 | def not_found(error) -> str: 39 | """ Not found handler 40 | """ 41 | return jsonify({"error": "Not found"}), 404 42 | 43 | 44 | @app.before_request 45 | def before_request() -> str: 46 | """ method to handler before request """ 47 | if auth is None: 48 | return 49 | excluded_paths = ['/api/v1/status/', 50 | '/api/v1/unauthorized/', 51 | '/api/v1/forbidden/'] 52 | if not auth.require_auth(request.path, excluded_paths): 53 | return 54 | if auth.authorization_header(request) is None: 55 | abort(401) 56 | if auth.current_user(request) is None: 57 | abort(403) 58 | 59 | 60 | if __name__ == "__main__": 61 | host = getenv("API_HOST", "0.0.0.0") 62 | port = getenv("API_PORT", "5000") 63 | app.run(host=host, port=port) 64 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/api/v1/auth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/api/v1/auth/__init__.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/api/v1/auth/auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Auth class, Require auth with stars """ 3 | from flask import request 4 | from typing import List, TypeVar 5 | 6 | 7 | class Auth(): 8 | """ manage the API authentication """ 9 | def require_auth(self, path: str, excluded_paths: List[str]) -> bool: 10 | """ require authorithation """ 11 | if path is None or excluded_paths is None or not len(excluded_paths): 12 | return True 13 | if path[-1] != '/': 14 | path += '/' 15 | for i in excluded_paths: 16 | if i.endswith('*'): 17 | if path.startswith(i[:1]): 18 | return False 19 | if path in excluded_paths: 20 | return False 21 | else: 22 | return True 23 | 24 | def authorization_header(self, request=None) -> str: 25 | """ authorization header """ 26 | if request is None: 27 | return None 28 | if not request.headers.get("Authorization"): 29 | return None 30 | return request.headers.get("Authorization") 31 | 32 | def current_user(self, request=None) -> TypeVar('User'): 33 | """ current user """ 34 | return None 35 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/api/v1/auth/basic_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Basic auth, Base64 part, Base64 decode, User credentials, User object, 3 | Overload current_user - and BOOM!, Allow password with ":" """ 4 | from api.v1.auth.auth import Auth 5 | from base64 import b64decode 6 | from typing import TypeVar 7 | from models.user import User 8 | 9 | 10 | class BasicAuth(Auth): 11 | """ BasicAuth class """ 12 | def extract_base64_authorization_header(self, 13 | authorization_header: str 14 | ) -> str: 15 | """ returns the Base64 part of the Authorization header 16 | for a Basic Authentication """ 17 | if authorization_header is None: 18 | return None 19 | if not isinstance(authorization_header, str): 20 | return None 21 | if not authorization_header.startswith("Basic "): 22 | return None 23 | base = authorization_header.split(' ') 24 | return base[1] 25 | 26 | def decode_base64_authorization_header(self, 27 | base64_authorization_header: str 28 | ) -> str: 29 | """ returns the decoded value of a Base64 string """ 30 | if base64_authorization_header is None: 31 | return None 32 | if not isinstance(base64_authorization_header, str): 33 | return None 34 | try: 35 | baseEncode = base64_authorization_header.encode('utf-8') 36 | baseDecode = b64decode(baseEncode) 37 | decodedValue = baseDecode.decode('utf-8') 38 | return decodedValue 39 | except Exception: 40 | return None 41 | 42 | def extract_user_credentials(self, 43 | decoded_base64_authorization_header: str 44 | ) -> (str, str): 45 | """ returns the user email and password 46 | from the Base64 decoded value """ 47 | if decoded_base64_authorization_header is None: 48 | return None, None 49 | if not isinstance(decoded_base64_authorization_header, str): 50 | return None, None 51 | if ':' not in decoded_base64_authorization_header: 52 | return None, None 53 | credentials = decoded_base64_authorization_header.split(':', 1) 54 | return credentials[0], credentials[1] 55 | 56 | def user_object_from_credentials(self, user_email: str, 57 | user_pwd: str) -> TypeVar('User'): 58 | """ returns the User instance based on his email and password """ 59 | if user_email is None or not isinstance(user_email, str): 60 | return None 61 | if user_pwd is None or not isinstance(user_pwd, str): 62 | return None 63 | try: 64 | users = User.search({'email': user_email}) 65 | for user in users: 66 | if user.is_valid_password(user_pwd): 67 | return user 68 | except Exception: 69 | return None 70 | 71 | def current_user(self, request=None) -> TypeVar('User'): 72 | """ overloads Auth and retrieves the User instance for a request """ 73 | try: 74 | header = self.authorization_header(request) 75 | base64Header = self.extract_base64_authorization_header(header) 76 | decodeValue = self.decode_base64_authorization_header(base64Header) 77 | credentials = self.extract_user_credentials(decodeValue) 78 | user = self.user_object_from_credentials(credentials[0], 79 | credentials[1]) 80 | return user 81 | except Exception: 82 | return None 83 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/api/v1/views/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ DocDocDocDocDocDoc 3 | """ 4 | from flask import Blueprint 5 | 6 | app_views = Blueprint("app_views", __name__, url_prefix="/api/v1") 7 | 8 | from api.v1.views.index import * 9 | from api.v1.views.users import * 10 | 11 | User.load_from_file() 12 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/api/v1/views/index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Module of Index views 3 | """ 4 | from flask import jsonify, abort 5 | from api.v1.views import app_views 6 | 7 | 8 | @app_views.route('/status', methods=['GET'], strict_slashes=False) 9 | def status() -> str: 10 | """ GET /api/v1/status 11 | Return: 12 | - the status of the API 13 | """ 14 | return jsonify({"status": "OK"}) 15 | 16 | 17 | @app_views.route('/unauthorized', methods=['GET'], strict_slashes=False) 18 | def unauthorized() -> str: 19 | """ endpoint to testing (unauthorized) 401 error handler """ 20 | abort(401) 21 | 22 | 23 | @app_views.route('/forbidden', methods=['GET'], strict_slashes=False) 24 | def forbidden() -> str: 25 | """ endpoint to testing (forbidden) 403 error handler """ 26 | abort(403) 27 | 28 | 29 | @app_views.route('/stats/', strict_slashes=False) 30 | def stats() -> str: 31 | """ GET /api/v1/stats 32 | Return: 33 | - the number of each objects 34 | """ 35 | from models.user import User 36 | stats = {} 37 | stats['users'] = User.count() 38 | return jsonify(stats) 39 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/api/v1/views/users.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Module of Users views 3 | """ 4 | from api.v1.views import app_views 5 | from flask import abort, jsonify, request 6 | from models.user import User 7 | 8 | 9 | @app_views.route('/users', methods=['GET'], strict_slashes=False) 10 | def view_all_users() -> str: 11 | """ GET /api/v1/users 12 | Return: 13 | - list of all User objects JSON represented 14 | """ 15 | all_users = [user.to_json() for user in User.all()] 16 | return jsonify(all_users) 17 | 18 | 19 | @app_views.route('/users/', methods=['GET'], strict_slashes=False) 20 | def view_one_user(user_id: str = None) -> str: 21 | """ GET /api/v1/users/:id 22 | Path parameter: 23 | - User ID 24 | Return: 25 | - User object JSON represented 26 | - 404 if the User ID doesn't exist 27 | """ 28 | if user_id is None: 29 | abort(404) 30 | user = User.get(user_id) 31 | if user is None: 32 | abort(404) 33 | return jsonify(user.to_json()) 34 | 35 | 36 | @app_views.route('/users/', methods=['DELETE'], strict_slashes=False) 37 | def delete_user(user_id: str = None) -> str: 38 | """ DELETE /api/v1/users/:id 39 | Path parameter: 40 | - User ID 41 | Return: 42 | - empty JSON is the User has been correctly deleted 43 | - 404 if the User ID doesn't exist 44 | """ 45 | if user_id is None: 46 | abort(404) 47 | user = User.get(user_id) 48 | if user is None: 49 | abort(404) 50 | user.remove() 51 | return jsonify({}), 200 52 | 53 | 54 | @app_views.route('/users', methods=['POST'], strict_slashes=False) 55 | def create_user() -> str: 56 | """ POST /api/v1/users/ 57 | JSON body: 58 | - email 59 | - password 60 | - last_name (optional) 61 | - first_name (optional) 62 | Return: 63 | - User object JSON represented 64 | - 400 if can't create the new User 65 | """ 66 | rj = None 67 | error_msg = None 68 | try: 69 | rj = request.get_json() 70 | except Exception as e: 71 | rj = None 72 | if rj is None: 73 | error_msg = "Wrong format" 74 | if error_msg is None and rj.get("email", "") == "": 75 | error_msg = "email missing" 76 | if error_msg is None and rj.get("password", "") == "": 77 | error_msg = "password missing" 78 | if error_msg is None: 79 | try: 80 | user = User() 81 | user.email = rj.get("email") 82 | user.password = rj.get("password") 83 | user.first_name = rj.get("first_name") 84 | user.last_name = rj.get("last_name") 85 | user.save() 86 | return jsonify(user.to_json()), 201 87 | except Exception as e: 88 | error_msg = "Can't create User: {}".format(e) 89 | return jsonify({'error': error_msg}), 400 90 | 91 | 92 | @app_views.route('/users/', methods=['PUT'], strict_slashes=False) 93 | def update_user(user_id: str = None) -> str: 94 | """ PUT /api/v1/users/:id 95 | Path parameter: 96 | - User ID 97 | JSON body: 98 | - last_name (optional) 99 | - first_name (optional) 100 | Return: 101 | - User object JSON represented 102 | - 404 if the User ID doesn't exist 103 | - 400 if can't update the User 104 | """ 105 | if user_id is None: 106 | abort(404) 107 | user = User.get(user_id) 108 | if user is None: 109 | abort(404) 110 | rj = None 111 | try: 112 | rj = request.get_json() 113 | except Exception as e: 114 | rj = None 115 | if rj is None: 116 | return jsonify({'error': "Wrong format"}), 400 117 | if rj.get('first_name') is not None: 118 | user.first_name = rj.get('first_name') 119 | if rj.get('last_name') is not None: 120 | user.last_name = rj.get('last_name') 121 | user.save() 122 | return jsonify(user.to_json()), 200 123 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/archive.zip -------------------------------------------------------------------------------- /0x01-Basic_authentication/images/meme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/images/meme.png -------------------------------------------------------------------------------- /0x01-Basic_authentication/main_0.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 0 3 | """ 4 | from api.v1.auth.auth import Auth 5 | 6 | a = Auth() 7 | 8 | print(a.require_auth("/api/v1/status/", ["/api/v1/status/"])) 9 | print(a.authorization_header()) 10 | print(a.current_user()) 11 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/main_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 1 3 | """ 4 | from api.v1.auth.auth import Auth 5 | 6 | a = Auth() 7 | 8 | print(a.require_auth(None, None)) 9 | print(a.require_auth(None, [])) 10 | print(a.require_auth("/api/v1/status/", [])) 11 | print(a.require_auth("/api/v1/status/", ["/api/v1/status/"])) 12 | print(a.require_auth("/api/v1/status", ["/api/v1/status/"])) 13 | print(a.require_auth("/api/v1/users", ["/api/v1/status/"])) 14 | print(a.require_auth("/api/v1/users", ["/api/v1/status/", "/api/v1/stats"])) 15 | 16 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/main_100.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 100 3 | """ 4 | import base64 5 | from api.v1.auth.basic_auth import BasicAuth 6 | from models.user import User 7 | 8 | """ Create a user test """ 9 | user_email = "bob100@hbtn.io" 10 | user_clear_pwd = "H0lberton:School:98!" 11 | 12 | user = User() 13 | user.email = user_email 14 | user.password = user_clear_pwd 15 | print("New user: {}".format(user.id)) 16 | user.save() 17 | 18 | basic_clear = "{}:{}".format(user_email, user_clear_pwd) 19 | print("Basic Base64: {}".format(base64.b64encode(basic_clear.encode('utf-8')).decode("utf-8"))) 20 | 21 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/main_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 2 3 | """ 4 | from api.v1.auth.basic_auth import BasicAuth 5 | 6 | a = BasicAuth() 7 | 8 | print(a.extract_base64_authorization_header(None)) 9 | print(a.extract_base64_authorization_header(89)) 10 | print(a.extract_base64_authorization_header("Holberton School")) 11 | print(a.extract_base64_authorization_header("Basic Holberton")) 12 | print(a.extract_base64_authorization_header("Basic SG9sYmVydG9u")) 13 | print(a.extract_base64_authorization_header("Basic SG9sYmVydG9uIFNjaG9vbA==")) 14 | print(a.extract_base64_authorization_header("Basic1234")) 15 | 16 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/main_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 3 3 | """ 4 | from api.v1.auth.basic_auth import BasicAuth 5 | 6 | a = BasicAuth() 7 | 8 | print(a.decode_base64_authorization_header(None)) 9 | print(a.decode_base64_authorization_header(89)) 10 | print(a.decode_base64_authorization_header("Holberton School")) 11 | print(a.decode_base64_authorization_header("SG9sYmVydG9u")) 12 | print(a.decode_base64_authorization_header("SG9sYmVydG9uIFNjaG9vbA==")) 13 | print(a.decode_base64_authorization_header(a.extract_base64_authorization_header("Basic SG9sYmVydG9uIFNjaG9vbA=="))) 14 | 15 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/main_4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 4 3 | """ 4 | from api.v1.auth.basic_auth import BasicAuth 5 | 6 | a = BasicAuth() 7 | 8 | print(a.extract_user_credentials(None)) 9 | print(a.extract_user_credentials(89)) 10 | print(a.extract_user_credentials("Holberton School")) 11 | print(a.extract_user_credentials("Holberton:School")) 12 | print(a.extract_user_credentials("bob@gmail.com:toto1234")) 13 | 14 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/main_5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 5 3 | """ 4 | import uuid 5 | from api.v1.auth.basic_auth import BasicAuth 6 | from models.user import User 7 | 8 | """ Create a user test """ 9 | user_email = str(uuid.uuid4()) 10 | user_clear_pwd = str(uuid.uuid4()) 11 | user = User() 12 | user.email = user_email 13 | user.first_name = "Bob" 14 | user.last_name = "Dylan" 15 | user.password = user_clear_pwd 16 | print("New user: {}".format(user.display_name())) 17 | user.save() 18 | 19 | """ Retreive this user via the class BasicAuth """ 20 | 21 | a = BasicAuth() 22 | 23 | u = a.user_object_from_credentials(None, None) 24 | print(u.display_name() if u is not None else "None") 25 | 26 | u = a.user_object_from_credentials(89, 98) 27 | print(u.display_name() if u is not None else "None") 28 | 29 | u = a.user_object_from_credentials("email@notfound.com", "pwd") 30 | print(u.display_name() if u is not None else "None") 31 | 32 | u = a.user_object_from_credentials(user_email, "pwd") 33 | print(u.display_name() if u is not None else "None") 34 | 35 | u = a.user_object_from_credentials(user_email, user_clear_pwd) 36 | print(u.display_name() if u is not None else "None") 37 | 38 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/main_6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 6 3 | """ 4 | import base64 5 | from api.v1.auth.basic_auth import BasicAuth 6 | from models.user import User 7 | 8 | """ Create a user test """ 9 | user_email = "bob@hbtn.io" 10 | user_clear_pwd = "H0lbertonSchool98!" 11 | user = User() 12 | user.email = user_email 13 | user.password = user_clear_pwd 14 | print("New user: {} / {}".format(user.id, user.display_name())) 15 | user.save() 16 | 17 | basic_clear = "{}:{}".format(user_email, user_clear_pwd) 18 | print("Basic Base64: {}".format(base64.b64encode(basic_clear.encode('utf-8')).decode("utf-8"))) 19 | 20 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/models/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/models/.DS_Store -------------------------------------------------------------------------------- /0x01-Basic_authentication/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x01-Basic_authentication/models/__init__.py -------------------------------------------------------------------------------- /0x01-Basic_authentication/models/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Base module 3 | """ 4 | from datetime import datetime 5 | from typing import TypeVar, List, Iterable 6 | from os import path 7 | import json 8 | import uuid 9 | 10 | 11 | TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S" 12 | DATA = {} 13 | 14 | 15 | class Base(): 16 | """ Base class 17 | """ 18 | 19 | def __init__(self, *args: list, **kwargs: dict): 20 | """ Initialize a Base instance 21 | """ 22 | s_class = str(self.__class__.__name__) 23 | if DATA.get(s_class) is None: 24 | DATA[s_class] = {} 25 | 26 | self.id = kwargs.get('id', str(uuid.uuid4())) 27 | if kwargs.get('created_at') is not None: 28 | self.created_at = datetime.strptime(kwargs.get('created_at'), 29 | TIMESTAMP_FORMAT) 30 | else: 31 | self.created_at = datetime.utcnow() 32 | if kwargs.get('updated_at') is not None: 33 | self.updated_at = datetime.strptime(kwargs.get('updated_at'), 34 | TIMESTAMP_FORMAT) 35 | else: 36 | self.updated_at = datetime.utcnow() 37 | 38 | def __eq__(self, other: TypeVar('Base')) -> bool: 39 | """ Equality 40 | """ 41 | if type(self) != type(other): 42 | return False 43 | if not isinstance(self, Base): 44 | return False 45 | return (self.id == other.id) 46 | 47 | def to_json(self, for_serialization: bool = False) -> dict: 48 | """ Convert the object a JSON dictionary 49 | """ 50 | result = {} 51 | for key, value in self.__dict__.items(): 52 | if not for_serialization and key[0] == '_': 53 | continue 54 | if type(value) is datetime: 55 | result[key] = value.strftime(TIMESTAMP_FORMAT) 56 | else: 57 | result[key] = value 58 | return result 59 | 60 | @classmethod 61 | def load_from_file(cls): 62 | """ Load all objects from file 63 | """ 64 | s_class = cls.__name__ 65 | file_path = ".db_{}.json".format(s_class) 66 | DATA[s_class] = {} 67 | if not path.exists(file_path): 68 | return 69 | 70 | with open(file_path, 'r') as f: 71 | objs_json = json.load(f) 72 | for obj_id, obj_json in objs_json.items(): 73 | DATA[s_class][obj_id] = cls(**obj_json) 74 | 75 | @classmethod 76 | def save_to_file(cls): 77 | """ Save all objects to file 78 | """ 79 | s_class = cls.__name__ 80 | file_path = ".db_{}.json".format(s_class) 81 | objs_json = {} 82 | for obj_id, obj in DATA[s_class].items(): 83 | objs_json[obj_id] = obj.to_json(True) 84 | 85 | with open(file_path, 'w') as f: 86 | json.dump(objs_json, f) 87 | 88 | def save(self): 89 | """ Save current object 90 | """ 91 | s_class = self.__class__.__name__ 92 | self.updated_at = datetime.utcnow() 93 | DATA[s_class][self.id] = self 94 | self.__class__.save_to_file() 95 | 96 | def remove(self): 97 | """ Remove object 98 | """ 99 | s_class = self.__class__.__name__ 100 | if DATA[s_class].get(self.id) is not None: 101 | del DATA[s_class][self.id] 102 | self.__class__.save_to_file() 103 | 104 | @classmethod 105 | def count(cls) -> int: 106 | """ Count all objects 107 | """ 108 | s_class = cls.__name__ 109 | return len(DATA[s_class].keys()) 110 | 111 | @classmethod 112 | def all(cls) -> Iterable[TypeVar('Base')]: 113 | """ Return all objects 114 | """ 115 | return cls.search() 116 | 117 | @classmethod 118 | def get(cls, id: str) -> TypeVar('Base'): 119 | """ Return one object by ID 120 | """ 121 | s_class = cls.__name__ 122 | return DATA[s_class].get(id) 123 | 124 | @classmethod 125 | def search(cls, attributes: dict = {}) -> List[TypeVar('Base')]: 126 | """ Search all objects with matching attributes 127 | """ 128 | s_class = cls.__name__ 129 | 130 | def _search(obj): 131 | if len(attributes) == 0: 132 | return True 133 | for k, v in attributes.items(): 134 | if (getattr(obj, k) != v): 135 | return False 136 | return True 137 | 138 | return list(filter(_search, DATA[s_class].values())) 139 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/models/user.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ User module 3 | """ 4 | import hashlib 5 | from models.base import Base 6 | 7 | 8 | class User(Base): 9 | """ User class 10 | """ 11 | 12 | def __init__(self, *args: list, **kwargs: dict): 13 | """ Initialize a User instance 14 | """ 15 | super().__init__(*args, **kwargs) 16 | self.email = kwargs.get('email') 17 | self._password = kwargs.get('_password') 18 | self.first_name = kwargs.get('first_name') 19 | self.last_name = kwargs.get('last_name') 20 | 21 | @property 22 | def password(self) -> str: 23 | """ Getter of the password 24 | """ 25 | return self._password 26 | 27 | @password.setter 28 | def password(self, pwd: str): 29 | """ Setter of a new password: encrypt in SHA256 30 | """ 31 | if pwd is None or type(pwd) is not str: 32 | self._password = None 33 | else: 34 | self._password = hashlib.sha256(pwd.encode()).hexdigest().lower() 35 | 36 | def is_valid_password(self, pwd: str) -> bool: 37 | """ Validate a password 38 | """ 39 | if pwd is None or type(pwd) is not str: 40 | return False 41 | if self.password is None: 42 | return False 43 | pwd_e = pwd.encode() 44 | return hashlib.sha256(pwd_e).hexdigest().lower() == self.password 45 | 46 | def display_name(self) -> str: 47 | """ Display User name based on email/first_name/last_name 48 | """ 49 | if self.email is None and self.first_name is None \ 50 | and self.last_name is None: 51 | return "" 52 | if self.first_name is None and self.last_name is None: 53 | return "{}".format(self.email) 54 | if self.last_name is None: 55 | return "{}".format(self.first_name) 56 | if self.first_name is None: 57 | return "{}".format(self.last_name) 58 | else: 59 | return "{} {}".format(self.first_name, self.last_name) 60 | -------------------------------------------------------------------------------- /0x01-Basic_authentication/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.1.2 2 | Flask-Cors==3.0.8 3 | Jinja2==2.11.2 4 | requests==2.18.4 5 | pycodestyle==2.6.0 6 | -------------------------------------------------------------------------------- /0x02-Session_authentication/.db_User.json: -------------------------------------------------------------------------------- 1 | {"da0fa0de-b1cf-4e91-870b-131460ef74e7": {"id": "da0fa0de-b1cf-4e91-870b-131460ef74e7", "created_at": "2023-03-09T10:17:20", "updated_at": "2023-03-09T10:17:20", "email": "bob@hbtn.io", "_password": "a5c904771b8617de27d3511d1f538094e26c120da663363b3f760f7b894f9d69", "first_name": null, "last_name": null}} -------------------------------------------------------------------------------- /0x02-Session_authentication/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /0x02-Session_authentication/README.md: -------------------------------------------------------------------------------- 1 | # 0x02. Session authentication 2 | 3 | | `Baeck-end` | `Authentification` | 4 | 5 | ## Background Context 6 | In this project, you will implement a **Session Authentication**. You are not allowed to install any other module. 7 | 8 | In the industry, you should **not** implement your own Session authentication system and use a module or framework that doing it for you (like in Python-Flask: [Flask-HTTPAuth(https://intranet.alxswe.com/rltoken/_ZTQTaMKjx1S_xATshexkA)]). Here, for the learning purpose, we will walk through each step of this mechanism to understand it by doing. 9 | 10 | ## Resources 11 | **Read or watch:** 12 | 13 | - [REST API Authentication Mechanisms - Only the session auth part](https://intranet.alxswe.com/rltoken/oofk0VhuS0ZFZTNTVrQeaQ) 14 | - [HTTP Cookie](https://intranet.alxswe.com/rltoken/peLV8xuJ4PDJMOVFqk-d2g) 15 | - [Flask](https://intranet.alxswe.com/rltoken/AI1tFR5XriGfR8Tz7YTYQA) 16 | - [Flask Cookie](https://intranet.alxswe.com/rltoken/AI1tFR5XriGfR8Tz7YTYQA) 17 | 18 | ## Learning Objectives 19 | At the end of this project, you are expected to be able to explain to anyone, without the help of Google: 20 | 21 | ### General 22 | - What authentication means 23 | - What session authentication means 24 | - What Cookies are 25 | - How to send Cookies 26 | - How to parse Cookies 27 | 28 | ## Tasks 29 | 30 | + [x] 0. **Et moi et moi et moi!** 31 | + Copy all your work of the [0x06. Basic authentication](../0x01-Basic_authentication/) project in this new folder. 32 | + In this version, you implemented a **Basic authentication** for giving you access to all User endpoints: 33 | + `GET /api/v1/users`. 34 | + `POST /api/v1/users`. 35 | + `GET /api/v1/users/`. 36 | + `PUT /api/v1/users/`. 37 | + `DELETE /api/v1/users/`. 38 | + Now, you will add a new endpoint: `GET /users/me` to retrieve the authenticated `User` object. 39 | + Copy folders `models` and `api` from the previous project [0x06. Basic authentication](../0x01-Basic_authentication/). 40 | + Please make sure all mandatory tasks of this previous project are done at 100% because this project (and the rest of this track) will be based on it. 41 | + Update `@app.before_request` in [api/v1/app.py](api/v1/app.py): 42 | + Assign the result of `auth.current_user(request)` to `request.current_user`. 43 | + Update method for the route `GET /api/v1/users/` in [api/v1/views/users.py](api/v1/views/users.py): 44 | + If `` is equal to `me` and `request.current_user` is `None`: `abort(404)`. 45 | + If `` is equal to `me` and `request.current_user` is not `None`: return the authenticated `User` in a JSON response (like a normal case of `GET /api/v1/users/` where `` is a valid `User` ID) 46 | + Otherwise, keep the same behavior. 47 | 48 | + [x] 1. **Empty session** 49 | + Create a class `SessionAuth` in [api/v1/auth/session_auth.py](api/v1/auth/session_auth.py) that inherits from `Auth`. For the moment this class will be empty. It's the first step for creating a new authentication mechanism: 50 | + Validate if everything inherits correctly without any overloading. 51 | + Validate the "switch" by using environment variables. 52 | + Update [api/v1/app.py](api/v1/app.py) for using `SessionAuth` instance for the variable `auth` depending on the value of the environment variable `AUTH_TYPE`, If `AUTH_TYPE` is equal to `session_auth`: 53 | + Import `SessionAuth` from [api.v1.auth.session_auth](api.v1.auth.session_auth) 54 | + Create an instance of `SessionAuth` and assign it to the variable auth 55 | Otherwise, keep the previous mechanism. 56 | 57 | + [x] 2. **Create a session** 58 | + Update `SessionAuth` class: 59 | + Create a class attribute `user_id_by_session_id` initialized by an empty dictionary. 60 | + Create an instance method `def create_session(self, user_id: str = None) -> str:` that creates a Session ID for a `user_id`: 61 | + Return `None` if `user_id` is `None`. 62 | + Return `None` if `user_id` is not a string. 63 | + Otherwise: 64 | + Generate a Session ID using `uuid` module and `uuid4()` like `id` in `Base`. 65 | + Use this Session ID as key of the dictionary `user_id_by_session_id` - the value for this key must be `user_id`. 66 | + Return the Session ID. 67 | + The same `user_id` can have multiple Session ID - indeed, the `user_id` is the value in the dictionary `user_id_by_session_id`. 68 | + Now you an "in-memory" Session ID storing. You will be able to retrieve an `User` id based on a Session ID. 69 | 70 | + [x] 3. **User ID for Session ID** 71 | + Update `SessionAuth` class: 72 | + Create an instance method `def user_id_for_session_id(self, session_id: str = None) -> str:` that returns a `User` ID based on a Session ID: 73 | + Return `None` if `session_id` is `None`. 74 | + Return `None` if `session_id` is not a string. 75 | + Return the value (the User ID) for the key `session_id` in the dictionary `user_id_by_session_id`. 76 | + You must use `.get()` built-in for accessing in a dictionary a value based on key. 77 | + Now you have 2 methods (`create_session` and `user_id_for_session_id`) for storing and retrieving a link between a User ID and a Session ID. 78 | 79 | + [x] 4. **Session cookie** 80 | + Update [api/v1/auth/auth.py](api/v1/auth/auth.py) by adding the method `def session_cookie(self, request=None):` that returns a cookie value from a request: 81 | + Return `None` if `request` is `None`. 82 | + Return the value of the cookie named `_my_session_id` from `request` - the name of the cookie must be defined by the environment variable `SESSION_NAME`. 83 | + You must use `.get()` built-in for accessing the cookie in the request cookies dictionary. 84 | + You must use the environment variable `SESSION_NAME` to define the name of the cookie used for the Session ID. 85 | 86 | + [x] 5. **Before request** 87 | + Update the `@app.before_request` method in [api/v1/app.py](api/v1/app.py): 88 | + Add the URL path `/api/v1/auth_session/login/` in the list of excluded paths of the method `require_auth` - this route doesn't exist yet but it should be accessible outside authentication 89 | + If `auth.authorization_header(request)` and `auth.session_cookie(request)` return `None`, `abort(401)` 90 | 91 | + [x] 6. **Use Session ID for identifying a User** 92 | + Update `SessionAuth` class: 93 | 94 | + Create an instance method `def current_user(self, request=None):` (overload) that returns a `User` instance based on a cookie value: 95 | 96 | + You must use `self.session_cookie(...)` and `self.user_id_for_session_id(...)` to return the User ID based on the cookie `_my_session_id`. 97 | + By using this User ID, you will be able to retrieve a `User` instance from the database - you can use `User.get(...)` for retrieving a `User` from the database. 98 | + Now, you will be able to get a User based on his session ID. 99 | 100 | + [x] 7. **New view for Session Authentication** 101 | + Create a new Flask view that handles all routes for the Session authentication. 102 | + In the file [api/v1/views/session_auth.py](api/v1/views/session_auth.py), create a route `POST /auth_session/login` (= `POST /api/v1/auth_session/login`): 103 | + Slash tolerant (`/auth_session/login` == `/auth_session/login/`). 104 | + You must use `request.form.get()` to retrieve `email` and `password` parameters. 105 | + If `email` is missing or empty, return the JSON `{ "error": "email missing" }` with the status code `400`. 106 | + If `password` is missing or empty, return the JSON `{ "error": "password missing" }` with the status code `400`. 107 | + Retrieve the `User` instance based on the `email` - you must use the class method `search` of `User` (same as the one used for the `BasicAuth`). 108 | + If no `User` found, return the JSON `{ "error": "no user found for this email" }` with the status code `404`. 109 | + If the `password` is not the one of the `User` found, return the JSON `{ "error": "wrong password" }` with the status code `401` - you must use `is_valid_password` from the `User` instance. 110 | + Otherwise, create a Session ID for the `User` ID: 111 | + You must use from `api.v1.app import auth` - **WARNING: please import it only where you need it** - not on top of the file (can generate circular import - and break first tasks of this project). 112 | + You must use `auth.create_session(..)` for creating a Session ID. 113 | + Return the dictionary representation of the `User` - you must use `to_json()` method from User. 114 | + You must set the cookie to the response - you must use the value of the environment variable `SESSION_NAME` as cookie name - [tip](https://stackoverflow.com/questions/26587485/can-a-cookie-be-set-when-using-jsonify). 115 | + In the file [api/v1/views/__init__.py](api/v1/views/__init__.py), you must add this new view at the end of the file. 116 | + Now you have an authentication based on a Session ID stored in cookie, perfect for a website (browsers love cookies). 117 | 118 | + [x] 8. **Logout** 119 | + Update the class `SessionAuth` by adding a new method `def destroy_session(self, request=None):` that deletes the user session / logout: 120 | + If the `request` is equal to `None`, return `False`. 121 | + If the `request` doesn't contain the Session ID cookie, return `False` - you must use `self.session_cookie(request)`. 122 | + If the Session ID of the request is not linked to any User ID, return `False` - you must use self.`user_id_for_session_id(...)`. 123 | + Otherwise, delete in `self.user_id_by_session_id` the Session ID (as key of this dictionary) and return `True`. 124 | + Update the file [api/v1/views/session_auth.py](api/v1/views/session_auth.py), by adding a new route `DELETE /api/v1/auth_session/logout`: 125 | + Slash tolerant. 126 | + You must use `from api.v1.app import auth`. 127 | + You must use `auth.destroy_session(request)` for deleting the Session ID contents in the request as cookie: 128 | + If `destroy_session` returns `False`, `abort(404)`. 129 | + Otherwise, return an empty JSON dictionary with the status code `200`. 130 | 131 | + [x] 9. **Expiration?** 132 | + Actually you have 2 authentication systems: 133 | + Basic authentication. 134 | + Session authentication. 135 | + Now you will add an expiration date to a Session ID. 136 | + Create a class `SessionExpAuth` that inherits from `SessionAuth` in the file [api/v1/auth/session_exp_auth.py](api/v1/auth/session_exp_auth.py): 137 | + Overload `def __init__(self):` method: 138 | + Assign an instance attribute `session_duration`: 139 | + To the environment variable `SESSION_DURATION` casts to an integer. 140 | + If this environment variable doesn't exist or can't be parse to an integer, assign to 0. 141 | + Overload `def create_session(self, user_id=None):`: 142 | + Create a Session ID by calling `super()` - `super()` will call the `create_session()` method of `SessionAuth`. 143 | + Return `None` if `super()` can't create a Session ID. 144 | + Use this Session ID as key of the dictionary `user_id_by_session_id` - the value for this key must be a dictionary (called "session dictionary"): 145 | + The key `user_id` must be set to the variable `user_id`. 146 | + The key `created_at` must be set to the current datetime - you must use `datetime.now()`. 147 | + Return the Session ID created. 148 | + Overload `def user_id_for_session_id(self, session_id=None):`: 149 | + Return `None` if `session_id` is `None`. 150 | + Return `None` if `user_id_by_session_id` doesn't contain any key equals to `session_id`. 151 | + Return the `user_id` key from the session dictionary if `self.session_duration` is equal or under 0. 152 | + Return `None` if session dictionary doesn't contain a key `created_at`. 153 | + Return `None` if the `created_at` + `session_duration` seconds are before the current datetime. [datetime - timedelta](https://docs.python.org/3.5/library/datetime.html#timedelta-objects). 154 | + Otherwise, return `user_id` from the `session` dictionary. 155 | + Update [api/v1/app.py](api/v1/app.py) to instantiate `auth` with `SessionExpAuth` if the environment variable `AUTH_TYPE` is equal to `session_exp_auth`. 156 | 157 | + [x] 10. **Sessions in database** 158 | + Since the beginning, all Session IDs are stored in memory. It means, if your application stops, all Session IDs are lost. 159 | + To avoid that, you will create a new authentication system, based on Session ID stored in database (for us, it will be in a file, like `User`). 160 | + Create a new model `UserSession` in [models/user_session.py](models/user_session.py) that inherits from `Base`: 161 | + Implement the `def __init__(self, *args: list, **kwargs: dict):` like in `User` but for these 2 attributes: 162 | + `user_id`: string. 163 | + `session_id`: string. 164 | + Create a new authentication class `SessionDBAuth` in [api/v1/auth/session_db_auth.py](api/v1/auth/session_db_auth.py) that inherits from `SessionExpAuth`: 165 | + Overload `def create_session(self, user_id=None):` that creates and stores new instance of `UserSession` and returns the Session ID. 166 | + Overload `def user_id_for_session_id(self, session_id=None):` that returns the User ID by requesting `UserSession` in the database based on `session_id`. 167 | + Overload `def destroy_session(self, request=None):` that destroys the `UserSession` based on the Session ID from the request cookie. 168 | + Update [api/v1/app.py](api/v1/app.py) to instantiate `auth` with `SessionDBAuth` if the environment variable `AUTH_TYPE` is equal to `session_db_auth`. -------------------------------------------------------------------------------- /0x02-Session_authentication/api/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x02-Session_authentication/api/.DS_Store -------------------------------------------------------------------------------- /0x02-Session_authentication/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x02-Session_authentication/api/__init__.py -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x02-Session_authentication/api/v1/.DS_Store -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x02-Session_authentication/api/v1/__init__.py -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Route module for the API. 3 | """ 4 | import os 5 | from os import getenv 6 | from flask import Flask, jsonify, abort, request 7 | from flask_cors import (CORS, cross_origin) 8 | 9 | from api.v1.views import app_views 10 | from api.v1.auth.auth import Auth 11 | from api.v1.auth.basic_auth import BasicAuth 12 | from api.v1.auth.session_auth import SessionAuth 13 | from api.v1.auth.session_db_auth import SessionDBAuth 14 | from api.v1.auth.session_exp_auth import SessionExpAuth 15 | 16 | 17 | app = Flask(__name__) 18 | app.register_blueprint(app_views) 19 | CORS(app, resources={r"/api/v1/*": {"origins": "*"}}) 20 | auth = None 21 | auth_type = getenv('AUTH_TYPE', 'auth') 22 | if auth_type == 'auth': 23 | auth = Auth() 24 | if auth_type == 'basic_auth': 25 | auth = BasicAuth() 26 | if auth_type == 'session_auth': 27 | auth = SessionAuth() 28 | if auth_type == 'session_exp_auth': 29 | auth = SessionExpAuth() 30 | if auth_type == 'session_db_auth': 31 | auth = SessionDBAuth() 32 | 33 | 34 | @app.errorhandler(404) 35 | def not_found(error) -> str: 36 | """Not found handler. 37 | """ 38 | return jsonify({"error": "Not found"}), 404 39 | 40 | 41 | @app.errorhandler(401) 42 | def unauthorized(error) -> str: 43 | """Unauthorized handler. 44 | """ 45 | return jsonify({"error": "Unauthorized"}), 401 46 | 47 | 48 | @app.errorhandler(403) 49 | def forbidden(error) -> str: 50 | """Forbidden handler. 51 | """ 52 | return jsonify({"error": "Forbidden"}), 403 53 | 54 | 55 | @app.before_request 56 | def authenticate_user(): 57 | """Authenticates a user before processing a request. 58 | """ 59 | if auth: 60 | excluded_paths = [ 61 | "/api/v1/status/", 62 | "/api/v1/unauthorized/", 63 | "/api/v1/forbidden/", 64 | "/api/v1/auth_session/login/", 65 | ] 66 | if auth.require_auth(request.path, excluded_paths): 67 | user = auth.current_user(request) 68 | if auth.authorization_header(request) is None and \ 69 | auth.session_cookie(request) is None: 70 | abort(401) 71 | if user is None: 72 | abort(403) 73 | request.current_user = user 74 | 75 | 76 | if __name__ == "__main__": 77 | host = getenv("API_HOST", "0.0.0.0") 78 | port = getenv("API_PORT", "5000") 79 | app.run(host=host, port=port) 80 | -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/auth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x02-Session_authentication/api/v1/auth/__init__.py -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/auth/auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Authentication module for the API. 3 | """ 4 | import os 5 | import re 6 | from typing import List, TypeVar 7 | from flask import request 8 | 9 | 10 | class Auth: 11 | """Authentication class. 12 | """ 13 | def require_auth(self, path: str, excluded_paths: List[str]) -> bool: 14 | """Checks if a path requires authentication. 15 | """ 16 | if path is not None and excluded_paths is not None: 17 | for exclusion_path in map(lambda x: x.strip(), excluded_paths): 18 | pattern = '' 19 | if exclusion_path[-1] == '*': 20 | pattern = '{}.*'.format(exclusion_path[0:-1]) 21 | elif exclusion_path[-1] == '/': 22 | pattern = '{}/*'.format(exclusion_path[0:-1]) 23 | else: 24 | pattern = '{}/*'.format(exclusion_path) 25 | if re.match(pattern, path): 26 | return False 27 | return True 28 | 29 | def authorization_header(self, request=None) -> str: 30 | """Gets the authorization header field from the request. 31 | """ 32 | if request is not None: 33 | return request.headers.get('Authorization', None) 34 | return None 35 | 36 | def current_user(self, request=None) -> TypeVar('User'): 37 | """Gets the current user from the request. 38 | """ 39 | return None 40 | 41 | def session_cookie(self, request=None) -> str: 42 | """Gets the value of the cookie named SESSION_NAME. 43 | """ 44 | if request is not None: 45 | cookie_name = os.getenv('SESSION_NAME') 46 | return request.cookies.get(cookie_name) 47 | -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/auth/basic_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Basic authentication module for the API. 3 | """ 4 | import re 5 | import base64 6 | import binascii 7 | from typing import Tuple, TypeVar 8 | 9 | from .auth import Auth 10 | from models.user import User 11 | 12 | 13 | class BasicAuth(Auth): 14 | """Basic authentication class. 15 | """ 16 | def extract_base64_authorization_header( 17 | self, 18 | authorization_header: str) -> str: 19 | """Extracts the Base64 part of the Authorization header 20 | for a Basic Authentication. 21 | """ 22 | if type(authorization_header) == str: 23 | pattern = r'Basic (?P.+)' 24 | field_match = re.fullmatch(pattern, authorization_header.strip()) 25 | if field_match is not None: 26 | return field_match.group('token') 27 | return None 28 | 29 | def decode_base64_authorization_header( 30 | self, 31 | base64_authorization_header: str, 32 | ) -> str: 33 | """Decodes a base64-encoded authorization header. 34 | """ 35 | if type(base64_authorization_header) == str: 36 | try: 37 | res = base64.b64decode( 38 | base64_authorization_header, 39 | validate=True, 40 | ) 41 | return res.decode('utf-8') 42 | except (binascii.Error, UnicodeDecodeError): 43 | return None 44 | 45 | def extract_user_credentials( 46 | self, 47 | decoded_base64_authorization_header: str, 48 | ) -> Tuple[str, str]: 49 | """Extracts user credentials from a base64-decoded authorization 50 | header that uses the Basic authentication flow. 51 | """ 52 | if type(decoded_base64_authorization_header) == str: 53 | pattern = r'(?P[^:]+):(?P.+)' 54 | field_match = re.fullmatch( 55 | pattern, 56 | decoded_base64_authorization_header.strip(), 57 | ) 58 | if field_match is not None: 59 | user = field_match.group('user') 60 | password = field_match.group('password') 61 | return user, password 62 | return None, None 63 | 64 | def user_object_from_credentials( 65 | self, 66 | user_email: str, 67 | user_pwd: str) -> TypeVar('User'): 68 | """Retrieves a user based on the user's authentication credentials. 69 | """ 70 | if type(user_email) == str and type(user_pwd) == str: 71 | try: 72 | users = User.search({'email': user_email}) 73 | except Exception: 74 | return None 75 | if len(users) <= 0: 76 | return None 77 | if users[0].is_valid_password(user_pwd): 78 | return users[0] 79 | return None 80 | 81 | def current_user(self, request=None) -> TypeVar('User'): 82 | """Retrieves the user from a request. 83 | """ 84 | auth_header = self.authorization_header(request) 85 | b64_auth_token = self.extract_base64_authorization_header(auth_header) 86 | auth_token = self.decode_base64_authorization_header(b64_auth_token) 87 | email, password = self.extract_user_credentials(auth_token) 88 | return self.user_object_from_credentials(email, password) 89 | -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/auth/session_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Session authentication module for the API. 3 | """ 4 | from uuid import uuid4 5 | from flask import request 6 | 7 | from .auth import Auth 8 | from models.user import User 9 | 10 | 11 | class SessionAuth(Auth): 12 | """Session authentication class. 13 | """ 14 | user_id_by_session_id = {} 15 | 16 | def create_session(self, user_id: str = None) -> str: 17 | """Creates a session id for the user. 18 | """ 19 | if type(user_id) is str: 20 | session_id = str(uuid4()) 21 | self.user_id_by_session_id[session_id] = user_id 22 | return session_id 23 | 24 | def user_id_for_session_id(self, session_id: str = None) -> str: 25 | """Retrieves the user id of the user associated with 26 | a given session id. 27 | """ 28 | if type(session_id) is str: 29 | return self.user_id_by_session_id.get(session_id) 30 | 31 | def current_user(self, request=None) -> User: 32 | """Retrieves the user associated with the request. 33 | """ 34 | user_id = self.user_id_for_session_id(self.session_cookie(request)) 35 | return User.get(user_id) 36 | 37 | def destroy_session(self, request=None): 38 | """Destroys an authenticated session. 39 | """ 40 | session_id = self.session_cookie(request) 41 | user_id = self.user_id_for_session_id(session_id) 42 | if (request is None or session_id is None) or user_id is None: 43 | return False 44 | if session_id in self.user_id_by_session_id: 45 | del self.user_id_by_session_id[session_id] 46 | return True 47 | -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/auth/session_db_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Session authentication with expiration 3 | and storage support module for the API. 4 | """ 5 | from flask import request 6 | from datetime import datetime, timedelta 7 | 8 | from models.user_session import UserSession 9 | from .session_exp_auth import SessionExpAuth 10 | 11 | 12 | class SessionDBAuth(SessionExpAuth): 13 | """Session authentication class with expiration and storage support. 14 | """ 15 | 16 | def create_session(self, user_id=None) -> str: 17 | """Creates and stores a session id for the user. 18 | """ 19 | session_id = super().create_session(user_id) 20 | if type(session_id) == str: 21 | kwargs = { 22 | 'user_id': user_id, 23 | 'session_id': session_id, 24 | } 25 | user_session = UserSession(**kwargs) 26 | user_session.save() 27 | return session_id 28 | 29 | def user_id_for_session_id(self, session_id=None): 30 | """Retrieves the user id of the user associated with 31 | a given session id. 32 | """ 33 | try: 34 | sessions = UserSession.search({'session_id': session_id}) 35 | except Exception: 36 | return None 37 | if len(sessions) <= 0: 38 | return None 39 | cur_time = datetime.now() 40 | time_span = timedelta(seconds=self.session_duration) 41 | exp_time = sessions[0].created_at + time_span 42 | if exp_time < cur_time: 43 | return None 44 | return sessions[0].user_id 45 | 46 | def destroy_session(self, request=None) -> bool: 47 | """Destroys an authenticated session. 48 | """ 49 | session_id = self.session_cookie(request) 50 | try: 51 | sessions = UserSession.search({'session_id': session_id}) 52 | except Exception: 53 | return False 54 | if len(sessions) <= 0: 55 | return False 56 | sessions[0].remove() 57 | return True 58 | -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/auth/session_exp_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Session authentication with expiration module for the API. 3 | """ 4 | import os 5 | from flask import request 6 | from datetime import datetime, timedelta 7 | 8 | from .session_auth import SessionAuth 9 | 10 | 11 | class SessionExpAuth(SessionAuth): 12 | """Session authentication class with expiration. 13 | """ 14 | 15 | def __init__(self) -> None: 16 | """Initializes a new SessionExpAuth instance. 17 | """ 18 | super().__init__() 19 | try: 20 | self.session_duration = int(os.getenv('SESSION_DURATION', '0')) 21 | except Exception: 22 | self.session_duration = 0 23 | 24 | def create_session(self, user_id=None): 25 | """Creates a session id for the user. 26 | """ 27 | session_id = super().create_session(user_id) 28 | if type(session_id) != str: 29 | return None 30 | self.user_id_by_session_id[session_id] = { 31 | 'user_id': user_id, 32 | 'created_at': datetime.now(), 33 | } 34 | return session_id 35 | 36 | def user_id_for_session_id(self, session_id=None) -> str: 37 | """Retrieves the user id of the user associated with 38 | a given session id. 39 | """ 40 | if session_id in self.user_id_by_session_id: 41 | session_dict = self.user_id_by_session_id[session_id] 42 | if self.session_duration <= 0: 43 | return session_dict['user_id'] 44 | if 'created_at' not in session_dict: 45 | return None 46 | cur_time = datetime.now() 47 | time_span = timedelta(seconds=self.session_duration) 48 | exp_time = session_dict['created_at'] + time_span 49 | if exp_time < cur_time: 50 | return None 51 | return session_dict['user_id'] 52 | -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/views/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x02-Session_authentication/api/v1/views/.DS_Store -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/views/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ DocDocDocDocDocDoc 3 | """ 4 | from flask import Blueprint 5 | 6 | app_views = Blueprint("app_views", __name__, url_prefix="/api/v1") 7 | 8 | from api.v1.views.index import * 9 | from api.v1.views.users import * 10 | 11 | User.load_from_file() 12 | 13 | from api.v1.views.session_auth import * 14 | -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/views/index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Module of Index views. 3 | """ 4 | from flask import jsonify, abort 5 | from api.v1.views import app_views 6 | 7 | 8 | @app_views.route('/status', methods=['GET'], strict_slashes=False) 9 | def status() -> str: 10 | """GET /api/v1/status 11 | Return: 12 | - the status of the API. 13 | """ 14 | return jsonify({"status": "OK"}) 15 | 16 | 17 | @app_views.route('/stats/', strict_slashes=False) 18 | def stats() -> str: 19 | """GET /api/v1/stats 20 | Return: 21 | - the number of each objects. 22 | """ 23 | from models.user import User 24 | stats = {} 25 | stats['users'] = User.count() 26 | return jsonify(stats) 27 | 28 | 29 | @app_views.route('/unauthorized/', strict_slashes=False) 30 | def unauthorized() -> None: 31 | """GET /api/v1/unauthorized 32 | Return: 33 | - Unauthorized error. 34 | """ 35 | abort(401) 36 | 37 | 38 | @app_views.route('/forbidden/', strict_slashes=False) 39 | def forbidden() -> None: 40 | """GET /api/v1/forbidden 41 | Return: 42 | - Forbidden error. 43 | """ 44 | abort(403) 45 | -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/views/session_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Module of session authenticating views. 3 | """ 4 | import os 5 | from typing import Tuple 6 | from flask import abort, jsonify, request 7 | 8 | from models.user import User 9 | from api.v1.views import app_views 10 | 11 | 12 | @app_views.route('/auth_session/login', methods=['POST'], strict_slashes=False) 13 | def login() -> Tuple[str, int]: 14 | """POST /api/v1/auth_session/login 15 | Return: 16 | - JSON representation of a User object. 17 | """ 18 | not_found_res = {"error": "no user found for this email"} 19 | email = request.form.get('email') 20 | if email is None or len(email.strip()) == 0: 21 | return jsonify({"error": "email missing"}), 400 22 | password = request.form.get('password') 23 | if password is None or len(password.strip()) == 0: 24 | return jsonify({"error": "password missing"}), 400 25 | try: 26 | users = User.search({'email': email}) 27 | except Exception: 28 | return jsonify(not_found_res), 404 29 | if len(users) <= 0: 30 | return jsonify(not_found_res), 404 31 | if users[0].is_valid_password(password): 32 | from api.v1.app import auth 33 | sessiond_id = auth.create_session(getattr(users[0], 'id')) 34 | res = jsonify(users[0].to_json()) 35 | res.set_cookie(os.getenv("SESSION_NAME"), sessiond_id) 36 | return res 37 | return jsonify({"error": "wrong password"}), 401 38 | 39 | @app_views.route( 40 | '/auth_session/logout', methods=['DELETE'], strict_slashes=False) 41 | def logout() -> Tuple[str, int]: 42 | """DELETE /api/v1/auth_session/logout 43 | Return: 44 | - An empty JSON object. 45 | """ 46 | from api.v1.app import auth 47 | is_destroyed = auth.destroy_session(request) 48 | if not is_destroyed: 49 | abort(404) 50 | return jsonify({}) 51 | -------------------------------------------------------------------------------- /0x02-Session_authentication/api/v1/views/users.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Module of Users views. 3 | """ 4 | from api.v1.views import app_views 5 | from flask import abort, jsonify, request 6 | from models.user import User 7 | 8 | 9 | @app_views.route('/users', methods=['GET'], strict_slashes=False) 10 | def view_all_users() -> str: 11 | """GET /api/v1/users 12 | Return: 13 | - list of all User objects JSON represented. 14 | """ 15 | all_users = [user.to_json() for user in User.all()] 16 | return jsonify(all_users) 17 | 18 | 19 | @app_views.route('/users/', methods=['GET'], strict_slashes=False) 20 | def view_one_user(user_id: str = None) -> str: 21 | """GET /api/v1/users/:id 22 | Path parameter: 23 | - User ID. 24 | Return: 25 | - User object JSON represented. 26 | - 404 if the User ID doesn't exist. 27 | """ 28 | if user_id is None: 29 | abort(404) 30 | if user_id == 'me': 31 | if request.current_user is None: 32 | abort(404) 33 | else: 34 | return jsonify(request.current_user.to_json()) 35 | user = User.get(user_id) 36 | if user is None: 37 | abort(404) 38 | return jsonify(user.to_json()) 39 | 40 | 41 | @app_views.route('/users/', methods=['DELETE'], strict_slashes=False) 42 | def delete_user(user_id: str = None) -> str: 43 | """DELETE /api/v1/users/:id 44 | Path parameter: 45 | - User ID. 46 | Return: 47 | - empty JSON is the User has been correctly deleted. 48 | - 404 if the User ID doesn't exist. 49 | """ 50 | if user_id is None: 51 | abort(404) 52 | user = User.get(user_id) 53 | if user is None: 54 | abort(404) 55 | user.remove() 56 | return jsonify({}), 200 57 | 58 | 59 | @app_views.route('/users', methods=['POST'], strict_slashes=False) 60 | def create_user() -> str: 61 | """POST /api/v1/users/ 62 | JSON body: 63 | - email. 64 | - password. 65 | - last_name (optional). 66 | - first_name (optional). 67 | Return: 68 | - User object JSON represented. 69 | - 400 if can't create the new User. 70 | """ 71 | rj = None 72 | error_msg = None 73 | try: 74 | rj = request.get_json() 75 | except Exception as e: 76 | rj = None 77 | if rj is None: 78 | error_msg = "Wrong format" 79 | if error_msg is None and rj.get("email", "") == "": 80 | error_msg = "email missing" 81 | if error_msg is None and rj.get("password", "") == "": 82 | error_msg = "password missing" 83 | if error_msg is None: 84 | try: 85 | user = User() 86 | user.email = rj.get("email") 87 | user.password = rj.get("password") 88 | user.first_name = rj.get("first_name") 89 | user.last_name = rj.get("last_name") 90 | user.save() 91 | return jsonify(user.to_json()), 201 92 | except Exception as e: 93 | error_msg = "Can't create User: {}".format(e) 94 | return jsonify({'error': error_msg}), 400 95 | 96 | 97 | @app_views.route('/users/', methods=['PUT'], strict_slashes=False) 98 | def update_user(user_id: str = None) -> str: 99 | """PUT /api/v1/users/:id 100 | Path parameter: 101 | - User ID. 102 | JSON body: 103 | - last_name (optional). 104 | - first_name (optional). 105 | Return: 106 | - User object JSON represented. 107 | - 404 if the User ID doesn't exist. 108 | - 400 if can't update the User. 109 | """ 110 | if user_id is None: 111 | abort(404) 112 | user = User.get(user_id) 113 | if user is None: 114 | abort(404) 115 | rj = None 116 | try: 117 | rj = request.get_json() 118 | except Exception as e: 119 | rj = None 120 | if rj is None: 121 | return jsonify({'error': "Wrong format"}), 400 122 | if rj.get('first_name') is not None: 123 | user.first_name = rj.get('first_name') 124 | if rj.get('last_name') is not None: 125 | user.last_name = rj.get('last_name') 126 | user.save() 127 | return jsonify(user.to_json()), 200 128 | -------------------------------------------------------------------------------- /0x02-Session_authentication/main_0.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 0 3 | """ 4 | import base64 5 | from api.v1.auth.basic_auth import BasicAuth 6 | from models.user import User 7 | 8 | """ Create a user test """ 9 | user_email = "bob@hbtn.io" 10 | user_clear_pwd = "H0lbertonSchool98!" 11 | 12 | user = User() 13 | user.email = user_email 14 | user.password = user_clear_pwd 15 | print("New user: {}".format(user.id)) 16 | user.save() 17 | 18 | basic_clear = "{}:{}".format(user_email, user_clear_pwd) 19 | print("Basic Base64: {}".format(base64.b64encode( 20 | basic_clear.encode('utf-8')).decode("utf-8"))) 21 | -------------------------------------------------------------------------------- /0x02-Session_authentication/main_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 1 3 | """ 4 | from api.v1.auth.session_auth import SessionAuth 5 | 6 | sa = SessionAuth() 7 | 8 | print("{}: {}".format(type(sa.user_id_by_session_id), sa.user_id_by_session_id)) 9 | 10 | user_id = None 11 | session = sa.create_session(user_id) 12 | print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id)) 13 | 14 | user_id = 89 15 | session = sa.create_session(user_id) 16 | print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id)) 17 | 18 | user_id = "abcde" 19 | session = sa.create_session(user_id) 20 | print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id)) 21 | 22 | user_id = "fghij" 23 | session = sa.create_session(user_id) 24 | print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id)) 25 | 26 | user_id = "abcde" 27 | session = sa.create_session(user_id) 28 | print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id)) 29 | -------------------------------------------------------------------------------- /0x02-Session_authentication/main_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 2 3 | """ 4 | from api.v1.auth.session_auth import SessionAuth 5 | 6 | sa = SessionAuth() 7 | 8 | user_id_1 = "abcde" 9 | session_1 = sa.create_session(user_id_1) 10 | print("{} => {}: {}".format(user_id_1, session_1, sa.user_id_by_session_id)) 11 | 12 | user_id_2 = "fghij" 13 | session_2 = sa.create_session(user_id_2) 14 | print("{} => {}: {}".format(user_id_2, session_2, sa.user_id_by_session_id)) 15 | 16 | print("---") 17 | 18 | tmp_session_id = None 19 | tmp_user_id = sa.user_id_for_session_id(tmp_session_id) 20 | print("{} => {}".format(tmp_session_id, tmp_user_id)) 21 | 22 | tmp_session_id = 89 23 | tmp_user_id = sa.user_id_for_session_id(tmp_session_id) 24 | print("{} => {}".format(tmp_session_id, tmp_user_id)) 25 | 26 | tmp_session_id = "doesntexist" 27 | tmp_user_id = sa.user_id_for_session_id(tmp_session_id) 28 | print("{} => {}".format(tmp_session_id, tmp_user_id)) 29 | 30 | print("---") 31 | 32 | tmp_session_id = session_1 33 | tmp_user_id = sa.user_id_for_session_id(tmp_session_id) 34 | print("{} => {}".format(tmp_session_id, tmp_user_id)) 35 | 36 | tmp_session_id = session_2 37 | tmp_user_id = sa.user_id_for_session_id(tmp_session_id) 38 | print("{} => {}".format(tmp_session_id, tmp_user_id)) 39 | 40 | print("---") 41 | 42 | session_1_bis = sa.create_session(user_id_1) 43 | print("{} => {}: {}".format(user_id_1, session_1_bis, sa.user_id_by_session_id)) 44 | 45 | tmp_user_id = sa.user_id_for_session_id(session_1_bis) 46 | print("{} => {}".format(session_1_bis, tmp_user_id)) 47 | 48 | tmp_user_id = sa.user_id_for_session_id(session_1) 49 | print("{} => {}".format(session_1, tmp_user_id)) 50 | -------------------------------------------------------------------------------- /0x02-Session_authentication/main_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Cookie server 3 | """ 4 | from flask import Flask, request 5 | from api.v1.auth.auth import Auth 6 | 7 | auth = Auth() 8 | 9 | app = Flask(__name__) 10 | 11 | 12 | @app.route('/', methods=['GET'], strict_slashes=False) 13 | def root_path(): 14 | """ Root path 15 | """ 16 | return "Cookie value: {}\n".format(auth.session_cookie(request)) 17 | 18 | 19 | if __name__ == "__main__": 20 | app.run(host="0.0.0.0", port="5000") 21 | -------------------------------------------------------------------------------- /0x02-Session_authentication/main_4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 4 3 | """ 4 | from api.v1.auth.basic_auth import BasicAuth 5 | 6 | a = BasicAuth() 7 | 8 | print(a.extract_user_credentials(None)) 9 | print(a.extract_user_credentials(89)) 10 | print(a.extract_user_credentials("Holberton School")) 11 | print(a.extract_user_credentials("Holberton:School")) 12 | print(a.extract_user_credentials("bob@gmail.com:toto1234")) 13 | -------------------------------------------------------------------------------- /0x02-Session_authentication/main_5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 5 3 | """ 4 | import uuid 5 | from api.v1.auth.basic_auth import BasicAuth 6 | from models.user import User 7 | 8 | """ Create a user test """ 9 | user_email = str(uuid.uuid4()) 10 | user_clear_pwd = str(uuid.uuid4()) 11 | user = User() 12 | user.email = user_email 13 | user.first_name = "Bob" 14 | user.last_name = "Dylan" 15 | user.password = user_clear_pwd 16 | print("New user: {}".format(user.display_name())) 17 | user.save() 18 | 19 | """ Retreive this user via the class BasicAuth """ 20 | 21 | a = BasicAuth() 22 | 23 | u = a.user_object_from_credentials(None, None) 24 | print(u.display_name() if u is not None else "None") 25 | 26 | u = a.user_object_from_credentials(89, 98) 27 | print(u.display_name() if u is not None else "None") 28 | 29 | u = a.user_object_from_credentials("email@notfound.com", "pwd") 30 | print(u.display_name() if u is not None else "None") 31 | 32 | u = a.user_object_from_credentials(user_email, "pwd") 33 | print(u.display_name() if u is not None else "None") 34 | 35 | u = a.user_object_from_credentials(user_email, user_clear_pwd) 36 | print(u.display_name() if u is not None else "None") 37 | -------------------------------------------------------------------------------- /0x02-Session_authentication/main_6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Main 6 3 | """ 4 | import base64 5 | from api.v1.auth.basic_auth import BasicAuth 6 | from models.user import User 7 | 8 | """ Create a user test """ 9 | user_email = "bob@hbtn.io" 10 | user_clear_pwd = "H0lbertonSchool98!" 11 | user = User() 12 | user.email = user_email 13 | user.password = user_clear_pwd 14 | print("New user: {} / {}".format(user.id, user.display_name())) 15 | user.save() 16 | 17 | basic_clear = "{}:{}".format(user_email, user_clear_pwd) 18 | print("Basic Base64: {}".format(base64.b64encode( 19 | basic_clear.encode('utf-8')).decode("utf-8"))) 20 | -------------------------------------------------------------------------------- /0x02-Session_authentication/models/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x02-Session_authentication/models/.DS_Store -------------------------------------------------------------------------------- /0x02-Session_authentication/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-user-data/a4dbbe5de9f9cc848009c7844c251d80b3324f20/0x02-Session_authentication/models/__init__.py -------------------------------------------------------------------------------- /0x02-Session_authentication/models/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Base module. 3 | """ 4 | import json 5 | import uuid 6 | from os import path 7 | from datetime import datetime 8 | from typing import TypeVar, List, Iterable 9 | 10 | 11 | TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S" 12 | DATA = {} 13 | 14 | 15 | class Base(): 16 | """Base class. 17 | """ 18 | 19 | def __init__(self, *args: list, **kwargs: dict): 20 | """Initialize a Base instance. 21 | """ 22 | s_class = str(self.__class__.__name__) 23 | if DATA.get(s_class) is None: 24 | DATA[s_class] = {} 25 | 26 | self.id = kwargs.get('id', str(uuid.uuid4())) 27 | if kwargs.get('created_at') is not None: 28 | self.created_at = datetime.strptime(kwargs.get('created_at'), 29 | TIMESTAMP_FORMAT) 30 | else: 31 | self.created_at = datetime.utcnow() 32 | if kwargs.get('updated_at') is not None: 33 | self.updated_at = datetime.strptime(kwargs.get('updated_at'), 34 | TIMESTAMP_FORMAT) 35 | else: 36 | self.updated_at = datetime.utcnow() 37 | 38 | def __eq__(self, other: TypeVar('Base')) -> bool: 39 | """Equality. 40 | """ 41 | if type(self) != type(other): 42 | return False 43 | if not isinstance(self, Base): 44 | return False 45 | return (self.id == other.id) 46 | 47 | def to_json(self, for_serialization: bool = False) -> dict: 48 | """Convert the object a JSON dictionary. 49 | """ 50 | result = {} 51 | for key, value in self.__dict__.items(): 52 | if not for_serialization and key[0] == '_': 53 | continue 54 | if type(value) is datetime: 55 | result[key] = value.strftime(TIMESTAMP_FORMAT) 56 | else: 57 | result[key] = value 58 | return result 59 | 60 | @classmethod 61 | def load_from_file(cls): 62 | """Load all objects from file. 63 | """ 64 | s_class = cls.__name__ 65 | file_path = ".db_{}.json".format(s_class) 66 | DATA[s_class] = {} 67 | if not path.exists(file_path): 68 | return 69 | 70 | with open(file_path, 'r') as f: 71 | objs_json = json.load(f) 72 | for obj_id, obj_json in objs_json.items(): 73 | DATA[s_class][obj_id] = cls(**obj_json) 74 | 75 | @classmethod 76 | def save_to_file(cls): 77 | """Save all objects to file. 78 | """ 79 | s_class = cls.__name__ 80 | file_path = ".db_{}.json".format(s_class) 81 | objs_json = {} 82 | for obj_id, obj in DATA[s_class].items(): 83 | objs_json[obj_id] = obj.to_json(True) 84 | 85 | with open(file_path, 'w') as f: 86 | json.dump(objs_json, f) 87 | 88 | def save(self): 89 | """Save current object. 90 | """ 91 | s_class = self.__class__.__name__ 92 | self.updated_at = datetime.utcnow() 93 | DATA[s_class][self.id] = self 94 | self.__class__.save_to_file() 95 | 96 | def remove(self): 97 | """Remove object. 98 | """ 99 | s_class = self.__class__.__name__ 100 | if DATA[s_class].get(self.id) is not None: 101 | del DATA[s_class][self.id] 102 | self.__class__.save_to_file() 103 | 104 | @classmethod 105 | def count(cls) -> int: 106 | """Count all objects. 107 | """ 108 | s_class = cls.__name__ 109 | return len(DATA[s_class].keys()) 110 | 111 | @classmethod 112 | def all(cls) -> Iterable[TypeVar('Base')]: 113 | """Return all objects. 114 | """ 115 | return cls.search() 116 | 117 | @classmethod 118 | def get(cls, id: str) -> TypeVar('Base'): 119 | """Return one object by ID. 120 | """ 121 | s_class = cls.__name__ 122 | return DATA[s_class].get(id) 123 | 124 | @classmethod 125 | def search(cls, attributes: dict = {}) -> List[TypeVar('Base')]: 126 | """Search all objects with matching attributes. 127 | """ 128 | s_class = cls.__name__ 129 | def _search(obj): 130 | if len(attributes) == 0: 131 | return True 132 | for k, v in attributes.items(): 133 | if (getattr(obj, k) != v): 134 | return False 135 | return True 136 | 137 | return list(filter(_search, DATA[s_class].values())) 138 | -------------------------------------------------------------------------------- /0x02-Session_authentication/models/user.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """User module. 3 | """ 4 | import hashlib 5 | from models.base import Base 6 | 7 | 8 | class User(Base): 9 | """User class. 10 | """ 11 | 12 | def __init__(self, *args: list, **kwargs: dict): 13 | """Initialize a User instance. 14 | """ 15 | super().__init__(*args, **kwargs) 16 | self.email = kwargs.get('email') 17 | self._password = kwargs.get('_password') 18 | self.first_name = kwargs.get('first_name') 19 | self.last_name = kwargs.get('last_name') 20 | 21 | @property 22 | def password(self) -> str: 23 | """Getter of the password. 24 | """ 25 | return self._password 26 | 27 | @password.setter 28 | def password(self, pwd: str): 29 | """Setter of a new password: encrypt in SHA256. 30 | 31 | WARNING: Use a better password hashing algorithm like argon2 32 | or bcrypt in real-world projects. 33 | """ 34 | if pwd is None or type(pwd) is not str: 35 | self._password = None 36 | else: 37 | self._password = hashlib.sha256(pwd.encode()).hexdigest().lower() 38 | 39 | def is_valid_password(self, pwd: str) -> bool: 40 | """Validate a password. 41 | """ 42 | if pwd is None or type(pwd) is not str: 43 | return False 44 | if self.password is None: 45 | return False 46 | pwd_e = pwd.encode() 47 | return hashlib.sha256(pwd_e).hexdigest().lower() == self.password 48 | 49 | def display_name(self) -> str: 50 | """Display User name based on email/first_name/last_name. 51 | """ 52 | if self.email is None and self.first_name is None \ 53 | and self.last_name is None: 54 | return "" 55 | if self.first_name is None and self.last_name is None: 56 | return "{}".format(self.email) 57 | if self.last_name is None: 58 | return "{}".format(self.first_name) 59 | if self.first_name is None: 60 | return "{}".format(self.last_name) 61 | else: 62 | return "{} {}".format(self.first_name, self.last_name) 63 | -------------------------------------------------------------------------------- /0x02-Session_authentication/models/user_session.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """User session module. 3 | """ 4 | from models.base import Base 5 | 6 | 7 | class UserSession(Base): 8 | """User session class. 9 | """ 10 | 11 | def __init__(self, *args: list, **kwargs: dict): 12 | """Initializes a User session instance. 13 | """ 14 | super().__init__(*args, **kwargs) 15 | self.user_id = kwargs.get('user_id') 16 | self.session_id = kwargs.get('session_id') 17 | -------------------------------------------------------------------------------- /0x02-Session_authentication/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2022.12.7 2 | chardet==3.0.4 3 | click==8.1.3 4 | Flask==1.1.2 5 | Flask-Cors==3.0.8 6 | idna==2.6 7 | importlib-metadata==6.0.0 8 | itsdangerous==2.0.1 9 | Jinja2==2.11.2 10 | MarkupSafe==2.0.1 11 | pycodestyle==2.6.0 12 | requests==2.18.4 13 | six==1.16.0 14 | typing_extensions==4.5.0 15 | urllib3==1.22 16 | Werkzeug==2.0.3 17 | zipp==3.15.0 18 | -------------------------------------------------------------------------------- /0x03-user_authentication_service/README.md: -------------------------------------------------------------------------------- 1 | # User Authentication Service 2 | 3 | This project contains tasks for learning to create a user authentication service. 4 | 5 | ## Requirements 6 | 7 | + SQLAlchemy 1.3.x 8 | + pycodestyle 2.5 9 | + bcrypt 10 | + python3 3.7 11 | 12 | ## Tasks To Complete 13 | 14 | + [x] 0. **User model**
[user.py](user.py) contains a SQLAlchemy model named `User` for a database table named `users` (by using the [mapping declaration](https://docs.sqlalchemy.org/en/13/orm/tutorial.html#declare-a-mapping) of SQLAlchemy) and meets the following requirements: 15 | + The model will have the following attributes: 16 | + `id`, the integer primary key. 17 | + `email`, a non-nullable string. 18 | + `hashed_password`, a non-nullable string. 19 | + `session_id`, a nullable string. 20 | + `reset_token`, a nullable string. 21 | 22 | + [x] 1. **create user**
[db.py](db.py) contains a completion of the `DB` class provided below to implement the `add_user` method according to the given requirements: 23 | +   24 | ```python 25 | #!/usr/bin/env python3 26 | """DB module. 27 | """ 28 | from sqlalchemy import create_engine 29 | from sqlalchemy.orm import sessionmaker 30 | from sqlalchemy.orm.session import Session 31 | from sqlalchemy.ext.declarative import declarative_base 32 | 33 | from user import Base 34 | 35 | 36 | class DB: 37 | """DB class. 38 | """ 39 | 40 | def __init__(self) -> None: 41 | """Initialize a new DB instance. 42 | """ 43 | self._engine = create_engine("sqlite:///a.db", echo=False) 44 | Base.metadata.drop_all(self._engine) 45 | Base.metadata.create_all(self._engine) 46 | self.__session = None 47 | 48 | @property 49 | def _session(self) -> Session: 50 | """Memoized session object. 51 | """ 52 | if self.__session is None: 53 | DBSession = sessionmaker(bind=self._engine) 54 | self.__session = DBSession() 55 | return self.__session 56 | ``` 57 | + Note that `DB._session` is a private property and hence should NEVER be used from outside the DB class. 58 | + Implement the `add_user` method, which has two required string arguments: `email` and `hashed_password`, and returns a `User` object. The method should save the user to the database. No validations are required at this stage. 59 | 60 | + [x] 2. **Find user**
[db.py](db.py) contains the following updates: 61 | + Implement the `DB.find_user_by` method. This method takes in arbitrary keyword arguments and returns the first row found in the `users` table as filtered by the method’s input arguments. No validation of input arguments required at this point. 62 | + Make sure that SQLAlchemy’s `NoResultFound` and `InvalidRequestError` are raised when no results are found, or when wrong query arguments are passed, respectively. 63 | + **Warning:** 64 | + `NoResultFound` has been moved from `sqlalchemy.orm.exc` to `sqlalchemy.exc` between the version 1.3.x and 1.4.x of SQLAchemy - please make sure you are importing it from `sqlalchemy.orm.exc`. 65 | 66 | + [x] 3. **update user**
[db.py](db.py) contains the following updates: 67 | + Implement the `DB.update_user` method that takes as arguments a required `user_id` integer, an arbitrary keyword arguments, and returns `None`. 68 | + The method will use `find_user_by` to locate the user to update, then will update the user’s attributes as passed in the method’s arguments then commit changes to the database. 69 | + If an argument that does not correspond to a user attribute is passed, raise a `ValueError`. 70 | 71 | + [x] 4. **Hash password**
[auth.py](auth.py) contains a `_hash_password` method that takes in a password string arguments and returns bytes, which is a salted hash of the input password, hashed with `bcrypt.hashpw`. 72 | 73 | + [x] 5. **Register user**
[auth.py](auth.py) contains the following updates: 74 | + Implement the `Auth.register_user` in the `Auth` class provided below: 75 | ```python 76 | from db import DB 77 | 78 | 79 | class Auth: 80 | """Auth class to interact with the authentication database. 81 | """ 82 | 83 | def __init__(self): 84 | self._db = DB() 85 | ``` 86 | + Note that `Auth._db` is a private property and should NEVER be used from outside the class. 87 | + `Auth.register_user` should take mandatory `email` and `password` string arguments and return a `User` object. 88 | + If a user already exist with the passed `email`, raise a `ValueError` with the message `User already exists`. 89 | + If not, hash the password with `_hash_password`, save the user to the database using `self._db` and return the `User` object. 90 | 91 | + [x] 6. **Basic Flask app**
[app.py](app.py) contains a basic Flask app with the following requirements: 92 | + Create a Flask app that has a single `GET` route (`"/"`) and use `flask.jsonify` to return a JSON payload of the form: 93 | ```json 94 | {"message": "Bienvenue"} 95 | ``` 96 | + Add the following code at the end of the module: 97 | ```python 98 | if __name__ == "__main__": 99 | app.run(host="0.0.0.0", port="5000") 100 | ``` 101 | 102 | + [x] 7. **Register user**
[app.py](app.py) contains the following updates: 103 | + Implement the end-point to register a user. Define a `users` function that implements the `POST /users` route. 104 | + Import the `Auth` object and instantiate it at the root of the module as such: 105 | ```python 106 | from auth import Auth 107 | 108 | 109 | AUTH = Auth() 110 | ``` 111 | + The end-point should expect two form data fields: `"email"` and `"password"`. If the user does not exist, the end-point should register it and respond with the following JSON payload: 112 | ```json 113 | {"email": "", "message": "user created"} 114 | ``` 115 | + If the user is already registered, catch the exception and return a JSON payload of the form 116 | ```json 117 | {"message": "email already registered"} 118 | ``` 119 | and return a 400 status code. 120 | + Remember that you should only use `AUTH` in this app. `DB` is a lower abstraction that is proxied by `Auth`. 121 | 122 | + [x] 8. **Credentials validation**
[auth.py](auth.py) contains the following updates: 123 | + Implement the `Auth.valid_login` method. It should expect `email` and `password` required arguments and return a boolean. 124 | + Try locating the user by email. If it exists, check the password with `bcrypt.checkpw`. If it matches return `True`. In any other case, return `False`. 125 | 126 | + [x] 9. **Generate UUIDs**
[auth.py](auth.py) contains the following updates: 127 | + Implement a `_generate_uuid` function in the `auth` module. The function should return a string representation of a new UUID. Use the `uuid` module. 128 | + Note that the method is private to the `auth` module and should **NOT** be used outside of it. 129 | 130 | + [x] 10. **Get session ID**
[auth.py](auth.py) contains the following updates: 131 | + Implement the `Auth.create_session` method. It takes an `email` string argument and returns the session ID as a string. 132 | + The method should find the user corresponding to the email, generate a new UUID and store it in the database as the user’s `session_id`, then return the session ID. 133 | + Remember that only public methods of `self._db` can be used. 134 | 135 | + [x] 11. **Log in**
[app.py](app.py) contains the following updates: 136 | + Implement a `login` function to respond to the `POST /sessions` route. 137 | + The request is expected to contain form data with `"email"` and a `"password"` fields. 138 | + If the login information is incorrect, use `flask.abort` to respond with a 401 HTTP status. 139 | + Otherwise, create a new session for the user, store it the session ID as a cookie with key `"session_id"` on the response and return a JSON payload of the form: 140 | ```json 141 | {"email": "", "message": "logged in"} 142 | ``` 143 | 144 | + [x] 12. **Find user by session ID**
[auth.py](auth.py) contains the following updates: 145 | + Implement the `Auth.get_user_from_session_id` method. It takes a single `session_id` string argument and returns the corresponding `User` or `None`. 146 | + If the session ID is `None` or no user is found, return `None`. Otherwise return the corresponding user. 147 | + Remember to only use public methods of `self._db`. 148 | 149 | + [x] 13. **Destroy session**
[auth.py](auth.py) contains the following updates: 150 | + Implement `Auth.destroy_session`. The method takes a single `user_id` integer argument and returns `None`. 151 | + The method updates the corresponding user’s session ID to `None`. 152 | + Remember to only use public methods of `self._db`. 153 | 154 | + [x] 14. **Log out**
[app.py](app.py) contains the following updates: 155 | + Implement a `logout` function to respond to the `DELETE /sessions` route. 156 | + The request is expected to contain the session ID as a cookie with key `"session_id"`. 157 | + Find the user with the requested session ID. If the user exists destroy the session and redirect the user to `GET /`. If the user does not exist, respond with a 403 HTTP status. 158 | 159 | + [x] 15. **User profile**
[app.py](app.py) contains the following updates: 160 | + Implement a `profile` function to respond to the `GET /profile` route. 161 | + The request is expected to contain a `session_id` cookie. Use it to find the user. If the user exist, respond with a 200 HTTP status and the following JSON payload: 162 | ```json 163 | {"email": ""} 164 | ``` 165 | + If the session ID is invalid or the user does not exist, respond with a 403 HTTP status. 166 | 167 | + [x] 16. **Generate reset password token**
[auth.py](auth.py) contains the following updates: 168 | + Implement the `Auth.get_reset_password_token` method. It takes an `email` string argument and returns a string. 169 | + Find the user corresponding to the email. If the user does not exist, raise a `ValueError` exception. If it exists, generate a UUID and update the user’s `reset_token` database field. Return the token. 170 | 171 | + [x] 17. **Get reset password token**
[app.py](app.py) contains the following updates: 172 | + Implement a `get_reset_password_token` function to respond to the `POST /reset_password` route. 173 | + The request is expected to contain form data with the `"email"` field. 174 | + If the email is not registered, respond with a 403 status code. Otherwise, generate a token and respond with a 200 HTTP status and the following JSON payload: 175 | ```json 176 | {"email": "", "reset_token": ""} 177 | ``` 178 | 179 | + [x] 18. **Update password**
[auth.py](auth.py) contains the following updates: 180 | + Implement the `Auth.update_password` method. It takes `reset_token` string argument and a `password` string argument and returns `None`. 181 | + Use the `reset_token` to find the corresponding user. If it does not exist, raise a `ValueError` exception. 182 | + Otherwise, hash the password and update the user’s `hashed_password` field with the new hashed password and the `reset_token` field to `None`. 183 | 184 | + [x] 19. **Update password end-point**
[app.py](app.py) contains the following updates: 185 | + Implement the `update_password` function in the `app` module to respond to the `PUT /reset_password` route. 186 | + The request is expected to contain form data with fields `"email"`, `"reset_token"` and `"new_password"`. 187 | + Update the password. If the token is invalid, catch the exception and respond with a 403 HTTP code. 188 | + If the token is valid, respond with a 200 HTTP code and the following JSON payload: 189 | ```json 190 | {"email": "", "message": "Password updated"} 191 | ``` 192 | 193 | + [x] 20. **End-to-end integration test** 194 | + Start the Flask app you created in the previous tasks. Open a new terminal window. 195 | + Create a new module called [main.py](main.py). Create one function for each of the following sub tasks. Use the `requests` module to query your web server for the corresponding end-point. Use `assert` to validate the response’s expected status code and payload (if any) for each sub task: 196 | + `register_user(email: str, password: str) -> None`. 197 | + `log_in_wrong_password(email: str, password: str) -> None`. 198 | + `log_in(email: str, password: str) -> str`. 199 | + `profile_unlogged() -> None`. 200 | + `profile_logged(session_id: str) -> None`. 201 | + `log_out(session_id: str) -> None`. 202 | + `reset_password_token(email: str) -> str`. 203 | + `update_password(email: str, reset_token: str, new_password: str) -> None`. 204 | + Copy the following code at the end of the `main` module: 205 | ```python 206 | EMAIL = "guillaume@holberton.io" 207 | PASSWD = "b4l0u" 208 | NEW_PASSWD = "t4rt1fl3tt3" 209 | 210 | 211 | if __name__ == "__main__": 212 | 213 | register_user(EMAIL, PASSWD) 214 | log_in_wrong_password(EMAIL, NEW_PASSWD) 215 | profile_unlogged() 216 | session_id = log_in(EMAIL, PASSWD) 217 | profile_logged(session_id) 218 | log_out(session_id) 219 | reset_token = reset_password_token(EMAIL) 220 | update_password(EMAIL, reset_token, NEW_PASSWD) 221 | log_in(EMAIL, NEW_PASSWD) 222 | ``` 223 | + Run `python3 main.py`. If everything is correct, you should see no output. 224 | -------------------------------------------------------------------------------- /0x03-user_authentication_service/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """A simple Flask app with user authentication features. 3 | """ 4 | from flask import Flask, jsonify, request, abort, redirect 5 | 6 | from auth import Auth 7 | 8 | 9 | app = Flask(__name__) 10 | AUTH = Auth() 11 | 12 | 13 | @app.route("/", methods=["GET"], strict_slashes=False) 14 | def index() -> str: 15 | """GET / 16 | Return: 17 | - The home page's payload. 18 | """ 19 | return jsonify({"message": "Bienvenue"}) 20 | 21 | 22 | @app.route("/users", methods=["POST"], strict_slashes=False) 23 | def users() -> str: 24 | """POST /users 25 | Return: 26 | - The account creation payload. 27 | """ 28 | email, password = request.form.get("email"), request.form.get("password") 29 | try: 30 | AUTH.register_user(email, password) 31 | return jsonify({"email": email, "message": "user created"}) 32 | except ValueError: 33 | return jsonify({"message": "email already registered"}), 400 34 | 35 | 36 | @app.route("/sessions", methods=["POST"], strict_slashes=False) 37 | def login() -> str: 38 | """POST /sessions 39 | Return: 40 | - The account login payload. 41 | """ 42 | email, password = request.form.get("email"), request.form.get("password") 43 | if not AUTH.valid_login(email, password): 44 | abort(401) 45 | session_id = AUTH.create_session(email) 46 | response = jsonify({"email": email, "message": "logged in"}) 47 | response.set_cookie("session_id", session_id) 48 | return response 49 | 50 | 51 | @app.route("/sessions", methods=["DELETE"], strict_slashes=False) 52 | def logout() -> str: 53 | """DELETE /sessions 54 | Return: 55 | - Redirects to home route. 56 | """ 57 | session_id = request.cookies.get("session_id") 58 | user = AUTH.get_user_from_session_id(session_id) 59 | if user is None: 60 | abort(403) 61 | AUTH.destroy_session(user.id) 62 | return redirect("/") 63 | 64 | 65 | @app.route("/profile", methods=["GET"], strict_slashes=False) 66 | def profile() -> str: 67 | """GET /profile 68 | Return: 69 | - The user's profile information. 70 | """ 71 | session_id = request.cookies.get("session_id") 72 | user = AUTH.get_user_from_session_id(session_id) 73 | if user is None: 74 | abort(403) 75 | return jsonify({"email": user.email}) 76 | 77 | 78 | @app.route("/reset_password", methods=["POST"], strict_slashes=False) 79 | def get_reset_password_token() -> str: 80 | """POST /reset_password 81 | Return: 82 | - The user's password reset payload. 83 | """ 84 | email = request.form.get("email") 85 | reset_token = None 86 | try: 87 | reset_token = AUTH.get_reset_password_token(email) 88 | except ValueError: 89 | reset_token = None 90 | if reset_token is None: 91 | abort(403) 92 | return jsonify({"email": email, "reset_token": reset_token}) 93 | 94 | 95 | @app.route("/reset_password", methods=["PUT"], strict_slashes=False) 96 | def update_password() -> str: 97 | """PUT /reset_password 98 | 99 | Return: 100 | - The user's password updated payload. 101 | """ 102 | email = request.form.get("email") 103 | reset_token = request.form.get("reset_token") 104 | new_password = request.form.get("new_password") 105 | is_password_changed = False 106 | try: 107 | AUTH.update_password(reset_token, new_password) 108 | is_password_changed = True 109 | except ValueError: 110 | is_password_changed = False 111 | if not is_password_changed: 112 | abort(403) 113 | return jsonify({"email": email, "message": "Password updated"}) 114 | 115 | 116 | if __name__ == "__main__": 117 | app.run(host="0.0.0.0", port="5000") 118 | -------------------------------------------------------------------------------- /0x03-user_authentication_service/auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """A module for authentication-related routines. 3 | """ 4 | import bcrypt 5 | from uuid import uuid4 6 | from typing import Union 7 | from sqlalchemy.orm.exc import NoResultFound 8 | 9 | from db import DB 10 | from user import User 11 | 12 | 13 | def _hash_password(password: str) -> bytes: 14 | """Hashes a password. 15 | """ 16 | return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) 17 | 18 | 19 | def _generate_uuid() -> str: 20 | """Generates a UUID. 21 | """ 22 | return str(uuid4()) 23 | 24 | 25 | class Auth: 26 | """Auth class to interact with the authentication database. 27 | """ 28 | 29 | def __init__(self): 30 | """Initializes a new Auth instance. 31 | """ 32 | self._db = DB() 33 | 34 | def register_user(self, email: str, password: str) -> User: 35 | """Adds a new user to the database. 36 | """ 37 | try: 38 | self._db.find_user_by(email=email) 39 | except NoResultFound: 40 | return self._db.add_user(email, _hash_password(password)) 41 | raise ValueError("User {} already exists".format(email)) 42 | 43 | def valid_login(self, email: str, password: str) -> bool: 44 | """Checks if a user's login details are valid. 45 | """ 46 | user = None 47 | try: 48 | user = self._db.find_user_by(email=email) 49 | if user is not None: 50 | return bcrypt.checkpw( 51 | password.encode("utf-8"), 52 | user.hashed_password, 53 | ) 54 | except NoResultFound: 55 | return False 56 | return False 57 | 58 | def create_session(self, email: str) -> str: 59 | """Creates a new session for a user. 60 | """ 61 | user = None 62 | try: 63 | user = self._db.find_user_by(email=email) 64 | except NoResultFound: 65 | return None 66 | if user is None: 67 | return None 68 | session_id = _generate_uuid() 69 | self._db.update_user(user.id, session_id=session_id) 70 | return session_id 71 | 72 | def get_user_from_session_id(self, session_id: str) -> Union[User, None]: 73 | """Retrieves a user based on a given session ID. 74 | """ 75 | user = None 76 | if session_id is None: 77 | return None 78 | try: 79 | user = self._db.find_user_by(session_id=session_id) 80 | except NoResultFound: 81 | return None 82 | return user 83 | 84 | def destroy_session(self, user_id: int) -> None: 85 | """Destroys a session associated with a given user. 86 | """ 87 | if user_id is None: 88 | return None 89 | self._db.update_user(user_id, session_id=None) 90 | 91 | def get_reset_password_token(self, email: str) -> str: 92 | """Generates a password reset token for a user. 93 | """ 94 | user = None 95 | try: 96 | user = self._db.find_user_by(email=email) 97 | except NoResultFound: 98 | user = None 99 | if user is None: 100 | raise ValueError() 101 | reset_token = _generate_uuid() 102 | self._db.update_user(user.id, reset_token=reset_token) 103 | return reset_token 104 | 105 | def update_password(self, reset_token: str, password: str) -> None: 106 | """Updates a user's password given the user's reset token. 107 | """ 108 | user = None 109 | try: 110 | user = self._db.find_user_by(reset_token=reset_token) 111 | except NoResultFound: 112 | user = None 113 | if user is None: 114 | raise ValueError() 115 | new_password_hash = _hash_password(password) 116 | self._db.update_user( 117 | user.id, 118 | hashed_password=new_password_hash, 119 | reset_token=None, 120 | ) 121 | -------------------------------------------------------------------------------- /0x03-user_authentication_service/db.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """DB module. 3 | """ 4 | from sqlalchemy import create_engine, tuple_ 5 | from sqlalchemy.exc import InvalidRequestError 6 | from sqlalchemy.ext.declarative import declarative_base 7 | from sqlalchemy.orm import sessionmaker 8 | from sqlalchemy.orm.exc import NoResultFound 9 | from sqlalchemy.orm.session import Session 10 | 11 | from user import Base, User 12 | 13 | 14 | class DB: 15 | """DB class. 16 | """ 17 | 18 | def __init__(self) -> None: 19 | """Initialize a new DB instance. 20 | """ 21 | self._engine = create_engine("sqlite:///a.db", echo=False) 22 | Base.metadata.drop_all(self._engine) 23 | Base.metadata.create_all(self._engine) 24 | self.__session = None 25 | 26 | @property 27 | def _session(self) -> Session: 28 | """Memoized session object. 29 | """ 30 | if self.__session is None: 31 | DBSession = sessionmaker(bind=self._engine) 32 | self.__session = DBSession() 33 | return self.__session 34 | 35 | def add_user(self, email: str, hashed_password: str) -> User: 36 | """Adds a new user to the database. 37 | """ 38 | try: 39 | new_user = User(email=email, hashed_password=hashed_password) 40 | self._session.add(new_user) 41 | self._session.commit() 42 | except Exception: 43 | self._session.rollback() 44 | new_user = None 45 | return new_user 46 | 47 | def find_user_by(self, **kwargs) -> User: 48 | """Finds a user based on a set of filters. 49 | """ 50 | fields, values = [], [] 51 | for key, value in kwargs.items(): 52 | if hasattr(User, key): 53 | fields.append(getattr(User, key)) 54 | values.append(value) 55 | else: 56 | raise InvalidRequestError() 57 | result = self._session.query(User).filter( 58 | tuple_(*fields).in_([tuple(values)]) 59 | ).first() 60 | if result is None: 61 | raise NoResultFound() 62 | return result 63 | 64 | def update_user(self, user_id: int, **kwargs) -> None: 65 | """Updates a user based on a given id. 66 | """ 67 | user = self.find_user_by(id=user_id) 68 | if user is None: 69 | return 70 | update_source = {} 71 | for key, value in kwargs.items(): 72 | if hasattr(User, key): 73 | update_source[getattr(User, key)] = value 74 | else: 75 | raise ValueError() 76 | self._session.query(User).filter(User.id == user_id).update( 77 | update_source, 78 | synchronize_session=False, 79 | ) 80 | self._session.commit() 81 | -------------------------------------------------------------------------------- /0x03-user_authentication_service/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """A simple end-to-end (E2E) integration test for `app.py`. 3 | """ 4 | import requests 5 | 6 | 7 | EMAIL = "guillaume@holberton.io" 8 | PASSWD = "b4l0u" 9 | NEW_PASSWD = "t4rt1fl3tt3" 10 | BASE_URL = "http://0.0.0.0:5000" 11 | 12 | 13 | def register_user(email: str, password: str) -> None: 14 | """Tests registering a user. 15 | """ 16 | url = "{}/users".format(BASE_URL) 17 | body = { 18 | 'email': email, 19 | 'password': password, 20 | } 21 | res = requests.post(url, data=body) 22 | assert res.status_code == 200 23 | assert res.json() == {"email": email, "message": "user created"} 24 | res = requests.post(url, data=body) 25 | assert res.status_code == 400 26 | assert res.json() == {"message": "email already registered"} 27 | 28 | 29 | def log_in_wrong_password(email: str, password: str) -> None: 30 | """Tests logging in with a wrong password. 31 | """ 32 | url = "{}/sessions".format(BASE_URL) 33 | body = { 34 | 'email': email, 35 | 'password': password, 36 | } 37 | res = requests.post(url, data=body) 38 | assert res.status_code == 401 39 | 40 | 41 | def log_in(email: str, password: str) -> str: 42 | """Tests logging in. 43 | """ 44 | url = "{}/sessions".format(BASE_URL) 45 | body = { 46 | 'email': email, 47 | 'password': password, 48 | } 49 | res = requests.post(url, data=body) 50 | assert res.status_code == 200 51 | assert res.json() == {"email": email, "message": "logged in"} 52 | return res.cookies.get('session_id') 53 | 54 | 55 | def profile_unlogged() -> None: 56 | """Tests retrieving profile information whilst logged out. 57 | """ 58 | url = "{}/profile".format(BASE_URL) 59 | res = requests.get(url) 60 | assert res.status_code == 403 61 | 62 | 63 | def profile_logged(session_id: str) -> None: 64 | """Tests retrieving profile information whilst logged in. 65 | """ 66 | url = "{}/profile".format(BASE_URL) 67 | req_cookies = { 68 | 'session_id': session_id, 69 | } 70 | res = requests.get(url, cookies=req_cookies) 71 | assert res.status_code == 200 72 | assert "email" in res.json() 73 | 74 | 75 | def log_out(session_id: str) -> None: 76 | """Tests logging out of a session. 77 | """ 78 | url = "{}/sessions".format(BASE_URL) 79 | req_cookies = { 80 | 'session_id': session_id, 81 | } 82 | res = requests.delete(url, cookies=req_cookies) 83 | assert res.status_code == 200 84 | assert res.json() == {"message": "Bienvenue"} 85 | 86 | 87 | def reset_password_token(email: str) -> str: 88 | """Tests requesting a password reset. 89 | """ 90 | url = "{}/reset_password".format(BASE_URL) 91 | body = {'email': email} 92 | res = requests.post(url, data=body) 93 | assert res.status_code == 200 94 | assert "email" in res.json() 95 | assert res.json()["email"] == email 96 | assert "reset_token" in res.json() 97 | return res.json().get('reset_token') 98 | 99 | 100 | def update_password(email: str, reset_token: str, new_password: str) -> None: 101 | """Tests updating a user's password. 102 | """ 103 | url = "{}/reset_password".format(BASE_URL) 104 | body = { 105 | 'email': email, 106 | 'reset_token': reset_token, 107 | 'new_password': new_password, 108 | } 109 | res = requests.put(url, data=body) 110 | assert res.status_code == 200 111 | assert res.json() == {"email": email, "message": "Password updated"} 112 | 113 | 114 | if __name__ == "__main__": 115 | register_user(EMAIL, PASSWD) 116 | log_in_wrong_password(EMAIL, NEW_PASSWD) 117 | profile_unlogged() 118 | session_id = log_in(EMAIL, PASSWD) 119 | profile_logged(session_id) 120 | log_out(session_id) 121 | reset_token = reset_password_token(EMAIL) 122 | update_password(EMAIL, reset_token, NEW_PASSWD) 123 | log_in(EMAIL, NEW_PASSWD) 124 | -------------------------------------------------------------------------------- /0x03-user_authentication_service/user.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """The `user` model's module. 3 | """ 4 | from sqlalchemy import Column, Integer, String 5 | from sqlalchemy.ext.declarative import declarative_base 6 | 7 | 8 | Base = declarative_base() 9 | 10 | 11 | class User(Base): 12 | """Represents a record from the `user` table. 13 | """ 14 | __tablename__ = "users" 15 | id = Column(Integer, primary_key=True) 16 | email = Column(String(250), nullable=False) 17 | hashed_password = Column(String(250), nullable=False) 18 | session_id = Column(String(250), nullable=True) 19 | reset_token = Column(String(250), nullable=True) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # alx-backend-user-data --------------------------------------------------------------------------------