Оглавление

Что такое фреймворк?

Из чего состоит Flask

Flask включает в себя компоненты для работы:

Flask не включает в себя, но может быть расширен для работы:

Маршрут или роут

Эндпоинт

Представление или вьюшка

Минимальное приложение Flask

Что происходит в минимальном приложении

Маршутизация во Flask

Правила маршрутизации во Flask

Данные из сегментов URL

Преобразование типов

Что такое фреймворк?

Фреймворк — набор готовых компонентов, из которых мы, как из деталек конструктора лего, собираем свое приложение.

Flask называют микрофреймворком, в отличие от других фреймворков, в которых есть компоненты на любой случай, у Flask по умолчанию всё очень просто. Разницу между фреймворком и микрофреймворком можно представить как разницу между квартирой с отделкой и квартирой без отделки.

Из чего состоит Flask

Как вы знаете из курса, чтобы приложение заработало, кто-то должен слушать порты и получать HTTP-запросы от клиентов. За это отвечает веб-сервер, и свой сервер у Flask есть. От веб-сервера запрос передается на WSGI-сервер — промежуточный сервер, который уже написан на Python и соединяет веб-сервер и приложение. Такой сервер тоже есть в пакете Flask. Наконец дальше запрос передается Flask-приложению — это то приложение, которое мы будем писать c использованием объектов (классов и функций) Flask.

Flask включает в себя компоненты для работы:


                    
с маршрутами и методами,
статическими файлами,
загрузкой файлов
шаблонами,
cookies и сессиями,
ошибками,
JSON-данными,
тестами,
промежуточным слоем WSGI.
                

Flask не включает в себя, но может быть расширен для работы:

Далее, чтобы обрабатывать запросы к разным адресам, нужно привязать к разным адресам и методам разные функции. Например, функция page_index() будет обрабатывать запросы к адресу /index, функция page_catalog() будет обрабатывать запросы к адресу /catalog и так далее! Такие адреса обычно называются маршрутами. Маршрут с указанием метода называется эндпоинтом, а сам процесс сопоставления адреса и функции (или классов) — маршрутизацией.


                    
с базой данных,
инструментами обработки форм,
инструментами контроля доступа,
инструментами кеширования,
админкой,
инструментами отладки,
очередями.
                

Маршрут или роут

это URL, к которому можно обратиться разными HTTP-методами. Маршрут может иметь несколько эндпоинтов.

Эндпоинт

это обращение к маршруту отдельным HTTP-методом. Эндпоинт выполняет конкретную задачу, принимает параметры и возвращает данные. Функция или класс, которые непосредственно обрабатывают запрос, называются view или «представлением». Представление работает с параметрами запроса и возвращает нужный результат.

Представление или вьюшка

объект Python (функция или класс), который получает данные HTTP-запроса и возвращает данные для HTTP-ответа.

Для того чтобы запустить код на Python как сайт (или приложение), нужен WSGI (Web Server Gateway Interface). Flask прячет от нас всю сложность работы с WSGI, так что у нас получается такой интерфейс для смертных, чтобы мы могли работать с созданием сайтов без какой-то подкапотной сложности. Для этого Flask использует пакет Werkzeug (веркцойг), который берет на себя всю работу со сложной обработкой веб-сервера.

Минимальное приложение Flask


                    
# Сперва импортируем Flask

from flask import Flask

# Затем создадим экземпляр этого Flask, назовем его app - 
# это будет наше приложение

app = Flask(__name__)

# Что такое __name__ ? 
# При запуске сценария значение переменной __name__ равно __main__
# Эта переменная помогает Flask разбрираться, где он находится 
# и без нее он просто не заработает

# Теперь создадим функцию, которая будет что-то делать
# Например, page_index — это функция, которая будет возвращать 'Hello World!'

def page_index():
   return "Hello World!"

# Теперь используем метод у приложения, который зарегистрирует маршрут
# Например, для главной страницы будет вызвана функция page_index

app.add_url_rule('/',view_func=page_index)

#Теперь стартуем сервер, чтобы обрабатывать запросы от пользователей

app.run()
                

Что происходит в минимальном приложении

