From d91a028f45525dd4a769349102cd7ebe03250a43 Mon Sep 17 00:00:00 2001 From: Ina Date: Tue, 27 Sep 2022 15:53:09 +0200 Subject: [PATCH 001/123] stop if maintenance --- dds_web/api/__init__.py | 1 - dds_web/api/dds_decorators.py | 16 ++++++++++++++++ dds_web/api/project.py | 2 ++ dds_web/errors.py | 10 ++++++++++ dds_web/security/auth.py | 2 +- dds_web/utils.py | 2 ++ 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/dds_web/api/__init__.py b/dds_web/api/__init__.py index 0ebc5aeaf..1cda42aa2 100644 --- a/dds_web/api/__init__.py +++ b/dds_web/api/__init__.py @@ -29,7 +29,6 @@ def output_json(data, code, headers=None): resp.headers.extend(headers or {}) return resp - #################################################################################################### # RESOURCES ############################################################################ RESOURCES # #################################################################################################### diff --git a/dds_web/api/dds_decorators.py b/dds_web/api/dds_decorators.py index 57ba2bb0a..440c4890d 100644 --- a/dds_web/api/dds_decorators.py +++ b/dds_web/api/dds_decorators.py @@ -22,10 +22,13 @@ DatabaseError, DDSArgumentError, LoggedHTTPException, + MaintenanceOngoingException, MissingJsonError, S3ConnectionError, ) from dds_web.utils import get_username_or_request_ip +from dds_web.database import models +from dds_web import auth # initiate logging action_logger = structlog.getLogger("actions") @@ -212,3 +215,16 @@ def wrapper_logging_bind_request(*args, **kwargs): raise return wrapper_logging_bind_request + +def stop_if_maintenance(func): + """Check if DDS is in maintenance and in that case do not accept new commands.""" + @functools.wraps(func) + def maintenance_check(*args, **kwargs): + # Get maintenance row + row: models.Maintenance = models.Maintenance.query.first() + if row.active and auth.current_user().role != "Super Admin": + raise MaintenanceOngoingException() + + return func(*args, **kwargs) + + return maintenance_check \ No newline at end of file diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 1bd40111b..9ea2580b1 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -25,6 +25,7 @@ dbsession, json_required, handle_validation_errors, + stop_if_maintenance ) from dds_web.errors import ( AccessDeniedError, @@ -448,6 +449,7 @@ class UserProjects(flask_restful.Resource): @auth.login_required @logging_bind_request + @stop_if_maintenance def get(self): """Get info regarding all projects which user is involved in.""" return self.format_project_dict(current_user=auth.current_user()) diff --git a/dds_web/errors.py b/dds_web/errors.py index 37bf4608f..e1c50b1e8 100644 --- a/dds_web/errors.py +++ b/dds_web/errors.py @@ -423,3 +423,13 @@ def __init__( ): super().__init__(message) general_logger.warning(message) + + +class MaintenanceOngoingException(LoggedHTTPException): + + code = http.HTTPStatus.FORBIDDEN + + def __init__(self, message="Maintenance of DDS is ongoing."): + """Inform that maintenance is ongoing.""" + super().__init__(message) + general_logger.warning(message) \ No newline at end of file diff --git a/dds_web/security/auth.py b/dds_web/security/auth.py index 039d1a0f8..0c3b0fb71 100644 --- a/dds_web/security/auth.py +++ b/dds_web/security/auth.py @@ -18,7 +18,7 @@ # Own modules from dds_web import basic_auth, auth, mail -from dds_web.errors import AuthenticationError, AccessDeniedError, InviteError, TokenMissingError +from dds_web.errors import AuthenticationError, AccessDeniedError, InviteError, MaintenanceOngoingException, TokenMissingError from dds_web.database import models import dds_web.utils diff --git a/dds_web/utils.py b/dds_web/utils.py index 9bdaa2b82..3d18aaa73 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -18,6 +18,7 @@ import flask from dds_web.errors import ( AccessDeniedError, + MaintenanceOngoingException, VersionMismatchError, DDSArgumentError, NoSuchProjectError, @@ -571,3 +572,4 @@ def calculate_version_period_usage(version): version.time_invoiced = now return bytehours + From e76075e33bfe0f9190e343ec914e3cb33ed495c2 Mon Sep 17 00:00:00 2001 From: Ina Date: Tue, 27 Sep 2022 16:27:10 +0200 Subject: [PATCH 002/123] decorator, blocking non busy projects, not working --- dds_web/api/dds_decorators.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/dds_web/api/dds_decorators.py b/dds_web/api/dds_decorators.py index 440c4890d..62427058d 100644 --- a/dds_web/api/dds_decorators.py +++ b/dds_web/api/dds_decorators.py @@ -26,7 +26,7 @@ MissingJsonError, S3ConnectionError, ) -from dds_web.utils import get_username_or_request_ip +from dds_web.utils import collect_project, get_username_or_request_ip from dds_web.database import models from dds_web import auth @@ -221,9 +221,14 @@ def stop_if_maintenance(func): @functools.wraps(func) def maintenance_check(*args, **kwargs): # Get maintenance row - row: models.Maintenance = models.Maintenance.query.first() - if row.active and auth.current_user().role != "Super Admin": - raise MaintenanceOngoingException() + maintenance: models.Maintenance = models.Maintenance.query.first() + + # Super Admins always allowed in during maintenance + # Only busy projects accept requests - could be ongoing upload / download / delete + if maintenance.active and auth.current_user().role != "Super Admin": + if (req_args := flask.request.args) and (project_id := req_args.get("project")): + if not project_id or ((project := collect_project(project_id=project_id)) and not project.busy): + raise MaintenanceOngoingException() return func(*args, **kwargs) From 476a80e185a14f8b27536ec296d25670a6cefdba Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 08:30:55 +0200 Subject: [PATCH 003/123] stop if maintenance cleanup --- dds_web/api/__init__.py | 1 + dds_web/api/dds_decorators.py | 26 ++++++++++++++++++-------- dds_web/api/project.py | 4 +++- dds_web/errors.py | 2 +- dds_web/security/auth.py | 10 +++++++++- dds_web/utils.py | 1 - 6 files changed, 32 insertions(+), 12 deletions(-) diff --git a/dds_web/api/__init__.py b/dds_web/api/__init__.py index 1cda42aa2..0ebc5aeaf 100644 --- a/dds_web/api/__init__.py +++ b/dds_web/api/__init__.py @@ -29,6 +29,7 @@ def output_json(data, code, headers=None): resp.headers.extend(headers or {}) return resp + #################################################################################################### # RESOURCES ############################################################################ RESOURCES # #################################################################################################### diff --git a/dds_web/api/dds_decorators.py b/dds_web/api/dds_decorators.py index 62427058d..a4b4fed79 100644 --- a/dds_web/api/dds_decorators.py +++ b/dds_web/api/dds_decorators.py @@ -216,20 +216,30 @@ def wrapper_logging_bind_request(*args, **kwargs): return wrapper_logging_bind_request + def stop_if_maintenance(func): """Check if DDS is in maintenance and in that case do not accept new commands.""" + @functools.wraps(func) def maintenance_check(*args, **kwargs): + # Super admins are always allowed in + if auth.current_user().role == "Super Admin": + return func(*args, **kwargs) + # Get maintenance row maintenance: models.Maintenance = models.Maintenance.query.first() - + # Super Admins always allowed in during maintenance - # Only busy projects accept requests - could be ongoing upload / download / delete - if maintenance.active and auth.current_user().role != "Super Admin": - if (req_args := flask.request.args) and (project_id := req_args.get("project")): - if not project_id or ((project := collect_project(project_id=project_id)) and not project.busy): - raise MaintenanceOngoingException() + if maintenance.active: + # Project needs to be specified in order to allow access during maintenance + if not (req_args := flask.request.args) or not (project_id := req_args.get("project")): + raise MaintenanceOngoingException() + + # Only busy projects are allowed access during maintenance + project: models.Project = collect_project(project_id=project_id) + if not project.busy: + raise MaintenanceOngoingException() return func(*args, **kwargs) - - return maintenance_check \ No newline at end of file + + return maintenance_check diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 9ea2580b1..b9dd54ef8 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -25,7 +25,7 @@ dbsession, json_required, handle_validation_errors, - stop_if_maintenance + stop_if_maintenance, ) from dds_web.errors import ( AccessDeniedError, @@ -561,6 +561,7 @@ class RemoveContents(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request + @stop_if_maintenance @dbsession @handle_validation_errors def delete(self): @@ -630,6 +631,7 @@ def delete_project_contents(project, delete_bucket=False): class CreateProject(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request + @stop_if_maintenance @json_required @handle_validation_errors def post(self): diff --git a/dds_web/errors.py b/dds_web/errors.py index e1c50b1e8..30d68e842 100644 --- a/dds_web/errors.py +++ b/dds_web/errors.py @@ -432,4 +432,4 @@ class MaintenanceOngoingException(LoggedHTTPException): def __init__(self, message="Maintenance of DDS is ongoing."): """Inform that maintenance is ongoing.""" super().__init__(message) - general_logger.warning(message) \ No newline at end of file + general_logger.warning(message) diff --git a/dds_web/security/auth.py b/dds_web/security/auth.py index 0c3b0fb71..585aa4ccf 100644 --- a/dds_web/security/auth.py +++ b/dds_web/security/auth.py @@ -18,7 +18,13 @@ # Own modules from dds_web import basic_auth, auth, mail -from dds_web.errors import AuthenticationError, AccessDeniedError, InviteError, MaintenanceOngoingException, TokenMissingError +from dds_web.errors import ( + AuthenticationError, + AccessDeniedError, + InviteError, + MaintenanceOngoingException, + TokenMissingError, +) from dds_web.database import models import dds_web.utils @@ -77,6 +83,8 @@ def get_user_roles_common(user): has been specified, the user role is returned as Project Owner. Otherwise, it is Researcher. For all other users, return the value of the role set in the database table. + + Not run if the endpoint accepts all roles. """ if flask.request.path in "/api/v1/proj/create" and user.role not in [ "Unit Admin", diff --git a/dds_web/utils.py b/dds_web/utils.py index 3d18aaa73..9703d17e0 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -572,4 +572,3 @@ def calculate_version_period_usage(version): version.time_invoiced = now return bytehours - From 976d6e9d90c32e7a29652dcb6c7340bf6a2b4fb6 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 08:47:04 +0200 Subject: [PATCH 004/123] maintenance row to test init --- dds_web/api/project.py | 7 +++++++ tests/conftest.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index b9dd54ef8..d078f6f49 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -54,6 +54,7 @@ class ProjectStatus(flask_restful.Resource): @auth.login_required @logging_bind_request + @stop_if_maintenance @handle_validation_errors def get(self): """Get current project status and optionally entire status history""" @@ -80,6 +81,7 @@ def get(self): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request + @stop_if_maintenance @json_required @handle_validation_errors def post(self): @@ -402,6 +404,7 @@ class GetPublic(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request + @stop_if_maintenance @handle_validation_errors def get(self): """Get public key from database.""" @@ -423,6 +426,7 @@ class GetPrivate(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request + @stop_if_maintenance @handle_validation_errors def get(self): """Get private key from database.""" @@ -762,6 +766,7 @@ class ProjectUsers(flask_restful.Resource): @auth.login_required @logging_bind_request + @stop_if_maintenance @handle_validation_errors def get(self): # Verify project ID and access @@ -798,6 +803,7 @@ class ProjectAccess(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner"]) @logging_bind_request + @stop_if_maintenance @dbsession @json_required @handle_validation_errors @@ -904,6 +910,7 @@ def give_project_access(project_list, current_user, user): class ProjectBusy(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request + @stop_if_maintenance @dbsession @json_required def put(self): diff --git a/tests/conftest.py b/tests/conftest.py index 4675167b2..3681a74ae 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,6 +29,7 @@ Version, Identifier, DeletionRequest, + Maintenance ) import dds_web.utils from dds_web import create_app, db @@ -48,6 +49,8 @@ def fill_basic_db(db): """ Fill the database with basic data. """ + maintenance_row = Maintenance(active=False) + db.session.add(maintenance_row) units, users, projects = add_data_to_db() db.session.add_all(units) From 4c5eafc95f8c77a54054cedc73667e693030960f Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 08:58:17 +0200 Subject: [PATCH 005/123] fixed test --- tests/conftest.py | 2 +- tests/test_models.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3681a74ae..8b7c3d43c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,7 +29,7 @@ Version, Identifier, DeletionRequest, - Maintenance + Maintenance, ) import dds_web.utils from dds_web import create_app, db diff --git a/tests/test_models.py b/tests/test_models.py index 651e6b23d..bc433d7af 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -571,6 +571,9 @@ def test_delete_version(client): def test_new_maintenance_active(client: flask.testing.FlaskClient) -> None: """Create a new maintenance row.""" # Verify no maintenance row yet + assert models.Maintenance.query.count() == 1 + models.Maintenance.query.delete() + db.session.commit() assert models.Maintenance.query.count() == 0 # Create row @@ -586,6 +589,9 @@ def test_new_maintenance_active(client: flask.testing.FlaskClient) -> None: def test_new_maintenance_inactive(client: flask.testing.FlaskClient) -> None: """Create a new maintenance row.""" # Verify no maintenance row yet + assert models.Maintenance.query.count() == 1 + models.Maintenance.query.delete() + db.session.commit() assert models.Maintenance.query.count() == 0 # Create row From d3e115fab2eafce483bd6d1e3121e31d983c2ad5 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 09:07:35 +0200 Subject: [PATCH 006/123] added decorator to files --- dds_web/api/files.py | 10 ++++++++++ tests/api/test_superadmin_only.py | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/dds_web/api/files.py b/dds_web/api/files.py index daf81a171..fcbdfd340 100644 --- a/dds_web/api/files.py +++ b/dds_web/api/files.py @@ -25,6 +25,7 @@ logging_bind_request, json_required, handle_validation_errors, + stop_if_maintenance ) from dds_web.errors import ( AccessDeniedError, @@ -76,6 +77,7 @@ class NewFile(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request + @stop_if_maintenance @json_required @handle_validation_errors def post(self): @@ -110,6 +112,7 @@ def post(self): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request + @stop_if_maintenance @handle_validation_errors def put(self): """Update existing file.""" @@ -204,6 +207,7 @@ class MatchFiles(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request + @stop_if_maintenance @json_required @handle_validation_errors def get(self): @@ -244,6 +248,7 @@ class ListFiles(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request + @stop_if_maintenance @handle_validation_errors def get(self): """Get a list of files within the specified folder.""" @@ -406,6 +411,7 @@ class RemoveFile(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request + @stop_if_maintenance @json_required @handle_validation_errors def delete(self): @@ -522,6 +528,7 @@ class RemoveDir(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request + @stop_if_maintenance @json_required @handle_validation_errors def delete(self): @@ -636,6 +643,7 @@ class FileInfo(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request + @stop_if_maintenance @json_required @handle_validation_errors def get(self): @@ -670,6 +678,7 @@ class FileInfoAll(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request + @stop_if_maintenance @handle_validation_errors def get(self): """Get file info on all files.""" @@ -692,6 +701,7 @@ class UpdateFile(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request + @stop_if_maintenance @json_required @handle_validation_errors def put(self): diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 94a40eae6..bc14da1b3 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -693,8 +693,8 @@ def test_set_maintenance_off_ok(client: flask.testing.FlaskClient) -> None: setting = "off" # create record in Maintenance - current_mode: models.Maintenance = models.Maintenance(active=True) - db.session.add(current_mode) + current_mode: models.Maintenance = models.Maintenance.query.first() + current_mode.active = True db.session.commit() # Verify that maintenance is on From a0c95687c451d53324f95bbf87e02bad98113e74 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 09:12:02 +0200 Subject: [PATCH 007/123] user --- dds_web/api/user.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index bf6e47a68..bae184866 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -32,6 +32,7 @@ json_required, handle_validation_errors, handle_db_error, + stop_if_maintenance ) from dds_web.security.project_user_keys import ( generate_invite_key_pair, @@ -50,6 +51,7 @@ class AddUser(flask_restful.Resource): @auth.login_required(role=["Super Admin", "Unit Admin", "Unit Personnel", "Project Owner"]) @logging_bind_request + @stop_if_maintenance @json_required @handle_validation_errors def post(self): @@ -485,6 +487,7 @@ def compose_and_send_email_to_user(userobj, mail_type, link=None, project=None): class RetrieveUserInfo(flask_restful.Resource): @auth.login_required @logging_bind_request + @stop_if_maintenance def get(self): """Return own info when queried""" curr_user = auth.current_user() @@ -506,6 +509,7 @@ class DeleteUserSelf(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request + @stop_if_maintenance def delete(self): """Request deletion of own account.""" current_user = auth.current_user() @@ -626,6 +630,7 @@ class UserActivation(flask_restful.Resource): @auth.login_required(role=["Super Admin", "Unit Admin"]) @logging_bind_request + @stop_if_maintenance @json_required @handle_validation_errors def post(self): @@ -739,6 +744,7 @@ class DeleteUser(flask_restful.Resource): @auth.login_required(role=["Super Admin", "Unit Admin"]) @logging_bind_request + @stop_if_maintenance @handle_validation_errors def delete(self): """Delete user or invite in the DDS.""" @@ -864,6 +870,7 @@ def delete_invite(email): class RemoveUserAssociation(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request + @stop_if_maintenance @json_required @handle_validation_errors def post(self): @@ -944,6 +951,7 @@ class EncryptedToken(flask_restful.Resource): @basic_auth.login_required @logging_bind_request + @stop_if_maintenance def get(self): secondfactor_method = "TOTP" if auth.current_user().totp_enabled else "HOTP" return { @@ -960,6 +968,7 @@ class SecondFactor(flask_restful.Resource): """Take in and verify an authentication one-time code entered by an authenticated user with basic credentials""" @auth.login_required + @stop_if_maintenance @handle_validation_errors def get(self): @@ -974,6 +983,7 @@ class RequestTOTPActivation(flask_restful.Resource): """Request to switch from HOTP to TOTP for second factor authentication.""" @auth.login_required + @stop_if_maintenance def post(self): user = auth.current_user() if user.totp_enabled: @@ -1037,6 +1047,7 @@ class RequestHOTPActivation(flask_restful.Resource): # Using Basic auth since TOTP might have been lost, will still need access to email @basic_auth.login_required + @stop_if_maintenance def post(self): user = auth.current_user() @@ -1103,6 +1114,7 @@ class ShowUsage(flask_restful.Resource): @auth.login_required(role=["Super Admin", "Unit Admin", "Unit Personnel"]) @logging_bind_request + @stop_if_maintenance def get(self): current_user = auth.current_user() @@ -1185,6 +1197,7 @@ class Users(flask_restful.Resource): @auth.login_required(role=["Super Admin", "Unit Admin", "Unit Personnel"]) @logging_bind_request + @stop_if_maintenance @handle_db_error def get(self): """List unit users within the unit the current user is connected to, or the one defined by a superadmin.""" From 4286d1ce24af864094a98e177d335c324117587c Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 09:12:19 +0200 Subject: [PATCH 008/123] black --- dds_web/api/files.py | 2 +- dds_web/api/user.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dds_web/api/files.py b/dds_web/api/files.py index fcbdfd340..9fb7a9750 100644 --- a/dds_web/api/files.py +++ b/dds_web/api/files.py @@ -25,7 +25,7 @@ logging_bind_request, json_required, handle_validation_errors, - stop_if_maintenance + stop_if_maintenance, ) from dds_web.errors import ( AccessDeniedError, diff --git a/dds_web/api/user.py b/dds_web/api/user.py index bae184866..8131581ec 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -32,7 +32,7 @@ json_required, handle_validation_errors, handle_db_error, - stop_if_maintenance + stop_if_maintenance, ) from dds_web.security.project_user_keys import ( generate_invite_key_pair, From ac8eb3f1cd098092af07b8e5df11115a3a580677 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 09:26:21 +0200 Subject: [PATCH 009/123] s3 --- dds_web/api/s3.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dds_web/api/s3.py b/dds_web/api/s3.py index e34100270..c65b3bfad 100644 --- a/dds_web/api/s3.py +++ b/dds_web/api/s3.py @@ -14,7 +14,7 @@ # Own modules from dds_web import auth from dds_web.api.api_s3_connector import ApiS3Connector -from dds_web.api.dds_decorators import logging_bind_request, handle_validation_errors +from dds_web.api.dds_decorators import logging_bind_request, handle_validation_errors, stop_if_maintenance from dds_web.errors import ( S3ProjectNotFoundError, DatabaseError, @@ -32,6 +32,7 @@ class S3Info(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request + @stop_if_maintenance @handle_validation_errors def get(self): """Get the safespring project.""" From 7d3e96ea23fe3003be7a6957b5e3050d7b7003c4 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 10:35:14 +0200 Subject: [PATCH 010/123] comment --- dds_web/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index d0c6be831..3d42c640b 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -192,7 +192,9 @@ def prepare(): """Populate flask globals for template rendering""" from dds_web.utils import verify_cli_version from dds_web.utils import get_active_motds - + + # Check if maintenance mode is active and in that case display specific page + # Verify cli version compatible if "api/v1" in flask.request.path: verify_cli_version(version_cli=flask.request.headers.get("X-Cli-Version")) From 1d0acd9b1e39737cae61635052527ff47e99661b Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 12:15:17 +0200 Subject: [PATCH 011/123] error pages --- dds_web/__init__.py | 14 +++++--------- dds_web/errors.py | 1 - dds_web/templates/404.html | 18 ------------------ dds_web/web/root.py | 13 +++++++++++-- 4 files changed, 16 insertions(+), 30 deletions(-) delete mode 100644 dds_web/templates/404.html diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 3d42c640b..4be5137e3 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -36,7 +36,6 @@ from dds_web.scheduled_tasks import scheduler - #################################################################################################### # GLOBAL VARIABLES ############################################################## GLOBAL VARIABLES # #################################################################################################### @@ -192,12 +191,14 @@ def prepare(): """Populate flask globals for template rendering""" from dds_web.utils import verify_cli_version from dds_web.utils import get_active_motds - - # Check if maintenance mode is active and in that case display specific page - + # Verify cli version compatible if "api/v1" in flask.request.path: verify_cli_version(version_cli=flask.request.headers.get("X-Cli-Version")) + else: + maintenance: models.Maintenance = models.Maintenance.query.first() + if maintenance.active: + flask.abort(503) # Get message of the day flask.g.motd = get_active_motds() @@ -237,11 +238,6 @@ def prepare(): # Initialize marshmallows ma.init_app(app) - # Errors, TODO: Move somewhere else? - @app.errorhandler(sqlalchemy.exc.SQLAlchemyError) - def handle_sqlalchemyerror(e): - return f"SQLAlchemyError: {e}", 500 # TODO: Fix logging and a page - # Initialize login manager login_manager.init_app(app) diff --git a/dds_web/errors.py b/dds_web/errors.py index 30d68e842..d4c73d729 100644 --- a/dds_web/errors.py +++ b/dds_web/errors.py @@ -26,7 +26,6 @@ extra_info = {"result": "DENIED"} - class LoggedHTTPException(exceptions.HTTPException): """Base class to enable standard action logging on HTTP Exceptions""" diff --git a/dds_web/templates/404.html b/dds_web/templates/404.html deleted file mode 100644 index f66cf9bcf..000000000 --- a/dds_web/templates/404.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'base.html' %} - -{% block page_title -%} -404: Page not found -{%- endblock %} - -{% block body %} - - - -

Sorry, that page cannot be found: {{ request.base_url }}

- -

- You can get back to the homepage here. - If in doubt, please get in touch with the SciLifeLab Data Centre. -

- -{% endblock %} diff --git a/dds_web/web/root.py b/dds_web/web/root.py index 57e644cab..4b9dbc886 100644 --- a/dds_web/web/root.py +++ b/dds_web/web/root.py @@ -14,11 +14,11 @@ import cachetools import simplejson import flask +import sqlalchemy pages = Blueprint("pages", __name__) - @pages.route("/", methods=["GET"]) def home(): """Home page.""" @@ -54,4 +54,13 @@ def get_status(): @app.errorhandler(404) def page_not_found(e): # note that we set the 404 status explicitly - return render_template("404.html"), 404 + return render_template("errorpages/404.html"), 404 + +@app.errorhandler(sqlalchemy.exc.SQLAlchemyError) +def handle_sqlalchemyerror(e): + flask.current_app.logger.exception(e) + return render_template("errorpages/sqlalchemy.html"), 500 + +@app.errorhandler(503) +def maintenance_ongoing(e): + return flask.render_template("errorpages/503.html"), 503 \ No newline at end of file From 78f8bdce44a8def79659ce3efe31723f14f9c088 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 12:15:38 +0200 Subject: [PATCH 012/123] html error pages --- dds_web/templates/errorpages/404.html | 18 ++++++++++++++++++ dds_web/templates/errorpages/503.html | 13 +++++++++++++ dds_web/templates/errorpages/sqlalchemy.html | 13 +++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 dds_web/templates/errorpages/404.html create mode 100644 dds_web/templates/errorpages/503.html create mode 100644 dds_web/templates/errorpages/sqlalchemy.html diff --git a/dds_web/templates/errorpages/404.html b/dds_web/templates/errorpages/404.html new file mode 100644 index 000000000..f66cf9bcf --- /dev/null +++ b/dds_web/templates/errorpages/404.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} + +{% block page_title -%} +404: Page not found +{%- endblock %} + +{% block body %} + + + +

Sorry, that page cannot be found: {{ request.base_url }}

+ +

+ You can get back to the homepage here. + If in doubt, please get in touch with the SciLifeLab Data Centre. +

+ +{% endblock %} diff --git a/dds_web/templates/errorpages/503.html b/dds_web/templates/errorpages/503.html new file mode 100644 index 000000000..b13a37c03 --- /dev/null +++ b/dds_web/templates/errorpages/503.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} + +{% block page_title -%} +503: Service Unavailable +{%- endblock %} + +{% block body %} + + + +

Maintenance of DDS is ongoing. Please try again later.

+ +{% endblock %} diff --git a/dds_web/templates/errorpages/sqlalchemy.html b/dds_web/templates/errorpages/sqlalchemy.html new file mode 100644 index 000000000..a626149db --- /dev/null +++ b/dds_web/templates/errorpages/sqlalchemy.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} + +{% block page_title -%} +500: SQLAlchemyError +{%- endblock %} + +{% block body %} + + + +

There was an unexpected database issue. Please contact the SciLifeLab Data Centre.

