Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add logic for private writeup that must be shared with admin #195

Merged
merged 3 commits into from
Jul 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 42 additions & 31 deletions challengeutils/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,48 +138,59 @@ def _validate_project_id(proj, challenge):
def _validate_public_permissions(syn, proj):
"""Ensure project is shared with the public."""

auth_perms = syn.getPermissions(proj.entityId, AUTHENTICATED_USERS)
public_perms = syn.getPermissions(proj.entityId)
if ("READ" not in auth_perms or "DOWNLOAD" not in auth_perms) and \
"READ" not in public_perms:
return ("Your project is not publicly available. Visit "
"https://docs.synapse.org/articles/sharing_settings.html for "
"more details.")
return ""
error = "Your project is not publicly available."

try:
# Remove error message if the project is accessible by the public.
syn_users_perms = syn.getPermissions(
proj.entityId, AUTHENTICATED_USERS)
public_perms = syn.getPermissions(proj.entityId)
if set(syn_users_perms) == {"READ", "DOWNLOAD"} and \
"READ" in public_perms:
error = ""

except SynapseHTTPError as e:

# Raise exception message if error is not a permissions error.
if e.response.status_code != 403:
raise e

return error


def _validate_admin_permissions(syn, proj, admin):
"""Ensure project is shared with the given admin."""

admin_perms = syn.getPermissions(proj.entityId, admin)
if "READ" not in admin_perms or "DOWNLOAD" not in admin_perms:
return (f"Your private project should be shared with {admin}. Visit "
"https://docs.synapse.org/articles/sharing_settings.html for "
"more details.")
return ""
error = ("Project is private; please update its sharing settings."
f" Writeup should be shared with {admin}.")
try:
# Remove error message if admin has read and download permissions.
admin_perms = syn.getPermissions(proj.entityId, admin)
if set(admin_perms) == {"READ", "DOWNLOAD"}:
error = ""

except SynapseHTTPError as e:

# Raise exception message if error is not a permissions error.
if e.response.status_code != 403:
raise e

return error


def _check_project_permissions(syn, submission, public, admin):
"""Check the submission sharing settings."""

errors = []
try:
if public:
public_error = _validate_public_permissions(syn, submission)
if public_error:
errors.append(public_error)

if not public and admin is not None:
admin_error = _validate_admin_permissions(syn, submission, admin)
if admin_error:
errors.append(admin_error)

except SynapseHTTPError as e:
if e.response.status_code == 403:
errors.append(
"Submission is private; please update its sharing settings.")
else:
raise e
if public:
public_error = _validate_public_permissions(syn, submission)
if public_error:
errors.append(public_error)

if not public and admin is not None:
admin_error = _validate_admin_permissions(syn, submission, admin)
if admin_error:
errors.append(admin_error)
return errors


Expand Down
99 changes: 47 additions & 52 deletions tests/test_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
QUEUEID = "333"


