Commit c0e45aba authored by Jacek Lebioda's avatar Jacek Lebioda
Browse files

Restricted datasets work

parent 725bda0d
......@@ -12,6 +12,72 @@ import re
from beacon import helpers
class APIArgumentError(ValueError):
"""
Exception thrown if there's something wrong with the argument
provided to API query
"""
pass
def create_beacon_dataset(**kwargs):
"""
Creates an object which describes a dataset
Needs the following keys in `**kwargs`:
'id', 'name', 'assemblyId', 'createDateTime', 'updateDateTime'
Optional keys in `**kwargs`:
'description', 'version', 'variantCount', 'callCount', 'sampleCount'
'externalUrl', 'info'
"""
fields_required = ['id', 'name', 'assemblyId', 'createDateTime',
'updateDateTime']
fields_optional = ['description', 'version', 'variantCount', 'callCount',
'sampleCount', 'externalUrl', 'info']
return populate_fields({}, fields_required, fields_optional, **kwargs)
def validate_input(**kwargs):
"""
Validates query parameters. If there's a violation,
an `APIArgumentError` exception is raised
"""
def wrap(name, message, regexp):
re_regexp = re.compile(regexp)
if not helpers.check_input(kwargs, name, re_regexp):
arg = kwargs.get(name) if kwargs.get(name) is not None else '<<None>>'
raise APIArgumentError("Invalid {0} ({1}) - {2}".format(name, arg, message))
wrap('referenceName',
'accepted ones are: 1-22, X, Y, M, MT',
r'^([1-9]|1\d|2[012]|[Xx]|[Yy]|[Mm][Tt]?)$')
wrap('start',
'should be a positive number',
r'^\d+$')
wrap('assemblyId',
'accepted ones are: GRCh38, GRCh37 and NCBI36',
r'^([Gg][Rr][Cc][Hh](38|37)|[Nn][Cc][Bb][Ii]36)$')
wrap('referenceBases',
'should contain only A, C, T, G, D',
r'^[AaCcTtGgDd]*$')
wrap('alternateBases',
'should contain only A, C, T, G, D',
r'^[AaCcTtGgDd]*$')
whitelist = [True, False, 'true', 'false', 'True', 'False', '1']
if 'includeDatasetResponses' in kwargs and kwargs['includeDatasetResponses'] not in whitelist:
raise APIArgumentError("Invalid {0} ({1}) - {2}".format("includeDatasetResponses",
kwargs['includeDatasetResponses'],
'a boolean was expected'))
return True
def inject_required(what_to_inject, from_what, to_what):
"""
Extends the object `to_what` with `what_to_inject`.
......@@ -213,69 +279,3 @@ def create_beacon_error(**kwargs):
fields_optional = ['message', 'errorCode']
return populate_fields(error, fields_required, fields_optional, **kwargs)
def create_beacon_dataset(**kwargs):
"""
Creates an object which describes a dataset
Needs the following keys in `**kwargs`:
'id', 'name', 'assemblyId', 'createDateTime', 'updateDateTime'
Optional keys in `**kwargs`:
'description', 'version', 'variantCount', 'callCount', 'sampleCount'
'externalUrl', 'info'
"""
fields_required = ['id', 'name', 'assemblyId', 'createDateTime',
'updateDateTime']
fields_optional = ['description', 'version', 'variantCount', 'callCount',
'sampleCount', 'externalUrl', 'info']
return populate_fields({}, fields_required, fields_optional, **kwargs)
def validate_input(**kwargs):
"""
Validates query parameters. If there's a violation,
an `APIArgumentError` exception is raised
"""
def wrap(name, message, regexp):
re_regexp = re.compile(regexp)
if not helpers.check_input(kwargs, name, re_regexp):
arg = kwargs.get(name) if kwargs.get(name) is not None else '<<None>>'
raise APIArgumentError("Invalid {0} ({1}) - {2}".format(name, arg, message))
wrap('referenceName',
'accepted ones are: 1-22, X, Y, M, MT',
r'^([1-9]|1\d|2[012]|[Xx]|[Yy]|[Mm][Tt]?)$')
wrap('start',
'should be a positive number',
r'^\d+$')
wrap('assemblyId',
'accepted ones are: GRCh38, GRCh37 and NCBI36',
r'^([Gg][Rr][Cc][Hh](38|37)|[Nn][Cc][Bb][Ii]36)$')
wrap('referenceBases',
'should contain only A, C, T, G, D',
r'^[AaCcTtGgDd]*$')
wrap('alternateBases',
'should contain only A, C, T, G, D',
r'^[AaCcTtGgDd]*$')
whitelist = [True, False, 'true', 'false', 'True', 'False', '1']
if 'includeDatasetResponses' in kwargs and kwargs['includeDatasetResponses'] not in whitelist:
raise APIArgumentError("Invalid {0} ({1}) - {2}".format("includeDatasetResponses",
kwargs['includeDatasetResponses'],
'a boolean was expected'))
return True
class APIArgumentError(ValueError):
"""
Exception thrown if there's something wrong with the argument
provided to API query
"""
pass
......@@ -9,9 +9,6 @@ import json
import os
import urllib.request
from beacon.data_types import APIArgumentError
from beacon.middleware import build_query
def make_http_request(uri):
"""
......@@ -95,20 +92,3 @@ def load_json(path_to_file):
return the_object
except ValueError as e:
raise ValueError("It is not a correct JSON! {0}".format(str(e.args)))
def build_urls(instances, **kwargs):
"""
Returns list of all URLs to query.
"""
the_query = build_query(**kwargs)
if not len(instances):
message = "The beacon have no datasets or the specified datasets do not exist!"
raise APIArgumentError(message)
urls = [
uri['endpoint'] + the_query for uri in instances
]
return urls
......@@ -10,9 +10,9 @@ import re
from flask import jsonify
from beacon import data_types, helpers, liftover
from beacon.data_types import validate_input, APIArgumentError
from beacon.helpers import build_urls
from beacon.settings import restrict_access, raise_error_if_not_sufficient_permissions
from beacon.data_types import APIArgumentError, validate_input
from beacon.settings import raise_error_if_not_sufficient_permissions,\
restrict_access
def query(configuration, request, user_session_data):
......@@ -29,9 +29,11 @@ def query(configuration, request, user_session_data):
validate_input(**request)
data_sets = request.get('datasetIds', [])
instances = configuration.get_databases(data_sets)
instances, filtered_instances = restrict_access(instances, user_session_data)
raise_error_if_not_sufficient_permissions(filtered_instances, **request)
all_instances = configuration.get_databases(data_sets)
instances, filtered_instances = restrict_access(all_instances,
user_session_data)
raise_error_if_not_sufficient_permissions(filtered_instances,
**request)
urls = build_urls(instances, **request)
variantdb_responses = helpers.make_http_requests(urls)
......@@ -103,3 +105,18 @@ def include_dataset_responses(where_to_inject, responses_to_inject, databases):
# This is a place where obtaining more information would happen
def build_urls(instances, **kwargs):
"""
Returns list of all URLs to query.
"""
the_query = build_query(**kwargs)
if not len(instances):
message = "The beacon have no datasets or the specified datasets do not exist!"
raise APIArgumentError(message)
urls = [
uri['endpoint'] + the_query for uri in instances
]
return urls
......@@ -10,6 +10,7 @@ the settings file or load it from `local_settings.json` from project's root.
import os
import sys
from copy import deepcopy
from beacon import settings_loader
from beacon.data_types import APIArgumentError
from beacon.settings_loader import DATASET_PUBLIC, DATASET_CONTROLLED
......@@ -58,6 +59,28 @@ class Settings:
else:
return self.DatasetConnections
def get_beacon_information_for_user(self, user_data):
"""Returns information about the configuration with respect to the user's privileges"""
beacon_information = deepcopy(self.Beacon)
all_datasets = beacon_information.get('datasets', [])
for dataset in all_datasets:
dataset_metadata = self.get_metadata_for_dataset(dataset)
if should_be_accessible_by(dataset, dataset_metadata, user_data):
dataset['info']['authorized'] = "true"
else:
dataset['info']['authorized'] = "false"
beacon_information['datasets'] = all_datasets
return beacon_information
def get_metadata_for_dataset(self, dataset):
dataset_id = dataset.get('id')
for metadata in self.DatasetConnections:
if metadata['id'] == dataset_id:
return metadata
raise Exception('No metadata found for given dataset ID! id={}'.format(dataset_id))
def load_configuration(self):
"""
Tries to load the configuration file gracefully from two sources.
......@@ -109,24 +132,11 @@ class Settings:
self._try_to_load_from_source(filename, "config file")
def get_configuration_for_user(configuration, user_data):
"""Returns information about the configuration with respect to the user's privileges"""
all_datasets = configuration['datasets']
configuration['datasets'] = []
for dataset in all_datasets:
if should_be_accessible_by(dataset, user_data):
dataset['info']['authorized'] = "true"
else:
dataset['info']['authorized'] = "false"
configuration['datasets'].append(dataset)
return configuration
def should_be_accessible_by(dataset, user_data):
def should_be_accessible_by(dataset, dataset_metadata, user_data):
"""Checks, if the dataset provided in the argument should be accessible by the user"""
if is_dataset_public(dataset):
return True
if dataset['name'] in user_data:
if dataset_metadata['aaiResourceName'] in user_data:
return True
return False
......
......@@ -41,19 +41,22 @@ def register_views(app, configuration):
def view_login():
group_names = oidc.user_getfield("groupNames") or []
session['permissions'] = group_names
return render_template('index.html',
root=settings.API_PREFIX,
sub=oidc.user_getfield("sub"),
name=oidc.user_getfield("name"),
preferred_username=oidc.user_getfield("preferred_username"),
email=oidc.user_getfield("email"),
bona_fide_status=oidc.user_getfield("bona_fide_status"),
groupNames=oidc.user_getfield("groupNames"),
)
# In case anyone needs it
user_information = {
'sub': oidc.user_getfield("sub"),
'name': oidc.user_getfield("name"),
'preferred_username': oidc.user_getfield("preferred_username"),
'email': oidc.user_getfield("email"),
'bona_fide_status': oidc.user_getfield("bona_fide_status"),
'groupNames': oidc.user_getfield("groupNames")
}
return render_template('index.html', root=settings.API_PREFIX)
@app.route('/logout', methods=['GET', 'POST'])
def view_logout():
session['permissions'] = ''
session['permissions'] = []
oidc.logout()
return redirect('https://perun.elixir-czech.cz/oidc/endsession')
......@@ -63,7 +66,7 @@ def register_views(app, configuration):
def api_beacon():
"""`/beacon/` API endpoint"""
user_session_data = session.get('permissions', [])
beacon_information = settings.get_configuration_for_user(configuration.Beacon, user_session_data)
beacon_information = configuration.get_beacon_information_for_user(user_session_data)
return jsonify(beacon_information)
@app.route(settings.prefix('/beacon/query'),
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment