Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:opsmill/infrahub into ple-IFC-76…
Browse files Browse the repository at this point in the history
…-IFC-269-action-button
  • Loading branch information
pa-lem committed Sep 18, 2024
2 parents 83eebec + 60243c7 commit 2549a0e
Show file tree
Hide file tree
Showing 39 changed files with 765 additions and 155 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ jobs:
- name: "Check out repository code"
uses: "actions/checkout@v4"
- name: "Linting: markdownlint"
uses: DavidAnson/markdownlint-cli2-action@v16
uses: DavidAnson/markdownlint-cli2-action@v17
with:
config: .markdownlint.yaml
globs: |
Expand Down
12 changes: 6 additions & 6 deletions backend/infrahub/api/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,14 @@ async def get_menu(branch: Branch = Depends(get_branch_dep)) -> list[InterfaceMe
icon=_extract_node_icon(full_schema[InfrahubKind.GENERICACCOUNT]),
),
InterfaceMenu(
title="User Groups",
path=f"/objects/{InfrahubKind.USERGROUP}",
icon=_extract_node_icon(full_schema[InfrahubKind.USERGROUP]),
title="Account Groups",
path=f"/objects/{InfrahubKind.ACCOUNTGROUP}",
icon=_extract_node_icon(full_schema[InfrahubKind.ACCOUNTGROUP]),
),
InterfaceMenu(
title="User Roles",
path=f"/objects/{InfrahubKind.USERROLE}",
icon=_extract_node_icon(full_schema[InfrahubKind.USERROLE]),
title="Account Roles",
path=f"/objects/{InfrahubKind.ACCOUNTROLE}",
icon=_extract_node_icon(full_schema[InfrahubKind.ACCOUNTROLE]),
),
InterfaceMenu(
title="Permissions",
Expand Down
126 changes: 117 additions & 9 deletions backend/infrahub/core/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Optional, Union

from infrahub.core.constants import InfrahubKind
from infrahub.core.query import Query
from infrahub.core.registry import registry

if TYPE_CHECKING:
from infrahub.core.branch import Branch
from infrahub.database import InfrahubDatabase
from infrahub.permissions.constants import AssignedPermissions


# pylint: disable=redefined-builtin

Expand All @@ -27,6 +30,17 @@ def __str__(self) -> str:
return f"global:{self.action}:allow"


@dataclass
class ObjectPermission(Permission):
branch: str
namespace: str
name: str
decision: str

def __str__(self) -> str:
return f"object:{self.branch}:{self.namespace}:{self.name}:{self.action}:{self.decision}"


class AccountGlobalPermissionQuery(Query):
name: str = "account_global_permissions"

Expand All @@ -44,7 +58,7 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:

# ruff: noqa: E501
query = """
MATCH (account:CoreGenericAccount)
MATCH (account:%(generic_account_node)s)
WHERE account.uuid = $account_id
CALL {
WITH account
Expand All @@ -57,12 +71,20 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:
WITH account, r1 as r
WHERE r.status = "active"
WITH account
MATCH (account)-[]->(:Relationship {name: "group_member"})<-[]-(:CoreUserGroup)-[]->(:Relationship {name: "role__usergroups"})<-[]-(:CoreUserRole)-[]->(:Relationship {name: "role__permissions"})<-[]-(global_permission:CoreGlobalPermission)-[:HAS_ATTRIBUTE]->(:Attribute {name: "action"})-[:HAS_VALUE]->(global_permission_action:AttributeValue)
""" % {"branch_filter": branch_filter}
MATCH (account)-[]->(:Relationship {name: "group_member"})<-[]-(:%(group_node)s)-[]->(:Relationship {name: "role__accountgroups"})<-[]-(:%(account_role_node)s)-[]->(:Relationship {name: "role__permissions"})<-[]-(global_permission:%(global_permission_node)s)-[:HAS_ATTRIBUTE]->(:Attribute {name: "name"})-[:HAS_VALUE]->(global_permission_name:AttributeValue)
WITH global_permission, global_permission_name
MATCH (global_permission)-[:HAS_ATTRIBUTE]->(:Attribute {name: "action"})-[:HAS_VALUE]->(global_permission_action:AttributeValue)
""" % {
"branch_filter": branch_filter,
"generic_account_node": InfrahubKind.GENERICACCOUNT,
"account_role_node": InfrahubKind.ACCOUNTROLE,
"group_node": InfrahubKind.ACCOUNTGROUP,
"global_permission_node": InfrahubKind.GLOBALPERMISSION,
}

self.add_to_query(query)

self.return_labels = ["global_permission", "global_permission_action"]
self.return_labels = ["global_permission", "global_permission_name", "global_permission_action"]

def get_permissions(self) -> list[GlobalPermission]:
permissions: list[GlobalPermission] = []
Expand All @@ -71,24 +93,110 @@ def get_permissions(self) -> list[GlobalPermission]:
permissions.append(
GlobalPermission(
id=result.get("global_permission").get("uuid"),
name=result.get("global_permission_action").get("value"),
name=result.get("global_permission_name").get("value"),
action=result.get("global_permission_action").get("value"),
)
)

return permissions


async def fetch_permissions(
account_id: str, db: InfrahubDatabase, branch: Optional[Union[Branch, str]] = None
) -> dict[str, list[GlobalPermission]]:
class AccountObjectPermissionQuery(Query):
name: str = "account_object_permissions"

def __init__(self, account_id: str, **kwargs: Any):
self.account_id = account_id
super().__init__(**kwargs)

async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:
self.params["account_id"] = self.account_id

branch_filter, branch_params = self.branch.get_query_filter_path(
at=self.at.to_string(), branch_agnostic=self.branch_agnostic
)
self.params.update(branch_params)

query = """
MATCH (account:%(generic_account_node)s)
WHERE account.uuid = $account_id
CALL {
WITH account
MATCH (account)-[r:IS_PART_OF]-(root:Root)
WHERE %(branch_filter)s
RETURN account as account1, r as r1
ORDER BY r.branch_level DESC, r.from DESC
LIMIT 1
}
WITH account, r1 as r
WHERE r.status = "active"
WITH account
MATCH group_path = (account)-[]->(:Relationship {name: "group_member"})
<-[]-(:%(account_group_node)s)
-[]->(:Relationship {name: "role__accountgroups"})
<-[]-(:%(account_role_node)s)
-[]->(:Relationship {name: "role__permissions"})
<-[]-(object_permission:%(object_permission_node)s)
-[:HAS_ATTRIBUTE]->(:Attribute {name: "branch"})
-[:HAS_VALUE]->(object_permission_branch:AttributeValue)
WITH object_permission, object_permission_branch
WHERE all(r IN relationships(group_path) WHERE (%(branch_filter)s) AND r.status = "active")
MATCH namespace_path = (object_permission)-[:HAS_ATTRIBUTE]->(:Attribute {name: "namespace"})-[:HAS_VALUE]->(object_permission_namespace:AttributeValue)
WHERE all(r IN relationships(namespace_path) WHERE (%(branch_filter)s) AND r.status = "active")
MATCH name_path = (object_permission)-[:HAS_ATTRIBUTE]->(:Attribute {name: "name"})-[:HAS_VALUE]->(object_permission_name:AttributeValue)
WHERE all(r IN relationships(name_path) WHERE (%(branch_filter)s) AND r.status = "active")
MATCH action_path = (object_permission)-[:HAS_ATTRIBUTE]->(:Attribute {name: "action"})-[:HAS_VALUE]->(object_permission_action:AttributeValue)
WHERE all(r IN relationships(action_path) WHERE (%(branch_filter)s) AND r.status = "active")
MATCH decision_path = (object_permission)-[:HAS_ATTRIBUTE]->(:Attribute {name: "decision"})-[:HAS_VALUE]->(object_permission_decision:AttributeValue)
WHERE all(r IN relationships(decision_path) WHERE (%(branch_filter)s) AND r.status = "active")
""" % {
"branch_filter": branch_filter,
"account_group_node": InfrahubKind.ACCOUNTGROUP,
"account_role_node": InfrahubKind.ACCOUNTROLE,
"generic_account_node": InfrahubKind.GENERICACCOUNT,
"object_permission_node": InfrahubKind.OBJECTPERMISSION,
}

self.add_to_query(query)

self.return_labels = [
"object_permission",
"object_permission_branch",
"object_permission_namespace",
"object_permission_name",
"object_permission_action",
"object_permission_decision",
]

def get_permissions(self) -> list[ObjectPermission]:
permissions: list[ObjectPermission] = []
for result in self.get_results():
permissions.append(
ObjectPermission(
id=result.get("object_permission").get("uuid"),
branch=result.get("object_permission_branch").get("value"),
namespace=result.get("object_permission_namespace").get("value"),
name=result.get("object_permission_name").get("value"),
action=result.get("object_permission_action").get("value"),
decision=result.get("object_permission_decision").get("value"),
)
)

return permissions


async def fetch_permissions(account_id: str, db: InfrahubDatabase, branch: Branch) -> AssignedPermissions:
branch = await registry.get_branch(db=db, branch=branch)

query1 = await AccountGlobalPermissionQuery.init(db=db, branch=branch, account_id=account_id, branch_agnostic=True)
await query1.execute(db=db)
global_permissions = query1.get_permissions()

return {"global_permissions": global_permissions}
query2 = await AccountObjectPermissionQuery.init(db=db, branch=branch, account_id=account_id)
await query2.execute(db=db)
object_permissions = query2.get_permissions()

return {"global_permissions": global_permissions, "object_permissions": object_permissions}


class AccountTokenValidateQuery(Query):
Expand Down
13 changes: 13 additions & 0 deletions backend/infrahub/core/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ class GlobalPermissions(InfrahubStringEnum):
EDIT_DEFAULT_BRANCH = "edit_default_branch"


class PermissionAction(InfrahubStringEnum):
ANY = "any"
ADD = "create"
CHANGE = "update"
DELETE = "delete"
VIEW = "view"


class PermissionDecision(InfrahubStringEnum):
ALLOW = "allow"
DENY = "deny"


class AccountRole(InfrahubStringEnum):
ADMIN = "admin"
READ_ONLY = "read-only"
Expand Down
5 changes: 3 additions & 2 deletions backend/infrahub/core/constants/infrahubkind.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
ACCOUNT = "CoreAccount"
ACCOUNTGROUP = "CoreAccountGroup"
ACCOUNTROLE = "CoreAccountRole"
ACCOUNTTOKEN = "InternalAccountToken"
ARTIFACT = "CoreArtifact"
ARTIFACTCHECK = "CoreArtifactCheck"
Expand Down Expand Up @@ -37,6 +39,7 @@
NUMBERPOOL = "CoreNumberPool"
LINEAGEOWNER = "LineageOwner"
LINEAGESOURCE = "LineageSource"
OBJECTPERMISSION = "CoreObjectPermission"
OBJECTTHREAD = "CoreObjectThread"
PASSWORDCREDENTIAL = "CorePasswordCredential"
PROFILE = "CoreProfile"
Expand All @@ -59,8 +62,6 @@
TRANSFORM = "CoreTransformation"
TRANSFORMJINJA2 = "CoreTransformJinja2"
TRANSFORMPYTHON = "CoreTransformPython"
USERGROUP = "CoreUserGroup"
USERROLE = "CoreUserRole"
USERVALIDATOR = "CoreUserValidator"
VALIDATOR = "CoreValidator"
WEBHOOK = "CoreWebhook"
41 changes: 34 additions & 7 deletions backend/infrahub/core/initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

from infrahub import config, lock
from infrahub.core import registry
from infrahub.core.account import ObjectPermission
from infrahub.core.branch import Branch
from infrahub.core.constants import (
DEFAULT_IP_NAMESPACE,
GLOBAL_BRANCH_NAME,
AccountRole,
GlobalPermissions,
InfrahubKind,
PermissionAction,
PermissionDecision,
)
from infrahub.core.graph import GRAPH_VERSION
from infrahub.core.node import Node
Expand Down Expand Up @@ -288,22 +291,46 @@ async def create_ipam_namespace(
return obj


async def create_global_permissions(db: InfrahubDatabase) -> list[Node]:
async def create_initial_permissions(db: InfrahubDatabase) -> list[Node]:
objs: list[Node] = []

for permission in GlobalPermissions:
for global_permission in GlobalPermissions:
obj = await Node.init(db=db, schema=InfrahubKind.GLOBALPERMISSION)
await obj.new(db=db, name=format_label(permission.value), action=permission.value)
await obj.new(db=db, name=format_label(global_permission.value), action=global_permission.value)
await obj.save(db=db)
objs.append(obj)
log.info(f"Created global permission: {permission}")
log.info(f"Created global permission: {global_permission}")

for object_permission in [
# Allow anything for now to not break existing behaviour
ObjectPermission(
id="",
branch="any",
namespace="any",
name="any",
action=PermissionAction.ANY.value,
decision=PermissionDecision.ALLOW.value,
)
]:
obj = await Node.init(db=db, schema=InfrahubKind.OBJECTPERMISSION)
await obj.new(
db=db,
branch=object_permission.branch,
namespace=object_permission.namespace,
name=object_permission.name,
action=object_permission.action,
decision=object_permission.decision,
)
await obj.save(db=db)
objs.append(obj)
log.info(f"Created object permission: {object_permission!s}")

return objs


async def create_administrator_role(db: InfrahubDatabase, global_permissions: Optional[list[Node]] = None) -> Node:
role_name = "Administrator"
obj = await Node.init(db=db, schema=InfrahubKind.USERROLE)
obj = await Node.init(db=db, schema=InfrahubKind.ACCOUNTROLE)
await obj.new(db=db, name=role_name, permissions=global_permissions)
await obj.save(db=db)
log.info(f"Created User Role: {role_name}")
Expand All @@ -313,7 +340,7 @@ async def create_administrator_role(db: InfrahubDatabase, global_permissions: Op

async def create_administrators_group(db: InfrahubDatabase, role: Node, admin_accounts: list[CoreAccount]) -> Node:
group_name = "Administrators"
group = await Node.init(db=db, schema=InfrahubKind.USERGROUP)
group = await Node.init(db=db, schema=InfrahubKind.ACCOUNTGROUP)
await group.new(db=db, name=group_name, roles=[role])
await group.save(db=db)
log.info(f"Created User Group: {group_name}")
Expand Down Expand Up @@ -377,7 +404,7 @@ async def first_time_initialization(db: InfrahubDatabase) -> None:
# --------------------------------------------------
# Create Global Permissions and assign them
# --------------------------------------------------
global_permissions = await create_global_permissions(db=db)
global_permissions = await create_initial_permissions(db=db)
administrator_role = await create_administrator_role(db=db, global_permissions=global_permissions)
await create_administrators_group(db=db, role=administrator_role, admin_accounts=admin_accounts)

Expand Down
28 changes: 18 additions & 10 deletions backend/infrahub/core/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,16 @@ class CoreAccount(LineageOwner, LineageSource, CoreGenericAccount):
pass


class CoreAccountGroup(CoreGroup):
roles: RelationshipManager


class CoreAccountRole(CoreNode):
name: String
groups: RelationshipManager
permissions: RelationshipManager


class CoreArtifact(CoreTaskTarget):
name: String
status: Enum
Expand Down Expand Up @@ -360,6 +370,14 @@ class CoreNumberPool(CoreResourcePool, LineageSource):
end_range: Integer


class CoreObjectPermission(CoreBasePermission):
branch: String
namespace: String
name: String
action: Enum
decision: Enum


class CoreObjectThread(CoreThread):
object_path: String

Expand Down Expand Up @@ -431,16 +449,6 @@ class CoreTransformPython(CoreTransformation):
class_name: String


class CoreUserGroup(CoreGroup):
roles: RelationshipManager


class CoreUserRole(CoreNode):
name: String
groups: RelationshipManager
permissions: RelationshipManager


class CoreUserValidator(CoreValidator):
check_definition: RelationshipManager
repository: RelationshipManager
Expand Down
Loading

0 comments on commit 2549a0e

Please sign in to comment.