simple-login/app/utils.py
2022-03-29 18:40:52 +02:00

126 lines
3.3 KiB
Python

import random
import secrets
import string
import time
import urllib.parse
from functools import wraps
from typing import List, Optional
from unidecode import unidecode
from .config import WORDS_FILE_PATH, ALLOWED_REDIRECT_DOMAINS
from .log import LOG
with open(WORDS_FILE_PATH) as f:
LOG.d("load words file: %s", WORDS_FILE_PATH)
_words = f.read().split()
def random_word():
return random.choice(_words)
def word_exist(word):
return word in _words
def random_words():
"""Generate a random words. Used to generate user-facing string, for ex email addresses"""
# nb_words = random.randint(2, 3)
nb_words = 2
return "_".join([secrets.choice(_words) for i in range(nb_words)])
def random_string(length=10, include_digits=False):
"""Generate a random string of fixed length"""
letters = string.ascii_lowercase
if include_digits:
letters += string.digits
return "".join(secrets.choice(letters) for _ in range(length))
def convert_to_id(s: str):
"""convert a string to id-like: remove space, remove special accent"""
s = s.replace(" ", "")
s = s.lower()
s = unidecode(s)
return s
_ALLOWED_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-."
def convert_to_alphanumeric(s: str) -> str:
ret = []
# drop all control characters like shift, separator, etc
for c in s:
if c not in _ALLOWED_CHARS:
ret.append("_")
else:
ret.append(c)
return "".join(ret)
def encode_url(url):
return urllib.parse.quote(url, safe="")
def sanitize_email(email_address: str, not_lower=False) -> str:
if email_address:
email_address = email_address.strip().replace(" ", "").replace("\n", " ")
if not not_lower:
email_address = email_address.lower()
return email_address
class NextUrlSanitizer:
@staticmethod
def sanitize(url: Optional[str], allowed_domains: List[str]) -> Optional[str]:
if not url:
return None
# Relative redirect
if url[0] == "/":
return url
return NextUrlSanitizer.__handle_absolute_redirect(url, allowed_domains)
@staticmethod
def __handle_absolute_redirect(
url: str, allowed_domains: List[str]
) -> Optional[str]:
if not NextUrlSanitizer.__is_absolute_url(url):
# Unknown url, something like &next=something.example.com
return None
parsed = urllib.parse.urlparse(url)
if parsed.hostname in allowed_domains:
return url
# Not allowed domain
return None
@staticmethod
def __is_absolute_url(url: str) -> bool:
return url.startswith(("http://", "https://"))
def sanitize_next_url(url: Optional[str]) -> Optional[str]:
return NextUrlSanitizer.sanitize(url, ALLOWED_REDIRECT_DOMAINS)
def query2str(query):
"""Useful utility method to print out a SQLAlchemy query"""
return query.statement.compile(compile_kwargs={"literal_binds": True})
def debug_info(func):
@wraps(func)
def wrap(*args, **kwargs):
start = time.time()
LOG.d("start %s %s %s", func.__name__, args, kwargs)
ret = func(*args, **kwargs)
LOG.d("finish %s. Takes %s seconds", func.__name__, time.time() - start)
return ret
return wrap