Source code for flask_jwt_router._jwt_routes

"""
    Quick Start
    ===========

    Installation::

       pip install flask-jwt-router


    Basic Usage::

        from flask import Flask
        from flask_jwt_router import JwtRoutes

        app = Flask(__name__)
        # You are required to always set a unique SECRET_KEY for your app
        app.config["SECRET_KEY"] = "your_app_secret_key"

        JwtRoutes(app)

        # If you're using the Flask factory pattern:
        jwt_routes = JwtRoutes()

        def create_app(config):
            ...
            jwt_routes.init_app(app)


    Authorizing Routes
    ==================

    Define as a list of tuples::

        app.config["WHITE_LIST_ROUTES"] = [
            ("POST", "/register"),
        ]

        @app.route("/register", methods=["POST"])
        def register():
            return "I don't need authorizing!"


    Prefix your api name to whitelisted routes::

        # All routes will
        app.config["JWT_ROUTER_API_NAME"] = "/api/v1"
        app.config["WHITE_LIST_ROUTES"] = [
            ("POST", "/register"),
        ]

        @app.route("/api/v1/register", methods=["POST"])
        def register():
            return "I don't need authorizing!"


    Bypass Flask-JWT-Router on specified routes::

        # Define homepage template routes for example on JWT_IGNORE_ROUTES
        # & still get to use the api name on request handlers returning resources

        app.config["IGNORED_ROUTES"] = [
            ("GET", "/")
        ]


    Declare an entity model::

        # Create your entity model (example uses Flask-SqlAlchemy)

        class UserModel(db.Model):
            __tablename__ = "users"
            id = db.Column(db.Integer, primary_key=True)
            name = db.Column(db.String)

        JwtRoutes(app, entity_models=[UserModel, TeacherModel, ...etc])

        # Or pass later with `init_app`
        def create_app(config):
            ...
            jwt_routes.init_app(app, entity_models=[UserModel, TeacherModel, ...etc])

    Setting the Token Expire Duration
    =================================

    There are two ways to set the expire duration of the JWT.

    from your app config::

        # Set the token expire duration to 7 days
        app.config["JWT_EXPIRE_DAYS"] = 7

    using the :class:`~flask_jwt_router.set_exp` method::

        # Set the token expire duration to 14 days
        jwt_routes = JwtRoutes()
        # jwt_routes.init_app( ...etc
        jwt_routes.set_exp(expire_days=14)

    By default the expire duration is set to 30 days

    Authorization & Tokens
    ======================

    From your_app import jwt_routes::

        # white list the routes
        app.config["WHITE_LIST_ROUTES"] = [
            ("POST", "/register"),
            ("POST", "/login"),
        ]

        @app.route("/register", methods=["POST"])
        def register():
            # I'm registering a new user & returning a token!
            return jsonify({
                "token": jwt_routes.create_token(entity_id=1)
            })

        @app.route("/login", methods=["POST"])
        def login():
            # I'm authorized & updating my token!
            return jsonify({
                "token": jwt_routes.update_token(entity_id=1)
            })

    Create a new entity & return a new token::

        @app.route("/register", methods=["POST"])
        def register():
            user_data = request.get_json()
            try:
                user = UserModel(**user_data)
                user.create_user() # your entity creation logic

                # Here we pass the id as a kwarg to `create_token`
                token: str = jwt_routes.create_token(entity_id=user.id, table_name="user")

                # Now we can return a new token!
                return {
                    "message": "User successfully created.",
                    "token": str(token),  # casting is optional
                }, 200

    Access entity on Flask's global context::

        from app import app, jwt_routes

        # Example uses Marshmallow to serialize entity object

        @app.route("/login" methods=["GET"])
        def login():
            user_data = g.get("entity")
            try:
                user_dumped = UserSchema().dump(user_data)
            except ValidationError as _:
               return {
                           "error": "User requested does not exist."
                       }, 401
            return {
                "data": user_dumped,
                "token": jwt_routes.create_token(entity_id=user_data.id, table_name="user"),
            }, 200

    If you are handling a request with a token in the headers you can call::

        jwt_routes.update_token(entity_id=user_data.id)

    If you are handling a request without a token in the headers you can call::

        jwt_routes.create_token(entity_id=user_data.id, table_name="user")

"""

import logging
from warnings import warn
from typing import List, Dict

