infrahub-server 1.1.1__py3-none-any.whl → 1.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. infrahub/api/__init__.py +13 -5
  2. infrahub/api/artifact.py +9 -15
  3. infrahub/api/auth.py +7 -1
  4. infrahub/api/dependencies.py +15 -2
  5. infrahub/api/diff/diff.py +13 -7
  6. infrahub/api/file.py +5 -10
  7. infrahub/api/internal.py +19 -6
  8. infrahub/api/menu.py +8 -6
  9. infrahub/api/oauth2.py +25 -10
  10. infrahub/api/oidc.py +26 -10
  11. infrahub/api/query.py +2 -2
  12. infrahub/api/schema.py +48 -59
  13. infrahub/api/storage.py +8 -8
  14. infrahub/api/transformation.py +6 -5
  15. infrahub/auth.py +1 -26
  16. infrahub/cli/__init__.py +1 -1
  17. infrahub/cli/context.py +5 -8
  18. infrahub/cli/db.py +6 -6
  19. infrahub/cli/git_agent.py +1 -1
  20. infrahub/computed_attribute/models.py +1 -1
  21. infrahub/computed_attribute/tasks.py +1 -1
  22. infrahub/config.py +5 -5
  23. infrahub/core/account.py +2 -10
  24. infrahub/core/attribute.py +22 -0
  25. infrahub/core/branch/models.py +1 -1
  26. infrahub/core/branch/tasks.py +4 -3
  27. infrahub/core/diff/calculator.py +14 -0
  28. infrahub/core/diff/combiner.py +6 -2
  29. infrahub/core/diff/conflicts_enricher.py +2 -2
  30. infrahub/core/diff/coordinator.py +296 -87
  31. infrahub/core/diff/data_check_synchronizer.py +33 -4
  32. infrahub/core/diff/enricher/cardinality_one.py +3 -3
  33. infrahub/core/diff/enricher/hierarchy.py +4 -1
  34. infrahub/core/diff/merger/merger.py +11 -1
  35. infrahub/core/diff/merger/serializer.py +5 -29
  36. infrahub/core/diff/model/path.py +88 -4
  37. infrahub/core/diff/query/field_specifiers.py +35 -0
  38. infrahub/core/diff/query/roots_metadata.py +48 -0
  39. infrahub/core/diff/query/save.py +1 -0
  40. infrahub/core/diff/query_parser.py +27 -11
  41. infrahub/core/diff/repository/deserializer.py +7 -3
  42. infrahub/core/diff/repository/repository.py +100 -9
  43. infrahub/core/diff/tasks.py +1 -1
  44. infrahub/core/graph/__init__.py +1 -1
  45. infrahub/core/integrity/object_conflict/conflict_recorder.py +6 -1
  46. infrahub/core/ipam/utilization.py +6 -1
  47. infrahub/core/manager.py +8 -0
  48. infrahub/core/merge.py +6 -1
  49. infrahub/core/migrations/graph/__init__.py +2 -0
  50. infrahub/core/migrations/graph/m014_remove_index_attr_value.py +1 -1
  51. infrahub/core/migrations/graph/m015_diff_format_update.py +1 -1
  52. infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -1
  53. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +101 -0
  54. infrahub/core/migrations/query/attribute_add.py +5 -5
  55. infrahub/core/migrations/schema/tasks.py +2 -2
  56. infrahub/core/migrations/shared.py +3 -3
  57. infrahub/core/node/__init__.py +8 -2
  58. infrahub/core/node/constraints/grouped_uniqueness.py +9 -2
  59. infrahub/core/query/__init__.py +5 -2
  60. infrahub/core/query/diff.py +32 -19
  61. infrahub/core/query/ipam.py +30 -22
  62. infrahub/core/query/node.py +91 -40
  63. infrahub/core/schema/generated/attribute_schema.py +2 -2
  64. infrahub/core/schema/generated/base_node_schema.py +2 -2
  65. infrahub/core/schema/generated/relationship_schema.py +1 -1
  66. infrahub/core/schema/schema_branch_computed.py +1 -1
  67. infrahub/core/task/task_log.py +1 -1
  68. infrahub/core/validators/attribute/kind.py +1 -1
  69. infrahub/core/validators/interface.py +1 -2
  70. infrahub/core/validators/models/violation.py +1 -14
  71. infrahub/core/validators/shared.py +2 -2
  72. infrahub/core/validators/tasks.py +7 -4
  73. infrahub/core/validators/uniqueness/index.py +2 -4
  74. infrahub/database/index.py +1 -1
  75. infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
  76. infrahub/dependencies/builder/constraint/schema/attribute_kind.py +8 -0
  77. infrahub/dependencies/builder/diff/data_check_synchronizer.py +2 -0
  78. infrahub/git/base.py +3 -3
  79. infrahub/git/integrator.py +1 -1
  80. infrahub/graphql/api/endpoints.py +12 -3
  81. infrahub/graphql/app.py +2 -2
  82. infrahub/graphql/auth/query_permission_checker/default_branch_checker.py +2 -17
  83. infrahub/graphql/auth/query_permission_checker/merge_operation_checker.py +1 -12
  84. infrahub/graphql/auth/query_permission_checker/object_permission_checker.py +6 -40
  85. infrahub/graphql/auth/query_permission_checker/super_admin_checker.py +5 -8
  86. infrahub/graphql/enums.py +2 -2
  87. infrahub/graphql/initialization.py +27 -8
  88. infrahub/graphql/manager.py +9 -3
  89. infrahub/graphql/models.py +6 -0
  90. infrahub/graphql/mutations/account.py +14 -10
  91. infrahub/graphql/mutations/computed_attribute.py +11 -22
  92. infrahub/graphql/mutations/diff.py +2 -0
  93. infrahub/graphql/mutations/main.py +5 -16
  94. infrahub/graphql/mutations/proposed_change.py +11 -20
  95. infrahub/graphql/mutations/resource_manager.py +6 -3
  96. infrahub/graphql/mutations/schema.py +8 -7
  97. infrahub/graphql/mutations/tasks.py +1 -1
  98. infrahub/graphql/permissions.py +3 -4
  99. infrahub/graphql/queries/account.py +2 -11
  100. infrahub/graphql/queries/resource_manager.py +21 -10
  101. infrahub/graphql/query.py +3 -1
  102. infrahub/graphql/resolvers/resolver.py +5 -1
  103. infrahub/graphql/types/task.py +14 -2
  104. infrahub/menu/generator.py +6 -18
  105. infrahub/message_bus/messages/event_node_mutated.py +2 -2
  106. infrahub/message_bus/operations/check/repository.py +2 -4
  107. infrahub/message_bus/operations/event/branch.py +2 -4
  108. infrahub/message_bus/operations/requests/proposed_change.py +1 -1
  109. infrahub/message_bus/operations/requests/repository.py +3 -5
  110. infrahub/message_bus/types.py +1 -1
  111. infrahub/permissions/__init__.py +12 -3
  112. infrahub/permissions/backend.py +2 -17
  113. infrahub/permissions/constants.py +12 -8
  114. infrahub/permissions/local_backend.py +5 -102
  115. infrahub/permissions/manager.py +135 -0
  116. infrahub/permissions/report.py +14 -25
  117. infrahub/permissions/types.py +6 -0
  118. infrahub/proposed_change/tasks.py +1 -1
  119. infrahub/task_manager/models.py +34 -5
  120. infrahub/task_manager/task.py +14 -6
  121. infrahub/visuals.py +1 -3
  122. infrahub_sdk/client.py +204 -43
  123. infrahub_sdk/ctl/cli_commands.py +106 -6
  124. infrahub_sdk/data.py +3 -2
  125. infrahub_sdk/graphql.py +5 -0
  126. infrahub_sdk/node.py +21 -2
  127. infrahub_sdk/queries.py +69 -0
  128. infrahub_sdk/schema/main.py +1 -0
  129. infrahub_sdk/testing/schemas/animal.py +1 -0
  130. infrahub_sdk/types.py +6 -0
  131. infrahub_sdk/utils.py +17 -0
  132. {infrahub_server-1.1.1.dist-info → infrahub_server-1.1.3.dist-info}/METADATA +1 -1
  133. {infrahub_server-1.1.1.dist-info → infrahub_server-1.1.3.dist-info}/RECORD +136 -131
  134. infrahub/core/diff/query/empty_roots.py +0 -33
  135. {infrahub_server-1.1.1.dist-info → infrahub_server-1.1.3.dist-info}/LICENSE.txt +0 -0
  136. {infrahub_server-1.1.1.dist-info → infrahub_server-1.1.3.dist-info}/WHEEL +0 -0
  137. {infrahub_server-1.1.1.dist-info → infrahub_server-1.1.3.dist-info}/entry_points.txt +0 -0
