Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ Refactor API-keys service #6843

Merged
Merged
Show file tree
Hide file tree
Changes from 74 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
4439836
rename rest handler module
giancarloromeo Nov 26, 2024
2b3fb09
begin refactor
giancarloromeo Nov 28, 2024
7d0f37c
Merge branch 'master' into refactor-api-keys-service
giancarloromeo Nov 28, 2024
8aac0bf
refactor delete
giancarloromeo Nov 29, 2024
6579e4c
Merge branch 'master' into refactor-api-keys-service
giancarloromeo Nov 29, 2024
6852510
add id
giancarloromeo Nov 29, 2024
d10edfb
use list comprehension
giancarloromeo Nov 29, 2024
26e33f9
add base_url
giancarloromeo Nov 29, 2024
71c334c
services/webserver api version: 0.46.0 → 0.47.0
giancarloromeo Nov 29, 2024
9b30844
update openapi-spec
giancarloromeo Nov 29, 2024
25e96b0
continue refactor
giancarloromeo Dec 2, 2024
3e6b3a9
fix query param name
giancarloromeo Dec 2, 2024
cd416cc
refactor rpc
giancarloromeo Dec 2, 2024
de81e9c
refactor exception handling
giancarloromeo Dec 2, 2024
08183d6
update API spec
giancarloromeo Dec 2, 2024
a566850
Merge branch 'master' into refactor-api-keys-service
giancarloromeo Dec 3, 2024
fc73d01
fix exceptions handlers import
giancarloromeo Dec 3, 2024
03aa944
fix api_base_url generation
giancarloromeo Dec 3, 2024
1c5ced2
connect related issue
giancarloromeo Dec 3, 2024
d0b14e8
add test
giancarloromeo Dec 3, 2024
777a9d6
fix test
giancarloromeo Dec 3, 2024
5761e24
fix rpc
giancarloromeo Dec 3, 2024
8814283
continue refactoring
giancarloromeo Dec 3, 2024
b2394c5
continue refactoring
giancarloromeo Dec 3, 2024
cde1e0e
fix column name
giancarloromeo Dec 3, 2024
1e1b80c
continue refactoring
giancarloromeo Dec 4, 2024
bae24fc
fix typecheck
giancarloromeo Dec 4, 2024
442635f
continue refactoring
giancarloromeo Dec 4, 2024
834507a
fix rpc get key
giancarloromeo Dec 4, 2024
0fd1e5d
Merge branch 'master' into refactor-api-keys-service
giancarloromeo Dec 4, 2024
56caa82
update openapi spec
giancarloromeo Dec 4, 2024
4f09be2
fix typecheck
giancarloromeo Dec 4, 2024
cb7c873
remove errors
giancarloromeo Dec 4, 2024
a7e028a
use ID str
giancarloromeo Dec 4, 2024
a4ff6b3
rename symbols
giancarloromeo Dec 4, 2024
e29a971
continue refactoring
giancarloromeo Dec 4, 2024
4809a02
fix sonar
giancarloromeo Dec 4, 2024
4cad0bb
Merge branch 'master' into refactor-api-keys-service
giancarloromeo Dec 4, 2024
ad36509
fix director v2
giancarloromeo Dec 4, 2024
f7cfe72
Merge branch 'refactor-api-keys-service' of github.com:giancarloromeo…
giancarloromeo Dec 4, 2024
91390d4
fix test
giancarloromeo Dec 4, 2024
2ca2ba6
fix test
giancarloromeo Dec 4, 2024
387a197
fix typecheck
giancarloromeo Dec 4, 2024
29ea1c9
Merge branch 'master' into refactor-api-keys-service
giancarloromeo Dec 4, 2024
a5873b7
fix import
giancarloromeo Dec 6, 2024
ce3ed0a
rename
giancarloromeo Dec 6, 2024
e10dd19
separate models
giancarloromeo Dec 6, 2024
0758689
Merge branch 'master' into refactor-api-keys-service
giancarloromeo Dec 6, 2024
5b64d0c
get and delete
odeimaiz Dec 6, 2024
90295ec
camelCase
giancarloromeo Dec 6, 2024
5e8c177
api_base_url
odeimaiz Dec 6, 2024
af62360
Merge branch 'refactor-api-keys-service' of github.com:giancarloromeo…
odeimaiz Dec 6, 2024
198b3ce
camelCase
odeimaiz Dec 6, 2024
b58eef0
minor
odeimaiz Dec 6, 2024
0fdb6f4
update examples
giancarloromeo Dec 6, 2024
54f08fb
update examples
giancarloromeo Dec 6, 2024
cbb0278
add example
giancarloromeo Dec 6, 2024
9b937d0
Copy Base Url
odeimaiz Dec 6, 2024
5b22949
update example
giancarloromeo Dec 6, 2024
c10aec8
Merge pull request #3 from odeimaiz/odeimaiz-refactor/api-keys-service
giancarloromeo Dec 6, 2024
ff78470
silent sonar
giancarloromeo Dec 6, 2024
eceb799
Merge branch 'refactor-api-keys-service' of github.com:giancarloromeo…
giancarloromeo Dec 6, 2024
9d4d02a
update test
giancarloromeo Dec 6, 2024
c530793
fix field name
giancarloromeo Dec 6, 2024
c8b30ca
fix schema models
giancarloromeo Dec 6, 2024
e7bba25
models
giancarloromeo Dec 6, 2024
1271776
models
giancarloromeo Dec 6, 2024
c39f166
models
giancarloromeo Dec 6, 2024
d36fb00
Merge branch 'master' into refactor-api-keys-service
giancarloromeo Dec 6, 2024
6884100
Merge branch 'master' into refactor-api-keys-service
giancarloromeo Dec 9, 2024
5c8df32
comment
giancarloromeo Dec 9, 2024
71b64db
Merge remote-tracking branch 'upstream/master' into refactor-api-keys…
giancarloromeo Dec 9, 2024
08e4cd5
add issue ref
giancarloromeo Dec 9, 2024
5dfe8e3
fix imports
giancarloromeo Dec 9, 2024
de1c021
fix test
giancarloromeo Dec 9, 2024
f6fee9a
Merge branch 'master' into refactor-api-keys-service
giancarloromeo Dec 9, 2024
7857796
refactor rpc
giancarloromeo Dec 9, 2024
c773a50
Merge remote-tracking branch 'upstream/master' into refactor-api-keys…
giancarloromeo Dec 9, 2024
b55efd0
fix imports
giancarloromeo Dec 9, 2024
277e0f9
fix package
giancarloromeo Dec 9, 2024
59762a4
fix package
giancarloromeo Dec 9, 2024
d9dd232
Merge branch 'master' into refactor-api-keys-service
giancarloromeo Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 0 additions & 68 deletions api/specs/web-server/_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
from fastapi import APIRouter, status
from models_library.api_schemas_webserver.auth import (
AccountRequestInfo,
ApiKeyCreate,
ApiKeyGet,
UnregisterCheck,
)
from models_library.generics import Envelope
Expand Down Expand Up @@ -264,72 +262,6 @@ async def email_confirmation(code: str):
"""email link sent to user to confirm an action"""