Flask запустился (программа работает в бесконечном цикле) и слушает порты. Получив обращение, Flask смотрит URL запроса и, если для похожего адреса зарегистрирована функция, вызывает функцию, получает ее значение и возвращает клиенту. Функция обрабатывает запрос и возвращает ответ. Клиент получает ответ и показывает браузеру.

На самом деле, код выше можно переписать проще с помощью декораторов (что это такое мы расскажем уже в следующем уроке). Код ниже читается так: при обращении на маршрут / запрос обрабатывается функцией page_index.


                    
from flask import Flask

app = Flask(__name__)

@app.route("/")
def page_index():
    return "Главная страничка"

app.run()
                

Маршутизация во Flask

Чтобы создать страничку, просто добавьте маршрут в декоратор — и готово. Начнем мы с главной страницы или корня сайта. Все адреса называются со слеша / (косой черты), поэтому главная страница сайта — /


                    from flask import Flask

app = Flask(__name__)

@app.route("/")
def page_index():
    return "Главная страничка"

app.run()
                

app.run() – команда, которая запускает сервер. Если мы используем ее, нам не нужно запускать Flask из консоли, достаточно запустить просто .py файл! Запустите ваше приложение, затем перейдите по ссылке, которая появилась в консоли! По умолчанию Flask будет запущен на хосте localhost или 127.0.0.1 и порту 5000, но мы можем переопределить хост и порт так:


                    
app.run(host='0.0.0.0', port=8000)
                

Правила маршрутизации во Flask

Маршрут всегда начинается с /. Если не ставить в конце /, будут обрабатываться только адреса без /. Все названия функций должны быть уникальными. Функция должна возвращать строку.

Данные из сегментов URL

В начале занятия мы говорили, что пользователи отправляют данные, а приложение их обрабатывает. Как вытащить из URL что-то полезное и обработать в Python? Один из вариантов: передача данных прямо в адресной строке. Наверняка вы встречали адреса типа


                                                      
профиль/111
и рядом с ним
профиль/222
                

В этом примере это не разные маршруты, просто мы передаем ссылку на тот профиль, который хотим получить. Значит, нашей программе каким-то образом нужно взять и получить вот эти 111 и 222 (ведь мы не хотим на каждого пользователя, который у нас есть в системе, создавать отдельную вьюшку). Чтобы это сделать, нам нужно из адресной строки получить переменную, превратить ее в переменную, а потом обработать и вернуть профиль пользователя.

Для этого в route передаем в угловых скобках название переменной, в которую запишется значение этой части URL.

Такая запись означает, что обрабатываются все адреса, которые начинаются с /profile, а дальше следует еще один сегмент адреса и именно этот сегмент отправится в переменную uid. Кстати, в примере мы используем переменную uid, но может быть и любая другая переменная. Итак, чтобы у каждого пользователя была своя страница, нам нужно получать идентификатор пользователя из URL. Чтобы у каждого товара была своя страница, нам нужно получать идентификатор товара из URL. Например:


                    
/users/42
/catalog/items/420
                

Преобразование типов

Поскольку переменные берутся из адресной строки, они передаются в виде строки. Это легко проверить. Напишем такой код:


                    
@app.route('/users/<uid>')
def profile(uid):
   print(uid)
   print(type(uid))

   return ""
                

Переменная действительно имеет строковый тип, но у Flask есть встроенный инструмент, чтобы «на лету» превратить ее в число:


                                    
Затем откроем страницу:
/users/7
Такой запуск выведет в консоли:
7
str
                

                    
@app.route('/users/<int:uid>')
def profile(uid):
   print(uid)
   print(type(uid))

   return ""
                

                                    
Аналогично можно использовать:
<int:value> Для преобразования в челые числа
<float:value> Для преобразования в числа с точкой
<bool:value> Для преобразования в булевы значения
                

Оглавление

Глоссарий

Marshmallow

Пошаговый алгоритм использования marshmallow

Как сериализовать модель в JSON?

Как сериализовать модель в dict?

Как десериализовать JSON в модель ?

Правила REST

HTTP-методы и коды ответов на примере /books GET /books — получение списка сущностей

POST /books — создание новой записи

GET /books/{id} — получение конкретной сущности по ID

GET /books/{id} — получение конкретной сущности по ID