from ._config import Config
from ._entity import BaseEntity, Entity, _ORMType
from ._routing import BaseRouting, Routing
from ._authentication import BaseAuthentication, Authentication
from .oauth2.google import Google
from .oauth2._base import BaseOAuth
from .oauth2.http_requests import HttpRequests
from .oauth2._urls import GOOGLE_OAUTH_URL

# pylint:disable=invalid-name
logger = logging.getLogger()

EXPIRE_DEFAULT = 30


[docs]class JwtRoutes: """ If there app is None then self.init_app(app=None, **kwargs) need to be called inside the Flask app factory pattern. :param app: Flask application instance :param kwargs: entity_model """ #: Logging. logger = logging #: The Flask application instance. app = None #: A list of entity models entity_models: List[_ORMType] #: Low level expire member. See :class:`~flask_jwt_router._config` & set with JWT_EXPIRE_DAYS #: or use :class:`~flask_jwt_router.set_exp`. exp: int #: The class that is used to create Config objects. See :class:`~flask_jwt_router._config` #: for more information. config: Config #: The class that provides algorithms to :class:`~flask_jwt_router._jwt_routes`. # See :class:`~flask_jwt_router._authentication` for more information. auth: BaseAuthentication #: The class that is used to create Entity objects. See :class:`~flask_jwt_router._entity` #: for more information. entity: BaseEntity #: The class that is used to create Routing objects. See :class:`~flask_jwt_router._routing` #: for more information. routing: BaseRouting #: Optional Google OAuth 2.0 Single Sign On. See :class:`~flask_jwt_router.oauth2.google`` #: for more information. google: BaseOAuth #: Optional. See :class:`~flask_jwt_router.oauth2.google` google_oauth: Dict def __init__(self, app=None, **kwargs): self.entity_models = kwargs.get("entity_models") self.google_oauth = kwargs.get("google_oauth") self.config = Config() self.auth = Authentication() self.google = Google(HttpRequests(GOOGLE_OAUTH_URL)) self.app = app if app: self.init_app(app, entity_models=self.entity_models)
[docs] def init_app(self, app=None, **kwargs): """ You can use this to set up your config at runtime :param app: Flask application instance :return: """ self.app = app if app else self.app entity_models = self.entity_models or kwargs.get("entity_models") self.google_oauth = self.google_oauth or kwargs.get("google_oauth") app_config = self.get_app_config(self.app) if self.google_oauth: self.google.init(**self.google_oauth) self.config.init_config(app_config, entity_models=entity_models, google_oauth=self.google_oauth) self.entity = Entity(self.config) self.routing = Routing(self.app, self.config, self.entity, self.google) self.app.before_request(self.routing.before_middleware) if self.config.expire_days: self.exp = self.config.expire_days else: self.exp = EXPIRE_DEFAULT
# pylint:disable=no-self-use
[docs] def get_app_config(self, app): """ :param app: Flask Application Instance :return: Dict[str, Any] """ config = getattr(app, "config", {}) return config
# pylint:disable=no-self-use
[docs] def get_entity_id(self, **kwargs): """ :param kwargs: Dict[str, int] :return: str """ try: return kwargs['entity_id'] except KeyError as _: return None
# pylint:disable=no-self-use
[docs] def set_exp(self, **kwargs) -> None: """ :param kwargs: Dict[str, int] - expire_days: The expire time for the JWT in days :return: None """ try: self.exp = kwargs['expire_days'] except KeyError as _: self.exp = EXPIRE_DEFAULT
[docs] def create_token(self, **kwargs) -> str: """ :param kwargs: :return: str """ if 'entity_type' in kwargs: warn(("'entity_type' argument name has been deprecated and will be replaced" "in the next release. Use 'table_name' instead")) kwargs['table_name'] = kwargs['entity_type'] if 'table_name' not in kwargs: raise KeyError("create_token() missing 1 required argument: table_name") table_name = kwargs.get("table_name") self.config.entity_key = self.entity.get_attr_name(table_name) return self.auth.create_token(self.config, self.exp, **kwargs)
[docs] def update_token(self, **kwargs) -> str: """ :param kwargs: :return: str """ self.config.entity_key = self.entity.get_attr_name() table_name = self.entity.get_entity_from_ext().__tablename__ return self.auth.update_token(self.config, self.exp, table_name, **kwargs)
[docs] def encode_token(self, entity_id) -> str: """ :param entity_id: :return: """ self.config.entity_key = self.entity.get_attr_name() table_name = self.entity.get_entity_from_ext().__tablename__ return self.auth.encode_token(self.config, entity_id, self.exp, table_name)