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.
- infrahub/api/__init__.py +13 -5
- infrahub/api/artifact.py +9 -15
- infrahub/api/auth.py +7 -1
- infrahub/api/dependencies.py +15 -2
- infrahub/api/diff/diff.py +13 -7
- infrahub/api/file.py +5 -10
- infrahub/api/internal.py +19 -6
- infrahub/api/menu.py +8 -6
- infrahub/api/oauth2.py +25 -10
- infrahub/api/oidc.py +26 -10
- infrahub/api/query.py +2 -2
- infrahub/api/schema.py +48 -59
- infrahub/api/storage.py +8 -8
- infrahub/api/transformation.py +6 -5
- infrahub/auth.py +1 -26
- infrahub/cli/__init__.py +1 -1
- infrahub/cli/context.py +5 -8
- infrahub/cli/db.py +6 -6
- infrahub/cli/git_agent.py +1 -1
- infrahub/computed_attribute/models.py +1 -1
- infrahub/computed_attribute/tasks.py +1 -1
- infrahub/config.py +5 -5
- infrahub/core/account.py +2 -10
- infrahub/core/attribute.py +22 -0
- infrahub/core/branch/models.py +1 -1
- infrahub/core/branch/tasks.py +4 -3
- infrahub/core/diff/calculator.py +14 -0
- infrahub/core/diff/combiner.py +6 -2
- infrahub/core/diff/conflicts_enricher.py +2 -2
- infrahub/core/diff/coordinator.py +296 -87
- infrahub/core/diff/data_check_synchronizer.py +33 -4
- infrahub/core/diff/enricher/cardinality_one.py +3 -3
- infrahub/core/diff/enricher/hierarchy.py +4 -1
- infrahub/core/diff/merger/merger.py +11 -1
- infrahub/core/diff/merger/serializer.py +5 -29
- infrahub/core/diff/model/path.py +88 -4
- 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 +27 -11
- infrahub/core/diff/repository/deserializer.py +7 -3
- infrahub/core/diff/repository/repository.py +100 -9
- infrahub/core/diff/tasks.py +1 -1
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/integrity/object_conflict/conflict_recorder.py +6 -1
- infrahub/core/ipam/utilization.py +6 -1
- infrahub/core/manager.py +8 -0
- infrahub/core/merge.py +6 -1
- infrahub/core/migrations/graph/__init__.py +2 -0
- 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/m018_uniqueness_nulls.py +101 -0
- infrahub/core/migrations/query/attribute_add.py +5 -5
- infrahub/core/migrations/schema/tasks.py +2 -2
- infrahub/core/migrations/shared.py +3 -3
- infrahub/core/node/__init__.py +8 -2
- infrahub/core/node/constraints/grouped_uniqueness.py +9 -2
- infrahub/core/query/__init__.py +5 -2
- infrahub/core/query/diff.py +32 -19
- infrahub/core/query/ipam.py +30 -22
- infrahub/core/query/node.py +91 -40
- 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_computed.py +1 -1
- infrahub/core/task/task_log.py +1 -1
- infrahub/core/validators/attribute/kind.py +1 -1
- infrahub/core/validators/interface.py +1 -2
- infrahub/core/validators/models/violation.py +1 -14
- infrahub/core/validators/shared.py +2 -2
- infrahub/core/validators/tasks.py +7 -4
- infrahub/core/validators/uniqueness/index.py +2 -4
- infrahub/database/index.py +1 -1
- infrahub/dependencies/builder/constraint/schema/aggregated.py +2 -0
- infrahub/dependencies/builder/constraint/schema/attribute_kind.py +8 -0
- infrahub/dependencies/builder/diff/data_check_synchronizer.py +2 -0
- infrahub/git/base.py +3 -3
- infrahub/git/integrator.py +1 -1
- infrahub/graphql/api/endpoints.py +12 -3
- 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 +27 -8
- infrahub/graphql/manager.py +9 -3
- infrahub/graphql/models.py +6 -0
- infrahub/graphql/mutations/account.py +14 -10
- infrahub/graphql/mutations/computed_attribute.py +11 -22
- infrahub/graphql/mutations/diff.py +2 -0
- infrahub/graphql/mutations/main.py +5 -16
- infrahub/graphql/mutations/proposed_change.py +11 -20
- infrahub/graphql/mutations/resource_manager.py +6 -3
- infrahub/graphql/mutations/schema.py +8 -7
- infrahub/graphql/mutations/tasks.py +1 -1
- infrahub/graphql/permissions.py +3 -4
- infrahub/graphql/queries/account.py +2 -11
- infrahub/graphql/queries/resource_manager.py +21 -10
- infrahub/graphql/query.py +3 -1
- infrahub/graphql/resolvers/resolver.py +5 -1
- infrahub/graphql/types/task.py +14 -2
- infrahub/menu/generator.py +6 -18
- infrahub/message_bus/messages/event_node_mutated.py +2 -2
- infrahub/message_bus/operations/check/repository.py +2 -4
- infrahub/message_bus/operations/event/branch.py +2 -4
- infrahub/message_bus/operations/requests/proposed_change.py +1 -1
- infrahub/message_bus/operations/requests/repository.py +3 -5
- 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 +1 -1
- infrahub/task_manager/models.py +34 -5
- infrahub/task_manager/task.py +14 -6
- infrahub/visuals.py +1 -3
- infrahub_sdk/client.py +204 -43
- infrahub_sdk/ctl/cli_commands.py +106 -6
- infrahub_sdk/data.py +3 -2
- infrahub_sdk/graphql.py +5 -0
- infrahub_sdk/node.py +21 -2
- infrahub_sdk/queries.py +69 -0
- infrahub_sdk/schema/main.py +1 -0
- infrahub_sdk/testing/schemas/animal.py +1 -0
- infrahub_sdk/types.py +6 -0
- infrahub_sdk/utils.py +17 -0
- {infrahub_server-1.1.1.dist-info → infrahub_server-1.1.3.dist-info}/METADATA +1 -1
- {infrahub_server-1.1.1.dist-info → infrahub_server-1.1.3.dist-info}/RECORD +136 -131
- infrahub/core/diff/query/empty_roots.py +0 -33
- {infrahub_server-1.1.1.dist-info → infrahub_server-1.1.3.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.1.1.dist-info → infrahub_server-1.1.3.dist-info}/WHEEL +0 -0
- {infrahub_server-1.1.1.dist-info → infrahub_server-1.1.3.dist-info}/entry_points.txt +0 -0
|
@@ -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)
|
infrahub/permissions/report.py
CHANGED
|
@@ -6,21 +6,20 @@ from infrahub.core import registry
|
|
|
6
6
|
from infrahub.core.account import GlobalPermission
|
|
7
7
|
from infrahub.core.constants import GLOBAL_BRANCH_NAME, GlobalPermissions, InfrahubKind, PermissionDecision
|
|
8
8
|
from infrahub.core.schema.node_schema import NodeSchema
|
|
9
|
-
from infrahub.permissions.constants import
|
|
10
|
-
from infrahub.permissions.local_backend import LocalPermissionBackend
|
|
9
|
+
from infrahub.permissions.constants import BranchRelativePermissionDecision, PermissionDecisionFlag
|
|
11
10
|
|
|
12
11
|
if TYPE_CHECKING:
|
|
13
|
-
from infrahub.auth import AccountSession
|
|
14
12
|
from infrahub.core.branch import Branch
|
|
15
13
|
from infrahub.core.schema import MainSchemaTypes
|
|
16
|
-
from infrahub.
|
|
17
|
-
from infrahub.permissions.backend import PermissionBackend
|
|
14
|
+
from infrahub.permissions.manager import PermissionManager
|
|
18
15
|
from infrahub.permissions.types import KindPermissions
|
|
19
16
|
|
|
20
17
|
|
|
18
|
+
__all__ = ["report_schema_permissions"]
|
|
19
|
+
|
|
20
|
+
|
|
21
21
|
def get_permission_report( # noqa: PLR0911
|
|
22
|
-
|
|
23
|
-
permissions: AssignedPermissions,
|
|
22
|
+
permission_manager: PermissionManager,
|
|
24
23
|
branch: Branch,
|
|
25
24
|
node: MainSchemaTypes,
|
|
26
25
|
action: str,
|
|
@@ -56,9 +55,7 @@ def get_permission_report( # noqa: PLR0911
|
|
|
56
55
|
)
|
|
57
56
|
|
|
58
57
|
is_default_branch = branch.name in (GLOBAL_BRANCH_NAME, registry.default_branch)
|
|
59
|
-
decision =
|
|
60
|
-
permissions=permissions["object_permissions"], namespace=node.namespace, name=node.name, action=action
|
|
61
|
-
)
|
|
58
|
+
decision = permission_manager.report_object_permission(namespace=node.namespace, name=node.name, action=action)
|
|
62
59
|
|
|
63
60
|
if (
|
|
64
61
|
decision == PermissionDecisionFlag.ALLOW_ALL
|
|
@@ -75,16 +72,12 @@ def get_permission_report( # noqa: PLR0911
|
|
|
75
72
|
|
|
76
73
|
|
|
77
74
|
async def report_schema_permissions(
|
|
78
|
-
|
|
75
|
+
branch: Branch, permission_manager: PermissionManager, schemas: list[MainSchemaTypes]
|
|
79
76
|
) -> list[KindPermissions]:
|
|
80
|
-
perm_backend = LocalPermissionBackend()
|
|
81
|
-
permissions = await perm_backend.load_permissions(db=db, account_session=account_session, branch=branch)
|
|
82
|
-
|
|
83
77
|
global_permission_report: dict[GlobalPermissions, bool] = {}
|
|
84
78
|
for perm in GlobalPermissions:
|
|
85
|
-
global_permission_report[perm] =
|
|
86
|
-
|
|
87
|
-
permission_to_check=GlobalPermission(action=perm.value, decision=PermissionDecision.ALLOW_ALL.value),
|
|
79
|
+
global_permission_report[perm] = permission_manager.resolve_global_permission(
|
|
80
|
+
permission_to_check=GlobalPermission(action=perm.value, decision=PermissionDecision.ALLOW_ALL.value)
|
|
88
81
|
)
|
|
89
82
|
|
|
90
83
|
permission_objects: list[KindPermissions] = []
|
|
@@ -93,32 +86,28 @@ async def report_schema_permissions(
|
|
|
93
86
|
{
|
|
94
87
|
"kind": node.kind,
|
|
95
88
|
"create": get_permission_report(
|
|
96
|
-
|
|
97
|
-
permissions=permissions,
|
|
89
|
+
permission_manager=permission_manager,
|
|
98
90
|
branch=branch,
|
|
99
91
|
node=node,
|
|
100
92
|
action="create",
|
|
101
93
|
global_permission_report=global_permission_report,
|
|
102
94
|
),
|
|
103
95
|
"delete": get_permission_report(
|
|
104
|
-
|
|
105
|
-
permissions=permissions,
|
|
96
|
+
permission_manager=permission_manager,
|
|
106
97
|
branch=branch,
|
|
107
98
|
node=node,
|
|
108
99
|
action="delete",
|
|
109
100
|
global_permission_report=global_permission_report,
|
|
110
101
|
),
|
|
111
102
|
"update": get_permission_report(
|
|
112
|
-
|
|
113
|
-
permissions=permissions,
|
|
103
|
+
permission_manager=permission_manager,
|
|
114
104
|
branch=branch,
|
|
115
105
|
node=node,
|
|
116
106
|
action="update",
|
|
117
107
|
global_permission_report=global_permission_report,
|
|
118
108
|
),
|
|
119
109
|
"view": get_permission_report(
|
|
120
|
-
|
|
121
|
-
permissions=permissions,
|
|
110
|
+
permission_manager=permission_manager,
|
|
122
111
|
branch=branch,
|
|
123
112
|
node=node,
|
|
124
113
|
action="view",
|
infrahub/permissions/types.py
CHANGED
|
@@ -3,9 +3,15 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING, TypedDict
|
|
4
4
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
|
+
from infrahub.core.account import GlobalPermission, ObjectPermission
|
|
6
7
|
from infrahub.permissions.constants import BranchRelativePermissionDecision
|
|
7
8
|
|
|
8
9
|
|
|
10
|
+
class AssignedPermissions(TypedDict):
|
|
11
|
+
global_permissions: list[GlobalPermission]
|
|
12
|
+
object_permissions: list[ObjectPermission]
|
|
13
|
+
|
|
14
|
+
|
|
9
15
|
class KindPermissions(TypedDict):
|
|
10
16
|
kind: str
|
|
11
17
|
create: BranchRelativePermissionDecision
|
|
@@ -11,7 +11,7 @@ from infrahub_sdk.protocols import CoreGeneratorDefinition, CoreProposedChange
|
|
|
11
11
|
from prefect import flow, task
|
|
12
12
|
from prefect.cache_policies import NONE
|
|
13
13
|
from prefect.client.schemas.objects import (
|
|
14
|
-
State, # noqa:
|
|
14
|
+
State, # noqa: TC002
|
|
15
15
|
)
|
|
16
16
|
from prefect.logging import get_run_logger
|
|
17
17
|
from prefect.states import Completed, Failed
|
infrahub/task_manager/models.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
|
-
from typing import DefaultDict
|
|
3
2
|
from uuid import UUID
|
|
4
3
|
|
|
5
4
|
from prefect.client.schemas.objects import Log as PrefectLog
|
|
@@ -8,16 +7,46 @@ from pydantic import BaseModel, Field
|
|
|
8
7
|
from .constants import LOG_LEVEL_MAPPING
|
|
9
8
|
|
|
10
9
|
|
|
10
|
+
class RelatedNodeInfo(BaseModel):
|
|
11
|
+
id: str
|
|
12
|
+
kind: str | None = None
|
|
13
|
+
|
|
14
|
+
|
|
11
15
|
class RelatedNodesInfo(BaseModel):
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
flows: dict[UUID, dict[str, RelatedNodeInfo]] = Field(default_factory=lambda: defaultdict(dict))
|
|
17
|
+
nodes: dict[str, RelatedNodeInfo] = Field(default_factory=dict)
|
|
18
|
+
|
|
19
|
+
def add_nodes(self, flow_id: UUID, node_ids: list[str]) -> None:
|
|
20
|
+
for node_id in node_ids:
|
|
21
|
+
self.add_node(flow_id=flow_id, node_id=node_id)
|
|
22
|
+
|
|
23
|
+
def add_node(self, flow_id: UUID, node_id: str) -> None:
|
|
24
|
+
if node_id not in self.nodes:
|
|
25
|
+
node = RelatedNodeInfo(id=node_id)
|
|
26
|
+
self.nodes[node_id] = node
|
|
27
|
+
self.flows[flow_id][node_id] = self.nodes[node_id]
|
|
28
|
+
|
|
29
|
+
def get_related_nodes(self, flow_id: UUID) -> list[RelatedNodeInfo]:
|
|
30
|
+
if flow_id not in self.flows or len(self.flows[flow_id].keys()) == 0:
|
|
31
|
+
return []
|
|
32
|
+
return list(self.flows[flow_id].values())
|
|
33
|
+
|
|
34
|
+
def get_related_nodes_as_dict(self, flow_id: UUID) -> list[dict[str, str | None]]:
|
|
35
|
+
if flow_id not in self.flows or len(self.flows[flow_id].keys()) == 0:
|
|
36
|
+
return []
|
|
37
|
+
return [item.model_dump() for item in list(self.flows[flow_id].values())]
|
|
38
|
+
|
|
39
|
+
def get_first_related_node(self, flow_id: UUID) -> RelatedNodeInfo | None:
|
|
40
|
+
if nodes := self.get_related_nodes(flow_id=flow_id):
|
|
41
|
+
return nodes[0]
|
|
42
|
+
return None
|
|
14
43
|
|
|
15
44
|
def get_unique_related_node_ids(self) -> list[str]:
|
|
16
|
-
return list(
|
|
45
|
+
return list(self.nodes.keys())
|
|
17
46
|
|
|
18
47
|
|
|
19
48
|
class FlowLogs(BaseModel):
|
|
20
|
-
logs:
|
|
49
|
+
logs: defaultdict[UUID, list[PrefectLog]] = Field(default_factory=lambda: defaultdict(list))
|
|
21
50
|
|
|
22
51
|
def to_graphql(self, flow_id: UUID) -> list[dict]:
|
|
23
52
|
return [
|
infrahub/task_manager/task.py
CHANGED
|
@@ -69,15 +69,16 @@ class PrefectTask:
|
|
|
69
69
|
]
|
|
70
70
|
if not related_node_ids:
|
|
71
71
|
continue
|
|
72
|
-
related_nodes.
|
|
72
|
+
related_nodes.add_nodes(flow_id=flow.id, node_ids=related_node_ids)
|
|
73
73
|
|
|
74
74
|
if unique_related_node_ids := related_nodes.get_unique_related_node_ids():
|
|
75
75
|
query = await NodeGetKindQuery.init(db=db, ids=unique_related_node_ids)
|
|
76
76
|
await query.execute(db=db)
|
|
77
77
|
unique_related_node_ids_kind = await query.get_node_kind_map()
|
|
78
78
|
|
|
79
|
-
for
|
|
80
|
-
|
|
79
|
+
for node_id, node_kind in unique_related_node_ids_kind.items():
|
|
80
|
+
if node_id in related_nodes.nodes:
|
|
81
|
+
related_nodes.nodes[node_id].kind = node_kind
|
|
81
82
|
|
|
82
83
|
return related_nodes
|
|
83
84
|
|
|
@@ -223,7 +224,11 @@ class PrefectTask:
|
|
|
223
224
|
if "progress" in node_fields:
|
|
224
225
|
progress_flow = await cls._get_progress(client=client, flow_ids=[flow.id for flow in flows])
|
|
225
226
|
|
|
226
|
-
if
|
|
227
|
+
if (
|
|
228
|
+
"related_nodes" in node_fields
|
|
229
|
+
or "related_node" in node_fields
|
|
230
|
+
or "related_node_kind" in node_fields
|
|
231
|
+
):
|
|
227
232
|
related_nodes_info = await cls._get_related_nodes(db=db, flows=flows)
|
|
228
233
|
|
|
229
234
|
if "workflow" in node_fields:
|
|
@@ -238,6 +243,8 @@ class PrefectTask:
|
|
|
238
243
|
if log_fields:
|
|
239
244
|
logs = logs_flow.to_graphql(flow_id=flow.id)
|
|
240
245
|
|
|
246
|
+
related_node = related_nodes_info.get_first_related_node(flow_id=flow.id)
|
|
247
|
+
|
|
241
248
|
nodes.append(
|
|
242
249
|
{
|
|
243
250
|
"node": {
|
|
@@ -251,8 +258,9 @@ class PrefectTask:
|
|
|
251
258
|
"branch": await cls._extract_branch_name(flow=flow),
|
|
252
259
|
"tags": flow.tags,
|
|
253
260
|
"workflow": workflow_names.get(flow.flow_id, None),
|
|
254
|
-
"related_node":
|
|
255
|
-
"related_node_kind":
|
|
261
|
+
"related_node": related_node.id if related_node else None,
|
|
262
|
+
"related_node_kind": related_node.kind if related_node else None,
|
|
263
|
+
"related_nodes": related_nodes_info.get_related_nodes_as_dict(flow_id=flow.id),
|
|
256
264
|
"created_at": flow.created.to_iso8601_string(), # type: ignore
|
|
257
265
|
"updated_at": flow.updated.to_iso8601_string(), # type: ignore
|
|
258
266
|
"start_time": flow.start_time.to_iso8601_string() if flow.start_time else None,
|
infrahub/visuals.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
|
|
3
1
|
COLOR_SELECTION = [
|
|
4
2
|
"#ed6a5a",
|
|
5
3
|
"#f4f1bb",
|
|
@@ -24,7 +22,7 @@ COLOR_SELECTION = [
|
|
|
24
22
|
]
|
|
25
23
|
|
|
26
24
|
|
|
27
|
-
def select_color(existing:
|
|
25
|
+
def select_color(existing: list[str]) -> str:
|
|
28
26
|
"""Select a color from a predefined list without including anything from a list of existing colors."""
|
|
29
27
|
for color in COLOR_SELECTION:
|
|
30
28
|
if color not in existing:
|