Skip to content

Commit

Permalink
Merge pull request #4646 from opsmill/pog-default-permissions
Browse files Browse the repository at this point in the history
Create default roles and cleanups
  • Loading branch information
ogenstad authored Oct 17, 2024
2 parents 5ecd9b3 + 32d1e55 commit bd9bae2
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 14 deletions.
8 changes: 7 additions & 1 deletion backend/infrahub/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,11 @@ async def authenticate_with_password(

now = datetime.now(tz=timezone.utc)
refresh_expires = now + timedelta(seconds=config.SETTINGS.security.refresh_token_lifetime)

# The read-only account role is deprecated and will only be used for anonymous access
role = "read-write" if account.role.value.value == "read-only" else account.role.value.value
session_id = await create_db_refresh_token(db=db, account_id=account.id, expiration=refresh_expires)
access_token = generate_access_token(account_id=account.id, role=account.role.value.value, session_id=session_id)
access_token = generate_access_token(account_id=account.id, role=role, session_id=session_id)
refresh_token = generate_refresh_token(account_id=account.id, session_id=session_id, expiration=refresh_expires)

return models.UserToken(access_token=access_token, refresh_token=refresh_token)
Expand Down Expand Up @@ -240,6 +243,9 @@ async def validate_api_key(db: InfrahubDatabase, token: str) -> AccountSession:

await validate_active_account(db=db, account_id=str(account_id))

# The read-only account role is deprecated and will only be used for anonymous access
role = "read-write" if role == "read-only" else role

return AccountSession(account_id=account_id, role=role, auth_type=AuthType.API)


Expand Down
75 changes: 75 additions & 0 deletions backend/infrahub/core/initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
AccountRole,
GlobalPermissions,
InfrahubKind,
PermissionAction,
PermissionDecision,
)
from infrahub.core.graph import GRAPH_VERSION
Expand Down Expand Up @@ -335,6 +336,79 @@ async def create_super_administrator_role(db: InfrahubDatabase) -> Node:
return obj


async def create_default_roles(db: InfrahubDatabase) -> Node:
repo_permission = await Node.init(db=db, schema=InfrahubKind.GLOBALPERMISSION)
await repo_permission.new(
db=db,
name=format_label(GlobalPermissions.MANAGE_REPOSITORIES.value),
action=GlobalPermissions.MANAGE_REPOSITORIES.value,
decision=PermissionDecision.ALLOW_ALL.value,
)
await repo_permission.save(db=db)

schema_permission = await Node.init(db=db, schema=InfrahubKind.GLOBALPERMISSION)
await schema_permission.new(
db=db,
name=format_label(GlobalPermissions.MANAGE_SCHEMA.value),
action=GlobalPermissions.MANAGE_SCHEMA.value,
decision=PermissionDecision.ALLOW_ALL.value,
)
await schema_permission.save(db=db)

proposed_change_permission = await Node.init(db=db, schema=InfrahubKind.GLOBALPERMISSION)
await proposed_change_permission.new(
db=db,
name=format_label(GlobalPermissions.MERGE_PROPOSED_CHANGE.value),
action=GlobalPermissions.MERGE_PROPOSED_CHANGE.value,
decision=PermissionDecision.ALLOW_ALL.value,
)
await proposed_change_permission.save(db=db)

view_permission = await Node.init(db=db, schema=InfrahubKind.OBJECTPERMISSION)
await view_permission.new(
db=db,
name="*",
namespace="*",
action=PermissionAction.VIEW.value,
decision=PermissionDecision.ALLOW_ALL.value,
)
await view_permission.save(db=db)

modify_permission = await Node.init(db=db, schema=InfrahubKind.OBJECTPERMISSION)
await modify_permission.new(
db=db,
name="*",
namespace="*",
action=PermissionAction.ANY.value,
decision=PermissionDecision.ALLOW_OTHER.value,
)
await modify_permission.save(db=db)

role_name = "General Access"
role = await Node.init(db=db, schema=InfrahubKind.ACCOUNTROLE)
await role.new(
db=db,
name=role_name,
permissions=[
repo_permission,
schema_permission,
proposed_change_permission,
view_permission,
modify_permission,
],
)
await role.save(db=db)
log.info(f"Created account role: {role_name}")

group_name = "Infrahub Users"
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 account group: {group_name}")

return role


async def create_super_administrators_group(
db: InfrahubDatabase, role: Node, admin_accounts: list[CoreAccount]
) -> Node:
Expand Down Expand Up @@ -411,6 +485,7 @@ async def first_time_initialization(db: InfrahubDatabase) -> None:
administrator_role = await create_super_administrator_role(db=db)
await create_super_administrators_group(db=db, role=administrator_role, admin_accounts=admin_accounts)

