Skip to content

Commit c209772

Browse files
authored
feat: include is_admin in the self /user endpoint (#472)
Closes #287.
1 parent 7237310 commit c209772

4 files changed

Lines changed: 103 additions & 3 deletions

File tree

components/renku_data_services/users/api.spec.yaml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ paths:
1919
content:
2020
"application/json":
2121
schema:
22-
$ref: "#/components/schemas/UserWithId"
22+
$ref: "#/components/schemas/SelfUserInfo"
2323
default:
2424
$ref: "#/components/responses/Error"
2525
tags:
@@ -343,6 +343,29 @@ components:
343343
items:
344344
$ref: "#/components/schemas/UserWithId"
345345
uniqueItems: true
346+
SelfUserInfo:
347+
description: Information about the currently logged in user
348+
type: object
349+
additionalProperties: false
350+
properties:
351+
id:
352+
$ref: "#/components/schemas/UserId"
353+
username:
354+
$ref: "#/components/schemas/Username"
355+
email:
356+
$ref: "#/components/schemas/UserEmail"
357+
first_name:
358+
$ref: "#/components/schemas/UserFirstLastName"
359+
last_name:
360+
$ref: "#/components/schemas/UserFirstLastName"
361+
is_admin:
362+
description: Whether the user is a platform administrator or not
363+
type: boolean
364+
default: false
365+
required:
366+
- id
367+
- username
368+
- is_admin
346369
UserSecretKey:
347370
type: object
348371
additionalProperties: false

components/renku_data_services/users/apispec.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# generated by datamodel-codegen:
22
# filename: api.spec.yaml
3-
# timestamp: 2024-09-12T12:43:38+00:00
3+
# timestamp: 2024-10-18T13:00:24+00:00
44

55
from __future__ import annotations
66

@@ -140,6 +140,45 @@ class UsersWithId(RootModel[List[UserWithId]]):
140140
root: List[UserWithId]
141141

142142

143+
class SelfUserInfo(BaseAPISpec):
144+
model_config = ConfigDict(
145+
extra="forbid",
146+
)
147+
id: str = Field(
148+
...,
149+
description="Keycloak user ID",
150+
example="f74a228b-1790-4276-af5f-25c2424e9b0c",
151+
pattern="^[A-Za-z0-9]{1}[A-Za-z0-9-]+$",
152+
)
153+
username: str = Field(
154+
...,
155+
description="Handle of the user",
156+
example="some-username",
157+
max_length=99,
158+
min_length=1,
159+
)
160+
email: Optional[str] = Field(
161+
None, description="User email", example="some-user@gmail.com"
162+
)
163+
first_name: Optional[str] = Field(
164+
None,
165+
description="First or last name of the user",
166+
example="John",
167+
max_length=256,
168+
min_length=1,
169+
)
170+
last_name: Optional[str] = Field(
171+
None,
172+
description="First or last name of the user",
173+
example="John",
174+
max_length=256,
175+
min_length=1,
176+
)
177+
is_admin: bool = Field(
178+
False, description="Whether the user is a platform administrator or not"
179+
)
180+
181+
143182
class SecretWithId(BaseAPISpec):
144183
model_config = ConfigDict(
145184
extra="forbid",

components/renku_data_services/users/blueprints.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,14 @@ async def _get_self(_: Request, user: base_models.APIUser) -> JSONResponse:
6363
if not user_info:
6464
raise errors.MissingResourceError(message=f"The user with ID {user.id} cannot be found.")
6565
return validated_json(
66-
apispec.UserWithId,
66+
apispec.SelfUserInfo,
6767
dict(
6868
id=user_info.id,
6969
username=user_info.namespace.slug,
7070
email=user_info.email,
7171
first_name=user_info.first_name,
7272
last_name=user_info.last_name,
73+
is_admin=user.is_admin,
7374
),
7475
)
7576

test/bases/renku_data_services/data_api/test_users.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,40 @@ async def test_delete_user(sanic_client, admin_headers) -> None:
272272
for iuser in res.json
273273
]
274274
assert user not in users_response
275+
276+
277+
@pytest.mark.asyncio
278+
async def test_get_self_user(sanic_client, user_headers, regular_user) -> None:
279+
_, response = await sanic_client.get("/api/data/user", headers=user_headers)
280+
281+
assert response.status_code == 200, response.text
282+
assert response.json is not None
283+
user_info = response.json
284+
assert user_info.get("id") == regular_user.id
285+
assert user_info.get("username") == regular_user.namespace.slug
286+
assert user_info.get("email") == regular_user.email
287+
assert user_info.get("first_name") == regular_user.first_name
288+
assert user_info.get("last_name") == regular_user.last_name
289+
assert user_info.get("is_admin") is False
290+
291+
292+
@pytest.mark.asyncio
293+
async def test_get_self_user_as_admin(sanic_client, admin_headers, admin_user) -> None:
294+
_, response = await sanic_client.get("/api/data/user", headers=admin_headers)
295+
296+
assert response.status_code == 200, response.text
297+
assert response.json is not None
298+
user_info = response.json
299+
assert user_info.get("id") == admin_user.id
300+
assert user_info.get("username") == admin_user.namespace.slug
301+
assert user_info.get("email") == admin_user.email
302+
assert user_info.get("first_name") == admin_user.first_name
303+
assert user_info.get("last_name") == admin_user.last_name
304+
assert user_info.get("is_admin") is True
305+
306+
307+
@pytest.mark.asyncio
308+
async def test_get_self_user_unauthenticated(sanic_client) -> None:
309+
_, response = await sanic_client.get("/api/data/user")
310+
311+
assert response.status_code == 401, response.text

0 commit comments

Comments
 (0)