PUT /books{id} — полное обновление (всех полей) сущности по ID3

DELETE /books/{id} — удаление сущности по ID

PATCH /books/{id} — частичное обновление (не всех полей) сущности по ID

Сопоставление поведения для элемента и коллекций

HTTP Status Codes

Глоссарий

Сериализация и десериализация данных — это преобразование между необработанной структурой данных и экземплярами классов для их хранения и передачи.Например, преобразование объектов Python в JSON-представление. Marshmallow — это библиотека, которая не зависит от ORM/ODM/фреймворка, для конвертации сложных структур данных в JSON и обратно. Схема — это описание полей сложной структуры.


                            
REST — это не спецификация, не стандарт.

REST — это архитектурный стиль, парадигма, подход.

REST API — это способ организации API.

RESTful Service — это сервис, у которого API организован при помощи REST.
                        

Marshmallow

Типы полей в схеме: https://marshmallow.readthedocs.io/en/stable/_modules/marshmallow/fields.html


                            
- Str — строка.
- Int — число.
- Email — email.
- DateTime — дата-время.
- Bool — булево.
- Float — флоат, число с плавающей запятой.
- Decimal — десимл, число с фиксированной точностью, то есть определенным количеством знаков после запятой.
                        

Пошаговый алгоритм использования marshmallow


                            
#  Импорт
from marshmallow import Schema, fields

# Это модель, в ней мы ничего не меняем
class Book(db.Model):
    __tablename__ = 'books'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    year = db.Column(db.Integer)
    author = db.Column(db.String)

#  Описать сложную модель в виде класса схемы
class BookSchema(Schema):
    id = fields.Int(dump_only=True)
    name = fields.Str()
    year = fields.Int()
    author = fields.Str()

book = Book(id=1, name="Kolobok", author="people")

# Создать экземпляр схемы
book_schema = BookSchema()

# Выполнить метод схемы
json_string = book_schema.dumps(book)
                        

Как сериализовать модель в JSON?


                            
book = Book(id=1, name="Kolobok", author="people")

# Создать экземпляр схемы
book_schema = BookSchema()

# Выполнить метод схемы
print(book_schema.dumps(book))
                        

Как сериализовать модель в dict?


                            
book = Book(id=1, name="Kolobok", author="people")
book_schema = BookSchema()
print(book_schema.dump(book))
                        

Как десериализовать JSON в модель ?


                            
book_json_str = '{"name": "Kolobok", "author": "people", "year": 1567}'
book_schema = BookSchema()
# Получили dict
book_dict = book_schema.loads(book_json_str)
# Превращаем dict в модель
book = Book(**book_dict)
                        

Правила REST

1. Используйте множественное число в ресурсах. 2. Не используйте глаголы. 3. Используйте только строчные буквы(lowercase). 4. Старайтесь делать короткие URL.

HTTP-методы и коды ответов на примере /books GET /books — получение списка сущностей

*Получает:* ничего *Возвращает:* в body JSON массив запрашиваемых сущностей *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки

POST /books — создание новой записи

*Получает:* в body: JSON с полями сущности **Заголовок:** Content-Type: application/json *Возвращает:* 201 код пустое body *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки

GET /books/{id} — получение конкретной сущности по ID

*Получает:* идентификатор сущности в query path (URL) *Возвращает:* в body JSON с полями искомой сущности *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки

GET /books/{id} — получение конкретной сущности по ID

*Получает:* идентификатор сущности в query path (URL) *Возвращает:* в body JSON с полями искомой сущности *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки

PUT /books{id} — полное обновление (всех полей) сущности по ID3

*Получает:* идентификатор сущности в query path (URL) в body JSON с всеми полями сущности *Возвращает:* 204 код в body ничего или все поля сущности *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки

DELETE /books/{id} — удаление сущности по ID

*Получает:* идентификатор сущности в query path (URL) *Возвращает:* 204 код в body ничего *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки

PATCH /books/{id} — частичное обновление (не всех полей) сущности по ID

*Получает:* идентификатор сущности в query path (URL) в body JSON с полями сущности, которые надо обновить *Возвращает:* 204 код в body ничего или все поля сущности *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки

