Skip to content

Commit bd9bae2

Browse files
authored
Merge pull request #4646 from opsmill/pog-default-permissions
Create default roles and cleanups
2 parents 5ecd9b3 + 32d1e55 commit bd9bae2

File tree

7 files changed

+104
-14
lines changed

7 files changed

+104
-14
lines changed

backend/infrahub/auth.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,11 @@ async def authenticate_with_password(
8585

8686
now = datetime.now(tz=timezone.utc)
8787
refresh_expires = now + timedelta(seconds=config.SETTINGS.security.refresh_token_lifetime)
88+
89+
# The read-only account role is deprecated and will only be used for anonymous access
90+
role = "read-write" if account.role.value.value == "read-only" else account.role.value.value
8891
session_id = await create_db_refresh_token(db=db, account_id=account.id, expiration=refresh_expires)
89-
access_token = generate_access_token(account_id=account.id, role=account.role.value.value, session_id=session_id)
92+
access_token = generate_access_token(account_id=account.id, role=role, session_id=session_id)
9093
refresh_token = generate_refresh_token(account_id=account.id, session_id=session_id, expiration=refresh_expires)
9194

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

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

246+
# The read-only account role is deprecated and will only be used for anonymous access
247+
role = "read-write" if role == "read-only" else role
248+
243249
return AccountSession(account_id=account_id, role=role, auth_type=AuthType.API)
244250

245251

backend/infrahub/core/initialization.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
AccountRole,
1212
GlobalPermissions,
1313
InfrahubKind,
14+
PermissionAction,
1415
PermissionDecision,
1516
)
1617
from infrahub.core.graph import GRAPH_VERSION
@@ -335,6 +336,79 @@ async def create_super_administrator_role(db: InfrahubDatabase) -> Node:
335336
return obj
336337

337338

339+
async def create_default_roles(db: InfrahubDatabase) -> Node:
340+
repo_permission = await Node.init(db=db, schema=InfrahubKind.GLOBALPERMISSION)
341+
await repo_permission.new(
342+
db=db,
343+
name=format_label(GlobalPermissions.MANAGE_REPOSITORIES.value),
344+
action=GlobalPermissions.MANAGE_REPOSITORIES.value,
345+
decision=PermissionDecision.ALLOW_ALL.value,
346+
)
347+
await repo_permission.save(db=db)
348+
349+
schema_permission = await Node.init(db=db, schema=InfrahubKind.GLOBALPERMISSION)
350+
await schema_permission.new(
351+
db=db,
352+
name=format_label(GlobalPermissions.MANAGE_SCHEMA.value),
353+
action=GlobalPermissions.MANAGE_SCHEMA.value,
354+
decision=PermissionDecision.ALLOW_ALL.value,
355+
)
356+
await schema_permission.save(db=db)
357+
358+
proposed_change_permission = await Node.init(db=db, schema=InfrahubKind.GLOBALPERMISSION)
359+
await proposed_change_permission.new(
360+
db=db,
361+
name=format_label(GlobalPermissions.MERGE_PROPOSED_CHANGE.value),
362+
action=GlobalPermissions.MERGE_PROPOSED_CHANGE.value,
363+
decision=PermissionDecision.ALLOW_ALL.value,
364+
)
365+
await proposed_change_permission.save(db=db)
366+
367+
view_permission = await Node.init(db=db, schema=InfrahubKind.OBJECTPERMISSION)
368+
await view_permission.new(
369+
db=db,
370+
name="*",
371+
namespace="*",
372+
action=PermissionAction.VIEW.value,
373+
decision=PermissionDecision.ALLOW_ALL.value,
374+
)
375+
await view_permission.save(db=db)
376+
377+
modify_permission = await Node.init(db=db, schema=InfrahubKind.OBJECTPERMISSION)
378+
await modify_permission.new(
379+
db=db,
380+
name="*",
381+
namespace="*",
382+
action=PermissionAction.ANY.value,
383+
decision=PermissionDecision.ALLOW_OTHER.value,
384+
)
385+
await modify_permission.save(db=db)
386+
387+
role_name = "General Access"
388+
role = await Node.init(db=db, schema=InfrahubKind.ACCOUNTROLE)
389+
await role.new(
390+
db=db,
391+
name=role_name,
392+
permissions=[
393+
repo_permission,
394+
schema_permission,
395+
proposed_change_permission,
396+
view_permission,
397+
modify_permission,
398+
],
399+
)
400+
await role.save(db=db)
401+
log.info(f"Created account role: {role_name}")
402+
403+
group_name = "Infrahub Users"
404+
group = await Node.init(db=db, schema=InfrahubKind.ACCOUNTGROUP)
405+
await group.new(db=db, name=group_name, roles=[role])
406+
await group.save(db=db)
407+
log.info(f"Created account group: {group_name}")
408+
409+
return role
410+
411+
338412
async def create_super_administrators_group(
339413
db: InfrahubDatabase, role: Node, admin_accounts: list[CoreAccount]
340414
) -> Node:
@@ -411,6 +485,7 @@ async def first_time_initialization(db: InfrahubDatabase) -> None:
411485
administrator_role = await create_super_administrator_role(db=db)
412486
await create_super_administrators_group(db=db, role=administrator_role, admin_accounts=admin_accounts)
413487

