diff --git a/backend/infrahub/api/menu.py b/backend/infrahub/api/menu.py index 13202351ae..d94894371a 100644 --- a/backend/infrahub/api/menu.py +++ b/backend/infrahub/api/menu.py @@ -209,6 +209,21 @@ async def get_menu(branch: Branch = Depends(get_branch_dep)) -> list[InterfaceMe path=f"/objects/{InfrahubKind.ACCOUNT}", icon=_extract_node_icon(full_schema[InfrahubKind.ACCOUNT]), ), + InterfaceMenu( + title="User Groups", + path=f"/objects/{InfrahubKind.USERGROUP}", + icon=_extract_node_icon(full_schema[InfrahubKind.USERGROUP]), + ), + InterfaceMenu( + title="User Roles", + path=f"/objects/{InfrahubKind.USERROLE}", + icon=_extract_node_icon(full_schema[InfrahubKind.USERROLE]), + ), + InterfaceMenu( + title="Permissions", + path=f"/objects/{InfrahubKind.BASEPERMISSION}", + icon=_extract_node_icon(full_schema[InfrahubKind.BASEPERMISSION]), + ), InterfaceMenu( title="Webhooks", children=[ diff --git a/backend/infrahub/core/constants/__init__.py b/backend/infrahub/core/constants/__init__.py index 72d67b02d3..a80652729f 100644 --- a/backend/infrahub/core/constants/__init__.py +++ b/backend/infrahub/core/constants/__init__.py @@ -50,6 +50,16 @@ class PermissionLevel(enum.Flag): DEFAULT = 0 +class GlobalPermissions(InfrahubStringEnum): + EDIT_DEFAULT_BRANCH = "edit_default_branch" + MANAGE_USERS = "manage_users" + MANAGE_PERMISSIONS = "manage_permissions" + MANAGE_SCHEMA = "manage_schema" + MANAGE_REPOSITORIES = "manage_repositories" + MANAGE_ARTIFACTS = "manage_artifacts" + MERGE_PROPOSED_CHANGE = "merge_proposed_change" + + class AccountRole(InfrahubStringEnum): ADMIN = "admin" READ_ONLY = "read-only" diff --git a/backend/infrahub/core/constants/infrahubkind.py b/backend/infrahub/core/constants/infrahubkind.py index ccb3edefac..5143ddcc4d 100644 --- a/backend/infrahub/core/constants/infrahubkind.py +++ b/backend/infrahub/core/constants/infrahubkind.py @@ -34,6 +34,7 @@ NUMBERPOOL = "CoreNumberPool" LINEAGEOWNER = "LineageOwner" LINEAGESOURCE = "LineageSource" +OBJECTPERMISSION = "CoreObjectPermission" OBJECTTHREAD = "CoreObjectThread" PROFILE = "CoreProfile" PROPOSEDCHANGE = "CoreProposedChange" @@ -55,7 +56,6 @@ TRANSFORMJINJA2 = "CoreTransformJinja2" TRANSFORMPYTHON = "CoreTransformPython" USERGROUP = "CoreUserGroup" -USERPERMISSION = "CoreUserPermission" USERROLE = "CoreUserRole" USERVALIDATOR = "CoreUserValidator" VALIDATOR = "CoreValidator" diff --git a/backend/infrahub/core/initialization.py b/backend/infrahub/core/initialization.py index fed4ddf9b7..3f059bc0cd 100644 --- a/backend/infrahub/core/initialization.py +++ b/backend/infrahub/core/initialization.py @@ -4,7 +4,13 @@ from infrahub import config, lock from infrahub.core import registry from infrahub.core.branch import Branch -from infrahub.core.constants import DEFAULT_IP_NAMESPACE, GLOBAL_BRANCH_NAME, AccountRole, InfrahubKind +from infrahub.core.constants import ( + DEFAULT_IP_NAMESPACE, + GLOBAL_BRANCH_NAME, + AccountRole, + GlobalPermissions, + InfrahubKind, +) from infrahub.core.graph import GRAPH_VERSION from infrahub.core.node import Node from infrahub.core.node.ipam import BuiltinIPPrefix @@ -18,6 +24,7 @@ from infrahub.exceptions import DatabaseError from infrahub.log import get_logger from infrahub.storage import InfrahubObjectStorage +from infrahub.utils import format_label log = get_logger() @@ -272,14 +279,13 @@ async def create_ipam_namespace( async def create_global_permissions(db: InfrahubDatabase) -> list[CoreGlobalPermission]: objs: list[CoreGlobalPermission] = [] - actions = [("Edit default branch", "edit_default_branch")] - for name, action in actions: + for permission in GlobalPermissions: obj: CoreGlobalPermission = await Node.init(db=db, schema=InfrahubKind.GLOBALPERMISSION) - await obj.new(db=db, name=name, action=action) + await obj.new(db=db, name=format_label(permission.value), action=permission.value) await obj.save(db=db) objs.append(obj) - log.info(f"Created global permission: {name}") + log.info(f"Created global permission: {permission}") return objs diff --git a/backend/infrahub/core/protocols.py b/backend/infrahub/core/protocols.py index cef776cb1a..1db433237f 100644 --- a/backend/infrahub/core/protocols.py +++ b/backend/infrahub/core/protocols.py @@ -303,7 +303,7 @@ class CoreGeneratorValidator(CoreValidator): class CoreGlobalPermission(CoreBasePermission): name: String - action: String + action: Dropdown class CoreGraphQLQuery(CoreNode): @@ -346,6 +346,11 @@ class CoreNumberPool(CoreResourcePool, LineageSource): end_range: Integer +class CoreObjectPermission(CoreBasePermission): + kind: String + action: Enum + + class CoreObjectThread(CoreThread): object_path: String @@ -417,11 +422,6 @@ class CoreUserGroup(CoreNode): roles: RelationshipManager -class CoreUserPermission(CoreBasePermission): - kind: String - action: Enum - - class CoreUserRole(CoreNode): name: String groups: RelationshipManager diff --git a/backend/infrahub/core/schema/definitions/core.py b/backend/infrahub/core/schema/definitions/core.py index 7528e8c1c3..3655e6dd0e 100644 --- a/backend/infrahub/core/schema/definitions/core.py +++ b/backend/infrahub/core/schema/definitions/core.py @@ -11,6 +11,7 @@ BranchSupportType, ContentType, GeneratorInstanceStatus, + GlobalPermissions, InfrahubKind, ProposedChangeState, RelationshipDeleteBehavior, @@ -702,7 +703,7 @@ "description": "A permission grants right to a user", "label": "Base permission", "icon": "mdi:user-key", - "include_in_menu": True, + "include_in_menu": False, "generate_profile": False, "relationships": [ { @@ -1898,14 +1899,19 @@ "inherit_from": [InfrahubKind.BASEPERMISSION], "attributes": [ {"name": "name", "kind": "Text", "unique": True, "order_weight": 1000}, - {"name": "action", "kind": "Text", "order_weight": 2000}, + { + "name": "action", + "kind": "Dropdown", + "choices": [{"name": permission.value} for permission in GlobalPermissions], + "order_weight": 2000, + }, ], }, { - "name": "UserPermission", + "name": "ObjectPermission", "namespace": "Core", "description": "A permission that grants rights to perform actions on objects", - "label": "User permission", + "label": "Object permission", "include_in_menu": False, "order_by": ["kind__value", "action__value"], "display_labels": ["kind__value", "action__value"], @@ -1928,7 +1934,7 @@ "description": "A role defines a set of permissions to grant to a group of users", "label": "User role", "icon": "mdi:user-badge", - "include_in_menu": True, + "include_in_menu": False, "order_by": ["name__value"], "display_labels": ["name__value"], "generate_profile": False, @@ -1958,7 +1964,7 @@ "description": "A group of users to manage common permissions", "label": "User group", "icon": "mdi:account-group", - "include_in_menu": True, + "include_in_menu": False, "order_by": ["name__value"], "display_labels": ["name__value"], "generate_profile": False, diff --git a/backend/infrahub/graphql/auth/query_permission_checker/default_checker.py b/backend/infrahub/graphql/auth/query_permission_checker/default_checker.py index a1f6d4a675..0544bcf911 100644 --- a/backend/infrahub/graphql/auth/query_permission_checker/default_checker.py +++ b/backend/infrahub/graphql/auth/query_permission_checker/default_checker.py @@ -2,6 +2,7 @@ from infrahub import config from infrahub.auth import AccountSession +from infrahub.core.constants import GLOBAL_BRANCH_NAME, GlobalPermissions from infrahub.exceptions import AuthorizationError, PermissionDeniedError from infrahub.graphql.analyzer import InfrahubGraphQLQueryAnalyzer @@ -22,14 +23,14 @@ def __init__(self) -> None: async def supports(self, account_session: AccountSession) -> bool: if account_session.permissions: - self.can_edit_default_branch = "edit_default_branch" in account_session.permissions + self.can_edit_default_branch = GlobalPermissions.EDIT_DEFAULT_BRANCH.value in account_session.permissions return account_session.authenticated async def check(self, analyzed_query: InfrahubGraphQLQueryAnalyzer) -> None: for operation in analyzed_query.operations: if ( not self.can_edit_default_branch - and analyzed_query.branch.name == config.SETTINGS.initial.default_branch + and analyzed_query.branch.name in (GLOBAL_BRANCH_NAME, config.SETTINGS.initial.default_branch) and operation.operation_type == OperationType.MUTATION ): raise PermissionDeniedError( diff --git a/python_sdk/infrahub_sdk/protocols.py b/python_sdk/infrahub_sdk/protocols.py index ad8297896b..6173e0a759 100644 --- a/python_sdk/infrahub_sdk/protocols.py +++ b/python_sdk/infrahub_sdk/protocols.py @@ -336,6 +336,11 @@ class CoreNumberPool(CoreResourcePool, LineageSource): end_range: int +class CoreObjectPermission(CoreBasePermission): + kind: str + action: str + + class CoreObjectThread(CoreThread): object_path: str @@ -407,11 +412,6 @@ class CoreUserGroup(CoreNode): roles: Union[RelationshipManager, RelationshipManagerSync] -class CoreUserPermission(CoreBasePermission): - kind: str - action: str - - class CoreUserRole(CoreNode): name: str groups: Union[RelationshipManager, RelationshipManagerSync]