@router.get(
"/auth/api-keys",
operation_id="list_api_keys",
responses={
status.HTTP_200_OK: {
"description": "returns the display names of API keys",
"model": list[str],
},
status.HTTP_400_BAD_REQUEST: {
"description": "key name requested is invalid",
},
status.HTTP_401_UNAUTHORIZED: {
"description": "requires login to list keys",
},
status.HTTP_403_FORBIDDEN: {
"description": "not enough permissions to list keys",
},
},
)
async def list_api_keys():
"""lists display names of API keys by this user"""


@router.post(
"/auth/api-keys",
operation_id="create_api_key",
responses={
status.HTTP_200_OK: {
"description": "Authorization granted returning API key",
"model": ApiKeyGet,
},
status.HTTP_400_BAD_REQUEST: {
"description": "key name requested is invalid",
},
status.HTTP_401_UNAUTHORIZED: {
"description": "requires login to list keys",
},
status.HTTP_403_FORBIDDEN: {
"description": "not enough permissions to list keys",
},
},
)
async def create_api_key(_body: ApiKeyCreate):
"""creates API keys to access public API"""


@router.delete(
"/auth/api-keys",
operation_id="delete_api_key",
status_code=status.HTTP_204_NO_CONTENT,
responses={
status.HTTP_204_NO_CONTENT: {
"description": "api key successfully deleted",
},
status.HTTP_401_UNAUTHORIZED: {
"description": "requires login to delete a key",
},
status.HTTP_403_FORBIDDEN: {
"description": "not enough permissions to delete a key",
},
},
)
async def delete_api_key(_body: ApiKeyCreate):
"""deletes API key by name"""