488+
await create_default_roles(db=db)
414489
# --------------------------------------------------
415490
# Create Default IPAM Namespace
416491
# --------------------------------------------------

backend/infrahub/graphql/analyzer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ def __init__(
2222
self.query_variables: dict[str, Any] = query_variables or {}
2323
super().__init__(query=query, schema=schema)
2424

25+
@property
26+
def operation_names(self) -> list[str]:
27+
return [operation.name for operation in self.operations if operation.name is not None]
28+
2529
async def get_models_in_use(self, types: dict[str, Any]) -> set[str]:
2630
"""List of Infrahub models that are referenced in the query."""
2731
graphql_types = set()

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ class DefaultBranchPermissionChecker(GraphQLQueryPermissionCheckerInterface):
1717
permission_required = GlobalPermission(
1818
id="", name="", action=GlobalPermissions.EDIT_DEFAULT_BRANCH.value, decision=PermissionDecision.ALLOW_ALL.value
1919
)
20-
exempt_operations = ["BranchCreate"]
20+
exempt_operations = [
21+
"BranchCreate",
22+
"DiffUpdate",
23+
"InfrahubAccountSelfUpdate",
24+
"InfrahubAccountTokenCreate",
25+
"InfrahubAccountTokenDelete",
26+
]
2127

2228
async def supports(self, db: InfrahubDatabase, account_session: AccountSession, branch: Branch) -> bool:
2329
return account_session.authenticated
@@ -42,9 +48,8 @@ async def check(
4248
GLOBAL_BRANCH_NAME,
4349
registry.default_branch,
4450
)
45-
is_exempt_operation = analyzed_query.operation_name is not None and (
46-
analyzed_query.operation_name in self.exempt_operations
47-
or analyzed_query.operation_name.startswith("InfrahubAccount") # Allow user to manage self
51+
is_exempt_operation = all(
52+
operation_name in self.exempt_operations for operation_name in analyzed_query.operation_names
4853
)
4954

5055
if (

backend/infrahub/graphql/mutations/proposed_change.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ async def mutate_update( # pylint: disable=too-many-branches
112112
permission=GlobalPermission(
113113
id="",
114114
name="",
115-
action=GlobalPermissions.EDIT_DEFAULT_BRANCH.value,
115+
action=GlobalPermissions.MERGE_PROPOSED_CHANGE.value,
116116
decision=PermissionDecision.ALLOW_ALL.value,
117117
),
118118
branch=branch,

backend/tests/unit/graphql/auth/query_permission_checker/test_default_branch_checker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ async def test_account_with_permission(
8787
graphql_query.branch = MagicMock()
8888
graphql_query.branch.name = branch_name
8989
graphql_query.contains_mutation = contains_mutation
90-
graphql_query.operation_name = "CreateTags"
90+
graphql_query.operation_names = ["CreateTags"]
9191

9292
resolution = await checker.check(
9393
db=db,
@@ -114,7 +114,7 @@ async def test_account_without_permission(
114114
graphql_query.branch = MagicMock()
115115
graphql_query.branch.name = branch_name
116116
graphql_query.contains_mutation = contains_mutation
117-
graphql_query.operation_name = "CreateTags"
117+
graphql_query.operation_names = ["CreateTags"]
118118

119119
if not contains_mutation or branch_name != "main":
120120
resolution = await checker.check(

frontend/app/tests/e2e/role-management/read.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ test.describe("Role management - READ", () => {
88

99
await test.step("check counts", async () => {
1010
await expect(page.getByRole("link", { name: "Accounts 9" })).toBeVisible();
11-
await expect(page.getByRole("link", { name: "Groups 1" })).toBeVisible();
12-
await expect(page.getByRole("link", { name: "Roles 1" })).toBeVisible();
13-
await expect(page.getByRole("link", { name: "Global Permissions 1" })).toBeVisible();
14-
await expect(page.getByRole("link", { name: "Object Permissions 0" })).toBeVisible();
11+
await expect(page.getByRole("link", { name: "Groups 2" })).toBeVisible();
12+
await expect(page.getByRole("link", { name: "Roles 2" })).toBeVisible();
13+
await expect(page.getByRole("link", { name: "Global Permissions 4" })).toBeVisible();
14+
await expect(page.getByRole("link", { name: "Object Permissions 2" })).toBeVisible();
1515
});
1616

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

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

2828
await test.step("check roles view", async () => {
29-
await page.getByRole("link", { name: "Roles 1" }).click();
29+
await page.getByRole("link", { name: "Roles 2" }).click();
3030
await expect(page.getByRole("cell", { name: "Super Administrator" })).toBeVisible();
3131
await expect(page.getByRole("cell", { name: "1" }).first()).toBeVisible();
3232
});

0 commit comments

Comments
 (0)