-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9a848b7
commit 63ff39e
Showing
14 changed files
with
381 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
[settings] | ||
known_third_party = beanie,boto3,botocore,fastapi,fastapi_pagination,httpx,mongomock_motor,motor,pydantic,pydantic_settings,pymongo,pytest,slugify,starlette,typer,uvicorn | ||
known_third_party = beanie,boto3,botocore,fastapi,fastapi_pagination,httpx,mongomock_motor,motor,pydantic,pydantic_settings,pymongo,pytest,slugify,starlette,typer,uvicorn,yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
- app: | ||
name: "sfs" | ||
title: | ||
fr: "Service de stockage et gestion de fichiers" | ||
en: "File storage and management service" | ||
permissions: | ||
- code: "sfs:can-read-bucket" | ||
title: | ||
fr: "Peut lire un dossier d'images" | ||
en: "Can read an image folder" | ||
- code: "sfs:can-create-bucket" | ||
title: | ||
fr: "Peut créer un dossier d'images" | ||
en: "Can create an image folder" | ||
- code: "sfs:can-delete-bucket" | ||
title: | ||
fr: "Peut supprimer un dossier d'images" | ||
en: "Can delete an image folder" | ||
- code: "sfs:can-write-bucket" | ||
title: | ||
fr: "Peut écrire dans un dossier d'images" | ||
en: "Can write in an image folder" | ||
- code: "sfs:can-read-file" | ||
title: | ||
fr: "Peut lire un fichier" | ||
en: "Can read a file" | ||
- code: "sfs:can-write-file" | ||
title: | ||
fr: "Peut ajouter un fichier dans un dossier d'images" | ||
en: "Can add a file in an image folder" | ||
- code: "sfs:can-delete-file" | ||
title: | ||
fr: "Peut supprimer un fichier" | ||
en: "Can delete a file" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
from typing import Set | ||
from urllib.parse import urlencode, urljoin | ||
|
||
import httpx | ||
from fastapi import Header, status | ||
|
||
from src.config import settings | ||
from .error_codes import SfsErrorCodes | ||
from .exception import CustomHTTPException | ||
|
||
|
||
class CheckAccessAllow: | ||
""" | ||
This class is used to check if a user has the necessary permissions to access a resource. | ||
""" | ||
|
||
def __init__(self, permissions: Set, raise_exception: bool = True): | ||
self.url = urljoin(settings.API_AUTH_URL_BASE, settings.API_AUTH_CHECK_ACCESS_ENDPOINT) | ||
self.permissions = permissions | ||
self.raise_exception = raise_exception | ||
|
||
async def __call__(self, authorization: str = Header(...)): | ||
headers = {"Authorization": authorization} | ||
query_params = urlencode([("permission", item) for item in self.permissions]) | ||
url = f"{self.url}?{query_params}" | ||
|
||
async with httpx.AsyncClient() as client: | ||
response = await client.get(url, headers=headers) | ||
|
||
if response.is_success is False: | ||
if self.raise_exception: | ||
raise CustomHTTPException( | ||
error_code=SfsErrorCodes.AUTH_ACCESS_DENIED, | ||
error_message="Access denied", | ||
status_code=status.HTTP_403_FORBIDDEN, | ||
) | ||
return False | ||
|
||
access = response.json()["access"] | ||
if access is False and self.raise_exception: | ||
raise CustomHTTPException( | ||
error_code=SfsErrorCodes.AUTH_ACCESS_DENIED, | ||
error_message="Access denied", | ||
status_code=status.HTTP_403_FORBIDDEN, | ||
) | ||
|
||
return access |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import os.path | ||
from pathlib import Path | ||
|
||
import yaml | ||
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorCollection | ||
from slugify import slugify | ||
|
||
BASE_DIR = Path(__file__).parent.parent.parent | ||
|
||
|
||
async def __init_collection(client: AsyncIOMotorClient, collection_path: str) -> AsyncIOMotorCollection: | ||
""" | ||
Initialize a collection with a unique index on the 'app' field. | ||
:param client: Motor client instance to connect to the database. | ||
:rtype client: AsyncIOMotorClient | ||
:param collection_path: Path to the collection in the format 'database.collection'. | ||
:rtype collection_path: str | ||
:return: Collection instance. | ||
:rtype: AsyncIOMotorCollection | ||
""" | ||
database_name, collection_name = collection_path.split(".") | ||
database = client[database_name] | ||
collection = database[collection_name] | ||
|
||
await collection.create_index("app", unique=True, background=True) | ||
|
||
return collection | ||
|
||
|
||
async def __load_app_description(filpath) -> dict: | ||
""" | ||
Load the app description from a JSON file. | ||
:param filpath: Path to the JSON file. | ||
:rtype filpath: str | ||
:return: App description. | ||
:rtype: dict | ||
""" | ||
|
||
if os.path.exists(filpath) is False: | ||
raise ValueError("App description file not found.") | ||
|
||
with open(filpath, "r") as f: | ||
data = yaml.safe_load(f) | ||
|
||
return data | ||
|
||
|
||
async def __load_app_data( | ||
mongodb_client: AsyncIOMotorClient, | ||
collection_path: str, | ||
filename: str, | ||
data_key: str, | ||
update_key: str, | ||
update_value: callable, | ||
): | ||
""" | ||
Load app data from a JSON file and update the database. | ||
""" | ||
|
||
coll = await __init_collection(mongodb_client, collection_path) | ||
|
||
filepath = BASE_DIR / f"{filename}" | ||
data = await __load_app_description(filepath) | ||
|
||
if not (appname := data[0].get("app", {}).get("name", "").strip()): | ||
raise ValueError(f"App name '{appname}' not found in {filepath}") | ||
|
||
if not (value := data[0].get("app", {}).get(data_key, {})): | ||
raise ValueError(f"{data_key} section for app '{appname}' not found in {filepath}") | ||
|
||
await coll.update_one( | ||
{"app": slugify(appname)}, | ||
{"$set": {update_key: update_value(value)}}, | ||
upsert=True, | ||
) | ||
|
||
|
||
async def load_app_description( | ||
mongodb_client: AsyncIOMotorClient, | ||
collection_path: str = None, | ||
filename: str = "appdesc.yml", | ||
): | ||
""" | ||
Load the app description from a JSON file and update the database. | ||
""" | ||
|
||
if not (coll_path := collection_path or os.environ.get("APP_DESC_DB_COLLECTION")): | ||
raise ValueError("Invalid collection path") | ||
|
||
await __init_collection(mongodb_client, coll_path) | ||
|
||
await __load_app_data( | ||
mongodb_client, | ||
coll_path, | ||
filename, | ||
"title", | ||
"title", | ||
lambda value: value, | ||
) | ||
|
||
|
||
async def load_app_permissions( | ||
mongodb_client: AsyncIOMotorClient, | ||
collection_path: str = None, | ||
filename: str = "appdesc.yml", | ||
): | ||
""" | ||
Load the app permissions from a JSON file and update the database. | ||
""" | ||
|
||
if not (coll_path := collection_path or os.environ.get("PERMS_DB_COLLECTION")): | ||
raise ValueError("Invalid collection path") | ||
|
||
await __load_app_data( | ||
mongodb_client, | ||
coll_path, | ||
filename, | ||
"permissions", | ||
"permissions", | ||
lambda value: [ | ||
{ | ||
"code": slugify(perm.get("code", ""), regex_pattern=r"[^a-zA-Z0-9:]+"), | ||
"desc": perm.get("desc", ""), | ||
} | ||
for perm in value | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.