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
@@ -13,10 +13,9 @@ from infrahub.core.constants import (
13
13
  )
14
14
  from infrahub.core.manager import NodeManager
15
15
  from infrahub.core.node import Node
16
- from infrahub.core.registry import registry
17
16
  from infrahub.core.schema import NodeSchema
18
17
  from infrahub.database import InfrahubDatabase, retry_db_transaction
19
- from infrahub.exceptions import BranchNotFoundError, ValidationError
18
+ from infrahub.exceptions import BranchNotFoundError, PermissionDeniedError, ValidationError
20
19
  from infrahub.graphql.mutations.main import InfrahubMutationMixin
21
20
  from infrahub.graphql.types.enums import CheckType as GraphQLCheckType
22
21
  from infrahub.message_bus import messages
@@ -92,22 +91,6 @@ class InfrahubProposedChangeMutation(InfrahubMutationMixin, Mutation):
92
91
  ):
93
92
  context: GraphqlContext = info.context
94
93
 
95
- has_merge_permission = False
96
- if context.account_session:
97
- for permission_backend in registry.permission_backends:
98
- if has_merge_permission := await permission_backend.has_permission(
99
- db=context.db,
100
- account_session=context.active_account_session,
101
- permission=GlobalPermission(
102
- action=GlobalPermissions.MERGE_PROPOSED_CHANGE.value,
103
- decision=PermissionDecision.ALLOW_ALL.value,
104
- ),
105
- branch=branch,
106
- ):
107
- break
108
- else:
109
- has_merge_permission = True
110
-
111
94
  obj = await NodeManager.get_one_by_id_or_default_filter(
112
95
  db=context.db,
113
96
  kind=cls._meta.schema.kind,
@@ -125,8 +108,16 @@ class InfrahubProposedChangeMutation(InfrahubMutationMixin, Mutation):
125
108
  state.validate_state_transition(updated_state)
126
109
 
127
110
  # Check before starting a transaction, stopping in the middle of the transaction seems to break with memgraph
128
- if updated_state == ProposedChangeState.MERGED and not has_merge_permission:
129
- raise ValidationError("You do not have the permission to merge proposed changes")
111
+ if updated_state == ProposedChangeState.MERGED and context.account_session:
112
+ try:
113
+ context.active_permissions.raise_for_permission(
114
+ permission=GlobalPermission(
115
+ action=GlobalPermissions.MERGE_PROPOSED_CHANGE.value,
116
+ decision=PermissionDecision.ALLOW_ALL.value,
117
+ )
118
+ )
119
+ except PermissionDeniedError as exc:
120
+ raise ValidationError(str(exc)) from exc
130
121
 
131
122
  if updated_state == ProposedChangeState.MERGED:
132
123
  data["state"]["value"] = ProposedChangeState.MERGING.value
@@ -10,6 +10,7 @@ from infrahub.core import registry
10
10
  from infrahub.core.constants import InfrahubKind
11
11
  from infrahub.core.ipam.constants import PrefixMemberType
12
12
  from infrahub.core.schema import NodeSchema
13
+ from infrahub.database import retry_db_transaction
13
14
  from infrahub.exceptions import QueryValidationError, SchemaNotFoundError, ValidationError
14
15
 
15
16
  from ..queries.resource_manager import PoolAllocatedNode
@@ -32,7 +33,7 @@ class IPPrefixPoolGetResourceInput(InputObjectType):
32
33
  hfid = InputField(String(required=False), description="HFID of the pool to allocate from")
33
34
  identifier = InputField(String(required=False), description="Identifier for the allocated resource")
34
35
  prefix_length = InputField(Int(required=False), description="Size of the prefix to allocate")
35
- member_type = InputField(String(required=False), description="member_type of the newly created prefix")
36
+ member_type = InputField(String(required=False), description="Type of members for the newly created prefix")
36
37
  prefix_type = InputField(String(required=False), description="Kind of prefix to allocate")
37
38
  data = InputField(GenericScalar(required=False), description="Additional data to pass to the newly created prefix")
38
39
 
@@ -44,9 +45,9 @@ class IPAddressPoolGetResourceInput(InputObjectType):
44
45
  prefix_length = InputField(
45
46
  Int(required=False), description="Size of the prefix mask to allocate on the new IP address"
46
47
  )
47
- address_type = InputField(String(required=False), description="Kind of ip address to allocate")
48
+ address_type = InputField(String(required=False), description="Kind of IP address to allocate")
48
49
  data = InputField(
49
- GenericScalar(required=False), description="Additional data to pass to the newly created ip address"
50
+ GenericScalar(required=False), description="Additional data to pass to the newly created IP address"
50
51
  )
51
52
 
52
53
 
@@ -167,6 +168,7 @@ class InfrahubNumberPoolMutation(InfrahubMutationMixin, Mutation):
167
168
  super().__init_subclass_with_meta__(_meta=_meta, **options)
168
169
 
169
170
  @classmethod
171
+ @retry_db_transaction(name="resource_manager_create")
170
172
  async def mutate_create(
171
173
  cls,
172
174
  info: GraphQLResolveInfo,
@@ -196,6 +198,7 @@ class InfrahubNumberPoolMutation(InfrahubMutationMixin, Mutation):
196
198
  return await super().mutate_create(info=info, data=data, branch=branch)
197
199
 
198
200
  @classmethod
201
+ @retry_db_transaction(name="resource_manager_update")
199
202
  async def mutate_update(
200
203
  cls,
201
204
  info: GraphQLResolveInfo,
@@ -126,7 +126,7 @@ class SchemaDropdownRemove(Mutation):
126
126
  validate_kind_dropdown(kind=kind, attribute=attribute)
127
127
  dropdown = str(data.dropdown)
128
128
  nodes_with_dropdown = await NodeManager.query(
129
- db=context.db, schema=kind.kind, filters={f"{attribute}__value": dropdown}
129
+ db=context.db, schema=kind.kind, filters={f"{attribute}__value": dropdown}, branch=context.branch
130
130
  )
131
131
  if nodes_with_dropdown:
132
132
  raise ValidationError(f"There are still {kind.kind} objects using this dropdown")
@@ -208,14 +208,15 @@ class SchemaEnumRemove(Mutation):
208
208
  data: SchemaEnumInput,
209
209
  ) -> dict[str, bool]:
210
210
  context: GraphqlContext = info.context
211
- db = context.db
212
- branch = context.branch
213
- kind = db.schema.get(name=str(data.kind), branch=branch.name)
211
+
212
+ kind = context.db.schema.get(name=str(data.kind), branch=context.branch.name)
214
213
 
215
214
  attribute = str(data.attribute)
216
215
  enum = str(data.enum)
217
216
  validate_kind_enum(kind=kind, attribute=attribute)
218
- nodes_with_enum = await NodeManager.query(db=db, schema=kind.kind, filters={f"{attribute}__value": enum})
217
+ nodes_with_enum = await NodeManager.query(
218
+ db=context.db, schema=kind.kind, filters={f"{attribute}__value": enum}, branch=context.branch
219
+ )
219
220
  if nodes_with_enum:
220
221
  raise ValidationError(f"There are still {kind.kind} objects using this enum")
221
222
 
@@ -231,8 +232,8 @@ class SchemaEnumRemove(Mutation):
231
232
 
232
233
  await update_registry(
233
234
  kind=kind,
234
- branch=branch,
235
- db=db,
235
+ branch=context.branch,
236
+ db=context.db,
236
237
  account_id=context.active_account_session.account_id,
237
238
  service=context.active_service,
238
239
  )
@@ -31,7 +31,7 @@ async def merge_branch_mutation(branch: str) -> None:
31
31
  diff_coordinator = await component_registry.get_component(DiffCoordinator, db=db, branch=obj)
32
32
  diff_repository = await component_registry.get_component(DiffRepository, db=db, branch=obj)
33
33
  diff_merger = await component_registry.get_component(DiffMerger, db=db, branch=obj)
34
- enriched_diff = await diff_coordinator.update_branch_diff(base_branch=base_branch, diff_branch=obj)
34
+ enriched_diff = await diff_coordinator.update_branch_diff_and_return(base_branch=base_branch, diff_branch=obj)
35
35
  if enriched_diff.get_all_conflicts():
36
36
  raise ValidationError(
37
37
  f"Branch {obj.name} contains conflicts with the default branch."
@@ -4,15 +4,14 @@ from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from infrahub.core.registry import registry
6
6
  from infrahub.core.schema import GenericSchema
7
- from infrahub.permissions.report import report_schema_permissions
7
+ from infrahub.permissions import report_schema_permissions
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from infrahub.core.schema import MainSchemaTypes
11
- from infrahub.database import InfrahubDatabase
12
11
  from infrahub.graphql.initialization import GraphqlContext
13
12
 
14
13
 
15
- async def get_permissions(db: InfrahubDatabase, schema: MainSchemaTypes, context: GraphqlContext) -> dict[str, Any]:
14
+ async def get_permissions(schema: MainSchemaTypes, context: GraphqlContext) -> dict[str, Any]:
16
15
  schema_objects = [schema]
17
16
  if isinstance(schema, GenericSchema):
18
17
  for node_name in schema.used_by:
@@ -28,7 +27,7 @@ async def get_permissions(db: InfrahubDatabase, schema: MainSchemaTypes, context
28
27
  response: dict[str, Any] = {"count": len(schema_objects), "edges": []}
29
28
 
30
29
  nodes = await report_schema_permissions(
31
- db=db, schemas=schema_objects, branch=context.branch, account_session=context.active_account_session
30
+ branch=context.branch, permission_manager=context.active_permissions, schemas=schema_objects
32
31
  )
33
32
  response["edges"] = [{"node": node} for node in nodes]
34
33
 
@@ -5,7 +5,6 @@ from typing import TYPE_CHECKING, Any
5
5
  from graphene import Field, Int, List, ObjectType, String
6
6
  from infrahub_sdk.utils import extract_fields_first_node
7
7
 
8
- from infrahub.core import registry
9
8
  from infrahub.core.manager import NodeManager
10
9
  from infrahub.core.protocols import InternalAccountToken
11
10
  from infrahub.exceptions import PermissionDeniedError
@@ -14,7 +13,6 @@ if TYPE_CHECKING:
14
13
  from graphql import GraphQLResolveInfo
15
14
 
16
15
  from infrahub.graphql.initialization import GraphqlContext
17
- from infrahub.permissions.constants import AssignedPermissions
18
16
 
19
17
 
20
18
  class AccountTokenNode(ObjectType):
@@ -120,17 +118,10 @@ async def resolve_account_permissions(
120
118
  raise ValueError("An account_session is mandatory to execute this query")
121
119
 
122
120
  fields = await extract_fields_first_node(info)
123
- permissions: AssignedPermissions = {"global_permissions": [], "object_permissions": []}
124
- for permission_backend in registry.permission_backends:
125
- backend_permissions = await permission_backend.load_permissions(
126
- db=context.db, account_session=context.account_session, branch=context.branch
127
- )
128
- permissions["global_permissions"].extend(backend_permissions["global_permissions"])
129
- permissions["object_permissions"].extend(backend_permissions["object_permissions"])
130
121
 
131
122
  response: dict[str, dict[str, Any]] = {}
132
123
  if "global_permissions" in fields:
133
- global_list = permissions["global_permissions"]
124
+ global_list = context.active_permissions.permissions["global_permissions"]
134
125
  response["global_permissions"] = {"count": len(global_list)}
135
126
  response["global_permissions"]["edges"] = [
136
127
  {
@@ -145,7 +136,7 @@ async def resolve_account_permissions(
145
136
  for obj in global_list
146
137
  ]
147
138
  if "object_permissions" in fields:
148
- object_list = permissions["object_permissions"]
139
+ object_list = context.active_permissions.permissions["object_permissions"]
149
140
  response["object_permissions"] = {"count": len(object_list)}
150
141
  response["object_permissions"]["edges"] = [
151
142
  {
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any, Optional, Union
3
+ from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from graphene import Field, Float, Int, List, ObjectType, String
6
6
  from infrahub_sdk.utils import extract_fields_first_node
@@ -57,7 +57,7 @@ class PoolAllocatedEdge(ObjectType):
57
57
  node = Field(PoolAllocatedNode, required=True)
58
58
 
59
59
 
60
- def _validate_pool_type(pool_id: str, pool: Optional[CoreNode] = None) -> CoreNode:
60
+ def _validate_pool_type(pool_id: str, pool: CoreNode | None = None) -> CoreNode:
61
61
  if not pool or pool.get_kind() not in [
62
62
  InfrahubKind.IPADDRESSPOOL,
63
63
  InfrahubKind.IPPREFIXPOOL,
@@ -81,15 +81,21 @@ class PoolAllocated(ObjectType):
81
81
  limit: int = 10,
82
82
  ) -> dict:
83
83
  context: GraphqlContext = info.context
84
- pool: Optional[CoreNode] = await NodeManager.get_one(id=pool_id, db=context.db, branch=context.branch)
84
+ pool: CoreNode | None = await NodeManager.get_one(id=pool_id, db=context.db, branch=context.branch)
85
85
 
86
86
  fields = await extract_fields_first_node(info=info)
87
87
 
88
+ allocated_kinds: list[str] = []
88
89
  pool = _validate_pool_type(pool_id=pool_id, pool=pool)
89
- if pool.get_kind() == "CoreNumberPool":
90
- return await resolve_number_pool_allocation(
91
- db=context.db, context=context, pool=pool, fields=fields, offset=offset, limit=limit
92
- )
90
+ match pool.get_kind():
91
+ case InfrahubKind.NUMBERPOOL:
92
+ return await resolve_number_pool_allocation(
93
+ db=context.db, context=context, pool=pool, fields=fields, offset=offset, limit=limit
94
+ )
95
+ case InfrahubKind.IPPREFIXPOOL:
96
+ allocated_kinds.append(InfrahubKind.IPPREFIX)
97
+ case InfrahubKind.IPADDRESSPOOL:
98
+ allocated_kinds.append(InfrahubKind.IPADDRESS)
93
99
 
94
100
  resources = await pool.resources.get_peers(db=context.db) # type: ignore[attr-defined,union-attr]
95
101
  if resource_id not in resources:
@@ -100,7 +106,12 @@ class PoolAllocated(ObjectType):
100
106
  resource = resources[resource_id]
101
107
 
102
108
  query = await IPPrefixUtilization.init(
103
- db=context.db, at=context.at, ip_prefixes=[resource], offset=offset, limit=limit
109
+ db=context.db,
110
+ at=context.at,
111
+ ip_prefixes=[resource],
112
+ allocated_kinds=allocated_kinds,
113
+ offset=offset,
114
+ limit=limit,
104
115
  )
105
116
  response: dict[str, Any] = {}
106
117
  if "count" in fields:
@@ -170,7 +181,7 @@ class PoolUtilization(ObjectType):
170
181
  ) -> dict:
171
182
  context: GraphqlContext = info.context
172
183
  db: InfrahubDatabase = context.db
173
- pool: Optional[CoreNode] = await NodeManager.get_one(id=pool_id, db=db, branch=context.branch)
184
+ pool: CoreNode | None = await NodeManager.get_one(id=pool_id, db=db, branch=context.branch)
174
185
  pool = _validate_pool_type(pool_id=pool_id, pool=pool)
175
186
  if pool.get_kind() == "CoreNumberPool":
176
187
  return await resolve_number_pool_utilization(db=db, context=context, pool=pool)
@@ -212,7 +223,7 @@ class PoolUtilization(ObjectType):
212
223
  for resource_id, resource_node in resources_map.items():
213
224
  resource_total = None
214
225
  default_branch_total = None
215
- node_response: dict[str, Union[str, float, int]] = {}
226
+ node_response: dict[str, str | float | int] = {}
216
227
  if "id" in node_fields:
217
228
  node_response["id"] = resource_id
218
229
  if "kind" in node_fields:
infrahub/graphql/query.py CHANGED
@@ -37,7 +37,9 @@ async def execute_query(
37
37
  if not graphql_query:
38
38
  raise ValueError(f"Unable to find the {InfrahubKind.GRAPHQLQUERY} {name}")
39
39
 
40
- gql_params = prepare_graphql_params(branch=branch, db=db, at=at, include_mutation=False, include_subscription=False)
40
+ gql_params = await prepare_graphql_params(
41
+ branch=branch, db=db, at=at, include_mutation=False, include_subscription=False
42
+ )
41
43
 
42
44
  result = await graphql(
43
45
  schema=gql_params.schema,
@@ -9,6 +9,7 @@ from infrahub.core.manager import NodeManager
9
9
  from infrahub.core.query.node import NodeGetHierarchyQuery
10
10
  from infrahub.exceptions import NodeNotFoundError
11
11
 
12
+ from ..models import OrderModel
12
13
  from ..parser import extract_selection
13
14
  from ..permissions import get_permissions
14
15
  from ..types import RELATIONS_PROPERTY_MAP, RELATIONS_PROPERTY_MAP_REVERSED
@@ -33,6 +34,7 @@ async def account_resolver(
33
34
  filters={"ids": [context.account_session.account_id]},
34
35
  fields=fields,
35
36
  db=db,
37
+ order=OrderModel(disable=True),
36
38
  )
37
39
  if results:
38
40
  account_profile = await results[0].to_graphql(db=db, fields=fields)
@@ -132,6 +134,7 @@ async def default_paginated_list_resolver(
132
134
  info: GraphQLResolveInfo,
133
135
  offset: int | None = None,
134
136
  limit: int | None = None,
137
+ order: OrderModel | None = None,
135
138
  partial_match: bool = False,
136
139
  **kwargs: dict[str, Any],
137
140
  ) -> dict[str, Any]:
@@ -149,7 +152,7 @@ async def default_paginated_list_resolver(
149
152
  node_fields = edges.get("node", {})
150
153
 
151
154
  permission_set: Optional[dict[str, Any]] = None
152
- permissions = await get_permissions(db=db, schema=schema, context=context) if context.account_session else None
155
+ permissions = await get_permissions(schema=schema, context=context) if context.permissions else None
153
156
  if fields.get("permissions"):
154
157
  response["permissions"] = permissions
155
158
 
@@ -173,6 +176,7 @@ async def default_paginated_list_resolver(
173
176
  include_source=True,
174
177
  include_owner=True,
175
178
  partial_match=partial_match,
179
+ order=order,
176
180
  )
177
181
 
178
182
  if "count" in fields:
@@ -28,9 +28,21 @@ class Task(ObjectType):
28
28
  start_time = String(required=False)
29
29
 
30
30
 
31
+ class TaskRelatedNode(ObjectType):
32
+ id = String(required=True)
33
+ kind = String(required=True)
34
+
35
+
31
36
  class TaskNode(Task):
32
- related_node = String(required=False)
33
- related_node_kind = String(required=False)
37
+ related_node = String(
38
+ required=False,
39
+ deprecation_reason="This field is deprecated and it will be removed in a future release, use related_nodes instead",
40
+ )
41
+ related_node_kind = String(
42
+ required=False,
43
+ deprecation_reason="This field is deprecated and it will be removed in a future release, use related_nodes instead",
44
+ )
45
+ related_nodes = List(TaskRelatedNode)
34
46
  logs = Field(TaskLogEdge)
35
47
 
36
48
 
@@ -5,16 +5,14 @@ from typing import TYPE_CHECKING
5
5
  from infrahub.core import registry
6
6
  from infrahub.core.protocols import CoreMenuItem
7
7
  from infrahub.log import get_logger
8
- from infrahub.permissions.constants import AssignedPermissions
9
- from infrahub.permissions.local_backend import LocalPermissionBackend
10
8
 
11
9
  from .constants import FULL_DEFAULT_MENU
12
10
  from .models import MenuDict, MenuItemDict
13
11
 
14
12
  if TYPE_CHECKING:
15
- from infrahub.auth import AccountSession
16
13
  from infrahub.core.branch import Branch
17
14
  from infrahub.database import InfrahubDatabase
15
+ from infrahub.permissions import PermissionManager
18
16
 
19
17
  log = get_logger()
20
18
 
@@ -24,28 +22,18 @@ def get_full_name(obj: CoreMenuItem) -> str:
24
22
 
25
23
 
26
24
  async def generate_restricted_menu(
27
- db: InfrahubDatabase, branch: Branch, menu_items: list[CoreMenuItem], account: AccountSession | None = None
25
+ db: InfrahubDatabase, branch: Branch, menu_items: list[CoreMenuItem], account_permissions: PermissionManager | None
28
26
  ) -> MenuDict:
29
27
  menu = await generate_menu(db=db, branch=branch, menu_items=menu_items)
30
28
 
31
- permissions = AssignedPermissions(global_permissions=[], object_permissions=[])
32
- perm_backend = LocalPermissionBackend()
33
-
34
- if account:
35
- permissions = await perm_backend.load_permissions(db=db, account_session=account, branch=branch)
36
-
37
29
  for item in menu.data.values():
38
- has_permission: bool | None = None
30
+ has_permission = True
39
31
  for permission in item.get_global_permissions():
40
- has_permission = perm_backend.resolve_global_permission(
41
- permissions=permissions["global_permissions"], permission_to_check=permission
32
+ has_permission = account_permissions is not None and account_permissions.has_permission(
33
+ permission=permission
42
34
  )
43
- if has_permission:
44
- has_permission = True
45
- elif has_permission is None:
46
- has_permission = False
47
35
 
48
- if has_permission is False:
36
+ if not has_permission:
49
37
  item.hidden = True
50
38
 
51
39
  return menu
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict
1
+ from typing import Any
2
2
 
3
3
  from pydantic import Field
4
4
 
@@ -12,4 +12,4 @@ class EventNodeMutated(InfrahubMessage):
12
12
  kind: str = Field(..., description="The type of object modified")
13
13
  node_id: str = Field(..., description="The ID of the mutated node")
14
14
  action: str = Field(..., description="The action taken on the node")
15
- data: Dict[str, Any] = Field(..., description="Data on modified object")
15
+ data: dict[str, Any] = Field(..., description="Data on modified object")
@@ -1,5 +1,3 @@
1
- from typing import List
2
-
3
1
  from infrahub_sdk.uuidt import UUIDT
4
2
  from prefect import flow
5
3
  from prefect.logging import get_run_logger
@@ -29,10 +27,10 @@ async def check_definition(message: messages.CheckRepositoryCheckDefinition, ser
29
27
  )
30
28
  proposed_change = await service.client.get(kind=InfrahubKind.PROPOSEDCHANGE, id=message.proposed_change)
31
29
  validator_execution_id = str(UUIDT())
32
- check_execution_ids: List[str] = []
30
+ check_execution_ids: list[str] = []
33
31
  await proposed_change.validations.fetch()
34
32
  validator = None
35
- events: List[InfrahubMessage] = []
33
+ events: list[InfrahubMessage] = []
36
34
 
37
35
  for relationship in proposed_change.validations.peers:
38
36
  existing_validator = relationship.peer
@@ -1,5 +1,3 @@
1
- from typing import List
2
-
3
1
  from prefect import flow
4
2
 
5
3
  from infrahub.core import registry
@@ -24,14 +22,14 @@ async def merge(message: messages.EventBranchMerge, service: InfrahubServices) -
24
22
  async with service.database.start_session() as db:
25
23
  log.info("Branch merged", source_branch=message.source_branch, target_branch=message.target_branch)
26
24
 
27
- events: List[InfrahubMessage] = [
25
+ events: list[InfrahubMessage] = [
28
26
  messages.RefreshRegistryBranches(),
29
27
  ]
30
28
  component_registry = get_component_registry()
31
29
  default_branch = registry.get_branch_from_registry()
32
30
  diff_repository = await component_registry.get_component(DiffRepository, db=db, branch=default_branch)
33
31
  # send diff update requests for every branch-tracking diff
34
- branch_diff_roots = await diff_repository.get_empty_roots(base_branch_names=[message.target_branch])
32
+ branch_diff_roots = await diff_repository.get_roots_metadata(base_branch_names=[message.target_branch])
35
33
 
36
34
  await service.workflow.submit_workflow(
37
35
  workflow=TRIGGER_ARTIFACT_DEFINITION_GENERATE,
@@ -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: TCH001
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,
@@ -1,5 +1,3 @@
1
- from typing import List
2
-
3
1
  from infrahub_sdk.uuidt import UUIDT
4
2
  from prefect import flow
5
3
  from prefect.logging import get_run_logger
@@ -18,7 +16,7 @@ async def checks(message: messages.RequestRepositoryChecks, service: InfrahubSer
18
16
  await add_tags(branches=[message.source_branch], nodes=[message.proposed_change])
19
17
  log = get_run_logger()
20
18
 
21
- events: List[InfrahubMessage] = []
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: List[str] = []
27
+ check_execution_ids: list[str] = []
30
28
  await proposed_change.validations.fetch()
31
29
  await repository.checks.fetch()
32
30
 
@@ -106,7 +104,7 @@ async def user_checks(message: messages.RequestRepositoryUserChecks, service: In
106
104
  await add_tags(branches=[message.source_branch], nodes=[message.proposed_change])
107
105
  log = get_run_logger()
108
106
 
109
- events: List[InfrahubMessage] = []
107
+ events: list[InfrahubMessage] = []
110
108
 
111
109
  repository = await service.client.get(
112
110
  kind=InfrahubKind.GENERICREPOSITORY, id=message.repository_id, branch=message.source_branch, fragment=True
@@ -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: TCH002
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
@@ -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__ = ["LocalPermissionBackend", "PermissionBackend"]
7
+ __all__ = [
8
+ "AssignedPermissions",
9
+ "LocalPermissionBackend",
10
+ "PermissionBackend",
11
+ "PermissionManager",
12
+ "report_schema_permissions",
13
+ ]
@@ -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.constants import AssignedPermissions, PermissionDecisionFlag
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, account_session: AccountSession, branch: Branch
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
- if TYPE_CHECKING:
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
+ }