ALX-Back-end - User Data
ALX-Back-end - User Data
ALX-Back-end - User Data
Weight: 1
Project over - took place from Mar 1, 2023 5:00 AM to Mar 3, 2023 5:00 AM
In a nutshell…
Manual QA review: 0.0/3 mandatory
Altogether: 91.43%
Mandatory: 91.43%
logging documentation
bcrypt package
Learning Objectives
At the end of this project, you are expected to be able to explain to anyone, without the help
of Google:
The first line of all your files should be exactly #!/usr/bin/env python3
'print(__import__("my_module").__doc__)' )
'print(__import__("my_module").MyClass.__doc__)' )
All your functions (inside and outside a class) should have a documentation ( python3 -c
'print(__import__("my_module").MyClass.my_function.__doc__)' )
A documentation is not a simple word, it’s a real sentence explaining what’s the purpose of
the module, class or method (the length of it will be verified)
Tasks
0. Regex-ing
mandatory
Arguments:
separator : a string representing by which character is separating all fields in the log
line ( message )
The function should use a regex to replace occurrences of certain field values.
filter_datum = __import__('filtered_logger').filter_datum
bob@dylan:~$
bob@dylan:~$ ./main.py
name=egg;email=eggmin@eggsample.com;password=xxx;date_of_birth=xxx;
name=bob;email=bob@dylan.com;password=xxx;date_of_birth=xxx;
bob@dylan:~$
Repo:
Directory: 0x00-personal_data
File: filtered_logger.py
1. Log formatter
mandatory
import logging
class RedactingFormatter(logging.Formatter):
""" Redacting Formatter class
"""
REDACTION = "***"
FORMAT = "[HOLBERTON] %(name)s %(levelname)s %(asctime)-15s: %(message)s"
SEPARATOR = ";"
def __init__(self):
super(RedactingFormatter, self).__init__(self.FORMAT)
Implement the format method to filter values in incoming log records using filter_datum .
Values for fields in fields should be filtered.
DO NOT extrapolate FORMAT manually. The format method should be less than 5 lines long.
import logging
import re
RedactingFormatter = __import__('filtered_logger').RedactingFormatter
message = "name=Bob;email=bob@dylan.com;ssn=000-123-0000;password=bobby2019;"
log_record = logging.LogRecord("my_logger", logging.INFO, None, None, message, None, None)
formatter = RedactingFormatter(fields=("email", "ssn", "password"))
print(formatter.format(log_record))
bob@dylan:~$
bob@dylan:~$ ./main.py
[HOLBERTON] my_logger INFO 2019-11-19 18:24:25,105: name=Bob; email=***; ssn=***; password=***;
bob@dylan:~$
Repo:
Directory: 0x00-personal_data
File: filtered_logger.py
2. Create logger
mandatory
Implement a get_logger function that takes no arguments and returns a logging.Logger object.
The logger should be named "user_data" and only log up to logging.INFO level. It should not
propagate messages to other loggers. It should have
a StreamHandler with RedactingFormatter as formatter.
Create a tuple PII_FIELDS constant at the root of the module containing the fields
from user_data.csv that are considered PII. PII_FIELDS can contain only 5 fields - choose the
right list of fields that can are considered as “important” PIIs or information that you must
hide in your logs. Use it to parameterize the formatter.
import logging
get_logger = __import__('filtered_logger').get_logger
PII_FIELDS = __import__('filtered_logger').PII_FIELDS
print(get_logger.__annotations__.get('return'))
print("PII_FIELDS: {}".format(len(PII_FIELDS)))
bob@dylan:~$
bob@dylan:~$ ./main.py
<class 'logging.Logger'>
PII_FIELDS: 5
bob@dylan:~$
Repo:
Directory: 0x00-personal_data
File: filtered_logger.py
Database credentials should NEVER be stored in code or checked into version control. One
secure option is to store them as environment variable on the application server.
In this task, you will connect to a secure holberton database to read a users table. The
database is protected by a username and password that are set as environment variables on
the server named PERSONAL_DATA_DB_USERNAME (set the default as
“root”), PERSONAL_DATA_DB_PASSWORD (set the default as an empty string)
and PERSONAL_DATA_DB_HOST (set the default as “localhost”).
mysql-connector-python )
USE my_db;
bob@dylan:~$
bob@dylan:~$ cat main.sql | mysql -uroot -p
Enter password:
bob@dylan:~$
bob@dylan:~$ echo "SELECT COUNT(*) FROM users;" | mysql -uroot -p my_db
Enter password:
2
bob@dylan:~$
bob@dylan:~$ cat main.py
#!/usr/bin/env python3
"""
Main file
"""
get_db = __import__('filtered_logger').get_db
db = get_db()
cursor = db.cursor()
cursor.execute("SELECT COUNT(*) FROM users;")
for row in cursor:
print(row[0])
cursor.close()
db.close()
bob@dylan:~$
bob@dylan:~$ PERSONAL_DATA_DB_USERNAME=root PERSONAL_DATA_DB_PASSWORD=root PERSONAL_DATA_DB_HOST=
localhost PERSONAL_DATA_DB_NAME=my_db ./main.py
2
bob@dylan:~$
Repo:
Directory: 0x00-personal_data
File: filtered_logger.py
The function will obtain a database connection using get_db and retrieve all rows in
the users table and display each row under a filtered format like this:
[HOLBERTON] user_data INFO 2019-11-19 18:37:59,596: name=***; email=***; phone=***; ssn=***; pass
word=***; ip=e848:e856:4e0b:a056:54ad:1e98:8110:ce1b; last_login=2019-11-14T06:16:24; user_agent=
Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; KTXN);
Filtered fields:
name
phone
ssn
password
Only your main function should run when the module is executed.
USE my_db;
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:26
f2:93ea","2019-11-14 06:14:24","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHT
ML, like Gecko) Chrome/74.0.3729.157 Safari/537.36");
INSERT INTO users(name, email, phone, ssn, password, ip, last_login, user_agent) VALUES ("Belen B
ailey","bcevc@yahoo.com","(539) 233-4942","203-38-5395","^3EZ~TkX","f724:c5d1:a14d:c4c5:bae2:945
7:3769:1969","2019-11-14 06:16:19","Mozilla/5.0 (Linux; U; Android 4.1.2; de-de; GT-I9100 Build/J
ZO54K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30");
Repo:
Directory: 0x00-personal_data
File: filtered_logger.py
5. Encrypting passwords
mandatory
Implement a hash_password function that expects one string argument name password and
returns a salted, hashed password, which is a byte string.
hash_password = __import__('encrypt_password').hash_password
password = "MyAmazingPassw0rd"
print(hash_password(password))
print(hash_password(password))
bob@dylan:~$
bob@dylan:~$ ./main.py
b'$2b$12$Fnjf6ew.oPZtVksngJjh1.vYCnxRjPm2yt18kw6AuprMRpmhJVxJO'
b'$2b$12$xSAw.bxfSTAlIBglPMXeL.SJnzme3Gm0E7eOEKOVV2OhqOakyUN5m'
bob@dylan:~$
Directory: 0x00-personal_data
File: encrypt_password.py
Arguments:
Use bcrypt to validate that the provided password matches the hashed password.
hash_password = __import__('encrypt_password').hash_password
is_valid = __import__('encrypt_password').is_valid
password = "MyAmazingPassw0rd"
encrypted_password = hash_password(password)
print(encrypted_password)
print(is_valid(encrypted_password, password))
bob@dylan:~$
bob@dylan:~$ ./main.py
b'$2b$12$Fnjf6ew.oPZtVksngJjh1.vYCnxRjPm2yt18kw6AuprMRpmhJVxJO'
True
bob@dylan:~$
Repo:
Directory: 0x00-personal_data
File: encrypt_password.py
Weight: 1
Project over - took place from Mar 6, 2023 5:00 AM to Mar 8, 2023 5:00 AM
In a nutshell…
Auto QA review: 169.0/169 mandatory & 27.0/27 optional
Altogether: 200.0%
Mandatory: 100.0%
Optional: 100.0%
Background Context
In this project, you will learn what the authentication process means and implement a Basic
Authentication on a simple API.
In the industry, you should not implement your own Basic authentication system and use a
module or framework that doing it for you (like in Python-Flask: Flask-HTTPAuth). Here, for the
learning purpose, we will walk through each step of this mechanism to understand it by doing.
Base64 in Python
Flask
Base64 - concept
Learning Objectives
At the end of this project, you are expected to be able to explain to anyone, without the help
of Google:
General
What authentication means
What Base64 is
Requirements
Python Scripts
All your files will be interpreted/compiled on Ubuntu 18.04 LTS using python3 (version 3.7)
The first line of all your files should be exactly #!/usr/bin/env python3
'print(__import__("my_module").__doc__)' )
'print(__import__("my_module").MyClass.__doc__)' )
All your functions (inside and outside a class) should have a documentation ( python3 -c
'print(__import__("my_module").MyClass.my_function.__doc__)' )
A documentation is not a simple word, it’s a real sentence explaining what’s the purpose of
the module, class or method (the length of it will be verified)
Tasks
0. Simple-basic-API
mandatory
Score: 100.0% (Checks completed: 100.0%)
Repo:
Directory: 0x01-Basic_authentication
What the HTTP status code for a request unauthorized? 401 of course!
Edit api/v1/app.py :
Add a new error handler for this status code, the response must be:
For testing this new error handler, add a new endpoint in api/v1/views/index.py :
This endpoint must raise a 401 error by using abort - Custom Error Pages
In a second terminal:
Repo:
Directory: 0x01-Basic_authentication
Add a new error handler for this status code, the response must be:
For testing this new error handler, add a new endpoint in api/v1/views/index.py :
This endpoint must raise a 403 error by using abort - Custom Error Pages
Repo:
Directory: 0x01-Basic_authentication
3. Auth class
mandatory
Score: 100.0% (Checks completed: 100.0%)
This class is the template for all authentication system you will implement.
a = Auth()
print(a.require_auth("/api/v1/status/", ["/api/v1/status/"]))
print(a.authorization_header())
print(a.current_user())
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_0.py
False
None
None
bob@dylan:~$
Repo:
Directory: 0x01-Basic_authentication
a = Auth()
print(a.require_auth(None, None))
print(a.require_auth(None, []))
print(a.require_auth("/api/v1/status/", []))
print(a.require_auth("/api/v1/status/", ["/api/v1/status/"]))
print(a.require_auth("/api/v1/status", ["/api/v1/status/"]))
print(a.require_auth("/api/v1/users", ["/api/v1/status/"]))
print(a.require_auth("/api/v1/users", ["/api/v1/status/", "/api/v1/stats"]))
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_1.py
True
True
True
False
False
True
True
bob@dylan:~$
Repo:
Directory: 0x01-Basic_authentication
File: api/v1/auth/auth.py
5. Request validation!
mandatory
if auth :
Now the biggest piece is the filtering of each request. For that you will use the Flask
method before_request
if auth.current_user(request) returns None , raise the error 403 - you must use abort
In a second terminal:
Repo:
Directory: 0x01-Basic_authentication
6. Basic auth
mandatory
Update api/v1/app.py for using BasicAuth class instead of Auth depending of the value of the
environment variable AUTH_TYPE , If AUTH_TYPE is equal to basic_auth :
In a second terminal:
Repo:
str: in the class BasicAuth that returns the Base64 part of the Authorization header for a Basic
Authentication:
Return None if authorization_header doesn’t start by Basic (with a space at the end)
a = BasicAuth()
print(a.extract_base64_authorization_header(None))
print(a.extract_base64_authorization_header(89))
print(a.extract_base64_authorization_header("Holberton School"))
print(a.extract_base64_authorization_header("Basic Holberton"))
print(a.extract_base64_authorization_header("Basic SG9sYmVydG9u"))
print(a.extract_base64_authorization_header("Basic SG9sYmVydG9uIFNjaG9vbA=="))
print(a.extract_base64_authorization_header("Basic1234"))
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_2.py
None
None
None
Holberton
SG9sYmVydG9u
SG9sYmVydG9uIFNjaG9vbA==
None
bob@dylan:~$
Repo:
Directory: 0x01-Basic_authentication
str: in the class BasicAuth that returns the decoded value of a Base64
string base64_authorization_header :
Return None if base64_authorization_header is not a valid Base64 - you can use try/except
Otherwise, return the decoded value as UTF8 string - you can use decode('utf-8')
a = BasicAuth()
print(a.decode_base64_authorization_header(None))
print(a.decode_base64_authorization_header(89))
print(a.decode_base64_authorization_header("Holberton School"))
print(a.decode_base64_authorization_header("SG9sYmVydG9u"))
print(a.decode_base64_authorization_header("SG9sYmVydG9uIFNjaG9vbA=="))
print(a.decode_base64_authorization_header(a.extract_base64_authorization_header("Basic SG9sYmVyd
G9uIFNjaG9vbA==")))
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_3.py
None
None
None
Holberton
Holberton School
Holberton School
bob@dylan:~$
Repo:
Directory: 0x01-Basic_authentication
File: api/v1/auth/basic_auth.py
decoded value.
Otherwise, return the user email and the user password - these 2 values must be
separated by a :
a = BasicAuth()
print(a.extract_user_credentials(None))
print(a.extract_user_credentials(89))
print(a.extract_user_credentials("Holberton School"))
print(a.extract_user_credentials("Holberton:School"))
print(a.extract_user_credentials("bob@gmail.com:toto1234"))
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_4.py
(None, None)
(None, None)
(None, None)
('Holberton', 'School')
('bob@gmail.com', 'toto1234')
bob@dylan:~$
Repo:
Directory: 0x01-Basic_authentication
File: api/v1/auth/basic_auth.py
TypeVar('User'): in the class BasicAuth that returns the User instance based on his email and
password.
Return None if your database (file) doesn’t contain any User instance with email equal
to user_email - you should use the class method search of the User to lookup the list of
users based on their email. Don’t forget to test all cases: “what if there is no user in DB?”,
etc.
Return None if user_pwd is not the password of the User instance found - you must use the
method is_valid_password of User
a = BasicAuth()
u = a.user_object_from_credentials(None, None)
print(u.display_name() if u is not None else "None")
u = a.user_object_from_credentials(89, 98)
print(u.display_name() if u is not None else "None")
u = a.user_object_from_credentials("email@notfound.com", "pwd")
print(u.display_name() if u is not None else "None")
u = a.user_object_from_credentials(user_email, "pwd")
print(u.display_name() if u is not None else "None")
u = a.user_object_from_credentials(user_email, user_clear_pwd)
print(u.display_name() if u is not None else "None")
Repo:
Directory: 0x01-Basic_authentication
File: api/v1/auth/basic_auth.py
Now, you have all pieces for having a complete Basic authentication.
With this update, now your API is fully protected by a Basic Authentication. Enjoy!
In the first terminal:
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_6.py
New user: 9375973a-68c7-46aa-b135-29f79e837495 / bob@hbtn.io
Basic Base64: Ym9iQGhidG4uaW86SDBsYmVydG9uU2Nob29sOTgh
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=basic_auth python3 -m api.v1.app
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
In a second terminal:
Repo:
Directory: 0x01-Basic_authentication
File: api/v1/auth/basic_auth.py
user = User()
user.email = user_email
user.password = user_clear_pwd
print("New user: {}".format(user.id))
user.save()
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 ./main_100.py
New user: 5891469b-d2d5-4d33-b05d-02617d665368
Basic Base64: Ym9iMTAwQGhidG4uaW86SDBsYmVydG9uOlNjaG9vbDo5OCE=
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=basic_auth python3 -m api.v1.app
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
In a second terminal:
Repo:
Directory: 0x01-Basic_authentication
File: api/v1/auth/basic_auth.py
Repo:
Directory: 0x01-Basic_authentication
File: api/v1/auth/auth.py
Weight: 1
Project over - took place from Mar 8, 2023 5:00 AM to Mar 10, 2023 5:00 AM
In a nutshell…
Auto QA review: 135.0/135 mandatory & 46.0/46 optional
Altogether: 200.0%
Mandatory: 100.0%
Optional: 100.0%
Background Context
In this project, you will implement a Session Authentication. You are not allowed to install any
other module.
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). Here, for the
learning purpose, we will walk through each step of this mechanism to understand it by doing.
Resources
Read or watch:
HTTP Cookie
Flask
Flask Cookie
Learning Objectives
At the end of this project, you are expected to be able to explain to anyone, without the help
of Google:
Requirements
Python Scripts
All your files will be interpreted/compiled on Ubuntu 18.04 LTS using python3 (version 3.7)
The first line of all your files should be exactly #!/usr/bin/env python3
'print(__import__("my_module").__doc__)' )
'print(__import__("my_module").MyClass.__doc__)' )
All your functions (inside and outside a class) should have a documentation ( python3 -c
'print(__import__("my_module").MyClass.my_function.__doc__)' )
A documentation is not a simple word, it’s a real sentence explaining what’s the purpose of
the module, class or method (the length of it will be verified)
Tasks
0. Et moi et moi et moi!
mandatory
GET /api/v1/users
POST /api/v1/users
GET /api/v1/users/<user_id>
PUT /api/v1/users/<user_id>
DELETE /api/v1/users/<user_id>
Now, you will add a new endpoint: GET /users/me to retrieve the authenticated User object.
Copy folders models and api from the previous project 0x06. Basic authentication
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.
user = User()
user.email = user_email
user.password = user_clear_pwd
print("New user: {}".format(user.id))
user.save()
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=basic_auth ./main_0.py
In a second terminal:
Repo:
Directory: 0x02-Session_authentication
1. Empty session
mandatory
Update api/v1/app.py for using SessionAuth instance for the variable authdepending of the
value of the environment variable AUTH_TYPE , If AUTH_TYPE is equal to session_auth :
In a second terminal:
Repo:
Directory: 0x02-Session_authentication
2. Create a session
Create an instance method def create_session(self, user_id: str = None) -> str: that
creates a Session ID for a user_id :
Otherwise:
Use this Session ID as key of the dictionary user_id_by_session_id - the value for
this key must be user_id
The same user_id can have multiple Session ID - indeed, the user_id is the value in
the dictionary user_id_by_session_id
Now you an “in-memory” Session ID storing. You will be able to retrieve an User id based on a
Session ID.
sa = SessionAuth()
user_id = None
session = sa.create_session(user_id)
print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id))
user_id = 89
session = sa.create_session(user_id)
print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id))
user_id = "abcde"
session = sa.create_session(user_id)
print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id))
user_id = "fghij"
session = sa.create_session(user_id)
print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id))
user_id = "abcde"
session = sa.create_session(user_id)
print("{} => {}: {}".format(user_id, session, sa.user_id_by_session_id))
Repo:
Directory: 0x02-Session_authentication
File: api/v1/auth/session_auth.py
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:
Return the value (the User ID) for the key session_id in the
dictionary user_id_by_session_id .
You must use .get() built-in for accessing in a dictionary a value based on key
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.
sa = SessionAuth()
user_id_1 = "abcde"
session_1 = sa.create_session(user_id_1)
print("{} => {}: {}".format(user_id_1, session_1, sa.user_id_by_session_id))
user_id_2 = "fghij"
print("---")
tmp_session_id = None
tmp_user_id = sa.user_id_for_session_id(tmp_session_id)
print("{} => {}".format(tmp_session_id, tmp_user_id))
tmp_session_id = 89
tmp_user_id = sa.user_id_for_session_id(tmp_session_id)
print("{} => {}".format(tmp_session_id, tmp_user_id))
tmp_session_id = "doesntexist"
tmp_user_id = sa.user_id_for_session_id(tmp_session_id)
print("{} => {}".format(tmp_session_id, tmp_user_id))
print("---")
tmp_session_id = session_1
tmp_user_id = sa.user_id_for_session_id(tmp_session_id)
print("{} => {}".format(tmp_session_id, tmp_user_id))
tmp_session_id = session_2
tmp_user_id = sa.user_id_for_session_id(tmp_session_id)
print("{} => {}".format(tmp_session_id, tmp_user_id))
print("---")
session_1_bis = sa.create_session(user_id_1)
print("{} => {}: {}".format(user_id_1, session_1_bis, sa.user_id_by_session_id))
tmp_user_id = sa.user_id_for_session_id(session_1_bis)
print("{} => {}".format(session_1_bis, tmp_user_id))
tmp_user_id = sa.user_id_for_session_id(session_1)
print("{} => {}".format(session_1, tmp_user_id))
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_auth ./main_2.py
abcde => 8647f981-f503-4638-af23-7bb4a9e4b53f: {'8647f981-f503-4638-af23-7bb4a9e4b53f': 'abcde'}
fghij => a159ee3f-214e-4e91-9546-ca3ce873e975: {'a159ee3f-214e-4e91-9546-ca3ce873e975': 'fghij',
'8647f981-f503-4638-af23-7bb4a9e4b53f': 'abcde'}
---
None => None
89 => None
doesntexist => None
---
8647f981-f503-4638-af23-7bb4a9e4b53f => abcde
a159ee3f-214e-4e91-9546-ca3ce873e975 => fghij
---
abcde => 5d2930ba-f6d6-4a23-83d2-4f0abc8b8eee: {'a159ee3f-214e-4e91-9546-ca3ce873e975': 'fghij',
'8647f981-f503-4638-af23-7bb4a9e4b53f': 'abcde', '5d2930ba-f6d6-4a23-83d2-4f0abc8b8eee': 'abcd
e'}
5d2930ba-f6d6-4a23-83d2-4f0abc8b8eee => abcde
8647f981-f503-4638-af23-7bb4a9e4b53f => abcde
bob@dylan:~$
Repo:
Directory: 0x02-Session_authentication
4. Session cookie
mandatory
Score: 100.0% (Checks completed: 100.0%)
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
You must use .get() built-in for accessing the cookie in the request cookies dictionary
You must use the environment variable SESSION_NAME to define the name of the cookie used
for the Session ID
auth = Auth()
app = Flask(__name__)
if __name__ == "__main__":
app.run(host="0.0.0.0", port="5000")
In a second terminal:
Repo:
Directory: 0x02-Session_authentication
File: api/v1/auth/auth.py
5. Before request
mandatory
Score: 100.0% (Checks completed: 100.0%)
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
In a second terminal:
Repo:
Directory: 0x02-Session_authentication
File: api/v1/app.py
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.
Now, you will be able to get a User based on his session ID.
user = User()
user.email = user_email
user.password = user_clear_pwd
if __name__ == "__main__":
app.run(host="0.0.0.0", port="5000")
bob@dylan:~$
bob@dylan:~$ API_HOST=0.0.0.0 API_PORT=5000 AUTH_TYPE=session_auth SESSION_NAME=_my_session_id ./
main_4.py
User with ID: cf3ddee1-ff24-49e4-a40b-2540333fe992 has a Session ID: 9d1648aa-da79-4692-8236-5f9d
7f9e9485
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
....
In a second terminal:
Repo:
Directory: 0x02-Session_authentication
File: api/v1/auth/session_auth.py
/api/v1/auth_session/login ):
If email is missing or empty, return the JSON { "error": "email missing" } with the status
code 400
If password is missing or empty, return the JSON { "error": "password missing" } with the
status code 400
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 )
If no User found, return the JSON { "error": "no user found for this email" } with the
status code 404
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
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)
Return the dictionary representation of the User - you must use to_json() method
from User
You must set the cookie to the response - you must use the value of the
environment variable SESSION_NAME as cookie name - tip
In the file api/v1/views/__init__.py , you must add this new view at the end of the file.
In a second terminal:
Now you have an authentication based on a Session ID stored in cookie, perfect for a website
(browsers love cookies).
Repo:
Directory: 0x02-Session_authentication
8. Logout
mandatory
Score: 100.0% (Checks completed: 100.0%)
If the request doesn’t contain the Session ID cookie, return False - you must
use self.session_cookie(request)
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(...)
/api/v1/auth_session/logout :
Slash tolerant
Otherwise, return an empty JSON dictionary with the status code 200
In a second terminal:
Now, after getting a Session ID, you can request all protected API routes by using this Session
ID, no need anymore to send User email and password every time.
Repo:
Directory: 0x02-Session_authentication
9. Expiration?
#advanced
Basic authentication
Session authentication
Create a Session ID by calling super() - super() will call the create_session() method
of SessionAuth
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”):
The key created_at must be set to the current datetime - you must
use datetime.now()
Return the user_id key from the session dictionary if self.session_duration is equal or
under 0
Return None if the created_at + session_duration seconds are before the current
datetime. datetime - timedelta
In a second terminal:
Repo:
Directory: 0x02-Session_authentication
Implement the def __init__(self, *args: list, **kwargs: dict): like in User but for these 2
attributes:
user_id : string
session_id : string
Overload def create_session(self, user_id=None): that creates and stores new instance
of UserSession and returns the Session ID
In a second terminal:
Repo:
Directory: 0x02-Session_authentication
Weight: 1
Project over - took place from Mar 13, 2023 5:00 AM to Mar 17, 2023 5:00 AM
In a nutshell…
Auto QA review: 89.0/89 mandatory & 4.0/4 optional
Altogether: 200.0%
Mandatory: 100.0%
Optional: 100.0%
In the industry, you should not implement your own authentication system and use a module or
framework that doing it for you (like in Python-Flask: Flask-User). Here, for the learning
purpose, we will walk through each step of this mechanism to understand it by doing.
Flask documentation
Requests module
Learning Objectives
At the end of this project, you are expected to be able to explain to anyone, without the help
of Google:
Requirements
Allowed editors: vi , vim , emacs
All your files will be interpreted/compiled on Ubuntu 18.04 LTS using python3 (version 3.7)
The first line of all your files should be exactly #!/usr/bin/env python3
'print(__import__("my_module").__doc__)' )
'print(__import__("my_module").MyClass.__doc__)' )
All your functions (inside and outside a class) should have a documentation ( python3 -c
'print(__import__("my_module").MyClass.my_function.__doc__)' )
The flask app should only interact with Auth and never with DB directly.
Only public methods of Auth and DB should be used outside these classes
Setup
You will need to install bcrypt
Tasks
0. User model
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task you will create a SQLAlchemy model named User for a database table
named users (by using the mapping declaration of SQLAlchemy).
The model will have the following attributes:
print(User.__tablename__)
Repo:
Directory: 0x03-user_authentication_service
File: user.py
1. create user
mandatory
"""DB module
"""
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.session import Session
class DB:
"""DB class
"""
@property
def _session(self) -> Session:
"""Memoized session object
"""
if self.__session is None:
DBSession = sessionmaker(bind=self._engine)
self.__session = DBSession()
return self.__session
Note that DB._session is a private property and hence should NEVER be used from outside
the DB class.
from db import DB
from user import User
my_db = DB()
Repo:
Directory: 0x03-user_authentication_service
File: db.py
2. Find user
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task you will 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.
Make sure that SQLAlchemy’s NoResultFound and InvalidRequestError are raised when no
results are found, or when wrong query arguments are passed, respectively.
Warning:
my_db = DB()
find_user = my_db.find_user_by(email="test@test.com")
print(find_user.id)
try:
find_user = my_db.find_user_by(email="test2@test.com")
print(find_user.id)
except NoResultFound:
print("Not found")
try:
find_user = my_db.find_user_by(no_email="test@test.com")
print(find_user.id)
except InvalidRequestError:
print("Invalid")
Repo:
Directory: 0x03-user_authentication_service
File: db.py
3. update user
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement the DB.update_user method that takes as argument a
required user_id integer and arbitrary keyword arguments, and returns None .
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.
my_db = DB()
email = 'test@test.com'
hashed_password = "hashedPwd"
try:
my_db.update_user(user.id, hashed_password='NewPwd')
print("Password updated")
except ValueError:
print("Error")
Repo:
Directory: 0x03-user_authentication_service
File: db.py
4. Hash password
mandatory
print(_hash_password("Hello Holberton"))
Repo:
Directory: 0x03-user_authentication_service
File: auth.py
5. Register user
mandatory
from db import DB
class Auth:
"""Auth class to interact with the authentication database.
"""
def __init__(self):
self._db = DB()
Note that Auth._db is a private property and should NEVER be used from outside the class.
Auth.register_user should take mandatory email and password string arguments and return
a User object.
If a user already exist with the passed email, raise a ValueError with the message User <user's
If not, hash the password with _hash_password , save the user to the database
using self._db and return the User object.
email = 'me@me.com'
password = 'mySecuredPwd'
try:
user = auth.register_user(email, password)
print("successfully created a new user!")
except ValueError as err:
print("could not create a new user: {}".format(err))
try:
user = auth.register_user(email, password)
print("successfully created a new user!")
except ValueError as err:
print("could not create a new user: {}".format(err))
Repo:
Directory: 0x03-user_authentication_service
File: auth.py
Create a Flask app that has a single GET route ( "/" ) and use flask.jsonify to return a JSON
payload of the form:
{"message": "Bienvenue"}
if __name__ == "__main__":
app.run(host="0.0.0.0", port="5000")
Repo:
Directory: 0x03-user_authentication_service
File: app.py
7. Register user
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement the end-point to register a user. Define a users function that
implements the POST /users route.
Import the Auth object and instantiate it at the root of the module as such:
AUTH = Auth()
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:
If the user is already registered, catch the exception and return a JSON payload of the form
Terminal 2:
bob@dylan:~$
bob@dylan:~$ curl -XPOST localhost:5000/users -d 'email=bob@me.com' -d 'password=mySuperPwd' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)
> POST /users HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Length: 40
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 40 out of 40 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 400 BAD REQUEST
< Content-Type: application/json
< Content-Length: 39
< Server: Werkzeug/1.0.1 Python/3.7.3
< Date: Wed, 19 Aug 2020 00:03:33 GMT
<
{"message":"email already registered"}
bob@dylan:~$
Repo:
Directory: 0x03-user_authentication_service
File: app.py
8. Credentials validation
mandatory
Score: 100.0% (Checks completed: 100.0%)
email = 'bob@bob.com'
password = 'MyPwdOfBob'
auth = Auth()
auth.register_user(email, password)
print(auth.valid_login(email, password))
print(auth.valid_login(email, "WrongPwd"))
print(auth.valid_login("unknown@email", password))
Repo:
Directory: 0x03-user_authentication_service
File: auth.py
9. Generate UUIDs
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task you will implement a _generate_uuid function in the auth module. The function
should return a string representation of a new UUID. Use the uuid module.
Note that the method is private to the auth module and should NOT be used outside of it.
Repo:
Directory: 0x03-user_authentication_service
File: auth.py
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.
email = 'bob@bob.com'
password = 'MyPwdOfBob'
auth = Auth()
auth.register_user(email, password)
print(auth.create_session(email))
print(auth.create_session("unknown@email.com"))
Repo:
Directory: 0x03-user_authentication_service
File: auth.py
11. Log in
mandatory
Score: 100.0% (Checks completed: 100.0%)
In this task, you will implement a login function to respond to the POST /sessions route.
The request is expected to contain form data with "email" and a "password" fields.
If the login information is incorrect, use flask.abort to respond with a 401 HTTP status.
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
Directory: 0x03-user_authentication_service
File: app.py
If the session ID is None or no user is found, return None . Otherwise return the corresponding
user.
Remember to only use public methods of self._db .
Repo:
Directory: 0x03-user_authentication_service
File: auth.py
In this task, you will implement Auth.destroy_session . The method takes a single user_id integer
argument and returns None .
Repo:
Directory: 0x03-user_authentication_service
File: auth.py
In this task, you will implement a logout function to respond to the DELETE /sessions route.
The request is expected to contain the session ID as a cookie with key "session_id" .
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.
Repo:
Directory: 0x03-user_authentication_service
File: app.py
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:
If the session ID is invalid or the user does not exist, respond with a 403 HTTP status.
bob@dylan:~$
Repo:
Directory: 0x03-user_authentication_service
File: app.py
File: auth.py
/reset_password route.
The request is expected to contain form data with the "email" field.
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:
Repo:
Directory: 0x03-user_authentication_service
File: app.py
Repo:
Directory: 0x03-user_authentication_service
File: auth.py
Update the password. If the token is invalid, catch the exception and respond with a 403 HTTP
code.
If the token is valid, respond with a 200 HTTP code and the following JSON payload:
Repo:
Directory: 0x03-user_authentication_service
File: app.py
Create a new module called main.py . Create one function for each of the following 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 task.
EMAIL = "guillaume@holberton.io"
PASSWD = "b4l0u"
NEW_PASSWD = "t4rt1fl3tt3"
if __name__ == "__main__":
register_user(EMAIL, PASSWD)
log_in_wrong_password(EMAIL, NEW_PASSWD)
profile_unlogged()
session_id = log_in(EMAIL, PASSWD)
profile_logged(session_id)
log_out(session_id)
reset_token = reset_password_token(EMAIL)
update_password(EMAIL, reset_token, NEW_PASSWD)
log_in(EMAIL, NEW_PASSWD)
Directory: 0x03-user_authentication_service
File: main.py