+ +{% endblock %} From 8791bd25120b36bb22c7798004e9ce64cc2cf34f Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 14:00:55 +0200 Subject: [PATCH 013/123] change from sqlalchemyerror to db --- dds_web/templates/errorpages/sqlalchemy.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/templates/errorpages/sqlalchemy.html b/dds_web/templates/errorpages/sqlalchemy.html index a626149db..0f2b0d1de 100644 --- a/dds_web/templates/errorpages/sqlalchemy.html +++ b/dds_web/templates/errorpages/sqlalchemy.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block page_title -%} -500: SQLAlchemyError +500: Database Error {%- endblock %} {% block body %} From 0932939f2a1926b1ae787f9028bc79ffa08e2ffd Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 14:02:32 +0200 Subject: [PATCH 014/123] black --- dds_web/api/s3.py | 6 +++++- dds_web/errors.py | 1 + dds_web/web/root.py | 7 +++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/dds_web/api/s3.py b/dds_web/api/s3.py index c65b3bfad..8a2cdfb4c 100644 --- a/dds_web/api/s3.py +++ b/dds_web/api/s3.py @@ -14,7 +14,11 @@ # Own modules from dds_web import auth from dds_web.api.api_s3_connector import ApiS3Connector -from dds_web.api.dds_decorators import logging_bind_request, handle_validation_errors, stop_if_maintenance +from dds_web.api.dds_decorators import ( + logging_bind_request, + handle_validation_errors, + stop_if_maintenance, +) from dds_web.errors import ( S3ProjectNotFoundError, DatabaseError, diff --git a/dds_web/errors.py b/dds_web/errors.py index d4c73d729..30d68e842 100644 --- a/dds_web/errors.py +++ b/dds_web/errors.py @@ -26,6 +26,7 @@ extra_info = {"result": "DENIED"} + class LoggedHTTPException(exceptions.HTTPException): """Base class to enable standard action logging on HTTP Exceptions""" diff --git a/dds_web/web/root.py b/dds_web/web/root.py index 4b9dbc886..53d28cc76 100644 --- a/dds_web/web/root.py +++ b/dds_web/web/root.py @@ -19,6 +19,7 @@ pages = Blueprint("pages", __name__) + @pages.route("/", methods=["GET"]) def home(): """Home page.""" @@ -56,11 +57,13 @@ def page_not_found(e): # note that we set the 404 status explicitly return render_template("errorpages/404.html"), 404 + @app.errorhandler(sqlalchemy.exc.SQLAlchemyError) def handle_sqlalchemyerror(e): flask.current_app.logger.exception(e) - return render_template("errorpages/sqlalchemy.html"), 500 + return render_template("errorpages/sqlalchemy.html"), 500 + @app.errorhandler(503) def maintenance_ongoing(e): - return flask.render_template("errorpages/503.html"), 503 \ No newline at end of file + return flask.render_template("errorpages/503.html"), 503 From 056f8240b95b4111615f45858390cc5f27b9af4c Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 16:12:35 +0200 Subject: [PATCH 015/123] remove decorator usage --- dds_web/__init__.py | 31 +++++++++++++++++++++++---- dds_web/api/dds_decorators.py | 40 +++++++++++++++++------------------ dds_web/api/files.py | 10 --------- dds_web/api/project.py | 11 ---------- dds_web/api/s3.py | 2 -- dds_web/api/user.py | 13 ------------ 6 files changed, 47 insertions(+), 60 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 4be5137e3..b481ee4a7 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -191,14 +191,37 @@ def prepare(): """Populate flask globals for template rendering""" from dds_web.utils import verify_cli_version from dds_web.utils import get_active_motds + from dds_web.errors import MaintenanceOngoingException + + maintenance: models.Maintenance = models.Maintenance.query.first() + if maintenance.active: + approved = [ + f"/api/v1{x}" + for x in [ + "/proj/busy/any", + "/maintenance", + "/motd", + "/motd/send", + "/file/new", + "/file/update", + "/s3/proj", + "/user/info", + ] + ] + # Request not to accepted endpoint + # OR request to accepted endpoint but project not specified or busy + if flask.request.path not in approved: + raise MaintenanceOngoingException() + else: + if (req_args := flask.request.args) and (project_id := req_args.get("project")): + if not models.Project.query.filter_by( + public_id=project_id, busy=True + ).one_or_none(): + raise MaintenanceOngoingException() # Verify cli version compatible if "api/v1" in flask.request.path: verify_cli_version(version_cli=flask.request.headers.get("X-Cli-Version")) - else: - maintenance: models.Maintenance = models.Maintenance.query.first() - if maintenance.active: - flask.abort(503) # Get message of the day flask.g.motd = get_active_motds() diff --git a/dds_web/api/dds_decorators.py b/dds_web/api/dds_decorators.py index a4b4fed79..e5f03c837 100644 --- a/dds_web/api/dds_decorators.py +++ b/dds_web/api/dds_decorators.py @@ -217,29 +217,29 @@ def wrapper_logging_bind_request(*args, **kwargs): return wrapper_logging_bind_request -def stop_if_maintenance(func): - """Check if DDS is in maintenance and in that case do not accept new commands.""" +# def stop_if_maintenance(func): +# """Check if DDS is in maintenance and in that case do not accept new commands.""" - @functools.wraps(func) - def maintenance_check(*args, **kwargs): - # Super admins are always allowed in - if auth.current_user().role == "Super Admin": - return func(*args, **kwargs) +# @functools.wraps(func) +# def maintenance_check(*args, **kwargs): +# # Super admins are always allowed in +# if auth.current_user().role == "Super Admin": +# return func(*args, **kwargs) - # Get maintenance row - maintenance: models.Maintenance = models.Maintenance.query.first() +# # Get maintenance row +# maintenance: models.Maintenance = models.Maintenance.query.first() - # Super Admins always allowed in during maintenance - if maintenance.active: - # Project needs to be specified in order to allow access during maintenance - if not (req_args := flask.request.args) or not (project_id := req_args.get("project")): - raise MaintenanceOngoingException() +# # Super Admins always allowed in during maintenance +# if maintenance.active: +# # Project needs to be specified in order to allow access during maintenance +# if not (req_args := flask.request.args) or not (project_id := req_args.get("project")): +# raise MaintenanceOngoingException() - # Only busy projects are allowed access during maintenance - project: models.Project = collect_project(project_id=project_id) - if not project.busy: - raise MaintenanceOngoingException() +# # Only busy projects are allowed access during maintenance +# project: models.Project = collect_project(project_id=project_id) +# if not project.busy: +# raise MaintenanceOngoingException() - return func(*args, **kwargs) +# return func(*args, **kwargs) - return maintenance_check +# return maintenance_check diff --git a/dds_web/api/files.py b/dds_web/api/files.py index 9fb7a9750..daf81a171 100644 --- a/dds_web/api/files.py +++ b/dds_web/api/files.py @@ -25,7 +25,6 @@ logging_bind_request, json_required, handle_validation_errors, - stop_if_maintenance, ) from dds_web.errors import ( AccessDeniedError, @@ -77,7 +76,6 @@ class NewFile(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request - @stop_if_maintenance @json_required @handle_validation_errors def post(self): @@ -112,7 +110,6 @@ def post(self): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request - @stop_if_maintenance @handle_validation_errors def put(self): """Update existing file.""" @@ -207,7 +204,6 @@ class MatchFiles(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request - @stop_if_maintenance @json_required @handle_validation_errors def get(self): @@ -248,7 +244,6 @@ class ListFiles(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request - @stop_if_maintenance @handle_validation_errors def get(self): """Get a list of files within the specified folder.""" @@ -411,7 +406,6 @@ class RemoveFile(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request - @stop_if_maintenance @json_required @handle_validation_errors def delete(self): @@ -528,7 +522,6 @@ class RemoveDir(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request - @stop_if_maintenance @json_required @handle_validation_errors def delete(self): @@ -643,7 +636,6 @@ class FileInfo(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request - @stop_if_maintenance @json_required @handle_validation_errors def get(self): @@ -678,7 +670,6 @@ class FileInfoAll(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request - @stop_if_maintenance @handle_validation_errors def get(self): """Get file info on all files.""" @@ -701,7 +692,6 @@ class UpdateFile(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request - @stop_if_maintenance @json_required @handle_validation_errors def put(self): diff --git a/dds_web/api/project.py b/dds_web/api/project.py index d078f6f49..1bd40111b 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -25,7 +25,6 @@ dbsession, json_required, handle_validation_errors, - stop_if_maintenance, ) from dds_web.errors import ( AccessDeniedError, @@ -54,7 +53,6 @@ class ProjectStatus(flask_restful.Resource): @auth.login_required @logging_bind_request - @stop_if_maintenance @handle_validation_errors def get(self): """Get current project status and optionally entire status history""" @@ -81,7 +79,6 @@ def get(self): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request - @stop_if_maintenance @json_required @handle_validation_errors def post(self): @@ -404,7 +401,6 @@ class GetPublic(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request - @stop_if_maintenance @handle_validation_errors def get(self): """Get public key from database.""" @@ -426,7 +422,6 @@ class GetPrivate(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request - @stop_if_maintenance @handle_validation_errors def get(self): """Get private key from database.""" @@ -453,7 +448,6 @@ class UserProjects(flask_restful.Resource): @auth.login_required @logging_bind_request - @stop_if_maintenance def get(self): """Get info regarding all projects which user is involved in.""" return self.format_project_dict(current_user=auth.current_user()) @@ -565,7 +559,6 @@ class RemoveContents(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request - @stop_if_maintenance @dbsession @handle_validation_errors def delete(self): @@ -635,7 +628,6 @@ def delete_project_contents(project, delete_bucket=False): class CreateProject(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request - @stop_if_maintenance @json_required @handle_validation_errors def post(self): @@ -766,7 +758,6 @@ class ProjectUsers(flask_restful.Resource): @auth.login_required @logging_bind_request - @stop_if_maintenance @handle_validation_errors def get(self): # Verify project ID and access @@ -803,7 +794,6 @@ class ProjectAccess(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner"]) @logging_bind_request - @stop_if_maintenance @dbsession @json_required @handle_validation_errors @@ -910,7 +900,6 @@ def give_project_access(project_list, current_user, user): class ProjectBusy(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request - @stop_if_maintenance @dbsession @json_required def put(self): diff --git a/dds_web/api/s3.py b/dds_web/api/s3.py index 8a2cdfb4c..65e373a48 100644 --- a/dds_web/api/s3.py +++ b/dds_web/api/s3.py @@ -17,7 +17,6 @@ from dds_web.api.dds_decorators import ( logging_bind_request, handle_validation_errors, - stop_if_maintenance, ) from dds_web.errors import ( S3ProjectNotFoundError, @@ -36,7 +35,6 @@ class S3Info(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel"]) @logging_bind_request - @stop_if_maintenance @handle_validation_errors def get(self): """Get the safespring project.""" diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 8131581ec..bf6e47a68 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -32,7 +32,6 @@ json_required, handle_validation_errors, handle_db_error, - stop_if_maintenance, ) from dds_web.security.project_user_keys import ( generate_invite_key_pair, @@ -51,7 +50,6 @@ class AddUser(flask_restful.Resource): @auth.login_required(role=["Super Admin", "Unit Admin", "Unit Personnel", "Project Owner"]) @logging_bind_request - @stop_if_maintenance @json_required @handle_validation_errors def post(self): @@ -487,7 +485,6 @@ def compose_and_send_email_to_user(userobj, mail_type, link=None, project=None): class RetrieveUserInfo(flask_restful.Resource): @auth.login_required @logging_bind_request - @stop_if_maintenance def get(self): """Return own info when queried""" curr_user = auth.current_user() @@ -509,7 +506,6 @@ class DeleteUserSelf(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request - @stop_if_maintenance def delete(self): """Request deletion of own account.""" current_user = auth.current_user() @@ -630,7 +626,6 @@ class UserActivation(flask_restful.Resource): @auth.login_required(role=["Super Admin", "Unit Admin"]) @logging_bind_request - @stop_if_maintenance @json_required @handle_validation_errors def post(self): @@ -744,7 +739,6 @@ class DeleteUser(flask_restful.Resource): @auth.login_required(role=["Super Admin", "Unit Admin"]) @logging_bind_request - @stop_if_maintenance @handle_validation_errors def delete(self): """Delete user or invite in the DDS.""" @@ -870,7 +864,6 @@ def delete_invite(email): class RemoveUserAssociation(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request - @stop_if_maintenance @json_required @handle_validation_errors def post(self): @@ -951,7 +944,6 @@ class EncryptedToken(flask_restful.Resource): @basic_auth.login_required @logging_bind_request - @stop_if_maintenance def get(self): secondfactor_method = "TOTP" if auth.current_user().totp_enabled else "HOTP" return { @@ -968,7 +960,6 @@ class SecondFactor(flask_restful.Resource): """Take in and verify an authentication one-time code entered by an authenticated user with basic credentials""" @auth.login_required - @stop_if_maintenance @handle_validation_errors def get(self): @@ -983,7 +974,6 @@ class RequestTOTPActivation(flask_restful.Resource): """Request to switch from HOTP to TOTP for second factor authentication.""" @auth.login_required - @stop_if_maintenance def post(self): user = auth.current_user() if user.totp_enabled: @@ -1047,7 +1037,6 @@ class RequestHOTPActivation(flask_restful.Resource): # Using Basic auth since TOTP might have been lost, will still need access to email @basic_auth.login_required - @stop_if_maintenance def post(self): user = auth.current_user() @@ -1114,7 +1103,6 @@ class ShowUsage(flask_restful.Resource): @auth.login_required(role=["Super Admin", "Unit Admin", "Unit Personnel"]) @logging_bind_request - @stop_if_maintenance def get(self): current_user = auth.current_user() @@ -1197,7 +1185,6 @@ class Users(flask_restful.Resource): @auth.login_required(role=["Super Admin", "Unit Admin", "Unit Personnel"]) @logging_bind_request - @stop_if_maintenance @handle_db_error def get(self): """List unit users within the unit the current user is connected to, or the one defined by a superadmin.""" From c85190cf5f4387bc3488a74ca745da02c0e753a2 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 16:13:52 +0200 Subject: [PATCH 016/123] remove commented decoreator --- dds_web/api/dds_decorators.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/dds_web/api/dds_decorators.py b/dds_web/api/dds_decorators.py index e5f03c837..fc7bbe895 100644 --- a/dds_web/api/dds_decorators.py +++ b/dds_web/api/dds_decorators.py @@ -215,31 +215,3 @@ def wrapper_logging_bind_request(*args, **kwargs): raise return wrapper_logging_bind_request - - -# def stop_if_maintenance(func): -# """Check if DDS is in maintenance and in that case do not accept new commands.""" - -# @functools.wraps(func) -# def maintenance_check(*args, **kwargs): -# # Super admins are always allowed in -# if auth.current_user().role == "Super Admin": -# return func(*args, **kwargs) - -# # Get maintenance row -# maintenance: models.Maintenance = models.Maintenance.query.first() - -# # Super Admins always allowed in during maintenance -# if maintenance.active: -# # Project needs to be specified in order to allow access during maintenance -# if not (req_args := flask.request.args) or not (project_id := req_args.get("project")): -# raise MaintenanceOngoingException() - -# # Only busy projects are allowed access during maintenance -# project: models.Project = collect_project(project_id=project_id) -# if not project.busy: -# raise MaintenanceOngoingException() - -# return func(*args, **kwargs) - -# return maintenance_check From d71d9d446f734b14da9782c7d5b321a4c0405378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Wed, 28 Sep 2022 16:17:05 +0200 Subject: [PATCH 017/123] Apply suggestions from code review --- dds_web/api/dds_decorators.py | 5 +---- dds_web/api/s3.py | 5 +---- dds_web/errors.py | 2 +- dds_web/security/auth.py | 8 +------- dds_web/utils.py | 1 - 5 files changed, 4 insertions(+), 17 deletions(-) diff --git a/dds_web/api/dds_decorators.py b/dds_web/api/dds_decorators.py index fc7bbe895..57ba2bb0a 100644 --- a/dds_web/api/dds_decorators.py +++ b/dds_web/api/dds_decorators.py @@ -22,13 +22,10 @@ DatabaseError, DDSArgumentError, LoggedHTTPException, - MaintenanceOngoingException, MissingJsonError, S3ConnectionError, ) -from dds_web.utils import collect_project, get_username_or_request_ip -from dds_web.database import models -from dds_web import auth +from dds_web.utils import get_username_or_request_ip # initiate logging action_logger = structlog.getLogger("actions") diff --git a/dds_web/api/s3.py b/dds_web/api/s3.py index 65e373a48..e34100270 100644 --- a/dds_web/api/s3.py +++ b/dds_web/api/s3.py @@ -14,10 +14,7 @@ # Own modules from dds_web import auth from dds_web.api.api_s3_connector import ApiS3Connector -from dds_web.api.dds_decorators import ( - logging_bind_request, - handle_validation_errors, -) +from dds_web.api.dds_decorators import logging_bind_request, handle_validation_errors from dds_web.errors import ( S3ProjectNotFoundError, DatabaseError, diff --git a/dds_web/errors.py b/dds_web/errors.py index 30d68e842..c35697286 100644 --- a/dds_web/errors.py +++ b/dds_web/errors.py @@ -427,7 +427,7 @@ def __init__( class MaintenanceOngoingException(LoggedHTTPException): - code = http.HTTPStatus.FORBIDDEN + code = http.HTTPStatus.SERVICE_UNAVAILABLE def __init__(self, message="Maintenance of DDS is ongoing."): """Inform that maintenance is ongoing.""" diff --git a/dds_web/security/auth.py b/dds_web/security/auth.py index 585aa4ccf..579d31b10 100644 --- a/dds_web/security/auth.py +++ b/dds_web/security/auth.py @@ -18,13 +18,7 @@ # Own modules from dds_web import basic_auth, auth, mail -from dds_web.errors import ( - AuthenticationError, - AccessDeniedError, - InviteError, - MaintenanceOngoingException, - TokenMissingError, -) +from dds_web.errors import AuthenticationError, AccessDeniedError, InviteError, TokenMissingError from dds_web.database import models import dds_web.utils diff --git a/dds_web/utils.py b/dds_web/utils.py index 9703d17e0..9bdaa2b82 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -18,7 +18,6 @@ import flask from dds_web.errors import ( AccessDeniedError, - MaintenanceOngoingException, VersionMismatchError, DDSArgumentError, NoSuchProjectError, From 0962ebc53d71f02b32b4a4ca475b30e29a257bfd Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 16:31:08 +0200 Subject: [PATCH 018/123] block_if_maintenance function in utils --- dds_web/__init__.py | 31 ++----------------------------- dds_web/utils.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index b481ee4a7..a739c02f7 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -189,36 +189,9 @@ def create_app(testing=False, database_uri=None): @app.before_request def prepare(): """Populate flask globals for template rendering""" - from dds_web.utils import verify_cli_version - from dds_web.utils import get_active_motds - from dds_web.errors import MaintenanceOngoingException - - maintenance: models.Maintenance = models.Maintenance.query.first() - if maintenance.active: - approved = [ - f"/api/v1{x}" - for x in [ - "/proj/busy/any", - "/maintenance", - "/motd", - "/motd/send", - "/file/new", - "/file/update", - "/s3/proj", - "/user/info", - ] - ] - # Request not to accepted endpoint - # OR request to accepted endpoint but project not specified or busy - if flask.request.path not in approved: - raise MaintenanceOngoingException() - else: - if (req_args := flask.request.args) and (project_id := req_args.get("project")): - if not models.Project.query.filter_by( - public_id=project_id, busy=True - ).one_or_none(): - raise MaintenanceOngoingException() + from dds_web.utils import verify_cli_version, get_active_motds, block_if_maintenance + block_if_maintenance() # Verify cli version compatible if "api/v1" in flask.request.path: verify_cli_version(version_cli=flask.request.headers.get("X-Cli-Version")) diff --git a/dds_web/utils.py b/dds_web/utils.py index 9bdaa2b82..4bcf2db6a 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -21,6 +21,7 @@ VersionMismatchError, DDSArgumentError, NoSuchProjectError, + MaintenanceOngoingException ) import flask_mail import flask_login @@ -571,3 +572,34 @@ def calculate_version_period_usage(version): version.time_invoiced = now return bytehours + +# maintenance check +def block_if_maintenance(): + maintenance: models.Maintenance = models.Maintenance.query.first() + if maintenance.active: + if "api/v1" in flask.request.path: + approved = [ + f"/api/v1{x}" + for x in [ + "/proj/busy/any", + "/maintenance", + "/motd", + "/motd/send", + "/file/new", + "/file/update", + "/s3/proj", + "/user/info", + ] + ] + # Request not to accepted endpoint + # OR request to accepted endpoint but project not specified or busy + if flask.request.path not in approved: + raise MaintenanceOngoingException() + else: + if (req_args := flask.request.args) and (project_id := req_args.get("project")): + if not models.Project.query.filter_by( + public_id=project_id, busy=True + ).one_or_none(): + raise MaintenanceOngoingException() + else: + flask.abort(503) \ No newline at end of file From 352a26402b3826a234ad787d8df4c76c288c129b Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 28 Sep 2022 16:32:13 +0200 Subject: [PATCH 019/123] linting --- dds_web/utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dds_web/utils.py b/dds_web/utils.py index 4bcf2db6a..1b3cb1031 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -21,7 +21,7 @@ VersionMismatchError, DDSArgumentError, NoSuchProjectError, - MaintenanceOngoingException + MaintenanceOngoingException, ) import flask_mail import flask_login @@ -573,6 +573,7 @@ def calculate_version_period_usage(version): return bytehours + # maintenance check def block_if_maintenance(): maintenance: models.Maintenance = models.Maintenance.query.first() @@ -601,5 +602,5 @@ def block_if_maintenance(): public_id=project_id, busy=True ).one_or_none(): raise MaintenanceOngoingException() - else: - flask.abort(503) \ No newline at end of file + else: + flask.abort(503) From 2973f52b0076ad8668b9f51530e7f82a2ffa4492 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 09:31:10 +0200 Subject: [PATCH 020/123] changed if in maintenance utils func --- dds_web/utils.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/dds_web/utils.py b/dds_web/utils.py index 1b3cb1031..0b0577471 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -582,14 +582,14 @@ def block_if_maintenance(): approved = [ f"/api/v1{x}" for x in [ + "/file/new", + "/file/update", + "/proj/busy", "/proj/busy/any", + "/user/info", "/maintenance", "/motd", "/motd/send", - "/file/new", - "/file/update", - "/s3/proj", - "/user/info", ] ] # Request not to accepted endpoint @@ -597,10 +597,17 @@ def block_if_maintenance(): if flask.request.path not in approved: raise MaintenanceOngoingException() else: - if (req_args := flask.request.args) and (project_id := req_args.get("project")): - if not models.Project.query.filter_by( - public_id=project_id, busy=True - ).one_or_none(): - raise MaintenanceOngoingException() + req_args = flask.request.args + if not req_args: + raise MaintenanceOngoingException() + + project_id: str = req_args.get("project") + if not project_id: + raise MaintenanceOngoingException() + + if not models.Project.query.filter_by( + public_id=project_id, busy=True + ).one_or_none(): + raise MaintenanceOngoingException() else: flask.abort(503) From c13b6db7708602ac25d29c758d47a87dab2a18b7 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 09:33:30 +0200 Subject: [PATCH 021/123] endpoints approved for request during maintenance --- dds_web/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dds_web/utils.py b/dds_web/utils.py index 0b0577471..dac319b6a 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -585,11 +585,12 @@ def block_if_maintenance(): "/file/new", "/file/update", "/proj/busy", - "/proj/busy/any", "/user/info", "/maintenance", + "/unit/info/all", "/motd", "/motd/send", + "/proj/busy/any", ] ] # Request not to accepted endpoint From 2e466c13c3b050e41a01a7bc0013032462c3c947 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 09:42:21 +0200 Subject: [PATCH 022/123] black --- dds_web/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dds_web/utils.py b/dds_web/utils.py index dac319b6a..30db1e90e 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -601,11 +601,11 @@ def block_if_maintenance(): req_args = flask.request.args if not req_args: raise MaintenanceOngoingException() - + project_id: str = req_args.get("project") if not project_id: raise MaintenanceOngoingException() - + if not models.Project.query.filter_by( public_id=project_id, busy=True ).one_or_none(): From 3c89a1e4b578a69b12b4537ca5b05e3a8956b232 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 11:40:19 +0200 Subject: [PATCH 023/123] fixed if statements, project was required for incorrect ones --- dds_web/__init__.py | 2 +- dds_web/utils.py | 21 ++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index a739c02f7..e134ddb37 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -191,9 +191,9 @@ def prepare(): """Populate flask globals for template rendering""" from dds_web.utils import verify_cli_version, get_active_motds, block_if_maintenance - block_if_maintenance() # Verify cli version compatible if "api/v1" in flask.request.path: + block_if_maintenance() verify_cli_version(version_cli=flask.request.headers.get("X-Cli-Version")) # Get message of the day diff --git a/dds_web/utils.py b/dds_web/utils.py index 30db1e90e..2977d9869 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -599,16 +599,11 @@ def block_if_maintenance(): raise MaintenanceOngoingException() else: req_args = flask.request.args - if not req_args: - raise MaintenanceOngoingException() - - project_id: str = req_args.get("project") - if not project_id: - raise MaintenanceOngoingException() - - if not models.Project.query.filter_by( - public_id=project_id, busy=True - ).one_or_none(): - raise MaintenanceOngoingException() - else: - flask.abort(503) + if flask.request.path in ["/file/new", "/file/update", "/proj/busy"]: + if not (req_args and (project_id := req_args.get("project"))): + raise MaintenanceOngoingException() + + if not models.Project.query.filter_by( + public_id=project_id, busy=True + ).one_or_none(): + raise MaintenanceOngoingException() From 399a2fa87ce2a411b1ae2ad0f668e6271de32234 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 11:43:46 +0200 Subject: [PATCH 024/123] remove web related parts --- dds_web/templates/errorpages/404.html | 18 ------------------ dds_web/templates/errorpages/503.html | 13 ------------- dds_web/templates/errorpages/sqlalchemy.html | 13 ------------- dds_web/utils.py | 1 + dds_web/web/root.py | 11 +---------- 5 files changed, 2 insertions(+), 54 deletions(-) delete mode 100644 dds_web/templates/errorpages/404.html delete mode 100644 dds_web/templates/errorpages/503.html delete mode 100644 dds_web/templates/errorpages/sqlalchemy.html diff --git a/dds_web/templates/errorpages/404.html b/dds_web/templates/errorpages/404.html deleted file mode 100644 index f66cf9bcf..000000000 --- a/dds_web/templates/errorpages/404.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'base.html' %} - -{% block page_title -%} -404: Page not found -{%- endblock %} - -{% block body %} - - - -

Sorry, that page cannot be found: {{ request.base_url }}

- -

- You can get back to the homepage here. - If in doubt, please get in touch with the SciLifeLab Data Centre. -

- -{% endblock %} diff --git a/dds_web/templates/errorpages/503.html b/dds_web/templates/errorpages/503.html deleted file mode 100644 index b13a37c03..000000000 --- a/dds_web/templates/errorpages/503.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends 'base.html' %} - -{% block page_title -%} -503: Service Unavailable -{%- endblock %} - -{% block body %} - - - -

Maintenance of DDS is ongoing. Please try again later.

- -{% endblock %} diff --git a/dds_web/templates/errorpages/sqlalchemy.html b/dds_web/templates/errorpages/sqlalchemy.html deleted file mode 100644 index 0f2b0d1de..000000000 --- a/dds_web/templates/errorpages/sqlalchemy.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends 'base.html' %} - -{% block page_title -%} -500: Database Error -{%- endblock %} - -{% block body %} - - - -

There was an unexpected database issue. Please contact the SciLifeLab Data Centre.

- -{% endblock %} diff --git a/dds_web/utils.py b/dds_web/utils.py index 2977d9869..fe64ababe 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -576,6 +576,7 @@ def calculate_version_period_usage(version): # maintenance check def block_if_maintenance(): + """Block API requests if maintenance is ongoing and projects are busy.""" maintenance: models.Maintenance = models.Maintenance.query.first() if maintenance.active: if "api/v1" in flask.request.path: diff --git a/dds_web/web/root.py b/dds_web/web/root.py index 53d28cc76..142f7b865 100644 --- a/dds_web/web/root.py +++ b/dds_web/web/root.py @@ -14,7 +14,6 @@ import cachetools import simplejson import flask -import sqlalchemy pages = Blueprint("pages", __name__) @@ -55,15 +54,7 @@ def get_status(): @app.errorhandler(404) def page_not_found(e): # note that we set the 404 status explicitly - return render_template("errorpages/404.html"), 404 + return render_template("404.html"), 404 -@app.errorhandler(sqlalchemy.exc.SQLAlchemyError) -def handle_sqlalchemyerror(e): - flask.current_app.logger.exception(e) - return render_template("errorpages/sqlalchemy.html"), 500 - -@app.errorhandler(503) -def maintenance_ongoing(e): - return flask.render_template("errorpages/503.html"), 503 From 53950a597c54028fb7abb730c0ea2f95f3ee1ff2 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 11:44:56 +0200 Subject: [PATCH 025/123] move sqlalchemy errorhandler back to init --- dds_web/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index e134ddb37..0a90bf8b2 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -234,6 +234,11 @@ def prepare(): # Initialize marshmallows ma.init_app(app) + # Errors, TODO: Move somewhere else? + @app.errorhandler(sqlalchemy.exc.SQLAlchemyError) + def handle_sqlalchemyerror(e): + return f"SQLAlchemyError: {e}", 500 # TODO: Fix logging and a page + # Initialize login manager login_manager.init_app(app) From 5d97deef959f12dc0e45a160781945b1591c018c Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 11:45:24 +0200 Subject: [PATCH 026/123] black --- dds_web/__init__.py | 6 +++--- dds_web/utils.py | 4 ++-- dds_web/web/root.py | 3 --- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 0a90bf8b2..2e35b8ccf 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -234,9 +234,9 @@ def prepare(): # Initialize marshmallows ma.init_app(app) - # Errors, TODO: Move somewhere else? - @app.errorhandler(sqlalchemy.exc.SQLAlchemyError) - def handle_sqlalchemyerror(e): + # Errors, TODO: Move somewhere else? + @app.errorhandler(sqlalchemy.exc.SQLAlchemyError) + def handle_sqlalchemyerror(e): return f"SQLAlchemyError: {e}", 500 # TODO: Fix logging and a page # Initialize login manager diff --git a/dds_web/utils.py b/dds_web/utils.py index fe64ababe..92f2a6300 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -603,8 +603,8 @@ def block_if_maintenance(): if flask.request.path in ["/file/new", "/file/update", "/proj/busy"]: if not (req_args and (project_id := req_args.get("project"))): raise MaintenanceOngoingException() - + if not models.Project.query.filter_by( - public_id=project_id, busy=True + public_id=project_id, busy=True ).one_or_none(): raise MaintenanceOngoingException() diff --git a/dds_web/web/root.py b/dds_web/web/root.py index 142f7b865..57e644cab 100644 --- a/dds_web/web/root.py +++ b/dds_web/web/root.py @@ -55,6 +55,3 @@ def get_status(): def page_not_found(e): # note that we set the 404 status explicitly return render_template("404.html"), 404 - - - From 7a229d2b9e9fc6039067b0c8954d521bb8769144 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 11:45:55 +0200 Subject: [PATCH 027/123] moved404 --- dds_web/templates/404.html | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 dds_web/templates/404.html diff --git a/dds_web/templates/404.html b/dds_web/templates/404.html new file mode 100644 index 000000000..f66cf9bcf --- /dev/null +++ b/dds_web/templates/404.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} + +{% block page_title -%} +404: Page not found +{%- endblock %} + +{% block body %} + + + +

Sorry, that page cannot be found: {{ request.base_url }}

+ +

+ You can get back to the homepage here. + If in doubt, please get in touch with the SciLifeLab Data Centre. +