# test that only Project entity type is accepted
@pytest.mark.parametrize("project, output", [
(PROJ, ""),
(mock.create_autospec(
Expand All @@ -37,78 +36,74 @@
"Unknown entity type; please submit a Synapse project.")
])
def test__validate_ent_type_submission_type_project(project, output):
"""Submission is a Project; no errors expected."""

"""Tests that only a Project submission is accepted."""
assert submission._validate_ent_type(project) == output


# test that Challenge project is not accepted
@pytest.mark.parametrize("syn_id, output", [
("syn000", ""),
("syn123", "Submission should not be the Challenge site.")
])
def test__validate_project_id_nonchallenge_submission(syn_id, output):
"""Submission is not the Challenge site; no errors expected."""

"""Tests that project ID is not the Challenge project ID."""
assert submission._validate_project_id(PROJ, syn_id) == output


# test private project with admin requirement
def test__validate_admin_permissions_admin_permissions_req():
"""
Project should be shared with an admin; one call to check for
permissions expected.
"""
"""Tests sharing settings for an admin.

One call to check for permissions expected.
"""
admin = "me"
with patch.object(SYN, "getPermissions") as patch_perms:
errors = submission._validate_admin_permissions(
SYN, PROJ, admin=admin)
patch_perms.assert_called_once()

message = (
f"Your private project should be shared with {admin}. Visit "
"https://docs.synapse.org/articles/sharing_settings.html "
"for more details."
)
message = ("Project is private; please update its sharing settings."
f" Writeup should be shared with {admin}.")
assert errors == message


# test private project with public requirement
def test__validate_public_permissions_public_permissions_req():
"""
Project should be shared with the public (incl. other Synapse
users); two calls to check for permissions expected.
"""
"""Tests sharing settings to the public.

Two calls to check for permissions expected (one for all Synapse
users, another for the public).
"""
with patch.object(SYN, "getPermissions") as patch_perms:
errors = submission._validate_public_permissions(SYN, PROJ)
assert patch_perms.call_count == 2
assert errors == "Your project is not publicly available."

message = ("Your project is not publicly available. Visit "
"https://docs.synapse.org/articles/sharing_settings.html "
"for more details.")
assert errors == message

@pytest.mark.parametrize("public, admin, output", [
(True, None, "Your project is not publicly available."),
(True, "me", "Your project is not publicly available."),
(False, "me", ("Project is private; please update its sharing settings."
" Writeup should be shared with me."))
])
def test__check_project_permissions_errorcode(public, admin, output):
"""Tests that exception is thrown when project is private.

def test__check_project_permissions_errorcode():
If project is private, but is expected to be accessible by the public
or an admin user/team, then exception should be thrown. If both flags
are given, then `public` error message should take precedence.
"""
mocked_403 = SynapseHTTPError("foo", response=Mock(status_code=403))
with patch.object(SYN, "getPermissions",
side_effect=mocked_403) as patch_perms:
errors = submission._check_project_permissions(
SYN, PROJ, public=True, admin="bob")
assert errors == [
"Submission is private; please update its sharing settings."]
SYN, PROJ, public=public, admin=admin)
assert errors == [output]


# test that command works as expected
@pytest.mark.parametrize("syn_id, output", [
("syn000", "VALIDATED"),
("syn123", "INVALID")
])
def test_validate_project_command_success(syn_id, output):
"""Valid submission; status should be VALIDATED."""

"""Tests that overall functionality works."""
with patch.object(SYN, "getSubmission", return_value=PROJ) as patch_sub:
results = submission.validate_project(SYN, patch_sub, syn_id)
assert results.get('submission_status') == output
Expand All @@ -128,13 +123,13 @@ def test_validate_docker_submission_valid():
dockerRepositoryName=docker_repo,
dockerDigest=docker_digest)
with patch.object(SYN, "getConfigFile", return_value=config),\
patch.object(config, "items",
return_value={'username': username,
'password': password}),\
patch.object(SYN, "getSubmission",
return_value=docker_sub) as patch_get_sub,\
patch.object(dockertools, "validate_docker",
return_value=True) as patch_validate:
patch.object(config, "items",
return_value={'username': username,
'password': password}),\
patch.object(SYN, "getSubmission",
return_value=docker_sub) as patch_get_sub,\
patch.object(dockertools, "validate_docker",
return_value=True) as patch_validate:
valid = submission.validate_docker_submission(SYN, "123455")
patch_validate.assert_called_once_with(
docker_repo="syn1234/docker_repo",
Expand All @@ -152,12 +147,12 @@ def test_validate_docker_submission_nousername():
config = Mock()
password = str(uuid.uuid1())
with patch.object(SYN, "getConfigFile", return_value=config),\
patch.object(config, "items",
return_value={'username': None,
'password': password}),\
pytest.raises(ValueError,
match='Synapse config file must have username '
'and password'):
patch.object(config, "items",
return_value={'username': None,
'password': password}),\
pytest.raises(ValueError,
match='Synapse config file must have username '
'and password'):
submission.validate_docker_submission(SYN, "123455")


Expand All @@ -174,13 +169,13 @@ def test_validate_docker_submission_notdocker():
dockerRepositoryName=docker_repo,
dockerDigest=docker_digest)
with patch.object(SYN, "getConfigFile", return_value=config),\
patch.object(config, "items",
return_value={'username': username,
'password': password}),\
patch.object(SYN, "getSubmission",
return_value=docker_sub),\
pytest.raises(ValueError,
match='Submission is not a Docker submission'):
patch.object(config, "items",
return_value={'username': username,
'password': password}),\
patch.object(SYN, "getSubmission",
return_value=docker_sub),\
pytest.raises(ValueError,
match='Submission is not a Docker submission'):
submission.validate_docker_submission(SYN, "123455")


Expand Down