Skip to content

Commit

Permalink
Support client_credentials tokens (#425)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulineribeyre authored Jan 9, 2025
1 parent 0011979 commit 985a3c7
Show file tree
Hide file tree
Showing 7 changed files with 443 additions and 270 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ WORKDIR /$appname
# this will make sure than the dependencies is cached
COPY poetry.lock pyproject.toml /$appname/
RUN poetry config virtualenvs.create false \
&& poetry install -vv --no-root --no-dev --no-interaction \
&& poetry install -vv --no-root --without dev --no-interaction \
&& poetry show -v

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

# install sheepdog
RUN poetry config virtualenvs.create false \
&& poetry install -vv --no-dev --no-interaction \
&& poetry install -vv --without dev --no-interaction \
&& poetry show -v

RUN COMMIT=`git rev-parse HEAD` && echo "COMMIT=\"${COMMIT}\"" >$appname/version_data.py \
Expand Down
554 changes: 331 additions & 223 deletions poetry.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ include = [
[tool.poetry.dependencies]
python = ">=3.9,<3.10"
authlib = "*" # let authutils decide which version we're using
authutils = ">=6.0.0"
authutils = ">=6.2.6"
boto = ">=2.49.0"
botocore = "*"
datamodelutils = ">=1.0.0"
Expand Down Expand Up @@ -40,7 +40,7 @@ indexclient = ">=2.1.1"
urllib3 = "<2.0.0"
werkzeug = ">=3.0.6"

[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = ">=4.6.5"
pytest-cov = ">=2.5.1"
requests_mock = ">=1.4.0"
Expand Down
27 changes: 25 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def iss():

@pytest.fixture(scope="session")
def encoded_jwt(iss):
def encoded_jwt_function(private_key, user):
def encoded_jwt_function(private_key, user=None, client_id=None):
"""
Return an example JWT containing the claims and encoded with the private
key.
Expand All @@ -45,7 +45,14 @@ def encoded_jwt_function(private_key, user):
kid = list(JWT_KEYPAIR_FILES.keys())[0]
scopes = ["openid"]
token = utils.generate_signed_access_token(
kid, private_key, user, 3600, scopes, iss=iss, forced_exp_time=None
kid,
private_key,
user,
3600,
scopes,
iss=iss,
forced_exp_time=None,
client_id=client_id,
)
return token.token

Expand Down Expand Up @@ -79,6 +86,22 @@ def submitter(create_user_header):
return create_user_header(SUBMITTER_USERNAME)


@pytest.fixture(params=["user", "client"])
def submitter_and_client_submitter(request, create_user_header, encoded_jwt):
"""
Used to test select functionality with both a regular user token, and a token issued from
the `client_credentials` flow, linked to a client and not to a user.
"""
if request.param == "user":
return create_user_header(SUBMITTER_USERNAME)
else:
private_key = utils.read_file(
"./integration/resources/keys/test_private_key.pem"
)
token = encoded_jwt(private_key, client_id="test_client_id")
return {"Authorization": "bearer " + token}


@pytest.fixture()
def submitter_name():
return SUBMITTER_USERNAME
Expand Down
73 changes: 53 additions & 20 deletions tests/integration/datadictwithobjid/submission/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ def wrapper(*args, **kwargs):
return wrapper


def test_program_creation_endpoint(client, pg_driver, submitter):
def test_program_creation_endpoint(client, pg_driver, submitter_and_client_submitter):
# Does not test authz.
resp = put_cgci(client, auth=submitter)
resp = put_cgci(client, auth=submitter_and_client_submitter)
assert resp.status_code == 200, resp.data
print(resp.data)
resp = client.get("/v0/submission/")
Expand Down Expand Up @@ -85,9 +85,9 @@ def test_program_creation_endpoint_for_program_not_supported(
assert resp.status_code == 404


def test_project_creation_endpoint(client, pg_driver, submitter):
def test_project_creation_endpoint(client, pg_driver, submitter_and_client_submitter):
# Does not test authz.
resp = put_cgci_blgsp(client, auth=submitter)
resp = put_cgci_blgsp(client, auth=submitter_and_client_submitter)
assert resp.status_code == 200
resp = client.get("/v0/submission/CGCI/")
with pg_driver.session_scope():
Expand Down Expand Up @@ -147,8 +147,10 @@ def test_project_creation_invalid_due_to_registed_project_name(
assert resp.status_code == 400


def test_put_entity_creation_valid(client, pg_driver, cgci_blgsp, submitter):
headers = submitter
def test_put_entity_creation_valid(
client, pg_driver, cgci_blgsp, submitter_and_client_submitter
):
headers = submitter_and_client_submitter
data = json.dumps(
{
"type": "experiment",
Expand All @@ -160,7 +162,20 @@ def test_put_entity_creation_valid(client, pg_driver, cgci_blgsp, submitter):
assert resp.status_code == 200, resp.data


def test_unauthenticated_post(client, pg_driver, cgci_blgsp, submitter):
def test_unauthenticated_post(client, pg_driver, cgci_blgsp):
headers = {}
data = json.dumps(
{
"type": "case",
"submitter_id": "BLGSP-71-06-00019",
"projects": {"id": "daa208a7-f57a-562c-a04a-7a7c77542c98"},
}
)
resp = client.post(BLGSP_PATH, headers=headers, data=data)
assert resp.status_code == 401


def test_bad_token_post(client, pg_driver, cgci_blgsp):
# garbage token
headers = {"Authorization": "test"}
data = json.dumps(
Expand All @@ -175,9 +190,13 @@ def test_unauthenticated_post(client, pg_driver, cgci_blgsp, submitter):


def test_unauthorized_post(
client, pg_driver, cgci_blgsp, submitter, mock_arborist_requests
client,
pg_driver,
cgci_blgsp,
submitter_and_client_submitter,
mock_arborist_requests,
):
headers = submitter
headers = submitter_and_client_submitter
mock_arborist_requests(authorized=False)
resp = client.post(
BLGSP_PATH,
Expand Down Expand Up @@ -304,8 +323,10 @@ def do_test_post_example_entities_together(client, submitter):
assert condition_to_check, resp.data


def test_post_example_entities_together(client, pg_driver, cgci_blgsp, submitter):
do_test_post_example_entities_together(client, submitter)
def test_post_example_entities_together(
client, pg_driver, cgci_blgsp, submitter_and_client_submitter
):
do_test_post_example_entities_together(client, submitter_and_client_submitter)


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


def test_delete_entity(client, pg_driver, cgci_blgsp, submitter):
def test_delete_entity(client, pg_driver, cgci_blgsp, submitter_and_client_submitter):
resp = client.put(
BLGSP_PATH,
headers=submitter,
headers=submitter_and_client_submitter,
data=json.dumps(
{
"type": "experiment",
Expand All @@ -492,7 +513,7 @@ def test_delete_entity(client, pg_driver, cgci_blgsp, submitter):
assert resp.status_code == 200, resp.data
did = resp.json["entities"][0]["id"]
path = BLGSP_PATH + "entities/" + did
resp = client.delete(path, headers=submitter)
resp = client.delete(path, headers=submitter_and_client_submitter)
assert resp.status_code == 200, resp.data


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


def test_invalid_json(client, pg_driver, cgci_blgsp, submitter):
def test_invalid_json(client, pg_driver, cgci_blgsp, submitter_and_client_submitter):
resp = client.put(
BLGSP_PATH,
headers=submitter,
headers=submitter_and_client_submitter,
data="""{
"key1": "valid value",
"key2": not a string,
Expand Down Expand Up @@ -651,20 +672,32 @@ def test_export_entity_by_id_json(client, pg_driver, cgci_blgsp, submitter):
assert data[0]["id"] == case_id


def get_export_data(client, submitter, node_type, format_type, without_id):
def get_export_data(
client, submitter_and_client_submitter, node_type, format_type, without_id
):
path = "/v0/submission/CGCI/BLGSP/export/?node_label={}&format={}".format(
node_type, format_type
)
if without_id:
path += "&without_id=True"
r = client.get(path, headers=submitter)
r = client.get(path, headers=submitter_and_client_submitter)
return r


def test_export_all_node_types(
client, pg_driver, cgci_blgsp, require_index_exists_off, submitter
client,
pg_driver,
cgci_blgsp,
require_index_exists_off,
submitter_and_client_submitter,
):
do_test_export(client, pg_driver, submitter, "experimental_metadata", "tsv")
do_test_export(
client,
pg_driver,
submitter_and_client_submitter,
"experimental_metadata",
"tsv",
)


def test_export_node_with_array_json(
Expand Down
32 changes: 20 additions & 12 deletions tests/integration/datadictwithobjid/submission/test_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,23 @@ def test_data_file_not_indexed(
get_index_hash,
client,
pg_driver,
submitter,
submitter_and_client_submitter,
cgci_blgsp,
require_index_exists_off,
):
"""
Test node and data file creation when neither exist and no ID is provided.
"""
submit_first_experiment(client, pg_driver, submitter, cgci_blgsp)
submit_first_experiment(
client, pg_driver, submitter_and_client_submitter, cgci_blgsp
)

get_index_uuid.return_value = None
get_index_hash.return_value = None

resp = submit_metadata_file(client, pg_driver, submitter, cgci_blgsp)
resp = submit_metadata_file(
client, pg_driver, submitter_and_client_submitter, cgci_blgsp
)

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

data = r.json
assert data and len(data) == 1
Expand Down Expand Up @@ -377,14 +381,16 @@ def test_data_file_update_multiple_urls(
get_index_hash,
client,
pg_driver,
submitter,
submitter_and_client_submitter,
cgci_blgsp,
):
"""
Test submitting the same data again but updating the URL field (should
get added to the indexed file in index service).
"""
submit_first_experiment(client, pg_driver, submitter, cgci_blgsp)
submit_first_experiment(
client, pg_driver, submitter_and_client_submitter, cgci_blgsp
)

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

get_index_uuid.side_effect = get_index_by_uuid

submit_metadata_file(client, pg_driver, submitter, cgci_blgsp)
submit_metadata_file(client, pg_driver, submitter_and_client_submitter, cgci_blgsp)

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

# no index or alias creation
Expand Down Expand Up @@ -678,7 +684,7 @@ def test_data_file_update_url_invalid_id(
get_index_hash,
client,
pg_driver,
submitter,
submitter_and_client_submitter,
cgci_blgsp,
):
"""
Expand All @@ -689,7 +695,9 @@ def test_data_file_update_url_invalid_id(
FIXME: the 1:1 between node id and index/file id is temporary so this
test may need to be modified in the future
"""
submit_first_experiment(client, pg_driver, submitter, cgci_blgsp)
submit_first_experiment(
client, pg_driver, submitter_and_client_submitter, cgci_blgsp
)

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

submit_metadata_file(client, pg_driver, submitter, cgci_blgsp)
submit_metadata_file(client, pg_driver, submitter_and_client_submitter, cgci_blgsp)

# now submit again but change url
new_url = "some/new/url/location/to/add"
updated_file = copy.deepcopy(DEFAULT_METADATA_FILE)
updated_file["urls"] = new_url
updated_file["id"] = DEFAULT_UUID
resp = submit_metadata_file(
client, pg_driver, submitter, cgci_blgsp, data=updated_file
client, pg_driver, submitter_and_client_submitter, cgci_blgsp, data=updated_file
)

# no index or alias creation
Expand Down
Loading

0 comments on commit 985a3c7

Please sign in to comment.