Skip to content

Commit 2e4ed70

Browse files
committed
moving quidel signals to non-public access
1 parent 01365a1 commit 2e4ed70

File tree

6 files changed

+69
-2
lines changed

6 files changed

+69
-2
lines changed

docs/api/covidcast-signals/quidel-inactive.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ grand_parent: COVIDcast Main Endpoint
1515
1. TOC
1616
{:toc}
1717

18+
## Accessibility: Delphi-internal only
1819

1920
## COVID-19 Tests
2021
These signals are still active. Documentation is available on the [Quidel page](quidel.md).

docs/api/covidcast-signals/quidel.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ grand_parent: COVIDcast Main Endpoint
1515
1. TOC
1616
{:toc}
1717

18+
## Accessibility: Delphi-internal only
19+
1820
## COVID-19 Tests
1921

2022
* **Earliest issue available:** July 29, 2020

docs/api/covidcast_signals.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ dashboard](https://delphi.cmu.edu/covidcast/):
3636
| Early Indicators | COVID-Like Symptoms | [`fb-survey`](covidcast-signals/fb-survey.md) | `smoothed_wcli` |
3737
| Early Indicators | COVID-Like Symptoms in Community | [`fb-survey`](covidcast-signals/fb-survey.md) | `smoothed_whh_cmnty_cli` |
3838
| Early Indicators | COVID-Related Doctor Visits | [`doctor-visits`](covidcast-signals/doctor-visits.md) | `smoothed_adj_cli` |
39-
| Cases and Testing | COVID Antigen Test Positivity (Quidel) | [`quidel`](covidcast-signals/quidel.md) | `covid_ag_smoothed_pct_positive` |
4039
| Cases and Testing | COVID Cases | [`jhu-csse`](covidcast-signals/jhu-csse.md) | `confirmed_7dav_incidence_prop` |
4140
| Late Indicators | COVID Hospital Admissions | [`hhs`](covidcast-signals/hhs.md) | `confirmed_admissions_covid_1d_prop_7dav` |
4241
| Late Indicators | Deaths | [`jhu-csse`](covidcast-signals/jhu-csse.md) | `deaths_7dav_incidence_prop` |

src/server/_security.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ def decorated_function(*args, **kwargs):
8282
return decorator_wrapper
8383

8484

85+
# key is data "source" name, value is role name required to access that source
86+
sources_protected_by_roles = {
87+
'quidel': 'quidel',
88+
# the following two entries are needed because
89+
# the covidcast endpoint uses this method
90+
# to allow using various different "source" name aliases:
91+
# delphi.epidata.server.endpoints.covidcast_utils.model.create_source_signal_alias_mapper()
92+
# which, for reference, is populated by the file:
93+
# src/server/endpoints/covidcast_utils/db_sources.csv
94+
'quidel-covid-ag': 'quidel',
95+
'quidel-flu': 'quidel',
96+
}
97+
# TODO(<insert gh issue link here>): source this info from a better place than a hardcoded dict:
98+
# maybe somewhere in the db? maybe in src/server/endpoints/covidcast_utils/db_sources.csv ?
99+
100+
85101
def update_key_last_time_used(user):
86102
if user:
87103
# update last usage for this user's api key to "now()"

src/server/endpoints/covidcast.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@
3030
)
3131
from .._query import QueryBuilder, execute_query, run_query, parse_row, filter_fields
3232
from .._printer import create_printer, CSVPrinter
33+
from .._security import current_user, sources_protected_by_roles
3334
from .._validate import require_all
3435
from .._pandas import as_pandas, print_pandas
3536
from .covidcast_utils import compute_trend, compute_trends, compute_trend_value, CovidcastMetaEntry
3637
from ..utils import shift_day_value, day_to_time_value, time_value_to_iso, time_value_to_day, shift_week_value, time_value_to_week, guess_time_value_is_day, week_to_time_value, TimeValues
3738
from .covidcast_utils.model import TimeType, count_signal_time_types, data_sources, create_source_signal_alias_mapper
39+
from delphi.epidata.common.logger import get_structured_logger
3840

3941
# first argument is the endpoint name
4042
bp = Blueprint("covidcast", __name__)
@@ -43,9 +45,30 @@
4345
latest_table = "epimetric_latest_v"
4446
history_table = "epimetric_full_v"
4547

