Skip to content

Commit

Permalink
Add an endpoint for creating pipeline tokens
Browse files Browse the repository at this point in the history
Add a POST endpoint to be used with the admin token to create tokens
for a pipeline. There is no limit on the number of tokens that can be
created for a particular pipeline.
  • Loading branch information
kjsanger committed Jun 14, 2024
1 parent ec1a047 commit f5bc68b
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 5 deletions.
8 changes: 6 additions & 2 deletions docs/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,23 @@ Security is necessary in order to prevent accidental misuse of npg_porch. An aut

A note on HTTPS: Client libraries like `requests`, certain GUIs and Firefox will try to verify the server certificate authority. System-administered software are already configured correctly, but other packages installed by conda or pip may need to be told how the client may verify with a client certificate e.g. contained in `/usr/share/ca-certificates/`. It may also be useful to unset `https_proxy` and `HTTPS_PROXY` in your environment.

### Step 0 - get issued security tokens
### Step 0 - get issued authorisation tokens

Access to the service is loosely controlled with authorisation tokens. You will be issued with an admin token that enables you to register pipelines, and further tokens for pipeline-specific communication. Please do not share the tokens around and use them for purposes besides the specific pipeline. This will help us to monitor pipeline reliability and quality of service. Authorisation is achieved by HTTP Bearer Token:

`curl -L -H "Authorization: Bearer $TOKEN" https://$SERVER:$PORT`

Authorisation tokens are specific to a pipeline and more than one token can be issued for a pipeline. New tokens for a pipeline can be obtained using the admin token, from the pipeline's token endpoint:

`curl -L -X POST -H "Authorization: Bearer $ADMIN_TOKEN" https://$SERVER:$PORT/pipelines/$PIPELINE_NAME/token/$TOKEN_DESCRIPTION`

### Step 1 - register your pipeline with npg_porch

*Schema: npg_porch.model.pipeline*

Nothing in npg_porch can happen until there's a pipeline defined. For our purposes "pipeline" means "a thing you can run", and it may refer to specific code, or a wrapper that can run the pipeline in this particular way with some standard arguments.

You can name your pipeline however you like, but the name must be unique, and be as informative to you as possible. Version and a URI can be useful for undestanding what code is being run.
You can name your pipeline however you like, but the name must be unique, and be as informative to you as possible. Version and a URI can be useful for understanding what code is being run.

**pipeline-def.json**

Expand Down
15 changes: 14 additions & 1 deletion src/npg_porch/db/data_access.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2021, 2022 Genome Research Ltd.
# Copyright (C) 2021, 2022, 2024 Genome Research Ltd.
#
# Author: Kieron Taylor [email protected]
# Author: Marina Gourtovaia [email protected]
Expand Down Expand Up @@ -27,6 +27,8 @@
from npg_porch.db.models import Pipeline as DbPipeline, Task as DbTask, Event
from npg_porch.models import Task, Pipeline, TaskStateEnum

from npg_porch.db.models import Token


class AsyncDbAccessor:
'''
Expand Down Expand Up @@ -92,6 +94,17 @@ 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:
session = self.session
db_pipeline = await self._get_pipeline_db_object(name)

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


async def create_task(self, token_id: int, task: Task) -> Task:
self.logger.debug('CREATE TASK: ' + str(task))
session = self.session
Expand Down
30 changes: 28 additions & 2 deletions src/npg_porch/endpoints/pipelines.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2021, 2022 Genome Research Ltd.
# Copyright (C) 2021, 2022, 2024 Genome Research Ltd.
#
# Author: Kieron Taylor [email protected]
# Author: Marina Gourtovaia [email protected]
Expand All @@ -18,7 +18,7 @@
# 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 fastapi import APIRouter, HTTPException, Depends
from fastapi import APIRouter, HTTPException, Depends, Response
import logging
import re
from sqlalchemy.exc import IntegrityError
Expand Down Expand Up @@ -84,6 +84,32 @@ async def get_pipeline(
return pipeline


@router.post(
"/{pipeline_name}/token/{token_desc}",
response_model=str,
responses={
status.HTTP_201_CREATED: {"description": "Token was created"},
status.HTTP_404_NOT_FOUND: {"description": "Pipeline not found"},
},
summary="Create a new token for a pipeline and return it.",
description="""
Returns a token for the pipeline with the given name.
A valid token issued for any pipeline is required for authorisation."""
)
async def create_pipeline_token(
pipeline_name: str,
token_desc: str,
db_accessor=Depends(get_DbAccessor),
permissions=Depends(validate)
) -> Response:
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")


@router.post(
"/",
response_model=Pipeline,
Expand Down
34 changes: 34 additions & 0 deletions tests/pipeline_route_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,37 @@ def test_create_pipeline(async_minimum, fastapi_testclient):
headers=headers4power_user
)
assert response.status_code == status.HTTP_400_BAD_REQUEST


def test_create_pipeline_token(async_minimum, fastapi_testclient):
pipeline_name = "ptest four"
token_desc = "this is a token"

# Create a pipeline
desired_pipeline = Pipeline(
name=pipeline_name,
uri='http://test.com',
version='1'
)

http_create_pipeline(fastapi_testclient, desired_pipeline)

# Create new tokens for the pipeline. There is no limit on the number of tokens
# that can be created and no restriction on token description.
for _ in range(3):
response = fastapi_testclient.post(
f"/pipelines/{pipeline_name}/token/{token_desc}",
follow_redirects=True,
headers=headers4power_user
)

assert response.status_code == status.HTTP_201_CREATED
assert len(response.content.decode(encoding="utf-8")) == 32

# Create a new token for a non-existent pipeline
response = fastapi_testclient.post(
"/pipelines/not here/token/{token_desc}",
follow_redirects=True,
headers=headers4power_user
)
assert response.status_code == status.HTTP_404_NOT_FOUND

0 comments on commit f5bc68b

Please sign in to comment.