+ +{% endblock %} From e8b7ef8cdbe7a598a23c186f1240d6b9bffedc66 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 11:52:25 +0200 Subject: [PATCH 028/123] project_required_endpoints to simplify --- dds_web/utils.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/dds_web/utils.py b/dds_web/utils.py index 92f2a6300..c3e80b891 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -580,19 +580,17 @@ def block_if_maintenance(): maintenance: models.Maintenance = models.Maintenance.query.first() if maintenance.active: if "api/v1" in flask.request.path: - approved = [ + project_required_endpoints: typing.List = ["/file/new", "/file/update", "/proj/busy"] + approved: typing.List = [ f"/api/v1{x}" for x in [ - "/file/new", - "/file/update", - "/proj/busy", "/user/info", "/maintenance", "/unit/info/all", "/motd", "/motd/send", "/proj/busy/any", - ] + ].extend(project_required_endpoints) ] # Request not to accepted endpoint # OR request to accepted endpoint but project not specified or busy @@ -600,7 +598,7 @@ def block_if_maintenance(): raise MaintenanceOngoingException() else: req_args = flask.request.args - if flask.request.path in ["/file/new", "/file/update", "/proj/busy"]: + if flask.request.path in project_required_endpoints: if not (req_args and (project_id := req_args.get("project"))): raise MaintenanceOngoingException() From 19085b7136666f1b539ff5173ac99a488a8b5620 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 11:54:03 +0200 Subject: [PATCH 029/123] api added --- dds_web/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dds_web/utils.py b/dds_web/utils.py index c3e80b891..03af82a55 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -580,7 +580,9 @@ def block_if_maintenance(): maintenance: models.Maintenance = models.Maintenance.query.first() if maintenance.active: if "api/v1" in flask.request.path: - project_required_endpoints: typing.List = ["/file/new", "/file/update", "/proj/busy"] + project_required_endpoints: typing.List = [ + f"/api/v1{resource}" for resource in ["/file/new", "/file/update", "/proj/busy"] + ] approved: typing.List = [ f"/api/v1{x}" for x in [ @@ -590,8 +592,8 @@ def block_if_maintenance(): "/motd", "/motd/send", "/proj/busy/any", - ].extend(project_required_endpoints) - ] + ] + ].extend(project_required_endpoints) # Request not to accepted endpoint # OR request to accepted endpoint but project not specified or busy if flask.request.path not in approved: From 8b4bc5d21672705ea5b814dc2ba6520830a47ecd Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 12:03:46 +0200 Subject: [PATCH 030/123] change from extend --- dds_web/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/utils.py b/dds_web/utils.py index 03af82a55..0d66d83f9 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -593,7 +593,7 @@ def block_if_maintenance(): "/motd/send", "/proj/busy/any", ] - ].extend(project_required_endpoints) + ] + project_required_endpoints # Request not to accepted endpoint # OR request to accepted endpoint but project not specified or busy if flask.request.path not in approved: From d13c62eeb31436205779f9a328fcf031a234476d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Thu, 29 Sep 2022 12:56:06 +0200 Subject: [PATCH 031/123] Update trivy.yml --- .github/workflows/trivy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 30f65dbde..09abfd196 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -20,7 +20,7 @@ jobs: - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@0.7.1 with: - image-ref: "ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:dev" + image-ref: "ghcr.io/${{ env.REPOSITORY_OWNER }}/dds-backend:latest" format: "sarif" output: "trivy-results.sarif" severity: "CRITICAL,HIGH" From 5ddaa27e52099b998957a0f97010cfca35e454f6 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 14:41:22 +0200 Subject: [PATCH 032/123] tests for requests --- dds_web/utils.py | 55 ++-- tests/test_utils.py | 597 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 624 insertions(+), 28 deletions(-) diff --git a/dds_web/utils.py b/dds_web/utils.py index 0d66d83f9..1ea8d8dcb 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -579,32 +579,31 @@ def block_if_maintenance(): """Block API requests if maintenance is ongoing and projects are busy.""" maintenance: models.Maintenance = models.Maintenance.query.first() if maintenance.active: - if "api/v1" in flask.request.path: - project_required_endpoints: typing.List = [ - f"/api/v1{resource}" for resource in ["/file/new", "/file/update", "/proj/busy"] + project_required_endpoints: typing.List = [ + f"/api/v1{resource}" for resource in ["/file/new", "/file/update", "/proj/busy"] + ] + approved: typing.List = [ + f"/api/v1{x}" + for x in [ + "/user/info", + "/maintenance", + "/unit/info/all", + "/motd", + "/motd/send", + "/proj/busy/any", ] - approved: typing.List = [ - f"/api/v1{x}" - for x in [ - "/user/info", - "/maintenance", - "/unit/info/all", - "/motd", - "/motd/send", - "/proj/busy/any", - ] - ] + project_required_endpoints - # Request not to accepted endpoint - # OR request to accepted endpoint but project not specified or busy - if flask.request.path not in approved: - raise MaintenanceOngoingException() - else: - req_args = flask.request.args - if flask.request.path in project_required_endpoints: - if not (req_args and (project_id := req_args.get("project"))): - raise MaintenanceOngoingException() - - if not models.Project.query.filter_by( - public_id=project_id, busy=True - ).one_or_none(): - raise MaintenanceOngoingException() + ] + project_required_endpoints + # Request not to accepted endpoint + # OR request to accepted endpoint but project not specified or busy + if flask.request.path not in approved: + raise MaintenanceOngoingException() + else: + req_args = flask.request.args + if flask.request.path in project_required_endpoints: + if not (req_args and (project_id := req_args.get("project"))): + raise MaintenanceOngoingException() + + if not models.Project.query.filter_by( + public_id=project_id, busy=True + ).one_or_none(): + raise MaintenanceOngoingException() diff --git a/tests/test_utils.py b/tests/test_utils.py index dce4bdc3c..a301f0ce0 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -19,6 +19,9 @@ from flask.testing import FlaskClient import requests_mock import werkzeug +from tests import DDSEndpoint, DEFAULT_HEADER, UserAuth, USER_CREDENTIALS +import http +import time # Variables @@ -976,3 +979,597 @@ def test_calculate_version_period_usage_new_version(client: flask.testing.FlaskC assert existing_version.size_stored == 10000 assert bytehours < 10000.0 assert existing_version.time_invoiced + + +# block_if_maintenance + + +def test_block_if_maintenance_active_encryptedtoken_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try encrypted token - "/user/encrypted_token" + with patch.object(flask_mail.Mail, "send") as mock_mail_send: + response = client.get( + DDSEndpoint.ENCRYPTED_TOKEN, + auth=("researchuser", "password"), + headers=DEFAULT_HEADER, + ) + assert mock_mail_send.call_count == 0 + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_secondfactor_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try second factor - "/user/second_factor" + response = client.get( + DDSEndpoint.SECOND_FACTOR, + headers={"Authorization": f"Bearer made.up.token.long.version", **DEFAULT_HEADER}, + json={"TOTP": "somrthing"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_s3proj_not_approved(client: flask.testing.FlaskClient) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try s3info - "/s3/proj" + response = client.get( + DDSEndpoint.S3KEYS, + headers=DEFAULT_HEADER, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_fileslist_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try list files - "/files/list" + response = client.get( + DDSEndpoint.LIST_FILES, + headers=token, + query_string={"project": "public_project_id"}, + json={"show_size": True}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_filematch_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/file/match" + response = client.get( + DDSEndpoint.FILE_MATCH, + headers=token, + query_string={"project": "file_testing_project"}, + json=["non_existent_file"], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_removefile_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try remove file - "/file/rm" + from tests.test_files_new import FIRST_NEW_FILE + + response = client.delete( + DDSEndpoint.REMOVE_FILE, + headers=token, + query_string={"project": "file_testing_project"}, + json=[FIRST_NEW_FILE["name"]], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_removedir_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/file/rmdir" + from tests.test_files_new import FIRST_NEW_FILE + + response = client.delete( + DDSEndpoint.REMOVE_FOLDER, + headers=token, + query_string={"project": "file_testing_project"}, + json=[FIRST_NEW_FILE["subpath"]], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_fileinfo_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/file/info" + with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: + mock_url.return_value = "url" + response = client.get( + DDSEndpoint.FILE_INFO, + headers=token, + query_string={"project": "public_project_id"}, + json=["filename1"], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_fileallinfo_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/file/all/info" + with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: + mock_url.return_value = "url" + response = client.get( + DDSEndpoint.FILE_INFO_ALL, + headers=token, + query_string={"project": "public_project_id"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectlist_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/list" + response = client.get( + DDSEndpoint.LIST_PROJ, + headers=token, + json={"usage": True}, + content_type="application/json", + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_removeprojectcontents_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/rm" + response = client.delete( + DDSEndpoint.REMOVE_PROJ_CONT, + headers=token, + query_string={"project": "file_testing_project"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectpublic_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/public" + response = client.get( + DDSEndpoint.PROJ_PUBLIC, query_string={"project": "public_project_id"}, headers=token + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectprivate_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/private" + response = client.get( + DDSEndpoint.PROJ_PRIVATE, + query_string={"project": "public_project_id"}, + headers=token, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_createproject_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/create" + from tests.test_project_creation import proj_data + + response = client.post( + DDSEndpoint.PROJECT_CREATE, + headers=token, + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectusers_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/users" + response = client.get( + DDSEndpoint.LIST_PROJ_USERS, query_string={"project": "public_project_id"}, headers=token + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectstatus_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/status" + response = client.post( + DDSEndpoint.PROJECT_STATUS, + headers=token, + query_string={"project": "public_project_id"}, + json={"new_status": "Available"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectaccess_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/access" + response = client.post( + DDSEndpoint.PROJECT_ACCESS, + headers=token, + query_string={"project": "public_project_id"}, + json={"email": "unituser1@mailtrap.io"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_adduser_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/add" + from tests.api.test_user import first_new_user + + response = client.post( + DDSEndpoint.USER_ADD, + headers=token, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_deleteuser_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/delete" + invited_user_row = models.Invite.query.first() + response = client.delete( + DDSEndpoint.USER_DELETE, + headers=token, + json={"email": invited_user_row.email, "is_invite": True}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_deleteself_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["delete_me_researcher"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/delete_self" + with patch.object(flask_mail.Mail, "send") as mock_mail_send: + response = client.delete( + DDSEndpoint.USER_DELETE_SELF, + headers=token, + json=None, + ) + # One email for partial token but no new for deletion confirmation + assert mock_mail_send.call_count == 0 + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_revokeaccess_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/access/revoke" + from tests.test_project_creation import proj_data_with_existing_users + + email = proj_data_with_existing_users["users_to_add"][0]["email"] + response = client.post( + DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=token, + query_string={"project": "public_project_id"}, + json={"email": email}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_useractivation_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/activation" + from tests.test_user_activation import unituser + + response = client.post( + DDSEndpoint.USER_ACTIVATION, + headers=token, + json={**unituser, "action": "reactivate"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_hotp_not_approved(client: flask.testing.FlaskClient) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["researcher"]).as_tuple() + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/hotp/activate" + response = client.post( + DDSEndpoint.HOTP_ACTIVATION, + headers=DEFAULT_HEADER, + auth=token, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_totp_not_approved(client: flask.testing.FlaskClient) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/totp/activate" + response = client.post( + DDSEndpoint.TOTP_ACTIVATION, + headers=token, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_listusers_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/users" + response = client.get(DDSEndpoint.LIST_USERS, headers=token) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_finduser_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/find" + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.USER_FIND, headers=token, json={"username": models.User.query.first().username} + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_deactivatetotp_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/totp/deactivate" + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.TOTP_DEACTIVATE, + headers=token, + json={"username": models.User.query.first().username}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_usage_not_approved(client: flask.testing.FlaskClient) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/usage" + response = client.get( + DDSEndpoint.USAGE, + headers=token, + content_type="application/json", + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE From 5c71abf7254879d0c215bc1efb6cf05e1a17cf04 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 14:48:33 +0200 Subject: [PATCH 033/123] moved tests to init --- tests/test_init.py | 599 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_utils.py | 594 ------------------------------------------- 2 files changed, 599 insertions(+), 594 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index e576d21c0..ddb7e9b40 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -7,6 +7,11 @@ import typing from pyfakefs.fake_filesystem import FakeFilesystem import os +from tests import DDSEndpoint, DEFAULT_HEADER, UserAuth, USER_CREDENTIALS +import http +import werkzeug +import flask +import flask_mail @pytest.fixture @@ -218,3 +223,597 @@ def test_update_uploaded_file_with_log_nonexisting_file(client, runner, fs: Fake # Run command result: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) assert result.exit_code == 1 + + +# block_if_maintenance + + +def test_block_if_maintenance_active_encryptedtoken_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try encrypted token - "/user/encrypted_token" + with patch.object(flask_mail.Mail, "send") as mock_mail_send: + response = client.get( + DDSEndpoint.ENCRYPTED_TOKEN, + auth=("researchuser", "password"), + headers=DEFAULT_HEADER, + ) + assert mock_mail_send.call_count == 0 + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_secondfactor_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try second factor - "/user/second_factor" + response = client.get( + DDSEndpoint.SECOND_FACTOR, + headers={"Authorization": f"Bearer made.up.token.long.version", **DEFAULT_HEADER}, + json={"TOTP": "somrthing"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_s3proj_not_approved(client: flask.testing.FlaskClient) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try s3info - "/s3/proj" + response = client.get( + DDSEndpoint.S3KEYS, + headers=DEFAULT_HEADER, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_fileslist_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try list files - "/files/list" + response = client.get( + DDSEndpoint.LIST_FILES, + headers=token, + query_string={"project": "public_project_id"}, + json={"show_size": True}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_filematch_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/file/match" + response = client.get( + DDSEndpoint.FILE_MATCH, + headers=token, + query_string={"project": "file_testing_project"}, + json=["non_existent_file"], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_removefile_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try remove file - "/file/rm" + from tests.test_files_new import FIRST_NEW_FILE + + response = client.delete( + DDSEndpoint.REMOVE_FILE, + headers=token, + query_string={"project": "file_testing_project"}, + json=[FIRST_NEW_FILE["name"]], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_removedir_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/file/rmdir" + from tests.test_files_new import FIRST_NEW_FILE + + response = client.delete( + DDSEndpoint.REMOVE_FOLDER, + headers=token, + query_string={"project": "file_testing_project"}, + json=[FIRST_NEW_FILE["subpath"]], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_fileinfo_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/file/info" + with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: + mock_url.return_value = "url" + response = client.get( + DDSEndpoint.FILE_INFO, + headers=token, + query_string={"project": "public_project_id"}, + json=["filename1"], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_fileallinfo_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/file/all/info" + with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: + mock_url.return_value = "url" + response = client.get( + DDSEndpoint.FILE_INFO_ALL, + headers=token, + query_string={"project": "public_project_id"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectlist_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/list" + response = client.get( + DDSEndpoint.LIST_PROJ, + headers=token, + json={"usage": True}, + content_type="application/json", + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_removeprojectcontents_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/rm" + response = client.delete( + DDSEndpoint.REMOVE_PROJ_CONT, + headers=token, + query_string={"project": "file_testing_project"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectpublic_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/public" + response = client.get( + DDSEndpoint.PROJ_PUBLIC, query_string={"project": "public_project_id"}, headers=token + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectprivate_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/private" + response = client.get( + DDSEndpoint.PROJ_PRIVATE, + query_string={"project": "public_project_id"}, + headers=token, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_createproject_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/create" + from tests.test_project_creation import proj_data + + response = client.post( + DDSEndpoint.PROJECT_CREATE, + headers=token, + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectusers_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/users" + response = client.get( + DDSEndpoint.LIST_PROJ_USERS, query_string={"project": "public_project_id"}, headers=token + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectstatus_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/status" + response = client.post( + DDSEndpoint.PROJECT_STATUS, + headers=token, + query_string={"project": "public_project_id"}, + json={"new_status": "Available"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectaccess_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/access" + response = client.post( + DDSEndpoint.PROJECT_ACCESS, + headers=token, + query_string={"project": "public_project_id"}, + json={"email": "unituser1@mailtrap.io"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_adduser_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/add" + from tests.api.test_user import first_new_user + + response = client.post( + DDSEndpoint.USER_ADD, + headers=token, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_deleteuser_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/delete" + invited_user_row = models.Invite.query.first() + response = client.delete( + DDSEndpoint.USER_DELETE, + headers=token, + json={"email": invited_user_row.email, "is_invite": True}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_deleteself_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["delete_me_researcher"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/delete_self" + with patch.object(flask_mail.Mail, "send") as mock_mail_send: + response = client.delete( + DDSEndpoint.USER_DELETE_SELF, + headers=token, + json=None, + ) + # One email for partial token but no new for deletion confirmation + assert mock_mail_send.call_count == 0 + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_revokeaccess_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/access/revoke" + from tests.test_project_creation import proj_data_with_existing_users + + email = proj_data_with_existing_users["users_to_add"][0]["email"] + response = client.post( + DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=token, + query_string={"project": "public_project_id"}, + json={"email": email}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_useractivation_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/activation" + from tests.test_user_activation import unituser + + response = client.post( + DDSEndpoint.USER_ACTIVATION, + headers=token, + json={**unituser, "action": "reactivate"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_hotp_not_approved(client: flask.testing.FlaskClient) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["researcher"]).as_tuple() + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/hotp/activate" + response = client.post( + DDSEndpoint.HOTP_ACTIVATION, + headers=DEFAULT_HEADER, + auth=token, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_totp_not_approved(client: flask.testing.FlaskClient) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/totp/activate" + response = client.post( + DDSEndpoint.TOTP_ACTIVATION, + headers=token, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_listusers_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/users" + response = client.get(DDSEndpoint.LIST_USERS, headers=token) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_finduser_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/find" + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.USER_FIND, headers=token, json={"username": models.User.query.first().username} + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_deactivatetotp_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/totp/deactivate" + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.TOTP_DEACTIVATE, + headers=token, + json={"username": models.User.query.first().username}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_usage_not_approved(client: flask.testing.FlaskClient) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/usage" + response = client.get( + DDSEndpoint.USAGE, + headers=token, + content_type="application/json", + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE diff --git a/tests/test_utils.py b/tests/test_utils.py index a301f0ce0..48cde874d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -979,597 +979,3 @@ def test_calculate_version_period_usage_new_version(client: flask.testing.FlaskC assert existing_version.size_stored == 10000 assert bytehours < 10000.0 assert existing_version.time_invoiced - - -# block_if_maintenance - - -def test_block_if_maintenance_active_encryptedtoken_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # Try encrypted token - "/user/encrypted_token" - with patch.object(flask_mail.Mail, "send") as mock_mail_send: - response = client.get( - DDSEndpoint.ENCRYPTED_TOKEN, - auth=("researchuser", "password"), - headers=DEFAULT_HEADER, - ) - assert mock_mail_send.call_count == 0 - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_secondfactor_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # Try second factor - "/user/second_factor" - response = client.get( - DDSEndpoint.SECOND_FACTOR, - headers={"Authorization": f"Bearer made.up.token.long.version", **DEFAULT_HEADER}, - json={"TOTP": "somrthing"}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_s3proj_not_approved(client: flask.testing.FlaskClient) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # Try s3info - "/s3/proj" - response = client.get( - DDSEndpoint.S3KEYS, - headers=DEFAULT_HEADER, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_fileslist_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # Try list files - "/files/list" - response = client.get( - DDSEndpoint.LIST_FILES, - headers=token, - query_string={"project": "public_project_id"}, - json={"show_size": True}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_filematch_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/file/match" - response = client.get( - DDSEndpoint.FILE_MATCH, - headers=token, - query_string={"project": "file_testing_project"}, - json=["non_existent_file"], - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_removefile_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # Try remove file - "/file/rm" - from tests.test_files_new import FIRST_NEW_FILE - - response = client.delete( - DDSEndpoint.REMOVE_FILE, - headers=token, - query_string={"project": "file_testing_project"}, - json=[FIRST_NEW_FILE["name"]], - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_removedir_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/file/rmdir" - from tests.test_files_new import FIRST_NEW_FILE - - response = client.delete( - DDSEndpoint.REMOVE_FOLDER, - headers=token, - query_string={"project": "file_testing_project"}, - json=[FIRST_NEW_FILE["subpath"]], - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_fileinfo_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/file/info" - with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: - mock_url.return_value = "url" - response = client.get( - DDSEndpoint.FILE_INFO, - headers=token, - query_string={"project": "public_project_id"}, - json=["filename1"], - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_fileallinfo_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/file/all/info" - with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: - mock_url.return_value = "url" - response = client.get( - DDSEndpoint.FILE_INFO_ALL, - headers=token, - query_string={"project": "public_project_id"}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_projectlist_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/list" - response = client.get( - DDSEndpoint.LIST_PROJ, - headers=token, - json={"usage": True}, - content_type="application/json", - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_removeprojectcontents_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/rm" - response = client.delete( - DDSEndpoint.REMOVE_PROJ_CONT, - headers=token, - query_string={"project": "file_testing_project"}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_projectpublic_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/public" - response = client.get( - DDSEndpoint.PROJ_PUBLIC, query_string={"project": "public_project_id"}, headers=token - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_projectprivate_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/private" - response = client.get( - DDSEndpoint.PROJ_PRIVATE, - query_string={"project": "public_project_id"}, - headers=token, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_createproject_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/create" - from tests.test_project_creation import proj_data - - response = client.post( - DDSEndpoint.PROJECT_CREATE, - headers=token, - json=proj_data, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_projectusers_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/users" - response = client.get( - DDSEndpoint.LIST_PROJ_USERS, query_string={"project": "public_project_id"}, headers=token - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_projectstatus_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/status" - response = client.post( - DDSEndpoint.PROJECT_STATUS, - headers=token, - query_string={"project": "public_project_id"}, - json={"new_status": "Available"}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_projectaccess_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/access" - response = client.post( - DDSEndpoint.PROJECT_ACCESS, - headers=token, - query_string={"project": "public_project_id"}, - json={"email": "unituser1@mailtrap.io"}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_adduser_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/user/add" - from tests.api.test_user import first_new_user - - response = client.post( - DDSEndpoint.USER_ADD, - headers=token, - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_deleteuser_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/user/delete" - invited_user_row = models.Invite.query.first() - response = client.delete( - DDSEndpoint.USER_DELETE, - headers=token, - json={"email": invited_user_row.email, "is_invite": True}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_deleteself_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["delete_me_researcher"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/user/delete_self" - with patch.object(flask_mail.Mail, "send") as mock_mail_send: - response = client.delete( - DDSEndpoint.USER_DELETE_SELF, - headers=token, - json=None, - ) - # One email for partial token but no new for deletion confirmation - assert mock_mail_send.call_count == 0 - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_revokeaccess_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/user/access/revoke" - from tests.test_project_creation import proj_data_with_existing_users - - email = proj_data_with_existing_users["users_to_add"][0]["email"] - response = client.post( - DDSEndpoint.REMOVE_USER_FROM_PROJ, - headers=token, - query_string={"project": "public_project_id"}, - json={"email": email}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_useractivation_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/user/activation" - from tests.test_user_activation import unituser - - response = client.post( - DDSEndpoint.USER_ACTIVATION, - headers=token, - json={**unituser, "action": "reactivate"}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_hotp_not_approved(client: flask.testing.FlaskClient) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["researcher"]).as_tuple() - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/user/hotp/activate" - response = client.post( - DDSEndpoint.HOTP_ACTIVATION, - headers=DEFAULT_HEADER, - auth=token, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_totp_not_approved(client: flask.testing.FlaskClient) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/user/totp/activate" - response = client.post( - DDSEndpoint.TOTP_ACTIVATION, - headers=token, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_listusers_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/users" - response = client.get(DDSEndpoint.LIST_USERS, headers=token) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_finduser_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/user/find" - response: werkzeug.test.WrapperTestResponse = client.get( - DDSEndpoint.USER_FIND, headers=token, json={"username": models.User.query.first().username} - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_deactivatetotp_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/user/totp/deactivate" - response: werkzeug.test.WrapperTestResponse = client.put( - DDSEndpoint.TOTP_DEACTIVATE, - headers=token, - json={"username": models.User.query.first().username}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_usage_not_approved(client: flask.testing.FlaskClient) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/usage" - response = client.get( - DDSEndpoint.USAGE, - headers=token, - content_type="application/json", - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE From 461312d1829bd279946c75fe365b72991d79382e Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 29 Sep 2022 14:49:16 +0200 Subject: [PATCH 034/123] add comment --- tests/test_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_init.py b/tests/test_init.py index ddb7e9b40..e12e334c2 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -225,7 +225,7 @@ def test_update_uploaded_file_with_log_nonexisting_file(client, runner, fs: Fake assert result.exit_code == 1 -# block_if_maintenance +# block_if_maintenance - should be blocked in init by before_request def test_block_if_maintenance_active_encryptedtoken_not_approved( From db4959ce7b4e79c6840dc75f9a0c991e5a6b33e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Thu, 29 Sep 2022 14:52:39 +0200 Subject: [PATCH 035/123] Update tests/test_utils.py --- tests/test_utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 48cde874d..dce4bdc3c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -19,9 +19,6 @@ from flask.testing import FlaskClient import requests_mock import werkzeug -from tests import DDSEndpoint, DEFAULT_HEADER, UserAuth, USER_CREDENTIALS -import http -import time # Variables From 5ded4a43d95c9d01edc02d32483b6397ca64ef4e Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 30 Sep 2022 13:28:57 +0200 Subject: [PATCH 036/123] test for full data put when maintenance not active --- tests/test_init.py | 1640 ++++++++++++++++++++++++-------------------- 1 file changed, 887 insertions(+), 753 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index e12e334c2..86084edb6 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -26,794 +26,928 @@ def mock_commit(): # fill_db_wrapper -def test_fill_db_wrapper_production(client, runner) -> None: - """Run init-db with the production argument.""" - result: click.testing.Result = runner.invoke(fill_db_wrapper, ["production"]) - assert result.exit_code == 1 - - -def test_fill_db_wrapper_devsmall(client, runner) -> None: - """Run init-db with the dev-small argument.""" - result: click.testing.Result = runner.invoke(fill_db_wrapper, ["dev-small"]) - assert result.exit_code == 1 - - -# def test_fill_db_wrapper_devbig(client, runner) -> None: -# """Run init-db with the dev-big argument.""" -# result: click.testing.Result = runner.invoke(fill_db_wrapper, ["dev-big"]) +# def test_fill_db_wrapper_production(client, runner) -> None: +# """Run init-db with the production argument.""" +# result: click.testing.Result = runner.invoke(fill_db_wrapper, ["production"]) # assert result.exit_code == 1 -# create_new_unit - - -def create_command_options_from_dict(options: typing.Dict) -> typing.List: - """Create a list with options and values from a dict.""" - # Create command options - command_options: typing.List = [] - for key, val in options.items(): - command_options.append(f"--{key}") - command_options.append(val) - - return command_options - - -correct_unit: typing.Dict = { - "name": "newname", - "public_id": "newpublicid", - "external_display_name": "newexternaldisplay", - "contact_email": "newcontact@mail.com", - "internal_ref": "newinternalref", - "safespring_endpoint": "newsafespringendpoint", - "safespring_name": "newsafespringname", - "safespring_access": "newsafespringaccess", - "safespring_secret": "newsafespringsecret", - "days_in_available": 45, - "days_in_expired": 15, -} - - -def test_create_new_unit_public_id_too_long(client, runner) -> None: - """Create new unit, public_id too long.""" - # Change public_id - incorrect_unit: typing.Dict = correct_unit.copy() - incorrect_unit["public_id"] = "public" * 10 - - # Get command options - command_options = create_command_options_from_dict(options=incorrect_unit) - - # Run command - result: click.testing.Result = runner.invoke(create_new_unit, command_options) - # assert "The 'public_id' can be a maximum of 50 characters" in result.output - assert ( - not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() - ) - - -def test_create_new_unit_public_id_incorrect_characters(client, runner) -> None: - """Create new unit, public_id has invalid characters (here _).""" - # Change public_id - incorrect_unit: typing.Dict = correct_unit.copy() - incorrect_unit["public_id"] = "new_public_id" - - # Get command options - command_options = create_command_options_from_dict(options=incorrect_unit) - - # Run command - result: click.testing.Result = runner.invoke(create_new_unit, command_options) - # assert "The 'public_id' can only contain letters, numbers, dots (.) and hyphens (-)." in result.output - assert ( - not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() - ) - - -def test_create_new_unit_public_id_starts_with_dot(client, runner) -> None: - """Create new unit, public_id starts with invalid character (. or -).""" - # Change public_id - incorrect_unit: typing.Dict = correct_unit.copy() - incorrect_unit["public_id"] = ".newpublicid" - - # Get command options - command_options = create_command_options_from_dict(options=incorrect_unit) - - # Run command - result: click.testing.Result = runner.invoke(create_new_unit, command_options) - # assert "The 'public_id' must begin with a letter or number." in result.output - assert ( - not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() - ) - - # Change public_id again - incorrect_unit["public_id"] = "-newpublicid" - - # Get command options - command_options = create_command_options_from_dict(options=incorrect_unit) - - # Run command - result: click.testing.Result = runner.invoke(create_new_unit, command_options) - # assert "The 'public_id' must begin with a letter or number." in result.output - assert ( - not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() - ) - - -def test_create_new_unit_public_id_too_many_dots(client, runner) -> None: - """Create new unit, public_id has invalid number of dots.""" - # Change public_id - incorrect_unit: typing.Dict = correct_unit.copy() - incorrect_unit["public_id"] = "new.public..id" - - # Get command options - command_options = create_command_options_from_dict(options=incorrect_unit) - - # Run command - result: click.testing.Result = runner.invoke(create_new_unit, command_options) - # assert "The 'public_id' should not contain more than two dots." in result.output - assert ( - not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() - ) - - -def test_create_new_unit_public_id_invalid_start(client, runner) -> None: - """Create new unit, public_id starts with prefix.""" - # Change public_id - incorrect_unit: typing.Dict = correct_unit.copy() - incorrect_unit["public_id"] = "xn--newpublicid" - - # Get command options - command_options = create_command_options_from_dict(options=incorrect_unit) - - # Run command - result: click.testing.Result = runner.invoke(create_new_unit, command_options) - # assert "The 'public_id' cannot begin with the 'xn--' prefix." in result.output - assert ( - not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() - ) - - -def test_create_new_unit_success(client, runner) -> None: - """Create new unit, public_id starts with prefix.""" - # Get command options - command_options = create_command_options_from_dict(options=correct_unit) - - with patch("dds_web.db.session.commit", mock_commit): - # Run command - result: click.testing.Result = runner.invoke(create_new_unit, command_options) - # assert f"Unit '{correct_unit['name']}' created" in result.output - - -# Update uploaded file with log - - -def mock_no_project(): - return None - - -def test_update_uploaded_file_with_log_nonexisting_project(client, runner) -> None: - """Add file info to non existing project.""" - # Create command options - command_options: typing.List = [ - "--project", - "projectdoesntexist", - "--path-to-log-file", - "somefile", - ] - - # Run command - assert db.session.query(models.Project).all() - with patch("dds_web.database.models.Project.query.filter_by", mock_no_project): - result: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) - assert result.exit_code == 1 - - -def test_update_uploaded_file_with_log_nonexisting_file(client, runner, fs: FakeFilesystem) -> None: - """Attempt to read file which does not exist.""" - # Verify that fake file does not exist - non_existent_log_file: str = "this_is_not_a_file.json" - assert not os.path.exists(non_existent_log_file) - - # Create command options - command_options: typing.List = [ - "--project", - "projectdoesntexist", - "--path-to-log-file", - non_existent_log_file, - ] - - # Run command - result: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) - assert result.exit_code == 1 - - -# block_if_maintenance - should be blocked in init by before_request - - -def test_block_if_maintenance_active_encryptedtoken_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # Try encrypted token - "/user/encrypted_token" - with patch.object(flask_mail.Mail, "send") as mock_mail_send: - response = client.get( - DDSEndpoint.ENCRYPTED_TOKEN, - auth=("researchuser", "password"), - headers=DEFAULT_HEADER, - ) - assert mock_mail_send.call_count == 0 - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_secondfactor_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # Try second factor - "/user/second_factor" - response = client.get( - DDSEndpoint.SECOND_FACTOR, - headers={"Authorization": f"Bearer made.up.token.long.version", **DEFAULT_HEADER}, - json={"TOTP": "somrthing"}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_s3proj_not_approved(client: flask.testing.FlaskClient) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # Try s3info - "/s3/proj" - response = client.get( - DDSEndpoint.S3KEYS, - headers=DEFAULT_HEADER, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_fileslist_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # Try list files - "/files/list" - response = client.get( - DDSEndpoint.LIST_FILES, - headers=token, - query_string={"project": "public_project_id"}, - json={"show_size": True}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_filematch_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/file/match" - response = client.get( - DDSEndpoint.FILE_MATCH, - headers=token, - query_string={"project": "file_testing_project"}, - json=["non_existent_file"], - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_removefile_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # Try remove file - "/file/rm" - from tests.test_files_new import FIRST_NEW_FILE - - response = client.delete( - DDSEndpoint.REMOVE_FILE, - headers=token, - query_string={"project": "file_testing_project"}, - json=[FIRST_NEW_FILE["name"]], - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_removedir_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/file/rmdir" - from tests.test_files_new import FIRST_NEW_FILE - - response = client.delete( - DDSEndpoint.REMOVE_FOLDER, - headers=token, - query_string={"project": "file_testing_project"}, - json=[FIRST_NEW_FILE["subpath"]], - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_fileinfo_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/file/info" - with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: - mock_url.return_value = "url" - response = client.get( - DDSEndpoint.FILE_INFO, - headers=token, - query_string={"project": "public_project_id"}, - json=["filename1"], - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_fileallinfo_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/file/all/info" - with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: - mock_url.return_value = "url" - response = client.get( - DDSEndpoint.FILE_INFO_ALL, - headers=token, - query_string={"project": "public_project_id"}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_projectlist_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/list" - response = client.get( - DDSEndpoint.LIST_PROJ, - headers=token, - json={"usage": True}, - content_type="application/json", - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_removeprojectcontents_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/rm" - response = client.delete( - DDSEndpoint.REMOVE_PROJ_CONT, - headers=token, - query_string={"project": "file_testing_project"}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_projectpublic_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/public" - response = client.get( - DDSEndpoint.PROJ_PUBLIC, query_string={"project": "public_project_id"}, headers=token - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_projectprivate_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/private" - response = client.get( - DDSEndpoint.PROJ_PRIVATE, - query_string={"project": "public_project_id"}, - headers=token, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_createproject_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/create" - from tests.test_project_creation import proj_data - - response = client.post( - DDSEndpoint.PROJECT_CREATE, - headers=token, - json=proj_data, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_projectusers_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/users" - response = client.get( - DDSEndpoint.LIST_PROJ_USERS, query_string={"project": "public_project_id"}, headers=token - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_projectstatus_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - - # "/proj/status" - response = client.post( - DDSEndpoint.PROJECT_STATUS, - headers=token, - query_string={"project": "public_project_id"}, - json={"new_status": "Available"}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_projectaccess_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() +# def test_fill_db_wrapper_devsmall(client, runner) -> None: +# """Run init-db with the dev-small argument.""" +# result: click.testing.Result = runner.invoke(fill_db_wrapper, ["dev-small"]) +# assert result.exit_code == 1 - # "/proj/access" - response = client.post( - DDSEndpoint.PROJECT_ACCESS, - headers=token, - query_string={"project": "public_project_id"}, - json={"email": "unituser1@mailtrap.io"}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_adduser_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - # "/user/add" - from tests.api.test_user import first_new_user +# # def test_fill_db_wrapper_devbig(client, runner) -> None: +# # """Run init-db with the dev-big argument.""" +# # result: click.testing.Result = runner.invoke(fill_db_wrapper, ["dev-big"]) +# # assert result.exit_code == 1 - response = client.post( - DDSEndpoint.USER_ADD, - headers=token, - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE +# # create_new_unit -def test_block_if_maintenance_active_deleteuser_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() +# def create_command_options_from_dict(options: typing.Dict) -> typing.List: +# """Create a list with options and values from a dict.""" +# # Create command options +# command_options: typing.List = [] +# for key, val in options.items(): +# command_options.append(f"--{key}") +# command_options.append(val) - # "/user/delete" - invited_user_row = models.Invite.query.first() - response = client.delete( - DDSEndpoint.USER_DELETE, - headers=token, - json={"email": invited_user_row.email, "is_invite": True}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_deleteself_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["delete_me_researcher"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() +# return command_options - # "/user/delete_self" - with patch.object(flask_mail.Mail, "send") as mock_mail_send: - response = client.delete( - DDSEndpoint.USER_DELETE_SELF, - headers=token, - json=None, - ) - # One email for partial token but no new for deletion confirmation - assert mock_mail_send.call_count == 0 - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_revokeaccess_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - # "/user/access/revoke" - from tests.test_project_creation import proj_data_with_existing_users - - email = proj_data_with_existing_users["users_to_add"][0]["email"] - response = client.post( - DDSEndpoint.REMOVE_USER_FROM_PROJ, - headers=token, - query_string={"project": "public_project_id"}, - json={"email": email}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -def test_block_if_maintenance_active_useractivation_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() +# correct_unit: typing.Dict = { +# "name": "newname", +# "public_id": "newpublicid", +# "external_display_name": "newexternaldisplay", +# "contact_email": "newcontact@mail.com", +# "internal_ref": "newinternalref", +# "safespring_endpoint": "newsafespringendpoint", +# "safespring_name": "newsafespringname", +# "safespring_access": "newsafespringaccess", +# "safespring_secret": "newsafespringsecret", +# "days_in_available": 45, +# "days_in_expired": 15, +# } - # "/user/activation" - from tests.test_user_activation import unituser - response = client.post( - DDSEndpoint.USER_ACTIVATION, - headers=token, - json={**unituser, "action": "reactivate"}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE +# def test_create_new_unit_public_id_too_long(client, runner) -> None: +# """Create new unit, public_id too long.""" +# # Change public_id +# incorrect_unit: typing.Dict = correct_unit.copy() +# incorrect_unit["public_id"] = "public" * 10 +# # Get command options +# command_options = create_command_options_from_dict(options=incorrect_unit) -def test_block_if_maintenance_active_hotp_not_approved(client: flask.testing.FlaskClient) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["researcher"]).as_tuple() +# # Run command +# result: click.testing.Result = runner.invoke(create_new_unit, command_options) +# # assert "The 'public_id' can be a maximum of 50 characters" in result.output +# assert ( +# not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() +# ) - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - # "/user/hotp/activate" - response = client.post( - DDSEndpoint.HOTP_ACTIVATION, - headers=DEFAULT_HEADER, - auth=token, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE +# def test_create_new_unit_public_id_incorrect_characters(client, runner) -> None: +# """Create new unit, public_id has invalid characters (here _).""" +# # Change public_id +# incorrect_unit: typing.Dict = correct_unit.copy() +# incorrect_unit["public_id"] = "new_public_id" +# # Get command options +# command_options = create_command_options_from_dict(options=incorrect_unit) -def test_block_if_maintenance_active_totp_not_approved(client: flask.testing.FlaskClient) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) +# # Run command +# result: click.testing.Result = runner.invoke(create_new_unit, command_options) +# # assert "The 'public_id' can only contain letters, numbers, dots (.) and hyphens (-)." in result.output +# assert ( +# not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() +# ) - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() - # "/user/totp/activate" - response = client.post( - DDSEndpoint.TOTP_ACTIVATION, - headers=token, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE +# def test_create_new_unit_public_id_starts_with_dot(client, runner) -> None: +# """Create new unit, public_id starts with invalid character (. or -).""" +# # Change public_id +# incorrect_unit: typing.Dict = correct_unit.copy() +# incorrect_unit["public_id"] = ".newpublicid" +# # Get command options +# command_options = create_command_options_from_dict(options=incorrect_unit) -def test_block_if_maintenance_active_listusers_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) +# # Run command +# result: click.testing.Result = runner.invoke(create_new_unit, command_options) +# # assert "The 'public_id' must begin with a letter or number." in result.output +# assert ( +# not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() +# ) - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() +# # Change public_id again +# incorrect_unit["public_id"] = "-newpublicid" - # "/users" - response = client.get(DDSEndpoint.LIST_USERS, headers=token) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE +# # Get command options +# command_options = create_command_options_from_dict(options=incorrect_unit) +# # Run command +# result: click.testing.Result = runner.invoke(create_new_unit, command_options) +# # assert "The 'public_id' must begin with a letter or number." in result.output +# assert ( +# not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() +# ) -def test_block_if_maintenance_active_finduser_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() +# def test_create_new_unit_public_id_too_many_dots(client, runner) -> None: +# """Create new unit, public_id has invalid number of dots.""" +# # Change public_id +# incorrect_unit: typing.Dict = correct_unit.copy() +# incorrect_unit["public_id"] = "new.public..id" + +# # Get command options +# command_options = create_command_options_from_dict(options=incorrect_unit) + +# # Run command +# result: click.testing.Result = runner.invoke(create_new_unit, command_options) +# # assert "The 'public_id' should not contain more than two dots." in result.output +# assert ( +# not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() +# ) + + +# def test_create_new_unit_public_id_invalid_start(client, runner) -> None: +# """Create new unit, public_id starts with prefix.""" +# # Change public_id +# incorrect_unit: typing.Dict = correct_unit.copy() +# incorrect_unit["public_id"] = "xn--newpublicid" + +# # Get command options +# command_options = create_command_options_from_dict(options=incorrect_unit) + +# # Run command +# result: click.testing.Result = runner.invoke(create_new_unit, command_options) +# # assert "The 'public_id' cannot begin with the 'xn--' prefix." in result.output +# assert ( +# not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() +# ) + + +# def test_create_new_unit_success(client, runner) -> None: +# """Create new unit, public_id starts with prefix.""" +# # Get command options +# command_options = create_command_options_from_dict(options=correct_unit) + +# with patch("dds_web.db.session.commit", mock_commit): +# # Run command +# result: click.testing.Result = runner.invoke(create_new_unit, command_options) +# # assert f"Unit '{correct_unit['name']}' created" in result.output + + +# # Update uploaded file with log + + +# def mock_no_project(): +# return None - # "/user/find" - response: werkzeug.test.WrapperTestResponse = client.get( - DDSEndpoint.USER_FIND, headers=token, json={"username": models.User.query.first().username} - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE +# def test_update_uploaded_file_with_log_nonexisting_project(client, runner) -> None: +# """Add file info to non existing project.""" +# # Create command options +# command_options: typing.List = [ +# "--project", +# "projectdoesntexist", +# "--path-to-log-file", +# "somefile", +# ] + +# # Run command +# assert db.session.query(models.Project).all() +# with patch("dds_web.database.models.Project.query.filter_by", mock_no_project): +# result: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) +# assert result.exit_code == 1 -def test_block_if_maintenance_active_deactivatetotp_not_approved( - client: flask.testing.FlaskClient, -) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True - db.session.commit() +# def test_update_uploaded_file_with_log_nonexisting_file(client, runner, fs: FakeFilesystem) -> None: +# """Attempt to read file which does not exist.""" +# # Verify that fake file does not exist +# non_existent_log_file: str = "this_is_not_a_file.json" +# assert not os.path.exists(non_existent_log_file) - # "/user/totp/deactivate" - response: werkzeug.test.WrapperTestResponse = client.put( - DDSEndpoint.TOTP_DEACTIVATE, - headers=token, - json={"username": models.User.query.first().username}, - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE +# # Create command options +# command_options: typing.List = [ +# "--project", +# "projectdoesntexist", +# "--path-to-log-file", +# non_existent_log_file, +# ] +# # Run command +# result: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) +# assert result.exit_code == 1 -def test_block_if_maintenance_active_usage_not_approved(client: flask.testing.FlaskClient) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" - # Auth before maintenance on - token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - # Get maintenance row and set to active - maintenance: models.Maintenance = models.Maintenance.query.first() - maintenance.active = True +# # block_if_maintenance - should be blocked in init by before_request + + +# def test_block_if_maintenance_active_encryptedtoken_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # Try encrypted token - "/user/encrypted_token" +# with patch.object(flask_mail.Mail, "send") as mock_mail_send: +# response = client.get( +# DDSEndpoint.ENCRYPTED_TOKEN, +# auth=("researchuser", "password"), +# headers=DEFAULT_HEADER, +# ) +# assert mock_mail_send.call_count == 0 +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_secondfactor_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # Try second factor - "/user/second_factor" +# response = client.get( +# DDSEndpoint.SECOND_FACTOR, +# headers={"Authorization": f"Bearer made.up.token.long.version", **DEFAULT_HEADER}, +# json={"TOTP": "somrthing"}, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_s3proj_not_approved(client: flask.testing.FlaskClient) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # Try s3info - "/s3/proj" +# response = client.get( +# DDSEndpoint.S3KEYS, +# headers=DEFAULT_HEADER, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_fileslist_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # Try list files - "/files/list" +# response = client.get( +# DDSEndpoint.LIST_FILES, +# headers=token, +# query_string={"project": "public_project_id"}, +# json={"show_size": True}, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_filematch_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/file/match" +# response = client.get( +# DDSEndpoint.FILE_MATCH, +# headers=token, +# query_string={"project": "file_testing_project"}, +# json=["non_existent_file"], +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_removefile_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # Try remove file - "/file/rm" +# from tests.test_files_new import FIRST_NEW_FILE + +# response = client.delete( +# DDSEndpoint.REMOVE_FILE, +# headers=token, +# query_string={"project": "file_testing_project"}, +# json=[FIRST_NEW_FILE["name"]], +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_removedir_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/file/rmdir" +# from tests.test_files_new import FIRST_NEW_FILE + +# response = client.delete( +# DDSEndpoint.REMOVE_FOLDER, +# headers=token, +# query_string={"project": "file_testing_project"}, +# json=[FIRST_NEW_FILE["subpath"]], +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_fileinfo_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/file/info" +# with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: +# mock_url.return_value = "url" +# response = client.get( +# DDSEndpoint.FILE_INFO, +# headers=token, +# query_string={"project": "public_project_id"}, +# json=["filename1"], +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_fileallinfo_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/file/all/info" +# with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: +# mock_url.return_value = "url" +# response = client.get( +# DDSEndpoint.FILE_INFO_ALL, +# headers=token, +# query_string={"project": "public_project_id"}, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_projectlist_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/proj/list" +# response = client.get( +# DDSEndpoint.LIST_PROJ, +# headers=token, +# json={"usage": True}, +# content_type="application/json", +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_removeprojectcontents_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/proj/rm" +# response = client.delete( +# DDSEndpoint.REMOVE_PROJ_CONT, +# headers=token, +# query_string={"project": "file_testing_project"}, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_projectpublic_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/proj/public" +# response = client.get( +# DDSEndpoint.PROJ_PUBLIC, query_string={"project": "public_project_id"}, headers=token +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_projectprivate_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/proj/private" +# response = client.get( +# DDSEndpoint.PROJ_PRIVATE, +# query_string={"project": "public_project_id"}, +# headers=token, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_createproject_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/proj/create" +# from tests.test_project_creation import proj_data + +# response = client.post( +# DDSEndpoint.PROJECT_CREATE, +# headers=token, +# json=proj_data, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_projectusers_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/proj/users" +# response = client.get( +# DDSEndpoint.LIST_PROJ_USERS, query_string={"project": "public_project_id"}, headers=token +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_projectstatus_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/proj/status" +# response = client.post( +# DDSEndpoint.PROJECT_STATUS, +# headers=token, +# query_string={"project": "public_project_id"}, +# json={"new_status": "Available"}, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_projectaccess_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/proj/access" +# response = client.post( +# DDSEndpoint.PROJECT_ACCESS, +# headers=token, +# query_string={"project": "public_project_id"}, +# json={"email": "unituser1@mailtrap.io"}, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_adduser_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/user/add" +# from tests.api.test_user import first_new_user + +# response = client.post( +# DDSEndpoint.USER_ADD, +# headers=token, +# json=first_new_user, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_deleteuser_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/user/delete" +# invited_user_row = models.Invite.query.first() +# response = client.delete( +# DDSEndpoint.USER_DELETE, +# headers=token, +# json={"email": invited_user_row.email, "is_invite": True}, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_deleteself_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["delete_me_researcher"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/user/delete_self" +# with patch.object(flask_mail.Mail, "send") as mock_mail_send: +# response = client.delete( +# DDSEndpoint.USER_DELETE_SELF, +# headers=token, +# json=None, +# ) +# # One email for partial token but no new for deletion confirmation +# assert mock_mail_send.call_count == 0 +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_revokeaccess_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/user/access/revoke" +# from tests.test_project_creation import proj_data_with_existing_users + +# email = proj_data_with_existing_users["users_to_add"][0]["email"] +# response = client.post( +# DDSEndpoint.REMOVE_USER_FROM_PROJ, +# headers=token, +# query_string={"project": "public_project_id"}, +# json={"email": email}, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_useractivation_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/user/activation" +# from tests.test_user_activation import unituser + +# response = client.post( +# DDSEndpoint.USER_ACTIVATION, +# headers=token, +# json={**unituser, "action": "reactivate"}, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_hotp_not_approved(client: flask.testing.FlaskClient) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["researcher"]).as_tuple() + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/user/hotp/activate" +# response = client.post( +# DDSEndpoint.HOTP_ACTIVATION, +# headers=DEFAULT_HEADER, +# auth=token, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_totp_not_approved(client: flask.testing.FlaskClient) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/user/totp/activate" +# response = client.post( +# DDSEndpoint.TOTP_ACTIVATION, +# headers=token, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_listusers_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/users" +# response = client.get(DDSEndpoint.LIST_USERS, headers=token) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_finduser_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/user/find" +# response: werkzeug.test.WrapperTestResponse = client.get( +# DDSEndpoint.USER_FIND, headers=token, json={"username": models.User.query.first().username} +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_deactivatetotp_not_approved( +# client: flask.testing.FlaskClient, +# ) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/user/totp/deactivate" +# response: werkzeug.test.WrapperTestResponse = client.put( +# DDSEndpoint.TOTP_DEACTIVATE, +# headers=token, +# json={"username": models.User.query.first().username}, +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +# def test_block_if_maintenance_active_usage_not_approved(client: flask.testing.FlaskClient) -> None: +# """Certain endpoints should be blocked if maintenance is not active.""" +# # Auth before maintenance on +# token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + +# # Get maintenance row and set to active +# maintenance: models.Maintenance = models.Maintenance.query.first() +# maintenance.active = True +# db.session.commit() + +# # "/usage" +# response = client.get( +# DDSEndpoint.USAGE, +# headers=token, +# content_type="application/json", +# ) +# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + +def test_block_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> None: + """Go through all endpoints that the upload command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = models.User.query.filter_by(username="unituser").one_or_none().projects[0] + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) db.session.commit() - - # "/usage" - response = client.get( - DDSEndpoint.USAGE, - headers=token, - content_type="application/json", - ) - assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # __get_s3_info + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.S3KEYS, headers=token, query_string={"project": project.public_id}) + assert response.status_code == http.HTTPStatus.OK + # - verify response + expected_output: typing.Dict = { + "safespring_project": user.unit.safespring_name, + "url": user.unit.safespring_endpoint, + "keys": {"access_key": user.unit.safespring_access, "secret_key": user.unit.safespring_secret}, + "bucket": project.bucket, + } + for x, y in expected_output.items(): + assert x in response.json + assert response.json[x] == y + + # __get_key (public) + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.PROJ_PUBLIC, headers=token, query_string={"project": project.public_id}) + assert response.status_code == http.HTTPStatus.OK + # - verify resposne + public_key: str = response.json.get("public") + assert public_key + assert public_key == project.public_key.hex().upper() + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put(DDSEndpoint.PROJECT_BUSY, headers=token, query_string={"project": project.public_id}, json={"busy": True}) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # check_previous_upload + # - request + files = models.File.query.filter_by(project_id=project.id).all() + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.FILE_MATCH, headers=token, query_string={"project": project.public_id}, json=[f.name for f in files]) + assert response.status_code == http.HTTPStatus.OK + # - verify response + expected_output: typing.Dict = {file.name: file.name_in_bucket for file in files} + returned_files: typing.Dict = response.json.get("files") + assert returned_files + for x, y in expected_output.items(): + assert x in returned_files + assert returned_files[x] == y + + + # add_file_db + # - file info + file_info = { + "name": "newfile", + "name_in_bucket": "new_file", + "subpath": ".", + "size": 0, + "size_processed": 0, + "compressed": False, + "salt": "s"*32, + "public_key": "p"*64, + "checksum": "c"*64, + } + # - request + response: werkzeug.test.WrapperTestResponse = client.post(DDSEndpoint.FILE_NEW, headers=token, query_string={"project": project.public_id}, json=file_info) + assert response.status_code == http.HTTPStatus.OK + # - verify response + message: str = response.json.get("message") + assert message == f"File '{file_info['name']}' added to db." + created_file: models.File = models.File.query.filter_by( + name=file_info["name"], + name_in_bucket=file_info["name_in_bucket"], + subpath=file_info["subpath"], + size_original=file_info["size"], + size_stored=file_info["size_processed"], + compressed=file_info["compressed"], + salt=file_info["salt"], + public_key=file_info["public_key"], + checksum=file_info["checksum"] + ).one_or_none() + assert created_file + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put(DDSEndpoint.PROJECT_BUSY, headers=token, query_string={"project": project.public_id}, json={"busy": False}) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() \ No newline at end of file From 39e4cd5031dc6669a1140b92538829e7e8ad1c85 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 30 Sep 2022 13:56:02 +0200 Subject: [PATCH 037/123] test: maintenance after authentication --- dds_web/utils.py | 12 ++- tests/test_init.py | 213 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 196 insertions(+), 29 deletions(-) diff --git a/dds_web/utils.py b/dds_web/utils.py index 1ea8d8dcb..5cb79e126 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -582,7 +582,7 @@ def block_if_maintenance(): project_required_endpoints: typing.List = [ f"/api/v1{resource}" for resource in ["/file/new", "/file/update", "/proj/busy"] ] - approved: typing.List = [ + admin_endpoints: typing.List = [ f"/api/v1{x}" for x in [ "/user/info", @@ -592,12 +592,18 @@ def block_if_maintenance(): "/motd/send", "/proj/busy/any", ] - ] + project_required_endpoints + ] + approved_endpoints: typing.List = project_required_endpoints + admin_endpoints + flask.current_app.logger.debug(project_required_endpoints) + flask.current_app.logger.debug(admin_endpoints) # Request not to accepted endpoint # OR request to accepted endpoint but project not specified or busy - if flask.request.path not in approved: + current_endpoint: str = flask.request.path + if current_endpoint not in approved_endpoints: + flask.current_app.logger.debug(f"no it's not there - {flask.request.path}") raise MaintenanceOngoingException() else: + flask.current_app.logger.debug(f"yes it's there - {flask.request.path}") req_args = flask.request.args if flask.request.path in project_required_endpoints: if not (req_args and (project_id := req_args.get("project"))): diff --git a/tests/test_init.py b/tests/test_init.py index 86084edb6..ba1c67f15 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -818,15 +818,18 @@ def mock_commit(): # ) # assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + def test_block_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> None: - """Go through all endpoints that the upload command uses. - + """Go through all endpoints that the upload command uses. + Check what happens when maintenance is set to active after upload started. """ # Auth username: str = "unituser" token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) - project: models.Project = models.User.query.filter_by(username="unituser").one_or_none().projects[0] + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) # list_all_active_motds # - new motd @@ -848,12 +851,12 @@ def test_block_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> # - verify response user: models.User = models.User.query.filter_by(username=username).one_or_none() expected_output: typing.Dict = { - "email_primary": user.primary_email, - "emails_all": [x.email for x in user.emails], - "role": user.role, - "username": username, - "name": user.name, - } + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } info: typing.Dict = response.json.get("info") assert info for x, y in expected_output.items(): @@ -862,14 +865,19 @@ def test_block_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> # __get_s3_info # - request - response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.S3KEYS, headers=token, query_string={"project": project.public_id}) + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.S3KEYS, headers=token, query_string={"project": project.public_id} + ) assert response.status_code == http.HTTPStatus.OK # - verify response expected_output: typing.Dict = { - "safespring_project": user.unit.safespring_name, - "url": user.unit.safespring_endpoint, - "keys": {"access_key": user.unit.safespring_access, "secret_key": user.unit.safespring_secret}, - "bucket": project.bucket, + "safespring_project": user.unit.safespring_name, + "url": user.unit.safespring_endpoint, + "keys": { + "access_key": user.unit.safespring_access, + "secret_key": user.unit.safespring_secret, + }, + "bucket": project.bucket, } for x, y in expected_output.items(): assert x in response.json @@ -877,8 +885,10 @@ def test_block_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> # __get_key (public) # - request - response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.PROJ_PUBLIC, headers=token, query_string={"project": project.public_id}) - assert response.status_code == http.HTTPStatus.OK + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PUBLIC, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK # - verify resposne public_key: str = response.json.get("public") assert public_key @@ -886,7 +896,12 @@ def test_block_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> # change_busy_status - busy # - request - response: werkzeug.test.WrapperTestResponse = client.put(DDSEndpoint.PROJECT_BUSY, headers=token, query_string={"project": project.public_id}, json={"busy": True}) + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) assert response.status_code == http.HTTPStatus.OK # - verify response busy_status_set: bool = response.json.get("ok") @@ -898,7 +913,12 @@ def test_block_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> # check_previous_upload # - request files = models.File.query.filter_by(project_id=project.id).all() - response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.FILE_MATCH, headers=token, query_string={"project": project.public_id}, json=[f.name for f in files]) + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.FILE_MATCH, + headers=token, + query_string={"project": project.public_id}, + json=[f.name for f in files], + ) assert response.status_code == http.HTTPStatus.OK # - verify response expected_output: typing.Dict = {file.name: file.name_in_bucket for file in files} @@ -907,7 +927,6 @@ def test_block_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> for x, y in expected_output.items(): assert x in returned_files assert returned_files[x] == y - # add_file_db # - file info @@ -918,12 +937,17 @@ def test_block_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> "size": 0, "size_processed": 0, "compressed": False, - "salt": "s"*32, - "public_key": "p"*64, - "checksum": "c"*64, + "salt": "s" * 32, + "public_key": "p" * 64, + "checksum": "c" * 64, } # - request - response: werkzeug.test.WrapperTestResponse = client.post(DDSEndpoint.FILE_NEW, headers=token, query_string={"project": project.public_id}, json=file_info) + response: werkzeug.test.WrapperTestResponse = client.post( + DDSEndpoint.FILE_NEW, + headers=token, + query_string={"project": project.public_id}, + json=file_info, + ) assert response.status_code == http.HTTPStatus.OK # - verify response message: str = response.json.get("message") @@ -937,17 +961,154 @@ def test_block_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> compressed=file_info["compressed"], salt=file_info["salt"], public_key=file_info["public_key"], - checksum=file_info["checksum"] + checksum=file_info["checksum"], ).one_or_none() assert created_file # change_busy_status - busy # - request - response: werkzeug.test.WrapperTestResponse = client.put(DDSEndpoint.PROJECT_BUSY, headers=token, query_string={"project": project.public_id}, json={"busy": False}) + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) assert response.status_code == http.HTTPStatus.OK # - verify response busy_status_set: bool = response.json.get("ok") assert busy_status_set message: str = response.json.get("message") assert message == f"Project {project.public_id} was set to not busy." - assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() \ No newline at end of file + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + + +def test_block_if_maintenancen_active_after_auth(client: flask.testing.FlaskClient) -> None: + """Go through all endpoints that the upload command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # __get_s3_info + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.S3KEYS, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + # __get_key (public) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PUBLIC, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + assert not models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # check_previous_upload + # - request + files = models.File.query.filter_by(project_id=project.id).all() + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.FILE_MATCH, + headers=token, + query_string={"project": project.public_id}, + json=[f.name for f in files], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + # add_file_db + # - file info + file_info = { + "name": "newfile", + "name_in_bucket": "new_file", + "subpath": ".", + "size": 0, + "size_processed": 0, + "compressed": False, + "salt": "s" * 32, + "public_key": "p" * 64, + "checksum": "c" * 64, + } + # - request + response: werkzeug.test.WrapperTestResponse = client.post( + DDSEndpoint.FILE_NEW, + headers=token, + query_string={"project": project.public_id}, + json=file_info, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + created_file: models.File = models.File.query.filter_by( + name=file_info["name"], + name_in_bucket=file_info["name_in_bucket"], + subpath=file_info["subpath"], + size_original=file_info["size"], + size_stored=file_info["size_processed"], + compressed=file_info["compressed"], + salt=file_info["salt"], + public_key=file_info["public_key"], + checksum=file_info["checksum"], + ).one_or_none() + assert not created_file + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE From fae1c2cf74b3789a6f4de7ae50280769b423d84a Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 30 Sep 2022 15:15:10 +0200 Subject: [PATCH 038/123] test for if maintenance set during upload --- tests/test_init.py | 461 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 461 insertions(+) diff --git a/tests/test_init.py b/tests/test_init.py index ba1c67f15..861753878 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1112,3 +1112,464 @@ def test_block_if_maintenancen_active_after_auth(client: flask.testing.FlaskClie json={"busy": False}, ) assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + +def test_block_if_maintenancen_active_after_busy(client: flask.testing.FlaskClient) -> None: + """Go through all endpoints that the upload command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # __get_s3_info + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.S3KEYS, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + expected_output: typing.Dict = { + "safespring_project": user.unit.safespring_name, + "url": user.unit.safespring_endpoint, + "keys": { + "access_key": user.unit.safespring_access, + "secret_key": user.unit.safespring_secret, + }, + "bucket": project.bucket, + } + for x, y in expected_output.items(): + assert x in response.json + assert response.json[x] == y + + # __get_key (public) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PUBLIC, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify resposne + public_key: str = response.json.get("public") + assert public_key + assert public_key == project.public_key.hex().upper() + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # check_previous_upload + # - request + files = models.File.query.filter_by(project_id=project.id).all() + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.FILE_MATCH, + headers=token, + query_string={"project": project.public_id}, + json=[f.name for f in files], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + assert response.json.get("message") == "Maintenance of DDS is ongoing." + returned_files: typing.Dict = response.json.get("files") + assert not returned_files + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_if_maintenancen_active_after_check_previous_upload(client: flask.testing.FlaskClient) -> None: + """Go through all endpoints that the upload command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # __get_s3_info + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.S3KEYS, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + expected_output: typing.Dict = { + "safespring_project": user.unit.safespring_name, + "url": user.unit.safespring_endpoint, + "keys": { + "access_key": user.unit.safespring_access, + "secret_key": user.unit.safespring_secret, + }, + "bucket": project.bucket, + } + for x, y in expected_output.items(): + assert x in response.json + assert response.json[x] == y + + # __get_key (public) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PUBLIC, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify resposne + public_key: str = response.json.get("public") + assert public_key + assert public_key == project.public_key.hex().upper() + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # check_previous_upload + # - request + files = models.File.query.filter_by(project_id=project.id).all() + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.FILE_MATCH, + headers=token, + query_string={"project": project.public_id}, + json=[f.name for f in files], + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + expected_output: typing.Dict = {file.name: file.name_in_bucket for file in files} + returned_files: typing.Dict = response.json.get("files") + assert returned_files + for x, y in expected_output.items(): + assert x in returned_files + assert returned_files[x] == y + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # add_file_db + # - file info + file_info = { + "name": "newfile", + "name_in_bucket": "new_file", + "subpath": ".", + "size": 0, + "size_processed": 0, + "compressed": False, + "salt": "s" * 32, + "public_key": "p" * 64, + "checksum": "c" * 64, + } + # - request + response: werkzeug.test.WrapperTestResponse = client.post( + DDSEndpoint.FILE_NEW, + headers=token, + query_string={"project": project.public_id}, + json=file_info, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + message: str = response.json.get("message") + assert message == f"File '{file_info['name']}' added to db." + created_file: models.File = models.File.query.filter_by( + name=file_info["name"], + name_in_bucket=file_info["name_in_bucket"], + subpath=file_info["subpath"], + size_original=file_info["size"], + size_stored=file_info["size_processed"], + compressed=file_info["compressed"], + salt=file_info["salt"], + public_key=file_info["public_key"], + checksum=file_info["checksum"], + ).one_or_none() + assert created_file + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_if_maintenancen_active_after_add_file_db(client: flask.testing.FlaskClient) -> None: + """Go through all endpoints that the upload command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # __get_s3_info + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.S3KEYS, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + expected_output: typing.Dict = { + "safespring_project": user.unit.safespring_name, + "url": user.unit.safespring_endpoint, + "keys": { + "access_key": user.unit.safespring_access, + "secret_key": user.unit.safespring_secret, + }, + "bucket": project.bucket, + } + for x, y in expected_output.items(): + assert x in response.json + assert response.json[x] == y + + # __get_key (public) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PUBLIC, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify resposne + public_key: str = response.json.get("public") + assert public_key + assert public_key == project.public_key.hex().upper() + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # check_previous_upload + # - request + files = models.File.query.filter_by(project_id=project.id).all() + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.FILE_MATCH, + headers=token, + query_string={"project": project.public_id}, + json=[f.name for f in files], + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + expected_output: typing.Dict = {file.name: file.name_in_bucket for file in files} + returned_files: typing.Dict = response.json.get("files") + assert returned_files + for x, y in expected_output.items(): + assert x in returned_files + assert returned_files[x] == y + + # add_file_db + # - file info + file_info = { + "name": "newfile", + "name_in_bucket": "new_file", + "subpath": ".", + "size": 0, + "size_processed": 0, + "compressed": False, + "salt": "s" * 32, + "public_key": "p" * 64, + "checksum": "c" * 64, + } + # - request + response: werkzeug.test.WrapperTestResponse = client.post( + DDSEndpoint.FILE_NEW, + headers=token, + query_string={"project": project.public_id}, + json=file_info, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + message: str = response.json.get("message") + assert message == f"File '{file_info['name']}' added to db." + created_file: models.File = models.File.query.filter_by( + name=file_info["name"], + name_in_bucket=file_info["name_in_bucket"], + subpath=file_info["subpath"], + size_original=file_info["size"], + size_stored=file_info["size_processed"], + compressed=file_info["compressed"], + salt=file_info["salt"], + public_key=file_info["public_key"], + checksum=file_info["checksum"], + ).one_or_none() + assert created_file + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() \ No newline at end of file From baf8b3d9d35d3778b1aa41badea1fc30436b2cee Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 30 Sep 2022 15:15:39 +0200 Subject: [PATCH 039/123] black --- tests/test_init.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 861753878..4408e4fd7 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1113,6 +1113,7 @@ def test_block_if_maintenancen_active_after_auth(client: flask.testing.FlaskClie ) assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + def test_block_if_maintenancen_active_after_busy(client: flask.testing.FlaskClient) -> None: """Go through all endpoints that the upload command uses. @@ -1240,7 +1241,10 @@ def test_block_if_maintenancen_active_after_busy(client: flask.testing.FlaskClie assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_if_maintenancen_active_after_check_previous_upload(client: flask.testing.FlaskClient) -> None: + +def test_block_if_maintenancen_active_after_check_previous_upload( + client: flask.testing.FlaskClient, +) -> None: """Go through all endpoints that the upload command uses. Check what happens when maintenance is set to active after upload started. @@ -1407,6 +1411,7 @@ def test_block_if_maintenancen_active_after_check_previous_upload(client: flask. assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + def test_block_if_maintenancen_active_after_add_file_db(client: flask.testing.FlaskClient) -> None: """Go through all endpoints that the upload command uses. @@ -1572,4 +1577,4 @@ def test_block_if_maintenancen_active_after_add_file_db(client: flask.testing.Fl assert busy_status_set message: str = response.json.get("message") assert message == f"Project {project.public_id} was set to not busy." - assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() \ No newline at end of file + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() From ee4a4e5ea4ecd9a0a164ecce8f954b073dedaf06 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 30 Sep 2022 15:52:11 +0200 Subject: [PATCH 040/123] tests for get process when no maintenance --- tests/test_init.py | 155 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 5 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 4408e4fd7..bb5c71328 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -2,6 +2,8 @@ import click.testing import pytest from dds_web import db +import dds_web +import dds_web.api.api_s3_connector from dds_web.database import models from unittest.mock import patch import typing @@ -818,8 +820,9 @@ def mock_commit(): # ) # assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE +# block data put -def test_block_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> None: +def test_block_put_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> None: """Go through all endpoints that the upload command uses. Check what happens when maintenance is set to active after upload started. @@ -982,7 +985,7 @@ def test_block_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_if_maintenancen_active_after_auth(client: flask.testing.FlaskClient) -> None: +def test_block_put_if_maintenancen_active_after_auth(client: flask.testing.FlaskClient) -> None: """Go through all endpoints that the upload command uses. Check what happens when maintenance is set to active after upload started. @@ -1114,7 +1117,7 @@ def test_block_if_maintenancen_active_after_auth(client: flask.testing.FlaskClie assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE -def test_block_if_maintenancen_active_after_busy(client: flask.testing.FlaskClient) -> None: +def test_block_put_if_maintenancen_active_after_busy(client: flask.testing.FlaskClient) -> None: """Go through all endpoints that the upload command uses. Check what happens when maintenance is set to active after upload started. @@ -1242,7 +1245,7 @@ def test_block_if_maintenancen_active_after_busy(client: flask.testing.FlaskClie assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_if_maintenancen_active_after_check_previous_upload( +def test_block_put_if_maintenancen_active_after_check_previous_upload( client: flask.testing.FlaskClient, ) -> None: """Go through all endpoints that the upload command uses. @@ -1412,7 +1415,7 @@ def test_block_if_maintenancen_active_after_check_previous_upload( assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_if_maintenancen_active_after_add_file_db(client: flask.testing.FlaskClient) -> None: +def test_block_put_if_maintenancen_active_after_add_file_db(client: flask.testing.FlaskClient) -> None: """Go through all endpoints that the upload command uses. Check what happens when maintenance is set to active after upload started. @@ -1578,3 +1581,145 @@ def test_block_if_maintenancen_active_after_add_file_db(client: flask.testing.Fl message: str = response.json.get("message") assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +# block data get + +def test_block_get_if_maintenancen_not_active(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the upload command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # __get_key (public) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PUBLIC, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify resposne + public_key: str = response.json.get("public") + assert public_key + assert public_key == project.public_key.hex().upper() + + # __get_key (private) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PRIVATE, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert response.json.get("private") # not testing encryption stuff here + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # __collect_file_info_remote + # - request + files = models.File.query.filter_by(project_id=project.id).all() + with patch( + "dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url" + ) as mock_url: + mock_url.return_value = "url" + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.FILE_INFO, + headers=token, + query_string={"project": project.public_id}, + json=[f.name for f in files], + ) + assert response.status_code == http.HTTPStatus.OK + + # update_db + # - file info + file_to_update: models.File = files[0] + assert file_to_update.time_latest_download is None + file_info = {"name": file_to_update.name} + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.FILE_UPDATE, + headers=token, + query_string={"project": project.public_id}, + json=file_info, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + message: str = response.json.get("message") + assert message == "File info updated." + updated_file: models.File = models.File.query.filter_by( + name=file_to_update.name, + name_in_bucket=file_to_update.name_in_bucket, + subpath=file_to_update.subpath, + size_original=file_to_update.size_original, + size_stored=file_to_update.size_stored, + compressed=file_to_update.compressed, + salt=file_to_update.salt, + public_key=file_to_update.public_key, + checksum=file_to_update.checksum, + ).one_or_none() + assert updated_file and updated_file.time_latest_download is not None + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() \ No newline at end of file From 61ca091989c0abaa8309f3dfe7190e5409d4e06a Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 30 Sep 2022 16:05:02 +0200 Subject: [PATCH 041/123] test download process with maintenance started during download --- tests/test_init.py | 533 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 532 insertions(+), 1 deletion(-) diff --git a/tests/test_init.py b/tests/test_init.py index bb5c71328..2f53c82dc 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1585,7 +1585,533 @@ def test_block_put_if_maintenancen_active_after_add_file_db(client: flask.testin # block data get def test_block_get_if_maintenancen_not_active(client: flask.testing.FlaskClient, boto3_session) -> None: - """Go through all endpoints that the upload command uses. + """Go through all endpoints that the download command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # __get_key (public) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PUBLIC, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify resposne + public_key: str = response.json.get("public") + assert public_key + assert public_key == project.public_key.hex().upper() + + # __get_key (private) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PRIVATE, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert response.json.get("private") # not testing encryption stuff here + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # __collect_file_info_remote + # - request + files = models.File.query.filter_by(project_id=project.id).all() + with patch( + "dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url" + ) as mock_url: + mock_url.return_value = "url" + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.FILE_INFO, + headers=token, + query_string={"project": project.public_id}, + json=[f.name for f in files], + ) + assert response.status_code == http.HTTPStatus.OK + + # update_db + # - file info + file_to_update: models.File = files[0] + assert file_to_update.time_latest_download is None + file_info = {"name": file_to_update.name} + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.FILE_UPDATE, + headers=token, + query_string={"project": project.public_id}, + json=file_info, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + message: str = response.json.get("message") + assert message == "File info updated." + updated_file: models.File = models.File.query.filter_by( + name=file_to_update.name, + name_in_bucket=file_to_update.name_in_bucket, + subpath=file_to_update.subpath, + size_original=file_to_update.size_original, + size_stored=file_to_update.size_stored, + compressed=file_to_update.compressed, + salt=file_to_update.salt, + public_key=file_to_update.public_key, + checksum=file_to_update.checksum, + ).one_or_none() + assert updated_file and updated_file.time_latest_download is not None + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_get_if_maintenancen_active_after_auth(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the download command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # __get_key (public) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PUBLIC, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify resposne + assert response.json.get("message") == "Maintenance of DDS is ongoing." + + # __get_key (private) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PRIVATE, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + assert response.json.get("message") == "Maintenance of DDS is ongoing." + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + assert response.json.get("message") == "Maintenance of DDS is ongoing." + + # __collect_file_info_remote + # - request + files = models.File.query.filter_by(project_id=project.id).all() + with patch( + "dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url" + ) as mock_url: + mock_url.return_value = "url" + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.FILE_INFO, + headers=token, + query_string={"project": project.public_id}, + json=[f.name for f in files], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + assert response.json.get("message") == "Maintenance of DDS is ongoing." + + # update_db + # - file info + file_to_update: models.File = files[0] + assert file_to_update.time_latest_download is None + file_info = {"name": file_to_update.name} + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.FILE_UPDATE, + headers=token, + query_string={"project": project.public_id}, + json=file_info, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + assert response.json.get("message") == "Maintenance of DDS is ongoing." + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + assert response.json.get("message") == "Maintenance of DDS is ongoing." + +def test_block_get_if_maintenancen_active_after_busy(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the download command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # __get_key (public) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PUBLIC, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify resposne + public_key: str = response.json.get("public") + assert public_key + assert public_key == project.public_key.hex().upper() + + # __get_key (private) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PRIVATE, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert response.json.get("private") # not testing encryption stuff here + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + + # __collect_file_info_remote + # - request + files = models.File.query.filter_by(project_id=project.id).all() + with patch( + "dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url" + ) as mock_url: + mock_url.return_value = "url" + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.FILE_INFO, + headers=token, + query_string={"project": project.public_id}, + json=[f.name for f in files], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + assert response.json.get("message") == "Maintenance of DDS is ongoing." + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_get_if_maintenancen_active_after_collect_file_info_remote(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the download command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # __get_key (public) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PUBLIC, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify resposne + public_key: str = response.json.get("public") + assert public_key + assert public_key == project.public_key.hex().upper() + + # __get_key (private) + # - request + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.PROJ_PRIVATE, headers=token, query_string={"project": project.public_id} + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert response.json.get("private") # not testing encryption stuff here + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # __collect_file_info_remote + # - request + files = models.File.query.filter_by(project_id=project.id).all() + with patch( + "dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url" + ) as mock_url: + mock_url.return_value = "url" + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.FILE_INFO, + headers=token, + query_string={"project": project.public_id}, + json=[f.name for f in files], + ) + assert response.status_code == http.HTTPStatus.OK + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # update_db + # - file info + file_to_update: models.File = files[0] + assert file_to_update.time_latest_download is None + file_info = {"name": file_to_update.name} + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.FILE_UPDATE, + headers=token, + query_string={"project": project.public_id}, + json=file_info, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + message: str = response.json.get("message") + assert message == "File info updated." + updated_file: models.File = models.File.query.filter_by( + name=file_to_update.name, + name_in_bucket=file_to_update.name_in_bucket, + subpath=file_to_update.subpath, + size_original=file_to_update.size_original, + size_stored=file_to_update.size_stored, + compressed=file_to_update.compressed, + salt=file_to_update.salt, + public_key=file_to_update.public_key, + checksum=file_to_update.checksum, + ).one_or_none() + assert updated_file and updated_file.time_latest_download is not None + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_get_if_maintenancen_active_after_update_db(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the download command uses. Check what happens when maintenance is set to active after upload started. """ @@ -1708,6 +2234,11 @@ def test_block_get_if_maintenancen_not_active(client: flask.testing.FlaskClient, ).one_or_none() assert updated_file and updated_file.time_latest_download is not None + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + # change_busy_status - busy # - request response: werkzeug.test.WrapperTestResponse = client.put( From c20bb6bf87f02cb7cac293ee3de189e1e5092fa7 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 30 Sep 2022 16:44:43 +0200 Subject: [PATCH 042/123] tests for data removal process --- tests/test_init.py | 918 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 918 insertions(+) diff --git a/tests/test_init.py b/tests/test_init.py index 2f53c82dc..f37df00c9 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -2238,7 +2238,925 @@ def test_block_get_if_maintenancen_active_after_update_db(client: flask.testing. maintenance: models.Maintenance = models.Maintenance.query.first() maintenance.active = True db.session.commit() + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +# block data rm + +def test_block_rm_all_if_maintenancen_not_active(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the remove command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # __collect_file_info_remote + # - request + response: werkzeug.test.WrapperTestResponse = client.delete( + DDSEndpoint.REMOVE_PROJ_CONT, + headers=token, + query_string={"project": project.public_id}, + ) + assert response.status_code == http.HTTPStatus.OK + assert response.json.get("removed") is True + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + + +def test_block_rm_all_if_maintenancen_after_auth(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the remove command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + assert response.json.get("message") == "Maintenance of DDS is ongoing." + assert not models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # remove_all + # - request + response: werkzeug.test.WrapperTestResponse = client.delete( + DDSEndpoint.REMOVE_PROJ_CONT, + headers=token, + query_string={"project": project.public_id}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + assert response.json.get("message") == "Maintenance of DDS is ongoing." + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + assert response.json.get("message") == "Maintenance of DDS is ongoing." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_rm_all_if_maintenancen_active_after_busy(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the remove command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # remove_all + # - request + response: werkzeug.test.WrapperTestResponse = client.delete( + DDSEndpoint.REMOVE_PROJ_CONT, + headers=token, + query_string={"project": project.public_id}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + assert response.json.get("message") == "Maintenance of DDS is ongoing." + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_rm_all_if_maintenancen_active_after_rm(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the remove command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # remove_all + # - request + response: werkzeug.test.WrapperTestResponse = client.delete( + DDSEndpoint.REMOVE_PROJ_CONT, + headers=token, + query_string={"project": project.public_id}, + ) + assert response.status_code == http.HTTPStatus.OK + assert response.json.get("removed") is True + assert models.File.query.filter_by(project_id=project.public_id).count() == 0 + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_rm_file_if_maintenancen_after_auth(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the remove command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + assert response.json.get("message") == "Maintenance of DDS is ongoing." + assert not models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # remove_file + # - get files + files: typing.List = [file.name for file in models.File.query.all()] + # - request + response: werkzeug.test.WrapperTestResponse = client.delete( + DDSEndpoint.REMOVE_FILE, + headers=token, + query_string={"project": project.public_id}, + json=files + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + assert response.json.get("message") == "Maintenance of DDS is ongoing." + assert models.File.query.filter(models.File.name.in_(files)).count() == len(files) + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + assert response.json.get("message") == "Maintenance of DDS is ongoing." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_rm_file_if_maintenancen_active_after_busy(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the remove command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # remove_file + # - get files + files: typing.List = [file.name for file in models.File.query.all()] + # - request + response: werkzeug.test.WrapperTestResponse = client.delete( + DDSEndpoint.REMOVE_FILE, + headers=token, + query_string={"project": project.public_id}, + json=files + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + assert response.json.get("message") == "Maintenance of DDS is ongoing." + assert models.File.query.filter(models.File.name.in_(files)).count() == len(files) + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_rm_file_if_maintenancen_active_after_rm(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the remove command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # remove_file + # - get files + files: typing.List = [file.name for file in models.File.query.all()] + # - request + response: werkzeug.test.WrapperTestResponse = client.delete( + DDSEndpoint.REMOVE_FILE, + headers=token, + query_string={"project": project.public_id}, + json=files + ) + assert response.status_code == http.HTTPStatus.OK + assert not models.File.query.filter(models.File.name.in_(files)).all() + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_rm_folder_if_maintenancen_after_auth(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the remove command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + assert response.json.get("message") == "Maintenance of DDS is ongoing." + assert not models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # remove_folder + # - get folders + files_in_folders: typing.List = models.File.query.filter(models.File.subpath != ".").all() + folders: typing.List = list(set(file.subpath for file in files_in_folders)) + assert folders + # - request + response: werkzeug.test.WrapperTestResponse = client.delete( + DDSEndpoint.REMOVE_FOLDER, + headers=token, + query_string={"project": project.public_id}, + json=folders + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + assert response.json.get("message") == "Maintenance of DDS is ongoing." + assert models.File.query.filter(models.File.subpath.in_(folders)).count() == len(files_in_folders) + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # - verify response + assert response.json.get("message") == "Maintenance of DDS is ongoing." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_rm_folder_if_maintenancen_active_after_busy(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the remove command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # remove_folder + files_in_folders: typing.List = models.File.query.filter(models.File.subpath != ".").all() + folders: typing.List = list(set(file.subpath for file in files_in_folders)) + assert folders + # - request + response: werkzeug.test.WrapperTestResponse = client.delete( + DDSEndpoint.REMOVE_FOLDER, + headers=token, + query_string={"project": project.public_id}, + json=folders + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + assert response.json.get("message") == "Maintenance of DDS is ongoing." + assert models.File.query.filter(models.File.subpath.in_(folders)).count() == len(files_in_folders) + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to not busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + +def test_block_rm_folder_if_maintenancen_active_after_rm(client: flask.testing.FlaskClient, boto3_session) -> None: + """Go through all endpoints that the remove command uses. + + Check what happens when maintenance is set to active after upload started. + """ + # Auth + username: str = "unituser" + token: typing.Dict = UserAuth(USER_CREDENTIALS[username]).token(client) + project: models.Project = ( + models.User.query.filter_by(username="unituser").one_or_none().projects[0] + ) + + # list_all_active_motds + # - new motd + new_motd_message: str = "Test motd" + new_motd: models.MOTD = models.MOTD(message=new_motd_message) + db.session.add(new_motd) + db.session.commit() + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.MOTD, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + assert isinstance(response.json.get("motds"), list) + assert new_motd_message in response.json.get("motds")[0]["Message"] + + # get_user_name_if_logged_in + # - request + response: werkzeug.test.WrapperTestResponse = client.get(DDSEndpoint.USER_INFO, headers=token) + assert response.status_code == http.HTTPStatus.OK + # - verify response + user: models.User = models.User.query.filter_by(username=username).one_or_none() + expected_output: typing.Dict = { + "email_primary": user.primary_email, + "emails_all": [x.email for x in user.emails], + "role": user.role, + "username": username, + "name": user.name, + } + info: typing.Dict = response.json.get("info") + assert info + for x, y in expected_output.items(): + assert x in info + assert info[x] == y + + # change_busy_status - busy + # - request + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + # - verify response + busy_status_set: bool = response.json.get("ok") + assert busy_status_set + message: str = response.json.get("message") + assert message == f"Project {project.public_id} was set to busy." + assert models.Project.query.filter_by(public_id=project.public_id, busy=True).one_or_none() + + # remove_folder + # - get folders + files_in_folders: typing.List = models.File.query.filter(models.File.subpath != ".").all() + folders: typing.List = list(set(file.subpath for file in files_in_folders)) + assert folders + # - request + response: werkzeug.test.WrapperTestResponse = client.delete( + DDSEndpoint.REMOVE_FOLDER, + headers=token, + query_string={"project": project.public_id}, + json=folders + ) + assert response.status_code == http.HTTPStatus.OK + assert not models.File.query.filter(models.File.subpath.in_(folders)).all() + + # Set maintenance to on + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + # change_busy_status - busy # - request response: werkzeug.test.WrapperTestResponse = client.put( From c7f9f46418fbb4738a0810351d46760e04e159c0 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 30 Sep 2022 16:44:59 +0200 Subject: [PATCH 043/123] black --- tests/test_init.py | 142 +++++++++++++++++++++++++++++---------------- 1 file changed, 92 insertions(+), 50 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index f37df00c9..2030815be 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -820,7 +820,8 @@ def mock_commit(): # ) # assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE -# block data put +# block data put + def test_block_put_if_maintenancen_not_active(client: flask.testing.FlaskClient) -> None: """Go through all endpoints that the upload command uses. @@ -1415,7 +1416,9 @@ def test_block_put_if_maintenancen_active_after_check_previous_upload( assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_put_if_maintenancen_active_after_add_file_db(client: flask.testing.FlaskClient) -> None: +def test_block_put_if_maintenancen_active_after_add_file_db( + client: flask.testing.FlaskClient, +) -> None: """Go through all endpoints that the upload command uses. Check what happens when maintenance is set to active after upload started. @@ -1582,9 +1585,13 @@ def test_block_put_if_maintenancen_active_after_add_file_db(client: flask.testin assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + # block data get -def test_block_get_if_maintenancen_not_active(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_get_if_maintenancen_not_active( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the download command uses. Check what happens when maintenance is set to active after upload started. @@ -1646,7 +1653,7 @@ def test_block_get_if_maintenancen_not_active(client: flask.testing.FlaskClient, ) assert response.status_code == http.HTTPStatus.OK # - verify response - assert response.json.get("private") # not testing encryption stuff here + assert response.json.get("private") # not testing encryption stuff here # change_busy_status - busy # - request @@ -1667,9 +1674,7 @@ def test_block_get_if_maintenancen_not_active(client: flask.testing.FlaskClient, # __collect_file_info_remote # - request files = models.File.query.filter_by(project_id=project.id).all() - with patch( - "dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url" - ) as mock_url: + with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: mock_url.return_value = "url" response: werkzeug.test.WrapperTestResponse = client.get( DDSEndpoint.FILE_INFO, @@ -1724,7 +1729,10 @@ def test_block_get_if_maintenancen_not_active(client: flask.testing.FlaskClient, assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_get_if_maintenancen_active_after_auth(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_get_if_maintenancen_active_after_auth( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the download command uses. Check what happens when maintenance is set to active after upload started. @@ -1806,9 +1814,7 @@ def test_block_get_if_maintenancen_active_after_auth(client: flask.testing.Flask # __collect_file_info_remote # - request files = models.File.query.filter_by(project_id=project.id).all() - with patch( - "dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url" - ) as mock_url: + with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: mock_url.return_value = "url" response: werkzeug.test.WrapperTestResponse = client.get( DDSEndpoint.FILE_INFO, @@ -1847,7 +1853,10 @@ def test_block_get_if_maintenancen_active_after_auth(client: flask.testing.Flask # - verify response assert response.json.get("message") == "Maintenance of DDS is ongoing." -def test_block_get_if_maintenancen_active_after_busy(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_get_if_maintenancen_active_after_busy( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the download command uses. Check what happens when maintenance is set to active after upload started. @@ -1909,7 +1918,7 @@ def test_block_get_if_maintenancen_active_after_busy(client: flask.testing.Flask ) assert response.status_code == http.HTTPStatus.OK # - verify response - assert response.json.get("private") # not testing encryption stuff here + assert response.json.get("private") # not testing encryption stuff here # change_busy_status - busy # - request @@ -1932,13 +1941,10 @@ def test_block_get_if_maintenancen_active_after_busy(client: flask.testing.Flask maintenance.active = True db.session.commit() - # __collect_file_info_remote # - request files = models.File.query.filter_by(project_id=project.id).all() - with patch( - "dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url" - ) as mock_url: + with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: mock_url.return_value = "url" response: werkzeug.test.WrapperTestResponse = client.get( DDSEndpoint.FILE_INFO, @@ -1965,7 +1971,10 @@ def test_block_get_if_maintenancen_active_after_busy(client: flask.testing.Flask assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_get_if_maintenancen_active_after_collect_file_info_remote(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_get_if_maintenancen_active_after_collect_file_info_remote( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the download command uses. Check what happens when maintenance is set to active after upload started. @@ -2027,7 +2036,7 @@ def test_block_get_if_maintenancen_active_after_collect_file_info_remote(client: ) assert response.status_code == http.HTTPStatus.OK # - verify response - assert response.json.get("private") # not testing encryption stuff here + assert response.json.get("private") # not testing encryption stuff here # change_busy_status - busy # - request @@ -2048,9 +2057,7 @@ def test_block_get_if_maintenancen_active_after_collect_file_info_remote(client: # __collect_file_info_remote # - request files = models.File.query.filter_by(project_id=project.id).all() - with patch( - "dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url" - ) as mock_url: + with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: mock_url.return_value = "url" response: werkzeug.test.WrapperTestResponse = client.get( DDSEndpoint.FILE_INFO, @@ -2064,7 +2071,7 @@ def test_block_get_if_maintenancen_active_after_collect_file_info_remote(client: maintenance: models.Maintenance = models.Maintenance.query.first() maintenance.active = True db.session.commit() - + # update_db # - file info file_to_update: models.File = files[0] @@ -2110,7 +2117,10 @@ def test_block_get_if_maintenancen_active_after_collect_file_info_remote(client: assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_get_if_maintenancen_active_after_update_db(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_get_if_maintenancen_active_after_update_db( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the download command uses. Check what happens when maintenance is set to active after upload started. @@ -2172,7 +2182,7 @@ def test_block_get_if_maintenancen_active_after_update_db(client: flask.testing. ) assert response.status_code == http.HTTPStatus.OK # - verify response - assert response.json.get("private") # not testing encryption stuff here + assert response.json.get("private") # not testing encryption stuff here # change_busy_status - busy # - request @@ -2193,9 +2203,7 @@ def test_block_get_if_maintenancen_active_after_update_db(client: flask.testing. # __collect_file_info_remote # - request files = models.File.query.filter_by(project_id=project.id).all() - with patch( - "dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url" - ) as mock_url: + with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: mock_url.return_value = "url" response: werkzeug.test.WrapperTestResponse = client.get( DDSEndpoint.FILE_INFO, @@ -2255,9 +2263,13 @@ def test_block_get_if_maintenancen_active_after_update_db(client: flask.testing. assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() + # block data rm -def test_block_rm_all_if_maintenancen_not_active(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_rm_all_if_maintenancen_not_active( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the remove command uses. Check what happens when maintenance is set to active after upload started. @@ -2344,7 +2356,9 @@ def test_block_rm_all_if_maintenancen_not_active(client: flask.testing.FlaskClie assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_rm_all_if_maintenancen_after_auth(client: flask.testing.FlaskClient, boto3_session) -> None: +def test_block_rm_all_if_maintenancen_after_auth( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the remove command uses. Check what happens when maintenance is set to active after upload started. @@ -2355,7 +2369,7 @@ def test_block_rm_all_if_maintenancen_after_auth(client: flask.testing.FlaskClie project: models.Project = ( models.User.query.filter_by(username="unituser").one_or_none().projects[0] ) - + # Set maintenance to on maintenance: models.Maintenance = models.Maintenance.query.first() maintenance.active = True @@ -2429,7 +2443,10 @@ def test_block_rm_all_if_maintenancen_after_auth(client: flask.testing.FlaskClie assert response.json.get("message") == "Maintenance of DDS is ongoing." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_rm_all_if_maintenancen_active_after_busy(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_rm_all_if_maintenancen_active_after_busy( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the remove command uses. Check what happens when maintenance is set to active after upload started. @@ -2520,7 +2537,10 @@ def test_block_rm_all_if_maintenancen_active_after_busy(client: flask.testing.Fl assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_rm_all_if_maintenancen_active_after_rm(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_rm_all_if_maintenancen_active_after_rm( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the remove command uses. Check what happens when maintenance is set to active after upload started. @@ -2612,7 +2632,10 @@ def test_block_rm_all_if_maintenancen_active_after_rm(client: flask.testing.Flas assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_rm_file_if_maintenancen_after_auth(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_rm_file_if_maintenancen_after_auth( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the remove command uses. Check what happens when maintenance is set to active after upload started. @@ -2623,7 +2646,7 @@ def test_block_rm_file_if_maintenancen_after_auth(client: flask.testing.FlaskCli project: models.Project = ( models.User.query.filter_by(username="unituser").one_or_none().projects[0] ) - + # Set maintenance to on maintenance: models.Maintenance = models.Maintenance.query.first() maintenance.active = True @@ -2682,7 +2705,7 @@ def test_block_rm_file_if_maintenancen_after_auth(client: flask.testing.FlaskCli DDSEndpoint.REMOVE_FILE, headers=token, query_string={"project": project.public_id}, - json=files + json=files, ) assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE assert response.json.get("message") == "Maintenance of DDS is ongoing." @@ -2701,7 +2724,10 @@ def test_block_rm_file_if_maintenancen_after_auth(client: flask.testing.FlaskCli assert response.json.get("message") == "Maintenance of DDS is ongoing." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_rm_file_if_maintenancen_active_after_busy(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_rm_file_if_maintenancen_active_after_busy( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the remove command uses. Check what happens when maintenance is set to active after upload started. @@ -2774,7 +2800,7 @@ def test_block_rm_file_if_maintenancen_active_after_busy(client: flask.testing.F DDSEndpoint.REMOVE_FILE, headers=token, query_string={"project": project.public_id}, - json=files + json=files, ) assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE assert response.json.get("message") == "Maintenance of DDS is ongoing." @@ -2796,7 +2822,10 @@ def test_block_rm_file_if_maintenancen_active_after_busy(client: flask.testing.F assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_rm_file_if_maintenancen_active_after_rm(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_rm_file_if_maintenancen_active_after_rm( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the remove command uses. Check what happens when maintenance is set to active after upload started. @@ -2864,7 +2893,7 @@ def test_block_rm_file_if_maintenancen_active_after_rm(client: flask.testing.Fla DDSEndpoint.REMOVE_FILE, headers=token, query_string={"project": project.public_id}, - json=files + json=files, ) assert response.status_code == http.HTTPStatus.OK assert not models.File.query.filter(models.File.name.in_(files)).all() @@ -2890,7 +2919,10 @@ def test_block_rm_file_if_maintenancen_active_after_rm(client: flask.testing.Fla assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_rm_folder_if_maintenancen_after_auth(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_rm_folder_if_maintenancen_after_auth( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the remove command uses. Check what happens when maintenance is set to active after upload started. @@ -2901,7 +2933,7 @@ def test_block_rm_folder_if_maintenancen_after_auth(client: flask.testing.FlaskC project: models.Project = ( models.User.query.filter_by(username="unituser").one_or_none().projects[0] ) - + # Set maintenance to on maintenance: models.Maintenance = models.Maintenance.query.first() maintenance.active = True @@ -2962,11 +2994,13 @@ def test_block_rm_folder_if_maintenancen_after_auth(client: flask.testing.FlaskC DDSEndpoint.REMOVE_FOLDER, headers=token, query_string={"project": project.public_id}, - json=folders + json=folders, ) assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE assert response.json.get("message") == "Maintenance of DDS is ongoing." - assert models.File.query.filter(models.File.subpath.in_(folders)).count() == len(files_in_folders) + assert models.File.query.filter(models.File.subpath.in_(folders)).count() == len( + files_in_folders + ) # change_busy_status - busy # - request @@ -2981,7 +3015,10 @@ def test_block_rm_folder_if_maintenancen_after_auth(client: flask.testing.FlaskC assert response.json.get("message") == "Maintenance of DDS is ongoing." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_rm_folder_if_maintenancen_active_after_busy(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_rm_folder_if_maintenancen_active_after_busy( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the remove command uses. Check what happens when maintenance is set to active after upload started. @@ -3055,11 +3092,13 @@ def test_block_rm_folder_if_maintenancen_active_after_busy(client: flask.testing DDSEndpoint.REMOVE_FOLDER, headers=token, query_string={"project": project.public_id}, - json=folders + json=folders, ) assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE assert response.json.get("message") == "Maintenance of DDS is ongoing." - assert models.File.query.filter(models.File.subpath.in_(folders)).count() == len(files_in_folders) + assert models.File.query.filter(models.File.subpath.in_(folders)).count() == len( + files_in_folders + ) # change_busy_status - busy # - request @@ -3077,7 +3116,10 @@ def test_block_rm_folder_if_maintenancen_active_after_busy(client: flask.testing assert message == f"Project {project.public_id} was set to not busy." assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() -def test_block_rm_folder_if_maintenancen_active_after_rm(client: flask.testing.FlaskClient, boto3_session) -> None: + +def test_block_rm_folder_if_maintenancen_active_after_rm( + client: flask.testing.FlaskClient, boto3_session +) -> None: """Go through all endpoints that the remove command uses. Check what happens when maintenance is set to active after upload started. @@ -3147,7 +3189,7 @@ def test_block_rm_folder_if_maintenancen_active_after_rm(client: flask.testing.F DDSEndpoint.REMOVE_FOLDER, headers=token, query_string={"project": project.public_id}, - json=folders + json=folders, ) assert response.status_code == http.HTTPStatus.OK assert not models.File.query.filter(models.File.subpath.in_(folders)).all() @@ -3171,4 +3213,4 @@ def test_block_rm_folder_if_maintenancen_active_after_rm(client: flask.testing.F assert busy_status_set message: str = response.json.get("message") assert message == f"Project {project.public_id} was set to not busy." - assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() \ No newline at end of file + assert models.Project.query.filter_by(public_id=project.public_id, busy=False).one_or_none() From a0c51928871b304a9d397c90101190899997b274 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 30 Sep 2022 16:45:31 +0200 Subject: [PATCH 044/123] uncommented commented stuff --- tests/test_init.py | 1508 ++++++++++++++++++++++---------------------- 1 file changed, 754 insertions(+), 754 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 2030815be..a79dee090 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -28,797 +28,797 @@ def mock_commit(): # fill_db_wrapper -# def test_fill_db_wrapper_production(client, runner) -> None: -# """Run init-db with the production argument.""" -# result: click.testing.Result = runner.invoke(fill_db_wrapper, ["production"]) -# assert result.exit_code == 1 +def test_fill_db_wrapper_production(client, runner) -> None: + """Run init-db with the production argument.""" + result: click.testing.Result = runner.invoke(fill_db_wrapper, ["production"]) + assert result.exit_code == 1 + + +def test_fill_db_wrapper_devsmall(client, runner) -> None: + """Run init-db with the dev-small argument.""" + result: click.testing.Result = runner.invoke(fill_db_wrapper, ["dev-small"]) + assert result.exit_code == 1 -# def test_fill_db_wrapper_devsmall(client, runner) -> None: -# """Run init-db with the dev-small argument.""" -# result: click.testing.Result = runner.invoke(fill_db_wrapper, ["dev-small"]) +# def test_fill_db_wrapper_devbig(client, runner) -> None: +# """Run init-db with the dev-big argument.""" +# result: click.testing.Result = runner.invoke(fill_db_wrapper, ["dev-big"]) # assert result.exit_code == 1 -# # def test_fill_db_wrapper_devbig(client, runner) -> None: -# # """Run init-db with the dev-big argument.""" -# # result: click.testing.Result = runner.invoke(fill_db_wrapper, ["dev-big"]) -# # assert result.exit_code == 1 +# create_new_unit -# # create_new_unit +def create_command_options_from_dict(options: typing.Dict) -> typing.List: + """Create a list with options and values from a dict.""" + # Create command options + command_options: typing.List = [] + for key, val in options.items(): + command_options.append(f"--{key}") + command_options.append(val) + return command_options -# def create_command_options_from_dict(options: typing.Dict) -> typing.List: -# """Create a list with options and values from a dict.""" -# # Create command options -# command_options: typing.List = [] -# for key, val in options.items(): -# command_options.append(f"--{key}") -# command_options.append(val) -# return command_options +correct_unit: typing.Dict = { + "name": "newname", + "public_id": "newpublicid", + "external_display_name": "newexternaldisplay", + "contact_email": "newcontact@mail.com", + "internal_ref": "newinternalref", + "safespring_endpoint": "newsafespringendpoint", + "safespring_name": "newsafespringname", + "safespring_access": "newsafespringaccess", + "safespring_secret": "newsafespringsecret", + "days_in_available": 45, + "days_in_expired": 15, +} -# correct_unit: typing.Dict = { -# "name": "newname", -# "public_id": "newpublicid", -# "external_display_name": "newexternaldisplay", -# "contact_email": "newcontact@mail.com", -# "internal_ref": "newinternalref", -# "safespring_endpoint": "newsafespringendpoint", -# "safespring_name": "newsafespringname", -# "safespring_access": "newsafespringaccess", -# "safespring_secret": "newsafespringsecret", -# "days_in_available": 45, -# "days_in_expired": 15, -# } +def test_create_new_unit_public_id_too_long(client, runner) -> None: + """Create new unit, public_id too long.""" + # Change public_id + incorrect_unit: typing.Dict = correct_unit.copy() + incorrect_unit["public_id"] = "public" * 10 + + # Get command options + command_options = create_command_options_from_dict(options=incorrect_unit) + + # Run command + result: click.testing.Result = runner.invoke(create_new_unit, command_options) + # assert "The 'public_id' can be a maximum of 50 characters" in result.output + assert ( + not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() + ) -# def test_create_new_unit_public_id_too_long(client, runner) -> None: -# """Create new unit, public_id too long.""" -# # Change public_id -# incorrect_unit: typing.Dict = correct_unit.copy() -# incorrect_unit["public_id"] = "public" * 10 +def test_create_new_unit_public_id_incorrect_characters(client, runner) -> None: + """Create new unit, public_id has invalid characters (here _).""" + # Change public_id + incorrect_unit: typing.Dict = correct_unit.copy() + incorrect_unit["public_id"] = "new_public_id" -# # Get command options -# command_options = create_command_options_from_dict(options=incorrect_unit) + # Get command options + command_options = create_command_options_from_dict(options=incorrect_unit) -# # Run command -# result: click.testing.Result = runner.invoke(create_new_unit, command_options) -# # assert "The 'public_id' can be a maximum of 50 characters" in result.output -# assert ( -# not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() -# ) + # Run command + result: click.testing.Result = runner.invoke(create_new_unit, command_options) + # assert "The 'public_id' can only contain letters, numbers, dots (.) and hyphens (-)." in result.output + assert ( + not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() + ) -# def test_create_new_unit_public_id_incorrect_characters(client, runner) -> None: -# """Create new unit, public_id has invalid characters (here _).""" -# # Change public_id -# incorrect_unit: typing.Dict = correct_unit.copy() -# incorrect_unit["public_id"] = "new_public_id" +def test_create_new_unit_public_id_starts_with_dot(client, runner) -> None: + """Create new unit, public_id starts with invalid character (. or -).""" + # Change public_id + incorrect_unit: typing.Dict = correct_unit.copy() + incorrect_unit["public_id"] = ".newpublicid" -# # Get command options -# command_options = create_command_options_from_dict(options=incorrect_unit) + # Get command options + command_options = create_command_options_from_dict(options=incorrect_unit) -# # Run command -# result: click.testing.Result = runner.invoke(create_new_unit, command_options) -# # assert "The 'public_id' can only contain letters, numbers, dots (.) and hyphens (-)." in result.output -# assert ( -# not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() -# ) + # Run command + result: click.testing.Result = runner.invoke(create_new_unit, command_options) + # assert "The 'public_id' must begin with a letter or number." in result.output + assert ( + not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() + ) + # Change public_id again + incorrect_unit["public_id"] = "-newpublicid" -# def test_create_new_unit_public_id_starts_with_dot(client, runner) -> None: -# """Create new unit, public_id starts with invalid character (. or -).""" -# # Change public_id -# incorrect_unit: typing.Dict = correct_unit.copy() -# incorrect_unit["public_id"] = ".newpublicid" + # Get command options + command_options = create_command_options_from_dict(options=incorrect_unit) -# # Get command options -# command_options = create_command_options_from_dict(options=incorrect_unit) + # Run command + result: click.testing.Result = runner.invoke(create_new_unit, command_options) + # assert "The 'public_id' must begin with a letter or number." in result.output + assert ( + not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() + ) -# # Run command -# result: click.testing.Result = runner.invoke(create_new_unit, command_options) -# # assert "The 'public_id' must begin with a letter or number." in result.output -# assert ( -# not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() -# ) -# # Change public_id again -# incorrect_unit["public_id"] = "-newpublicid" +def test_create_new_unit_public_id_too_many_dots(client, runner) -> None: + """Create new unit, public_id has invalid number of dots.""" + # Change public_id + incorrect_unit: typing.Dict = correct_unit.copy() + incorrect_unit["public_id"] = "new.public..id" -# # Get command options -# command_options = create_command_options_from_dict(options=incorrect_unit) + # Get command options + command_options = create_command_options_from_dict(options=incorrect_unit) -# # Run command -# result: click.testing.Result = runner.invoke(create_new_unit, command_options) -# # assert "The 'public_id' must begin with a letter or number." in result.output -# assert ( -# not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() -# ) + # Run command + result: click.testing.Result = runner.invoke(create_new_unit, command_options) + # assert "The 'public_id' should not contain more than two dots." in result.output + assert ( + not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() + ) -# def test_create_new_unit_public_id_too_many_dots(client, runner) -> None: -# """Create new unit, public_id has invalid number of dots.""" -# # Change public_id -# incorrect_unit: typing.Dict = correct_unit.copy() -# incorrect_unit["public_id"] = "new.public..id" - -# # Get command options -# command_options = create_command_options_from_dict(options=incorrect_unit) - -# # Run command -# result: click.testing.Result = runner.invoke(create_new_unit, command_options) -# # assert "The 'public_id' should not contain more than two dots." in result.output -# assert ( -# not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() -# ) - - -# def test_create_new_unit_public_id_invalid_start(client, runner) -> None: -# """Create new unit, public_id starts with prefix.""" -# # Change public_id -# incorrect_unit: typing.Dict = correct_unit.copy() -# incorrect_unit["public_id"] = "xn--newpublicid" - -# # Get command options -# command_options = create_command_options_from_dict(options=incorrect_unit) - -# # Run command -# result: click.testing.Result = runner.invoke(create_new_unit, command_options) -# # assert "The 'public_id' cannot begin with the 'xn--' prefix." in result.output -# assert ( -# not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() -# ) - - -# def test_create_new_unit_success(client, runner) -> None: -# """Create new unit, public_id starts with prefix.""" -# # Get command options -# command_options = create_command_options_from_dict(options=correct_unit) - -# with patch("dds_web.db.session.commit", mock_commit): -# # Run command -# result: click.testing.Result = runner.invoke(create_new_unit, command_options) -# # assert f"Unit '{correct_unit['name']}' created" in result.output - - -# # Update uploaded file with log - - -# def mock_no_project(): -# return None +def test_create_new_unit_public_id_invalid_start(client, runner) -> None: + """Create new unit, public_id starts with prefix.""" + # Change public_id + incorrect_unit: typing.Dict = correct_unit.copy() + incorrect_unit["public_id"] = "xn--newpublicid" + # Get command options + command_options = create_command_options_from_dict(options=incorrect_unit) -# def test_update_uploaded_file_with_log_nonexisting_project(client, runner) -> None: -# """Add file info to non existing project.""" -# # Create command options -# command_options: typing.List = [ -# "--project", -# "projectdoesntexist", -# "--path-to-log-file", -# "somefile", -# ] - -# # Run command -# assert db.session.query(models.Project).all() -# with patch("dds_web.database.models.Project.query.filter_by", mock_no_project): -# result: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) -# assert result.exit_code == 1 + # Run command + result: click.testing.Result = runner.invoke(create_new_unit, command_options) + # assert "The 'public_id' cannot begin with the 'xn--' prefix." in result.output + assert ( + not db.session.query(models.Unit).filter(models.Unit.name == incorrect_unit["name"]).all() + ) -# def test_update_uploaded_file_with_log_nonexisting_file(client, runner, fs: FakeFilesystem) -> None: -# """Attempt to read file which does not exist.""" -# # Verify that fake file does not exist -# non_existent_log_file: str = "this_is_not_a_file.json" -# assert not os.path.exists(non_existent_log_file) +def test_create_new_unit_success(client, runner) -> None: + """Create new unit, public_id starts with prefix.""" + # Get command options + command_options = create_command_options_from_dict(options=correct_unit) -# # Create command options -# command_options: typing.List = [ -# "--project", -# "projectdoesntexist", -# "--path-to-log-file", -# non_existent_log_file, -# ] + with patch("dds_web.db.session.commit", mock_commit): + # Run command + result: click.testing.Result = runner.invoke(create_new_unit, command_options) + # assert f"Unit '{correct_unit['name']}' created" in result.output -# # Run command -# result: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) -# assert result.exit_code == 1 + +# Update uploaded file with log + + +def mock_no_project(): + return None + + +def test_update_uploaded_file_with_log_nonexisting_project(client, runner) -> None: + """Add file info to non existing project.""" + # Create command options + command_options: typing.List = [ + "--project", + "projectdoesntexist", + "--path-to-log-file", + "somefile", + ] + + # Run command + assert db.session.query(models.Project).all() + with patch("dds_web.database.models.Project.query.filter_by", mock_no_project): + result: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) + assert result.exit_code == 1 + + +def test_update_uploaded_file_with_log_nonexisting_file(client, runner, fs: FakeFilesystem) -> None: + """Attempt to read file which does not exist.""" + # Verify that fake file does not exist + non_existent_log_file: str = "this_is_not_a_file.json" + assert not os.path.exists(non_existent_log_file) + + # Create command options + command_options: typing.List = [ + "--project", + "projectdoesntexist", + "--path-to-log-file", + non_existent_log_file, + ] + + # Run command + result: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) + assert result.exit_code == 1 + + +# block_if_maintenance - should be blocked in init by before_request + + +def test_block_if_maintenance_active_encryptedtoken_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try encrypted token - "/user/encrypted_token" + with patch.object(flask_mail.Mail, "send") as mock_mail_send: + response = client.get( + DDSEndpoint.ENCRYPTED_TOKEN, + auth=("researchuser", "password"), + headers=DEFAULT_HEADER, + ) + assert mock_mail_send.call_count == 0 + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_secondfactor_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try second factor - "/user/second_factor" + response = client.get( + DDSEndpoint.SECOND_FACTOR, + headers={"Authorization": f"Bearer made.up.token.long.version", **DEFAULT_HEADER}, + json={"TOTP": "somrthing"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_s3proj_not_approved(client: flask.testing.FlaskClient) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try s3info - "/s3/proj" + response = client.get( + DDSEndpoint.S3KEYS, + headers=DEFAULT_HEADER, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_fileslist_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try list files - "/files/list" + response = client.get( + DDSEndpoint.LIST_FILES, + headers=token, + query_string={"project": "public_project_id"}, + json={"show_size": True}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_filematch_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/file/match" + response = client.get( + DDSEndpoint.FILE_MATCH, + headers=token, + query_string={"project": "file_testing_project"}, + json=["non_existent_file"], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_removefile_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # Try remove file - "/file/rm" + from tests.test_files_new import FIRST_NEW_FILE + + response = client.delete( + DDSEndpoint.REMOVE_FILE, + headers=token, + query_string={"project": "file_testing_project"}, + json=[FIRST_NEW_FILE["name"]], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_removedir_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/file/rmdir" + from tests.test_files_new import FIRST_NEW_FILE + + response = client.delete( + DDSEndpoint.REMOVE_FOLDER, + headers=token, + query_string={"project": "file_testing_project"}, + json=[FIRST_NEW_FILE["subpath"]], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_fileinfo_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/file/info" + with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: + mock_url.return_value = "url" + response = client.get( + DDSEndpoint.FILE_INFO, + headers=token, + query_string={"project": "public_project_id"}, + json=["filename1"], + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_fileallinfo_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/file/all/info" + with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: + mock_url.return_value = "url" + response = client.get( + DDSEndpoint.FILE_INFO_ALL, + headers=token, + query_string={"project": "public_project_id"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE -# # block_if_maintenance - should be blocked in init by before_request - - -# def test_block_if_maintenance_active_encryptedtoken_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # Try encrypted token - "/user/encrypted_token" -# with patch.object(flask_mail.Mail, "send") as mock_mail_send: -# response = client.get( -# DDSEndpoint.ENCRYPTED_TOKEN, -# auth=("researchuser", "password"), -# headers=DEFAULT_HEADER, -# ) -# assert mock_mail_send.call_count == 0 -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_secondfactor_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # Try second factor - "/user/second_factor" -# response = client.get( -# DDSEndpoint.SECOND_FACTOR, -# headers={"Authorization": f"Bearer made.up.token.long.version", **DEFAULT_HEADER}, -# json={"TOTP": "somrthing"}, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_s3proj_not_approved(client: flask.testing.FlaskClient) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # Try s3info - "/s3/proj" -# response = client.get( -# DDSEndpoint.S3KEYS, -# headers=DEFAULT_HEADER, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_fileslist_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # Try list files - "/files/list" -# response = client.get( -# DDSEndpoint.LIST_FILES, -# headers=token, -# query_string={"project": "public_project_id"}, -# json={"show_size": True}, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_filematch_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/file/match" -# response = client.get( -# DDSEndpoint.FILE_MATCH, -# headers=token, -# query_string={"project": "file_testing_project"}, -# json=["non_existent_file"], -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_removefile_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # Try remove file - "/file/rm" -# from tests.test_files_new import FIRST_NEW_FILE - -# response = client.delete( -# DDSEndpoint.REMOVE_FILE, -# headers=token, -# query_string={"project": "file_testing_project"}, -# json=[FIRST_NEW_FILE["name"]], -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_removedir_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/file/rmdir" -# from tests.test_files_new import FIRST_NEW_FILE - -# response = client.delete( -# DDSEndpoint.REMOVE_FOLDER, -# headers=token, -# query_string={"project": "file_testing_project"}, -# json=[FIRST_NEW_FILE["subpath"]], -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_fileinfo_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/file/info" -# with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: -# mock_url.return_value = "url" -# response = client.get( -# DDSEndpoint.FILE_INFO, -# headers=token, -# query_string={"project": "public_project_id"}, -# json=["filename1"], -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_fileallinfo_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/file/all/info" -# with patch("dds_web.api.api_s3_connector.ApiS3Connector.generate_get_url") as mock_url: -# mock_url.return_value = "url" -# response = client.get( -# DDSEndpoint.FILE_INFO_ALL, -# headers=token, -# query_string={"project": "public_project_id"}, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_projectlist_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/proj/list" -# response = client.get( -# DDSEndpoint.LIST_PROJ, -# headers=token, -# json={"usage": True}, -# content_type="application/json", -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_removeprojectcontents_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/proj/rm" -# response = client.delete( -# DDSEndpoint.REMOVE_PROJ_CONT, -# headers=token, -# query_string={"project": "file_testing_project"}, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_projectpublic_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/proj/public" -# response = client.get( -# DDSEndpoint.PROJ_PUBLIC, query_string={"project": "public_project_id"}, headers=token -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_projectprivate_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/proj/private" -# response = client.get( -# DDSEndpoint.PROJ_PRIVATE, -# query_string={"project": "public_project_id"}, -# headers=token, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_createproject_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/proj/create" -# from tests.test_project_creation import proj_data - -# response = client.post( -# DDSEndpoint.PROJECT_CREATE, -# headers=token, -# json=proj_data, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_projectusers_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/proj/users" -# response = client.get( -# DDSEndpoint.LIST_PROJ_USERS, query_string={"project": "public_project_id"}, headers=token -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_projectstatus_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/proj/status" -# response = client.post( -# DDSEndpoint.PROJECT_STATUS, -# headers=token, -# query_string={"project": "public_project_id"}, -# json={"new_status": "Available"}, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_projectaccess_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/proj/access" -# response = client.post( -# DDSEndpoint.PROJECT_ACCESS, -# headers=token, -# query_string={"project": "public_project_id"}, -# json={"email": "unituser1@mailtrap.io"}, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_adduser_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/user/add" -# from tests.api.test_user import first_new_user - -# response = client.post( -# DDSEndpoint.USER_ADD, -# headers=token, -# json=first_new_user, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_deleteuser_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/user/delete" -# invited_user_row = models.Invite.query.first() -# response = client.delete( -# DDSEndpoint.USER_DELETE, -# headers=token, -# json={"email": invited_user_row.email, "is_invite": True}, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_deleteself_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["delete_me_researcher"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/user/delete_self" -# with patch.object(flask_mail.Mail, "send") as mock_mail_send: -# response = client.delete( -# DDSEndpoint.USER_DELETE_SELF, -# headers=token, -# json=None, -# ) -# # One email for partial token but no new for deletion confirmation -# assert mock_mail_send.call_count == 0 -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_revokeaccess_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/user/access/revoke" -# from tests.test_project_creation import proj_data_with_existing_users - -# email = proj_data_with_existing_users["users_to_add"][0]["email"] -# response = client.post( -# DDSEndpoint.REMOVE_USER_FROM_PROJ, -# headers=token, -# query_string={"project": "public_project_id"}, -# json={"email": email}, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_useractivation_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/user/activation" -# from tests.test_user_activation import unituser - -# response = client.post( -# DDSEndpoint.USER_ACTIVATION, -# headers=token, -# json={**unituser, "action": "reactivate"}, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_hotp_not_approved(client: flask.testing.FlaskClient) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["researcher"]).as_tuple() - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/user/hotp/activate" -# response = client.post( -# DDSEndpoint.HOTP_ACTIVATION, -# headers=DEFAULT_HEADER, -# auth=token, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_totp_not_approved(client: flask.testing.FlaskClient) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/user/totp/activate" -# response = client.post( -# DDSEndpoint.TOTP_ACTIVATION, -# headers=token, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_listusers_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/users" -# response = client.get(DDSEndpoint.LIST_USERS, headers=token) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_finduser_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/user/find" -# response: werkzeug.test.WrapperTestResponse = client.get( -# DDSEndpoint.USER_FIND, headers=token, json={"username": models.User.query.first().username} -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_deactivatetotp_not_approved( -# client: flask.testing.FlaskClient, -# ) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/user/totp/deactivate" -# response: werkzeug.test.WrapperTestResponse = client.put( -# DDSEndpoint.TOTP_DEACTIVATE, -# headers=token, -# json={"username": models.User.query.first().username}, -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE - - -# def test_block_if_maintenance_active_usage_not_approved(client: flask.testing.FlaskClient) -> None: -# """Certain endpoints should be blocked if maintenance is not active.""" -# # Auth before maintenance on -# token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) - -# # Get maintenance row and set to active -# maintenance: models.Maintenance = models.Maintenance.query.first() -# maintenance.active = True -# db.session.commit() - -# # "/usage" -# response = client.get( -# DDSEndpoint.USAGE, -# headers=token, -# content_type="application/json", -# ) -# assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE +def test_block_if_maintenance_active_projectlist_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/list" + response = client.get( + DDSEndpoint.LIST_PROJ, + headers=token, + json={"usage": True}, + content_type="application/json", + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_removeprojectcontents_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/rm" + response = client.delete( + DDSEndpoint.REMOVE_PROJ_CONT, + headers=token, + query_string={"project": "file_testing_project"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectpublic_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/public" + response = client.get( + DDSEndpoint.PROJ_PUBLIC, query_string={"project": "public_project_id"}, headers=token + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectprivate_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/private" + response = client.get( + DDSEndpoint.PROJ_PRIVATE, + query_string={"project": "public_project_id"}, + headers=token, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_createproject_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/create" + from tests.test_project_creation import proj_data + + response = client.post( + DDSEndpoint.PROJECT_CREATE, + headers=token, + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectusers_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/users" + response = client.get( + DDSEndpoint.LIST_PROJ_USERS, query_string={"project": "public_project_id"}, headers=token + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectstatus_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/status" + response = client.post( + DDSEndpoint.PROJECT_STATUS, + headers=token, + query_string={"project": "public_project_id"}, + json={"new_status": "Available"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_projectaccess_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/proj/access" + response = client.post( + DDSEndpoint.PROJECT_ACCESS, + headers=token, + query_string={"project": "public_project_id"}, + json={"email": "unituser1@mailtrap.io"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_adduser_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/add" + from tests.api.test_user import first_new_user + + response = client.post( + DDSEndpoint.USER_ADD, + headers=token, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_deleteuser_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/delete" + invited_user_row = models.Invite.query.first() + response = client.delete( + DDSEndpoint.USER_DELETE, + headers=token, + json={"email": invited_user_row.email, "is_invite": True}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_deleteself_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["delete_me_researcher"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/delete_self" + with patch.object(flask_mail.Mail, "send") as mock_mail_send: + response = client.delete( + DDSEndpoint.USER_DELETE_SELF, + headers=token, + json=None, + ) + # One email for partial token but no new for deletion confirmation + assert mock_mail_send.call_count == 0 + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_revokeaccess_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/access/revoke" + from tests.test_project_creation import proj_data_with_existing_users + + email = proj_data_with_existing_users["users_to_add"][0]["email"] + response = client.post( + DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=token, + query_string={"project": "public_project_id"}, + json={"email": email}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_useractivation_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/activation" + from tests.test_user_activation import unituser + + response = client.post( + DDSEndpoint.USER_ACTIVATION, + headers=token, + json={**unituser, "action": "reactivate"}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_hotp_not_approved(client: flask.testing.FlaskClient) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["researcher"]).as_tuple() + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/hotp/activate" + response = client.post( + DDSEndpoint.HOTP_ACTIVATION, + headers=DEFAULT_HEADER, + auth=token, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_totp_not_approved(client: flask.testing.FlaskClient) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/totp/activate" + response = client.post( + DDSEndpoint.TOTP_ACTIVATION, + headers=token, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_listusers_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/users" + response = client.get(DDSEndpoint.LIST_USERS, headers=token) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_finduser_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/find" + response: werkzeug.test.WrapperTestResponse = client.get( + DDSEndpoint.USER_FIND, headers=token, json={"username": models.User.query.first().username} + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_deactivatetotp_not_approved( + client: flask.testing.FlaskClient, +) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/user/totp/deactivate" + response: werkzeug.test.WrapperTestResponse = client.put( + DDSEndpoint.TOTP_DEACTIVATE, + headers=token, + json={"username": models.User.query.first().username}, + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + + +def test_block_if_maintenance_active_usage_not_approved(client: flask.testing.FlaskClient) -> None: + """Certain endpoints should be blocked if maintenance is not active.""" + # Auth before maintenance on + token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) + + # Get maintenance row and set to active + maintenance: models.Maintenance = models.Maintenance.query.first() + maintenance.active = True + db.session.commit() + + # "/usage" + response = client.get( + DDSEndpoint.USAGE, + headers=token, + content_type="application/json", + ) + assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE # block data put From d6af15ff3fac0710c02631cde6f14617c0e13c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Fri, 30 Sep 2022 16:47:12 +0200 Subject: [PATCH 045/123] Update dds_web/errors.py --- dds_web/errors.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dds_web/errors.py b/dds_web/errors.py index c35697286..ee82fedbf 100644 --- a/dds_web/errors.py +++ b/dds_web/errors.py @@ -432,4 +432,3 @@ class MaintenanceOngoingException(LoggedHTTPException): def __init__(self, message="Maintenance of DDS is ongoing."): """Inform that maintenance is ongoing.""" super().__init__(message) - general_logger.warning(message) From f62f9a149b3f9764360e2fc7f8724c58b8d763f4 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 30 Sep 2022 16:51:27 +0200 Subject: [PATCH 046/123] comments for simpler reading of function --- dds_web/utils.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dds_web/utils.py b/dds_web/utils.py index 5cb79e126..03c092781 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -577,8 +577,12 @@ def calculate_version_period_usage(version): # maintenance check def block_if_maintenance(): """Block API requests if maintenance is ongoing and projects are busy.""" + # Get maintenance row maintenance: models.Maintenance = models.Maintenance.query.first() + + # Possibly block request if maintenance ongoing / planned if maintenance.active: + # Endpoints accepting requests during active maintenance / planned project_required_endpoints: typing.List = [ f"/api/v1{resource}" for resource in ["/file/new", "/file/update", "/proj/busy"] ] @@ -594,21 +598,22 @@ def block_if_maintenance(): ] ] approved_endpoints: typing.List = project_required_endpoints + admin_endpoints - flask.current_app.logger.debug(project_required_endpoints) - flask.current_app.logger.debug(admin_endpoints) + # Request not to accepted endpoint # OR request to accepted endpoint but project not specified or busy current_endpoint: str = flask.request.path if current_endpoint not in approved_endpoints: - flask.current_app.logger.debug(f"no it's not there - {flask.request.path}") + # Request not accepted during maintenance raise MaintenanceOngoingException() else: - flask.current_app.logger.debug(f"yes it's there - {flask.request.path}") + # Request accepted during maintenance but... req_args = flask.request.args - if flask.request.path in project_required_endpoints: + if current_endpoint in project_required_endpoints: + # Request requires a project if not (req_args and (project_id := req_args.get("project"))): raise MaintenanceOngoingException() + # Request requires an busy project if not models.Project.query.filter_by( public_id=project_id, busy=True ).one_or_none(): From f315c2b4688916fa9c5fc433c667ed0cb00850db Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 30 Sep 2022 16:51:37 +0200 Subject: [PATCH 047/123] black --- tests/test_init.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_init.py b/tests/test_init.py index a79dee090..108a9582e 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -820,6 +820,7 @@ def test_block_if_maintenance_active_usage_not_approved(client: flask.testing.Fl ) assert response.status_code == http.HTTPStatus.SERVICE_UNAVAILABLE + # block data put From b794f7e52d32299d680a4bd50ecce26c7aa5d937 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 3 Oct 2022 15:24:00 +0200 Subject: [PATCH 048/123] update PR template and changelog info --- .github/pull_request_template.md | 3 ++- CHANGELOG.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 78d9bcc1c..2f77a630b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -17,12 +17,13 @@ - [ ] Bug fix (non-breaking) - [ ] New feature (non-breaking) - [ ] Breaking change (breaking, will cause existing functionality to not work as expected) +- [ ] Tests (only) # Checklist: ## General -- [ ] [Changelog](../CHANGELOG.md): New row added +- [ ] [Changelog](../CHANGELOG.md): New row added. Not needed when PR includes _only_ tests. - [ ] Database schema has changed - [ ] A new migration is included in the PR - [ ] The change does not require a migration diff --git a/CHANGELOG.md b/CHANGELOG.md index 253cd3fcb..9cbff3436 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Data Delivery System Web / API: Changelog -Please add a _short_ line describing the PR you make, if the PR implements a specific feature or functionality, or refactor. Not needed if you add very small and unnoticable changes. +Please add a _short_ line describing the PR you make, if the PR implements a specific feature or functionality, or refactor. Not needed if you add very small and unnoticable changes. Not needed when PR includes _only_ tests for already existing feature. ## Sprint (2022-02-09 - 2022-02-23) From cb80f4d1899d41bc09e0120c726a15d17e75fc33 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 5 Oct 2022 09:37:51 +0200 Subject: [PATCH 049/123] display different messages to different users and remove footer --- dds_web/__init__.py | 1 + dds_web/templates/404.html | 2 +- dds_web/templates/technical_overview.html | 16 +++++++--------- dds_web/web/root.py | 1 + 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index d0c6be831..daa061df4 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -208,6 +208,7 @@ def prepare(): elif flask_login.current_user.is_authenticated: flask.g.current_user = flask_login.current_user.username flask.g.current_user_emails = flask_login.current_user.emails + flask.g.role = flask_login.current_user.role elif flask.request.authorization: flask.g.current_user = flask.request.authorization.get("username") flask.g.current_user_emails = flask.request.authorization.get("emails") diff --git a/dds_web/templates/404.html b/dds_web/templates/404.html index f66cf9bcf..d77a5238a 100644 --- a/dds_web/templates/404.html +++ b/dds_web/templates/404.html @@ -12,7 +12,7 @@

