Skip to content

Commit b7d26f5

Browse files
authored
IFC-761 Use flags to handle branch permissions (#4601)
1 parent 5bdfdcb commit b7d26f5

File tree

29 files changed

+461
-254
lines changed

29 files changed

+461
-254
lines changed

backend/infrahub/api/schema.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
from infrahub.api.dependencies import get_branch_dep, get_current_user, get_db
1717
from infrahub.api.exceptions import SchemaNotValidError
1818
from infrahub.core import registry
19+
from infrahub.core.account import GlobalPermission
1920
from infrahub.core.branch import Branch # noqa: TCH001
20-
from infrahub.core.constants import GlobalPermissions, PermissionDecision
21+
from infrahub.core.constants import GLOBAL_BRANCH_NAME, GlobalPermissions, PermissionDecision
2122
from infrahub.core.migrations.schema.models import SchemaApplyMigrationData
2223
from infrahub.core.models import ( # noqa: TCH001
2324
SchemaBranchHash,
@@ -247,7 +248,16 @@ async def load_schema(
247248
if not await permission_backend.has_permission(
248249
db=db,
249250
account_id=account_session.account_id,
250-
permission=f"global:{GlobalPermissions.MANAGE_SCHEMA.value}:{PermissionDecision.ALLOW.value}",
251+
permission=GlobalPermission(
252+
id="",
253+
name="",
254+
action=GlobalPermissions.MANAGE_SCHEMA.value,
255+
decision=(
256+
PermissionDecision.ALLOW_DEFAULT
257+
if branch.name in (GLOBAL_BRANCH_NAME, registry.default_branch)
258+
else PermissionDecision.ALLOW_OTHER
259+
).value,
260+
),
251261
branch=branch,
252262
):
253263
raise PermissionDeniedError("You are not allowed to manage the schema")

backend/infrahub/core/account.py

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from dataclasses import dataclass
44
from typing import TYPE_CHECKING, Any, Optional, Union
55

6-
from infrahub.core.constants import InfrahubKind
6+
from infrahub.core.constants import InfrahubKind, PermissionDecision
77
from infrahub.core.query import Query
88
from infrahub.core.registry import registry
99

@@ -21,22 +21,23 @@ class Permission:
2121
id: str
2222
name: str
2323
action: str
24-
decision: str
24+
decision: int
2525

2626

2727
@dataclass
2828
class GlobalPermission(Permission):
2929
def __str__(self) -> str:
30-
return f"global:{self.action}:{self.decision}"
30+
decision = PermissionDecision(self.decision)
31+
return f"global:{self.action}:{decision.name.lower()}"
3132

3233

3334
@dataclass
3435
class ObjectPermission(Permission):
35-
branch: str
3636
namespace: str
3737

3838
def __str__(self) -> str:
39-
return f"object:{self.branch}:{self.namespace}:{self.name}:{self.action}:{self.decision}"
39+
decision = PermissionDecision(self.decision)
40+
return f"object:{self.namespace}:{self.name}:{self.action}:{decision.name.lower()}"
4041

4142

4243
class AccountGlobalPermissionQuery(Query):
@@ -234,17 +235,6 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:
234235
RETURN object_permission
235236
}
236237
WITH object_permission
237-
CALL {
238-
WITH object_permission
239-
MATCH (object_permission)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "branch"})-[r2:HAS_VALUE]->(object_permission_branch:AttributeValue)
240-
WHERE all(r IN [r1, r2] WHERE (%(branch_filter)s))
241-
WITH object_permission_branch, r1, r2, (r1.status = "active" AND r2.status = "active") AS is_active
242-
ORDER BY object_permission_branch.uuid, r2.branch_level DESC, r2.from DESC, r1.branch_level DESC, r1.from DESC
243-
WITH object_permission_branch, head(collect(is_active)) as latest_is_active
244-
WHERE latest_is_active = TRUE
245-
RETURN object_permission_branch
246-
}
247-
WITH object_permission, object_permission_branch
248238
249239
CALL {
250240
WITH object_permission
@@ -254,7 +244,7 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:
254244
ORDER BY r2.branch_level DESC, r2.from DESC, r1.branch_level DESC, r1.from DESC
255245
LIMIT 1
256246
}
257-
WITH object_permission, object_permission_branch, object_permission_namespace, is_active AS opn_is_active
247+
WITH object_permission, object_permission_namespace, is_active AS opn_is_active
258248
WHERE opn_is_active = TRUE
259249
CALL {
260250
WITH object_permission
@@ -264,7 +254,7 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:
264254
ORDER BY r2.branch_level DESC, r2.from DESC, r1.branch_level DESC, r1.from DESC
265255
LIMIT 1
266256
}
267-
WITH object_permission, object_permission_branch, object_permission_namespace, object_permission_name, is_active AS opn_is_active
257+
WITH object_permission, object_permission_namespace, object_permission_name, is_active AS opn_is_active
268258
WHERE opn_is_active = TRUE
269259
CALL {
270260
WITH object_permission
@@ -274,7 +264,7 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:
274264
ORDER BY r2.branch_level DESC, r2.from DESC, r1.branch_level DESC, r1.from DESC
275265
LIMIT 1
276266
}
277-
WITH object_permission, object_permission_branch, object_permission_namespace, object_permission_name, object_permission_action, is_active AS opa_is_active
267+
WITH object_permission, object_permission_namespace, object_permission_name, object_permission_action, is_active AS opa_is_active
278268
WHERE opa_is_active = TRUE
279269
CALL {
280270
WITH object_permission
@@ -284,7 +274,7 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:
284274
ORDER BY r2.branch_level DESC, r2.from DESC, r1.branch_level DESC, r1.from DESC
285275
LIMIT 1
286276
}
287-
WITH object_permission, object_permission_branch, object_permission_namespace, object_permission_name, object_permission_action, object_permission_decision, is_active AS opd_is_active
277+
WITH object_permission, object_permission_namespace, object_permission_name, object_permission_action, object_permission_decision, is_active AS opd_is_active
288278
WHERE opd_is_active = TRUE
289279
""" % {
290280
"branch_filter": branch_filter,
@@ -298,7 +288,6 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:
298288

299289
self.return_labels = [
300290
"object_permission",
301-
"object_permission_branch",
302291
"object_permission_namespace",
303292
"object_permission_name",
304293
"object_permission_action",
@@ -311,7 +300,6 @@ def get_permissions(self) -> list[ObjectPermission]:
311300
permissions.append(
312301
ObjectPermission(
313302
id=result.get("object_permission").get("uuid"),
314-
branch=result.get("object_permission_branch").get("value"),
315303
namespace=result.get("object_permission_namespace").get("value"),
316304
name=result.get("object_permission_name").get("value"),
317305
action=result.get("object_permission_action").get("value"),

backend/infrahub/core/attribute.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -465,9 +465,7 @@ async def to_graphql(
465465
"""Generate GraphQL Payload for this attribute."""
466466
# pylint: disable=too-many-branches
467467

468-
response: dict[str, Any] = {
469-
"id": self.id,
470-
}
468+
response: dict[str, Any] = {"id": self.id}
471469

472470
if fields and isinstance(fields, dict):
473471
field_names = fields.keys()
@@ -508,6 +506,9 @@ async def to_graphql(
508506
)
509507
continue
510508

509+
if field_name == "permissions":
510+
response["permissions"] = {"view": "ALLOW", "update": "ALLOW"}
511+
511512
if field_name.startswith("_"):
512513
field = getattr(self, field_name[1:])
513514
else:

backend/infrahub/core/constants/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from infrahub.core.constants import infrahubkind as InfrahubKind
66
from infrahub.exceptions import ValidationError
7-
from infrahub.utils import InfrahubStringEnum
7+
from infrahub.utils import InfrahubNumberEnum, InfrahubStringEnum
88

99
from .schema import FlagProperty, NodeProperty, SchemaElementPathType, UpdateSupport, UpdateValidationErrorType
1010

@@ -69,9 +69,11 @@ class PermissionAction(InfrahubStringEnum):
6969
VIEW = "view"
7070

7171

72-
class PermissionDecision(InfrahubStringEnum):
73-
ALLOW = "allow"
74-
DENY = "deny"
72+
class PermissionDecision(InfrahubNumberEnum):
73+
DENY = 1
74+
ALLOW_DEFAULT = 2
75+
ALLOW_OTHER = 4
76+
ALLOW_ALL = 6
7577

7678

7779
class AccountRole(InfrahubStringEnum):

backend/infrahub/core/enums.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def generate_python_enum(name: str, options: list[Any]) -> type[enum.Enum]:
99
main_attrs = {}
1010
for option in options:
1111
if isinstance(option, int):
12-
enum_name = str(option)
12+
enum_name = f"Value_{option!s}"
1313
else:
1414
enum_name = "_".join(re.findall(ENUM_NAME_REGEX, option)).upper()
1515

backend/infrahub/core/initialization.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ async def create_initial_permission(db: InfrahubDatabase) -> Node:
300300
db=db,
301301
name=format_label(GlobalPermissions.SUPER_ADMIN.value),
302302
action=GlobalPermissions.SUPER_ADMIN.value,
303-
decision=PermissionDecision.ALLOW.value,
303+
decision=PermissionDecision.ALLOW_ALL.value,
304304
)
305305
await permission.save(db=db)
306306
log.info(f"Created global permission: {GlobalPermissions.SUPER_ADMIN}")
@@ -329,7 +329,7 @@ async def create_super_administrator_role(db: InfrahubDatabase) -> Node:
329329
db=db,
330330
name=format_label(GlobalPermissions.SUPER_ADMIN.value),
331331
action=GlobalPermissions.SUPER_ADMIN.value,
332-
decision=PermissionDecision.ALLOW.value,
332+
decision=PermissionDecision.ALLOW_ALL.value,
333333
)
334334
await permission.save(db=db)
335335
log.info(f"Created global permission: {GlobalPermissions.SUPER_ADMIN}")

backend/infrahub/core/node/permissions.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from typing import TYPE_CHECKING, Optional
44

5+
from infrahub.permissions.constants import PermissionDecisionFlag
6+
57
from . import Node
68

79
if TYPE_CHECKING:
@@ -27,7 +29,8 @@ async def to_graphql(
2729

2830
if fields:
2931
if "identifier" in fields:
30-
response["identifier"] = {"value": f"global:{self.action.value}:{self.decision.value.value}"} # type: ignore[attr-defined]
32+
decision = PermissionDecisionFlag(value=self.decision.value.value) # type: ignore[attr-defined]
33+
response["identifier"] = {"value": f"global:{self.action.value}:{decision.name.lower()}"} # type: ignore[attr-defined,union-attr]
3134

3235
return response
3336

@@ -51,11 +54,9 @@ async def to_graphql(
5154

5255
if fields:
5356
if "identifier" in fields:
57+
decision = PermissionDecisionFlag(value=self.decision.value.value) # type: ignore[attr-defined]
5458
response["identifier"] = {
55-
"value": (
56-
f"object:{self.branch.value}:{self.namespace.value}:{self.name.value}:{self.action.value.value}:" # type: ignore[attr-defined]
57-
f"{self.decision.value.value}" # type: ignore[attr-defined]
58-
)
59+
"value": f"object:{self.namespace.value}:{self.name.value}:{self.action.value.value}:{decision.name.lower()}" # type: ignore[attr-defined,union-attr]
5960
}
6061

6162
return response

backend/infrahub/core/protocols.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,6 @@ class CoreNumberPool(CoreResourcePool, LineageSource):
391391

392392

393393
class CoreObjectPermission(CoreBasePermission):
394-
branch: String
395394
namespace: String
396395
name: String
397396
action: Enum

backend/infrahub/core/schema/definitions/core.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -933,9 +933,9 @@
933933
"attributes": [
934934
{
935935
"name": "decision",
936-
"kind": "Text",
936+
"kind": "Number",
937937
"enum": PermissionDecision.available_types(),
938-
"default_value": PermissionDecision.ALLOW.value,
938+
"default_value": PermissionDecision.ALLOW_ALL.value,
939939
"order_weight": 5000,
940940
},
941941
{
@@ -2186,15 +2186,12 @@
21862186
"description": "A permission that grants rights to perform actions on objects",
21872187
"label": "Object permission",
21882188
"include_in_menu": False,
2189-
"order_by": ["branch__value", "namespace__value", "name__value", "action__value", "decision__value"],
2190-
"display_labels": ["branch__value", "namespace__value", "name__value", "action__value", "decision__value"],
2191-
"uniqueness_constraints": [
2192-
["branch__value", "namespace__value", "name__value", "action__value", "decision__value"]
2193-
],
2189+
"order_by": ["namespace__value", "name__value", "action__value", "decision__value"],
2190+
"display_labels": ["namespace__value", "name__value", "action__value", "decision__value"],
2191+
"uniqueness_constraints": [["namespace__value", "name__value", "action__value", "decision__value"]],
21942192
"generate_profile": False,
21952193
"inherit_from": [InfrahubKind.BASEPERMISSION],
21962194
"attributes": [
2197-
{"name": "branch", "kind": "Text", "order_weight": 1000},
21982195
{"name": "namespace", "kind": "Text", "order_weight": 2000},
21992196
{"name": "name", "kind": "Text", "order_weight": 3000},
22002197
{

backend/infrahub/graphql/auth/query_permission_checker/default_branch_checker.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from infrahub.auth import AccountSession
22
from infrahub.core import registry
3+
from infrahub.core.account import GlobalPermission
34
from infrahub.core.branch import Branch
45
from infrahub.core.constants import GLOBAL_BRANCH_NAME, GlobalPermissions, PermissionDecision
56
from infrahub.database import InfrahubDatabase
@@ -13,7 +14,9 @@
1314
class DefaultBranchPermissionChecker(GraphQLQueryPermissionCheckerInterface):
1415
"""Checker that makes sure a user account can edit data in the default branch."""
1516

16-
permission_required = f"global:{GlobalPermissions.EDIT_DEFAULT_BRANCH.value}:{PermissionDecision.ALLOW.value}"
17+
permission_required = GlobalPermission(
18+
id="", name="", action=GlobalPermissions.EDIT_DEFAULT_BRANCH.value, decision=PermissionDecision.ALLOW_ALL.value
19+
)
1720
exempt_operations = ["BranchCreate"]
1821

1922
async def supports(self, db: InfrahubDatabase, account_session: AccountSession, branch: Branch) -> bool:

0 commit comments

Comments
 (0)