Skip to content

Commit

Permalink
Add a Token model and return this instead of a bare string
Browse files Browse the repository at this point in the history
This makes the token creation endpoint better aligned with the others
in behaviour (all now return JSON with correct Content-Type for both
success and HTTP errors).

The token JSON includes the relevant pipeline name and token
description, an improvment over anonymous strings.
  • Loading branch information
kjsanger committed Jun 17, 2024
1 parent c377852 commit 18d5ea6
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 17 deletions.
15 changes: 7 additions & 8 deletions src/npg_porch/db/data_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@
from sqlalchemy.orm import contains_eager, joinedload
from sqlalchemy.orm.exc import NoResultFound

from npg_porch.db.models import Pipeline as DbPipeline, Task as DbTask, Event
from npg_porch.db.models import Pipeline as DbPipeline, Task as DbTask, Token as DbToken, Event
from npg_porch.models import Task, Pipeline, TaskStateEnum

from npg_porch.db.models import Token
from npg_porch.models.token import Token


class AsyncDbAccessor:
Expand Down Expand Up @@ -94,15 +93,15 @@ async def create_pipeline(self, pipeline: Pipeline) -> Pipeline:
await session.commit()
return pipe.convert_to_model()


async def create_pipeline_token(self, name: str, desc: str) -> str:
async def create_pipeline_token(self, name: str, desc: str) -> Token:
session = self.session
db_pipeline = await self._get_pipeline_db_object(name)

token = Token(pipeline=db_pipeline, description=desc)
session.add(token)
db_token = DbToken(pipeline=db_pipeline, description=desc)
session.add(db_token)
await session.commit()
return token.token

return Token(name=db_pipeline.name, token=db_token.token, description=desc)


async def create_task(self, token_id: int, task: Task) -> Task:
Expand Down
4 changes: 2 additions & 2 deletions src/npg_porch/db/models/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@

class Token(Base):
'''
A string token ussued to client applications for the purpose of
A string token issued to client applications for the purpose of
authorizing them to perform certain actions.
'''

def random_token():
def random_token(self):
'''
Returns a 32 characters long random string. The chance of a
collision is small.
Expand Down
9 changes: 5 additions & 4 deletions src/npg_porch/endpoints/pipelines.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from npg_porch.models.permission import RolesEnum
from npg_porch.db.connection import get_DbAccessor
from npg_porch.auth.token import validate

from npg_porch.models.token import Token

router = APIRouter(
prefix="/pipelines",
Expand Down Expand Up @@ -86,7 +86,8 @@ async def get_pipeline(

@router.post(
"/{pipeline_name}/token/{token_desc}",
response_model=str,
response_model=Token,
status_code=status.HTTP_201_CREATED,
responses={
status.HTTP_201_CREATED: {"description": "Token was created"},
status.HTTP_404_NOT_FOUND: {"description": "Pipeline not found"},
Expand All @@ -101,13 +102,13 @@ async def create_pipeline_token(
token_desc: str,
db_accessor=Depends(get_DbAccessor),
permissions=Depends(validate)
) -> Response:
) -> Token:
try:
token = await db_accessor.create_pipeline_token(name=pipeline_name, desc=token_desc)
except NoResultFound:
raise HTTPException(status_code=404,
detail=f"Pipeline '{pipeline_name}' not found")
return Response(status_code=status.HTTP_201_CREATED, content=token, media_type="text/plain")
return token


@router.post(
Expand Down
38 changes: 38 additions & 0 deletions src/npg_porch/models/token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright (C) 2024 Genome Research Ltd.
#
# This file is part of npg_porch
#
# npg_porch is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.

from pydantic import BaseModel, ConfigDict, Field


class Token(BaseModel):
model_config = ConfigDict(extra='forbid')

name: str = Field(
default=None,
title='Pipeline Name',
description='A user-controlled name of an existing pipeline'
)
description: str | None = Field(
default=None,
title='Description',
description='A user-controlled description of the token'
)
token: str | None = Field(
default=None,
title='Token',
description='An authorisation token'
)
8 changes: 5 additions & 3 deletions tests/pipeline_route_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ def test_create_pipeline_token(async_minimum, fastapi_testclient):
# Create a pipeline
desired_pipeline = Pipeline(
name=pipeline_name,
uri='http://test.com',
version='1'
uri="http://test.com",
version="1"
)

http_create_pipeline(fastapi_testclient, desired_pipeline)
Expand All @@ -189,7 +189,9 @@ def test_create_pipeline_token(async_minimum, fastapi_testclient):
)

assert response.status_code == status.HTTP_201_CREATED
assert len(response.content.decode(encoding="utf-8")) == 32
assert response.json()["name"] == pipeline_name
assert response.json()["description"] == token_desc
assert len(response.json()["token"]) == 32

# Create a new token for a non-existent pipeline
response = fastapi_testclient.post(
Expand Down

0 comments on commit 18d5ea6

Please sign in to comment.