nci-cidc-api-modules 1.0.17__py3-none-any.whl → 1.0.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -62,7 +62,6 @@ DEV_CFUNCTIONS_SERVER = environ.get("DEV_CFUNCTIONS_SERVER")
62
62
  ### Configure Auth0 ###
63
63
  AUTH0_DOMAIN = environ.get("AUTH0_DOMAIN")
64
64
  AUTH0_CLIENT_ID = environ.get("AUTH0_CLIENT_ID")
65
- ALGORITHMS = ["RS256"]
66
65
 
67
66
  ### Configure GCP ###
68
67
  GOOGLE_CLOUD_PROJECT = environ["GOOGLE_CLOUD_PROJECT"]
@@ -957,24 +957,14 @@ details_dict = {
957
957
  "TSV-formatted table containing metadata about the run",
958
958
  "",
959
959
  ),
960
- "/mibi/multichannel_image.ome.tiff": FileDetails(
961
- "miscellaneous",
960
+ "/mibi/.ome.tiff": FileDetails(
961
+ "source",
962
962
  "Analysis-ready multilayer OME-TIFF image file",
963
963
  "",
964
964
  ),
965
- "/mibi/cluster_labels.tif": FileDetails(
966
- "miscellaneous",
967
- "TIF-formatted whole cell segmentation masks for each multiplexed image",
968
- "",
969
- ),
970
- "/mibi/channel_names.csv": FileDetails(
971
- "miscellaneous",
972
- "CSV-formatted table of each channel and the corresponding mass",
973
- "",
974
- ),
975
- "/mibi/single_cell_table.csv": FileDetails(
976
- "miscellaneous",
977
- "Single cell data table containing eg integrated expression values, XY location",
965
+ "/mibi/he_file.": FileDetails(
966
+ "source",
967
+ "H and E file from MIBI analysis",
978
968
  "",
979
969
  ),
980
970
  }
@@ -340,20 +340,12 @@ assay_facets: Facets = {
340
340
  "TSV-formatted table containing metadata about the run",
341
341
  ),
342
342
  "Multichannel OME TIFFs": FacetConfig(
343
- ["/mibi/multichannel_image.ome.tiff"],
343
+ ["/mibi/.ome.tiff"],
344
344
  "Analysis-ready multilayer OME-TIFF image file",
345
345
  ),
346
- "Segmentation Masks": FacetConfig(
347
- ["/mibi/cluster_labels.tif"],
348
- "TIF-formatted whole cell segmentation masks for each multiplexed image",
349
- ),
350
- "Channel Names": FacetConfig(
351
- ["/mibi/channel_names.csv"],
352
- "CSV-formatted table of each channel and the corresponding mass",
353
- ),
354
- "Single-cell Data": FacetConfig(
355
- ["/mibi/single_cell_table.csv"],
356
- "Single cell data table containing eg integrated expression values, XY location",
346
+ "H&E File": FacetConfig(
347
+ ["/mibi/he_file."],
348
+ "H and E file from MIBI analysis",
357
349
  ),
358
350
  },
359
351
  "mIF": {
cidc_api/models/models.py CHANGED
@@ -2194,6 +2194,12 @@ class UploadJobs(CommonColumns):
2194
2194
  return None
2195
2195
  return upload
2196
2196
 
2197
+ @classmethod
2198
+ @with_default_session
2199
+ def find_by_trial_id(cls, trial_id: str, session):
2200
+ uploads = session.query(UploadJobs).filter_by(trial_id=trial_id)
2201
+ return uploads
2202
+
2197
2203
  @with_default_session
2198
2204
  def ingestion_success(
2199
2205
  self, trial, session: Session, commit: bool = False, send_email: bool = False
@@ -2803,6 +2809,30 @@ class DownloadableFiles(CommonColumns):
2803
2809
  )
2804
2810
  return results
2805
2811
 
2812
+ @classmethod
2813
+ @with_default_session
2814
+ def remove_participants_and_samples_info_files(
2815
+ cls, trial_id: str, session: Session
2816
+ ):
2817
+ """
2818
+ Remove participants info and samples info downloadable files
2819
+ """
2820
+ files_to_delete = (
2821
+ session.query(cls)
2822
+ .filter(cls.trial_id == trial_id)
2823
+ .filter(
2824
+ or_(
2825
+ cls.upload_type == "participants info",
2826
+ cls.upload_type == "samples info",
2827
+ )
2828
+ )
2829
+ .all()
2830
+ )
2831
+
2832
+ for file in files_to_delete:
2833
+ file.delete(commit=True)
2834
+ session.execute("REFRESH MATERIALIZED VIEW CONCURRENTLY trial_summaries_mv")
2835
+
2806
2836
  @classmethod
2807
2837
  @with_default_session
2808
2838
  def get_trial_facets_with_permissions(
cidc_api/shared/auth.py CHANGED
@@ -1,19 +1,18 @@
1
1
  from functools import wraps
2
2
  from typing import List
3
3
 
4
- import requests
5
4
  from packaging import version
6
- from jose import jwt
5
+
7
6
  from flask import g, request, current_app as app, Flask
8
7
  from werkzeug.exceptions import Unauthorized, BadRequest, PreconditionFailed
9
8
 
10
9
  from ..models import Users, UserSchema
11
- from ..config.settings import AUTH0_DOMAIN, ALGORITHMS, AUTH0_CLIENT_ID
10
+
12
11
  from ..config.logging import get_logger
13
12
 
14
- logger = get_logger(__name__)
13
+ from ..shared.jose import decode_id_token
15
14
 
16
- TIMEOUT_IN_SECONDS = 20
15
+ logger = get_logger(__name__)
17
16
 
18
17
 
19
18
  ### Main auth utility functions ###
@@ -147,8 +146,7 @@ _user_schema = UserSchema()
147
146
 
148
147
  def authenticate() -> Users:
149
148
  id_token = _extract_token()
150
- public_key = _get_issuer_public_key(id_token)
151
- token_payload = _decode_id_token(id_token, public_key)
149
+ token_payload = decode_id_token(id_token)
152
150
  profile = {"email": token_payload["email"]}
153
151
  return _user_schema.load(profile)
154
152
 
@@ -173,87 +171,6 @@ def _extract_token() -> str:
173
171
  return id_token
174
172
 
175
173
 
176
- def _get_issuer_public_key(token: str) -> dict:
177
- """
178
- Get the appropriate public key to check this token for authenticity.
179
-
180
- Args:
181
- token: an encoded JWT.
182
-
183
- Raises:
184
- Unauthorized: if no public key can be found.
185
-
186
- Returns:
187
- str: the public key.
188
- """
189
- try:
190
- header = jwt.get_unverified_header(token)
191
- except jwt.JWTError as e:
192
- raise Unauthorized(str(e)) from e
193
-
194
- # Get public keys from our Auth0 domain
195
- jwks_url = f"https://{AUTH0_DOMAIN}/.well-known/jwks.json"
196
- jwks = requests.get(jwks_url, timeout=TIMEOUT_IN_SECONDS).json()
197
-
198
- # Obtain the public key used to sign this token
199
- public_key = None
200
- for key in jwks["keys"]:
201
- if key["kid"] == header["kid"]:
202
- public_key = key
203
-
204
- # If no matching public key was found, we can't validate the token
205
- if not public_key:
206
- raise Unauthorized(f"Found no public key with id {header['kid']}")
207
-
208
- return public_key
209
-
210
-
211
- def _decode_id_token(token: str, public_key: dict) -> dict:
212
- """
213
- Decodes the token and checks it for validity.
214
-
215
- Args:
216
- token: the JWT to validate and decode
217
- public_key: public_key
218
-
219
- Raises:
220
- Unauthorized:
221
- - if token is expired
222
- - if token has invalid claims
223
- - if token signature is invalid in any way
224
- - if no `.email` field on token
225
-
226
- Returns:
227
- dict: the decoded token as a dictionary.
228
- """
229
- try:
230
- payload = jwt.decode(
231
- token,
232
- public_key,
233
- algorithms=ALGORITHMS,
234
- audience=AUTH0_CLIENT_ID,
235
- issuer=f"https://{AUTH0_DOMAIN}/",
236
- options={"verify_at_hash": False},
237
- )
238
- except jwt.ExpiredSignatureError as e:
239
- raise Unauthorized(
240
- f"{e} Token expired. Obtain a new login token from the CIDC Portal, then try logging in again."
241
- ) from e
242
- except jwt.JWTClaimsError as e:
243
- raise Unauthorized(str(e)) from e
244
- except jwt.JWTError as e:
245
- raise Unauthorized(str(e)) from e
246
-
247
- # Currently, only id_tokens are accepted for authentication.
248
- # Going forward, we could also accept access tokens that we
249
- # use to query the userinfo endpoint.
250
- if "email" not in payload:
251
- msg = "An id_token with an 'email' field is required to authenticate"
252
- raise Unauthorized(msg)
253
-
254
- return payload
255
-
256
-
257
174
  ### Authorization logic ###
258
175
  def authorize(
259
176
  user: Users, allowed_roles: List[str], resource: str, method: str
@@ -0,0 +1,72 @@
1
+ # external modules
2
+ import requests
3
+ from joserfc import jwt
4
+ from joserfc.jwk import RSAKey
5
+ from werkzeug.exceptions import Unauthorized
6
+ from cachetools import cached, TTLCache
7
+
8
+ # loacal modules
9
+ from ..config.settings import AUTH0_DOMAIN, AUTH0_CLIENT_ID
10
+
11
+
12
+ ALGORITHMS = ["RS256"]
13
+ TIMEOUT_IN_SECONDS = 20
14
+ PUBLIC_KEYS_CACHE = TTLCache(maxsize=3600, ttl=1024) # 1 hour, 1 MB
15
+
16
+
17
+ @cached(cache=PUBLIC_KEYS_CACHE)
18
+ def get_jwks() -> list:
19
+ # get jwks from our Auth0 domain
20
+ return requests.get(
21
+ f"https://{AUTH0_DOMAIN}/.well-known/jwks.json", timeout=TIMEOUT_IN_SECONDS
22
+ ).json()
23
+
24
+
25
+ def decode_id_token(token: str) -> dict:
26
+ """
27
+ Decodes the token and checks it for validity.
28
+
29
+ Args:
30
+ token: the JWT to validate and decode
31
+
32
+ Raises:
33
+ Unauthorized:
34
+ - if token is expired
35
+ - if token has invalid claims (email, aud and iss)
36
+ - if token signature is invalid
37
+
38
+ Returns:
39
+ dict: claims as a dictionary.
40
+ """
41
+
42
+ jwks = get_jwks()["keys"]
43
+ if not jwks:
44
+ raise Unauthorized("No public keys found")
45
+
46
+ decoded_token = False
47
+
48
+ for jwk in jwks:
49
+ if decoded_token:
50
+ continue
51
+ try:
52
+ key = RSAKey.import_key(jwk)
53
+ decoded_token = jwt.decode(token, key, ALGORITHMS)
54
+ except Exception as e:
55
+ pass
56
+
57
+ if decoded_token:
58
+ claims = decoded_token.claims
59
+ else:
60
+ raise Unauthorized("No valid public key found")
61
+
62
+ try:
63
+ claims_requests = jwt.JWTClaimsRegistry(
64
+ iss={"essential": True, "value": f"https://{AUTH0_DOMAIN}/"},
65
+ aud={"essential": True, "value": AUTH0_CLIENT_ID},
66
+ email={"essential": True},
67
+ )
68
+ claims_requests.validate(claims)
69
+ except Exception as e:
70
+ raise Unauthorized(str(e)) from e
71
+
72
+ return claims
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nci_cidc_api_modules
3
- Version: 1.0.17
3
+ Version: 1.0.19
4
4
  Summary: SQLAlchemy data models and configuration tools used in the NCI CIDC API
5
5
  Home-page: https://github.com/NCI-CIDC/cidc-api-gae
6
6
  License: MIT license
@@ -2,24 +2,25 @@ cidc_api/config/__init__.py,sha256=5mX8GAPxUKV84iS-aGOoE-4m68LsOCGCDptXNdlgvj0,1
2
2
  cidc_api/config/db.py,sha256=ayeeNV-sV20hGoFyMMTMncI2V-FI9lVN3JV-Lmpr3xI,1981
3
3
  cidc_api/config/logging.py,sha256=gJ2TGgQVREng4Hv0phlCCkQai7HhumKYjJxubpxS6Q0,1090
4
4
  cidc_api/config/secrets.py,sha256=2DXeew1Pm0lnf2SLuo8wW5c5kOJp2WrhjflxZGsY_Ng,1505
5
- cidc_api/config/settings.py,sha256=Ua6UpiQu9l1ZD-YlmpaWuQOv9tPyYLxWFLC2DEJdAyQ,4044
5
+ cidc_api/config/settings.py,sha256=2VHOVWdN4yUCNYMob2gaWgH2-1_4sbuo46JEIVT_PCY,4021
6
6
  cidc_api/csms/__init__.py,sha256=eJkY6rWNOAUBmSd4G1_U6h7i472druKEtBdVmgFZVPg,20
7
7
  cidc_api/csms/auth.py,sha256=25Yma2Kz3KLENAPSeBYacFuSZXng-EDgmgInKBsRyP0,3191
8
8
  cidc_api/models/__init__.py,sha256=bl445G8Zic9YbhZ8ZBni07wtBMhLJRMBA-JqjLxx2bw,66
9
9
  cidc_api/models/csms_api.py,sha256=Wp4b53vwOqSlOIaoAYGlI1p8ZfXRXmVJ6MLcsvzq0LA,31664
10
10
  cidc_api/models/migrations.py,sha256=gp9vtkYbA9FFy2s-7woelAmsvQbJ41LO2_DY-YkFIrQ,11464
11
- cidc_api/models/models.py,sha256=Hjp9sieGdldNbUzneFi-7vRYyo9wwr0D-0m_UbxsDEk,124106
11
+ cidc_api/models/models.py,sha256=SBVbIUbkeUSAA2jl-3IL93HDdR8tRrTHYQ80nH_8gq0,125025
12
12
  cidc_api/models/schemas.py,sha256=7tDYtmULuzTt2kg7RorWhte06ffalgpQKrFiDRGcPEQ,2711
13
13
  cidc_api/models/files/__init__.py,sha256=8BMTnUSHzUbz0lBeEQY6NvApxDD3GMWMduoVMos2g4Y,213
14
- cidc_api/models/files/details.py,sha256=DimrGmyL216j9eB47ZlX19b5GANf2nrD_crl79RPgqg,62699
15
- cidc_api/models/files/facets.py,sha256=n2Xi4zaC2pcJQw31gOLFxnF0nwtUBIkvw-5Th21-5uQ,29403
14
+ cidc_api/models/files/details.py,sha256=h6R0p_hi-ukHsO7HV-3Wukccp0zRLJ1Oie_JNA_7Pl0,62274
15
+ cidc_api/models/files/facets.py,sha256=0owlp-is2QJ7DemcsJ4VdlnN255NkkV1Cimg0VaXHpY,28967
16
16
  cidc_api/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- cidc_api/shared/auth.py,sha256=VMd_3QJE2iG16QxuGzHBV9MzJJItOZNn9gcw0_iUBLI,11647
17
+ cidc_api/shared/auth.py,sha256=EzMpYAR_gN5x985hgFAXTd24xygyctqZ80Yp05Ph_HQ,9104
18
18
  cidc_api/shared/emails.py,sha256=5dyuKlpcg1M4P_RrAt0ss2hiCqb-Y7p2XXR1d9uBXg8,4868
19
19
  cidc_api/shared/gcloud_client.py,sha256=7dDs0crLMJKdIp4IDSfrZBMB3h-zvWNieB81azoeLO4,33746
20
+ cidc_api/shared/jose.py,sha256=QO30uIhbYDwzPEWWJXz0PfyV7E1AZHReEZJUVT70UJY,1844
20
21
  cidc_api/shared/rest_utils.py,sha256=LMfBpvJRjkfQjCzVXuhTTe4Foz4wlvaKg6QntyR-Hkc,6648
21
- nci_cidc_api_modules-1.0.17.dist-info/LICENSE,sha256=pNYWVTHaYonnmJyplmeAp7tQAjosmDpAWjb34jjv7Xs,1102
22
- nci_cidc_api_modules-1.0.17.dist-info/METADATA,sha256=s-VGHMZCidyAeiQWFl4IP7XjZPgpDZzK2ESu3TmwPUc,40673
23
- nci_cidc_api_modules-1.0.17.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
24
- nci_cidc_api_modules-1.0.17.dist-info/top_level.txt,sha256=rNiRzL0lJGi5Q9tY9uSoMdTbJ-7u5c_D2E86KA94yRA,9
25
- nci_cidc_api_modules-1.0.17.dist-info/RECORD,,
22
+ nci_cidc_api_modules-1.0.19.dist-info/LICENSE,sha256=pNYWVTHaYonnmJyplmeAp7tQAjosmDpAWjb34jjv7Xs,1102
23
+ nci_cidc_api_modules-1.0.19.dist-info/METADATA,sha256=cYPVMMJcy0KeUbdFLmchOSZc4EM16gjepAAgxPtQAXc,40673
24
+ nci_cidc_api_modules-1.0.19.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
25
+ nci_cidc_api_modules-1.0.19.dist-info/top_level.txt,sha256=rNiRzL0lJGi5Q9tY9uSoMdTbJ-7u5c_D2E86KA94yRA,9
26
+ nci_cidc_api_modules-1.0.19.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (71.1.0)
2
+ Generator: setuptools (72.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5