Сопоставление поведения для элемента и коллекций

https://www.notion.so/69d0ea43d35745c19f7fb1c37668872c?v=fcdb9cbfdb32436f940cac6e56b78757

HTTP Status Codes


                            
200: OK

201: Created

204: No Content

300: Multiple Choices

301: Moved Permanently

400: Bad Request

401: Unauthorized

403: Forbidden

404: Not Found

405: Method Not Allowed

500: Internal Server Error

503: Service Unavailable
                        

Оглавление

Глоссарий

Как сконфигурировать Flask-RESTX

Как сконфигурировать неймспейс в Flask-RESTX

Как выглядит Class-Based View со всеми методами и сериализацией?

Глоссарий

**Class Based View** — это способ оформления хендлеров (@), views (представления, вьюх) и endpoints в виде класса с методами get/post/put/patch/delete. **Flask-RESTX** — это фреймворк для упрощения создания RESTful-сервиса с использованием Flask. Он дает возможность создавать Class-Based View и агрегировать эндпоинты в неймспейсы.

Как сконфигурировать Flask-RESTX


                        
from flask import Flask
from flask_restx import Api, Resource

app = Flask(__name__)
api = Api(app)
                    

Как сконфигурировать неймспейс в Flask-RESTX


                        
from flask import Flask
from flask_restx import Api, Resource

app = Flask(__name__)
api = Api(app)
notes_ns = api.namespace('notes')

# регистрируем вот так класс, и тогда все методы 
# этого класса будут доступны по пути /notes
@notes_ns.route('/')
class NotesView(Resource):
	...

# регистрируем вот так класс, и тогда все методы 
# этого класса будут доступны по пути /notes/<id>
@notes_ns.route('/<int:nid>')
class NoteView(Resource):
	...

# --------------------------------------------------------------------------------
# а если создать неймспейс, вот так
all_ns = api.namespace('')

# а классы регистрировать вот так

@all_ns.route('/notes')
class NotesView(Resource):
	...

@all_ns.route('/notes/<int:nid>')
class NoteView(Resource):
	...

# то ничего не изменится, но стало менее удобно
                    

Как выглядит Class-Based View со всеми методами и сериализацией?


                        
from flask import Flask
from flask_restx import Api, Resource

app = Flask(__name__)
api = Api(app)
book_ns = api.namespace('books')

# здесь регистрируем класс (CBV) (ресурс) по определенному пути (эндпоинту)
@book_ns.route('/')
# наследуем класс от класса Resource
class BooksView(Resource):
# пишем все нужные методы 

# получение списка сущностей, мы отдаем список всех сущностей из БД
    def get(self):
        all_books = Book.query.all()
        return books_schema.dump(all_books), 200
# создание сущности, здесь мы получаем данные из запроса и создаем новую сущность в БД
    def post(self):
        req_json = request.json
        new_user = Book(**req_json)
        with db.session.begin():
            db.session.add(new_user)
        return "", 201


@book_ns.route('/<int:uid>')
class BookView(Resource):
# получение конкретной сущности по идентификатору
    def get(self, uid: int):
        try:
            book = Book.query.get(uid)
            return book_schema.dump(book), 200
        except Exception as e:
            return "", 404

# обновление сущности по идентификатору
    def put(self, uid):
        book = Book.query.get(uid)
        req_json = request.json
        book.name = req_json.get("name")
        book.email = req_json.get("email")
        book.age = req_json.get("age")
        db.session.add(book)
        db.session.commit()
        return "", 204

# частичное обновление сущности, здесь мы получаем только часть полей и их обновляем у сущности
    def patch(self, uid):
        book = Book.query.get(uid)
        req_json = request.json
        if "name" in req_json:
            book.name = req_json.get("name")
        if "email" in req_json:
            book.email = req_json.get("email")
        if "age" in req_json:
            book.age = req_json.get("age")
        db.session.add(book)
        db.session.commit()
        return "", 204

# удаление сущности
    def delete(self, uid: int):
        user = Book.query.get(uid)
        db.session.delete(user)
        db.session.commit()
        return "", 204
                    

Оглавление

Архитектура

Простая архиектура

Архитектура

это каркас приложения, структура директорий и способы взаимодействия объектов друг с другом.