infrahub/api/__init__.py CHANGED
@@ -1,11 +1,13 @@
1
- from typing import NoReturn
1
+ from __future__ import annotations
2
2
 
3
- from fastapi import APIRouter
3
+ from typing import TYPE_CHECKING, NoReturn
4
+
5
+ from fastapi import APIRouter, Depends
4
6
  from fastapi.openapi.docs import (
5
7
  get_redoc_html,
6
8
  get_swagger_ui_html,
7
9
  )
8
- from starlette.responses import HTMLResponse
10
+ from starlette.responses import HTMLResponse # noqa: TC002
9
11
 
10
12
  from infrahub.api import (
11
13
  artifact,
@@ -21,8 +23,12 @@ from infrahub.api import (
21
23
  storage,
22
24
  transformation,
23
25
  )
26
+ from infrahub.api.dependencies import get_current_user
24
27
  from infrahub.exceptions import ResourceNotFoundError
25
28
 
29
+ if TYPE_CHECKING:
30
+ from infrahub.auth import AccountSession
31
+
26
32
  router = APIRouter(prefix="/api")
27
33
 
28
34
  router.include_router(artifact.router)
@@ -40,7 +46,9 @@ router.include_router(transformation.router)
40
46
 
41
47
 
42
48
  @router.get("/docs", include_in_schema=False)
43
- async def custom_swagger_ui_html() -> HTMLResponse:
49
+ async def custom_swagger_ui_html(
50
+ _: AccountSession = Depends(get_current_user),
51
+ ) -> HTMLResponse:
44
52
  return get_swagger_ui_html(
45
53
  openapi_url="/api/openapi.json",
46
54
  title="Infrahub - Swagger UI",
@@ -50,7 +58,7 @@ async def custom_swagger_ui_html() -> HTMLResponse:
50
58
 
51
59
 
52
60
  @router.get("/redoc", include_in_schema=False)
53
- async def redoc_html() -> HTMLResponse:
61
+ async def redoc_html(_: AccountSession = Depends(get_current_user)) -> HTMLResponse:
54
62
  return get_redoc_html(
55
63
  openapi_url="/api/openapi.json",
56
64
  title="Infrahub - ReDoc",
infrahub/api/artifact.py CHANGED
@@ -5,13 +5,13 @@ from typing import TYPE_CHECKING
5
5
  from fastapi import APIRouter, Body, Depends, Request, Response
6
6
  from pydantic import BaseModel, Field
7
7
 
8
- from infrahub.api.dependencies import BranchParams, get_branch_params, get_current_user, get_db
8
+ from infrahub.api.dependencies import BranchParams, get_branch_params, get_current_user, get_db, get_permission_manager
9
9
  from infrahub.core import registry
10
10
  from infrahub.core.account import ObjectPermission
11
11
  from infrahub.core.constants import GLOBAL_BRANCH_NAME, InfrahubKind, PermissionAction
12
12
  from infrahub.core.protocols import CoreArtifactDefinition
13
- from infrahub.database import InfrahubDatabase # noqa: TCH001
14
- from infrahub.exceptions import NodeNotFoundError, PermissionDeniedError
13
+ from infrahub.database import InfrahubDatabase # noqa: TC001
14
+ from infrahub.exceptions import NodeNotFoundError
15
15
  from infrahub.git.models import RequestArtifactDefinitionGenerate
16
16
  from infrahub.log import get_logger
17
17
  from infrahub.permissions.constants import PermissionDecisionFlag
@@ -19,6 +19,7 @@ from infrahub.workflows.catalogue import REQUEST_ARTIFACT_DEFINITION_GENERATE
19
19
 
20
20
  if TYPE_CHECKING:
21
21
  from infrahub.auth import AccountSession
22
+ from infrahub.permissions import PermissionManager
22
23
 
23
24
  log = get_logger()
24
25
  router = APIRouter(prefix="/artifact")
@@ -37,7 +38,7 @@ async def get_artifact(
37
38
  artifact_id: str,
38
39
  db: InfrahubDatabase = Depends(get_db),
39
40
  branch_params: BranchParams = Depends(get_branch_params),
40
- _: str = Depends(get_current_user),
41
+ _: AccountSession = Depends(get_current_user),
41
42
  ) -> Response:
42
43
  artifact = await registry.manager.get_one(db=db, id=artifact_id, branch=branch_params.branch, at=branch_params.at)
43
44
  if not artifact:
@@ -61,25 +62,18 @@ async def generate_artifact(
61
62
  ),
62
63
  db: InfrahubDatabase = Depends(get_db),
63
64
  branch_params: BranchParams = Depends(get_branch_params),
64
- account_session: AccountSession = Depends(get_current_user),
65
+ permission_manager: PermissionManager = Depends(get_permission_manager),
65
66
  ) -> None:
66
67
  permission_decision = (
67
68
  PermissionDecisionFlag.ALLOW_DEFAULT
68
69
  if branch_params.branch.name in (GLOBAL_BRANCH_NAME, registry.default_branch)
69
70
  else PermissionDecisionFlag.ALLOW_OTHER
70
71
  )
71
- for permission in [
72
+ permissions = [
72
73
  ObjectPermission(namespace="Core", name="Artifact", action=action.value, decision=permission_decision)
73
74
  for action in (PermissionAction.CREATE, PermissionAction.UPDATE)
74
- ]:
75
- has_permission = False
76
- for permission_backend in registry.permission_backends:
77
- if has_permission := await permission_backend.has_permission(
78
- db=db, account_session=account_session, permission=permission, branch=branch_params.branch
79
- ):
80
- break
81
- if not has_permission:
82
- raise PermissionDeniedError(f"You do not have the following permission: {permission}")
75
+ ]
76
+ permission_manager.raise_for_permissions(permissions=permissions)
83
77
 
84
78
  # Verify that the artifact definition exists for the requested branch
85
79
  artifact_definition = await registry.manager.get_one_by_id_or_default_filter(
infrahub/api/auth.py CHANGED
@@ -1,3 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from fastapi import APIRouter, Depends, Response
2
6
 
3
7
  from infrahub import config, models
@@ -8,7 +12,9 @@ from infrahub.auth import (
8
12
  create_fresh_access_token,
9
13
  invalidate_refresh_token,
10
14
  )
11
- from infrahub.database import InfrahubDatabase
15
+
16
+ if TYPE_CHECKING:
17
+ from infrahub.database import InfrahubDatabase
12
18
 
13
19
  router = APIRouter(prefix="/auth")
14
20
 
@@ -8,11 +8,12 @@ from pydantic import BaseModel, ConfigDict
8
8
 
9
9
  from infrahub import config
10
10
  from infrahub.auth import AccountSession, authentication_token, validate_jwt_access_token, validate_jwt_refresh_token
11
- from infrahub.core.branch import Branch # noqa: TCH001
11
+ from infrahub.core.branch import Branch # noqa: TC001
12
12
  from infrahub.core.registry import registry
13
13
  from infrahub.core.timestamp import Timestamp
14
- from infrahub.database import InfrahubDatabase # noqa: TCH001
14
+ from infrahub.database import InfrahubDatabase # noqa: TC001
15
15
  from infrahub.exceptions import AuthorizationError
16
+ from infrahub.permissions import PermissionManager
16
17
 
17
18
  if TYPE_CHECKING:
18
19
  from neo4j import AsyncSession
@@ -120,3 +121,15 @@ async def get_current_user(
120
121
  return account_session
121
122
 
122
123
  raise AuthorizationError("Authentication is required")
124
+
125
+
126
+ async def get_permission_manager(
127
+ db: InfrahubDatabase = Depends(get_db),
128
+ branch_params: BranchParams = Depends(get_branch_params),
129
+ account_session: AccountSession = Depends(get_current_user),
130
+ ) -> PermissionManager:
131
+ """Return a `PermissionManager` for an account session based on a branch."""
132
+ permission_manager = PermissionManager(account_session=account_session)
133
+ await permission_manager.load_permissions(db=db, branch=branch_params.branch)
134
+
135
+ return permission_manager
infrahub/api/diff/diff.py CHANGED
@@ -1,13 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections import defaultdict
4
- from typing import TYPE_CHECKING, Optional
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  from fastapi import APIRouter, Depends, Request
7
7
 
8
8
  from infrahub.api.dependencies import get_branch_dep, get_current_user, get_db
9
9
  from infrahub.core import registry
10
- from infrahub.core.branch import Branch # noqa: TCH001
11
10
  from infrahub.core.diff.artifacts.calculator import ArtifactDiffCalculator
12
11
  from infrahub.core.diff.branch_differ import BranchDiffer
13
12
  from infrahub.core.diff.model.diff import (
@@ -15,9 +14,11 @@ from infrahub.core.diff.model.diff import (
15
14
  BranchDiffFile,
16
15
  BranchDiffRepository,
17
16
  )
18
- from infrahub.database import InfrahubDatabase # noqa: TCH001
19
17
 
20
18
  if TYPE_CHECKING:
19
+ from infrahub.auth import AccountSession
20
+ from infrahub.core.branch import Branch
21
+ from infrahub.database import InfrahubDatabase
21
22
  from infrahub.services import InfrahubServices
22
23
 
23
24
 
@@ -29,17 +30,22 @@ async def get_diff_files(
29
30
  request: Request,
30
31
  db: InfrahubDatabase = Depends(get_db),
31
32
  branch: Branch = Depends(get_branch_dep),
32
- time_from: Optional[str] = None,
33
- time_to: Optional[str] = None,
33
+ time_from: str | None = None,
34
+ time_to: str | None = None,
34
35
  branch_only: bool = True,
35
- _: str = Depends(get_current_user),
36
+ _: AccountSession = Depends(get_current_user),
36
37
  ) -> dict[str, dict[str, BranchDiffRepository]]:
37
38
  response: dict[str, dict[str, BranchDiffRepository]] = defaultdict(dict)
38
39
  service: InfrahubServices = request.app.state.service
39
40
 
40
41
  # Query the Diff for all files and repository from the database
41
42
  diff = await BranchDiffer.init(
42
- db=db, branch=branch, diff_from=time_from, diff_to=time_to, branch_only=branch_only, service=service
43
+ db=db,
44
+ branch=branch,
45
+ diff_from=time_from,
46
+ diff_to=time_to,
47
+ branch_only=branch_only,
48
+ service=service,
43
49
  )
44
50
  diff_files = await diff.get_files()
45
51
 
infrahub/api/file.py CHANGED
@@ -1,19 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Optional, Union
3
+ from typing import TYPE_CHECKING
4
4
 
5
5
  from fastapi import APIRouter, Depends, Request
6
6
  from starlette.responses import PlainTextResponse
7
7
 
8
- from infrahub.api.dependencies import (
9
- BranchParams,
10
- get_branch_params,
11
- get_current_user,
12
- get_db,
13
- )
8
+ from infrahub.api.dependencies import BranchParams, get_branch_params, get_current_user, get_db
14
9
  from infrahub.core.constants import InfrahubKind
15
10
  from infrahub.core.manager import NodeManager
16
- from infrahub.database import InfrahubDatabase # noqa: TCH001
11
+ from infrahub.database import InfrahubDatabase # noqa: TC001
17
12
  from infrahub.exceptions import CommitNotFoundError, PropagatedFromWorkerError
18
13
  from infrahub.message_bus.messages import GitFileGet, GitFileGetResponse
19
14
 
@@ -32,13 +27,13 @@ async def get_file(
32
27
  file_path: str,
33
28
  branch_params: BranchParams = Depends(get_branch_params),
34
29
  db: InfrahubDatabase = Depends(get_db),
35
- commit: Optional[str] = None,
30
+ commit: str | None = None,
36
31
  _: str = Depends(get_current_user),
37
32
  ) -> PlainTextResponse:
38
33
  """Retrieve a file from a git repository."""
39
34
  service: InfrahubServices = request.app.state.service
40
35
 
41
- repo: Union[CoreRepository, CoreReadOnlyRepository] = await NodeManager.get_one_by_id_or_default_filter(
36
+ repo: CoreRepository | CoreReadOnlyRepository = await NodeManager.get_one_by_id_or_default_filter(
42
37
  db=db,
43
38
  id=repository_id,
44
39
  kind=InfrahubKind.GENERICREPOSITORY,
infrahub/api/internal.py CHANGED
@@ -1,16 +1,27 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
- from typing import Optional
4
+ from typing import TYPE_CHECKING
3
5
 
4
6
  import ujson
5
- from fastapi import APIRouter, Request
7
+ from fastapi import APIRouter, Depends, Request
6
8
  from lunr.index import Index
7
9
  from pydantic import BaseModel
8
10
 
9
11
  from infrahub import config
10
- from infrahub.config import AnalyticsSettings, ExperimentalFeaturesSettings, LoggingSettings, MainSettings
12
+ from infrahub.api.dependencies import get_current_user
13
+ from infrahub.config import ( # noqa: TC001
14
+ AnalyticsSettings,
15
+ ExperimentalFeaturesSettings,
16
+ LoggingSettings,
17
+ MainSettings,
18
+ )
11
19
  from infrahub.core import registry
12
20
  from infrahub.exceptions import NodeNotFoundError
13
21
 
22
+ if TYPE_CHECKING:
23
+ from infrahub.auth import AccountSession
24
+
14
25
  router = APIRouter()
15
26
 
16
27
 
@@ -39,7 +50,7 @@ async def get_config() -> ConfigAPI:
39
50
 
40
51
 
41
52
  @router.get("/info")
42
- async def get_info(request: Request) -> InfoAPI:
53
+ async def get_info(request: Request, _: AccountSession = Depends(get_current_user)) -> InfoAPI:
43
54
  return InfoAPI(deployment_id=str(registry.id), version=request.app.version)
44
55
 
45
56
 
@@ -47,7 +58,7 @@ class SearchDocs:
47
58
  def __init__(self) -> None:
48
59
  self._title_documents: list[dict] = []
49
60
  self._heading_documents: list[dict] = []
50
- self._heading_index: Optional[Index] = None
61
+ self._heading_index: Index | None = None
51
62
 
52
63
  def _load_json(self) -> None:
53
64
  """
@@ -142,7 +153,9 @@ class SearchResultAPI(BaseModel):
142
153
 
143
154
 
144
155
  @router.get("/search/docs", include_in_schema=False)
145
- async def search_docs(query: str, limit: Optional[int] = None) -> list[SearchResultAPI]:
156
+ async def search_docs(
157
+ query: str, limit: int | None = None, _: AccountSession = Depends(get_current_user)
158
+ ) -> list[SearchResultAPI]:
146
159
  smart_query = smart_queries(query)
147
160
  search_results = search_docs_loader.heading_index.search(smart_query)
148
161
  heading_results = [
infrahub/api/menu.py CHANGED
@@ -4,17 +4,17 @@ from typing import TYPE_CHECKING
4
4
 
5
5
  from fastapi import APIRouter, Depends
6
6
 
7
- from infrahub.api.dependencies import get_branch_dep, get_current_user, get_db
7
+ from infrahub.api.dependencies import get_branch_dep, get_db, get_permission_manager
8
8
  from infrahub.core import registry
9
- from infrahub.core.branch import Branch # noqa: TCH001
9
+ from infrahub.core.branch import Branch # noqa: TC001
10
10
  from infrahub.core.protocols import CoreMenuItem
11
11
  from infrahub.log import get_logger
12
12
  from infrahub.menu.generator import generate_restricted_menu
13
- from infrahub.menu.models import Menu # noqa: TCH001
13
+ from infrahub.menu.models import Menu # noqa: TC001
14
14
 
15
15
  if TYPE_CHECKING:
16
- from infrahub.auth import AccountSession
17
16
  from infrahub.database import InfrahubDatabase
17
+ from infrahub.permissions import PermissionManager
18
18
 
19
19
 
20
20
  log = get_logger()
@@ -25,10 +25,12 @@ router = APIRouter(prefix="/menu")
25
25
  async def get_menu(
26
26
  db: InfrahubDatabase = Depends(get_db),
27
27
  branch: Branch = Depends(get_branch_dep),
28
- account_session: AccountSession = Depends(get_current_user),
28
+ permission_manager: PermissionManager = Depends(get_permission_manager),
29
29
  ) -> Menu:
30
30
  log.info("menu_request", branch=branch.name)
31
31
 
32
32
  menu_items = await registry.manager.query(db=db, schema=CoreMenuItem, branch=branch, prefetch_relationships=True)
33
- menu = await generate_restricted_menu(db=db, branch=branch, account=account_session, menu_items=menu_items)
33
+ menu = await generate_restricted_menu(
34
+ db=db, branch=branch, menu_items=menu_items, account_permissions=permission_manager
35
+ )
34
36
  return menu.to_rest()
infrahub/api/oauth2.py CHANGED
@@ -3,9 +3,11 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
  from urllib.parse import urljoin
5
5
 
6
+ import ujson
6
7
  from authlib.integrations.httpx_client import AsyncOAuth2Client
7
8
  from fastapi import APIRouter, Depends, Request, Response
8
9
  from fastapi.responses import JSONResponse, RedirectResponse
10
+ from opentelemetry import trace
9
11
 
10
12
  from infrahub import config, models
11
13
  from infrahub.api.dependencies import get_db
@@ -20,27 +22,34 @@ if TYPE_CHECKING:
20
22
  from infrahub.database import InfrahubDatabase
21
23
  from infrahub.services import InfrahubServices
22
24
 
25
+ # pylint: disable=R0801
26
+
23
27
  log = get_logger()
24
28
  router = APIRouter(prefix="/oauth2")
25
29
 
26
30
 
27
31
  def _get_redirect_url(request: Request, provider_name: str) -> str:
28
- """This function is mostly to support local development when the frontend runs on different ports compared to the API."""
29
- base_url = config.SETTINGS.dev.frontend_url or str(request.base_url)
32
+ """Return public redirect URL."""
33
+ base_url = config.SETTINGS.main.public_url or str(request.base_url)
30
34
  return urljoin(base_url, f"auth/oauth2/{provider_name}/callback")
31
35
 
32
36
 
33
37
  @router.get("/{provider_name:str}/authorize")
34
38
  async def authorize(request: Request, provider_name: str, final_url: str | None = None) -> Response:
35
39
  provider = config.SETTINGS.security.get_oauth2_provider(provider=provider_name)
36
- client = AsyncOAuth2Client(
37
- client_id=provider.client_id,
38
- client_secret=provider.client_secret,
39
- scope=provider.scopes,
40
- )
40
+
41
+ with trace.get_tracer(__name__).start_as_current_span("sso_oauth2_client_configuration") as span:
42
+ span.set_attribute("provider_name", provider_name)
43
+ span.set_attribute("scopes", provider.scopes)
44
+
45
+ client = AsyncOAuth2Client(
46
+ client_id=provider.client_id,
47
+ client_secret=provider.client_secret,
48
+ scope=provider.scopes,
49
+ )
41
50
 
42
51
  redirect_uri = _get_redirect_url(request=request, provider_name=provider_name)
43
- final_url = final_url or config.SETTINGS.dev.frontend_url or str(request.base_url)
52
+ final_url = final_url or config.SETTINGS.main.public_url or str(request.base_url)
44
53
 
45
54
  authorization_uri, state = client.create_authorization_url(
46
55
  url=provider.authorization_url, redirect_uri=redirect_uri, scope=provider.scopes, final_url=final_url
@@ -88,7 +97,10 @@ async def token(
88
97
 
89
98
  token_response = await service.http.post(provider.token_url, data=token_data)
90
99
  _validate_response(response=token_response)
91
- payload = token_response.json()
100
+
101
+ with trace.get_tracer(__name__).start_as_current_span("sso_token_request") as span:
102
+ span.set_attribute("token_request_data", ujson.dumps(token_response.json()))
103
+ payload = token_response.json()
92
104
 
93
105
  headers = {"Authorization": f"{payload.get('token_type')} {payload.get('access_token')}"}
94
106
  if provider.userinfo_method == config.UserInfoMethod.GET:
@@ -102,7 +114,10 @@ async def token(
102
114
  if not sso_groups and config.SETTINGS.security.sso_user_default_group:
103
115
  sso_groups = [config.SETTINGS.security.sso_user_default_group]
104
116
 
105
- user_token = await signin_sso_account(db=db, account_name=user_info["name"], sso_groups=sso_groups)
117
+ with trace.get_tracer(__name__).start_as_current_span("signin_sso_account") as span:
118
+ span.set_attribute("account_name", ujson.dumps(userinfo_response.json()))
119
+ span.set_attribute("sso_groups", sso_groups)
120
+ user_token = await signin_sso_account(db=db, account_name=user_info["name"], sso_groups=sso_groups)
106
121
 
107
122
  response.set_cookie(
108
123
  "access_token", user_token.access_token, httponly=True, max_age=config.SETTINGS.security.access_token_lifetime
infrahub/api/oidc.py CHANGED
@@ -3,9 +3,11 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
  from urllib.parse import urljoin
5
5
 
6
+ import ujson
6
7
  from authlib.integrations.httpx_client import AsyncOAuth2Client
7
8
  from fastapi import APIRouter, Depends, Request, Response
8
9
  from fastapi.responses import JSONResponse, RedirectResponse
10
+ from opentelemetry import trace
9
11
  from pydantic import BaseModel, HttpUrl
10
12
 
11
13
  from infrahub import config, models
@@ -21,6 +23,8 @@ if TYPE_CHECKING:
21
23
  from infrahub.database import InfrahubDatabase
22
24
  from infrahub.services import InfrahubServices
23
25
 
26
+ # pylint: disable=R0801
27
+
24
28
  log = get_logger()
25
29
  router = APIRouter(prefix="/oidc")
26
30
 
@@ -54,8 +58,8 @@ class OIDCDiscoveryConfig(BaseModel):
54
58
 
55
59
 
56
60
  def _get_redirect_url(request: Request, provider_name: str) -> str:
57
- """This function is mostly to support local development when the frontend runs on different ports compared to the API."""
58
- base_url = config.SETTINGS.dev.frontend_url or str(request.base_url)
61
+ """Return public redirect URL."""
62
+ base_url = config.SETTINGS.main.public_url or str(request.base_url)
59
63
  return urljoin(base_url, f"auth/oidc/{provider_name}/callback")
60
64
 
61
65
 
@@ -68,14 +72,19 @@ async def authorize(request: Request, provider_name: str, final_url: str | None
68
72
  _validate_response(response=response)
69
73
  oidc_config = OIDCDiscoveryConfig(**response.json())
70
74
 
71
- client = AsyncOAuth2Client(
72
- client_id=provider.client_id,
73
- client_secret=provider.client_secret,
74
- scope=provider.scopes,
75
- )
75
+ with trace.get_tracer(__name__).start_as_current_span("sso_oauth2_client_configuration") as span:
76
+ span.set_attribute("provider_name", provider_name)
77
+ span.set_attribute("scopes", provider.scopes)
78
+ span.set_attribute("discovery_url", provider.discovery_url)
79
+
80
+ client = AsyncOAuth2Client(
81
+ client_id=provider.client_id,
82
+ client_secret=provider.client_secret,
83
+ scope=provider.scopes,
84
+ )
76
85
 
77
86
  redirect_uri = _get_redirect_url(request=request, provider_name=provider_name)
78
- final_url = final_url or config.SETTINGS.dev.frontend_url or str(request.base_url)
87
+ final_url = final_url or config.SETTINGS.main.public_url or str(request.base_url)
79
88
 
80
89
  authorization_uri, state = client.create_authorization_url(
81
90
  url=str(oidc_config.authorization_endpoint), redirect_uri=redirect_uri, scope=provider.scopes
@@ -126,7 +135,10 @@ async def token(
126
135
 
127
136
  token_response = await service.http.post(str(oidc_config.token_endpoint), data=token_data)
128
137
  _validate_response(response=token_response)
129
- payload = token_response.json()
138
+
139
+ with trace.get_tracer(__name__).start_as_current_span("sso_token_request") as span:
140
+ span.set_attribute("token_request_data", ujson.dumps(token_response.json()))
141
+ payload = token_response.json()
130
142
 
131
143
  headers = {"Authorization": f"{payload.get('token_type')} {payload.get('access_token')}"}
132
144
 
@@ -138,10 +150,14 @@ async def token(
138
150
  _validate_response(response=userinfo_response)
139
151
  user_info = userinfo_response.json()
140
152
  sso_groups = user_info.get("groups", [])
153
+
141
154
  if not sso_groups and config.SETTINGS.security.sso_user_default_group:
142
155
  sso_groups = [config.SETTINGS.security.sso_user_default_group]
143
156
 
144
- user_token = await signin_sso_account(db=db, account_name=user_info["name"], sso_groups=sso_groups)
157
+ with trace.get_tracer(__name__).start_as_current_span("signin_sso_account") as span:
158
+ span.set_attribute("account_name", ujson.dumps(userinfo_response.json()))
159
+ span.set_attribute("sso_groups", sso_groups)
160
+ user_token = await signin_sso_account(db=db, account_name=user_info["name"], sso_groups=sso_groups)
145
161
 
146
162
  response.set_cookie(
147
163
  "access_token", user_token.access_token, httponly=True, max_age=config.SETTINGS.security.access_token_lifetime
infrahub/api/query.py CHANGED
@@ -10,7 +10,7 @@ from infrahub.api.dependencies import BranchParams, get_branch_params, get_curre
10
10
  from infrahub.core import registry
11
11
  from infrahub.core.constants import InfrahubKind
12
12
  from infrahub.core.protocols import CoreGraphQLQuery
13
- from infrahub.database import InfrahubDatabase # noqa: TCH001
13
+ from infrahub.database import InfrahubDatabase # noqa: TC001
14
14
  from infrahub.graphql.analyzer import InfrahubGraphQLQueryAnalyzer
15
15
  from infrahub.graphql.api.dependencies import build_graphql_query_permission_checker
16
16
  from infrahub.graphql.initialization import prepare_graphql_params
@@ -57,7 +57,7 @@ async def execute_query(
57
57
  db=db, id=query_id, kind=CoreGraphQLQuery, branch=branch_params.branch, at=branch_params.at
58
58
  )
59
59
 
60
- gql_params = prepare_graphql_params(
60
+ gql_params = await prepare_graphql_params(
61
61
  db=db, branch=branch_params.branch, at=branch_params.at, account_session=account_session
62
62
  )
63
63
  analyzed_query = InfrahubGraphQLQueryAnalyzer(