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

Add authentication using API key #214

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ MONGO_PASSWORD=nlpmongo
SERVER_PROTOCOL=http://
SERVER_DOMAIN=localhost
SERVER_PORT=8080
SERVER_SECRET_KEY=changeme

## DB
DB_PROTOCOL=mongodb://
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ Node using Docker (production mode) or the Python development server.
- Using Docker: http://localhost/ui
- Using Python: http://localhost:8080/ui

### Authentication

A user who send requests to a data node instance must authenticate itself with
an API key. Below is an example on how to specify the API key in a `curl`
request:

```console
curl -X GET "http://localhost:8080/api/v1/datasets?limit=10" -H "accept: application/json" -H "X-API-Key: changeme"
```

The API key used by the data node must be set at startup as the environment
variable `SERVER_SECRET_KEY`. The value of this environment variable can be set
in the configuration file `.env`.

## Contributing

Thinking about contributing to this project? Get started by reading our
Expand Down
12 changes: 6 additions & 6 deletions server/openapi_server/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@
from mongoengine import connect

from openapi_server import encoder
from openapi_server.config import Config as config
from openapi_server.config import config


app = connexion.App(__name__, specification_dir='./openapi/')
app.app.json_encoder = encoder.JSONEncoder
app.add_api('openapi.yaml', pythonic_params=True)

connect(
db=config().db_database,
username=config().db_username,
password=config().db_password,
host=config().db_host
db=config.db_database,
username=config.db_username,
password=config.db_password,
host=config.db_host
)

app.add_url_rule('/', 'root', lambda: flask.redirect('/api/v1/ui'))
app.add_url_rule('/ui', 'ui', lambda: flask.redirect('/api/v1/ui'))


def main():
app.run(port=config().server_port, debug=False)
app.run(port=config.server_port, debug=True)


if __name__ == '__main__':
Expand Down
19 changes: 13 additions & 6 deletions server/openapi_server/config.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import os
# from abc import abstractmethod
# import string
# import random


defaultValues = {
"SERVER_PROTOCOL": "http://",
"SERVER_DOMAIN": "localhost",
"SERVER_PORT": "8080",
"SERVER_SECRET_KEY": "",
# "SERVER_SECRET_KEY": ''.join(random.sample(string.ascii_letters + string.digits, 32)), # noqa: E501
"DB_PROTOCOL": "mongodb://",
"DB_DOMAIN": "localhost",
"DB_PORT": "27017",
Expand All @@ -25,11 +29,7 @@ def __init__(self):
def get_property(self, property_name):
if os.getenv(property_name) is not None:
return os.getenv(property_name)
# we don't want KeyError?
if property_name not in self._defaultValues.keys():
return None # No default value found
# return default value
return self._defaultValues[property_name]
return self._defaultValues.get(property_name)


class Config(AbstractConfig):
Expand Down Expand Up @@ -63,6 +63,10 @@ def server_api_url(self):
base_path='/api/v1'
)

@property
def secret_key(self):
return self.get_property('SERVER_SECRET_KEY')

@property
def db_protocol(self):
return self.get_property('DB_PROTOCOL')
Expand Down Expand Up @@ -91,3 +95,6 @@ def db_password(self):
def db_host(self):
return "%s%s:%s" % (
self.db_protocol, self.db_domain, self.db_port)


config = Config()
4 changes: 2 additions & 2 deletions server/openapi_server/controllers/annotation_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from openapi_server.dbmodels.annotation_store import AnnotationStore as DbAnnotationStore # noqa: E501
from openapi_server.dbmodels.annotation import Annotation as DbAnnotation
from openapi_server import util
from openapi_server.config import Config
from openapi_server.config import config