@router.get(
"/auth/captcha",
operation_id="request_captcha",
Expand Down
60 changes: 60 additions & 0 deletions api/specs/web-server/_auth_api_keys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from typing import Annotated

from fastapi import APIRouter, Depends, status
from models_library.api_schemas_webserver.auth import (
ApiKeyCreateRequest,
ApiKeyCreateResponse,
ApiKeyGet,
)
from models_library.generics import Envelope
from models_library.rest_error import EnvelopedError
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.api_keys._exceptions_handlers import _TO_HTTP_ERROR_MAP
from simcore_service_webserver.api_keys._rest import ApiKeysPathParams

router = APIRouter(
prefix=f"/{API_VTAG}",
tags=["auth"],
responses={
i.status_code: {"model": EnvelopedError} for i in _TO_HTTP_ERROR_MAP.values()
},
)


@router.post(
"/auth/api-keys",
operation_id="create_api_key",
status_code=status.HTTP_201_CREATED,
response_model=Envelope[ApiKeyCreateResponse],
)
async def create_api_key(_body: ApiKeyCreateRequest):
"""creates API keys to access public API"""


@router.get(
"/auth/api-keys",
operation_id="list_api_keys",
response_model=Envelope[list[ApiKeyGet]],
status_code=status.HTTP_200_OK,
)
async def list_api_keys():
"""lists API keys by this user"""


@router.get(
"/auth/api-keys/{api_key_id}",
operation_id="get_api_key",
response_model=Envelope[ApiKeyGet],
status_code=status.HTTP_200_OK,
)
async def get_api_key(_path: Annotated[ApiKeysPathParams, Depends()]):
"""returns the API Key with the given ID"""


@router.delete(
"/auth/api-keys/{api_key_id}",
operation_id="delete_api_key",
status_code=status.HTTP_204_NO_CONTENT,
)
async def delete_api_key(_path: Annotated[ApiKeysPathParams, Depends()]):
"""deletes the API key with the given ID"""
1 change: 1 addition & 0 deletions api/specs/web-server/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#
# core ---
"_auth",
"_auth_api_keys",
"_groups",
"_tags",
"_tags_groups", # after _tags
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from datetime import timedelta
from typing import Any
from typing import Annotated, Any

from pydantic import BaseModel, ConfigDict, Field, SecretStr
from models_library.basic_types import IDStr
from pydantic import AliasGenerator, ConfigDict, Field, HttpUrl, SecretStr
from pydantic.alias_generators import to_camel

from ..emails import LowerCaseEmailStr
from ._base import InputSchema
from ._base import InputSchema, OutputSchema


class AccountRequestInfo(InputSchema):
Expand Down Expand Up @@ -51,42 +53,97 @@ class UnregisterCheck(InputSchema):
#


class ApiKeyCreate(BaseModel):
display_name: str = Field(..., min_length=3)
class ApiKeyCreateRequest(InputSchema):
display_name: Annotated[str, Field(..., min_length=3)]
expiration: timedelta | None = Field(
None,
description="Time delta from creation time to expiration. If None, then it does not expire.",
)

model_config = ConfigDict(
alias_generator=AliasGenerator(
validation_alias=to_camel,
),
from_attributes=True,
giancarloromeo marked this conversation as resolved.
Show resolved Hide resolved
json_schema_extra={
"examples": [
{
"displayName": "test-api-forever",
},
{
"displayName": "test-api-for-one-day",
"expiration": 60 * 60 * 24,
},
{
"displayName": "test-api-for-another-day",
"expiration": "24:00:00",
},
]
},
)


class ApiKeyCreateResponse(OutputSchema):
id: IDStr
display_name: Annotated[str, Field(..., min_length=3)]
expiration: timedelta | None = Field(
None,
description="Time delta from creation time to expiration. If None, then it does not expire.",
)
api_base_url: HttpUrl
api_key: str
api_secret: str

model_config = ConfigDict(
alias_generator=AliasGenerator(
serialization_alias=to_camel,
),
from_attributes=True,
giancarloromeo marked this conversation as resolved.
Show resolved Hide resolved
json_schema_extra={
"examples": [
{
"id": "42",
"display_name": "test-api-forever",
"api_base_url": "http://api.osparc.io/v0", # NOSONAR
"api_key": "key",
"api_secret": "secret",
},
{
"id": "48",
"display_name": "test-api-for-one-day",
"expiration": 60 * 60 * 24,
"api_base_url": "http://api.sim4life.io/v0", # NOSONAR
"api_key": "key",
"api_secret": "secret",
},
{
"id": "54",
"display_name": "test-api-for-another-day",
"expiration": "24:00:00",
"api_base_url": "http://api.osparc-master.io/v0", # NOSONAR
"api_key": "key",
"api_secret": "secret",
},
]
}
},
)


