Skip to content

Commit 09deac7

Browse files
committed
feat(user): enforce role-based restrictions for manual user creation and modification
1 parent 88bdd6a commit 09deac7

2 files changed

Lines changed: 106 additions & 0 deletions

File tree

app/operation/user.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,9 +513,18 @@ async def _enforce_user_limits(
513513
if next_plan is not None and not features.can_use_next_plan:
514514
await self.raise_error(message="Next plan is not allowed for your role", code=403, db=db)
515515

516+
async def _enforce_manual_user_write_access(self, admin: AdminDetails, db: AsyncSession) -> None:
517+
if admin.is_owner or admin.role is None:
518+
return
519+
if admin.role.access.require_template:
520+
await self.raise_error(message="Manual user create/modify is not allowed for your role", code=403, db=db)
521+
516522
async def create_user(
517523
self, db: AsyncSession, new_user: UserCreate, admin: AdminDetails, *, skip_role_limits: bool = False
518524
) -> UserResponse:
525+
if not skip_role_limits:
526+
await self._enforce_manual_user_write_access(admin, db)
527+
519528
global_hwid_conf = await hwid_settings()
520529
effective_hwid_conf = resolve_effective_hwid_settings(
521530
global_hwid_conf,
@@ -728,6 +737,9 @@ async def _modify_user(
728737
*,
729738
skip_role_limits: bool = False,
730739
) -> UserNotificationResponse:
740+
if not skip_role_limits:
741+
await self._enforce_manual_user_write_access(admin, db)
742+
731743
validated_groups = await self._prepare_modified_user(
732744
db, db_user, modified_user, admin, skip_role_limits=skip_role_limits
733745
)

tests/api/test_user.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,24 @@ def _create_limited_user_creator_role(access_token: str) -> dict:
123123
return response.json()
124124

125125

126+
def _create_template_required_user_role(access_token: str, *, template_ids: list[int] | None = None) -> dict:
127+
response = client.post(
128+
"/api/admin-role",
129+
headers=auth_headers(access_token),
130+
json={
131+
"name": unique_name("template_required_user_role"),
132+
"permissions": {"users": {"create": True, "read": True, "update": True, "delete": True}},
133+
"access": {
134+
"require_template": True,
135+
"allowed_template_ids": template_ids,
136+
"allowed_group_ids": None,
137+
},
138+
},
139+
)
140+
assert response.status_code == status.HTTP_201_CREATED
141+
return response.json()
142+
143+
126144
def _delete_role(access_token: str, role_id: int) -> None:
127145
client.delete(f"/api/admin-role/{role_id}", headers=auth_headers(access_token))
128146

@@ -2018,6 +2036,49 @@ def test_create_user_with_template(access_token):
20182036
cleanup_groups(access_token, core, groups)
20192037

20202038

2039+
def test_role_requiring_template_cannot_manually_create_user(access_token):
2040+
core, groups = setup_groups(access_token, 1)
2041+
template = create_user_template(access_token, group_ids=[groups[0]["id"]])
2042+
role = _create_template_required_user_role(access_token, template_ids=[template["id"]])
2043+
admin = create_admin(access_token, role_id=role["id"])
2044+
admin_token = _login(admin["username"], admin["password"])
2045+
manual_username = unique_name("manual_template_required")
2046+
template_username = unique_name("template_required")
2047+
template_user_created = False
2048+
2049+
try:
2050+
manual_response = client.post(
2051+
"/api/user",
2052+
headers=auth_headers(admin_token),
2053+
json={
2054+
"username": manual_username,
2055+
"proxy_settings": {},
2056+
"data_limit": 1024 * 1024,
2057+
"data_limit_reset_strategy": "no_reset",
2058+
"status": "active",
2059+
"group_ids": [groups[0]["id"]],
2060+
},
2061+
)
2062+
assert manual_response.status_code == status.HTTP_403_FORBIDDEN
2063+
assert manual_response.json()["detail"] == "Manual user create/modify is not allowed for your role"
2064+
2065+
template_response = client.post(
2066+
"/api/user/from_template",
2067+
headers=auth_headers(admin_token),
2068+
json={"username": template_username, "user_template_id": template["id"]},
2069+
)
2070+
assert template_response.status_code == status.HTTP_201_CREATED
2071+
assert template_response.json()["username"] == template_username
2072+
template_user_created = True
2073+
finally:
2074+
if template_user_created:
2075+
delete_user(access_token, template_username)
2076+
delete_admin(access_token, admin["username"])
2077+
_delete_role(access_token, role["id"])
2078+
delete_user_template(access_token, template["id"])
2079+
cleanup_groups(access_token, core, groups)
2080+
2081+
20212082
def test_modify_user_with_template(access_token):
20222083
core, groups = setup_groups(access_token, 1)
20232084
template = create_user_template(access_token, group_ids=[groups[0]["id"]])
@@ -2043,6 +2104,39 @@ def test_modify_user_with_template(access_token):
20432104
cleanup_groups(access_token, core, groups)
20442105

20452106

2107+
def test_role_requiring_template_cannot_manually_modify_user(access_token):
2108+
core, groups = setup_groups(access_token, 1)
2109+
template = create_user_template(access_token, group_ids=[groups[0]["id"]])
2110+
role = _create_template_required_user_role(access_token, template_ids=[template["id"]])
2111+
admin = create_admin(access_token, role_id=role["id"])
2112+
admin_token = _login(admin["username"], admin["password"])
2113+
user = create_user(access_token, group_ids=[groups[0]["id"]], payload={"username": unique_name("tmpl_req_mod")})
2114+
2115+
try:
2116+
manual_response = client.put(
2117+
f"/api/user/{user['username']}",
2118+
headers=auth_headers(admin_token),
2119+
json={"data_limit": 2 * 1024 * 1024},
2120+
)
2121+
assert manual_response.status_code == status.HTTP_403_FORBIDDEN
2122+
assert manual_response.json()["detail"] == "Manual user create/modify is not allowed for your role"
2123+
2124+
template_response = client.put(
2125+
f"/api/user/from_template/{user['username']}",
2126+
headers=auth_headers(admin_token),
2127+
json={"user_template_id": template["id"]},
2128+
)
2129+
assert template_response.status_code == status.HTTP_200_OK
2130+
assert template_response.json()["data_limit"] == template["data_limit"]
2131+
assert template_response.json()["status"] == template["status"]
2132+
finally:
2133+
delete_user(access_token, user["username"])
2134+
delete_admin(access_token, admin["username"])
2135+
_delete_role(access_token, role["id"])
2136+
delete_user_template(access_token, template["id"])
2137+
cleanup_groups(access_token, core, groups)
2138+
2139+
20462140
def test_modify_user_with_template_does_not_reset_usage_when_hwid_limit_is_invalid(access_token):
20472141
core, groups = setup_groups(access_token, 1)
20482142
template = create_user_template(access_token, group_ids=[groups[0]["id"]], hwid_limit=2, reset_usages=True)

0 commit comments

Comments
 (0)