def create_annotation(dataset_id, annotation_store_id, annotation_id): # noqa: E501
Expand Down Expand Up @@ -205,7 +205,7 @@ def list_annotations(dataset_id, annotation_store_id, limit=None, offset=None):
next_ = (
"%s/datasets/%s/annotationStores/%s/annotations"
"?limit=%s&offset=%s") % \
(Config().server_api_url, dataset_id, annotation_store_id, limit, # noqa: E501
(config.server_api_url, dataset_id, annotation_store_id, limit, # noqa: E501
offset + limit)
res = PageOfAnnotations(
offset=offset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from openapi_server.dbmodels.annotation import Annotation as DbAnnotation # noqa: E501
from openapi_server.dbmodels.annotation_store import AnnotationStore as DbAnnotationStore # noqa: E501
from openapi_server.dbmodels.dataset import Dataset as DbDataset
from openapi_server.config import Config
from openapi_server.config import config


def create_annotation_store(dataset_id, annotation_store_id): # noqa: E501
Expand Down Expand Up @@ -140,7 +140,7 @@ def list_annotation_stores(dataset_id, limit=None, offset=None): # noqa: E501
next_ = ""
if len(annotation_stores) == limit:
next_ = "%s/datasets/%s/annotationStores?limit=%s&offset=%s" % \
(Config().server_api_url, dataset_id, limit, offset + limit)
(config.server_api_url, dataset_id, limit, offset + limit)
res = PageOfAnnotationStores(
offset=offset,
limit=limit,
Expand Down
4 changes: 2 additions & 2 deletions server/openapi_server/controllers/dataset_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from openapi_server.models.dataset_create_response import DatasetCreateResponse # noqa: E501
from openapi_server.models.error import Error # noqa: E501
from openapi_server.models.page_of_datasets import PageOfDatasets # noqa: E501
from openapi_server.config import Config
from openapi_server.config import config
from openapi_server.controllers.annotation_store_controller import delete_annotation_store_by_name, list_annotation_stores # noqa: E501
from openapi_server.controllers.fhir_store_controller import delete_fhir_store_by_name, list_fhir_stores # noqa: E501

Expand Down Expand Up @@ -127,7 +127,7 @@ def list_datasets(limit=None, offset=None): # noqa: E501
next_ = ""
if len(datasets) == limit:
next_ = "%s/datasets?limit=%s&offset=%s" % \
(Config().server_api_url, limit, offset + limit)
(config.server_api_url, limit, offset + limit)
res = PageOfDatasets(
offset=offset,
limit=limit,
Expand Down
4 changes: 2 additions & 2 deletions server/openapi_server/controllers/fhir_store_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from openapi_server.dbmodels.fhir_store import FhirStore as DbFhirStore
from openapi_server.dbmodels.note import Note as DbNote
from openapi_server.dbmodels.patient import Patient as DbPatient
from openapi_server.config import Config
from openapi_server.config import config


def create_fhir_store(dataset_id, fhir_store_id): # noqa: E501
Expand Down Expand Up @@ -150,7 +150,7 @@ def list_fhir_stores(dataset_id, limit=None, offset=None): # noqa: E501
next_ = ""
if len(fhir_stores) == limit:
next_ = "%s/datasets/%s/FhirStores?limit=%s&offset=%s" % \
(Config().server_api_url, dataset_id, limit, offset + limit)
(config.server_api_url, dataset_id, limit, offset + limit)
res = PageOfFhirStores(
offset=offset,
limit=limit,
Expand Down
4 changes: 2 additions & 2 deletions server/openapi_server/controllers/note_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from openapi_server.dbmodels.fhir_store import FhirStore as DbFhirStore
from openapi_server.dbmodels.note import Note as DbNote
from openapi_server.dbmodels.patient import Patient as DbPatient
from openapi_server.config import Config
from openapi_server.config import config


def create_note(dataset_id, fhir_store_id, note_id): # noqa: E501
Expand Down Expand Up @@ -180,7 +180,7 @@ def list_notes(dataset_id, fhir_store_id, limit=None, offset=None): # noqa: E50
if len(notes) == limit:
next_ = '{api_url}/{fhir_store_name}/fhir/Note?limit={limit}' \
'&offset={offset}'.format(
api_url=Config().server_api_url,
api_url=config.server_api_url,
fhir_store_name=store_name,
limit=limit,
offset=offset + limit
Expand Down
4 changes: 2 additions & 2 deletions server/openapi_server/controllers/patient_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from openapi_server.models.patient import Patient # noqa: E501
from openapi_server.models.patient_create_request import PatientCreateRequest # noqa: E501
from openapi_server.models.patient_create_response import PatientCreateResponse # noqa: E501
from openapi_server.config import Config
from openapi_server.config import config
from openapi_server.controllers.note_controller import delete_notes_by_patient # noqa: E501


Expand Down Expand Up @@ -154,7 +154,7 @@ def list_patients(dataset_id, fhir_store_id, limit=None, offset=None): # noqa:
if len(patients) == limit:
next_ = '{api_url}/{fhir_store_name}/fhir/Patient?limit={limit}' \
'&offset={offset}'.format(
api_url=Config().server_api_url,
api_url=config.server_api_url,
fhir_store_name=store_name,
limit=limit,
offset=offset + limit
Expand Down
27 changes: 26 additions & 1 deletion server/openapi_server/controllers/security_controller_.py
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
# from typing import List
from openapi_server.config import config


def info_from_ApiKeyAuth(api_key, required_scopes):
"""
Check and retrieve authentication information from api_key. Returned value
will be passed in 'token_info' parameter of your operation function, if
there is one. 'sub' or 'uid' will be set in 'user' parameter of your
operation function, if there is one.

:param api_key API key provided by Authorization header :type api_key: str
:param required_scopes Always None. Used for other authentication method
:type required_scopes: None :return: Information attached to provided
api_key or None if api_key is invalid or does not allow access to called API
:rtype: dict | None
"""
try:
# disable authentication if secret key is empty
# TODO the time required to evaluate the key value must be independent
# of the content of the api key defined by the server.
if not config.secret_key or api_key == config.secret_key:
return {'uid': 'user_id'}
except Exception as error:
print("Invalid API key", error)

return None
Loading