1. Производительность — это то, насколько быстро ваше приложение обрабатывает запросы. 2. Расширяемость — это то, насколько легко и красиво можно добавить новую функцию в приложение в будущем. 3. Тестируемость — это то, насколько легко и качественно можно протестировать ваше приложение. 4. Поддерживаемость — это то, насколько легко и быстро можно будет исправить ошибки и/или неточности, найденные в приложении.. 5. Читаемость — легко ли читать код, понимать, что он делает, и осознавать логику, которую в этот код заложил разработчик.

Проект с готовой упрощенной архитектурой, в которой: 1. Вынесены Resources в views/<entity_name>.py. 2. Вынесены models в models.py. 3. Настройка БД в setup_db.py. 4. Создан config. https://github.com/skypro-008/lesson18/tree/main/project-easy

Простая архиектура

Q: Где находятся views? A: В views/entity_name.py.

Q: Где находятся модели и схемы сериализации? A: В models.py.

Q: Где настройка Flask-SQLAlchemy? A: В setup_db.py.

Q: В какой функции в app.py мы работаем с расширениями Flask (Flask-RESTX, Flask-SQLAlchemy)? A: В register_extenstions.

Оглавление

Глоссарий

Простая архитектура

Архитектура MVC в нашей реализации

Селф-чек

Глоссарий

**Бизнес-логика** — это логика, алгоритмы, код, связанный с доменными сущностями (относящиеся к предметной области), а не к обвязке вокруг нее. **DAO** (Data Access Object) — объекты с доступом к данным в хранилище. **Предметная область** — это объекты, которые относятся к предметной области, например классы «Пользователь», «Книга», «Фильм».

Простая архитектура

Архитектура MVC в нашей реализации

Селф-чек

Q: Где находятся модели? A: В dao/models/entity_name.py.

Q: Где находится DAO? A: В dao/entity_name.py.

Q: Где находится бизнес-логика? A: В service/entity_name.py.

Q: Кто использует DAO? A: Сервисы.

Q: Кто использует сервисы? A: views

Q: Как идет dataflow данных? A: views → service → dao → storage

Оглавление

JWT — JSON Web Token

Декораторы

В чем смысл декоратора

Как выглядит простой декоратор

JWT

Как работать с JWT при помощи PyJWT

Какие виды аутентификации есть

Что такое JWT

Как сгенерировать токен

Как применить декоратор на метод CBV

JWT — JSON Web Token

Аутентификация — процедура проверки подлинности, например проверка подлинности пользователя путем сравнения введенного им пароля с паролем, сохраненным в базе данных.

Идентификация — процедура, в результате которой для субъекта идентификации выявляется его идентификатор. Идентификатор — это логин и пароль. Например, когда пользователь логинится, он вводит свои логин и пароль.

Авторизация — предоставление пользователю прав на выполнение определенных действий.

Декоратор — функция, которая позволяет обернуть другую функцию для расширения ее функциональности без непосредственного изменения ее кода.

Декораторы


                        
# Создаем декоратор
def decorator_name(func):
    def wrapper():
        print('До выполнения функции')
        func()
        print('После выполнения функции')
    return wrapper

# Применяем декоратор
@decorator_name
def my_func():
    print('OK')

# Смотрим результат
$ python3 app.py
До выполнения функции
OK
После выполнения функции
                    

В чем смысл декоратора

В расширении функциональности без изменения кода функции.

Как выглядит простой декоратор


                        
def decorator_name(func):
    def wrapper():
        print('До выполнения функции')
        func()
        print('После выполнения функции')

    return wrapper
                    

JWT

# Устанавливаем библиотеку для работы с JWT. pip3 install pyjwt

# Какая библиотека используется в Python для работы с JWT? PyJWT

# Как выглядит декоратор проверки JWT в заголовке Authorization реквеста?


                        
def auth_required(func):
    def wrapper(*args, **kwargs):
        if 'Authorization' not in request.headers:
            abort(401)

        data = request.headers['Authorization']
        token = data.split("Bearer ")[-1]
        try:
            jwt.decode(token, secret, algorithms=[algo])
        except Exception as e:
            print("JWT Decode Exception", e)
            abort(401)
        return func(*args, **kwargs)

    return wrapper
                    

