infrahub-server 1.1.0b2__py3-none-any.whl → 1.1.2__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.
- infrahub/api/artifact.py +8 -15
- infrahub/api/dependencies.py +15 -2
- infrahub/api/diff/diff.py +2 -2
- infrahub/api/file.py +2 -7
- infrahub/api/menu.py +9 -7
- infrahub/api/oauth2.py +3 -3
- infrahub/api/oidc.py +3 -3
- infrahub/api/query.py +4 -4
- infrahub/api/schema.py +24 -41
- infrahub/api/transformation.py +3 -3
- infrahub/auth.py +0 -2
- infrahub/cli/db.py +9 -11
- infrahub/cli/tasks.py +3 -1
- infrahub/computed_attribute/constants.py +2 -2
- infrahub/computed_attribute/models.py +20 -8
- infrahub/computed_attribute/tasks.py +136 -61
- infrahub/config.py +18 -4
- infrahub/core/account.py +8 -11
- infrahub/core/attribute.py +15 -0
- infrahub/core/branch/constants.py +2 -0
- infrahub/core/branch/models.py +3 -2
- infrahub/core/branch/tasks.py +150 -52
- infrahub/core/constraint/node/runner.py +11 -10
- infrahub/core/diff/calculator.py +4 -3
- infrahub/core/diff/combiner.py +1 -0
- infrahub/core/diff/coordinator.py +309 -105
- infrahub/core/diff/data_check_synchronizer.py +33 -4
- infrahub/core/diff/enricher/hierarchy.py +2 -0
- infrahub/core/diff/merger/merger.py +1 -1
- infrahub/core/diff/model/path.py +101 -3
- infrahub/core/diff/query/artifact.py +52 -28
- infrahub/core/diff/query/field_specifiers.py +35 -0
- infrahub/core/diff/query/roots_metadata.py +48 -0
- infrahub/core/diff/query/save.py +1 -0
- infrahub/core/diff/query_parser.py +52 -19
- infrahub/core/diff/repository/deserializer.py +7 -3
- infrahub/core/diff/repository/repository.py +98 -7
- infrahub/core/diff/tasks.py +29 -25
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/constraints.py +4 -2
- infrahub/core/integrity/object_conflict/conflict_recorder.py +6 -1
- infrahub/core/manager.py +157 -43
- infrahub/core/merge.py +3 -2
- infrahub/core/migrations/graph/__init__.py +4 -0
- infrahub/core/migrations/graph/m012_convert_account_generic.py +12 -6
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -5
- infrahub/core/migrations/graph/m014_remove_index_attr_value.py +1 -1
- infrahub/core/migrations/graph/m015_diff_format_update.py +1 -1
- infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +1 -1
- infrahub/core/migrations/graph/m017_add_core_profile.py +44 -0
- infrahub/core/migrations/graph/m018_uniqueness_nulls.py +101 -0
- infrahub/core/migrations/query/attribute_add.py +2 -1
- infrahub/core/migrations/query/delete_element_in_schema.py +2 -1
- infrahub/core/migrations/query/node_duplicate.py +2 -1
- infrahub/core/migrations/query/relationship_duplicate.py +2 -1
- infrahub/core/migrations/query/schema_attribute_update.py +1 -1
- infrahub/core/migrations/schema/tasks.py +2 -2
- infrahub/core/migrations/shared.py +3 -3
- infrahub/core/node/__init__.py +34 -7
- infrahub/core/node/constraints/grouped_uniqueness.py +9 -2
- infrahub/core/query/__init__.py +12 -6
- infrahub/core/query/diff.py +23 -17
- infrahub/core/query/ipam.py +9 -4
- infrahub/core/query/node.py +161 -65
- infrahub/core/query/relationship.py +19 -17
- infrahub/core/query/resource_manager.py +21 -11
- infrahub/core/query/standard_node.py +13 -19
- infrahub/core/registry.py +0 -1
- infrahub/core/relationship/model.py +17 -5
- infrahub/core/schema/definitions/internal.py +1 -1
- infrahub/core/schema/generated/attribute_schema.py +2 -2
- infrahub/core/schema/generated/base_node_schema.py +2 -2
- infrahub/core/schema/generated/relationship_schema.py +1 -1
- infrahub/core/schema/schema_branch.py +16 -48
- infrahub/core/schema/schema_branch_computed.py +15 -4
- infrahub/core/task/task_log.py +1 -1
- infrahub/core/utils.py +5 -4
- infrahub/core/validators/interface.py +1 -2
- infrahub/core/validators/shared.py +2 -2
- infrahub/core/validators/tasks.py +8 -7
- infrahub/core/validators/uniqueness/index.py +2 -4
- infrahub/core/validators/uniqueness/query.py +2 -1
- infrahub/database/__init__.py +37 -20
- infrahub/database/index.py +1 -1
- infrahub/database/neo4j.py +3 -1
- infrahub/dependencies/builder/diff/data_check_synchronizer.py +2 -0
- infrahub/events/branch_action.py +13 -14
- infrahub/events/node_action.py +10 -10
- infrahub/events/schema_action.py +6 -5
- infrahub/exceptions.py +29 -16
- infrahub/generators/tasks.py +2 -1
- infrahub/git/base.py +106 -32
- infrahub/git/integrator.py +34 -3
- infrahub/git/models.py +5 -1
- infrahub/git/repository.py +19 -65
- infrahub/git/tasks.py +43 -22
- infrahub/graphql/app.py +2 -2
- infrahub/graphql/auth/query_permission_checker/default_branch_checker.py +2 -17
- infrahub/graphql/auth/query_permission_checker/merge_operation_checker.py +1 -12
- infrahub/graphql/auth/query_permission_checker/object_permission_checker.py +6 -40
- infrahub/graphql/auth/query_permission_checker/super_admin_checker.py +5 -8
- infrahub/graphql/enums.py +2 -2
- infrahub/graphql/initialization.py +30 -8
- infrahub/graphql/loaders/node.py +82 -0
- infrahub/graphql/manager.py +10 -4
- infrahub/graphql/models.py +6 -0
- infrahub/graphql/mutations/account.py +10 -6
- infrahub/graphql/mutations/branch.py +0 -2
- infrahub/graphql/mutations/computed_attribute.py +14 -23
- infrahub/graphql/mutations/diff.py +2 -0
- infrahub/graphql/mutations/ipam.py +13 -8
- infrahub/graphql/mutations/main.py +83 -8
- infrahub/graphql/mutations/proposed_change.py +11 -20
- infrahub/graphql/mutations/repository.py +3 -0
- infrahub/graphql/mutations/resource_manager.py +3 -0
- infrahub/graphql/mutations/schema.py +8 -7
- infrahub/graphql/mutations/tasks.py +36 -37
- infrahub/graphql/permissions.py +3 -4
- infrahub/graphql/queries/account.py +2 -11
- infrahub/graphql/queries/task.py +2 -12
- infrahub/graphql/query.py +3 -1
- infrahub/graphql/{resolver.py → resolvers/resolver.py} +14 -61
- infrahub/graphql/resolvers/single_relationship.py +169 -0
- infrahub/graphql/schema.py +0 -6
- infrahub/graphql/subscription/graphql_query.py +7 -1
- infrahub/graphql/types/task.py +14 -2
- infrahub/groups/tasks.py +9 -3
- infrahub/log.py +4 -0
- infrahub/menu/generator.py +6 -18
- infrahub/message_bus/messages/check_generator_run.py +1 -0
- infrahub/message_bus/messages/event_node_mutated.py +2 -2
- infrahub/message_bus/messages/refresh_git_fetch.py +1 -0
- infrahub/message_bus/messages/request_repository_userchecks.py +2 -1
- infrahub/message_bus/operations/check/artifact.py +3 -1
- infrahub/message_bus/operations/check/generator.py +20 -2
- infrahub/message_bus/operations/check/repository.py +31 -21
- infrahub/message_bus/operations/event/branch.py +2 -4
- infrahub/message_bus/operations/git/file.py +1 -3
- infrahub/message_bus/operations/git/repository.py +7 -2
- infrahub/message_bus/operations/requests/artifact_definition.py +6 -5
- infrahub/message_bus/operations/requests/generator_definition.py +10 -6
- infrahub/message_bus/operations/requests/proposed_change.py +31 -13
- infrahub/message_bus/operations/requests/repository.py +15 -13
- infrahub/message_bus/types.py +1 -1
- infrahub/permissions/__init__.py +12 -3
- infrahub/permissions/backend.py +2 -17
- infrahub/permissions/constants.py +12 -8
- infrahub/permissions/local_backend.py +5 -102
- infrahub/permissions/manager.py +135 -0
- infrahub/permissions/report.py +14 -25
- infrahub/permissions/types.py +6 -0
- infrahub/proposed_change/tasks.py +12 -9
- infrahub/server.py +3 -1
- infrahub/services/scheduler.py +1 -3
- infrahub/task_manager/models.py +34 -5
- infrahub/task_manager/task.py +10 -6
- infrahub/tasks/dummy.py +3 -0
- infrahub/tasks/telemetry.py +26 -24
- infrahub/transformations/tasks.py +2 -0
- infrahub/visuals.py +1 -3
- infrahub/webhook/models.py +6 -2
- infrahub/webhook/tasks.py +1 -1
- infrahub/workers/infrahub_async.py +14 -0
- infrahub/workflows/catalogue.py +24 -5
- infrahub/workflows/utils.py +11 -3
- infrahub_sdk/_importer.py +13 -4
- infrahub_sdk/analyzer.py +9 -7
- infrahub_sdk/batch.py +54 -8
- infrahub_sdk/branch.py +22 -20
- infrahub_sdk/checks.py +14 -41
- infrahub_sdk/client.py +769 -575
- infrahub_sdk/code_generator.py +21 -17
- infrahub_sdk/config.py +15 -13
- infrahub_sdk/ctl/branch.py +1 -1
- infrahub_sdk/ctl/check.py +15 -11
- infrahub_sdk/ctl/cli_commands.py +122 -21
- infrahub_sdk/ctl/client.py +18 -16
- infrahub_sdk/ctl/config.py +6 -7
- infrahub_sdk/ctl/exporter.py +2 -3
- infrahub_sdk/ctl/generator.py +11 -8
- infrahub_sdk/ctl/render.py +1 -1
- infrahub_sdk/ctl/repository.py +9 -7
- infrahub_sdk/ctl/schema.py +8 -4
- infrahub_sdk/ctl/transform.py +1 -1
- infrahub_sdk/ctl/utils.py +13 -9
- infrahub_sdk/ctl/validate.py +3 -2
- infrahub_sdk/data.py +5 -4
- infrahub_sdk/exceptions.py +14 -14
- infrahub_sdk/generator.py +9 -10
- infrahub_sdk/graphql.py +9 -4
- infrahub_sdk/groups.py +1 -3
- infrahub_sdk/node.py +188 -152
- infrahub_sdk/object_store.py +5 -5
- infrahub_sdk/playback.py +11 -7
- infrahub_sdk/protocols_base.py +30 -30
- infrahub_sdk/pytest_plugin/items/base.py +21 -7
- infrahub_sdk/pytest_plugin/items/check.py +11 -7
- infrahub_sdk/pytest_plugin/items/graphql_query.py +2 -2
- infrahub_sdk/pytest_plugin/items/jinja2_transform.py +5 -5
- infrahub_sdk/pytest_plugin/items/python_transform.py +12 -7
- infrahub_sdk/pytest_plugin/loader.py +2 -2
- infrahub_sdk/pytest_plugin/models.py +10 -10
- infrahub_sdk/pytest_plugin/plugin.py +3 -2
- infrahub_sdk/pytest_plugin/utils.py +1 -1
- infrahub_sdk/queries.py +69 -0
- infrahub_sdk/query_groups.py +17 -17
- infrahub_sdk/schema/__init__.py +687 -0
- infrahub_sdk/schema/main.py +343 -0
- infrahub_sdk/schema/repository.py +237 -0
- infrahub_sdk/spec/menu.py +2 -2
- infrahub_sdk/spec/object.py +14 -10
- infrahub_sdk/store.py +21 -23
- infrahub_sdk/task_report.py +20 -22
- infrahub_sdk/testing/__init__.py +0 -0
- infrahub_sdk/testing/docker.py +18 -0
- infrahub_sdk/testing/repository.py +107 -0
- infrahub_sdk/testing/schemas/__init__.py +0 -0
- infrahub_sdk/testing/schemas/animal.py +182 -0
- infrahub_sdk/testing/schemas/car_person.py +252 -0
- infrahub_sdk/timestamp.py +1 -2
- infrahub_sdk/transfer/exporter/interface.py +3 -2
- infrahub_sdk/transfer/exporter/json.py +13 -10
- infrahub_sdk/transfer/importer/json.py +9 -6
- infrahub_sdk/transfer/schema_sorter.py +8 -4
- infrahub_sdk/transforms.py +5 -46
- infrahub_sdk/types.py +19 -10
- infrahub_sdk/utils.py +22 -5
- infrahub_sdk/uuidt.py +5 -6
- infrahub_sdk/yaml.py +8 -6
- {infrahub_server-1.1.0b2.dist-info → infrahub_server-1.1.2.dist-info}/METADATA +3 -2
- {infrahub_server-1.1.0b2.dist-info → infrahub_server-1.1.2.dist-info}/RECORD +240 -225
- infrahub_testcontainers/__init__.py +6 -0
- {infrahub/testing → infrahub_testcontainers}/container.py +28 -22
- {infrahub/testing → infrahub_testcontainers}/docker-compose.test.yml +33 -40
- {infrahub/testing → infrahub_testcontainers}/helpers.py +31 -26
- infrahub/core/diff/query/empty_roots.py +0 -33
- infrahub/graphql/mutations/task.py +0 -147
- infrahub/testing/schemas/car_person.py +0 -125
- infrahub_sdk/schema.py +0 -1080
- /infrahub/{testing → graphql/loaders}/__init__.py +0 -0
- /infrahub/{testing/schemas → graphql/resolvers}/__init__.py +0 -0
- {infrahub_server-1.1.0b2.dist-info → infrahub_server-1.1.2.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.1.0b2.dist-info → infrahub_server-1.1.2.dist-info}/WHEEL +0 -0
- {infrahub_server-1.1.0b2.dist-info → infrahub_server-1.1.2.dist-info}/entry_points.txt +0 -0
|
@@ -2,21 +2,23 @@ from typing import Optional
|
|
|
2
2
|
|
|
3
3
|
from infrahub_sdk.uuidt import UUIDT
|
|
4
4
|
from prefect import flow
|
|
5
|
+
from prefect.logging import get_run_logger
|
|
5
6
|
|
|
6
7
|
from infrahub.core.constants import InfrahubKind, ValidatorConclusion, ValidatorState
|
|
7
8
|
from infrahub.core.timestamp import Timestamp
|
|
8
9
|
from infrahub.message_bus import InfrahubMessage, Meta, messages
|
|
9
10
|
from infrahub.message_bus.types import KVTTL
|
|
10
11
|
from infrahub.services import InfrahubServices
|
|
12
|
+
from infrahub.workflows.utils import add_tags
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
@flow(
|
|
15
|
+
@flow(
|
|
16
|
+
name="generator-definition-check",
|
|
17
|
+
flow_run_name="Validate Generator selection for {message.generator_definition.definition_name}",
|
|
18
|
+
)
|
|
14
19
|
async def check(message: messages.RequestGeneratorDefinitionCheck, service: InfrahubServices) -> None:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
generator_definition=message.generator_definition.definition_id,
|
|
18
|
-
source_branch=message.source_branch,
|
|
19
|
-
)
|
|
20
|
+
log = get_run_logger()
|
|
21
|
+
await add_tags(branches=[message.source_branch], nodes=[message.proposed_change])
|
|
20
22
|
events: list[InfrahubMessage] = []
|
|
21
23
|
|
|
22
24
|
proposed_change = await service.client.get(kind=InfrahubKind.PROPOSEDCHANGE, id=message.proposed_change)
|
|
@@ -87,6 +89,7 @@ async def check(message: messages.RequestGeneratorDefinitionCheck, service: Infr
|
|
|
87
89
|
check_execution_id = str(UUIDT())
|
|
88
90
|
check_execution_ids.append(check_execution_id)
|
|
89
91
|
requested_instances += 1
|
|
92
|
+
log.info(f"Trigger execution of {message.generator_definition.definition_name} for {member.display_label}")
|
|
90
93
|
events.append(
|
|
91
94
|
messages.CheckGeneratorRun(
|
|
92
95
|
generator_definition=message.generator_definition,
|
|
@@ -101,6 +104,7 @@ async def check(message: messages.RequestGeneratorDefinitionCheck, service: Infr
|
|
|
101
104
|
target_id=member.id,
|
|
102
105
|
target_name=member.display_label,
|
|
103
106
|
validator_id=validator.id,
|
|
107
|
+
proposed_change=message.proposed_change,
|
|
104
108
|
meta=Meta(validator_execution_id=validator_execution_id, check_execution_id=check_execution_id),
|
|
105
109
|
)
|
|
106
110
|
)
|
|
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from enum import IntFlag
|
|
4
4
|
|
|
5
|
-
from prefect import flow
|
|
5
|
+
from prefect import flow, task
|
|
6
|
+
from prefect.logging import get_run_logger
|
|
6
7
|
from pydantic import BaseModel
|
|
7
8
|
|
|
8
9
|
from infrahub import lock
|
|
@@ -11,7 +12,6 @@ from infrahub.core.diff.coordinator import DiffCoordinator
|
|
|
11
12
|
from infrahub.core.registry import registry
|
|
12
13
|
from infrahub.dependencies.registry import get_component_registry
|
|
13
14
|
from infrahub.git.repository import InfrahubRepository
|
|
14
|
-
from infrahub.log import get_logger
|
|
15
15
|
from infrahub.message_bus import InfrahubMessage, messages
|
|
16
16
|
from infrahub.message_bus.types import (
|
|
17
17
|
ProposedChangeArtifactDefinition,
|
|
@@ -26,7 +26,7 @@ from infrahub.proposed_change.models import (
|
|
|
26
26
|
RequestProposedChangeSchemaIntegrity,
|
|
27
27
|
RequestProposedChangeUserTests,
|
|
28
28
|
)
|
|
29
|
-
from infrahub.services import InfrahubServices # noqa:
|
|
29
|
+
from infrahub.services import InfrahubServices # noqa: TC001
|
|
30
30
|
from infrahub.workflows.catalogue import (
|
|
31
31
|
REQUEST_PROPOSED_CHANGE_DATA_INTEGRITY,
|
|
32
32
|
REQUEST_PROPOSED_CHANGE_REPOSITORY_CHECKS,
|
|
@@ -34,8 +34,7 @@ from infrahub.workflows.catalogue import (
|
|
|
34
34
|
REQUEST_PROPOSED_CHANGE_SCHEMA_INTEGRITY,
|
|
35
35
|
REQUEST_PROPOSED_CHANGE_USER_TESTS,
|
|
36
36
|
)
|
|
37
|
-
|
|
38
|
-
log = get_logger()
|
|
37
|
+
from infrahub.workflows.utils import add_tags
|
|
39
38
|
|
|
40
39
|
|
|
41
40
|
class DefinitionSelect(IntFlag):
|
|
@@ -64,13 +63,15 @@ class DefinitionSelect(IntFlag):
|
|
|
64
63
|
return "Doesn't require changes due to no relevant modified kinds or file changes in Git"
|
|
65
64
|
|
|
66
65
|
|
|
67
|
-
@flow(name="proposed-changed-pipeline")
|
|
66
|
+
@flow(name="proposed-changed-pipeline", flow_run_name="Execute Pipeline")
|
|
68
67
|
async def pipeline(message: messages.RequestProposedChangePipeline, service: InfrahubServices) -> None:
|
|
69
68
|
events: list[InfrahubMessage] = []
|
|
70
69
|
|
|
71
70
|
repositories = await _get_proposed_change_repositories(message=message, service=service)
|
|
72
71
|
|
|
73
|
-
if message.source_branch_sync_with_git and await _validate_repository_merge_conflicts(
|
|
72
|
+
if message.source_branch_sync_with_git and await _validate_repository_merge_conflicts(
|
|
73
|
+
repositories=repositories, service=service
|
|
74
|
+
):
|
|
74
75
|
for repo in repositories:
|
|
75
76
|
if not repo.read_only and repo.internal_status == RepositoryInternalStatus.ACTIVE.value:
|
|
76
77
|
events.append(
|
|
@@ -86,7 +87,7 @@ async def pipeline(message: messages.RequestProposedChangePipeline, service: Inf
|
|
|
86
87
|
await service.send(message=event)
|
|
87
88
|
return
|
|
88
89
|
|
|
89
|
-
await _gather_repository_repository_diffs(repositories=repositories)
|
|
90
|
+
await _gather_repository_repository_diffs(repositories=repositories, service=service)
|
|
90
91
|
|
|
91
92
|
async with service.database.start_transaction() as dbt:
|
|
92
93
|
destination_branch = await registry.get_branch(db=dbt, branch=message.destination_branch)
|
|
@@ -187,9 +188,12 @@ async def pipeline(message: messages.RequestProposedChangePipeline, service: Inf
|
|
|
187
188
|
|
|
188
189
|
@flow(
|
|
189
190
|
name="proposed-changed-refresh-artifact",
|
|
190
|
-
flow_run_name="
|
|
191
|
+
flow_run_name="Trigger artifacts refresh",
|
|
191
192
|
)
|
|
192
193
|
async def refresh_artifacts(message: messages.RequestProposedChangeRefreshArtifacts, service: InfrahubServices) -> None:
|
|
194
|
+
await add_tags(branches=[message.source_branch], nodes=[message.proposed_change])
|
|
195
|
+
log = get_run_logger()
|
|
196
|
+
|
|
193
197
|
definition_information = await service.client.execute_graphql(
|
|
194
198
|
query=GATHER_ARTIFACT_DEFINITIONS,
|
|
195
199
|
branch_name=message.source_branch,
|
|
@@ -227,6 +231,7 @@ async def refresh_artifacts(message: messages.RequestProposedChangeRefreshArtifa
|
|
|
227
231
|
)
|
|
228
232
|
|
|
229
233
|
if select:
|
|
234
|
+
log.info(f"Trigger processing of {artifact_definition.definition_name}")
|
|
230
235
|
msg = messages.RequestArtifactDefinitionCheck(
|
|
231
236
|
artifact_definition=artifact_definition,
|
|
232
237
|
branch_diff=message.branch_diff,
|
|
@@ -511,26 +516,39 @@ async def _get_proposed_change_repositories(
|
|
|
511
516
|
return _parse_proposed_change_repositories(message=message, source=source_all, destination=destination_all)
|
|
512
517
|
|
|
513
518
|
|
|
514
|
-
|
|
519
|
+
@task(name="proposed-change-validate-repository-conflicts", task_run_name="Validate conflicts on repository")
|
|
520
|
+
async def _validate_repository_merge_conflicts(
|
|
521
|
+
repositories: list[ProposedChangeRepository], service: InfrahubServices
|
|
522
|
+
) -> bool:
|
|
523
|
+
log = get_run_logger()
|
|
515
524
|
conflicts = False
|
|
516
525
|
for repo in repositories:
|
|
517
526
|
if repo.has_diff and not repo.is_staging:
|
|
518
|
-
git_repo = await InfrahubRepository.init(
|
|
527
|
+
git_repo = await InfrahubRepository.init(
|
|
528
|
+
id=repo.repository_id, name=repo.repository_name, client=service.client
|
|
529
|
+
)
|
|
519
530
|
async with lock.registry.get(name=repo.repository_name, namespace="repository"):
|
|
520
531
|
repo.conflicts = await git_repo.get_conflicts(
|
|
521
532
|
source_branch=repo.source_branch, dest_branch=repo.destination_branch
|
|
522
533
|
)
|
|
523
534
|
if repo.conflicts:
|
|
535
|
+
log.info(f"{len(repo.conflicts)} conflict(s) identified on {repo.repository_name}")
|
|
524
536
|
conflicts = True
|
|
537
|
+
else:
|
|
538
|
+
log.info(f"no conflict identified for {repo.repository_name}")
|
|
525
539
|
|
|
526
540
|
return conflicts
|
|
527
541
|
|
|
528
542
|
|
|
529
|
-
async def _gather_repository_repository_diffs(
|
|
543
|
+
async def _gather_repository_repository_diffs(
|
|
544
|
+
repositories: list[ProposedChangeRepository], service: InfrahubServices
|
|
545
|
+
) -> None:
|
|
530
546
|
for repo in repositories:
|
|
531
547
|
if repo.has_diff and repo.source_commit and repo.destination_commit:
|
|
532
548
|
# TODO we need to find a way to return all files in the repo if the repo is new
|
|
533
|
-
git_repo = await InfrahubRepository.init(
|
|
549
|
+
git_repo = await InfrahubRepository.init(
|
|
550
|
+
id=repo.repository_id, name=repo.repository_name, client=service.client
|
|
551
|
+
)
|
|
534
552
|
|
|
535
553
|
files_changed: list[str] = []
|
|
536
554
|
files_added: list[str] = []
|
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
|
|
3
1
|
from infrahub_sdk.uuidt import UUIDT
|
|
4
2
|
from prefect import flow
|
|
3
|
+
from prefect.logging import get_run_logger
|
|
5
4
|
|
|
6
5
|
from infrahub.core.constants import InfrahubKind
|
|
7
6
|
from infrahub.core.timestamp import Timestamp
|
|
8
|
-
from infrahub.log import get_logger
|
|
9
7
|
from infrahub.message_bus import InfrahubMessage, messages
|
|
10
8
|
from infrahub.message_bus.types import KVTTL
|
|
11
9
|
from infrahub.services import InfrahubServices
|
|
12
|
-
|
|
13
|
-
log = get_logger()
|
|
10
|
+
from infrahub.workflows.utils import add_tags
|
|
14
11
|
|
|
15
12
|
|
|
16
|
-
@flow(name="repository-check")
|
|
13
|
+
@flow(name="repository-check", flow_run_name="Running repository checks for repository {message.repository}")
|
|
17
14
|
async def checks(message: messages.RequestRepositoryChecks, service: InfrahubServices) -> None:
|
|
18
15
|
"""Request to start validation checks on a specific repository."""
|
|
19
|
-
|
|
16
|
+
await add_tags(branches=[message.source_branch], nodes=[message.proposed_change])
|
|
17
|
+
log = get_run_logger()
|
|
20
18
|
|
|
21
|
-
events:
|
|
19
|
+
events: list[InfrahubMessage] = []
|
|
22
20
|
|
|
23
21
|
repository = await service.client.get(
|
|
24
22
|
kind=InfrahubKind.GENERICREPOSITORY, id=message.repository, branch=message.source_branch
|
|
@@ -26,7 +24,7 @@ async def checks(message: messages.RequestRepositoryChecks, service: InfrahubSer
|
|
|
26
24
|
proposed_change = await service.client.get(kind=InfrahubKind.PROPOSEDCHANGE, id=message.proposed_change)
|
|
27
25
|
|
|
28
26
|
validator_execution_id = str(UUIDT())
|
|
29
|
-
check_execution_ids:
|
|
27
|
+
check_execution_ids: list[str] = []
|
|
30
28
|
await proposed_change.validations.fetch()
|
|
31
29
|
await repository.checks.fetch()
|
|
32
30
|
|
|
@@ -76,7 +74,7 @@ async def checks(message: messages.RequestRepositoryChecks, service: InfrahubSer
|
|
|
76
74
|
)
|
|
77
75
|
|
|
78
76
|
checks_in_execution = ",".join(check_execution_ids)
|
|
79
|
-
log.info("Checks in execution
|
|
77
|
+
log.info(f"Checks in execution {checks_in_execution}")
|
|
80
78
|
await service.cache.set(
|
|
81
79
|
key=f"validator_execution_id:{validator_execution_id}:checks",
|
|
82
80
|
value=checks_in_execution,
|
|
@@ -98,14 +96,18 @@ async def checks(message: messages.RequestRepositoryChecks, service: InfrahubSer
|
|
|
98
96
|
|
|
99
97
|
@flow(
|
|
100
98
|
name="repository-users-check",
|
|
101
|
-
flow_run_name="Evaluating user-defined checks on repository {message.
|
|
99
|
+
flow_run_name="Evaluating user-defined checks on repository {message.repository_name}",
|
|
102
100
|
)
|
|
103
101
|
async def user_checks(message: messages.RequestRepositoryUserChecks, service: InfrahubServices) -> None:
|
|
104
102
|
"""Request to start validation checks on a specific repository for User-defined checks."""
|
|
105
|
-
|
|
103
|
+
|
|
104
|
+
await add_tags(branches=[message.source_branch], nodes=[message.proposed_change])
|
|
105
|
+
log = get_run_logger()
|
|
106
|
+
|
|
107
|
+
events: list[InfrahubMessage] = []
|
|
106
108
|
|
|
107
109
|
repository = await service.client.get(
|
|
108
|
-
kind=InfrahubKind.GENERICREPOSITORY, id=message.
|
|
110
|
+
kind=InfrahubKind.GENERICREPOSITORY, id=message.repository_id, branch=message.source_branch, fragment=True
|
|
109
111
|
)
|
|
110
112
|
await repository.checks.fetch()
|
|
111
113
|
|
infrahub/message_bus/types.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import re
|
|
4
4
|
from enum import Enum
|
|
5
5
|
|
|
6
|
-
from infrahub_sdk.diff import NodeDiff # noqa:
|
|
6
|
+
from infrahub_sdk.diff import NodeDiff # noqa: TC002
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
|
|
9
9
|
from infrahub.core.constants import InfrahubKind, RepositoryInternalStatus
|
infrahub/permissions/__init__.py
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
from .backend import PermissionBackend
|
|
2
|
-
from .local_backend import LocalPermissionBackend
|
|
1
|
+
from infrahub.permissions.backend import PermissionBackend
|
|
2
|
+
from infrahub.permissions.local_backend import LocalPermissionBackend
|
|
3
|
+
from infrahub.permissions.manager import PermissionManager
|
|
4
|
+
from infrahub.permissions.report import report_schema_permissions
|
|
5
|
+
from infrahub.permissions.types import AssignedPermissions
|
|
3
6
|
|
|
4
|
-
__all__ = [
|
|
7
|
+
__all__ = [
|
|
8
|
+
"AssignedPermissions",
|
|
9
|
+
"LocalPermissionBackend",
|
|
10
|
+
"PermissionBackend",
|
|
11
|
+
"PermissionManager",
|
|
12
|
+
"report_schema_permissions",
|
|
13
|
+
]
|
infrahub/permissions/backend.py
CHANGED
|
@@ -5,28 +5,13 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
|
|
6
6
|
if TYPE_CHECKING:
|
|
7
7
|
from infrahub.auth import AccountSession
|
|
8
|
-
from infrahub.core.account import GlobalPermission, ObjectPermission
|
|
9
8
|
from infrahub.core.branch import Branch
|
|
10
9
|
from infrahub.database import InfrahubDatabase
|
|
11
|
-
from infrahub.permissions.
|
|
10
|
+
from infrahub.permissions.types import AssignedPermissions
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class PermissionBackend(ABC):
|
|
15
14
|
@abstractmethod
|
|
16
15
|
async def load_permissions(
|
|
17
|
-
self, db: InfrahubDatabase,
|
|
16
|
+
self, db: InfrahubDatabase, branch: Branch, account_session: AccountSession
|
|
18
17
|
) -> AssignedPermissions: ...
|
|
19
|
-
|
|
20
|
-
@abstractmethod
|
|
21
|
-
def report_object_permission(
|
|
22
|
-
self, permissions: list[ObjectPermission], namespace: str, name: str, action: str
|
|
23
|
-
) -> PermissionDecisionFlag: ...
|
|
24
|
-
|
|
25
|
-
@abstractmethod
|
|
26
|
-
async def has_permission(
|
|
27
|
-
self,
|
|
28
|
-
db: InfrahubDatabase,
|
|
29
|
-
account_session: AccountSession,
|
|
30
|
-
permission: GlobalPermission | ObjectPermission,
|
|
31
|
-
branch: Branch,
|
|
32
|
-
) -> bool: ...
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from enum import IntFlag, StrEnum, auto
|
|
4
|
-
from typing import TYPE_CHECKING, TypedDict
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
from infrahub.core.account import GlobalPermission, ObjectPermission
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class AssignedPermissions(TypedDict):
|
|
11
|
-
global_permissions: list[GlobalPermission]
|
|
12
|
-
object_permissions: list[ObjectPermission]
|
|
5
|
+
from infrahub.core.constants import GlobalPermissions
|
|
13
6
|
|
|
14
7
|
|
|
15
8
|
class PermissionDecisionFlag(IntFlag):
|
|
@@ -26,3 +19,14 @@ class BranchRelativePermissionDecision(StrEnum):
|
|
|
26
19
|
ALLOW = auto()
|
|
27
20
|
ALLOW_DEFAULT = auto()
|
|
28
21
|
ALLOW_OTHER = auto()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
GLOBAL_PERMISSION_DENIAL_MESSAGE = {
|
|
25
|
+
GlobalPermissions.EDIT_DEFAULT_BRANCH.value: "You are not allowed to change data in the default branch",
|
|
26
|
+
GlobalPermissions.MERGE_BRANCH.value: "You are not allowed to merge a branch",
|
|
27
|
+
GlobalPermissions.MERGE_PROPOSED_CHANGE.value: "You are not allowed to merge proposed changes",
|
|
28
|
+
GlobalPermissions.MANAGE_SCHEMA.value: "You are not allowed to manage the schema",
|
|
29
|
+
GlobalPermissions.MANAGE_ACCOUNTS.value: "You are not allowed to manage user accounts, groups or roles",
|
|
30
|
+
GlobalPermissions.MANAGE_PERMISSIONS.value: "You are not allowed to manage permissions",
|
|
31
|
+
GlobalPermissions.MANAGE_REPOSITORIES.value: "You are not allowed to manage repositories",
|
|
32
|
+
}
|
|
@@ -3,11 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from infrahub import config
|
|
6
|
-
from infrahub.core.account import
|
|
7
|
-
from infrahub.core.constants import GlobalPermissions, PermissionDecision
|
|
6
|
+
from infrahub.core.account import fetch_permissions, fetch_role_permissions
|
|
8
7
|
from infrahub.core.manager import NodeManager
|
|
9
8
|
from infrahub.core.protocols import CoreAccountRole
|
|
10
|
-
from infrahub.permissions.constants import PermissionDecisionFlag
|
|
11
9
|
|
|
12
10
|
from .backend import PermissionBackend
|
|
13
11
|
|
|
@@ -15,79 +13,14 @@ if TYPE_CHECKING:
|
|
|
15
13
|
from infrahub.auth import AccountSession
|
|
16
14
|
from infrahub.core.branch import Branch
|
|
17
15
|
from infrahub.database import InfrahubDatabase
|
|
18
|
-
from infrahub.permissions.
|
|
16
|
+
from infrahub.permissions.types import AssignedPermissions
|
|
19
17
|
|
|
18
|
+
__all__ = ["LocalPermissionBackend"]
|
|
20
19
|
|
|
21
|
-
class LocalPermissionBackend(PermissionBackend):
|
|
22
|
-
wildcard_values = ["*"]
|
|
23
|
-
wildcard_actions = ["any"]
|
|
24
|
-
|
|
25
|
-
def _compute_specificity(self, permission: ObjectPermission) -> int:
|
|
26
|
-
specificity = 0
|
|
27
|
-
if permission.namespace not in self.wildcard_values:
|
|
28
|
-
specificity += 1
|
|
29
|
-
if permission.name not in self.wildcard_values:
|
|
30
|
-
specificity += 1
|
|
31
|
-
if permission.action not in self.wildcard_actions:
|
|
32
|
-
specificity += 1
|
|
33
|
-
if not permission.decision & PermissionDecisionFlag.ALLOW_ALL:
|
|
34
|
-
specificity += 1
|
|
35
|
-
return specificity
|
|
36
|
-
|
|
37
|
-
def report_object_permission(
|
|
38
|
-
self, permissions: list[ObjectPermission], namespace: str, name: str, action: str
|
|
39
|
-
) -> PermissionDecisionFlag:
|
|
40
|
-
"""Given a set of permissions, return the permission decision for a given kind and action."""
|
|
41
|
-
highest_specificity: int = -1
|
|
42
|
-
combined_decision = PermissionDecisionFlag.DENY
|
|
43
|
-
|
|
44
|
-
for permission in permissions:
|
|
45
|
-
if (
|
|
46
|
-
permission.namespace in [namespace, *self.wildcard_values]
|
|
47
|
-
and permission.name in [name, *self.wildcard_values]
|
|
48
|
-
and permission.action in [action, *self.wildcard_actions]
|
|
49
|
-
):
|
|
50
|
-
permission_decision = PermissionDecisionFlag(value=permission.decision)
|
|
51
|
-
# Compute the specifity of a permission to keep the decision of the most specific if two or more permissions overlap
|
|
52
|
-
specificity = self._compute_specificity(permission=permission)
|
|
53
|
-
if specificity > highest_specificity:
|
|
54
|
-
combined_decision = permission_decision
|
|
55
|
-
highest_specificity = specificity
|
|
56
|
-
elif specificity == highest_specificity and permission_decision != PermissionDecisionFlag.DENY:
|
|
57
|
-
combined_decision |= permission_decision
|
|
58
|
-
|
|
59
|
-
return combined_decision
|
|
60
|
-
|
|
61
|
-
def resolve_object_permission(
|
|
62
|
-
self, permissions: list[ObjectPermission], permission_to_check: ObjectPermission
|
|
63
|
-
) -> bool:
|
|
64
|
-
"""Compute the permissions and check if the one provided is allowed."""
|
|
65
|
-
required_decision = PermissionDecisionFlag(value=permission_to_check.decision)
|
|
66
|
-
combined_decision = self.report_object_permission(
|
|
67
|
-
permissions=permissions,
|
|
68
|
-
namespace=permission_to_check.namespace,
|
|
69
|
-
name=permission_to_check.name,
|
|
70
|
-
action=permission_to_check.action,
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
return combined_decision & required_decision == required_decision
|
|
74
|
-
|
|
75
|
-
def resolve_global_permission(
|
|
76
|
-
self, permissions: list[GlobalPermission], permission_to_check: GlobalPermission
|
|
77
|
-
) -> bool:
|
|
78
|
-
grant_permission = False
|
|
79
|
-
|
|
80
|
-
for permission in permissions:
|
|
81
|
-
if permission.action == permission_to_check.action:
|
|
82
|
-
# Early exit on deny as deny preempt allow
|
|
83
|
-
if permission.decision == PermissionDecisionFlag.DENY:
|
|
84
|
-
return False
|
|
85
|
-
grant_permission = True
|
|
86
|
-
|
|
87
|
-
return grant_permission
|
|
88
20
|
|
|
21
|
+
class LocalPermissionBackend(PermissionBackend):
|
|
89
22
|
async def load_permissions(
|
|
90
|
-
self, db: InfrahubDatabase,
|
|
23
|
+
self, db: InfrahubDatabase, branch: Branch, account_session: AccountSession
|
|
91
24
|
) -> AssignedPermissions:
|
|
92
25
|
if not account_session.authenticated:
|
|
93
26
|
anonymous_permissions: AssignedPermissions = {"global_permissions": [], "object_permissions": []}
|
|
@@ -103,33 +36,3 @@ class LocalPermissionBackend(PermissionBackend):
|
|
|
103
36
|
return anonymous_permissions
|
|
104
37
|
|
|
105
38
|
return await fetch_permissions(db=db, account_id=account_session.account_id, branch=branch)
|
|
106
|
-
|
|
107
|
-
async def has_permission(
|
|
108
|
-
self,
|
|
109
|
-
db: InfrahubDatabase,
|
|
110
|
-
account_session: AccountSession,
|
|
111
|
-
permission: GlobalPermission | ObjectPermission,
|
|
112
|
-
branch: Branch,
|
|
113
|
-
) -> bool:
|
|
114
|
-
granted_permissions = await self.load_permissions(db=db, account_session=account_session, branch=branch)
|
|
115
|
-
is_super_admin = self.resolve_global_permission(
|
|
116
|
-
permissions=granted_permissions["global_permissions"],
|
|
117
|
-
permission_to_check=GlobalPermission(
|
|
118
|
-
action=GlobalPermissions.SUPER_ADMIN, decision=PermissionDecision.ALLOW_ALL
|
|
119
|
-
),
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
if isinstance(permission, GlobalPermission):
|
|
123
|
-
return (
|
|
124
|
-
self.resolve_global_permission(
|
|
125
|
-
permissions=granted_permissions["global_permissions"], permission_to_check=permission
|
|
126
|
-
)
|
|
127
|
-
or is_super_admin
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
self.resolve_object_permission(
|
|
132
|
-
permissions=granted_permissions["object_permissions"], permission_to_check=permission
|
|
133
|
-
)
|
|
134
|
-
or is_super_admin
|
|
135
|
-
)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Sequence
|
|
4
|
+
|
|
5
|
+
from infrahub.core import registry
|
|
6
|
+
from infrahub.core.account import GlobalPermission
|
|
7
|
+
from infrahub.core.constants import GlobalPermissions, PermissionDecision
|
|
8
|
+
from infrahub.exceptions import PermissionDeniedError
|
|
9
|
+
from infrahub.permissions.constants import GLOBAL_PERMISSION_DENIAL_MESSAGE, PermissionDecisionFlag
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from infrahub.auth import AccountSession
|
|
13
|
+
from infrahub.core.account import ObjectPermission
|
|
14
|
+
from infrahub.core.branch import Branch
|
|
15
|
+
from infrahub.database import InfrahubDatabase
|
|
16
|
+
from infrahub.permissions.types import AssignedPermissions
|
|
17
|
+
|
|
18
|
+
__all__ = ["PermissionManager"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PermissionManager:
|
|
22
|
+
wildcard_values = ["*"]
|
|
23
|
+
wildcard_actions = ["any"]
|
|
24
|
+
|
|
25
|
+
def __init__(self, account_session: AccountSession) -> None:
|
|
26
|
+
self.account_session = account_session
|
|
27
|
+
self.permissions: AssignedPermissions = {"global_permissions": [], "object_permissions": []}
|
|
28
|
+
|
|
29
|
+
async def load_permissions(self, db: InfrahubDatabase, branch: Branch) -> None:
|
|
30
|
+
"""Load permissions from the configured backends into memory."""
|
|
31
|
+
for permission_backend in registry.permission_backends:
|
|
32
|
+
backend_permissions = await permission_backend.load_permissions(
|
|
33
|
+
db=db, branch=branch, account_session=self.account_session
|
|
34
|
+
)
|
|
35
|
+
self.permissions["global_permissions"].extend(backend_permissions["global_permissions"])
|
|
36
|
+
self.permissions["object_permissions"].extend(backend_permissions["object_permissions"])
|
|
37
|
+
|
|
38
|
+
def _compute_specificity(self, permission: ObjectPermission) -> int:
|
|
39
|
+
"""Return how specific a permission is."""
|
|
40
|
+
specificity = 0
|
|
41
|
+
if permission.namespace not in self.wildcard_values:
|
|
42
|
+
specificity += 1
|
|
43
|
+
if permission.name not in self.wildcard_values:
|
|
44
|
+
specificity += 1
|
|
45
|
+
if permission.action not in self.wildcard_actions:
|
|
46
|
+
specificity += 1
|
|
47
|
+
if not permission.decision & PermissionDecisionFlag.ALLOW_ALL:
|
|
48
|
+
specificity += 1
|
|
49
|
+
return specificity
|
|
50
|
+
|
|
51
|
+
def report_object_permission(self, namespace: str, name: str, action: str) -> PermissionDecisionFlag:
|
|
52
|
+
"""Given a set of permissions, return the permission decision for a given kind and action."""
|
|
53
|
+
highest_specificity: int = -1
|
|
54
|
+
combined_decision = PermissionDecisionFlag.DENY
|
|
55
|
+
|
|
56
|
+
for permission in self.permissions["object_permissions"]:
|
|
57
|
+
if (
|
|
58
|
+
permission.namespace in [namespace, *self.wildcard_values]
|
|
59
|
+
and permission.name in [name, *self.wildcard_values]
|
|
60
|
+
and permission.action in [action, *self.wildcard_actions]
|
|
61
|
+
):
|
|
62
|
+
permission_decision = PermissionDecisionFlag(value=permission.decision)
|
|
63
|
+
# Compute the specifity of a permission to keep the decision of the most specific if two or more permissions overlap
|
|
64
|
+
specificity = self._compute_specificity(permission=permission)
|
|
65
|
+
if specificity > highest_specificity:
|
|
66
|
+
combined_decision = permission_decision
|
|
67
|
+
highest_specificity = specificity
|
|
68
|
+
elif specificity == highest_specificity and permission_decision != PermissionDecisionFlag.DENY:
|
|
69
|
+
combined_decision |= permission_decision
|
|
70
|
+
|
|
71
|
+
return combined_decision
|
|
72
|
+
|
|
73
|
+
def resolve_object_permission(self, permission_to_check: ObjectPermission) -> bool:
|
|
74
|
+
"""Compute the permissions and check if the one provided is granted."""
|
|
75
|
+
required_decision = PermissionDecisionFlag(value=permission_to_check.decision)
|
|
76
|
+
combined_decision = self.report_object_permission(
|
|
77
|
+
namespace=permission_to_check.namespace, name=permission_to_check.name, action=permission_to_check.action
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return combined_decision & required_decision == required_decision
|
|
81
|
+
|
|
82
|
+
def resolve_global_permission(self, permission_to_check: GlobalPermission) -> bool:
|
|
83
|
+
"""Tell if a global permission is granted."""
|
|
84
|
+
grant_permission = False
|
|
85
|
+
|
|
86
|
+
for permission in self.permissions["global_permissions"]:
|
|
87
|
+
if permission.action == permission_to_check.action:
|
|
88
|
+
# Early exit on deny as deny preempt allow
|
|
89
|
+
if permission.decision == PermissionDecisionFlag.DENY:
|
|
90
|
+
return False
|
|
91
|
+
grant_permission = True
|
|
92
|
+
|
|
93
|
+
return grant_permission
|
|
94
|
+
|
|
95
|
+
def has_permission(self, permission: GlobalPermission | ObjectPermission) -> bool:
|
|
96
|
+
"""Tell if a permission is granted given the permissions loaded in memory."""
|
|
97
|
+
is_super_admin = self.resolve_global_permission(
|
|
98
|
+
permission_to_check=GlobalPermission(
|
|
99
|
+
action=GlobalPermissions.SUPER_ADMIN, decision=PermissionDecision.ALLOW_ALL
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if isinstance(permission, GlobalPermission):
|
|
104
|
+
return self.resolve_global_permission(permission_to_check=permission) or is_super_admin
|
|
105
|
+
|
|
106
|
+
return self.resolve_object_permission(permission_to_check=permission) or is_super_admin
|
|
107
|
+
|
|
108
|
+
def has_permissions(self, permissions: Sequence[GlobalPermission | ObjectPermission]) -> bool:
|
|
109
|
+
"""Same as `has_permission` but for multiple permissions, return `True` only if all permissions are granted."""
|
|
110
|
+
return all(self.has_permission(permission=permission) for permission in permissions)
|
|
111
|
+
|
|
112
|
+
def raise_for_permission(self, permission: GlobalPermission | ObjectPermission, message: str = "") -> None:
|
|
113
|
+
"""Same as `has_permission` but raise a `PermissionDeniedError` if the permission is not granted."""
|
|
114
|
+
if self.has_permission(permission=permission):
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
if not message:
|
|
118
|
+
if isinstance(permission, GlobalPermission) and permission.action in GLOBAL_PERMISSION_DENIAL_MESSAGE:
|
|
119
|
+
message = GLOBAL_PERMISSION_DENIAL_MESSAGE[permission.action]
|
|
120
|
+
else:
|
|
121
|
+
message = f"You do not have the following permission: {permission!s}"
|
|
122
|
+
|
|
123
|
+
raise PermissionDeniedError(message=message)
|
|
124
|
+
|
|
125
|
+
def raise_for_permissions(
|
|
126
|
+
self, permissions: Sequence[GlobalPermission | ObjectPermission], message: str = ""
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Same as `has_permissions` but raise a `PermissionDeniedError` if any of the permissions is not granted."""
|
|
129
|
+
if self.has_permissions(permissions=permissions):
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
if not message:
|
|
133
|
+
message = f"You do not have one of the following permissions: {' | '.join([str(p) for p in permissions])}"
|
|
134
|
+
|
|
135
|
+
raise PermissionDeniedError(message=message)
|