class ApiKeyGet(BaseModel):
display_name: str = Field(..., min_length=3)
api_key: str
api_secret: str
class ApiKeyGet(OutputSchema):
id: IDStr
display_name: Annotated[str, Field(..., min_length=3)]

model_config = ConfigDict(
alias_generator=AliasGenerator(
serialization_alias=to_camel,
),
from_attributes=True,
giancarloromeo marked this conversation as resolved.
Show resolved Hide resolved
json_schema_extra={
"examples": [
{"display_name": "myapi", "api_key": "key", "api_secret": "secret"},
{
"id": "42",
"display_name": "myapi",
},
]
},
)
35 changes: 35 additions & 0 deletions packages/models-library/src/models_library/rpc_auth_api_keys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import datetime as dt
from typing import Annotated

from models_library.basic_types import IDStr
from pydantic import BaseModel, ConfigDict, Field


class ApiKeyCreate(BaseModel):
display_name: Annotated[str, Field(..., min_length=3)]
expiration: dt.timedelta | None = None

model_config = ConfigDict(
from_attributes=True,
)


class ApiKeyGet(BaseModel):
id: IDStr
display_name: Annotated[str, Field(..., min_length=3)]
api_key: str | None = None
giancarloromeo marked this conversation as resolved.
Show resolved Hide resolved
api_secret: str | None = None

model_config = ConfigDict(
from_attributes=True,
json_schema_extra={
"examples": [
{
"id": "42",
"display_name": "test-api-forever",
"api_key": "key",
"api_secret": "secret",
},
]
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

from aiocache import cached # type: ignore[import-untyped]
from fastapi import FastAPI
from models_library.api_schemas_webserver.auth import ApiKeyGet
from models_library.products import ProductName
from models_library.rpc_auth_api_keys import ApiKeyGet
from models_library.users import UserID

from ._api_auth_rpc import get_or_create_api_key_and_secret
Expand All @@ -30,10 +30,13 @@ async def _get_or_create_for(
product_name: ProductName,
user_id: UserID,
) -> ApiKeyGet:

name = create_unique_api_name_for(product_name, user_id)
display_name = create_unique_api_name_for(product_name, user_id)
return await get_or_create_api_key_and_secret(
app, product_name=product_name, user_id=user_id, name=name, expiration=None
app,
user_id=user_id,
product_name=product_name,
display_name=display_name,
expiration=None,
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

from fastapi import FastAPI
from models_library.api_schemas_webserver import WEBSERVER_RPC_NAMESPACE
from models_library.api_schemas_webserver.auth import ApiKeyGet
from models_library.products import ProductName
from models_library.rabbitmq_basic_types import RPCMethodName
from models_library.rpc_auth_api_keys import ApiKeyGet
from models_library.users import UserID
from pydantic import TypeAdapter

Expand All @@ -20,16 +20,16 @@ async def get_or_create_api_key_and_secret(
*,
product_name: ProductName,
user_id: UserID,
name: str,
display_name: str,
expiration: timedelta | None = None,
) -> ApiKeyGet:
rpc_client = get_rabbitmq_rpc_client(app)
result = await rpc_client.request(
WEBSERVER_RPC_NAMESPACE,
TypeAdapter(RPCMethodName).validate_python("get_or_create_api_keys"),
product_name=product_name,
TypeAdapter(RPCMethodName).validate_python("get_or_create_api_key"),
user_id=user_id,
name=name,
display_name=display_name,
expiration=expiration,
product_name=product_name,
)
return ApiKeyGet.model_validate(result)
6 changes: 3 additions & 3 deletions services/director-v2/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
from asgi_lifespan import LifespanManager
from faker import Faker
from fastapi import FastAPI
from models_library.api_schemas_webserver.auth import ApiKeyGet
from models_library.products import ProductName
from models_library.projects import Node, NodesDict
from models_library.rpc_auth_api_keys import ApiKeyGet
from models_library.users import UserID
from pytest_mock import MockerFixture
from pytest_simcore.helpers.monkeypatch_envs import (
Expand Down Expand Up @@ -347,15 +347,15 @@ async def _create(
*,
product_name: ProductName,
user_id: UserID,
name: str,
display_name: str,
expiration: timedelta,
):
assert app
assert product_name
assert user_id
assert expiration is None

fake_data.display_name = name
fake_data.display_name = display_name
return fake_data

# mocks RPC interface
Expand Down
Loading
Loading