Как работать с JWT при помощи PyJWT


                        
import calendar
import datetime

import jwt

secret = 's3cR$eT'
algo = 'HS256'


# Создаем токен с временем истечения
def generate_token(data):
    min30 = datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
    data["exp"] = calendar.timegm(min30.timetuple())
    return jwt.encode(data, secret, algorithm=algo)

# Проверяем валидность токена и не истек ли он
def check_token(token):
    try:
        jwt.decode(token, secret, algorithms=[algo])
        return True
    except Exception as e:
        return False


if __name__ == '__main__':
    data = {
        'username': 'myname',
        'role': 'user'
    }
    token = generate_token(data)
    is_ok = check_token(token)
                    

Какие виды аутентификации есть


                        
- Basic
- Token
- OAuth 2.0
                    

Что такое JWT

Это JSON-объект, который определен в открытом стандарте RFC 7519. Он считается одним из безопасных способов передачи информации между двумя участниками. header.payload.signature

Как сгенерировать токен


                        
data = {
	"username:" "ok",
	"role": "user"
}
secret = "Secret"
algo = 'HS256'
token = jwt.encode(data, secret, algorithm=algo)
                    

Как применить декоратор на метод CBV


                        
import jwt
from flask import request, abort, Flask
from flask_restx import Api, Resource

algo = 'HS256'
secret = 's3cR3t'


def auth_required(func):
    def wrapper(*args, **kwargs):
        if 'Authorization' not in request.headers:
            abort(401)

        data = request.headers['Authorization']
        token = data.split("Bearer ")[-1]
        try:
            jwt.decode(token, secret, algorithms=[algo])
        except Exception as e:
            print("JWT Decode Exception", e)
            abort(401)
        return func(*args, **kwargs)

    return wrapper


app = Flask(__name__)
api = Api(app)
book_ns = api.namespace('')


@book_ns.route('/books')
class BooksView(Resource):
    def get(self):
        return [], 200

    @auth_required
    def post(self):
        return "", 201


if __name__ == '__main__':
    app.run(debug=False)
                    

Оглавление

Глоссарий

Регистрация

Пароль

### Как злоумышленник может получить пароль?

Хеши

Как проверить, что хеши одинаковые?

refresh_token

Как сгенерить refresh_token?

Как проверить валидность refresh-токена?

Проверка роли

Глоссарий

Хеш-функция — это функция, которая необратимо искажает исходную строку (пароль), получается хеш, так что нельзя получить из хеша исходную строку — пароль.

Соль — строка, которая добавляется перед паролем, чтобы хеш получился сложнее и его сложнее было подобрать.

Хеш — это искаженная строка (результат работы хеш-функции), построенная на базе другой, нормальной строки — пароля.

Регистрация


                        
# views/users
from flask import request
from flask_restx import Resource, Namespace
from marshmallow import Schema, fields

from helpers import auth_required
from implemented import user_service

user_ns = Namespace('users')


class UserSchema(Schema):
    id = fields.Int()
    username = fields.Str()
    password = fields.Str()
    role = fields.Str()


@user_ns.route('/')
class UsersView(Resource):
    def get(self):
        all_users = user_service.get_all()
        res = UserSchema(many=True).dump(all_users)
        return res, 200

    def post(self):
        req_json = request.json
        user = user_service.create(req_json)
        return "", 201, {"location": f"/users/{user.id}"}


@user_ns.route('/<int:uid>')
class UserView(Resource):
    @auth_required
    def delete(self, uid):
        user_service.delete(uid)
        return "", 204

# service/user.py
import base64
import hashlib
import hmac

from constants import PWD_HASH_ITERATIONS, PWD_HASH_SALT
from dao.user import UserDAO


