Skip to content

Commit

Permalink
Merge branch 'introduce-vip-models-pricing-4-part' of github.com:matu…
Browse files Browse the repository at this point in the history
…sdrobuliak66/osparc-simcore into introduce-vip-models-pricing-4-part
  • Loading branch information
matusdrobuliak66 committed Dec 10, 2024
2 parents 3755dce + 754cd58 commit 8f264b1
Show file tree
Hide file tree
Showing 40 changed files with 1,381 additions and 836 deletions.
14 changes: 8 additions & 6 deletions .github/workflows/ci-testing-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ concurrency:
jobs:
api-specs:
timeout-minutes: 10
name: "check oas' are up to date"
name: "check OAS' are up to date"
runs-on: ubuntu-latest
steps:
- name: setup python environment
Expand All @@ -35,11 +35,13 @@ jobs:
run: |
uv venv .venv && source .venv/bin/activate
make openapi-specs
./ci/github/helpers/openapi-specs-diff.bash diff \
if ! ./ci/github/helpers/openapi-specs-diff.bash diff \
https://raw.githubusercontent.com/${{ github.event.pull_request.head.repo.full_name }}/refs/heads/${{ github.event.pull_request.head.ref }} \
.
.; then \
echo "::error:: OAS are not up to date. Run 'make openapi-specs' to update them"; exit 1; \
fi
api-server-backwards-compatibility:
api-server-oas-breaking:
needs: api-specs
timeout-minutes: 10
name: "api-server backwards compatibility"
Expand All @@ -62,11 +64,11 @@ jobs:
https://raw.githubusercontent.com/${{ github.event.pull_request.base.repo.full_name }}/refs/heads/${{ github.event.pull_request.base.ref }}/services/api-server/openapi.json \
/specs/services/api-server/openapi.json
oas-backwards-compatibility:
all-oas-breaking:
needs: api-specs
continue-on-error: true
timeout-minutes: 10
name: "oas backwards compatibility"
name: "OAS backwards compatibility"
runs-on: ubuntu-latest
steps:
- name: setup python environment
Expand Down
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,
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,
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,
json_schema_extra={
"examples": [
{"display_name": "myapi", "api_key": "key", "api_secret": "secret"},
{
"id": "42",
"display_name": "myapi",
},
]
},
)
Empty file.
Empty file.
Empty file.
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
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",
},
]
},
)
Empty file.
Empty file.
Loading

0 comments on commit 8f264b1

Please sign in to comment.