You can get back to the homepage here. - If in doubt, please get in touch with the SciLifeLab Data Centre. + If in doubt, please get in touch with {{ 'the SciLifeLab Data Centre (datacentre@scilifelab.se)' if g.current_user and g.role and g.role != 'Researcher' else 'support'}}.

{% endblock %} diff --git a/dds_web/templates/technical_overview.html b/dds_web/templates/technical_overview.html index 07ee271f9..513d24986 100644 --- a/dds_web/templates/technical_overview.html +++ b/dds_web/templates/technical_overview.html @@ -1,19 +1,17 @@ {% extends "base.html" %} {% block body %} + +{% if g.current_user and g.role and g.role in ["Unit Admin", "Unit Personnel", "Super Admin"] %} + {% set contact = "datacentre@scilifelab.se" %} +{% else %} + {% set contact = "" %} +{% endif %} +

Technical Overview of the Data Delivery System

The technical overview provides an overview of how the DDS works and is meant to be used. The document can be found on GitHub or downloaded as a PDF.

Read on GitHub Download the PDF 


-
-
-
-
-

The Data Delivery System is developed and maintained by the SciLifeLab Data Centre. With any questions please email datacentre@scilifelab.se.

-
-
-
-
{% endblock %} diff --git a/dds_web/web/root.py b/dds_web/web/root.py index 57e644cab..5fccb43ce 100644 --- a/dds_web/web/root.py +++ b/dds_web/web/root.py @@ -23,6 +23,7 @@ def home(): """Home page.""" form = forms.LoginForm() + flask.abort(404) return render_template("home.html", form=form) From 9a5c4778f9986ce91fe443c6105b064bce636bae Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 5 Oct 2022 09:43:01 +0200 Subject: [PATCH 050/123] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cbff3436..91fdded70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -151,3 +151,7 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - New table: `Maintenance`, for keeping track of DDS maintenance mode ([#1284](https://github.com/ScilifelabDataCentre/dds_web/pull/1284)) - New endpoint: SetMaintenance - set maintenance mode to on or off ([#1286](https://github.com/ScilifelabDataCentre/dds_web/pull/1286)) - New endpoint: AnyProjectsBusy - check if any projects are busy in DDS ([#1288](https://github.com/ScilifelabDataCentre/dds_web/pull/1288)) + +## Sprint (2022-09-30 - 2022-10-14) + +- Display different information on who to contact depending on user role ([#1292](https://github.com/ScilifelabDataCentre/dds_web/pull/1292)) From e28b73b470edfae697a388878a01c160fa89629a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Wed, 5 Oct 2022 09:46:16 +0200 Subject: [PATCH 051/123] Update dds_web/web/root.py --- dds_web/web/root.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dds_web/web/root.py b/dds_web/web/root.py index 5fccb43ce..57e644cab 100644 --- a/dds_web/web/root.py +++ b/dds_web/web/root.py @@ -23,7 +23,6 @@ def home(): """Home page.""" form = forms.LoginForm() - flask.abort(404) return render_template("home.html", form=form) From adeb2466fa62096742eb3d5e9a59a3854526a7df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Fri, 30 Sep 2022 12:31:25 +0200 Subject: [PATCH 052/123] Fix project listing in invites --- dds_web/database/models.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dds_web/database/models.py b/dds_web/database/models.py index 8569a21b8..354100005 100644 --- a/dds_web/database/models.py +++ b/dds_web/database/models.py @@ -835,8 +835,11 @@ class Invite(db.Model): @property def projects(self): """Return list of project items.""" - - return [proj.project for proj in self.project_associations] + if self.project_invite_keys: + projects = [proj.project for proj in self.project_invite_keys] + else: + projects = None + return projects def __str__(self): """Called by str(), creates representation of object""" From 95b59a759c4c9a3e20914692aa3e482591535e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Fri, 30 Sep 2022 12:42:34 +0200 Subject: [PATCH 053/123] Add entry in changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cbff3436..b17e5088b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -151,3 +151,4 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - New table: `Maintenance`, for keeping track of DDS maintenance mode ([#1284](https://github.com/ScilifelabDataCentre/dds_web/pull/1284)) - New endpoint: SetMaintenance - set maintenance mode to on or off ([#1286](https://github.com/ScilifelabDataCentre/dds_web/pull/1286)) - New endpoint: AnyProjectsBusy - check if any projects are busy in DDS ([#1288](https://github.com/ScilifelabDataCentre/dds_web/pull/1288)) +- Bug fix: Fix the Invite.projects database model ([#1290](https://github.com/ScilifelabDataCentre/dds_web/pull/1290)) From f6fcbaa2ac3703167487bc7f081a671c1efdefb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Fri, 30 Sep 2022 12:43:37 +0200 Subject: [PATCH 054/123] Use empty list instead of None to make it easier to use --- dds_web/database/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/database/models.py b/dds_web/database/models.py index 354100005..15a7ccc1f 100644 --- a/dds_web/database/models.py +++ b/dds_web/database/models.py @@ -838,7 +838,7 @@ def projects(self): if self.project_invite_keys: projects = [proj.project for proj in self.project_invite_keys] else: - projects = None + projects = [] return projects def __str__(self): From 45f56429929bdf912642093cdafb16a9925416b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Wed, 5 Oct 2022 12:29:21 +0200 Subject: [PATCH 055/123] Remove unused file --- tests/test_db_constraints.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/test_db_constraints.py diff --git a/tests/test_db_constraints.py b/tests/test_db_constraints.py deleted file mode 100644 index e69de29bb..000000000 From 8f608ca117f5eaca7a517bb26fa8be42f00f289e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Wed, 5 Oct 2022 13:58:39 +0200 Subject: [PATCH 056/123] Add test --- tests/test_models.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_models.py b/tests/test_models.py index 651e6b23d..b66507727 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -436,6 +436,24 @@ def __setup_invite(unit_name, invite_email): return invite +def test_invite_custom_properties(client): + """Confirm that the custom model properties are functional.""" + unit_name = "Unit 1" + invite_email = "proj_invite@example.com" + invite = __setup_invite(unit_name, invite_email) + + project = models.Project.query.first() + project_invite = models.ProjectInviteKeys(invite=invite, project=project, key="asd".encode()) + + project.project_invite_keys.append(project_invite) + + assert invite.projects == [project] + + db.session.delete(invite) + db.session.delete(project_invite) + db.session.commit() + + def test_delete_invite(client): """ Invite row deleted From bc52e5b427769ea61989b6a7b5d9c4b52c118e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Wed, 5 Oct 2022 14:55:01 +0200 Subject: [PATCH 057/123] Remove unneeded lines --- tests/test_models.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index b66507727..f1bef7a2a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -444,15 +444,8 @@ def test_invite_custom_properties(client): project = models.Project.query.first() project_invite = models.ProjectInviteKeys(invite=invite, project=project, key="asd".encode()) - - project.project_invite_keys.append(project_invite) - assert invite.projects == [project] - db.session.delete(invite) - db.session.delete(project_invite) - db.session.commit() - def test_delete_invite(client): """ From 88a3cd581ab052b9a97f456f78f37de80954c1a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Thu, 6 Oct 2022 21:37:44 +0200 Subject: [PATCH 058/123] Add concurrency checks on codeql, docker/trivy, and testing --- .github/workflows/codeql-analysis.yml | 4 +++- .github/workflows/docker-compose-tests.yml | 3 +++ .github/workflows/publish_and_trivyscan.yml | 7 +++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 27b5af07d..fbeb0ab63 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -28,7 +28,9 @@ jobs: actions: read contents: read security-events: write - + concurrency: + group: ${{ github.ref }}-codeql + cancel-in-progress: true strategy: fail-fast: false matrix: diff --git a/.github/workflows/docker-compose-tests.yml b/.github/workflows/docker-compose-tests.yml index 276c2fade..5f6444ac4 100644 --- a/.github/workflows/docker-compose-tests.yml +++ b/.github/workflows/docker-compose-tests.yml @@ -8,6 +8,9 @@ on: jobs: pytest: + concurrency: + group: ${{ github.ref }}-pytest + cancel-in-progress: true runs-on: ubuntu-latest steps: diff --git a/.github/workflows/publish_and_trivyscan.yml b/.github/workflows/publish_and_trivyscan.yml index f3d4249a3..adc809c42 100644 --- a/.github/workflows/publish_and_trivyscan.yml +++ b/.github/workflows/publish_and_trivyscan.yml @@ -13,6 +13,13 @@ jobs: if: github.repository == 'ScilifelabDataCentre/dds_web' name: Push Docker image to Docker Hub runs-on: ubuntu-latest + permissions: + contents: read + packages: write + security-events: write + concurrency: + group: ${{ github.ref }}-docker-trivy + cancel-in-progress: true steps: - name: Check out the repo uses: actions/checkout@v2 From 6219f9ccfea0b193f8cf12914d7f37ba5400e40e Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 7 Oct 2022 12:05:51 +0200 Subject: [PATCH 059/123] prettier --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 671789977..866ca471e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -153,5 +153,6 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - New endpoint: AnyProjectsBusy - check if any projects are busy in DDS ([#1288](https://github.com/ScilifelabDataCentre/dds_web/pull/1288)) ## Sprint (2022-09-30 - 2022-10-14) + - Bug fix: Fix the Invite.projects database model ([#1290](https://github.com/ScilifelabDataCentre/dds_web/pull/1290)) - Display different information on who to contact depending on user role ([#1292](https://github.com/ScilifelabDataCentre/dds_web/pull/1292)) From 4a7c0f76f5048aba0c8775ce07695783f272f1b3 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 10 Oct 2022 08:48:49 +0200 Subject: [PATCH 060/123] fix confusing docstrings after review --- dds_web/utils.py | 2 +- tests/test_init.py | 56 +++++++++++++++++++++++----------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/dds_web/utils.py b/dds_web/utils.py index 03c092781..5adb0e3af 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -613,7 +613,7 @@ def block_if_maintenance(): if not (req_args and (project_id := req_args.get("project"))): raise MaintenanceOngoingException() - # Request requires an busy project + # Request requires a busy project if not models.Project.query.filter_by( public_id=project_id, busy=True ).one_or_none(): diff --git a/tests/test_init.py b/tests/test_init.py index 108a9582e..b255ae17c 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -233,7 +233,7 @@ def test_update_uploaded_file_with_log_nonexisting_file(client, runner, fs: Fake def test_block_if_maintenance_active_encryptedtoken_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Get maintenance row and set to active maintenance: models.Maintenance = models.Maintenance.query.first() maintenance.active = True @@ -253,7 +253,7 @@ def test_block_if_maintenance_active_encryptedtoken_not_approved( def test_block_if_maintenance_active_secondfactor_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Requests with wrong 2FA should be blocked if maintenance is active.""" # Get maintenance row and set to active maintenance: models.Maintenance = models.Maintenance.query.first() maintenance.active = True @@ -269,7 +269,7 @@ def test_block_if_maintenance_active_secondfactor_not_approved( def test_block_if_maintenance_active_s3proj_not_approved(client: flask.testing.FlaskClient) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """More requests to be blocked if maintenance is active.""" # Get maintenance row and set to active maintenance: models.Maintenance = models.Maintenance.query.first() maintenance.active = True @@ -286,7 +286,7 @@ def test_block_if_maintenance_active_s3proj_not_approved(client: flask.testing.F def test_block_if_maintenance_active_fileslist_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """More requests to be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) @@ -308,7 +308,7 @@ def test_block_if_maintenance_active_fileslist_not_approved( def test_block_if_maintenance_active_filematch_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """More requests to be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) @@ -330,7 +330,7 @@ def test_block_if_maintenance_active_filematch_not_approved( def test_block_if_maintenance_active_removefile_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) @@ -354,7 +354,7 @@ def test_block_if_maintenance_active_removefile_not_approved( def test_block_if_maintenance_active_removedir_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) @@ -378,7 +378,7 @@ def test_block_if_maintenance_active_removedir_not_approved( def test_block_if_maintenance_active_fileinfo_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) @@ -402,7 +402,7 @@ def test_block_if_maintenance_active_fileinfo_not_approved( def test_block_if_maintenance_active_fileallinfo_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["researchuser"]).token(client) @@ -425,7 +425,7 @@ def test_block_if_maintenance_active_fileallinfo_not_approved( def test_block_if_maintenance_active_projectlist_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) @@ -447,7 +447,7 @@ def test_block_if_maintenance_active_projectlist_not_approved( def test_block_if_maintenance_active_removeprojectcontents_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) @@ -468,7 +468,7 @@ def test_block_if_maintenance_active_removeprojectcontents_not_approved( def test_block_if_maintenance_active_projectpublic_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) @@ -487,7 +487,7 @@ def test_block_if_maintenance_active_projectpublic_not_approved( def test_block_if_maintenance_active_projectprivate_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) @@ -508,7 +508,7 @@ def test_block_if_maintenance_active_projectprivate_not_approved( def test_block_if_maintenance_active_createproject_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) @@ -531,7 +531,7 @@ def test_block_if_maintenance_active_createproject_not_approved( def test_block_if_maintenance_active_projectusers_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) @@ -550,7 +550,7 @@ def test_block_if_maintenance_active_projectusers_not_approved( def test_block_if_maintenance_active_projectstatus_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) @@ -572,7 +572,7 @@ def test_block_if_maintenance_active_projectstatus_not_approved( def test_block_if_maintenance_active_projectaccess_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) @@ -594,7 +594,7 @@ def test_block_if_maintenance_active_projectaccess_not_approved( def test_block_if_maintenance_active_adduser_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) @@ -617,7 +617,7 @@ def test_block_if_maintenance_active_adduser_not_approved( def test_block_if_maintenance_active_deleteuser_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) @@ -639,7 +639,7 @@ def test_block_if_maintenance_active_deleteuser_not_approved( def test_block_if_maintenance_active_deleteself_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["delete_me_researcher"]).token(client) @@ -663,7 +663,7 @@ def test_block_if_maintenance_active_deleteself_not_approved( def test_block_if_maintenance_active_revokeaccess_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) @@ -688,7 +688,7 @@ def test_block_if_maintenance_active_revokeaccess_not_approved( def test_block_if_maintenance_active_useractivation_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unitadmin"]).token(client) @@ -709,7 +709,7 @@ def test_block_if_maintenance_active_useractivation_not_approved( def test_block_if_maintenance_active_hotp_not_approved(client: flask.testing.FlaskClient) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["researcher"]).as_tuple() @@ -728,7 +728,7 @@ def test_block_if_maintenance_active_hotp_not_approved(client: flask.testing.Fla def test_block_if_maintenance_active_totp_not_approved(client: flask.testing.FlaskClient) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) @@ -748,7 +748,7 @@ def test_block_if_maintenance_active_totp_not_approved(client: flask.testing.Fla def test_block_if_maintenance_active_listusers_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) @@ -765,7 +765,7 @@ def test_block_if_maintenance_active_listusers_not_approved( def test_block_if_maintenance_active_finduser_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) @@ -784,7 +784,7 @@ def test_block_if_maintenance_active_finduser_not_approved( def test_block_if_maintenance_active_deactivatetotp_not_approved( client: flask.testing.FlaskClient, ) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["superadmin"]).token(client) @@ -803,7 +803,7 @@ def test_block_if_maintenance_active_deactivatetotp_not_approved( def test_block_if_maintenance_active_usage_not_approved(client: flask.testing.FlaskClient) -> None: - """Certain endpoints should be blocked if maintenance is not active.""" + """Certain endpoints should be blocked if maintenance is active.""" # Auth before maintenance on token = UserAuth(USER_CREDENTIALS["unituser"]).token(client) From 8be4df4eacd91d65bae8295318eda51cdcabadcf Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 10 Oct 2022 10:45:40 +0200 Subject: [PATCH 061/123] unneccesary variable --- dds_web/templates/technical_overview.html | 7 ------- 1 file changed, 7 deletions(-) diff --git a/dds_web/templates/technical_overview.html b/dds_web/templates/technical_overview.html index 513d24986..b9a149ae3 100644 --- a/dds_web/templates/technical_overview.html +++ b/dds_web/templates/technical_overview.html @@ -1,13 +1,6 @@ {% extends "base.html" %} {% block body %} - -{% if g.current_user and g.role and g.role in ["Unit Admin", "Unit Personnel", "Super Admin"] %} - {% set contact = "datacentre@scilifelab.se" %} -{% else %} - {% set contact = "" %} -{% endif %} -

Technical Overview of the Data Delivery System

The technical overview provides an overview of how the DDS works and is meant to be used. The document can be found on GitHub or downloaded as a PDF.

From 822a6692f81c239d7f828411be46658ca69f8d72 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 10 Oct 2022 10:52:20 +0200 Subject: [PATCH 062/123] only contact DC if 404 --- dds_web/templates/404.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/templates/404.html b/dds_web/templates/404.html index d77a5238a..0252f5a03 100644 --- a/dds_web/templates/404.html +++ b/dds_web/templates/404.html @@ -12,7 +12,7 @@

You can get back to the homepage here. - If in doubt, please get in touch with {{ 'the SciLifeLab Data Centre (datacentre@scilifelab.se)' if g.current_user and g.role and g.role != 'Researcher' else 'support'}}. + If in doubt, please get in touch with the SciLifeLab Data Centre (datacentre@scilifelab.se).

{% endblock %} From 39a97fec7f00c233feab7face66f5c9f9b7f59e4 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 10 Oct 2022 10:54:16 +0200 Subject: [PATCH 063/123] remove g.role --- dds_web/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 34778c603..2e35b8ccf 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -207,7 +207,6 @@ def prepare(): elif flask_login.current_user.is_authenticated: flask.g.current_user = flask_login.current_user.username flask.g.current_user_emails = flask_login.current_user.emails - flask.g.role = flask_login.current_user.role elif flask.request.authorization: flask.g.current_user = flask.request.authorization.get("username") flask.g.current_user_emails = flask.request.authorization.get("emails") From db033bda515e13a8e14f816639380a810ec9589f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Mon, 10 Oct 2022 10:57:52 +0200 Subject: [PATCH 064/123] Update CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 866ca471e..922c49030 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -155,4 +155,3 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe ## Sprint (2022-09-30 - 2022-10-14) - Bug fix: Fix the Invite.projects database model ([#1290](https://github.com/ScilifelabDataCentre/dds_web/pull/1290)) -- Display different information on who to contact depending on user role ([#1292](https://github.com/ScilifelabDataCentre/dds_web/pull/1292)) From 5ecc663068e3dc6d5aa9810ff0f0415a09ddb635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Fri, 30 Sep 2022 14:55:28 +0200 Subject: [PATCH 065/123] WIP invite endpoint --- dds_web/api/__init__.py | 1 + dds_web/api/user.py | 51 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/dds_web/api/__init__.py b/dds_web/api/__init__.py index 0ebc5aeaf..5168f6beb 100644 --- a/dds_web/api/__init__.py +++ b/dds_web/api/__init__.py @@ -76,6 +76,7 @@ def output_json(data, code, headers=None): user.RequestTOTPActivation, "/user/totp/activate", endpoint="request_totp_activation" ) api.add_resource(user.Users, "/users", endpoint="users") +api.add_resource(user.InvitedUsers, "/user/invites", endpoint="list_invites") # Super Admins ###################################################################### Super Admins # diff --git a/dds_web/api/user.py b/dds_web/api/user.py index bf6e47a68..620bc6bbd 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -1245,12 +1245,61 @@ def get_users(unit: models.Unit = None): # Unit Personnel and Unit Admins can list all users within their units unit_row = auth.current_user().unit - + # Get users in unit users_to_return = get_users(unit=unit_row) + flask.current_app.logger.error({ + "users": users_to_return, + "unit": unit_row.name, + "keys": keys, + "empty": not users_to_return, + }) return { "users": users_to_return, "unit": unit_row.name, "keys": keys, "empty": not users_to_return, } + + +class InvitedUsers(flask_restful.Resource): + """Provide a list of invited users.""" + + @auth.login_required() + def get(self): + out_model = ("email", "role", "created_at", "public_id") + current_user = auth.current_user() + if current_user.role == "Super Admin": + raw_hits = models.Invite.query.all() + elif current_user.role in ("Unit Admin", "Unit Personnel"): + unit = models.UnitUser.query.filter_by(username=current_user.username).one().unit + owned_projects = models.Project.query.filter_by(unit_id=unit.id).all() + unit_projects = set(proj.id for proj in owned_projects) + raw_invites = models.Invite.query.all() + raw_hits = [] + for inv in raw_invites: + if inv.role == "Researcher" and set(proj.id for proj in inv.projects).intersection(unit_projects): + raw_hits.append(inv) + elif inv.role in ("Unit Admin", "Unit Personnel") and inv.unit == unit: + raw_hits.append(inv) + elif current_user.role == "Researcher": + owned_projects = models.ProjectUsers.query.filter_by(user_id=current_user.username).filter_by(owner=1).all() + user_projects = set(proj.project_id for proj in owned_projects) + raw_invites = models.Invite.query.all() + raw_hits = [] + for inv in raw_invites: + if inv.role == "Researcher" and set(proj.id for proj in inv.projects).intersection(user_projects): + raw_hits.append(inv) + else: + raw_hits = [] + hits = [] + key_map = {"email": "Email", "role": "Role", "created_at": "Created"} + key_order = (key_map["email"], key_map["role"], "Projects", key_map["created_at"]) + for entry in raw_hits: + hit = {} + for key in key_map.keys(): + hit[key_map[key]] = getattr(entry, key) + hit["Projects"] = [project.public_id for project in entry.projects] + hits.append(hit) + + return {"invites": hits, "keys": key_order} From 115012bce63c1da74745e8e3349493ef806dd570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Thu, 6 Oct 2022 15:01:38 +0200 Subject: [PATCH 066/123] Add test --- tests/api/test_user.py | 71 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index 420dfe683..ee685254e 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1044,3 +1044,74 @@ def test_invite_users_should_have_different_timestamps(client): new_time_2, new_time_3, ] + + +def test_list_invites(client): + """ + Confirm that users can list relevant invites + + * Researcher who do not own any projects cannot see invites + * Researcher who own a project can see invites for that project + * Unit admin|personnel can see invites to any projects owned by the unit, and invites to the unit + * Superadmin can see any invites + * All invites contain columns for email, role, created_at, public_id + * Invites for unit admin|personnel also includes unit column for superadmin + """ + + def invite_user(user_data: str, as_user: str) -> dict: + params = "?" + if "project" in user_data: + params += "project=" + user_data["project"] + return client.post( + tests.DDSEndpoint.USER_ADD + params, + headers=tests.UserAuth(tests.USER_CREDENTIALS[as_user]).token(client), + json=user_data, + ) + + def get_list(as_user) -> dict: + return client.get( + tests.DDSEndpoint.LIST_INVITES, + headers=tests.UserAuth(tests.USER_CREDENTIALS[as_user]).token(client), + ) + + unit_invite = dict(new_unit_user) + unit_invite["unit"] = "Unit 1" + invite_user(unit_invite, "unitadmin") + invite_user(new_super_admin, "superadmin") + invite_user(first_new_user_existing_project, "unitadmin") + + import logging + from dds_web.database import models + + response = get_list("superadmin") + assert "invites" in response.json + assert len(response.json["invites"]) == 5 + for entry in response.json["invites"]: + for key in ["Email", "Role", "Projects", "Created"]: + assert key in entry + + logging.error(response.json) + for invite in models.Invite.query.all(): + logging.error(invite.__dict__) + for invite in models.ProjectInviteKeys.query.all(): + logging.error(invite.__dict__) + + response = get_list("unitadmin") + assert "invites" in response.json + assert len(response.json["invites"]) == 2 + for entry in response.json["invites"]: + for key in ["Email", "Role", "Projects", "Created"]: + assert key in entry + assert "Unit" not in entry + + response = get_list("projectowner") + assert "invites" in response.json + assert len(response.json["invites"]) == 1 + for entry in response.json["invites"]: + for key in ["Email", "Role", "Projects", "Created"]: + assert key in entry + assert "Unit" not in entry + + response = get_list("researchuser") + assert "invites" in response.json + assert len(response.json["invites"]) == 0 From 23a359327da8c48a6ed2b341b0062f110fdec444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Thu, 6 Oct 2022 15:02:06 +0200 Subject: [PATCH 067/123] Add endpoint to tests --- tests/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/__init__.py b/tests/__init__.py index 03b907c80..3055bfa83 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -205,6 +205,7 @@ class DDSEndpoint: LIST_PROJ = BASE_ENDPOINT + "/proj/list" LIST_FILES = BASE_ENDPOINT + "/files/list" LIST_PROJ_USERS = BASE_ENDPOINT + "/proj/users" + LIST_INVITES = BASE_ENDPOINT + "/user/invites" # Deleting urls REMOVE_PROJ_CONT = BASE_ENDPOINT + "/proj/rm" From ef574156a1caab2282372d396b77759a99222c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Thu, 6 Oct 2022 15:29:30 +0200 Subject: [PATCH 068/123] Add Unit to invite test --- tests/api/test_user.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index ee685254e..fe46bdb7a 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1087,14 +1087,10 @@ def get_list(as_user) -> dict: assert "invites" in response.json assert len(response.json["invites"]) == 5 for entry in response.json["invites"]: - for key in ["Email", "Role", "Projects", "Created"]: + for key in ["Email", "Role", "Projects", "Created", "Unit"]: assert key in entry - - logging.error(response.json) - for invite in models.Invite.query.all(): - logging.error(invite.__dict__) - for invite in models.ProjectInviteKeys.query.all(): - logging.error(invite.__dict__) + if entry["Role"] in ("Unit Admin", "Unit Personnel"): + assert entry["Unit"] == "Unit 1" response = get_list("unitadmin") assert "invites" in response.json From eca570c0b7bc3fe3ea570283e9e4ecbfeca309ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Thu, 6 Oct 2022 15:30:22 +0200 Subject: [PATCH 069/123] Add Unit to column for Unit users for superadmin --- dds_web/api/user.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 620bc6bbd..6bc0d4bf1 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -1294,12 +1294,19 @@ def get(self): raw_hits = [] hits = [] key_map = {"email": "Email", "role": "Role", "created_at": "Created"} - key_order = (key_map["email"], key_map["role"], "Projects", key_map["created_at"]) + key_order = [key_map["email"], key_map["role"], "Projects", key_map["created_at"]] + if current_user.role == "Super Admin": + key_map["unit"] = "Unit" + key_order = key_order.insert(1, "Unit") + for entry in raw_hits: hit = {} for key in key_map.keys(): hit[key_map[key]] = getattr(entry, key) + hit["Projects"] = [project.public_id for project in entry.projects] + if "Unit" in hit and hit["Unit"]: + hit["Unit"] = hit["Unit"].name hits.append(hit) return {"invites": hits, "keys": key_order} From 4a60eabaa0a37fbe39b974a123b34ef02c3338f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Thu, 6 Oct 2022 15:43:40 +0200 Subject: [PATCH 070/123] Add key check to test --- tests/api/test_user.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index fe46bdb7a..765d82cf2 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1091,6 +1091,7 @@ def get_list(as_user) -> dict: assert key in entry if entry["Role"] in ("Unit Admin", "Unit Personnel"): assert entry["Unit"] == "Unit 1" + assert response.json.get("keys"]) == ["Email", "Unit", "Role", "Projects", "Created"] response = get_list("unitadmin") assert "invites" in response.json @@ -1099,6 +1100,7 @@ def get_list(as_user) -> dict: for key in ["Email", "Role", "Projects", "Created"]: assert key in entry assert "Unit" not in entry + assert response.json.get("keys"]) == ["Email", "Role", "Projects", "Created"] response = get_list("projectowner") assert "invites" in response.json @@ -1107,7 +1109,9 @@ def get_list(as_user) -> dict: for key in ["Email", "Role", "Projects", "Created"]: assert key in entry assert "Unit" not in entry + assert response.json.get("keys"]) == ["Email", "Role", "Projects", "Created"] response = get_list("researchuser") assert "invites" in response.json assert len(response.json["invites"]) == 0 + assert response.json.get("keys"]) == ["Email", "Role", "Projects", "Created"] From 3a53ddd0de0c9795105e58734dc26d1a404ac862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Thu, 6 Oct 2022 15:49:12 +0200 Subject: [PATCH 071/123] Fix keys, black formatting --- dds_web/api/user.py | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 6bc0d4bf1..b9fc08242 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -1245,15 +1245,17 @@ def get_users(unit: models.Unit = None): # Unit Personnel and Unit Admins can list all users within their units unit_row = auth.current_user().unit - + # Get users in unit users_to_return = get_users(unit=unit_row) - flask.current_app.logger.error({ - "users": users_to_return, - "unit": unit_row.name, - "keys": keys, - "empty": not users_to_return, - }) + flask.current_app.logger.error( + { + "users": users_to_return, + "unit": unit_row.name, + "keys": keys, + "empty": not users_to_return, + } + ) return { "users": users_to_return, "unit": unit_row.name, @@ -1278,17 +1280,25 @@ def get(self): raw_invites = models.Invite.query.all() raw_hits = [] for inv in raw_invites: - if inv.role == "Researcher" and set(proj.id for proj in inv.projects).intersection(unit_projects): + if inv.role == "Researcher" and set(proj.id for proj in inv.projects).intersection( + unit_projects + ): raw_hits.append(inv) elif inv.role in ("Unit Admin", "Unit Personnel") and inv.unit == unit: raw_hits.append(inv) elif current_user.role == "Researcher": - owned_projects = models.ProjectUsers.query.filter_by(user_id=current_user.username).filter_by(owner=1).all() + owned_projects = ( + models.ProjectUsers.query.filter_by(user_id=current_user.username) + .filter_by(owner=1) + .all() + ) user_projects = set(proj.project_id for proj in owned_projects) raw_invites = models.Invite.query.all() raw_hits = [] for inv in raw_invites: - if inv.role == "Researcher" and set(proj.id for proj in inv.projects).intersection(user_projects): + if inv.role == "Researcher" and set(proj.id for proj in inv.projects).intersection( + user_projects + ): raw_hits.append(inv) else: raw_hits = [] @@ -1297,16 +1307,17 @@ def get(self): key_order = [key_map["email"], key_map["role"], "Projects", key_map["created_at"]] if current_user.role == "Super Admin": key_map["unit"] = "Unit" - key_order = key_order.insert(1, "Unit") + key_order.insert(1, "Unit") for entry in raw_hits: hit = {} for key in key_map.keys(): - hit[key_map[key]] = getattr(entry, key) + hit[key_map[key]] = getattr(entry, key) or "" hit["Projects"] = [project.public_id for project in entry.projects] if "Unit" in hit and hit["Unit"]: hit["Unit"] = hit["Unit"].name + hits.append(hit) - + return {"invites": hits, "keys": key_order} From e4a2b26af6332c5839c4f5a8d9ddf64534628dc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Thu, 6 Oct 2022 15:50:28 +0200 Subject: [PATCH 072/123] Fix test --- tests/api/test_user.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index 765d82cf2..2b58fb5d6 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1091,7 +1091,7 @@ def get_list(as_user) -> dict: assert key in entry if entry["Role"] in ("Unit Admin", "Unit Personnel"): assert entry["Unit"] == "Unit 1" - assert response.json.get("keys"]) == ["Email", "Unit", "Role", "Projects", "Created"] + assert response.json.get("keys") == ["Email", "Unit", "Role", "Projects", "Created"] response = get_list("unitadmin") assert "invites" in response.json @@ -1100,7 +1100,7 @@ def get_list(as_user) -> dict: for key in ["Email", "Role", "Projects", "Created"]: assert key in entry assert "Unit" not in entry - assert response.json.get("keys"]) == ["Email", "Role", "Projects", "Created"] + assert response.json.get("keys") == ["Email", "Role", "Projects", "Created"] response = get_list("projectowner") assert "invites" in response.json @@ -1109,9 +1109,9 @@ def get_list(as_user) -> dict: for key in ["Email", "Role", "Projects", "Created"]: assert key in entry assert "Unit" not in entry - assert response.json.get("keys"]) == ["Email", "Role", "Projects", "Created"] + assert response.json.get("keys") == ["Email", "Role", "Projects", "Created"] response = get_list("researchuser") assert "invites" in response.json assert len(response.json["invites"]) == 0 - assert response.json.get("keys"]) == ["Email", "Role", "Projects", "Created"] + assert response.json.get("keys") == ["Email", "Role", "Projects", "Created"] From 49505846d4493719fe90ef619cf69e8171cb0d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Thu, 6 Oct 2022 21:28:42 +0200 Subject: [PATCH 073/123] Remove logging used for testing --- dds_web/api/user.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index b9fc08242..d0713ecf9 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -1248,14 +1248,6 @@ def get_users(unit: models.Unit = None): # Get users in unit users_to_return = get_users(unit=unit_row) - flask.current_app.logger.error( - { - "users": users_to_return, - "unit": unit_row.name, - "keys": keys, - "empty": not users_to_return, - } - ) return { "users": users_to_return, "unit": unit_row.name, From 061da7beff3b94eb01d15baeb9e512f85af2c249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Thu, 6 Oct 2022 21:47:30 +0200 Subject: [PATCH 074/123] Add changelog note --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b17e5088b..ef8c1d52d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -152,3 +152,4 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - New endpoint: SetMaintenance - set maintenance mode to on or off ([#1286](https://github.com/ScilifelabDataCentre/dds_web/pull/1286)) - New endpoint: AnyProjectsBusy - check if any projects are busy in DDS ([#1288](https://github.com/ScilifelabDataCentre/dds_web/pull/1288)) - Bug fix: Fix the Invite.projects database model ([#1290](https://github.com/ScilifelabDataCentre/dds_web/pull/1290)) +- New endpoint: ListInvites - list invites ([#1294](https://github.com/ScilifelabDataCentre/dds_web/pull/1294)) From 0409299aa29d20c04f42c24d1fe42cc677ffd8fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Fri, 7 Oct 2022 12:05:59 +0200 Subject: [PATCH 075/123] Update tests --- tests/api/test_user.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index 2b58fb5d6..0dca413b5 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1099,6 +1099,8 @@ def get_list(as_user) -> dict: for entry in response.json["invites"]: for key in ["Email", "Role", "Projects", "Created"]: assert key in entry + if entry["Role"] in ("Unit Admin", "Unit Personnel"): + assert len(entry["Projects"]) == 5 assert "Unit" not in entry assert response.json.get("keys") == ["Email", "Role", "Projects", "Created"] @@ -1112,6 +1114,6 @@ def get_list(as_user) -> dict: assert response.json.get("keys") == ["Email", "Role", "Projects", "Created"] response = get_list("researchuser") - assert "invites" in response.json - assert len(response.json["invites"]) == 0 - assert response.json.get("keys") == ["Email", "Role", "Projects", "Created"] + assert response.status_code == http.HTTPStatus.FORBIDDEN + assert not response.json.get("invites") + assert not response.json.get("keys") From cb1883237a8fed6c350259f3d1330513662871f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Fri, 7 Oct 2022 12:06:39 +0200 Subject: [PATCH 076/123] Adapt to comments for pr --- dds_web/api/user.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index d0713ecf9..3914558d6 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -1259,16 +1259,17 @@ def get_users(unit: models.Unit = None): class InvitedUsers(flask_restful.Resource): """Provide a list of invited users.""" - @auth.login_required() + @auth.login_required def get(self): - out_model = ("email", "role", "created_at", "public_id") current_user = auth.current_user() if current_user.role == "Super Admin": + # superadmin can see all invites raw_hits = models.Invite.query.all() elif current_user.role in ("Unit Admin", "Unit Personnel"): - unit = models.UnitUser.query.filter_by(username=current_user.username).one().unit - owned_projects = models.Project.query.filter_by(unit_id=unit.id).all() - unit_projects = set(proj.id for proj in owned_projects) + # unit users can see all invites to the unit and any projects it owns + # start by getting all invites, then filter by project and unit + unit = current_user.unit + unit_projects = set(proj.id for proj in unit.projects) raw_invites = models.Invite.query.all() raw_hits = [] for inv in raw_invites: @@ -1279,11 +1280,15 @@ def get(self): elif inv.role in ("Unit Admin", "Unit Personnel") and inv.unit == unit: raw_hits.append(inv) elif current_user.role == "Researcher": + # researchers can see invitations to projects where they are the owner owned_projects = ( models.ProjectUsers.query.filter_by(user_id=current_user.username) .filter_by(owner=1) .all() ) + if not owned_projects: + raise ddserr.RoleException(message="You need to have the role 'Project Owner' in at least one project to be able to list any invites.") + # use set intersection to find overlaps between projects for invite and current_user user_projects = set(proj.project_id for proj in owned_projects) raw_invites = models.Invite.query.all() raw_hits = [] @@ -1293,23 +1298,29 @@ def get(self): ): raw_hits.append(inv) else: + # in case further roles are defined in the future raw_hits = [] + + # columns to return, map to displayed column names hits = [] - key_map = {"email": "Email", "role": "Role", "created_at": "Created"} - key_order = [key_map["email"], key_map["role"], "Projects", key_map["created_at"]] + key_map = {"email": "Email", "role": "Role", "created_at": "Created", "projects": "Projects"} + key_order = [key_map["email"], key_map["role"], key_map["projects"], key_map["created_at"]] + # superadmin also has a unit column if current_user.role == "Super Admin": key_map["unit"] = "Unit" key_order.insert(1, "Unit") + # extract the chosen columns for each entry for entry in raw_hits: hit = {} for key in key_map.keys(): hit[key_map[key]] = getattr(entry, key) or "" - hit["Projects"] = [project.public_id for project in entry.projects] - if "Unit" in hit and hit["Unit"]: + # represent projects with public_id + hit["Projects"] = [project.public_id for project in hit["Projects"]] + # represent unit with name + if hit.get("Unit"): hit["Unit"] = hit["Unit"].name - hits.append(hit) return {"invites": hits, "keys": key_order} From 8725eb6db3b7d9b6e26ae2b4e06bb9e612529ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Fri, 7 Oct 2022 14:15:40 +0200 Subject: [PATCH 077/123] Update for project filtering, need to fix tests --- dds_web/api/user.py | 72 +++++++++++++++++++++++------------------- tests/api/test_user.py | 6 ++-- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 3914558d6..df6ca710d 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -1262,65 +1262,73 @@ class InvitedUsers(flask_restful.Resource): @auth.login_required def get(self): current_user = auth.current_user() + + # columns to return, map to displayed column names + key_map = {"email": "Email", "role": "Role", "created_at": "Created", "projects": "Projects"} + key_order = [key_map["email"], key_map["role"], key_map["projects"], key_map["created_at"]] + if current_user.role == "Super Admin": + key_map["unit"] = "Unit" + key_order.insert(1, "Unit") + + def row_to_dict(entry) -> dict: + """Convert a db row to a dict, extracting only wanted columns.""" + hit = {} + for key in key_map.keys(): + hit[key_map[key]] = getattr(entry, key) or "" + # represent projects with public_id + hit["Projects"] = [project.public_id for project in hit["Projects"]] + # represent unit with name + if hit.get("Unit"): + hit["Unit"] = hit["Unit"].name + return hit + if current_user.role == "Super Admin": # superadmin can see all invites - raw_hits = models.Invite.query.all() + hits = [row_to_dict(entry) for entry in models.Invite.query.all()] + elif current_user.role in ("Unit Admin", "Unit Personnel"): # unit users can see all invites to the unit and any projects it owns # start by getting all invites, then filter by project and unit unit = current_user.unit unit_projects = set(proj.id for proj in unit.projects) + unit_projects_pubid = set(proj.public_id for proj in unit.projects) raw_invites = models.Invite.query.all() - raw_hits = [] + hits = [] for inv in raw_invites: + flask.current_app.logger.error(set(proj.id for proj in inv.projects)) + flask.current_app.logger.error(inv.role) if inv.role == "Researcher" and set(proj.id for proj in inv.projects).intersection( unit_projects - ): - raw_hits.append(inv) + ): + entry = row_to_dict(inv) + entry["Projects"] = [project for project in entry["Projects"] if project in unit_projects_pubid] + hits.append(entry) elif inv.role in ("Unit Admin", "Unit Personnel") and inv.unit == unit: - raw_hits.append(inv) + hits.append(row_to_dict(inv)) + elif current_user.role == "Researcher": # researchers can see invitations to projects where they are the owner - owned_projects = ( + project_connections = ( models.ProjectUsers.query.filter_by(user_id=current_user.username) .filter_by(owner=1) .all() ) - if not owned_projects: + if not project_connections: raise ddserr.RoleException(message="You need to have the role 'Project Owner' in at least one project to be able to list any invites.") # use set intersection to find overlaps between projects for invite and current_user - user_projects = set(proj.project_id for proj in owned_projects) + user_projects = set(entry.project.id for entry in project_connections) + user_projects_pubid = set(entry.project.public_id for entry in project_connections) raw_invites = models.Invite.query.all() - raw_hits = [] + hits = [] for inv in raw_invites: if inv.role == "Researcher" and set(proj.id for proj in inv.projects).intersection( user_projects ): - raw_hits.append(inv) + entry = row_to_dict(inv) + entry["Projects"] = [project for project in entry["Projects"] if project in user_projects_pubid] + hits.append(entry) else: # in case further roles are defined in the future raw_hits = [] - # columns to return, map to displayed column names - hits = [] - key_map = {"email": "Email", "role": "Role", "created_at": "Created", "projects": "Projects"} - key_order = [key_map["email"], key_map["role"], key_map["projects"], key_map["created_at"]] - # superadmin also has a unit column - if current_user.role == "Super Admin": - key_map["unit"] = "Unit" - key_order.insert(1, "Unit") - - # extract the chosen columns for each entry - for entry in raw_hits: - hit = {} - for key in key_map.keys(): - hit[key_map[key]] = getattr(entry, key) or "" - - # represent projects with public_id - hit["Projects"] = [project.public_id for project in hit["Projects"]] - # represent unit with name - if hit.get("Unit"): - hit["Unit"] = hit["Unit"].name - hits.append(hit) - return {"invites": hits, "keys": key_order} diff --git a/tests/api/test_user.py b/tests/api/test_user.py index 0dca413b5..111e3c4a9 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1080,9 +1080,6 @@ def get_list(as_user) -> dict: invite_user(new_super_admin, "superadmin") invite_user(first_new_user_existing_project, "unitadmin") - import logging - from dds_web.database import models - response = get_list("superadmin") assert "invites" in response.json assert len(response.json["invites"]) == 5 @@ -1101,6 +1098,8 @@ def get_list(as_user) -> dict: assert key in entry if entry["Role"] in ("Unit Admin", "Unit Personnel"): assert len(entry["Projects"]) == 5 + elif entry["Role"] == "Researcher": + assert len(entry["Projects"]) == 1 assert "Unit" not in entry assert response.json.get("keys") == ["Email", "Role", "Projects", "Created"] @@ -1111,6 +1110,7 @@ def get_list(as_user) -> dict: for key in ["Email", "Role", "Projects", "Created"]: assert key in entry assert "Unit" not in entry + assert len(entry["Projects"]) == 1 assert response.json.get("keys") == ["Email", "Role", "Projects", "Created"] response = get_list("researchuser") From c35996296668b39074749b78ab77fe0f3d686de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Fri, 7 Oct 2022 14:46:41 +0200 Subject: [PATCH 078/123] Add tests with projects from multiple units --- tests/api/test_user.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index 111e3c4a9..21aa790a2 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -22,6 +22,8 @@ first_new_user = {**first_new_email, "role": "Researcher"} first_new_owner = {**first_new_email, "role": "Project Owner"} first_new_user_existing_project = {**first_new_user, "project": "public_project_id"} +first_new_user_existing_project2 = {**first_new_user, "project": "second_public_project_id"} +first_new_user_existing_project3 = {**first_new_user, "project": "unit2testing"} first_new_user_extra_args = {**first_new_user, "extra": "test"} first_new_user_invalid_role = {**first_new_email, "role": "Invalid Role"} first_new_user_invalid_email = {"email": "first_invalid_email", "role": first_new_user["role"]} @@ -1061,7 +1063,7 @@ def test_list_invites(client): def invite_user(user_data: str, as_user: str) -> dict: params = "?" if "project" in user_data: - params += "project=" + user_data["project"] + params += f"project={user_data['project']}&" return client.post( tests.DDSEndpoint.USER_ADD + params, headers=tests.UserAuth(tests.USER_CREDENTIALS[as_user]).token(client), @@ -1077,8 +1079,12 @@ def get_list(as_user) -> dict: unit_invite = dict(new_unit_user) unit_invite["unit"] = "Unit 1" invite_user(unit_invite, "unitadmin") + unit_invite["unit"] = "The league of the extinct gentlemen" + invite_user(unit_invite, "superadmin") invite_user(new_super_admin, "superadmin") invite_user(first_new_user_existing_project, "unitadmin") + invite_user(first_new_user_existing_project2, "unitadmin") + invite_user(first_new_user_existing_project3, "delete_me_unituser") response = get_list("superadmin") assert "invites" in response.json @@ -1088,7 +1094,7 @@ def get_list(as_user) -> dict: assert key in entry if entry["Role"] in ("Unit Admin", "Unit Personnel"): assert entry["Unit"] == "Unit 1" - assert response.json.get("keys") == ["Email", "Unit", "Role", "Projects", "Created"] + assert response.json.get("keys", []) == ["Email", "Unit", "Role", "Projects", "Created"] response = get_list("unitadmin") assert "invites" in response.json @@ -1099,9 +1105,11 @@ def get_list(as_user) -> dict: if entry["Role"] in ("Unit Admin", "Unit Personnel"): assert len(entry["Projects"]) == 5 elif entry["Role"] == "Researcher": - assert len(entry["Projects"]) == 1 + assert len(entry["Projects"]) == 2 + elif entry["Role"] == "Superadmin": # Should never happen + assert False assert "Unit" not in entry - assert response.json.get("keys") == ["Email", "Role", "Projects", "Created"] + assert response.json.get("keys", []) == ["Email", "Role", "Projects", "Created"] response = get_list("projectowner") assert "invites" in response.json @@ -1111,7 +1119,7 @@ def get_list(as_user) -> dict: assert key in entry assert "Unit" not in entry assert len(entry["Projects"]) == 1 - assert response.json.get("keys") == ["Email", "Role", "Projects", "Created"] + assert response.json.get("keys", []) == ["Email", "Role", "Projects", "Created"] response = get_list("researchuser") assert response.status_code == http.HTTPStatus.FORBIDDEN From 82c03ddf95213c795996c01f99ace7ce2c0958d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Fri, 7 Oct 2022 14:49:40 +0200 Subject: [PATCH 079/123] Avoid defining invites globally --- tests/api/test_user.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index 21aa790a2..b11a48ceb 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -22,8 +22,6 @@ first_new_user = {**first_new_email, "role": "Researcher"} first_new_owner = {**first_new_email, "role": "Project Owner"} first_new_user_existing_project = {**first_new_user, "project": "public_project_id"} -first_new_user_existing_project2 = {**first_new_user, "project": "second_public_project_id"} -first_new_user_existing_project3 = {**first_new_user, "project": "unit2testing"} first_new_user_extra_args = {**first_new_user, "extra": "test"} first_new_user_invalid_role = {**first_new_email, "role": "Invalid Role"} first_new_user_invalid_email = {"email": "first_invalid_email", "role": first_new_user["role"]} @@ -1082,9 +1080,14 @@ def get_list(as_user) -> dict: unit_invite["unit"] = "The league of the extinct gentlemen" invite_user(unit_invite, "superadmin") invite_user(new_super_admin, "superadmin") - invite_user(first_new_user_existing_project, "unitadmin") - invite_user(first_new_user_existing_project2, "unitadmin") - invite_user(first_new_user_existing_project3, "delete_me_unituser") + + + researcher_to_project = dict(first_new_user_existing_project) + invite_user(researcher_to_project, "unitadmin") + researcher_to_project["project"] = "second_public_project_id" + invite_user(researcher_to_project, "unitadmin") + researcher_to_project["project"] = "unit2testing" + invite_user(researcher_to_project, "unitadmin") response = get_list("superadmin") assert "invites" in response.json From 84ae52de5fbb448a1307d740fcd4e08d3cf17565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Fri, 7 Oct 2022 14:50:37 +0200 Subject: [PATCH 080/123] Fix formatting --- dds_web/api/user.py | 24 ++++++++++++++++++------ tests/api/test_user.py | 1 - 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index df6ca710d..36a96a1e4 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -1264,7 +1264,12 @@ def get(self): current_user = auth.current_user() # columns to return, map to displayed column names - key_map = {"email": "Email", "role": "Role", "created_at": "Created", "projects": "Projects"} + key_map = { + "email": "Email", + "role": "Role", + "created_at": "Created", + "projects": "Projects", + } key_order = [key_map["email"], key_map["role"], key_map["projects"], key_map["created_at"]] if current_user.role == "Super Admin": key_map["unit"] = "Unit" @@ -1276,7 +1281,7 @@ def row_to_dict(entry) -> dict: for key in key_map.keys(): hit[key_map[key]] = getattr(entry, key) or "" # represent projects with public_id - hit["Projects"] = [project.public_id for project in hit["Projects"]] + hit["Projects"] = [project.public_id for project in hit["Projects"]] # represent unit with name if hit.get("Unit"): hit["Unit"] = hit["Unit"].name @@ -1299,9 +1304,11 @@ def row_to_dict(entry) -> dict: flask.current_app.logger.error(inv.role) if inv.role == "Researcher" and set(proj.id for proj in inv.projects).intersection( unit_projects - ): + ): entry = row_to_dict(inv) - entry["Projects"] = [project for project in entry["Projects"] if project in unit_projects_pubid] + entry["Projects"] = [ + project for project in entry["Projects"] if project in unit_projects_pubid + ] hits.append(entry) elif inv.role in ("Unit Admin", "Unit Personnel") and inv.unit == unit: hits.append(row_to_dict(inv)) @@ -1313,8 +1320,11 @@ def row_to_dict(entry) -> dict: .filter_by(owner=1) .all() ) + if not project_connections: - raise ddserr.RoleException(message="You need to have the role 'Project Owner' in at least one project to be able to list any invites.") + raise ddserr.RoleException( + message="You need to have the role 'Project Owner' in at least one project to be able to list any invites." + ) # use set intersection to find overlaps between projects for invite and current_user user_projects = set(entry.project.id for entry in project_connections) user_projects_pubid = set(entry.project.public_id for entry in project_connections) @@ -1325,7 +1335,9 @@ def row_to_dict(entry) -> dict: user_projects ): entry = row_to_dict(inv) - entry["Projects"] = [project for project in entry["Projects"] if project in user_projects_pubid] + entry["Projects"] = [ + project for project in entry["Projects"] if project in user_projects_pubid + ] hits.append(entry) else: # in case further roles are defined in the future diff --git a/tests/api/test_user.py b/tests/api/test_user.py index b11a48ceb..a1aa230c1 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1081,7 +1081,6 @@ def get_list(as_user) -> dict: invite_user(unit_invite, "superadmin") invite_user(new_super_admin, "superadmin") - researcher_to_project = dict(first_new_user_existing_project) invite_user(researcher_to_project, "unitadmin") researcher_to_project["project"] = "second_public_project_id" From c0b96c1bce617f9013173cf48b2f14a5019a3021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Fri, 7 Oct 2022 14:55:15 +0200 Subject: [PATCH 081/123] Add comment, remove debug lines --- dds_web/api/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 36a96a1e4..afafcd697 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -1300,12 +1300,11 @@ def row_to_dict(entry) -> dict: raw_invites = models.Invite.query.all() hits = [] for inv in raw_invites: - flask.current_app.logger.error(set(proj.id for proj in inv.projects)) - flask.current_app.logger.error(inv.role) if inv.role == "Researcher" and set(proj.id for proj in inv.projects).intersection( unit_projects ): entry = row_to_dict(inv) + # do not list projects the unit does not own entry["Projects"] = [ project for project in entry["Projects"] if project in unit_projects_pubid ] @@ -1335,6 +1334,7 @@ def row_to_dict(entry) -> dict: user_projects ): entry = row_to_dict(inv) + # do not list projects the current user does not own entry["Projects"] = [ project for project in entry["Projects"] if project in user_projects_pubid ] From bda62b8cbca9b05bc862b13e0e89c373beea9f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Mon, 10 Oct 2022 11:03:40 +0200 Subject: [PATCH 082/123] List projects as '----' for super admin --- dds_web/api/user.py | 8 +++++++- tests/api/test_user.py | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index afafcd697..85e00e539 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -1289,7 +1289,13 @@ def row_to_dict(entry) -> dict: if current_user.role == "Super Admin": # superadmin can see all invites - hits = [row_to_dict(entry) for entry in models.Invite.query.all()] + raw_invites = models.Invite.query.all() + hits = [] + for inv in raw_invites: + entry = row_to_dict(inv) + if inv.role == "Super Admin": + entry["Projects"] = "----" + hits.append(entry) elif current_user.role in ("Unit Admin", "Unit Personnel"): # unit users can see all invites to the unit and any projects it owns diff --git a/tests/api/test_user.py b/tests/api/test_user.py index a1aa230c1..a8bf79cc0 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1096,6 +1096,8 @@ def get_list(as_user) -> dict: assert key in entry if entry["Role"] in ("Unit Admin", "Unit Personnel"): assert entry["Unit"] == "Unit 1" + elif entry["Role"] in ("Unit Admin", "Unit Personnel"): + assert entry["Projects"] == "----" assert response.json.get("keys", []) == ["Email", "Unit", "Role", "Projects", "Created"] response = get_list("unitadmin") From 10222ea976f435181edd89b3e6328d169bbe53b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Mon, 10 Oct 2022 11:04:20 +0200 Subject: [PATCH 083/123] Fix formatting --- dds_web/api/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 85e00e539..27c6e6b95 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -1295,7 +1295,7 @@ def row_to_dict(entry) -> dict: entry = row_to_dict(inv) if inv.role == "Super Admin": entry["Projects"] = "----" - hits.append(entry) + hits.append(entry) elif current_user.role in ("Unit Admin", "Unit Personnel"): # unit users can see all invites to the unit and any projects it owns From 49028a2861df5eea6e88503a8f0ea1abf2998f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Mon, 10 Oct 2022 11:08:53 +0200 Subject: [PATCH 084/123] Add tests to verify correct data type --- tests/api/test_user.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index a8bf79cc0..ee41dcead 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1095,7 +1095,8 @@ def get_list(as_user) -> dict: for key in ["Email", "Role", "Projects", "Created", "Unit"]: assert key in entry if entry["Role"] in ("Unit Admin", "Unit Personnel"): - assert entry["Unit"] == "Unit 1" + assert entry["Unit"] == "Unit 1 +" assert isinstance(entry["Projects"], list) elif entry["Role"] in ("Unit Admin", "Unit Personnel"): assert entry["Projects"] == "----" assert response.json.get("keys", []) == ["Email", "Unit", "Role", "Projects", "Created"] @@ -1108,8 +1109,10 @@ def get_list(as_user) -> dict: assert key in entry if entry["Role"] in ("Unit Admin", "Unit Personnel"): assert len(entry["Projects"]) == 5 + assert isinstance(entry["Projects"], list) elif entry["Role"] == "Researcher": assert len(entry["Projects"]) == 2 + assert isinstance(entry["Projects"], list) elif entry["Role"] == "Superadmin": # Should never happen assert False assert "Unit" not in entry @@ -1123,6 +1126,7 @@ def get_list(as_user) -> dict: assert key in entry assert "Unit" not in entry assert len(entry["Projects"]) == 1 + assert isinstance(entry["Projects"], list) assert response.json.get("keys", []) == ["Email", "Role", "Projects", "Created"] response = get_list("researchuser") From 6a4fa46ca5c4a7339ca1af7e25c4940c0f93cac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Mon, 10 Oct 2022 11:11:58 +0200 Subject: [PATCH 085/123] Fix bad linebreak --- tests/api/test_user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index ee41dcead..770fc1f0c 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1095,8 +1095,8 @@ def get_list(as_user) -> dict: for key in ["Email", "Role", "Projects", "Created", "Unit"]: assert key in entry if entry["Role"] in ("Unit Admin", "Unit Personnel"): - assert entry["Unit"] == "Unit 1 -" assert isinstance(entry["Projects"], list) + assert entry["Unit"] == "Unit 1" + assert isinstance(entry["Projects"], list) elif entry["Role"] in ("Unit Admin", "Unit Personnel"): assert entry["Projects"] == "----" assert response.json.get("keys", []) == ["Email", "Unit", "Role", "Projects", "Created"] From 974d9fd6122dbf25803855beb6724a3bf0c1e82c Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 10 Oct 2022 15:53:19 +0200 Subject: [PATCH 086/123] update migration and change init-db command --- dds_web/__init__.py | 11 +++++++++++ migrations/versions/eb395af90e18_maintenance.py | 3 +++ 2 files changed, 14 insertions(+) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 2e35b8ccf..dcbf1fc94 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -310,6 +310,17 @@ def load_user(user_id): @flask.cli.with_appcontext def fill_db_wrapper(db_type): from dds_web.database import models + + if db_type != "production": + maintenance_rows: models.Maintenance = models.Maintenance.query.all() + if len(maintenance_rows) > 1: + for row in old_maintenance[1::]: + db.session.delete(row) + maintenance_rows[0].active = False + else: + maintenance_row: models.Maintenance = models.Maintenance(active=False) + db.session.add(maintenance_row) + db.session.commit() maintenance_active: bool = db_type == "production" flask.current_app.logger.info( diff --git a/migrations/versions/eb395af90e18_maintenance.py b/migrations/versions/eb395af90e18_maintenance.py index 64ff06536..dc9898134 100644 --- a/migrations/versions/eb395af90e18_maintenance.py +++ b/migrations/versions/eb395af90e18_maintenance.py @@ -24,6 +24,9 @@ def upgrade(): sa.Column("active", sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint("id"), ) + op.execute( + "INSERT INTO maintenance (active) VALUES (1);" + ) # ### end Alembic commands ### From 1f6268d105fe4acffbfd9c23d856ed1d617cb884 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 10 Oct 2022 15:54:30 +0200 Subject: [PATCH 087/123] black --- dds_web/__init__.py | 2 +- migrations/versions/eb395af90e18_maintenance.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index dcbf1fc94..869e53aef 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -310,7 +310,7 @@ def load_user(user_id): @flask.cli.with_appcontext def fill_db_wrapper(db_type): from dds_web.database import models - + if db_type != "production": maintenance_rows: models.Maintenance = models.Maintenance.query.all() if len(maintenance_rows) > 1: diff --git a/migrations/versions/eb395af90e18_maintenance.py b/migrations/versions/eb395af90e18_maintenance.py index dc9898134..f19b91ade 100644 --- a/migrations/versions/eb395af90e18_maintenance.py +++ b/migrations/versions/eb395af90e18_maintenance.py @@ -24,9 +24,7 @@ def upgrade(): sa.Column("active", sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint("id"), ) - op.execute( - "INSERT INTO maintenance (active) VALUES (1);" - ) + op.execute("INSERT INTO maintenance (active) VALUES (1);") # ### end Alembic commands ### From 4c71c9150f5f8f333467d613d5abb63c35515018 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 10 Oct 2022 15:54:55 +0200 Subject: [PATCH 088/123] remove old maintenance code --- dds_web/__init__.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 869e53aef..84dad398a 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -322,21 +322,6 @@ def fill_db_wrapper(db_type): db.session.add(maintenance_row) db.session.commit() - maintenance_active: bool = db_type == "production" - flask.current_app.logger.info( - f"Setting maintenance as {'active' if maintenance_active else 'inactive'}..." - ) - - old_maintenance: models.Maintenance = models.Maintenance.query.all() - if len(old_maintenance) > 1: - for row in old_maintenance[1::]: - db.session.delete(row) - old_maintenance[0].active = maintenance_active - else: - new_maintenance: models.Maintenance = models.Maintenance(active=maintenance_active) - db.session.add(new_maintenance) - db.session.commit() - if db_type == "production": username = flask.current_app.config["SUPERADMIN_USERNAME"] password = flask.current_app.config["SUPERADMIN_PASSWORD"] From 55df29b03db50db1433dbbc694ec01126ffd3653 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 10 Oct 2022 15:55:10 +0200 Subject: [PATCH 089/123] missed one --- dds_web/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 84dad398a..a0ae14e33 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -314,7 +314,7 @@ def fill_db_wrapper(db_type): if db_type != "production": maintenance_rows: models.Maintenance = models.Maintenance.query.all() if len(maintenance_rows) > 1: - for row in old_maintenance[1::]: + for row in maintenance_rows[1::]: db.session.delete(row) maintenance_rows[0].active = False else: From 1904931200a5cdb3e1fe45d351aa63cb7e10010c Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 10 Oct 2022 15:56:14 +0200 Subject: [PATCH 090/123] move maintenance row to else --- dds_web/__init__.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index a0ae14e33..3f9245f88 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -311,17 +311,6 @@ def load_user(user_id): def fill_db_wrapper(db_type): from dds_web.database import models - if db_type != "production": - maintenance_rows: models.Maintenance = models.Maintenance.query.all() - if len(maintenance_rows) > 1: - for row in maintenance_rows[1::]: - db.session.delete(row) - maintenance_rows[0].active = False - else: - maintenance_row: models.Maintenance = models.Maintenance(active=False) - db.session.add(maintenance_row) - db.session.commit() - if db_type == "production": username = flask.current_app.config["SUPERADMIN_USERNAME"] password = flask.current_app.config["SUPERADMIN_PASSWORD"] @@ -349,6 +338,16 @@ def fill_db_wrapper(db_type): db.session.commit() flask.current_app.logger.info(f"Super Admin added: {username} ({email})") else: + maintenance_rows: models.Maintenance = models.Maintenance.query.all() + if len(maintenance_rows) > 1: + for row in maintenance_rows[1::]: + db.session.delete(row) + maintenance_rows[0].active = False + else: + maintenance_row: models.Maintenance = models.Maintenance(active=False) + db.session.add(maintenance_row) + db.session.commit() + flask.current_app.logger.info("Initializing development db") assert flask.current_app.config["USE_LOCAL_DB"] From 35ea227518832837637d4027159721a193fe2829 Mon Sep 17 00:00:00 2001 From: Ina Date: Mon, 10 Oct 2022 16:08:12 +0200 Subject: [PATCH 091/123] maintenance row fix --- dds_web/__init__.py | 10 ---------- tests/conftest.py | 5 +++-- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 3f9245f88..3d9498d65 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -338,16 +338,6 @@ def fill_db_wrapper(db_type): db.session.commit() flask.current_app.logger.info(f"Super Admin added: {username} ({email})") else: - maintenance_rows: models.Maintenance = models.Maintenance.query.all() - if len(maintenance_rows) > 1: - for row in maintenance_rows[1::]: - db.session.delete(row) - maintenance_rows[0].active = False - else: - maintenance_row: models.Maintenance = models.Maintenance(active=False) - db.session.add(maintenance_row) - db.session.commit() - flask.current_app.logger.info("Initializing development db") assert flask.current_app.config["USE_LOCAL_DB"] diff --git a/tests/conftest.py b/tests/conftest.py index 8b7c3d43c..550868c5c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -49,8 +49,9 @@ def fill_basic_db(db): """ Fill the database with basic data. """ - maintenance_row = Maintenance(active=False) - db.session.add(maintenance_row) + maintenance_row = Maintenance.query.first() + maintenance_row.active = False + db.session.commit() units, users, projects = add_data_to_db() db.session.add_all(units) From fccdf61cbf567a5ba78c46aa616a8d89ed341332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Mon, 10 Oct 2022 16:34:25 +0200 Subject: [PATCH 092/123] Revert "Update maintenance migration and change init-db command" --- dds_web/__init__.py | 15 +++++++++++++++ migrations/versions/eb395af90e18_maintenance.py | 1 - tests/conftest.py | 5 ++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 3d9498d65..2e35b8ccf 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -311,6 +311,21 @@ def load_user(user_id): def fill_db_wrapper(db_type): from dds_web.database import models + maintenance_active: bool = db_type == "production" + flask.current_app.logger.info( + f"Setting maintenance as {'active' if maintenance_active else 'inactive'}..." + ) + + old_maintenance: models.Maintenance = models.Maintenance.query.all() + if len(old_maintenance) > 1: + for row in old_maintenance[1::]: + db.session.delete(row) + old_maintenance[0].active = maintenance_active + else: + new_maintenance: models.Maintenance = models.Maintenance(active=maintenance_active) + db.session.add(new_maintenance) + db.session.commit() + if db_type == "production": username = flask.current_app.config["SUPERADMIN_USERNAME"] password = flask.current_app.config["SUPERADMIN_PASSWORD"] diff --git a/migrations/versions/eb395af90e18_maintenance.py b/migrations/versions/eb395af90e18_maintenance.py index f19b91ade..64ff06536 100644 --- a/migrations/versions/eb395af90e18_maintenance.py +++ b/migrations/versions/eb395af90e18_maintenance.py @@ -24,7 +24,6 @@ def upgrade(): sa.Column("active", sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint("id"), ) - op.execute("INSERT INTO maintenance (active) VALUES (1);") # ### end Alembic commands ### diff --git a/tests/conftest.py b/tests/conftest.py index 550868c5c..8b7c3d43c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -49,9 +49,8 @@ def fill_basic_db(db): """ Fill the database with basic data. """ - maintenance_row = Maintenance.query.first() - maintenance_row.active = False - db.session.commit() + maintenance_row = Maintenance(active=False) + db.session.add(maintenance_row) units, users, projects = add_data_to_db() db.session.add_all(units) From bef122456dc4ba8bbb429ca2c1745be32d5c21e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Mon, 10 Oct 2022 16:35:01 +0200 Subject: [PATCH 093/123] Revert "Revert "Update maintenance migration and change init-db command"" --- dds_web/__init__.py | 15 --------------- migrations/versions/eb395af90e18_maintenance.py | 1 + tests/conftest.py | 5 +++-- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 2e35b8ccf..3d9498d65 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -311,21 +311,6 @@ def load_user(user_id): def fill_db_wrapper(db_type): from dds_web.database import models - maintenance_active: bool = db_type == "production" - flask.current_app.logger.info( - f"Setting maintenance as {'active' if maintenance_active else 'inactive'}..." - ) - - old_maintenance: models.Maintenance = models.Maintenance.query.all() - if len(old_maintenance) > 1: - for row in old_maintenance[1::]: - db.session.delete(row) - old_maintenance[0].active = maintenance_active - else: - new_maintenance: models.Maintenance = models.Maintenance(active=maintenance_active) - db.session.add(new_maintenance) - db.session.commit() - if db_type == "production": username = flask.current_app.config["SUPERADMIN_USERNAME"] password = flask.current_app.config["SUPERADMIN_PASSWORD"] diff --git a/migrations/versions/eb395af90e18_maintenance.py b/migrations/versions/eb395af90e18_maintenance.py index 64ff06536..f19b91ade 100644 --- a/migrations/versions/eb395af90e18_maintenance.py +++ b/migrations/versions/eb395af90e18_maintenance.py @@ -24,6 +24,7 @@ def upgrade(): sa.Column("active", sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint("id"), ) + op.execute("INSERT INTO maintenance (active) VALUES (1);") # ### end Alembic commands ### diff --git a/tests/conftest.py b/tests/conftest.py index 8b7c3d43c..550868c5c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -49,8 +49,9 @@ def fill_basic_db(db): """ Fill the database with basic data. """ - maintenance_row = Maintenance(active=False) - db.session.add(maintenance_row) + maintenance_row = Maintenance.query.first() + maintenance_row.active = False + db.session.commit() units, users, projects = add_data_to_db() db.session.add_all(units) From b8d9b1545d5a7820654528d5e8dd979ce67ea6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Tue, 11 Oct 2022 08:11:38 +0200 Subject: [PATCH 094/123] Revert "Update maintenance migration and change init-db command" --- dds_web/__init__.py | 15 +++++++++++++++ migrations/versions/eb395af90e18_maintenance.py | 1 - tests/conftest.py | 5 ++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 3d9498d65..2e35b8ccf 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -311,6 +311,21 @@ def load_user(user_id): def fill_db_wrapper(db_type): from dds_web.database import models + maintenance_active: bool = db_type == "production" + flask.current_app.logger.info( + f"Setting maintenance as {'active' if maintenance_active else 'inactive'}..." + ) + + old_maintenance: models.Maintenance = models.Maintenance.query.all() + if len(old_maintenance) > 1: + for row in old_maintenance[1::]: + db.session.delete(row) + old_maintenance[0].active = maintenance_active + else: + new_maintenance: models.Maintenance = models.Maintenance(active=maintenance_active) + db.session.add(new_maintenance) + db.session.commit() + if db_type == "production": username = flask.current_app.config["SUPERADMIN_USERNAME"] password = flask.current_app.config["SUPERADMIN_PASSWORD"] diff --git a/migrations/versions/eb395af90e18_maintenance.py b/migrations/versions/eb395af90e18_maintenance.py index f19b91ade..64ff06536 100644 --- a/migrations/versions/eb395af90e18_maintenance.py +++ b/migrations/versions/eb395af90e18_maintenance.py @@ -24,7 +24,6 @@ def upgrade(): sa.Column("active", sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint("id"), ) - op.execute("INSERT INTO maintenance (active) VALUES (1);") # ### end Alembic commands ### diff --git a/tests/conftest.py b/tests/conftest.py index 550868c5c..8b7c3d43c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -49,9 +49,8 @@ def fill_basic_db(db): """ Fill the database with basic data. """ - maintenance_row = Maintenance.query.first() - maintenance_row.active = False - db.session.commit() + maintenance_row = Maintenance(active=False) + db.session.add(maintenance_row) units, users, projects = add_data_to_db() db.session.add_all(units) From a545d540c720e89055cec85bc0f14c0cd1beac09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Tue, 11 Oct 2022 08:11:58 +0200 Subject: [PATCH 095/123] Revert "Revert "Update maintenance migration and change init-db command"" --- dds_web/__init__.py | 15 --------------- migrations/versions/eb395af90e18_maintenance.py | 1 + tests/conftest.py | 5 +++-- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 2e35b8ccf..3d9498d65 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -311,21 +311,6 @@ def load_user(user_id): def fill_db_wrapper(db_type): from dds_web.database import models - maintenance_active: bool = db_type == "production" - flask.current_app.logger.info( - f"Setting maintenance as {'active' if maintenance_active else 'inactive'}..." - ) - - old_maintenance: models.Maintenance = models.Maintenance.query.all() - if len(old_maintenance) > 1: - for row in old_maintenance[1::]: - db.session.delete(row) - old_maintenance[0].active = maintenance_active - else: - new_maintenance: models.Maintenance = models.Maintenance(active=maintenance_active) - db.session.add(new_maintenance) - db.session.commit() - if db_type == "production": username = flask.current_app.config["SUPERADMIN_USERNAME"] password = flask.current_app.config["SUPERADMIN_PASSWORD"] diff --git a/migrations/versions/eb395af90e18_maintenance.py b/migrations/versions/eb395af90e18_maintenance.py index 64ff06536..f19b91ade 100644 --- a/migrations/versions/eb395af90e18_maintenance.py +++ b/migrations/versions/eb395af90e18_maintenance.py @@ -24,6 +24,7 @@ def upgrade(): sa.Column("active", sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint("id"), ) + op.execute("INSERT INTO maintenance (active) VALUES (1);") # ### end Alembic commands ### diff --git a/tests/conftest.py b/tests/conftest.py index 8b7c3d43c..550868c5c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -49,8 +49,9 @@ def fill_basic_db(db): """ Fill the database with basic data. """ - maintenance_row = Maintenance(active=False) - db.session.add(maintenance_row) + maintenance_row = Maintenance.query.first() + maintenance_row.active = False + db.session.commit() units, users, projects = add_data_to_db() db.session.add_all(units) From d84d7d97693e055ca7e12cf13e692efd0c684b9b Mon Sep 17 00:00:00 2001 From: Ina Date: Tue, 11 Oct 2022 08:48:09 +0200 Subject: [PATCH 096/123] edit maintenance migration to bulk insert --- migrations/versions/eb395af90e18_maintenance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/versions/eb395af90e18_maintenance.py b/migrations/versions/eb395af90e18_maintenance.py index f19b91ade..7abd2ddf6 100644 --- a/migrations/versions/eb395af90e18_maintenance.py +++ b/migrations/versions/eb395af90e18_maintenance.py @@ -18,13 +18,13 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table( + maintenance_table = op.create_table( "maintenance", sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), sa.Column("active", sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint("id"), ) - op.execute("INSERT INTO maintenance (active) VALUES (1);") + op.bulk_insert(maintenance_table, [{"active": True}]) # ### end Alembic commands ### From f5e40ec036854f6886b04802d54f3d72feede99e Mon Sep 17 00:00:00 2001 From: Ina Date: Tue, 11 Oct 2022 11:09:58 +0200 Subject: [PATCH 097/123] maintenance row change in db_init --- dds_web/development/db_init.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dds_web/development/db_init.py b/dds_web/development/db_init.py index 22f8ba422..3595f935a 100644 --- a/dds_web/development/db_init.py +++ b/dds_web/development/db_init.py @@ -28,6 +28,9 @@ def fill_db(): """Fills the database with initial entries used for development.""" + maintenance_row = models.Maintenance.query.first() + maintenance_row.active = False + # Foreign key/relationship updates: # The model with the row db.relationship should append the row of the model with foreign key From 76deb56aad57ae213f01338f3a7b389f4120f05f Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Thu, 13 Oct 2022 15:36:03 +0200 Subject: [PATCH 098/123] add Is Active to project info --- dds_web/api/project.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 1bd40111b..dc0745c70 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -480,6 +480,7 @@ def format_project_dict(self, current_user): "PI": p.pi, "Status": p.current_status, "Last updated": p.date_updated if p.date_updated else p.date_created, + "Is Active": p.is_active } # Get proj size and update total size From 24b2c57d545c416a71fb59f9ea069b391d9fcfb6 Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Thu, 13 Oct 2022 15:38:26 +0200 Subject: [PATCH 099/123] black --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index dc0745c70..d8187d7d3 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -480,7 +480,7 @@ def format_project_dict(self, current_user): "PI": p.pi, "Status": p.current_status, "Last updated": p.date_updated if p.date_updated else p.date_created, - "Is Active": p.is_active + "Is Active": p.is_active, } # Get proj size and update total size From 35711a7f518cf6ca7d5a939b475f016f783ee75a Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Fri, 14 Oct 2022 09:19:02 +0200 Subject: [PATCH 100/123] Ina's review suggestion --- dds_web/api/project.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index d8187d7d3..ca151df9f 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -472,15 +472,26 @@ def format_project_dict(self, current_user): "Unit Personnel", ] + # Get info for projects + get_all = flask.request.json.get("show_all", False) if flask.request.json else False + all_filters = ( + [] if get_all else [models.Project.is_active == True] + ) # Default is to only get active projects + all_filters.append( + models.Project.public_id.in_([x.public_id for x in current_user.projects]) + ) + + # Apply the filters + user_projects = models.Project.query.filter(sqlalchemy.and_(*all_filters)).all() + # Get info for all projects - for p in current_user.projects: + for p in user_projects: project_info = { "Project ID": p.public_id, "Title": p.title, "PI": p.pi, "Status": p.current_status, "Last updated": p.date_updated if p.date_updated else p.date_created, - "Is Active": p.is_active, } # Get proj size and update total size From a48a39ea110e1b47363f92bea10b967a8bd303b1 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 14 Oct 2022 13:40:10 +0200 Subject: [PATCH 101/123] return creator --- dds_web/api/project.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 1bd40111b..2c7641ab7 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -480,6 +480,7 @@ def format_project_dict(self, current_user): "PI": p.pi, "Status": p.current_status, "Last updated": p.date_updated if p.date_updated else p.date_created, + "Created by": p.creator.name, } # Get proj size and update total size From c097e1ff0e32698892a7cab270455119d9caea0f Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 14 Oct 2022 13:55:39 +0200 Subject: [PATCH 102/123] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c6ba7b8f..9caf6d3ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -156,3 +156,7 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - Bug fix: Fix the Invite.projects database model ([#1290](https://github.com/ScilifelabDataCentre/dds_web/pull/1290)) - New endpoint: ListInvites - list invites ([#1294](https://github.com/ScilifelabDataCentre/dds_web/pull/1294)) + +## Sprint (2022-10-14 - 2022-10-28) + +- Return name of project creator from UserProjects ([#1303](https://github.com/ScilifelabDataCentre/dds_web/pull/1303)) From 06f6d6863b25d7d22da80b08e84762cd1d8edb3f Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Wed, 19 Oct 2022 10:14:42 +0200 Subject: [PATCH 103/123] tests --- tests/test_project_listing.py | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/test_project_listing.py b/tests/test_project_listing.py index 1f4e71cdf..1c17fcfaa 100644 --- a/tests/test_project_listing.py +++ b/tests/test_project_listing.py @@ -5,6 +5,7 @@ import unittest # Own +from dds_web import db from dds_web.database import models import tests @@ -57,6 +58,46 @@ def test_list_proj_unit_user(client): assert "Usage" in public_project.keys() and public_project["Usage"] is not None +def test_list_only_active_projects_unit_user(client): + """Unit admin should be able to list only active projects without --show-all flag""" + + # set one of the project as inactive + inactive_project: models.Project = models.Project.query.first() + inactive_project.is_active = False + db.session.commit() + + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) + response = client.get( + tests.DDSEndpoint.LIST_PROJ, + headers=token, + json={"usage": True}, + content_type="application/json", + ) + + assert response.status_code == http.HTTPStatus.OK + assert len(response.json.get("project_info")) == 4 + + +def test_list_all_projects_unit_user(client): + """Unit admin should be able to list inactive projects with the --show-all flag""" + + # set one of the project as inactive + inactive_project: models.Project = models.Project.query.first() + inactive_project.is_active = False + db.session.commit() + + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) + response = client.get( + tests.DDSEndpoint.LIST_PROJ, + headers=token, + json={"usage": True, "show_all": True}, + content_type="application/json", + ) + + assert response.status_code == http.HTTPStatus.OK + assert len(response.json.get("project_info")) == 5 + + def test_proj_private_successful(client): """Successfully get the private key""" From f6c434c36bade337e478fb0547b6851c3609316c Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Wed, 19 Oct 2022 10:34:14 +0200 Subject: [PATCH 104/123] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c6ba7b8f..8f8999c3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -156,3 +156,7 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - Bug fix: Fix the Invite.projects database model ([#1290](https://github.com/ScilifelabDataCentre/dds_web/pull/1290)) - New endpoint: ListInvites - list invites ([#1294](https://github.com/ScilifelabDataCentre/dds_web/pull/1294)) + +## Sprint (2022-10-14 - 2022-10-28) + +- Limit projects listing to active projects only; a `--show-all` flag can be used for listing all projects, active and inactive ([#1302](https://github.com/ScilifelabDataCentre/dds_web/pull/1302)) From 3ddd1c4508a6836f00aa69273c85dcc5bb3e765b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= <35953392+inaod568@users.noreply.github.com> Date: Wed, 19 Oct 2022 12:21:07 +0200 Subject: [PATCH 105/123] Update CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c6573884..997853487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -159,6 +159,5 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe ## Sprint (2022-10-14 - 2022-10-28) - - Limit projects listing to active projects only; a `--show-all` flag can be used for listing all projects, active and inactive ([#1302](https://github.com/ScilifelabDataCentre/dds_web/pull/1302)) - Return name of project creator from UserProjects ([#1303](https://github.com/ScilifelabDataCentre/dds_web/pull/1303)) From 129dfe4b6135000c5d8fa1806700b17e423cd943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Wed, 19 Oct 2022 15:41:31 +0200 Subject: [PATCH 106/123] Make version from the Github build process available inside the container --- .github/workflows/publish_and_trivyscan.yml | 1 + Dockerfiles/backend.Dockerfile | 4 ++++ docker-compose.yml | 1 + 3 files changed, 6 insertions(+) diff --git a/.github/workflows/publish_and_trivyscan.yml b/.github/workflows/publish_and_trivyscan.yml index adc809c42..909d89d8f 100644 --- a/.github/workflows/publish_and_trivyscan.yml +++ b/.github/workflows/publish_and_trivyscan.yml @@ -68,5 +68,6 @@ jobs: file: Dockerfiles/backend.Dockerfile context: . push: true + build-args: version=${{ github.ref_name }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfiles/backend.Dockerfile b/Dockerfiles/backend.Dockerfile index 954690583..9db625df5 100644 --- a/Dockerfiles/backend.Dockerfile +++ b/Dockerfiles/backend.Dockerfile @@ -19,6 +19,10 @@ RUN apk add jpeg-dev zlib-dev libjpeg RUN apk add tzdata ENV TZ="UCT" +# Extract version from Github during build +ARG version +ENV DDS_VERSION=$version + # Copy the content to a code folder in container COPY ./requirements.txt /code/requirements.txt diff --git a/docker-compose.yml b/docker-compose.yml index 11bb9a3a9..1d2d2c885 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -51,6 +51,7 @@ services: working_dir: /code command: sh -c "flask db upgrade && flask init-db $$DB_TYPE && flask run -h 0.0.0.0 -p 5000" environment: + - DDS_VERSION=local-dev - DDS_APP_CONFIG=/code/dds_web/sensitive/dds_app.cfg - FLASK_ENV=development - FLASK_APP=dds_web From ecc0afe92f1dd1f4b693fd12b34468516c84d1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Wed, 19 Oct 2022 15:42:01 +0200 Subject: [PATCH 107/123] Add a custom filter with the dds version --- dds_web/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 3d9498d65..a88ee4d61 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -271,6 +271,11 @@ def load_user(user_id): app.cli.add_command(update_uploaded_file_with_log) app.cli.add_command(lost_files_s3_db) + # Make version available inside jinja templates: + @app.template_filter("dds_version") + def dds_version_filter(_): + return os.environ.get("DDS_VERSION", "Unknown") + with app.app_context(): # Everything in here has access to sessions from dds_web.database import models From 127305d2754ad885933558be6560591f28d2719a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20=C3=96stberg?= Date: Wed, 19 Oct 2022 15:42:27 +0200 Subject: [PATCH 108/123] Add version to the footer of the web pages --- dds_web/templates/base.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dds_web/templates/base.html b/dds_web/templates/base.html index 370901d76..9a1bf9820 100644 --- a/dds_web/templates/base.html +++ b/dds_web/templates/base.html @@ -51,6 +51,11 @@

Important Information