class UserService:
    def __init__(self, dao: UserDAO):
        self.dao = dao

    def get_one(self, bid):
        return self.dao.get_one(bid)

    def get_all(self):
        return self.dao.get_all()

    def get_by_username(self, username):
        return self.dao.get_by_username(username)

    def create(self, user_d):
        user_d["password"] = self.make_user_password_hash(user_d.get("password"))
        return self.dao.create(user_d)

    def update(self, user_d):
        user_d["password"] = self.make_user_password_hash(user_d.get("password"))
        self.dao.update(user_d)
        return self.dao

    def delete(self, rid):
        self.dao.delete(rid)

    def make_user_password_hash(self, password):
        return base64.b64encode(hashlib.pbkdf2_hmac(
            'sha256',
            password.encode('utf-8'),
            PWD_HASH_SALT,
            PWD_HASH_ITERATIONS
        ))

    def compare_passwords(self, password_hash, other_password) -> bool:
        return hmac.compare_digest(
            base64.b64decode(password_hash),
            hashlib.pbkdf2_hmac('sha256', other_password.encode(), PWD_HASH_SALT, PWD_HASH_ITERATIONS)
        )

# dao/model/user.py
from setup_db import db


class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String)
    password = db.Column(db.String)
    role = db.Column(db.String)

# dao/user.py
from dao.model.user import User


class UserDAO:
    def __init__(self, session):
        self.session = session

    def get_one(self, bid):
        return self.session.query(User).get(bid)

    def get_by_username(self, username):
        return self.session.query(User).filter(User.username == username).first()

    def get_all(self):
        return self.session.query(User).all()

    def create(self, user_d):
        ent = User(**user_d)
        self.session.add(ent)
        self.session.commit()
        return ent

    def delete(self, rid):
        user = self.get_one(rid)
        self.session.delete(user)
        self.session.commit()

    def update(self, user_d):
        user = self.get_one(user_d.get("id"))
        user.name = user_d.get("name")
        user.password = user_d.get("password")

        self.session.add(user)
        self.session.commit()
                    

Пароль


                        
### Какой пароль считается сложным?

Тот, который соответствует всем требованиям:

- Минимум 8 символов (UhS^4b2FSz).
- Минимум 1 буква в верхнем регистре (Z).
- Минимум 1 цифра (3).
- Минимум 1 спецсимвол (%).
- В нем не должно быть словарного слова (password, magic, baby, etc.).
- В нем не должно быть закодировано слово (MyB@BY или r@@m).
                    

### Как злоумышленник может получить пароль?


                        
- Брутфорс.
- Похищение базы с хешами.
- Социальная инженерия.
                    

Хеши

Как захешировать пароль солью?


                        
base64.b64encode(hashlib.pbkdf2_hmac(
            'sha256',
            password.encode('utf-8'),
            '<anything>',
            1000
        ))
                    

Как проверить, что хеши одинаковые?


                        hmac.compare_digest(
   base64.b64decode(password_hash),
   hashlib.pbkdf2_hmac('sha256', other_password.encode(), '<anything>', 1000)
)
                    

refresh_token

### **Зачем нужен refresh_token?** Чтобы не заставлять пользователя вводить свои данные для получения access_token.

Как сгенерить refresh_token?

Так же, как access_token.


                        
days130 = datetime.datetime.utcnow() + datetime.timedelta(days=130)
data["exp"] = calendar.timegm(days130.timetuple())
refresh_token = jwt.encode(data, JWT_SECRET, algorithm=JWT_ALGORITHM)
                    

Как проверить валидность refresh-токена?

Так же, как и access-токена.


                        
secret = 's3cR$eT'
algo = 'HS256'

# Если метод decode не выдаст exception — токен валиден
def check_token(token):
    try:
        jwt.decode(token, secret, algorithms=[algo])
        return True
    except Exception as e:
        return False
                    

Проверка роли

Пример декоратора, проверяющего роль:


                        
secret = 's3cR$eT'
algo = 'HS256'

def admin_required(func):
    def wrapper(*args, **kwargs):
        if 'Authorization' not in request.headers:
            abort(401)

        data = request.headers['Authorization']
        token = data.split("Bearer ")[-1]
        try:
            user = jwt.decode(token, secret, algorithms=[algo])
            role = user.get("role")
            if role != "admin":
                abort(400)
        except Exception as e:
            print("JWT Decode Exception", e)
            abort(401)
        return func(*args, **kwargs)

    return wrapper
                    

Про передачу параметра роли в декораторе читайте в статье «Python: декорируем декораторы» https://habr.com/ru/post/187482/

© 2023 Все права защищены