Skip to content

Commit

Permalink
IFC-1031 Fix access to REST endpoints for anonymous (#5415)
Browse files Browse the repository at this point in the history
  • Loading branch information
gmazoyer authored Jan 10, 2025
1 parent 7285441 commit 65dedb2
Show file tree
Hide file tree
Showing 26 changed files with 393 additions and 157 deletions.
18 changes: 13 additions & 5 deletions backend/infrahub/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from typing import NoReturn
from __future__ import annotations

from fastapi import APIRouter
from typing import TYPE_CHECKING, NoReturn

from fastapi import APIRouter, Depends
from fastapi.openapi.docs import (
get_redoc_html,
get_swagger_ui_html,
)
from starlette.responses import HTMLResponse
from starlette.responses import HTMLResponse # noqa: TC002

from infrahub.api import (
artifact,
Expand All @@ -21,8 +23,12 @@
storage,
transformation,
)
from infrahub.api.dependencies import get_current_user
from infrahub.exceptions import ResourceNotFoundError

if TYPE_CHECKING:
from infrahub.auth import AccountSession

router = APIRouter(prefix="/api")

router.include_router(artifact.router)
Expand All @@ -40,7 +46,9 @@


@router.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html() -> HTMLResponse:
async def custom_swagger_ui_html(
_: AccountSession = Depends(get_current_user),
) -> HTMLResponse:
return get_swagger_ui_html(
openapi_url="/api/openapi.json",
title="Infrahub - Swagger UI",
Expand All @@ -50,7 +58,7 @@ async def custom_swagger_ui_html() -> HTMLResponse:


@router.get("/redoc", include_in_schema=False)
async def redoc_html() -> HTMLResponse:
async def redoc_html(_: AccountSession = Depends(get_current_user)) -> HTMLResponse:
return get_redoc_html(
openapi_url="/api/openapi.json",
title="Infrahub - ReDoc",
Expand Down
3 changes: 2 additions & 1 deletion backend/infrahub/api/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from infrahub.workflows.catalogue import REQUEST_ARTIFACT_DEFINITION_GENERATE

if TYPE_CHECKING:
from infrahub.auth import AccountSession
from infrahub.permissions import PermissionManager

log = get_logger()
Expand All @@ -37,7 +38,7 @@ async def get_artifact(
artifact_id: str,
db: InfrahubDatabase = Depends(get_db),
branch_params: BranchParams = Depends(get_branch_params),
_: str = Depends(get_current_user),
_: AccountSession = Depends(get_current_user),
) -> Response:
artifact = await registry.manager.get_one(db=db, id=artifact_id, branch=branch_params.branch, at=branch_params.at)
if not artifact:
Expand Down
8 changes: 7 additions & 1 deletion backend/infrahub/api/auth.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from fastapi import APIRouter, Depends, Response

from infrahub import config, models
Expand All @@ -8,7 +12,9 @@
create_fresh_access_token,
invalidate_refresh_token,
)
from infrahub.database import InfrahubDatabase

if TYPE_CHECKING:
from infrahub.database import InfrahubDatabase

router = APIRouter(prefix="/auth")

Expand Down
20 changes: 13 additions & 7 deletions backend/infrahub/api/diff/diff.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
from __future__ import annotations

from collections import defaultdict
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING

from fastapi import APIRouter, Depends, Request

from infrahub.api.dependencies import get_branch_dep, get_current_user, get_db
from infrahub.core import registry
from infrahub.core.branch import Branch # noqa: TC001
from infrahub.core.diff.artifacts.calculator import ArtifactDiffCalculator
from infrahub.core.diff.branch_differ import BranchDiffer
from infrahub.core.diff.model.diff import (
BranchDiffArtifact,
BranchDiffFile,
BranchDiffRepository,
)
from infrahub.database import InfrahubDatabase # noqa: TC001

if TYPE_CHECKING:
from infrahub.auth import AccountSession
from infrahub.core.branch import Branch
from infrahub.database import InfrahubDatabase
from infrahub.services import InfrahubServices


Expand All @@ -29,17 +30,22 @@ async def get_diff_files(
request: Request,
db: InfrahubDatabase = Depends(get_db),
branch: Branch = Depends(get_branch_dep),
time_from: Optional[str] = None,
time_to: Optional[str] = None,
time_from: str | None = None,
time_to: str | None = None,
branch_only: bool = True,
_: str = Depends(get_current_user),
_: AccountSession = Depends(get_current_user),
) -> dict[str, dict[str, BranchDiffRepository]]:
response: dict[str, dict[str, BranchDiffRepository]] = defaultdict(dict)
service: InfrahubServices = request.app.state.service

# Query the Diff for all files and repository from the database
diff = await BranchDiffer.init(
db=db, branch=branch, diff_from=time_from, diff_to=time_to, branch_only=branch_only, service=service
db=db,
branch=branch,
diff_from=time_from,
diff_to=time_to,
branch_only=branch_only,
service=service,
)
diff_files = await diff.get_files()

Expand Down
6 changes: 3 additions & 3 deletions backend/infrahub/api/file.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Optional, Union
from typing import TYPE_CHECKING

from fastapi import APIRouter, Depends, Request
from starlette.responses import PlainTextResponse
Expand All @@ -27,13 +27,13 @@ async def get_file(
file_path: str,
branch_params: BranchParams = Depends(get_branch_params),
db: InfrahubDatabase = Depends(get_db),
commit: Optional[str] = None,
commit: str | None = None,
_: str = Depends(get_current_user),
) -> PlainTextResponse:
"""Retrieve a file from a git repository."""
service: InfrahubServices = request.app.state.service

repo: Union[CoreRepository, CoreReadOnlyRepository] = await NodeManager.get_one_by_id_or_default_filter(
repo: CoreRepository | CoreReadOnlyRepository = await NodeManager.get_one_by_id_or_default_filter(
db=db,
id=repository_id,
kind=InfrahubKind.GENERICREPOSITORY,
Expand Down
25 changes: 19 additions & 6 deletions backend/infrahub/api/internal.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
from __future__ import annotations

import re
from typing import Optional
from typing import TYPE_CHECKING

import ujson
from fastapi import APIRouter, Request
from fastapi import APIRouter, Depends, Request
from lunr.index import Index
from pydantic import BaseModel

from infrahub import config
from infrahub.config import AnalyticsSettings, ExperimentalFeaturesSettings, LoggingSettings, MainSettings
from infrahub.api.dependencies import get_current_user
from infrahub.config import ( # noqa: TC001
AnalyticsSettings,
ExperimentalFeaturesSettings,
LoggingSettings,
MainSettings,
)
from infrahub.core import registry
from infrahub.exceptions import NodeNotFoundError

if TYPE_CHECKING:
from infrahub.auth import AccountSession

router = APIRouter()


Expand Down Expand Up @@ -39,15 +50,15 @@ async def get_config() -> ConfigAPI:


@router.get("/info")
async def get_info(request: Request) -> InfoAPI:
async def get_info(request: Request, _: AccountSession = Depends(get_current_user)) -> InfoAPI:
return InfoAPI(deployment_id=str(registry.id), version=request.app.version)


class SearchDocs:
def __init__(self) -> None:
self._title_documents: list[dict] = []
self._heading_documents: list[dict] = []
self._heading_index: Optional[Index] = None
self._heading_index: Index | None = None

def _load_json(self) -> None:
"""
Expand Down Expand Up @@ -142,7 +153,9 @@ class SearchResultAPI(BaseModel):


@router.get("/search/docs", include_in_schema=False)
async def search_docs(query: str, limit: Optional[int] = None) -> list[SearchResultAPI]:
async def search_docs(
query: str, limit: int | None = None, _: AccountSession = Depends(get_current_user)
) -> list[SearchResultAPI]:
smart_query = smart_queries(query)
search_results = search_docs_loader.heading_index.search(smart_query)
heading_results = [
Expand Down
42 changes: 24 additions & 18 deletions backend/infrahub/api/schema.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Optional, Union
from typing import TYPE_CHECKING, Any

from fastapi import APIRouter, Depends, Query, Request
from pydantic import (
Expand Down Expand Up @@ -72,17 +72,17 @@ def set_kind(cls, values: Any) -> Any:


class APINodeSchema(NodeSchema, APISchemaMixin):
api_kind: Optional[str] = Field(default=None, alias="kind", validate_default=True)
api_kind: str | None = Field(default=None, alias="kind", validate_default=True)
hash: str


class APIGenericSchema(GenericSchema, APISchemaMixin):
api_kind: Optional[str] = Field(default=None, alias="kind", validate_default=True)
api_kind: str | None = Field(default=None, alias="kind", validate_default=True)
hash: str


class APIProfileSchema(ProfileSchema, APISchemaMixin):
api_kind: Optional[str] = Field(default=None, alias="kind", validate_default=True)
api_kind: str | None = Field(default=None, alias="kind", validate_default=True)
hash: str


Expand All @@ -103,16 +103,16 @@ class SchemasLoadAPI(BaseModel):


class JSONSchema(BaseModel):
title: Optional[str] = Field(None, description="Title of the schema")
description: Optional[str] = Field(None, description="Description of the schema")
title: str | None = Field(None, description="Title of the schema")
description: str | None = Field(None, description="Description of the schema")
type: str = Field(..., description="Type of the schema element (e.g., 'object', 'array', 'string')")
properties: Optional[dict[str, Any]] = Field(None, description="Properties of the object if type is 'object'")
items: Optional[Union[dict[str, Any], list[dict[str, Any]]]] = Field(
properties: dict[str, Any] | None = Field(None, description="Properties of the object if type is 'object'")
items: dict[str, Any] | list[dict[str, Any]] | None = Field(
None, description="Items of the array if type is 'array'"
)
required: Optional[list[str]] = Field(None, description="List of required properties if type is 'object'")
schema_spec: Optional[str] = Field(None, alias="$schema", description="Schema version identifier")
additional_properties: Optional[Union[bool, dict[str, Any]]] = Field(
required: list[str] | None = Field(None, description="List of required properties if type is 'object'")
schema_spec: str | None = Field(None, alias="$schema", description="Schema version identifier")
additional_properties: bool | dict[str, Any] | None = Field(
None, description="Specifies whether additional properties are allowed", alias="additionalProperties"
)

Expand Down Expand Up @@ -152,7 +152,9 @@ def evaluate_candidate_schemas(

@router.get("")
async def get_schema(
branch: Branch = Depends(get_branch_dep), namespaces: Union[list[str], None] = Query(default=None)
branch: Branch = Depends(get_branch_dep),
namespaces: list[str] | None = Query(default=None),
_: AccountSession = Depends(get_current_user),
) -> SchemaReadAPI:
log.debug("schema_request", branch=branch.name)
schema_branch = registry.schema.get_schema_branch(name=branch.name)
Expand Down Expand Up @@ -180,21 +182,23 @@ async def get_schema(


@router.get("/summary")
async def get_schema_summary(branch: Branch = Depends(get_branch_dep)) -> SchemaBranchHash:
async def get_schema_summary(
branch: Branch = Depends(get_branch_dep), _: AccountSession = Depends(get_current_user)
) -> SchemaBranchHash:
log.debug("schema_summary_request", branch=branch.name)
schema_branch = registry.schema.get_schema_branch(name=branch.name)
return schema_branch.get_hash_full()


@router.get("/{schema_kind}")
async def get_schema_by_kind(
schema_kind: str, branch: Branch = Depends(get_branch_dep)
) -> Union[APIProfileSchema, APINodeSchema, APIGenericSchema]:
schema_kind: str, branch: Branch = Depends(get_branch_dep), _: AccountSession = Depends(get_current_user)
) -> APIProfileSchema | APINodeSchema | APIGenericSchema:
log.debug("schema_kind_request", branch=branch.name)

schema = registry.schema.get(name=schema_kind, branch=branch, duplicate=False)

api_schema: dict[str, type[Union[APIProfileSchema, APINodeSchema, APIGenericSchema]]] = {
api_schema: dict[str, type[APIProfileSchema | APINodeSchema | APIGenericSchema]] = {
"profile": APIProfileSchema,
"node": APINodeSchema,
"generic": APIGenericSchema,
Expand All @@ -212,7 +216,9 @@ async def get_schema_by_kind(


@router.get("/json_schema/{schema_kind}")
async def get_json_schema_by_kind(schema_kind: str, branch: Branch = Depends(get_branch_dep)) -> JSONSchema:
async def get_json_schema_by_kind(
schema_kind: str, branch: Branch = Depends(get_branch_dep), _: AccountSession = Depends(get_current_user)
) -> JSONSchema:
log.debug("json_schema_kind_request", branch=branch.name)

fields: dict[str, Any] = {}
Expand Down Expand Up @@ -368,7 +374,7 @@ async def check_schema(
request: Request,
schemas: SchemasLoadAPI,
branch: Branch = Depends(get_branch_dep),
_: Any = Depends(get_current_user),
_: AccountSession = Depends(get_current_user),
) -> JSONResponse:
service: InfrahubServices = request.app.state.service
log.info("schema_check_request", branch=branch.name)
Expand Down
16 changes: 8 additions & 8 deletions backend/infrahub/api/storage.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from __future__ import annotations

import hashlib
from typing import TYPE_CHECKING

from fastapi import APIRouter, Depends, File, Response, UploadFile
from infrahub_sdk.uuidt import UUIDT
Expand All @@ -8,6 +11,9 @@
from infrahub.core import registry
from infrahub.log import get_logger

if TYPE_CHECKING:
from infrahub.auth import AccountSession

log = get_logger()
router = APIRouter(prefix="/storage")

Expand All @@ -22,10 +28,7 @@ class UploadContentPayload(BaseModel):


@router.get("/object/{identifier:str}")
def get_file(
identifier: str,
_: str = Depends(get_current_user),
) -> Response:
def get_file(identifier: str, _: AccountSession = Depends(get_current_user)) -> Response:
content = registry.storage.retrieve(identifier=identifier)
return Response(content=content)

Expand All @@ -48,10 +51,7 @@ def upload_content(


@router.post("/upload/file")
def upload_file(
file: UploadFile = File(...),
_: str = Depends(get_current_user),
) -> UploadResponse:
def upload_file(file: UploadFile = File(...), _: AccountSession = Depends(get_current_user)) -> UploadResponse:
# TODO need to optimized how we read the content of the file, especially if the file is really large
# Check this discussion for more details
# https://stackoverflow.com/questions/63048825/how-to-upload-file-using-fastapi
Expand Down
Loading

0 comments on commit 65dedb2

Please sign in to comment.