48+
def restrict_by_roles(source_signal_sets):
49+
# takes a list of SourceSignalSet objects
50+
# and returns only those from the list
51+
# that the current user is permitted to access.
52+
user = current_user
53+
allowed_source_signal_sets = []
54+
for src_sig_set in source_signal_sets:
55+
src = src_sig_set.source
56+
if src in sources_protected_by_roles:
57+
role = sources_protected_by_roles[src]
58+
if user and user.has_role(role):
59+
allowed_source_signal_sets.append(src_sig_set)
60+
else:
61+
# protected src and user does not have permission => leave it out of the srcsig sets
62+
get_structured_logger("covcast_endpt").warning("user requested restricted 'source'", api_key=(user and user.api_key), src=src)
63+
else:
64+
allowed_source_signal_sets.append(src_sig_set)
65+
return allowed_source_signal_sets
66+
67+
4668
@bp.route("/", methods=("GET", "POST"))
4769
def handle():
4870
source_signal_sets = parse_source_signal_sets()
71+
source_signal_sets = restrict_by_roles(source_signal_sets)
4972
source_signal_sets, alias_mapper = create_source_signal_alias_mapper(source_signal_sets)
5073
time_set = parse_time_set()
5174
geo_sets = parse_geo_sets()
@@ -102,6 +125,7 @@ def _verify_argument_time_type_matches(is_day_argument: bool, count_daily_signal
102125
def handle_trend():
103126
require_all(request, "window", "date")
104127
source_signal_sets = parse_source_signal_sets()
128+
source_signal_sets = restrict_by_roles(source_signal_sets)
105129
daily_signals, weekly_signals = count_signal_time_types(source_signal_sets)
106130
source_signal_sets, alias_mapper = create_source_signal_alias_mapper(source_signal_sets)
107131
geo_sets = parse_geo_sets()
@@ -157,6 +181,7 @@ def gen(rows):
157181
def handle_trendseries():
158182
require_all(request, "window")
159183
source_signal_sets = parse_source_signal_sets()
184+
source_signal_sets = restrict_by_roles(source_signal_sets)
160185
daily_signals, weekly_signals = count_signal_time_types(source_signal_sets)
161186
source_signal_sets, alias_mapper = create_source_signal_alias_mapper(source_signal_sets)
162187
geo_sets = parse_geo_sets()
@@ -405,8 +430,19 @@ def handle_meta():
405430
entry = by_signal.setdefault((row["data_source"], row["signal"]), [])
406431
entry.append(row)
407432

433+
user = current_user
408434
sources: List[Dict[str, Any]] = []
409435
for source in data_sources:
436+
src = source.db_source # TODO: might wanna check source.source in addition to .db_source
437+
if src in sources_protected_by_roles:
438+
role = sources_protected_by_roles[src]
439+
if not (user and user.has_role(role)):
440+
# if this is a protected source
441+
# and the user doesnt have the allowed role
442+
# (or if we have no user)
443+
# then skip this source
444+
continue
445+
410446
meta_signals: List[Dict[str, Any]] = []
411447

412448
for signal in source.signals:
@@ -448,6 +484,7 @@ def handle_coverage():
448484
"""
449485

450486
source_signal_sets = parse_source_signal_sets()
487+
source_signal_sets = restrict_by_roles(source_signal_sets)
451488
daily_signals, weekly_signals = count_signal_time_types(source_signal_sets)
452489
source_signal_sets, alias_mapper = create_source_signal_alias_mapper(source_signal_sets)
453490

src/server/endpoints/covidcast_meta.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .._params import extract_strings
99
from .._printer import create_printer
1010
from .._query import filter_fields
11+
from .._security import current_user, sources_protected_by_roles
1112
from delphi.epidata.common.logger import get_structured_logger
1213

1314
bp = Blueprint("covidcast_meta", __name__)
@@ -71,17 +72,28 @@ def handle():
7172
age = metadata["age"]
7273
reported_age = max(0, min(age, standard_age) - age_margin)
7374

75+
user = current_user
76+
7477
def cache_entry_gen():
7578
for entry in metadata_list:
7679
if time_types and entry.get("time_type") not in time_types:
7780
continue
7881
if geo_types and entry.get("geo_type") not in geo_types:
7982
continue
83+
entry_source = entry.get("data_source")
84+
if entry_source in sources_protected_by_roles:
85+
role = sources_protected_by_roles[entry_source]
86+
if not (user and user.has_role()):
87+
# if this is a protected source
88+
# and the user doesnt have the allowed role
89+
# (or if we have no user)
90+
# then skip this source
91+
continue
8092
if not signals:
8193
yield entry
8294
for signal in signals:
8395
# match source and (signal or no signal or signal = *)
84-
if entry.get("data_source") == signal.source and (
96+
if entry_source == signal.source and (
8597
signal.signal == "*" or signal.signal == entry.get("signal")
8698
):
8799
yield entry

0 commit comments

Comments
 (0)