diff --git a/SPRINTLOG.md b/SPRINTLOG.md index 64dd873a9..05c86da58 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -459,7 +459,8 @@ _Nothing merged during this sprint_ - Logging: Add which user name reset password ([https://github.com/ScilifelabDataCentre/dds_web/pull/1574](https://github.com/ScilifelabDataCentre/dds_web/pull/1574)) -# 2024-12-02 - 2024-12-13 +# 2024-12-02 – 2024-12-13 +- Change the error raised upon attempt to download data after a password reset to an AuthenticationError to avoid getting an alert ([#1571](https://github.com/ScilifelabDataCentre/dds_web/pull/1571)) - Filter out the MaintenanceModeException from the logs ([#1573](https://github.com/ScilifelabDataCentre/dds_web/pull/1573)) - Bugfix: Quick and dirty change to prevent `dds ls --tree` from failing systematically ([#1575](https://github.com/ScilifelabDataCentre/dds_web/pull/1575) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 0ce611cfe..9d3530041 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -592,7 +592,7 @@ def get(self): """Get private key from database.""" # Verify project ID and access project = project_schemas.ProjectRequiredSchema().load(flask.request.args) - + dds_web.utils.verify_project_user_key(project=project) flask.current_app.logger.debug("Getting the private key.") return flask.jsonify( diff --git a/dds_web/utils.py b/dds_web/utils.py index f815df4bf..46238db2c 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -85,6 +85,24 @@ def verify_project_access(project) -> None: ) +def verify_project_user_key(project) -> None: + """Verify that current authenticated user has a row in projectUserKeys.""" + project_key = models.ProjectUserKeys.query.filter_by( + project_id=project.id, user_id=auth.current_user().username + ).one_or_none() + if not project_key: + msg = ( + "You have lost access to this project. " + "This is likely due to a password reset, in which case you have lost access to all active projects. " + f"In order to regain access to this project, please contact {project.responsible_unit.external_display_name} ({project.responsible_unit.contact_email}) and ask them to run 'dds project access fix'." + ) + raise AccessDeniedError( + message=msg, + username=auth.current_user().username, + project=project.public_id, + ) + + def verify_cli_version(version_cli: str = None) -> None: """Verify that the CLI version in header is compatible with the web version.""" # Verify that version is specified diff --git a/tests/test_utils.py b/tests/test_utils.py index 7d70681b5..6a5dcfabd 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -136,6 +136,70 @@ def test_verify_project_access_ok(client: flask.testing.FlaskClient) -> None: utils.verify_project_access(project=project) +def test_verify_project_user_key_denied(client: flask.testing.FlaskClient) -> None: + """A user must have an entry in projectUserKeys to access a project.""" + # User + user = models.UnitUser.query.filter_by(unit_id=1).first() + assert user + + # Get project + project = models.Project.query.filter_by(unit_id=1).first() + assert project + + # get projectUserKey + projectUserKey = models.ProjectUserKeys.query.filter_by( + project_id=project.id, user_id=user.username + ).one_or_none() + assert projectUserKey + + # delete projectUserKey + db.session.delete(projectUserKey) + db.session.commit() + + # verify no projectUserKey + projectUserKey = models.ProjectUserKeys.query.filter_by( + project_id=project.id, user_id=user.username + ).one_or_none() + assert not projectUserKey + + # Set auth.current_user + flask.g.flask_httpauth_user = user + + # Verify project access -- not ok + with pytest.raises(AccessDeniedError) as err: + utils.verify_project_user_key(project=project) + + error_msg = ( + "You have lost access to this project. " + "This is likely due to a password reset, in which case you have lost access to all active projects. " + f"In order to regain access to this project, please contact {project.responsible_unit.external_display_name} ({project.responsible_unit.contact_email}) and ask them to run 'dds project access fix'." + ) + assert error_msg in str(err.value) + + +def test_verify_project_user_key_ok(client: flask.testing.FlaskClient) -> None: + """A user must have an entry in projectUserKeys to access a project.""" + # user + user = models.UnitUser.query.filter_by(unit_id=1).first() + assert user + + # Get project + project = models.Project.query.filter_by(unit_id=1).first() + assert project + + # get projectUserKey + projectUserKey = models.ProjectUserKeys.query.filter_by( + project_id=project.id, user_id=user.username + ).one_or_none() + assert projectUserKey + + # Set auth.current_user + flask.g.flask_httpauth_user = user + + # Verify project access -- ok + utils.verify_project_access(project=project) + + # verify_cli_version