Skip to content

Commit 985a3c7

Browse files
Support client_credentials tokens (#425)
1 parent 0011979 commit 985a3c7

File tree

7 files changed

+443
-270
lines changed

7 files changed

+443
-270
lines changed

Diff for: Dockerfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ WORKDIR /$appname
4242
# this will make sure than the dependencies is cached
4343
COPY poetry.lock pyproject.toml /$appname/
4444
RUN poetry config virtualenvs.create false \
45-
&& poetry install -vv --no-root --no-dev --no-interaction \
45+
&& poetry install -vv --no-root --without dev --no-interaction \
4646
&& poetry show -v
4747

4848
# copy source code ONLY after installing dependencies
@@ -53,7 +53,7 @@ COPY ./bin/confighelper.py /var/www/$appname/confighelper.py
5353

5454
# install sheepdog
5555
RUN poetry config virtualenvs.create false \
56-
&& poetry install -vv --no-dev --no-interaction \
56+
&& poetry install -vv --without dev --no-interaction \
5757
&& poetry show -v
5858

5959
RUN COMMIT=`git rev-parse HEAD` && echo "COMMIT=\"${COMMIT}\"" >$appname/version_data.py \

Diff for: poetry.lock

+331-223
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: pyproject.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ include = [
1212
[tool.poetry.dependencies]
1313
python = ">=3.9,<3.10"
1414
authlib = "*" # let authutils decide which version we're using
15-
authutils = ">=6.0.0"
15+
authutils = ">=6.2.6"
1616
boto = ">=2.49.0"
1717
botocore = "*"
1818
datamodelutils = ">=1.0.0"
@@ -40,7 +40,7 @@ indexclient = ">=2.1.1"
4040
urllib3 = "<2.0.0"
4141
werkzeug = ">=3.0.6"
4242

43-
[tool.poetry.dev-dependencies]
43+
[tool.poetry.group.dev.dependencies]
4444
pytest = ">=4.6.5"
4545
pytest-cov = ">=2.5.1"
4646
requests_mock = ">=1.4.0"

Diff for: tests/conftest.py

+25-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def iss():
3030

3131
@pytest.fixture(scope="session")
3232
def encoded_jwt(iss):
33-
def encoded_jwt_function(private_key, user):
33+
def encoded_jwt_function(private_key, user=None, client_id=None):
3434
"""
3535
Return an example JWT containing the claims and encoded with the private
3636
key.
@@ -45,7 +45,14 @@ def encoded_jwt_function(private_key, user):
4545
kid = list(JWT_KEYPAIR_FILES.keys())[0]
4646
scopes = ["openid"]
4747
token = utils.generate_signed_access_token(
48-
kid, private_key, user, 3600, scopes, iss=iss, forced_exp_time=None
48+
kid,
49+
private_key,
50+
user,
51+
3600,
52+
scopes,
53+
iss=iss,
54+
forced_exp_time=None,
55+
client_id=client_id,
4956
)
5057
return token.token
5158

@@ -79,6 +86,22 @@ def submitter(create_user_header):
7986
return create_user_header(SUBMITTER_USERNAME)
8087

8188

89+
@pytest.fixture(params=["user", "client"])
90+
def submitter_and_client_submitter(request, create_user_header, encoded_jwt):
91+
"""
92+
Used to test select functionality with both a regular user token, and a token issued from
93+
the `client_credentials` flow, linked to a client and not to a user.
94+
"""
95+
if request.param == "user":
96+
return create_user_header(SUBMITTER_USERNAME)
97+
else:
98+
private_key = utils.read_file(
99+
"./integration/resources/keys/test_private_key.pem"
100+
)
101+
token = encoded_jwt(private_key, client_id="test_client_id")
102+
return {"Authorization": "bearer " + token}
103+
104+
82105
@pytest.fixture()
83106
def submitter_name():
84107
return SUBMITTER_USERNAME

Diff for: tests/integration/datadictwithobjid/submission/test_endpoints.py

+53-20
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ def wrapper(*args, **kwargs):
5555
return wrapper
5656

5757

58-
def test_program_creation_endpoint(client, pg_driver, submitter):
58+
def test_program_creation_endpoint(client, pg_driver, submitter_and_client_submitter):
5959
# Does not test authz.
60-
resp = put_cgci(client, auth=submitter)
60+
resp = put_cgci(client, auth=submitter_and_client_submitter)
6161
assert resp.status_code == 200, resp.data
6262
print(resp.data)
6363
resp = client.get("/v0/submission/")
@@ -85,9 +85,9 @@ def test_program_creation_endpoint_for_program_not_supported(
8585
assert resp.status_code == 404
8686

8787

88-
def test_project_creation_endpoint(client, pg_driver, submitter):
88+
def test_project_creation_endpoint(client, pg_driver, submitter_and_client_submitter):
8989
# Does not test authz.
90-
resp = put_cgci_blgsp(client, auth=submitter)
90+
resp = put_cgci_blgsp(client, auth=submitter_and_client_submitter)
9191
assert resp.status_code == 200
9292
resp = client.get("/v0/submission/CGCI/")
9393
with pg_driver.session_scope():
@@ -147,8 +147,10 @@ def test_project_creation_invalid_due_to_registed_project_name(
147147
assert resp.status_code == 400
148148

149149

150-
def test_put_entity_creation_valid(client, pg_driver, cgci_blgsp, submitter):
151-
headers = submitter
150+
def test_put_entity_creation_valid(
151+
client, pg_driver, cgci_blgsp, submitter_and_client_submitter
152+
):
153+
headers = submitter_and_client_submitter
152154
data = json.dumps(
153155
{
154156
"type": "experiment",
@@ -160,7 +162,20 @@ def test_put_entity_creation_valid(client, pg_driver, cgci_blgsp, submitter):
160162
assert resp.status_code == 200, resp.data
161163

162164

163-
def test_unauthenticated_post(client, pg_driver, cgci_blgsp, submitter):
165+
def test_unauthenticated_post(client, pg_driver, cgci_blgsp):
166+
headers = {}
167+
data = json.dumps(
168+
{
169+
"type": "case",
170+
"submitter_id": "BLGSP-71-06-00019",
171+
"projects": {"id": "daa208a7-f57a-562c-a04a-7a7c77542c98"},
172+
}
173+
)
174+
resp = client.post(BLGSP_PATH, headers=headers, data=data)
175+
assert resp.status_code == 401
176+
177+
178+
def test_bad_token_post(client, pg_driver, cgci_blgsp):
164179
# garbage token
165180
headers = {"Authorization": "test"}
166181
data = json.dumps(
@@ -175,9 +190,13 @@ def test_unauthenticated_post(client, pg_driver, cgci_blgsp, submitter):
175190

176191

177192
def test_unauthorized_post(
178-
client, pg_driver, cgci_blgsp, submitter, mock_arborist_requests
193+
client,
194+
pg_driver,
195+
cgci_blgsp,
196+
submitter_and_client_submitter,
197+
mock_arborist_requests,
179198
):
180-
headers = submitter
199+
headers = submitter_and_client_submitter
181200
mock_arborist_requests(authorized=False)
182201
resp = client.post(
183202
BLGSP_PATH,
@@ -304,8 +323,10 @@ def do_test_post_example_entities_together(client, submitter):
304323
assert condition_to_check, resp.data
305324

306325

307-
def test_post_example_entities_together(client, pg_driver, cgci_blgsp, submitter):
308-
do_test_post_example_entities_together(client, submitter)
326+
def test_post_example_entities_together(
327+
client, pg_driver, cgci_blgsp, submitter_and_client_submitter
328+
):
329+
do_test_post_example_entities_together(client, submitter_and_client_submitter)
309330

310331

311332
def test_dictionary_list_entries(client, pg_driver, cgci_blgsp, submitter):
@@ -477,10 +498,10 @@ def test_disallow_cross_project_references(client, pg_driver, cgci_blgsp, submit
477498
assert resp.status_code == 400, resp.data
478499

479500

480-
def test_delete_entity(client, pg_driver, cgci_blgsp, submitter):
501+
def test_delete_entity(client, pg_driver, cgci_blgsp, submitter_and_client_submitter):
481502
resp = client.put(
482503
BLGSP_PATH,
483-
headers=submitter,
504+
headers=submitter_and_client_submitter,
484505
data=json.dumps(
485506
{
486507
"type": "experiment",
@@ -492,7 +513,7 @@ def test_delete_entity(client, pg_driver, cgci_blgsp, submitter):
492513
assert resp.status_code == 200, resp.data
493514
did = resp.json["entities"][0]["id"]
494515
path = BLGSP_PATH + "entities/" + did
495-
resp = client.delete(path, headers=submitter)
516+
resp = client.delete(path, headers=submitter_and_client_submitter)
496517
assert resp.status_code == 200, resp.data
497518

498519

@@ -536,10 +557,10 @@ def test_validator_error_types(client, pg_driver, cgci_blgsp, submitter):
536557
assert errors["longest_dimension"] == "INVALID_VALUE"
537558

538559

539-
def test_invalid_json(client, pg_driver, cgci_blgsp, submitter):
560+
def test_invalid_json(client, pg_driver, cgci_blgsp, submitter_and_client_submitter):
540561
resp = client.put(
541562
BLGSP_PATH,
542-
headers=submitter,
563+
headers=submitter_and_client_submitter,
543564
data="""{
544565
"key1": "valid value",
545566
"key2": not a string,
@@ -651,20 +672,32 @@ def test_export_entity_by_id_json(client, pg_driver, cgci_blgsp, submitter):
651672
assert data[0]["id"] == case_id
652673

653674

654-
def get_export_data(client, submitter, node_type, format_type, without_id):
675+
def get_export_data(
676+
client, submitter_and_client_submitter, node_type, format_type, without_id
677+
):
655678
path = "/v0/submission/CGCI/BLGSP/export/?node_label={}&format={}".format(
656679
node_type, format_type
657680
)
658681
if without_id:
659682
path += "&without_id=True"
660-
r = client.get(path, headers=submitter)
683+
r = client.get(path, headers=submitter_and_client_submitter)
661684
return r
662685

663686

664687
def test_export_all_node_types(
665-
client, pg_driver, cgci_blgsp, require_index_exists_off, submitter
688+
client,
689+
pg_driver,
690+
cgci_blgsp,
691+
require_index_exists_off,
692+
submitter_and_client_submitter,
666693
):
667-
do_test_export(client, pg_driver, submitter, "experimental_metadata", "tsv")
694+
do_test_export(
695+
client,
696+
pg_driver,
697+
submitter_and_client_submitter,
698+
"experimental_metadata",
699+
"tsv",
700+
)
668701

669702

670703
def test_export_node_with_array_json(

Diff for: tests/integration/datadictwithobjid/submission/test_upload.py

+20-12
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,23 @@ def test_data_file_not_indexed(
8282
get_index_hash,
8383
client,
8484
pg_driver,
85-
submitter,
85+
submitter_and_client_submitter,
8686
cgci_blgsp,
8787
require_index_exists_off,
8888
):
8989
"""
9090
Test node and data file creation when neither exist and no ID is provided.
9191
"""
92-
submit_first_experiment(client, pg_driver, submitter, cgci_blgsp)
92+
submit_first_experiment(
93+
client, pg_driver, submitter_and_client_submitter, cgci_blgsp
94+
)
9395

9496
get_index_uuid.return_value = None
9597
get_index_hash.return_value = None
9698

97-
resp = submit_metadata_file(client, pg_driver, submitter, cgci_blgsp)
99+
resp = submit_metadata_file(
100+
client, pg_driver, submitter_and_client_submitter, cgci_blgsp
101+
)
98102

99103
# index creation
100104
assert create_index.call_count == 1
@@ -117,7 +121,7 @@ def test_data_file_not_indexed(
117121
path = "/v0/submission/CGCI/BLGSP/export/?format=json&ids={nid}".format(
118122
nid=entity["id"]
119123
)
120-
r = client.get(path, headers=submitter)
124+
r = client.get(path, headers=submitter_and_client_submitter)
121125

122126
data = r.json
123127
assert data and len(data) == 1
@@ -377,14 +381,16 @@ def test_data_file_update_multiple_urls(
377381
get_index_hash,
378382
client,
379383
pg_driver,
380-
submitter,
384+
submitter_and_client_submitter,
381385
cgci_blgsp,
382386
):
383387
"""
384388
Test submitting the same data again but updating the URL field (should
385389
get added to the indexed file in index service).
386390
"""
387-
submit_first_experiment(client, pg_driver, submitter, cgci_blgsp)
391+
submit_first_experiment(
392+
client, pg_driver, submitter_and_client_submitter, cgci_blgsp
393+
)
388394

389395
document = MagicMock()
390396
document.did = "14fd1746-61bb-401a-96d2-342cfaf70000"
@@ -400,7 +406,7 @@ def get_index_by_uuid(uuid):
400406

401407
get_index_uuid.side_effect = get_index_by_uuid
402408

403-
submit_metadata_file(client, pg_driver, submitter, cgci_blgsp)
409+
submit_metadata_file(client, pg_driver, submitter_and_client_submitter, cgci_blgsp)
404410

405411
# now submit again but change url
406412
new_url = "some/new/url/location/to/add"
@@ -410,7 +416,7 @@ def get_index_by_uuid(uuid):
410416
# comma separated list of urls INCLUDING the url that's already there
411417
updated_file["urls"] = DEFAULT_URL + "," + new_url + "," + another_new_url
412418
resp = submit_metadata_file(
413-
client, pg_driver, submitter, cgci_blgsp, data=updated_file
419+
client, pg_driver, submitter_and_client_submitter, cgci_blgsp, data=updated_file
414420
)
415421

416422
# no index or alias creation
@@ -678,7 +684,7 @@ def test_data_file_update_url_invalid_id(
678684
get_index_hash,
679685
client,
680686
pg_driver,
681-
submitter,
687+
submitter_and_client_submitter,
682688
cgci_blgsp,
683689
):
684690
"""
@@ -689,7 +695,9 @@ def test_data_file_update_url_invalid_id(
689695
FIXME: the 1:1 between node id and index/file id is temporary so this
690696
test may need to be modified in the future
691697
"""
692-
submit_first_experiment(client, pg_driver, submitter, cgci_blgsp)
698+
submit_first_experiment(
699+
client, pg_driver, submitter_and_client_submitter, cgci_blgsp
700+
)
693701

694702
document = MagicMock()
695703
document.did = "14fd1746-61bb-401a-96d2-342cfaf70000"
@@ -699,15 +707,15 @@ def test_data_file_update_url_invalid_id(
699707
# the uuid provided doesn't have a matching indexed file
700708
get_index_uuid.return_value = None
701709

702-
submit_metadata_file(client, pg_driver, submitter, cgci_blgsp)
710+
submit_metadata_file(client, pg_driver, submitter_and_client_submitter, cgci_blgsp)
703711

704712
# now submit again but change url
705713
new_url = "some/new/url/location/to/add"
706714
updated_file = copy.deepcopy(DEFAULT_METADATA_FILE)
707715
updated_file["urls"] = new_url
708716
updated_file["id"] = DEFAULT_UUID
709717
resp = submit_metadata_file(
710-
client, pg_driver, submitter, cgci_blgsp, data=updated_file
718+
client, pg_driver, submitter_and_client_submitter, cgci_blgsp, data=updated_file
711719
)
712720

713721
# no index or alias creation

0 commit comments

Comments
 (0)