Skip to content

Commit 442635f

Browse files
continue refactoring
1 parent bae24fc commit 442635f

File tree

7 files changed

+115
-105
lines changed

7 files changed

+115
-105
lines changed

api/specs/web-server/_auth_api_keys.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ async def create_api_key(_body: ApiKeyCreate):
2929

3030
@router.get(
3131
"/auth/api-keys",
32-
operation_id="list_display_names",
33-
response_model=Envelope[list[str]],
32+
operation_id="list_api_keys",
33+
response_model=Envelope[list[ApiKeyGet]],
3434
status_code=status.HTTP_200_OK,
3535
)
3636
async def list_api_keys():

packages/models-library/src/models_library/api_schemas_webserver/auth.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class UnregisterCheck(InputSchema):
5252

5353

5454
class ApiKeyCreate(BaseModel):
55-
display_name: str = Field(..., min_length=3)
55+
display_name: Annotated[str, Field(..., min_length=3)]
5656
expiration: timedelta | None = Field(
5757
None,
5858
description="Time delta from creation time to expiration. If None, then it does not expire.",
@@ -81,8 +81,8 @@ class ApiKeyGet(BaseModel):
8181
id: int
8282
display_name: Annotated[str, Field(..., min_length=3)]
8383
api_base_url: HttpUrl | None = None
84-
api_key: str
85-
api_secret: str
84+
api_key: str | None = None
85+
api_secret: str | None = None
8686

8787
model_config = ConfigDict(
8888
populate_by_name=True,

services/web/server/src/simcore_service_webserver/api_keys/_api.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,6 @@
2020
_SECRET_LEN: Final = 20
2121

2222

23-
async def list_api_keys(
24-
app: web.Application,
25-
*,
26-
user_id: UserID,
27-
product_name: ProductName,
28-
) -> list[str]:
29-
names: list[str] = await _db.list_display_names(
30-
app, user_id=user_id, product_name=product_name
31-
)
32-
return names
33-
34-
3523
def _generate_api_key_and_secret(name: str):
3624
prefix = _PUNCTUATION_REGEX.sub("_", name[:5])
3725
api_key = f"{prefix}_{generate_token_secret_key(_KEY_LEN)}"
@@ -61,14 +49,26 @@ async def create_api_key(
6149
)
6250

6351

52+
async def get_api_keys(
53+
app: web.Application,
54+
*,
55+
user_id: UserID,
56+
product_name: ProductName,
57+
) -> list[ApiKey]:
58+
api_keys: list[ApiKey] = await _db.get_all(
59+
app, user_id=user_id, product_name=product_name
60+
)
61+
return api_keys
62+
63+
6464
async def get_api_key(
6565
app: web.Application,
6666
*,
6767
api_key_id: int,
6868
user_id: UserID,
6969
product_name: ProductName,
7070
) -> ApiKey:
71-
api_key = await _db.get(
71+
api_key: ApiKey | None = await _db.get(
7272
app, api_key_id=api_key_id, user_id=user_id, product_name=product_name
7373
)
7474
if api_key is not None:
@@ -86,18 +86,20 @@ async def get_or_create_api_key(
8686
expiration: dt.timedelta | None = None,
8787
) -> ApiKey:
8888

89-
api_key, api_secret = _generate_api_key_and_secret(display_name)
89+
key, secret = _generate_api_key_and_secret(display_name)
9090

91-
return await _db.get_or_create(
91+
api_key: ApiKey = await _db.get_or_create(
9292
app,
9393
user_id=user_id,
9494
product_name=product_name,
9595
display_name=display_name,
9696
expiration=expiration,
97-
api_key=api_key,
98-
api_secret=api_secret,
97+
api_key=key,
98+
api_secret=secret,
9999
)
100100

101+
return api_key
102+
101103

102104
async def delete_api_key(
103105
app: web.Application,

services/web/server/src/simcore_service_webserver/api_keys/_db.py

Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,6 @@
1616
_logger = logging.getLogger(__name__)
1717

1818

19-
async def list_display_names(
20-
app: web.Application,
21-
connection: AsyncConnection | None = None,
22-
*,
23-
user_id: UserID,
24-
product_name: ProductName,
25-
) -> list[str]:
26-
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
27-
stmt = sa.select(api_keys.c.display_name).where(
28-
(api_keys.c.user_id == user_id) & (api_keys.c.product_name == product_name)
29-
)
30-
31-
result = await conn.stream(stmt)
32-
rows = [row async for row in result]
33-
return [r.display_name for r in rows]
34-
35-
3619
async def create(
3720
app: web.Application,
3821
connection: AsyncConnection | None = None,
@@ -70,37 +53,6 @@ async def create(
7053
)
7154

7255

73-
async def get(
74-
app: web.Application,
75-
connection: AsyncConnection | None = None,
76-
*,
77-
api_key_id: int,
78-
user_id: UserID,
79-
product_name: ProductName,
80-
) -> ApiKey | None:
81-
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
82-
stmt = sa.select(api_keys).where(
83-
(api_keys.c.user_id == user_id)
84-
& (api_keys.c.id == api_key_id)
85-
& (api_keys.c.product_name == product_name)
86-
)
87-
88-
result = await conn.stream(stmt)
89-
row = await result.first()
90-
91-
return (
92-
ApiKey(
93-
id=row.id,
94-
display_name=row.display_name,
95-
expiration=row.expires_at,
96-
api_key=row.api_key,
97-
api_secret=row.api_secret,
98-
)
99-
if row
100-
else None
101-
)
102-
103-
10456
async def get_or_create(
10557
app: web.Application,
10658
connection: AsyncConnection | None = None,
@@ -147,6 +99,59 @@ async def get_or_create(
14799
)
148100

149101

102+
async def get_all(
103+
app: web.Application,
104+
connection: AsyncConnection | None = None,
105+
*,
106+
user_id: UserID,
107+
product_name: ProductName,
108+
) -> list[ApiKey]:
109+
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
110+
stmt = sa.select(api_keys.c.id, api_keys.c.display_name).where(
111+
(api_keys.c.user_id == user_id) & (api_keys.c.product_name == product_name)
112+
)
113+
114+
result = await conn.stream(stmt)
115+
rows = [row async for row in result]
116+
117+
return [
118+
ApiKey(
119+
id=row.id,
120+
display_name=row.display_name,
121+
)
122+
for row in rows
123+
]
124+
125+
126+
async def get(
127+
app: web.Application,
128+
connection: AsyncConnection | None = None,
129+
*,
130+
api_key_id: int,
131+
user_id: UserID,
132+
product_name: ProductName,
133+
) -> ApiKey | None:
134+
async with transaction_context(get_asyncpg_engine(app), connection) as conn:
135+
stmt = sa.select(api_keys).where(
136+
(api_keys.c.user_id == user_id)
137+
& (api_keys.c.id == api_key_id)
138+
& (api_keys.c.product_name == product_name)
139+
)
140+
141+
result = await conn.stream(stmt)
142+
row = await result.first()
143+
144+
return (
145+
ApiKey(
146+
id=row.id,
147+
display_name=row.display_name,
148+
expiration=row.expires_at,
149+
)
150+
if row
151+
else None
152+
)
153+
154+
150155
async def delete(
151156
app: web.Application,
152157
connection: AsyncConnection | None = None,

services/web/server/src/simcore_service_webserver/api_keys/_models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
class ApiKey:
77
id: str
88
display_name: str
9-
expiration: dt.timedelta
10-
api_key: str
11-
api_secret: str
9+
expiration: dt.timedelta | None = None
10+
api_key: str | None = None
11+
api_secret: str | None = None

services/web/server/src/simcore_service_webserver/api_keys/_rest.py

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from aiohttp.web import RouteTableDef
55
from models_library.api_schemas_webserver.auth import ApiKeyCreate, ApiKeyGet
66
from models_library.rest_base import StrictRequestParameters
7+
from pydantic import TypeAdapter
78
from servicelib.aiohttp import status
89
from servicelib.aiohttp.requests_validation import (
910
parse_request_body_as,
@@ -33,36 +34,6 @@ class ApiKeysPathParams(StrictRequestParameters):
3334
api_key_id: int
3435

3536

36-
@routes.get(f"/{API_VTAG}/auth/api-keys", name="list_api_keys")
37-
@login_required
38-
@permission_required("user.apikey.*")
39-
@handle_plugin_requests_exceptions
40-
async def list_api_keys(request: web.Request):
41-
req_ctx = RequestContext.model_validate(request)
42-
api_keys_names = await _api.list_api_keys(
43-
request.app,
44-
user_id=req_ctx.user_id,
45-
product_name=req_ctx.product_name,
46-
)
47-
return envelope_json_response(api_keys_names)
48-
49-
50-
@routes.get(f"/{API_VTAG}/auth/api-keys/{{api_key_id}}", name="get_api_key")
51-
@login_required
52-
@permission_required("user.apikey.*")
53-
@handle_plugin_requests_exceptions
54-
async def get_api_key(request: web.Request):
55-
req_ctx = RequestContext.model_validate(request)
56-
path_params = parse_request_path_parameters_as(ApiKeysPathParams, request)
57-
api_key: ApiKey = await _api.get_api_key(
58-
request.app,
59-
api_key_id=path_params.api_key_id,
60-
user_id=req_ctx.user_id,
61-
product_name=req_ctx.product_name,
62-
)
63-
return envelope_json_response(ApiKeyGet.model_validate(api_key))
64-
65-
6637
@routes.post(f"/{API_VTAG}/auth/api-keys", name="create_api_key")
6738
@login_required
6839
@permission_required("user.apikey.*")
@@ -90,6 +61,38 @@ async def create_api_key(request: web.Request):
9061
return envelope_json_response(api_key)
9162

9263

64+
@routes.get(f"/{API_VTAG}/auth/api-keys", name="get_api_keys")
65+
@login_required
66+
@permission_required("user.apikey.*")
67+
@handle_plugin_requests_exceptions
68+
async def get_api_keys(request: web.Request):
69+
req_ctx = RequestContext.model_validate(request)
70+
api_keys = await _api.get_api_keys(
71+
request.app,
72+
user_id=req_ctx.user_id,
73+
product_name=req_ctx.product_name,
74+
)
75+
return envelope_json_response(
76+
TypeAdapter(list[ApiKeyGet]).validate_python(api_keys)
77+
)
78+
79+
80+
@routes.get(f"/{API_VTAG}/auth/api-keys/{{api_key_id}}", name="get_api_key")
81+
@login_required
82+
@permission_required("user.apikey.*")
83+
@handle_plugin_requests_exceptions
84+
async def get_api_key(request: web.Request):
85+
req_ctx = RequestContext.model_validate(request)
86+
path_params = parse_request_path_parameters_as(ApiKeysPathParams, request)
87+
api_key: ApiKey = await _api.get_api_key(
88+
request.app,
89+
api_key_id=path_params.api_key_id,
90+
user_id=req_ctx.user_id,
91+
product_name=req_ctx.product_name,
92+
)
93+
return envelope_json_response(ApiKeyGet.model_validate(api_key))
94+
95+
9396
@routes.delete(f"/{API_VTAG}/auth/api-keys/{{api_key_id}}", name="delete_api_key")
9497
@login_required
9598
@permission_required("user.apikey.*")

services/web/server/tests/unit/with_dbs/01/test_api_keys.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ async def test_create_api_key(
110110

111111
resp = await client.get("/v0/auth/api-keys")
112112
data, _ = await assert_status(resp, expected)
113-
assert sorted(data) == [display_name]
113+
assert [d["display_name"] for d in data] == [display_name]
114114

115115

116116
@pytest.mark.parametrize(
@@ -162,7 +162,7 @@ async def test_create_api_key_with_expiration(
162162
# list created api-key
163163
resp = await client.get("/v0/auth/api-keys")
164164
data, _ = await assert_status(resp, expected)
165-
assert data == ["foo"]
165+
assert [d["display_name"] for d in data] == ["foo"]
166166

167167
# wait for api-key for it to expire and force-run scheduled task
168168
await asyncio.sleep(expiration_interval.seconds)

0 commit comments

Comments
 (0)