def test_permissions_revoke_all_iam_permissions(clean_db, monkeypatch): """ Smoke test that Permissions.revoke_all_iam_permissions calls revoke_download_access the right arguments. """ gcloud_client = mock_gcloud_client(monkeypatch) user = Users(email="*****@*****.**") user.insert() trial = TrialMetadata(trial_id=TRIAL_ID, metadata_json=METADATA) trial.insert() upload_types = ["wes_bam", "ihc", "rna_fastq", "plasma"] for upload_type in upload_types: Permissions( granted_to_user=user.id, trial_id=trial.trial_id, upload_type=upload_type, granted_by_user=user.id, ).insert() Permissions.revoke_all_iam_permissions() gcloud_client.revoke_download_access.assert_has_calls( [call(user.email, trial.trial_id, upload_type) for upload_type in upload_types] ) # not called on admins or nci biobank users gcloud_client.revoke_download_access.reset_mock() for role in [CIDCRole.ADMIN.value, CIDCRole.NCI_BIOBANK_USER.value]: user.role = role user.update() Permissions.revoke_all_iam_permissions() gcloud_client.revoke_download_access.assert_not_called()
def test_permissions_delete(clean_db, monkeypatch, caplog): gcloud_client = mock_gcloud_client(monkeypatch) user = Users(email="*****@*****.**") user.insert() trial = TrialMetadata(trial_id=TRIAL_ID, metadata_json=METADATA) trial.insert() perm = Permissions( granted_to_user=user.id, trial_id=trial.trial_id, upload_type="wes_bam", granted_by_user=user.id, ) perm.insert() # Deleting a record by a user doesn't exist leads to an error gcloud_client.reset_mocks() with pytest.raises(NoResultFound, match="no user with id"): perm.delete(deleted_by=999999) # Deletion of an existing permission leads to no error gcloud_client.reset_mocks() with caplog.at_level(logging.DEBUG): perm.delete(deleted_by=user.id) gcloud_client.revoke_download_access.assert_called_once() gcloud_client.grant_download_access.assert_not_called() assert any( log_record.message.strip() == f"admin-action: {user.email} removed from {user.email} the permission wes_bam on {trial.trial_id}" for log_record in caplog.records ) # Deleting an already-deleted record is idempotent gcloud_client.reset_mocks() perm.delete(deleted_by=user) gcloud_client.revoke_download_access.assert_called_once() gcloud_client.grant_download_access.assert_not_called() # Deleting a record whose user doesn't exist leads to an error gcloud_client.reset_mocks() with pytest.raises(NoResultFound, match="no user with id"): Permissions(granted_to_user=999999).delete(deleted_by=user) gcloud_client.revoke_download_access.assert_not_called() gcloud_client.grant_download_access.assert_not_called() # If revoking a permission from a "network-viewer", no GCS IAM actions are taken gcloud_client.revoke_download_access.reset_mock() user.role = CIDCRole.NETWORK_VIEWER.value user.update() perm = Permissions( granted_to_user=user.id, trial_id=trial.trial_id, upload_type="ihc", granted_by_user=user.id, ) perm.insert() perm.delete(deleted_by=user) gcloud_client.revoke_download_access.assert_not_called()
def test_user_confirm_approval(clean_db, monkeypatch): """Ensure that users are notified when their account goes from pending to approved.""" confirm_account_approval = MagicMock() monkeypatch.setattr( "cidc_api.shared.emails.confirm_account_approval", confirm_account_approval ) user = Users(email="*****@*****.**") user.insert() # The confirmation email shouldn't be sent for updates unrelated to account approval user.update(changes={"first_n": "foo"}) confirm_account_approval.assert_not_called() # The confirmation email should be sent for updates related to account approval user.update(changes={"approval_date": datetime.now()}) confirm_account_approval.assert_called_once_with(user, send_email=True)
def test_common_update(clean_db): """Test update, inherited from CommonColumns""" email = "foo" user = Users(id=1, email=email) # Record not found with pytest.raises(NoResultFound): user.update() user.insert() _updated = user._updated # Update via setattr and changes first_n = "hello" last_n = "goodbye" user.last_n = last_n user.update(changes={"first_n": first_n}) user = Users.find_by_id(user.id) assert user._updated > _updated assert user.first_n == first_n assert user.last_n == last_n _updated = user._updated _etag = user._etag # Make sure you can clear a field to null user.update(changes={"first_n": None}) user = Users.find_by_id(user.id) assert user._updated > _updated assert _etag != user._etag assert user.first_n is None _updated = user._updated _etag = user._etag # Make sure etags don't change if public fields don't change user.update() user = Users.find_by_id(user.id) assert user._updated > _updated assert _etag == user._etag
def test_permissions_insert(clean_db, monkeypatch, caplog): gcloud_client = mock_gcloud_client(monkeypatch) user = Users(email="*****@*****.**") user.insert() trial = TrialMetadata(trial_id=TRIAL_ID, metadata_json=METADATA) trial.insert() _insert = MagicMock() monkeypatch.setattr(CommonColumns, "insert", _insert) # if upload_type is invalid with pytest.raises(ValueError, match="invalid upload type"): Permissions(upload_type="foo", granted_to_user=user.id, trial_id=trial.trial_id) # if don't give granted_by_user perm = Permissions( granted_to_user=user.id, trial_id=trial.trial_id, upload_type="wes_bam" ) with pytest.raises(IntegrityError, match="`granted_by_user` user must be given"): perm.insert() _insert.assert_not_called() # if give bad granted_by_user _insert.reset_mock() perm = Permissions( granted_to_user=user.id, trial_id=trial.trial_id, upload_type="wes_bam", granted_by_user=999999, ) with pytest.raises(IntegrityError, match="`granted_by_user` user must exist"): perm.insert() _insert.assert_not_called() # if give bad granted_to_user _insert.reset_mock() perm = Permissions( granted_to_user=999999, trial_id=trial.trial_id, upload_type="wes_bam", granted_by_user=user.id, ) with pytest.raises(IntegrityError, match="`granted_to_user` user must exist"): perm.insert() _insert.assert_not_called() # This one will work _insert.reset_mock() perm = Permissions( granted_to_user=user.id, trial_id=trial.trial_id, upload_type="wes_bam", granted_by_user=user.id, ) with caplog.at_level(logging.DEBUG): perm.insert() _insert.assert_called_once() assert any( log_record.message.strip() == f"admin-action: {user.email} gave {user.email} the permission wes_bam on {trial.trial_id}" for log_record in caplog.records ) gcloud_client.grant_download_access.assert_called_once() # If granting a permission to a "network-viewer", no GCS IAM actions are taken _insert.reset_mock() gcloud_client.grant_download_access.reset_mock() user.role = CIDCRole.NETWORK_VIEWER.value user.update() perm = Permissions( granted_to_user=user.id, trial_id=trial.trial_id, upload_type="ihc", granted_by_user=user.id, ) perm.insert() _insert.assert_called_once() gcloud_client.grant_download_access.assert_not_called()
def test_authorize(cidc_api, clean_db): """Check that authorization works as expected.""" user = Users(**PAYLOAD) with cidc_api.app_context(): # Unregistered user should not be authorized to do anything to any resource except "users" with pytest.raises(Unauthorized, match="not registered"): auth.authorize(user, [], "some-resource", "some-http-method") # We can't track accesses for users who aren't registered assert user._accessed is None # Unregistered user should not be able to GET users with pytest.raises(Unauthorized, match="not registered"): auth.authorize(user, [], "users", "GET") assert user._accessed is None # Unregistered user should not be able to GET self with pytest.raises(Unauthorized, match="not registered"): auth.authorize(user, [], "self", "GET") assert user._accessed is None # Unregistered user should be able to POST users assert auth.authorize(user, [], "self", "POST") # Add the user to the db but don't approve yet user.insert() # Unapproved user isn't authorized to do anything with pytest.raises(Unauthorized, match="pending approval"): auth.authorize(user, [], "self", "POST") # Check that we tracked this user's last access assert user._accessed.date() == date.today() _accessed = user._accessed # Ensure unapproved user can access their own data assert auth.authorize(user, [], "self", "GET") # Give the user a role but don't approve them user.role = CIDCRole.CIMAC_USER.value user.update() # Unapproved user *with an authorized role* still shouldn't be authorized with pytest.raises(Unauthorized, match="pending approval"): auth.authorize(user, [CIDCRole.CIMAC_USER.value], "self", "POST") # Approve the user user.approval_date = datetime.now() user.update() # If user doesn't have required role, they should not be authorized. with pytest.raises(Unauthorized, match="not authorized to access"): auth.authorize(user, [CIDCRole.ADMIN.value], "some-resource", "some-http-method") # If user has an allowed role, they should be authorized assert auth.authorize(user, [CIDCRole.CIMAC_USER.value], "some-resource", "some-http-method") # If the resource has no role restrictions, they should be authorized assert auth.authorize(user, [], "some-resource", "some-http-method") # Disable user user.disabled = True user.update() # If user has an allowed role but is disabled, they should be unauthorized with pytest.raises(Unauthorized, match="disabled"): auth.authorize(user, [CIDCRole.CIMAC_USER.value], "some-resource", "some-http-method") # Ensure unapproved user can access their own data assert auth.authorize(user, [], "self", "GET") # If the resource has no role restrictions, they should be still unauthorized with pytest.raises(Unauthorized, match="disabled"): auth.authorize(user, [], "some-resource", "some-http-method") # Check that user's last access wasn't updated by all activity, # since it occurred on the same day as previous accesses assert user._accessed == _accessed