# -*- coding: utf-8 -*-
"""
Module to deal with url security or other security related stuff.
Module for improving security against cross-site request forgery (CSRF) attacks.
This is done by encoding the URLs with a security key ("SEC_SESSION_KEY" from
the app_configuration module) and salting a string unique to que current login
session.
"""
import typing
from flask import g, url_for, Blueprint, abort
from itsdangerous import URLSafeSerializer, BadSignature
bp = Blueprint("security", __name__, url_prefix="")
[docs]def token_encode(plain: typing.Any, salt: str, secret_key: str) -> typing.Union[str, bytes]:
"""
Encode obj with session salt.
Encodes a plain text string using a secret key and a salt unique to the
current login session (same as the decoder)
Parameters
----------
plain: Any
Plain text string
salt : Union[Iterable[Union[str, bytes]]
Salt unique to the current login session, default = g.salt
secret_key : Union[Iterable[Union[str, bytes]]
Secret key, default = current_app.config["SEC_SESSION_KEY"]
Returns
-------
str
An encoded string
"""
return URLSafeSerializer(secret_key, salt).dumps(plain)
[docs]def token_decode(signed: str, salt: str, secret_key: str) -> typing.Any:
"""
Decode string with session salt.
Decodes a signed string using a secret key and a salt unique to the current
login session (same as the encoder)
Parameters
----------
signed : str
Encoded string
salt : str
Salt unique to the current login session, default = g.salt
secret_key : str
Secret key, default = current_app.config["SEC_SESSION_KEY"]
Returns
-------
Any
A decoded string
"""
try:
obj = URLSafeSerializer(secret_key, salt).loads(signed)
except BadSignature:
abort(
403,
f" url_security : token_decode : User {g.user} tried to call "
+ " a save_url_for with wrong token (signature)",
)
return obj
[docs]@bp.app_template_filter("safe_url_for")
def safe_url_for(token: typing.Any, endpoint: str, secret_key: str, salt: str) -> str:
"""
Save url generator.
Generates URLs in a similar manner to Flask's built-in url_for() function,
but it encodes its keyword arguments to make them harder to guess for an
attacker.
Parameters
----------
token : Any
Object to encode
endpoint : str
Name of the view to be called. It has to take a str (token) as argument.
secret_key : Union[Iterable[Union[str, bytes]]
Secret key for encoding. To be passed for token_encode.
salt : Union[Iterable[Union[str, bytes]]
Salt for encoding. To be passed for token_encode.
Returns
-------
str
A safe URL for the given endpoint and token.
Example
-------
In the html you call save_url_for as filter
{{obj | save_url_view(view)}}
the view needs to have the signature:
@rout(view/string:token)
def view(token):
obj = token_decode(token,...)
"""
return url_for(endpoint, token=token_encode(token, secret_key, salt))