Source code for swh.web.auth.utils
# Copyright (C) 2020-2022 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
from __future__ import annotations
from base64 import urlsafe_b64encode
from typing import TYPE_CHECKING, List
from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from django.contrib.auth.decorators import user_passes_test
from django.http.request import HttpRequest
if TYPE_CHECKING:
from django.contrib.auth.models import Permission
SWH_AMBASSADOR_PERMISSION = "swh.ambassador"
API_SAVE_ORIGIN_PERMISSION = "swh.web.api.save_origin"
ADMIN_LIST_DEPOSIT_PERMISSION = "swh.web.admin.list_deposits"
MAILMAP_PERMISSION = "swh.web.mailmap"
ADD_FORGE_MODERATOR_PERMISSION = "swh.web.add_forge_now.moderator"
MAILMAP_ADMIN_PERMISSION = "swh.web.admin.mailmap"
API_SAVE_BULK_PERMISSION = "swh.web.api.save_bulk"
API_PROVENANCE_PERMISSION = "swh.web.api.provenance"
WEBAPP_PERMISSIONS = [
SWH_AMBASSADOR_PERMISSION,
API_SAVE_ORIGIN_PERMISSION,
ADMIN_LIST_DEPOSIT_PERMISSION,
MAILMAP_PERMISSION,
ADD_FORGE_MODERATOR_PERMISSION,
MAILMAP_ADMIN_PERMISSION,
API_PROVENANCE_PERMISSION,
]
def _get_fernet(password: bytes, salt: bytes) -> Fernet:
"""
Instantiate a Fernet system from a password and a salt value
(see https://cryptography.io/en/latest/fernet/).
Args:
password: user password that will be used to generate a Fernet key
derivation function
salt: value that will be used to generate a Fernet key
derivation function
Returns:
The Fernet system
"""
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
backend=default_backend(),
)
key = urlsafe_b64encode(kdf.derive(password))
return Fernet(key)
[docs]
def encrypt_data(data: bytes, password: bytes, salt: bytes) -> bytes:
"""
Encrypt data using Fernet system (symmetric encryption).
Args:
data: input data to encrypt
password: user password that will be used to generate a Fernet key
derivation function
salt: value that will be used to generate a Fernet key
derivation function
Returns:
The encrypted data
"""
return _get_fernet(password, salt).encrypt(data)
[docs]
def decrypt_data(data: bytes, password: bytes, salt: bytes) -> bytes:
"""
Decrypt data using Fernet system (symmetric encryption).
Args:
data: input data to decrypt
password: user password that will be used to generate a Fernet key
derivation function
salt: value that will be used to generate a Fernet key
derivation function
Returns:
The decrypted data
"""
return _get_fernet(password, salt).decrypt(data)
[docs]
def privileged_user(request: HttpRequest, permissions: List[str] = []) -> bool:
"""Determine whether a user is authenticated and is a privileged one (e.g ambassador).
This allows such user to have access to some more actions (e.g. bypass save code now
review, access to 'archives' type...).
A user is considered as privileged if he is a staff member or has any permission
from those provided as parameters.
Args:
request: Input django HTTP request
permissions: list of permission names to determine if user is privileged or not
Returns:
Whether the user is privileged or not.
"""
user = request.user
return user.is_authenticated and (
user.is_staff or any([user.has_perm(perm) for perm in permissions])
)
[docs]
def any_permission_required(*perms):
"""View decorator granting access to it if user has at least one
permission among those passed as parameters.
"""
def check_perms(user):
if any(user.has_perm(perm) for perm in perms):
return True
from swh.web.utils.exc import ForbiddenExc
raise ForbiddenExc
return user_passes_test(check_perms)
[docs]
def is_add_forge_now_moderator(user) -> bool:
"""Is a user considered an add-forge-now moderator?
Returns
True if a user is staff or has add forge now moderator permission
"""
return user.is_staff or user.has_perm(ADD_FORGE_MODERATOR_PERMISSION)
[docs]
def get_or_create_django_permission(perm_name: str) -> Permission:
"""Get or create a swh-web permission (different from django model permission)
out of a permission name string.
Args:
perm_name: Permission name (e.g. swh.web.api.throttling_exempted,
swh.ambassador, ...)
Returns:
The persisted permission
"""
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
app_label, codename = perm_name.rsplit(".", maxsplit=1)
content_type = ContentType.objects.get_or_create(
app_label=app_label,
model=codename,
)[0]
return Permission.objects.get_or_create(
codename=codename,
name=perm_name,
content_type=content_type,
)[0]