await create_default_roles(db=db)
# --------------------------------------------------
# Create Default IPAM Namespace
# --------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions backend/infrahub/graphql/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ def __init__(
self.query_variables: dict[str, Any] = query_variables or {}
super().__init__(query=query, schema=schema)

@property
def operation_names(self) -> list[str]:
return [operation.name for operation in self.operations if operation.name is not None]

async def get_models_in_use(self, types: dict[str, Any]) -> set[str]:
"""List of Infrahub models that are referenced in the query."""
graphql_types = set()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ class DefaultBranchPermissionChecker(GraphQLQueryPermissionCheckerInterface):
permission_required = GlobalPermission(
id="", name="", action=GlobalPermissions.EDIT_DEFAULT_BRANCH.value, decision=PermissionDecision.ALLOW_ALL.value
)
exempt_operations = ["BranchCreate"]
exempt_operations = [
"BranchCreate",
"DiffUpdate",
"InfrahubAccountSelfUpdate",
"InfrahubAccountTokenCreate",
"InfrahubAccountTokenDelete",
]

async def supports(self, db: InfrahubDatabase, account_session: AccountSession, branch: Branch) -> bool:
return account_session.authenticated
Expand All @@ -42,9 +48,8 @@ async def check(
GLOBAL_BRANCH_NAME,
registry.default_branch,
)
is_exempt_operation = analyzed_query.operation_name is not None and (
analyzed_query.operation_name in self.exempt_operations
or analyzed_query.operation_name.startswith("InfrahubAccount") # Allow user to manage self
is_exempt_operation = all(
operation_name in self.exempt_operations for operation_name in analyzed_query.operation_names
)

if (
Expand Down
2 changes: 1 addition & 1 deletion backend/infrahub/graphql/mutations/proposed_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ async def mutate_update( # pylint: disable=too-many-branches
permission=GlobalPermission(
id="",
name="",
action=GlobalPermissions.EDIT_DEFAULT_BRANCH.value,
action=GlobalPermissions.MERGE_PROPOSED_CHANGE.value,
decision=PermissionDecision.ALLOW_ALL.value,
),
branch=branch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ async def test_account_with_permission(
graphql_query.branch = MagicMock()
graphql_query.branch.name = branch_name
graphql_query.contains_mutation = contains_mutation
graphql_query.operation_name = "CreateTags"
graphql_query.operation_names = ["CreateTags"]

resolution = await checker.check(
db=db,
Expand All @@ -114,7 +114,7 @@ async def test_account_without_permission(
graphql_query.branch = MagicMock()
graphql_query.branch.name = branch_name
graphql_query.contains_mutation = contains_mutation
graphql_query.operation_name = "CreateTags"
graphql_query.operation_names = ["CreateTags"]

if not contains_mutation or branch_name != "main":
resolution = await checker.check(
Expand Down
12 changes: 6 additions & 6 deletions frontend/app/tests/e2e/role-management/read.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ test.describe("Role management - READ", () => {

await test.step("check counts", async () => {
await expect(page.getByRole("link", { name: "Accounts 9" })).toBeVisible();
await expect(page.getByRole("link", { name: "Groups 1" })).toBeVisible();
await expect(page.getByRole("link", { name: "Roles 1" })).toBeVisible();
await expect(page.getByRole("link", { name: "Global Permissions 1" })).toBeVisible();
await expect(page.getByRole("link", { name: "Object Permissions 0" })).toBeVisible();
await expect(page.getByRole("link", { name: "Groups 2" })).toBeVisible();
await expect(page.getByRole("link", { name: "Roles 2" })).toBeVisible();
await expect(page.getByRole("link", { name: "Global Permissions 4" })).toBeVisible();
await expect(page.getByRole("link", { name: "Object Permissions 2" })).toBeVisible();
});

await test.step("check accounts view", async () => {
Expand All @@ -20,13 +20,13 @@ test.describe("Role management - READ", () => {
});

await test.step("check groups view", async () => {
await page.getByRole("link", { name: "Groups 1" }).click();
await page.getByRole("link", { name: "Groups 2" }).click();
await expect(page.getByRole("cell", { name: "Administrators" })).toBeVisible();
await expect(page.getByRole("cell", { name: "+ 4" })).toBeVisible();
});

await test.step("check roles view", async () => {
await page.getByRole("link", { name: "Roles 1" }).click();
await page.getByRole("link", { name: "Roles 2" }).click();
await expect(page.getByRole("cell", { name: "Super Administrator" })).toBeVisible();
await expect(page.getByRole("cell", { name: "1" }).first()).toBeVisible();
});
Expand Down

0 comments on commit bd9bae2

Please sign in to comment.