Esempio n. 1
0
def test_vulnerabilities(client: FlaskClient) -> None:

    strings = (
        "xx",
        "x'x",
        'x"x',
        "x`x",
        "x#x",
        "x--x",
        "x\\*x",
        "x*x",
        "x+x",
        "x;x",
        "x(x",
        "x)x",
    )

    for s in strings:
        r = client.get(f"{API_URI}/tests/vulnerabilities/{s}",
                       query_string={"payload": s})
        assert r.status_code == 200

        r = client.post(f"{API_URI}/tests/vulnerabilities/{s}",
                        json={"payload": s})
        assert r.status_code == 200

    # Can't test x//x as url parameter
    r = client.get(f"{API_URI}/tests/vulnerabilities/x",
                   query_string={"payload": "x//x"})
    assert r.status_code == 200

    r = client.post(f"{API_URI}/tests/vulnerabilities/x",
                    json={"payload": "x//x"})
    assert r.status_code == 200
Esempio n. 2
0
        def test_test_unused_credentials(self, client: FlaskClient,
                                         faker: Faker) -> None:

            assert BaseTests.unused_credentials is not None
            assert len(BaseTests.unused_credentials) == 3

            data = {
                "username": BaseTests.unused_credentials[0],
                "password": faker.password(strong=True),
            }

            # Credentials are verified before the inactivity check
            r = client.post(f"{AUTH_URI}/login", json=data)
            assert r.status_code == 401
            resp = self.get_content(r)
            assert resp == "Invalid access credentials"

            data = {
                "username": BaseTests.unused_credentials[0],
                "password": BaseTests.unused_credentials[1],
            }

            # Login is blocked due to inactivity
            r = client.post(f"{AUTH_URI}/login", json=data)
            assert r.status_code == 403
            resp = self.get_content(r)
            assert resp == "Sorry, this account is blocked for inactivity"

            # Also password reset and blocked... how to recover the account !?

            reset_data = {"reset_email": BaseTests.unused_credentials[0]}
            r = client.post(f"{AUTH_URI}/reset", json=reset_data)
            assert r.status_code == 403
            resp = self.get_content(r)
            assert resp == "Sorry, this account is blocked for inactivity"

            events = self.get_last_events(2)
            assert events[0].event == Events.refused_login.value
            assert events[0].payload[
                "username"] == BaseTests.unused_credentials[0]
            assert (events[0].payload["motivation"] ==
                    "account blocked due to inactivity")
            assert events[1].event == Events.refused_login.value
            assert events[1].payload[
                "username"] == BaseTests.unused_credentials[0]
            assert (events[1].payload["motivation"] ==
                    "account blocked due to inactivity")
            assert events[1].url == "/auth/reset"

            # Goodbye temporary user
            self.delete_user(client, BaseTests.unused_credentials[2])
Esempio n. 3
0
    def test_database_exceptions(self, client: FlaskClient, faker: Faker) -> None:

        if not Env.get_bool("AUTH_ENABLE"):
            log.warning("Skipping dabase exceptions tests")
            return

        # This is a special value. The endpoint will try to create a group without
        # shortname. A BadRequest is expected because the database should refuse the
        # entry due to the missing property
        r = client.post(f"{API_URI}/tests/database/400")
        assert r.status_code == 400
        # This is the message of a DatabaseMissingRequiredProperty
        assert self.get_content(r) == "Missing property shortname required by Group"

        auth = Connector.get_authentication_instance()
        default_group = auth.get_group(name=DEFAULT_GROUP_NAME)
        assert default_group is not None

        # the /tests/database endpoint will change the default group fullname
        # as a side effect to the test the database_transaction decorator
        default_fullname = default_group.fullname

        random_name = faker.pystr()

        # This will create a new group with short/full name == random_name

        r = client.post(f"{API_URI}/tests/database/{random_name}")
        assert r.status_code == 200

        default_group = auth.get_group(name=DEFAULT_GROUP_NAME)
        assert default_group is not None

        # As a side effect the fullname of defaut_group is changed...
        assert default_group.fullname != default_fullname

        # ... and this is the new name
        new_fullname = default_group.fullname

        # This will try to create again a group with short/full name == random_name
        # but this will fail due to unique keys
        r = client.post(f"{API_URI}/tests/database/{random_name}")
        assert r.status_code == 409
        # This is the message of a DatabaseDuplicatedEntry
        self.get_content(r) == "A Group already exists with 'shortname': '400'"
        # The default group will not change again because the
        # database_transaction decorator will undo the change
        default_group = auth.get_group(name=DEFAULT_GROUP_NAME)
        assert default_group is not None

        assert default_group.fullname == new_fullname
Esempio n. 4
0
    def test_autocomplete(self, client: FlaskClient) -> None:

        # This test verifies that buildData is always able to randomly create
        # valid inputs for endpoints with inputs defined by marshamallow schemas
        schema = self.get_dynamic_input_schema(client, "tests/autocomplete",
                                               {})

        assert schema[0]["key"] == "elements"
        assert schema[0]["type"] == "string[]"
        assert "autocomplete_endpoint" in schema[0]
        assert "autocomplete_id_bind" in schema[0]
        assert "autocomplete_label_bind" in schema[0]
        assert "autocomplete_show_id" in schema[0]
        assert schema[0]["autocomplete_endpoint"] == "/api/tests/autocomplete"
        assert schema[0]["autocomplete_id_bind"] == "my_id"
        assert schema[0]["autocomplete_label_bind"] == "my_label"
        assert schema[0]["autocomplete_show_id"] is True

        autocomplete_endpoint = f"{SERVER_URI}{schema[0]['autocomplete_endpoint']}"

        r = client.get(f"{autocomplete_endpoint}/nobody")
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 0

        r = client.get(f"{autocomplete_endpoint}/oliver")
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) > 0
        assert schema[0]["autocomplete_id_bind"] in content[0]
        assert schema[0]["autocomplete_label_bind"] in content[0]

        r = client.get(f"{autocomplete_endpoint}/s the")
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) > 0
        assert schema[0]["autocomplete_id_bind"] in content[0]
        assert schema[0]["autocomplete_label_bind"] in content[0]

        rand = random.SystemRandom()

        data = []
        for _ in range(0, 3):
            element = rand.choice(content)
            data.append(element[schema[0]["autocomplete_id_bind"]])

        # put accepts a single id provided by the autocomplete endpoint
        r = client.put(f"{API_URI}/tests/autocomplete",
                       json={"element": data[0]})
        assert r.status_code == 204

        # post accepts a list of ids provided by the autocomplete endpoint
        r = client.post(
            f"{API_URI}/tests/autocomplete",
            json={"elements": orjson.dumps(data).decode("UTF8")},
        )
        assert r.status_code == 204
Esempio n. 5
0
        def test_01_login_ban_not_enabled(self, client: FlaskClient) -> None:

            uuid, data = self.create_user(client)
            # Login attempts are not registered, let's try to fail the login many times
            for _ in range(0, 10):
                self.do_login(client, data["email"], "wrong", status_code=401)

            events = self.get_last_events(1)
            assert events[0].event == Events.failed_login.value
            assert events[0].payload["username"] == data["email"]
            assert events[0].url == "/auth/login"

            # and verify that login is still allowed
            headers, _ = self.do_login(client, data["email"], data["password"])
            assert headers is not None

            events = self.get_last_events(1)
            assert events[0].event == Events.login.value
            assert events[0].user == data["email"]
            assert events[0].url == "/auth/login"

            # Furthermore the login/unlock endpoint is now enabled
            r = client.post(f"{AUTH_URI}/login/unlock/token")
            assert r.status_code == 404

            # Goodbye temporary user
            self.delete_user(client, uuid)
    def upload_file(
        client: FlaskClient,
        headers: Dict[str, str],
        fastq: Path,
        dataset_uuid: str,
        stream: bool = True,
    ) -> Response:
        # get the data for the upload request
        filename = fastq.name
        filesize = fastq.stat().st_size
        data = {
            "name": filename,
            "mimeType": "application/gzip",
            "size": filesize,
            "lastModified": int(fastq.stat().st_mtime),
        }

        r_post: Response = client.post(
            f"{API_URI}/dataset/{dataset_uuid}/files/upload",
            headers=headers,
            data=data)
        if r_post.status_code != 201:
            return r_post

        chunksize = int(filesize / 2) + 1
        range_start = 0

        with open(fastq, "rb") as f:
            while True:
                read_data = f.read(chunksize)
                # No more data to send, exit the loop
                # This case is never reached because in normal conditions
                # the loop is exited when the APIs respond with a code != 206
                if not read_data:  # pragma: no cover
                    break
                if range_start != 0:
                    range_start += 1
                range_max = range_start + chunksize
                if range_max > filesize:
                    range_max = filesize
                headers[
                    "Content-Range"] = f"bytes {range_start}-{range_max}/{filesize}"
                if stream:
                    r: Response = client.put(
                        f"{API_URI}/dataset/{dataset_uuid}/files/upload/{filename}",
                        headers=headers,
                        data=read_data,
                    )
                else:
                    # do not read data to test final size!=expected size
                    r = client.put(
                        f"{API_URI}/dataset/{dataset_uuid}/files/upload/{filename}",
                        headers=headers,
                    )
                if r.status_code != 206:
                    # the upload is completed or an error occurred
                    break
                range_start += chunksize
            return r
Esempio n. 7
0
    def test_inputs(self, client: FlaskClient) -> None:

        # This test verifies that buildData is always able to randomly create
        # valid inputs for endpoints with inputs defined by marshamallow schemas
        schema = self.getDynamicInputSchema(client, "tests/inputs", {})
        data = self.buildData(schema)

        r = client.post(f"{API_URI}/tests/inputs", data=data)
        assert r.status_code == 204
Esempio n. 8
0
    def check_endpoint(
        self,
        client: FlaskClient,
        method: str,
        endpoint: str,
        headers: Optional[Dict[str, str]],
        expected_authorized: bool,
        paths: List[str],
    ) -> List[str]:

        assert method in (
            "GET",
            "POST",
            "PUT",
            "PATCH",
            "DELETE",
        )

        path = self.get_path(method, endpoint)

        assert path in paths

        # SERVER_URI because api and auth are already included in endpoint
        full_endpoint = f"{SERVER_URI}/{endpoint}"

        if method == "GET":
            r = client.get(full_endpoint, headers=headers)
        elif method == "POST":
            r = client.post(full_endpoint, headers=headers)
        elif method == "PUT":
            r = client.put(full_endpoint, headers=headers)
        elif method == "PATCH":
            r = client.patch(full_endpoint, headers=headers)
        elif method == "DELETE":
            r = client.delete(full_endpoint, headers=headers)
        else:  # pragma: no cover
            pytest.fail("Unknown method")

        if expected_authorized:
            assert r.status_code != 401
        else:
            assert r.status_code != 400

        paths.remove(path)
        return paths
Esempio n. 9
0
    def test_outputs(self, client: FlaskClient) -> None:

        r = client.post(f"{API_URI}/tests/outputs/string")
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, str)
        assert response == "string"

        r = client.post(f"{API_URI}/tests/outputs/whatever")
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, str)
        assert response == "string"

        r = client.post(f"{API_URI}/tests/outputs/list")
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert response == ["a", "b", "c", "c"]

        r = client.post(f"{API_URI}/tests/outputs/tuple")
        assert r.status_code == 200
        response = self.get_content(r)
        # Tuples are serialized as lists
        assert isinstance(response, list)
        assert response == ["a", "b", "c", "c"]

        r = client.post(f"{API_URI}/tests/outputs/set")
        assert r.status_code == 200
        response = self.get_content(r)
        # Sets are serialized as lists
        assert isinstance(response, list)
        # But without duplicates :-) (and unordered...)
        assert sorted(response) == ["a", "b", "c"]

        r = client.post(f"{API_URI}/tests/outputs/dict")
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert response == {"a": 1, "b": 2, "c": 3}

        r = client.post(f"{API_URI}/tests/outputs/datetime")
        assert r.status_code == 200
        response = self.get_content(r)
        # datetimes are serialized as strings
        assert isinstance(response, str)
Esempio n. 10
0
    def test_admin_users(self, client: FlaskClient, faker: Faker) -> None:

        if not Env.get_bool("MAIN_LOGIN_ENABLE") or not Env.get_bool(
                "AUTH_ENABLE"):
            log.warning("Skipping admin/users tests")
            return

        project_tile = get_project_configuration("project.title",
                                                 default="YourProject")

        auth = Connector.get_authentication_instance()
        staff_role_enabled = Role.STAFF.value in [
            r.name for r in auth.get_roles()
        ]

        for role in (
                Role.ADMIN,
                Role.STAFF,
        ):

            if not staff_role_enabled:  # pragma: no cover
                log.warning(
                    "Skipping tests of admin/users endpoints, role Staff not enabled"
                )
                continue
            else:
                log.warning("Testing admin/users endpoints as {}", role)

            if role == Role.ADMIN:
                user_email = BaseAuthentication.default_user
                user_password = BaseAuthentication.default_password
            elif role == Role.STAFF:
                _, user_data = self.create_user(client, roles=[Role.STAFF])
                user_email = user_data.get("email")
                user_password = user_data.get("password")

            headers, _ = self.do_login(client, user_email, user_password)
            r = client.get(f"{API_URI}/admin/users", headers=headers)
            assert r.status_code == 200

            schema = self.get_dynamic_input_schema(client, "admin/users",
                                                   headers)
            data = self.buildData(schema)

            data["email_notification"] = True
            data["is_active"] = True
            data["expiration"] = None

            # Event 1: create
            r = client.post(f"{API_URI}/admin/users",
                            json=data,
                            headers=headers)
            assert r.status_code == 200
            uuid = self.get_content(r)
            assert isinstance(uuid, str)

            # A new User is created
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.create.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].url == "/api/admin/users"
            assert "name" in events[0].payload
            assert "surname" in events[0].payload
            assert "email" in events[0].payload

            # Save it for the following tests
            event_target_id1 = events[0].target_id

            mail = self.read_mock_email()
            body = mail.get("body", "")

            # Subject: is a key in the MIMEText
            assert body is not None
            assert mail.get("headers") is not None
            assert f"Subject: {project_tile}: New credentials" in mail.get(
                "headers", "")
            assert data.get("email", "MISSING").lower() in body
            assert (data.get("password", "MISSING") in body
                    or escape(str(data.get("password"))) in body)

            # Test the differences between post and put schema
            post_schema = {s["key"]: s for s in schema}

            tmp_schema = self.get_dynamic_input_schema(client,
                                                       f"admin/users/{uuid}",
                                                       headers,
                                                       method="put")
            put_schema = {s["key"]: s for s in tmp_schema}

            assert "email" in post_schema
            assert post_schema["email"]["required"]
            assert "email" not in put_schema

            assert "name" in post_schema
            assert post_schema["name"]["required"]
            assert "name" in put_schema
            assert not put_schema["name"]["required"]

            assert "surname" in post_schema
            assert post_schema["surname"]["required"]
            assert "surname" in put_schema
            assert not put_schema["surname"]["required"]

            assert "password" in post_schema
            assert post_schema["password"]["required"]
            assert "password" in put_schema
            assert not put_schema["password"]["required"]

            assert "group" in post_schema
            assert post_schema["group"]["required"]
            assert "group" in put_schema
            assert not put_schema["group"]["required"]

            # Event 2: read
            r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers)
            assert r.status_code == 200
            users_list = self.get_content(r)
            assert isinstance(users_list, dict)
            assert len(users_list) > 0
            # email is saved lowercase
            assert users_list.get("email") == data.get("email",
                                                       "MISSING").lower()

            # Access to the user
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.access.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id == event_target_id1
            assert events[0].url == f"/api/admin/users/{event_target_id1}"
            assert len(events[0].payload) == 0

            # Check duplicates
            r = client.post(f"{API_URI}/admin/users",
                            json=data,
                            headers=headers)
            assert r.status_code == 409
            assert (self.get_content(r) ==
                    f"A User already exists with email: {data['email']}")

            data["email"] = BaseAuthentication.default_user
            r = client.post(f"{API_URI}/admin/users",
                            json=data,
                            headers=headers)
            assert r.status_code == 409
            assert (
                self.get_content(r) ==
                f"A User already exists with email: {BaseAuthentication.default_user}"
            )

            # Create another user
            data2 = self.buildData(schema)
            data2["email_notification"] = True
            data2["is_active"] = True
            data2["expiration"] = None

            # Event 3: create
            r = client.post(f"{API_URI}/admin/users",
                            json=data2,
                            headers=headers)
            assert r.status_code == 200
            uuid2 = self.get_content(r)
            assert isinstance(uuid2, str)

            # Another User is created
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.create.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id != event_target_id1
            assert events[0].url == "/api/admin/users"
            assert "name" in events[0].payload
            assert "surname" in events[0].payload
            assert "email" in events[0].payload

            # Save it for the following tests
            event_target_id2 = events[0].target_id

            mail = self.read_mock_email()
            body = mail.get("body", "")
            # Subject: is a key in the MIMEText
            assert body is not None
            assert mail.get("headers") is not None
            assert f"Subject: {project_tile}: New credentials" in mail.get(
                "headers", "")
            assert data2.get("email", "MISSING").lower() in body
            pwd = data2.get("password", "MISSING")
            assert pwd in body or escape(str(pwd)) in body

            # send and invalid user_id
            r = client.put(
                f"{API_URI}/admin/users/invalid",
                json={"name": faker.name()},
                headers=headers,
            )
            assert r.status_code == 404

            # Event 4: modify
            r = client.put(
                f"{API_URI}/admin/users/{uuid}",
                json={"name": faker.name()},
                headers=headers,
            )
            assert r.status_code == 204

            # User 1 modified (same target_id as above)
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.modify.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id == event_target_id1
            assert events[0].url == f"/api/admin/users/{event_target_id1}"
            assert "name" in events[0].payload
            assert "surname" not in events[0].payload
            assert "email" not in events[0].payload
            assert "password" not in events[0].payload

            # email cannot be modified
            new_data = {"email": data.get("email")}
            r = client.put(f"{API_URI}/admin/users/{uuid2}",
                           json=new_data,
                           headers=headers)
            # from webargs >= 6 this endpoint no longer return a 204 but a 400
            # because email is an unknown field
            # assert r.status_code == 204
            assert r.status_code == 400

            # Event 5: read
            r = client.get(f"{API_URI}/admin/users/{uuid2}", headers=headers)
            assert r.status_code == 200
            users_list = self.get_content(r)
            assert isinstance(users_list, dict)
            assert len(users_list) > 0
            # email is not modified -> still equal to data2, not data1
            assert users_list.get("email") != data.get("email",
                                                       "MISSING").lower()
            assert users_list.get("email") == data2.get("email",
                                                        "MISSING").lower()

            # Access to user 2
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.access.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id == event_target_id2
            assert events[0].url == f"/api/admin/users/{event_target_id2}"
            assert len(events[0].payload) == 0

            r = client.delete(f"{API_URI}/admin/users/invalid",
                              headers=headers)
            assert r.status_code == 404

            # Event 6: delete
            r = client.delete(f"{API_URI}/admin/users/{uuid}", headers=headers)
            assert r.status_code == 204

            # User 1 is deleted (same target_id as above)
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.delete.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id == event_target_id1
            assert events[0].url == f"/api/admin/users/{event_target_id1}"
            assert len(events[0].payload) == 0

            r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers)
            assert r.status_code == 404

            # change password of user2
            # Event 7: modify
            newpwd = faker.password(strong=True)
            data = {"password": newpwd, "email_notification": True}
            r = client.put(f"{API_URI}/admin/users/{uuid2}",
                           json=data,
                           headers=headers)
            assert r.status_code == 204

            # User 2 modified (same target_id as above)
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.modify.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id == event_target_id2
            assert events[0].url == f"/api/admin/users/{event_target_id2}"
            assert "name" not in events[0].payload
            assert "surname" not in events[0].payload
            assert "email" not in events[0].payload
            assert "password" in events[0].payload
            assert "email_notification" in events[0].payload
            # Verify that the password is obfuscated in the log:
            assert events[0].payload["password"] == OBSCURE_VALUE

            mail = self.read_mock_email()
            # Subject: is a key in the MIMEText
            assert mail.get("body", "") is not None
            assert mail.get("headers", "") is not None
            assert f"Subject: {project_tile}: Password changed" in mail.get(
                "headers", "")
            assert data2.get("email",
                             "MISSING").lower() in mail.get("body", "")
            assert newpwd in mail.get(
                "body", "") or escape(newpwd) in mail.get("body", "")

            # login with a newly created user
            headers2, _ = self.do_login(client, data2.get("email"), newpwd)

            # normal users cannot access to this endpoint
            r = client.get(f"{API_URI}/admin/users", headers=headers2)
            assert r.status_code == 401

            r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers2)
            assert r.status_code == 401

            r = client.post(f"{API_URI}/admin/users",
                            json=data,
                            headers=headers2)
            assert r.status_code == 401

            r = client.put(
                f"{API_URI}/admin/users/{uuid}",
                json={"name": faker.name()},
                headers=headers2,
            )
            assert r.status_code == 401

            r = client.delete(f"{API_URI}/admin/users/{uuid}",
                              headers=headers2)
            assert r.status_code == 401

            # Users are not authorized to /admin/tokens
            # These two tests should be moved in test_endpoints_tokens.py
            r = client.get(f"{API_URI}/admin/tokens", headers=headers2)
            assert r.status_code == 401
            r = client.delete(f"{API_URI}/admin/tokens/xyz", headers=headers2)
            assert r.status_code == 401

            # let's delete the second user
            # Event 8: delete
            r = client.delete(f"{API_URI}/admin/users/{uuid2}",
                              headers=headers)
            assert r.status_code == 204

            # User 2 is deleted (same target_id as above)
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.delete.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id == event_target_id2
            assert events[0].url == f"/api/admin/users/{event_target_id2}"
            assert len(events[0].payload) == 0

            # Restore the default password (changed due to FORCE_FIRST_PASSWORD_CHANGE)
            # or MAX_PASSWORD_VALIDITY errors
            r = client.get(f"{AUTH_URI}/profile", headers=headers)
            assert r.status_code == 200
            content = self.get_content(r)
            assert isinstance(content, dict)
            uuid = str(content.get("uuid"))

            data = {
                "password": user_password,
                # very important, otherwise the default user will lose its role
                "roles": orjson.dumps([role]).decode("UTF8"),
            }
            # Event 9: modify
            r = client.put(f"{API_URI}/admin/users/{uuid}",
                           json=data,
                           headers=headers)
            assert r.status_code == 204

            # Default user is modified
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.modify.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id != event_target_id1
            assert events[0].target_id != event_target_id2
            assert events[0].url != f"/api/admin/users/{event_target_id1}"
            assert events[0].url != f"/api/admin/users/{event_target_id2}"
            assert "name" not in events[0].payload
            assert "surname" not in events[0].payload
            assert "email" not in events[0].payload
            assert "password" in events[0].payload
            assert "roles" in events[0].payload
            assert "email_notification" not in events[0].payload
            # Verify that the password is obfuscated in the log:
            assert events[0].payload["password"] == OBSCURE_VALUE

            r = client.get(f"{AUTH_URI}/logout", headers=headers)
            assert r.status_code == 204
Esempio n. 11
0
    def test_staff_restrictions(self, client: FlaskClient,
                                faker: Faker) -> None:

        if not Env.get_bool("MAIN_LOGIN_ENABLE") or not Env.get_bool(
                "AUTH_ENABLE"):
            log.warning("Skipping admin/users tests")
            return

        auth = Connector.get_authentication_instance()
        staff_role_enabled = Role.STAFF.value in [
            r.name for r in auth.get_roles()
        ]

        if not staff_role_enabled:  # pragma: no cover
            log.warning(
                "Skipping tests of admin/users restrictions, role Staff not enabled"
            )
            return

        staff_uuid, staff_data = self.create_user(client, roles=[Role.STAFF])
        staff_email = staff_data.get("email")
        staff_password = staff_data.get("password")
        staff_headers, _ = self.do_login(client, staff_email, staff_password)

        user_uuid, _ = self.create_user(client, roles=[Role.USER])

        admin_headers, _ = self.do_login(client, None, None)

        r = client.get(f"{AUTH_URI}/profile", headers=admin_headers)
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, dict)
        admin_uuid = content.get("uuid")

        # Staff users are not allowed to retrieve Admins' data
        r = client.get(f"{API_URI}/admin/users/{user_uuid}",
                       headers=admin_headers)
        assert r.status_code == 200

        r = client.get(f"{API_URI}/admin/users/{staff_uuid}",
                       headers=admin_headers)
        assert r.status_code == 200

        r = client.get(f"{API_URI}/admin/users/{admin_uuid}",
                       headers=admin_headers)
        assert r.status_code == 200

        r = client.get(f"{API_URI}/admin/users/{user_uuid}",
                       headers=staff_headers)
        assert r.status_code == 200

        r = client.get(f"{API_URI}/admin/users/{staff_uuid}",
                       headers=staff_headers)
        assert r.status_code == 200

        r = client.get(f"{API_URI}/admin/users/{admin_uuid}",
                       headers=staff_headers)
        assert r.status_code == 404
        content = self.get_content(r)
        assert content == "This user cannot be found or you are not authorized"

        # Staff users are not allowed to edit Admins
        r = client.put(
            f"{API_URI}/admin/users/{admin_uuid}",
            json={
                "name": faker.name(),
                "roles": orjson.dumps([Role.STAFF]).decode("UTF8"),
            },
            headers=staff_headers,
        )
        assert r.status_code == 404
        content = self.get_content(r)
        assert content == "This user cannot be found or you are not authorized"

        r = client.put(
            f"{API_URI}/admin/users/{staff_uuid}",
            json={
                "name": faker.name(),
                "roles": orjson.dumps([Role.STAFF]).decode("UTF8"),
            },
            headers=staff_headers,
        )
        assert r.status_code == 204

        r = client.put(
            f"{API_URI}/admin/users/{user_uuid}",
            json={
                "name": faker.name(),
                "roles": orjson.dumps([Role.USER]).decode("UTF8"),
            },
            headers=staff_headers,
        )
        assert r.status_code == 204

        # Admin role is not allowed for Staff users
        tmp_schema = self.get_dynamic_input_schema(client, "admin/users",
                                                   admin_headers)
        post_schema = {s["key"]: s for s in tmp_schema}
        assert "roles" in post_schema
        assert "options" in post_schema["roles"]
        assert "normal_user" in post_schema["roles"]["options"]
        assert "admin_root" in post_schema["roles"]["options"]

        tmp_schema = self.get_dynamic_input_schema(client,
                                                   f"admin/users/{user_uuid}",
                                                   admin_headers,
                                                   method="put")
        put_schema = {s["key"]: s for s in tmp_schema}

        assert "roles" in put_schema
        assert "options" in post_schema["roles"]
        assert "normal_user" in post_schema["roles"]["options"]
        assert "admin_root" in post_schema["roles"]["options"]

        tmp_schema = self.get_dynamic_input_schema(client, "admin/users",
                                                   staff_headers)
        post_schema = {s["key"]: s for s in tmp_schema}
        assert "roles" in post_schema
        assert "options" in post_schema["roles"]
        assert "normal_user" in post_schema["roles"]["options"]
        assert "admin_root" not in post_schema["roles"]["options"]

        tmp_schema = self.get_dynamic_input_schema(client,
                                                   f"admin/users/{user_uuid}",
                                                   staff_headers,
                                                   method="put")
        put_schema = {s["key"]: s for s in tmp_schema}

        assert "roles" in put_schema
        assert "options" in post_schema["roles"]
        assert "normal_user" in post_schema["roles"]["options"]
        assert "admin_root" not in post_schema["roles"]["options"]

        # Staff can't send role admin on put
        r = client.put(
            f"{API_URI}/admin/users/{user_uuid}",
            json={
                "name": faker.name(),
                "roles": orjson.dumps([Role.ADMIN]).decode("UTF8"),
            },
            headers=staff_headers,
        )
        assert r.status_code == 400

        # Staff can't send role admin on post
        schema = self.get_dynamic_input_schema(client, "admin/users",
                                               staff_headers)
        data = self.buildData(schema)

        data["email_notification"] = True
        data["is_active"] = True
        data["expiration"] = None
        data["roles"] = orjson.dumps([Role.ADMIN]).decode("UTF8")

        r = client.post(f"{API_URI}/admin/users",
                        json=data,
                        headers=staff_headers)
        assert r.status_code == 400

        # Admin users are filtered out when asked from a Staff user
        r = client.get(f"{API_URI}/admin/users", headers=admin_headers)
        assert r.status_code == 200
        users_list = self.get_content(r)
        assert isinstance(users_list, list)
        assert len(users_list) > 0
        email_list = [u.get("email") for u in users_list]
        assert staff_email in email_list
        assert BaseAuthentication.default_user in email_list

        r = client.get(f"{API_URI}/admin/users", headers=staff_headers)
        assert r.status_code == 200
        users_list = self.get_content(r)
        assert isinstance(users_list, list)
        assert len(users_list) > 0
        email_list = [u.get("email") for u in users_list]
        assert staff_email in email_list
        assert BaseAuthentication.default_user not in email_list

        # Staff users are not allowed to delete Admins
        r = client.delete(f"{API_URI}/admin/users/{admin_uuid}",
                          headers=staff_headers)
        assert r.status_code == 404
        content = self.get_content(r)
        assert content == "This user cannot be found or you are not authorized"

        r = client.delete(f"{API_URI}/admin/users/{user_uuid}",
                          headers=staff_headers)
        assert r.status_code == 204
Esempio n. 12
0
    def test_inputs(self, client: FlaskClient) -> None:

        # This test verifies that buildData is always able to randomly create
        # valid inputs for endpoints with inputs defined by marshamallow schemas
        schema = self.get_dynamic_input_schema(client, "tests/inputs", {})
        # Expected number of fields
        assert len(schema) == 14
        for field in schema:

            # Always in the schema
            assert "key" in field
            assert "type" in field
            assert "label" in field
            assert "description" in field
            assert "required" in field

            # Other optional keys
            # - default
            # - min
            # - max
            # - options
            # - schema in case of nested fields

        field = schema[0]
        assert len(field) == 6  # 5 mandatory fields + min
        assert field["key"] == "mystr"
        assert field["type"] == "string"
        # This is the default case: both label and description are not explicitly set
        # if key is lower-cased the corrisponding label will be titled
        assert field["label"] == field["key"].title()
        assert field["description"] == field["label"]
        assert field["required"]
        assert "min" in field
        assert field["min"] == 4
        assert "max" not in field

        field = schema[1]
        assert len(field) == 5  # 5 mandatory fields, min and max not set
        assert field["key"] == "MYDATE"
        assert field["type"] == "date"
        # Here the key is not lower cased and the label is not explicitly set
        # So the label will exactly match the key (without additiona of .title)
        assert field["label"] == field["key"]
        assert field["label"] != field["key"].title()
        assert field["description"] == field["label"]
        assert field["required"]

        field = schema[2]
        assert len(field) == 7  # 5 mandatory fields + min + max
        assert field["key"] == "MYDATETIME"
        assert field["type"] == "datetime"
        # Here the key is not lower cased and the label is not explicitly set
        # So the label will exactly match the key (without additiona of .title)
        assert field["label"] == field["key"]
        assert field["label"] != field["key"].title()
        assert field["description"] == field["label"]
        assert field["required"]
        assert "min" in field
        assert "max" in field

        field = schema[3]
        assert len(field) == 7  # 5 mandatory fields + min + max
        assert field["key"] == "myint_exclusive"
        assert field["type"] == "int"
        # Here an explicit label is defined but not a description, so is == to the label
        assert field["label"] != field["key"]
        assert field["label"] != field["key"].title()
        assert field["label"] == "Int exclusive field"
        assert field["description"] == field["label"]
        assert field["required"]
        assert "min" in field
        assert field["min"] == 2
        assert "max" in field
        assert field["max"] == 9

        field = schema[4]
        assert len(field) == 7  # 5 mandatory fields + min + max
        assert field["key"] == "myint_inclusive"
        assert field["type"] == "int"
        # Here both label and description are explicitly set
        assert field["label"] != field["key"]
        assert field["label"] != field["key"].title()
        assert field["label"] == "Int inclusive field"
        assert field["description"] != field["label"]
        assert field["description"] == "This field accepts values in a defined range"
        assert field["required"]
        assert "min" in field
        assert field["min"] == 1
        assert "max" in field
        assert field["max"] == 10

        field = schema[5]
        assert len(field) == 6  # 5 mandatory fields + options
        assert field["key"] == "myselect"
        assert field["type"] == "string"
        assert field["label"] == field["key"].title()
        assert field["description"] == field["label"]
        assert field["required"]
        assert "options" in field
        assert isinstance(field["options"], dict)
        assert len(field["options"]) == 2
        assert "a" in field["options"]
        assert "b" in field["options"]
        # The field defines labels and keys for all options
        assert field["options"]["a"] == "A"
        assert field["options"]["b"] == "B"

        field = schema[6]
        assert len(field) == 6  # 5 mandatory fields + options
        assert field["key"] == "myselect2"
        assert field["type"] == "string"
        assert field["label"] == field["key"].title()
        assert field["description"] == field["label"]
        assert field["required"]
        assert "options" in field
        assert isinstance(field["options"], dict)
        assert len(field["options"]) == 2
        assert "a" in field["options"]
        assert "b" in field["options"]
        # The field wrongly defines labels, so are defaulted to keys
        assert field["options"]["a"] == "a"
        assert field["options"]["b"] == "b"

        field = schema[7]
        assert len(field) == 6  # 5 mandatory fields + max
        assert field["key"] == "mymaxstr"
        assert field["type"] == "string"
        assert field["label"] == field["key"].title()
        assert field["description"] == field["label"]
        assert field["required"]
        assert "min" not in field
        assert "max" in field
        assert field["max"] == 7

        field = schema[8]
        assert len(field) == 7  # 5 mandatory fields + min + max
        assert field["key"] == "myequalstr"
        assert field["type"] == "string"
        assert field["label"] == field["key"].title()
        assert field["description"] == field["label"]
        assert field["required"]
        assert "min" in field
        assert "max" in field
        assert field["min"] == 6
        assert field["max"] == 6

        field = schema[9]
        assert len(field) == 6  # 5 mandatory fields + schema
        assert field["key"] == "mynested"
        assert field["type"] == "nested"
        assert field["label"] == field["key"].title()
        assert field["description"] == field["label"]
        assert field["required"]
        assert "schema" in field

        field = schema[10]
        assert len(field) == 6  # 5 mandatory fields + schema
        assert field["key"] == "mynullablenested"
        assert field["type"] == "nested"
        assert field["label"] == field["key"].title()
        assert field["description"] == field["label"]
        assert field["required"]
        assert "schema" in field

        field = schema[11]
        assert len(field) == 5  # 5 mandatory fields
        assert field["key"] == "mylist"
        assert field["type"] == "string[]"
        assert field["label"] == field["key"].title()
        assert field["description"] == field["label"]
        assert field["required"]

        field = schema[12]
        assert len(field) == 5  # 5 mandatory fields
        assert field["key"] == "mylist2"
        # The list is defined as List(CustomInt) and CustomInt is resolved as int
        assert field["type"] == "int[]"
        assert field["label"] == field["key"].title()
        assert field["description"] == field["label"]
        assert field["required"]

        field = schema[13]
        assert len(field) == 5  # 5 mandatory fields
        assert field["key"] == "mylist3"
        # The type is key[] ... should be something more explicative like FieldName[]
        # assert field["type"] == "CustomGenericField[]"
        assert field["type"] == "mylist3[]"
        assert field["label"] == field["key"].title()
        assert field["description"] == field["label"]
        assert field["required"]

        data = self.buildData(schema)

        # mylist3 is a list of custom field, buildData can't automatically set a value
        assert "mylist3" not in data
        data["mylist3"] = orjson.dumps(["mycustominputvalue"]).decode("UTF8")

        r = client.post(f"{API_URI}/tests/inputs", json=data)
        assert r.status_code == 204

        # This is to verify that access_token, if provided is excluded from parameters
        # And do not raise any ValidationError for unknown input

        if Env.get_bool("AUTH_ENABLE"):
            _, token = self.do_login(client, None, None)
            data["access_token"] = token
            r = client.post(f"{API_URI}/tests/inputs", json=data)
            assert r.status_code == 204

        # This is to verify that unknown inputs raise a ValidationError
        data["unknown"] = "input"
        r = client.post(f"{API_URI}/tests/inputs", json=data)
        assert r.status_code == 400
Esempio n. 13
0
    def test_chunked_upload_and_download(self, client: FlaskClient,
                                         faker: Faker) -> None:

        warnings.filterwarnings(
            "ignore", message="unclosed file <_io.BufferedReader name=")

        self.fname = self.get("fname")
        self.fcontent = self.get("fcontent")

        # as defined in test_upload.py for chunked uploads
        upload_folder = "fixed"

        r = client.post(f"{API_URI}/tests/chunkedupload", data={"force": True})
        assert r.status_code == 400

        filename = "fixed.filename.txt"
        data = {
            "force": True,
            "name": filename,
            "size": "999",
            "mimeType": "application/zip",
            "lastModified": 1590302749209,
        }
        r = client.post(f"{API_URI}/tests/chunkedupload", data=data)
        assert r.status_code == 201
        assert self.get_content(r) == ""
        upload_endpoint = get_location_header(
            r.headers, expected=f"{API_URI}/tests/chunkedupload/{filename}")

        data["force"] = False
        r = client.post(f"{API_URI}/tests/chunkedupload", data=data)
        assert r.status_code == 409
        assert self.get_content(r) == f"File '{filename}' already exists"

        with io.StringIO(faker.text()) as f:
            r = client.put(upload_endpoint, data=f)
        assert r.status_code == 400
        assert self.get_content(r) == "Invalid request"

        with io.StringIO(faker.text()) as f:
            r = client.put(
                upload_endpoint,
                data=f,
                headers={"Content-Range": "!"},
            )
        assert r.status_code == 400
        assert self.get_content(r) == "Invalid request"

        up_data = faker.pystr(min_chars=24, max_chars=48).lower()
        STR_LEN = len(up_data)
        with io.StringIO(up_data[0:5]) as f:
            r = client.put(
                upload_endpoint,
                data=f,
                headers={"Content-Range": f"bytes 0-5/{STR_LEN}"},
            )
        assert r.status_code == 206
        assert self.get_content(r) == "partial"

        destination_path = DATA_PATH.joinpath(upload_folder, filename)
        assert destination_path.exists()
        # The file is still writeable because the upload is in progress
        assert oct(os.stat(destination_path).st_mode & 0o777) != "0o440"

        with io.StringIO(up_data[5:]) as f:
            r = client.put(
                upload_endpoint,
                data=f,
                headers={"Content-Range": f"bytes 5-{STR_LEN}/{STR_LEN}"},
            )
        assert r.status_code == 200
        c = self.get_content(r)
        assert isinstance(c, dict)
        assert c.get("filename") is not None
        uploaded_filename = c.get("filename")
        meta = c.get("meta")
        assert meta is not None
        assert meta.get("charset") == "us-ascii"
        assert meta.get("type") == "text/plain"

        destination_path = DATA_PATH.joinpath(upload_folder, filename)
        assert destination_path.exists()
        assert oct(os.stat(destination_path).st_mode & 0o777) == "0o440"

        r = client.get(
            f"{API_URI}/tests/download/{upload_folder}/{uploaded_filename}")
        assert r.status_code == 200
        content = r.data.decode("utf-8")
        assert content == up_data

        r = client.get(
            f"{API_URI}/tests/download/{upload_folder}/{uploaded_filename}")
        assert r.status_code == 200
        content = r.data.decode("utf-8")
        assert content == up_data

        r = client.get(
            f"{API_URI}/tests/download/{upload_folder}/{uploaded_filename}",
            headers={"Range": ""},
        )
        assert r.status_code == 416

        r = client.get(
            f"{API_URI}/tests/download/{upload_folder}/{uploaded_filename}",
            headers={"Range": f"0-{STR_LEN - 1}"},
        )
        assert r.status_code == 416

        r = client.get(
            f"{API_URI}/tests/download/{upload_folder}/{uploaded_filename}",
            headers={"Range": "bytes=0-9999999999999999"},
        )
        assert r.status_code == 206

        r = client.get(
            f"{API_URI}/tests/download/{upload_folder}/{uploaded_filename}",
            headers={"Range": "bytes=0-4"},
        )
        assert r.status_code == 206
        content = r.data.decode("utf-8")
        assert content == up_data[0:5]

        r = client.get(
            f"{API_URI}/tests/download/{upload_folder}/{uploaded_filename}",
            headers={"Range": f"bytes=5-{STR_LEN - 1}"},
        )
        assert r.status_code == 206
        content = r.data.decode("utf-8")
        assert content == up_data[5:]

        r = client.get(
            f"{API_URI}/tests/download/{upload_folder}/{uploaded_filename}",
            headers={"Range": f"bytes=0-{STR_LEN - 1}"},
        )
        assert r.status_code == 206
        content = r.data.decode("utf-8")
        assert content == up_data

        # Send a new string as content file. Will be appended as prefix
        up_data2 = faker.pystr(min_chars=24, max_chars=48)
        STR_LEN = len(up_data2)
        with io.StringIO(up_data2) as f:
            r = client.put(
                upload_endpoint,
                data=f,
                headers={"Content-Range": f"bytes */{STR_LEN}"},
            )
        assert r.status_code == 503
        assert self.get_content(
            r) == "Permission denied: failed to write the file"

        # force the file to be writeable again
        destination_path = DATA_PATH.joinpath(upload_folder, filename)
        # -rw-rw----
        destination_path.chmod(0o660)

        with io.StringIO(up_data2) as f:
            r = client.put(
                upload_endpoint,
                data=f,
                headers={"Content-Range": f"bytes */{STR_LEN}"},
            )

        assert r.status_code == 200

        destination_path = DATA_PATH.joinpath(upload_folder, filename)
        assert destination_path.exists()
        # File permissions are restored
        assert oct(os.stat(destination_path).st_mode & 0o777) == "0o440"

        # c = self.get_content(r)
        # assert c.get('filename') is not None
        # uploaded_filename = c.get('filename')
        # meta = c.get('meta')
        # assert meta is not None
        # assert meta.get('charset') == 'us-ascii'
        # assert meta.get('type') == 'text/plain'

        # r = client.get(
        #     f'{API_URI}/tests/download/{upload_folder}/{uploaded_filename}'
        # )
        # assert r.status_code == 200
        # content = r.data.decode('utf-8')
        # # Uhmmm... should not be up_data2 + up_data ??
        # assert content == up_data + up_data2

        data["force"] = False
        r = client.post(f"{API_URI}/tests/chunkedupload", data=data)
        assert r.status_code == 409
        err = f"File '{uploaded_filename}' already exists"
        assert self.get_content(r) == err

        data["force"] = True
        r = client.post(f"{API_URI}/tests/chunkedupload", data=data)
        assert r.status_code == 201
        assert self.get_content(r) == ""
        upload_endpoint = get_location_header(
            r.headers, expected=f"{API_URI}/tests/chunkedupload/{filename}")

        data["name"] = "fixed.filename.notallowed"
        data["force"] = False
        r = client.post(f"{API_URI}/tests/chunkedupload", data=data)
        assert r.status_code == 400
        assert self.get_content(r) == "File extension not allowed"

        # Send an upload on a file endpoint not previously initialized
        filename = f"{faker.pystr()}.txt"
        with io.StringIO(up_data2) as f:
            r = client.put(
                f"{API_URI}/tests/chunkedupload/{filename}",
                data=f,
                headers={"Content-Range": f"bytes */{STR_LEN}"},
            )

        assert r.status_code == 503
        error = "Permission denied: the destination file does not exist"
        assert self.get_content(r) == error

        destination_path = DATA_PATH.joinpath(upload_folder, filename)
        assert not destination_path.exists()
Esempio n. 14
0
    def test_password_reset(self, client: FlaskClient, faker: Faker) -> None:

        if not Env.get_bool("ALLOW_PASSWORD_RESET") or not Env.get_bool("AUTH_ENABLE"):
            log.warning("Password reset is disabled, skipping tests")
            return

        project_tile = get_project_configuration("project.title", default="YourProject")
        proto = "https" if PRODUCTION else "http"

        # Request password reset, missing information
        r = client.post(f"{AUTH_URI}/reset")
        assert r.status_code == 400

        # Request password reset, missing information
        r = client.post(f"{AUTH_URI}/reset", json=faker.pydict(2))
        assert r.status_code == 400

        headers, _ = self.do_login(client, None, None)

        # Request password reset, wrong email
        wrong_email = faker.ascii_email()
        data = {"reset_email": wrong_email}
        r = client.post(f"{AUTH_URI}/reset", json=data)
        assert r.status_code == 403
        msg = f"Sorry, {wrong_email} is not recognized as a valid username"
        assert self.get_content(r) == msg

        # Request password reset, correct email
        data = {"reset_email": BaseAuthentication.default_user}
        r = client.post(f"{AUTH_URI}/reset", json=data)
        assert r.status_code == 200

        events = self.get_last_events(1)
        assert events[0].event == Events.reset_password_request.value
        assert events[0].user == data["reset_email"]
        assert events[0].url == "/auth/reset"

        resetmsg = "We'll send instructions to the email provided "
        resetmsg += "if it's associated with an account. "
        resetmsg += "Please check your spam/junk folder."

        assert self.get_content(r) == resetmsg

        mail = self.read_mock_email()
        body = mail.get("body")
        assert body is not None
        assert mail.get("headers") is not None
        # Subject: is a key in the MIMEText
        assert f"Subject: {project_tile}: Password Reset" in mail.get("headers", "")
        assert f"{proto}://localhost/public/reset/" in body

        token = self.get_token_from_body(body)
        assert token is not None

        # Do password reset
        r = client.put(f"{AUTH_URI}/reset/thisisatoken", json={})
        # this token is not valid
        assert r.status_code == 400

        # Check if token is valid
        r = client.put(f"{AUTH_URI}/reset/{token}", json={})
        assert r.status_code == 204

        # Token is still valid because no password still sent
        r = client.put(f"{AUTH_URI}/reset/{token}", json={})
        assert r.status_code == 204

        # Missing information
        data = {
            "new_password": BaseAuthentication.default_password,
        }
        r = client.put(f"{AUTH_URI}/reset/{token}", json=data)
        assert r.status_code == 400
        assert self.get_content(r) == "Invalid password"

        data = {
            "password_confirm": BaseAuthentication.default_password,
        }
        r = client.put(f"{AUTH_URI}/reset/{token}", json=data)
        assert r.status_code == 400
        assert self.get_content(r) == "Invalid password"

        # Request with old password
        data = {
            "new_password": BaseAuthentication.default_password,
            "password_confirm": BaseAuthentication.default_password,
        }
        r = client.put(f"{AUTH_URI}/reset/{token}", json=data)
        assert r.status_code == 409
        error = "The new password cannot match the previous password"
        assert self.get_content(r) == error

        min_pwd_len = Env.get_int("AUTH_MIN_PASSWORD_LENGTH", 9999)

        # Password too short
        data["new_password"] = faker.password(min_pwd_len - 1)
        data["password_confirm"] = faker.password(min_pwd_len - 1)
        r = client.put(f"{AUTH_URI}/reset/{token}", json=data)
        assert r.status_code == 400
        data["password_confirm"] = data["new_password"]
        r = client.put(f"{AUTH_URI}/reset/{token}", json=data)
        assert r.status_code == 400

        data["new_password"] = faker.password(min_pwd_len, strong=True)
        data["password_confirm"] = faker.password(min_pwd_len, strong=True)
        r = client.put(f"{AUTH_URI}/reset/{token}", json=data)
        assert r.status_code == 400
        assert self.get_content(r) == "New password does not match with confirmation"

        new_pwd = faker.password(min_pwd_len, strong=True)
        data["new_password"] = new_pwd
        data["password_confirm"] = new_pwd
        r = client.put(f"{AUTH_URI}/reset/{token}", json=data)
        assert r.status_code == 200

        # After a change password a spam of delete Token is expected
        # Reverse the list and skip all delete tokens to find the change password event
        events = self.get_last_events(100)
        events.reverse()
        for event in events:
            if event.event == Events.delete.value:
                assert event.target_type == "Token"
                continue

            assert event.event == Events.change_password.value
            assert event.user == BaseAuthentication.default_user
            break

        self.do_login(client, None, None, status_code=401)
        headers, _ = self.do_login(client, None, new_pwd)

        # Token is no longer valid
        r = client.put(f"{AUTH_URI}/reset/{token}", json={})
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid reset token"

        # Restore the default password
        if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"):
            data["totp_code"] = BaseTests.generate_totp(BaseAuthentication.default_user)

        data["password"] = new_pwd
        data["new_password"] = BaseAuthentication.default_password
        data["password_confirm"] = data["new_password"]
        r = client.put(f"{AUTH_URI}/profile", json=data, headers=headers)
        assert r.status_code == 204

        # After a change password a spam of delete Token is expected
        # Reverse the list and skip all delete tokens to find the change password event
        events = self.get_last_events(100)
        events.reverse()
        for event in events:
            if event.event == Events.delete.value:
                assert event.target_type == "Token"
                continue

            assert event.event == Events.change_password.value
            assert event.user == BaseAuthentication.default_user
            break

        self.do_login(client, None, new_pwd, status_code=401)
        self.do_login(client, None, None)

        # Token created for another user
        token = self.get_crafted_token("r")
        r = client.put(f"{AUTH_URI}/reset/{token}", json={})
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid reset token"

        # Token created for another user
        token = self.get_crafted_token("r", wrong_algorithm=True)
        r = client.put(f"{AUTH_URI}/reset/{token}", json={})
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid reset token"

        # Token created for another user
        token = self.get_crafted_token("r", wrong_secret=True)
        r = client.put(f"{AUTH_URI}/reset/{token}", json={})
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid reset token"

        headers, _ = self.do_login(client, None, None)
        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        uuid = response.get("uuid")

        token = self.get_crafted_token("x", user_id=uuid)
        r = client.put(f"{AUTH_URI}/reset/{token}", json={})
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid reset token"

        # token created for the correct user, but from outside the system!!
        token = self.get_crafted_token("r", user_id=uuid)
        r = client.put(f"{AUTH_URI}/reset/{token}", json={})
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid reset token"

        # Immature token
        token = self.get_crafted_token("r", user_id=uuid, immature=True)
        r = client.put(f"{AUTH_URI}/reset/{token}", json={})
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid reset token"

        # Expired token
        token = self.get_crafted_token("r", user_id=uuid, expired=True)
        r = client.put(f"{AUTH_URI}/reset/{token}", json={})
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid reset token: this request is expired"
Esempio n. 15
0
    def test_neo4j_inputs(self, client: FlaskClient) -> None:

        headers, _ = self.do_login(client, None, None)
        schema = self.get_dynamic_input_schema(client, "tests/neo4jinputs", headers)
        assert len(schema) == 1

        field = schema[0]
        assert field["key"] == "choice"
        # This is because the Neo4jChoice field is not completed for deserialization
        # It is should be automatically translated into a select, with options by
        # including a validation OneOf
        assert "options" not in field

        r = client.post(
            f"{API_URI}/tests/neo4jinputs", json={"choice": "A"}, headers=headers
        )
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "choice" in response
        assert "key" in response["choice"]
        assert "description" in response["choice"]
        assert response["choice"]["key"] == "A"
        assert response["choice"]["description"] == "AAA"

        assert "relationship_count" in response
        assert isinstance(response["relationship_count"], int)
        assert response["relationship_count"] > 0

        assert "relationship_single" in response
        assert isinstance(response["relationship_single"], dict)
        assert "uuid" in response["relationship_single"]

        assert "relationship_many" in response
        assert isinstance(response["relationship_many"], list)
        assert len(response["relationship_many"]) > 0
        assert isinstance(response["relationship_many"][0], dict)
        assert "token_type" in response["relationship_many"][0]

        r = client.post(
            f"{API_URI}/tests/neo4jinputs", json={"choice": "B"}, headers=headers
        )
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "choice" in response
        assert "key" in response["choice"]
        assert "description" in response["choice"]
        assert response["choice"]["key"] == "B"
        assert response["choice"]["description"] == "BBB"

        r = client.post(
            f"{API_URI}/tests/neo4jinputs", json={"choice": "C"}, headers=headers
        )
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "choice" in response
        assert "key" in response["choice"]
        assert "description" in response["choice"]
        assert response["choice"]["key"] == "C"
        assert response["choice"]["description"] == "CCC"

        r = client.post(
            f"{API_URI}/tests/neo4jinputs", json={"choice": "D"}, headers=headers
        )
        # This should fail, but Neo4jChoice are not validated as input
        # assert r.status_code == 400
        # Since validation is not implemented, D is accepted But since it is
        # not included in the choice, the description will simply match the key
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "choice" in response
        assert "key" in response["choice"]
        assert "description" in response["choice"]
        assert response["choice"]["key"] == "D"
        assert response["choice"]["description"] == "D"
Esempio n. 16
0
    def test_api_phenotype(self, client: FlaskClient, faker: Faker) -> None:
        # setup the test env
        (
            admin_headers,
            uuid_group_A,
            user_A1_uuid,
            user_A1_headers,
            uuid_group_B,
            user_B1_uuid,
            user_B1_headers,
            user_B2_uuid,
            user_B2_headers,
            study1_uuid,
            study2_uuid,
        ) = create_test_env(client, faker, study=True)

        # create a new phenotype with wrong age
        phenotype1 = {
            "name": faker.pystr(),
            "age": -2,
            "sex": "male",
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype1,
        )
        assert r.status_code == 400

        # create a new phenotype
        phenotype1["age"] = faker.pyint(0, 100)
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype1,
        )
        assert r.status_code == 200
        phenotype1_uuid = self.get_content(r)
        assert isinstance(phenotype1_uuid, str)

        # create a new phenotype in a study of an other group
        r = client.post(
            f"{API_URI}/study/{study2_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype1,
        )
        assert r.status_code == 404

        # create a new phenotype as admin not belonging to study group
        phenotype2 = {
            "name": faker.pystr(),
            "age": faker.pyint(0, 100),
            "sex": "female",
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=admin_headers,
            data=phenotype2,
        )
        assert r.status_code == 404

        # create a phenotype with a geodata and a list of hpo
        graph = neo4j.get_instance()
        geodata_nodes = graph.GeoData.nodes
        geodata_uuid = geodata_nodes[0].uuid
        hpo_nodes = graph.HPO.nodes
        hpo1_id = hpo_nodes[0].hpo_id
        hpo2_id = hpo_nodes[1].hpo_id
        phenotype2["birth_place"] = geodata_uuid
        phenotype2["hpo"] = [hpo1_id, hpo2_id]
        phenotype2["hpo"] = json.dumps(phenotype2["hpo"])

        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype2,
        )
        assert r.status_code == 200
        phenotype2_uuid = self.get_content(r)
        assert isinstance(phenotype2_uuid, str)

        # test phenotype access
        # test phenotype list response
        r = client.get(f"{API_URI}/study/{study1_uuid}/phenotypes",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert len(response) == 2

        # test phenotype list response for a study you don't have access
        r = client.get(f"{API_URI}/study/{study2_uuid}/phenotypes",
                       headers=user_B1_headers)
        assert r.status_code == 404

        # test phenotype list response for admin
        r = client.get(f"{API_URI}/study/{study1_uuid}/phenotypes",
                       headers=admin_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert len(response) == 2

        # test empty list of phenotypes in a study
        r = client.get(f"{API_URI}/study/{study2_uuid}/phenotypes",
                       headers=user_A1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert not response

        # study owner
        r = client.get(f"{API_URI}/phenotype/{phenotype1_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        # same group of the study owner
        r = client.get(f"{API_URI}/phenotype/{phenotype2_uuid}",
                       headers=user_B2_headers)
        assert r.status_code == 200
        # check hpo and geodata were correctly linked
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert response["birth_place"]["uuid"] == geodata_uuid
        assert len(response["hpo"]) == 2
        hpo_list = []
        for el in response["hpo"]:
            hpo_list.append(el["hpo_id"])
        assert hpo1_id in hpo_list
        assert hpo2_id in hpo_list

        # phenotype owned by an other group
        r = client.get(f"{API_URI}/phenotype/{phenotype1_uuid}",
                       headers=user_A1_headers)
        assert r.status_code == 404
        not_authorized_message = self.get_content(r)
        assert isinstance(not_authorized_message, str)

        # admin access
        r = client.get(f"{API_URI}/phenotype/{phenotype1_uuid}",
                       headers=admin_headers)
        assert r.status_code == 200

        # test phenotype modification

        # modify a non existent phenotype
        random_phenotype = faker.pystr()
        r = client.put(
            f"{API_URI}/phenotype/{random_phenotype}",
            headers=user_A1_headers,
            data={
                "name": faker.pystr(),
                "sex": "female"
            },
        )
        assert r.status_code == 404
        # modify a phenotype you do not own
        r = client.put(
            f"{API_URI}/phenotype/{phenotype1_uuid}",
            headers=user_A1_headers,
            data={
                "name": faker.pystr(),
                "sex": "female"
            },
        )
        assert r.status_code == 404

        # modify a phenotype using a wrong age
        phenotype1["age"] = -8
        r = client.put(
            f"{API_URI}/phenotype/{phenotype1_uuid}",
            headers=user_B1_headers,
            data=phenotype1,
        )
        assert r.status_code == 400

        # modify a phenotype you own
        phenotype1["age"] = faker.pyint(0, 100)
        r = client.put(
            f"{API_URI}/phenotype/{phenotype1_uuid}",
            headers=user_B1_headers,
            data=phenotype1,
        )
        assert r.status_code == 204

        # admin modify a phenotype of a group he don't belongs
        r = client.put(
            f"{API_URI}/phenotype/{phenotype1_uuid}",
            headers=admin_headers,
            data={
                "name": faker.pystr(),
                "sex": "female"
            },
        )
        assert r.status_code == 404

        # add a new hpo and change the previous geodata
        hpo3_id = hpo_nodes[2].hpo_id
        geodata2_uuid = geodata_nodes[1].uuid
        phenotype2["name"] = faker.pystr()
        phenotype2["sex"] = "male"
        phenotype2["birth_place"] = geodata2_uuid
        phenotype2["hpo"] = [hpo1_id, hpo2_id, hpo3_id]
        phenotype2["hpo"] = json.dumps(phenotype2["hpo"])
        r = client.put(
            f"{API_URI}/phenotype/{phenotype2_uuid}",
            headers=user_B1_headers,
            data=phenotype2,
        )
        assert r.status_code == 204
        r = client.get(f"{API_URI}/phenotype/{phenotype2_uuid}",
                       headers=user_B2_headers)
        res = self.get_content(r)
        assert isinstance(res, dict)
        assert res["birth_place"]["uuid"] == geodata2_uuid
        assert len(res["hpo"]) == 3

        # delete all hpo and geodata
        data: Dict[str, Any] = {**phenotype2}
        data.pop("birth_place", None)
        data.pop("hpo", None)
        r = client.put(f"{API_URI}/phenotype/{phenotype2_uuid}",
                       headers=user_B1_headers,
                       data=data)
        assert r.status_code == 204
        r = client.get(f"{API_URI}/phenotype/{phenotype2_uuid}",
                       headers=user_B2_headers)
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "birth_place" not in response
        assert not response["hpo"]

        # add a no existing geodata
        phenotype2["birth_place"] = faker.pystr()
        r = client.put(
            f"{API_URI}/phenotype/{phenotype2_uuid}",
            headers=user_B1_headers,
            data=phenotype2,
        )
        assert r.status_code == 400

        # delete a phenotype
        # delete a phenotype that does not exists
        r = client.delete(f"{API_URI}/phenotype/{random_phenotype}",
                          headers=user_A1_headers)
        assert r.status_code == 404
        # delete a phenotype in a study you do not own
        r = client.delete(f"{API_URI}/phenotype/{phenotype1_uuid}",
                          headers=user_A1_headers)
        assert r.status_code == 404
        # admin delete a phenotype of a group he don't belong
        r = client.delete(f"{API_URI}/phenotype/{phenotype1_uuid}",
                          headers=admin_headers)
        assert r.status_code == 404
        # delete a phenotype in a study you own
        r = client.delete(f"{API_URI}/phenotype/{phenotype1_uuid}",
                          headers=user_B1_headers)
        assert r.status_code == 204
        # delete a phenotype in a study own by your group
        r = client.delete(f"{API_URI}/phenotype/{phenotype2_uuid}",
                          headers=user_B2_headers)
        assert r.status_code == 204
        # check phenotype deletion
        r = client.get(f"{API_URI}/phenotype/{phenotype1_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 404
        not_existent_message = self.get_content(r)
        assert isinstance(not_existent_message, str)
        assert not_existent_message == not_authorized_message

        # delete all the elements used by the test
        delete_test_env(
            client,
            user_A1_headers,
            user_B1_headers,
            user_B1_uuid,
            user_B2_uuid,
            user_A1_uuid,
            uuid_group_A,
            uuid_group_B,
            study1_uuid=study1_uuid,
            study2_uuid=study2_uuid,
        )
Esempio n. 17
0
        def test_03_registration_and_login_ban(
            self, client: FlaskClient, faker: Faker
        ) -> None:
            if Env.get_bool("ALLOW_REGISTRATION"):

                registration_data = {}
                registration_data["email"] = faker.ascii_email()
                registration_data["name"] = faker.first_name()
                registration_data["surname"] = faker.last_name()
                registration_data["password"] = faker.password(strong=True)
                registration_data["password_confirm"] = registration_data["password"]
                r = client.post(f"{AUTH_URI}/profile", json=registration_data)
                # now the user is created but INACTIVE, activation endpoint is needed
                assert r.status_code == 200
                registration_message = "We are sending an email to your email address "
                registration_message += "where you will find the link to activate "
                registration_message += "your account"
                assert self.get_content(r) == registration_message

                # Registration endpoint send 2 mail: the first is the activation link,
                # the second (last) is the admin notification
                mail = self.read_mock_email(previous=True)
                body = mail.get("body")
                assert body is not None
                assert mail.get("headers") is not None
                # Subject: is a key in the MIMEText
                proto = "https" if PRODUCTION else "http"
                assert f"{proto}://localhost/public/register/" in body

                token = self.get_token_from_body(body)
                assert token is not None

                # 403 because the account is not activated
                self.do_login(
                    client,
                    registration_data["email"],
                    registration_data["password"],
                    status_code=403,
                )

                events = self.get_last_events(1)
                assert events[0].event == Events.refused_login.value
                assert events[0].payload["username"] == registration_data["email"]
                assert events[0].payload["motivation"] == "account not active"
                assert events[0].url == "/auth/login"

                self.delete_mock_email()

                for _ in range(0, max_login_attempts):
                    # Event if non activated if password is wrong the status is 401
                    self.do_login(
                        client,
                        registration_data["email"],
                        "wrong",
                        status_code=401,
                    )

                events = self.get_last_events(1)
                assert events[0].event == Events.failed_login.value
                assert events[0].payload["username"] == registration_data["email"]
                assert events[0].url == "/auth/login"

                self.verify_credentials_ban_notification()

                # After max_login_attempts the account is not blocked

                # profile activation forbidden due to blocked acount
                r = client.put(f"{AUTH_URI}/profile/activate/{token}")
                assert r.status_code == 403
                assert self.get_content(r) == BAN_MESSAGE

                events = self.get_last_events(1)
                assert events[0].event == Events.refused_login.value
                assert events[0].payload["username"] == registration_data["email"]
                assert (
                    events[0].payload["motivation"]
                    == "account blocked due to too many failed logins"
                )
                assert events[0].url == f"/auth/profile/activate/{token}"

                # request activation forbidden due to blocked acount
                r = client.post(
                    f"{AUTH_URI}/profile/activate",
                    json={"username": registration_data["email"]},
                )
                assert r.status_code == 403
                assert self.get_content(r) == BAN_MESSAGE

                events = self.get_last_events(1)
                assert events[0].event == Events.refused_login.value
                assert events[0].payload["username"] == registration_data["email"]
                assert (
                    events[0].payload["motivation"]
                    == "account blocked due to too many failed logins"
                )
                assert events[0].url == "/auth/profile/activate"

                time.sleep(ban_duration)

                r = client.post(
                    f"{AUTH_URI}/profile/activate",
                    json={"username": registration_data["email"]},
                )
                assert r.status_code == 200
    def test_02_GET_profile(self, client: FlaskClient, faker: Faker) -> None:
        """ Check if you can use your token for protected endpoints """

        # Check success
        log.info("*** VERIFY valid token")
        r = client.get(f"{AUTH_URI}/profile", headers=self.get("auth_header"))
        assert r.status_code == 200
        uuid = self.get_content(r).get("uuid")

        # Check failure
        log.info("*** VERIFY invalid token")
        r = client.get(f"{AUTH_URI}/profile")
        assert r.status_code == 401

        # Token created for a fake user
        token = self.get_crafted_token("f")
        headers = {"Authorization": f"Bearer {token}"}
        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 401

        # Token created for another user
        token = self.get_crafted_token("x")
        headers = {"Authorization": f"Bearer {token}"}
        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 401

        # Token created for another user
        token = self.get_crafted_token("f", wrong_algorithm=True)
        headers = {"Authorization": f"Bearer {token}"}
        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 401

        # Token created for another user
        token = self.get_crafted_token("f", wrong_secret=True)
        headers = {"Authorization": f"Bearer {token}"}
        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 401

        # token created for the correct user, but from outside the system!!
        token = self.get_crafted_token("f", user_id=uuid)
        headers = {"Authorization": f"Bearer {token}"}
        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 401

        # Immature token
        token = self.get_crafted_token("f", user_id=uuid, immature=True)
        headers = {"Authorization": f"Bearer {token}"}
        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 401

        # Expired token
        token = self.get_crafted_token("f", user_id=uuid, expired=True)
        headers = {"Authorization": f"Bearer {token}"}
        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 401

        # Sending malformed tokens
        headers = {"Authorization": "Bearer"}
        r = client.get(f"{AUTH_URI}/status", headers=headers)
        assert r.status_code == 401

        headers = {"Authorization": f"Bearer '{faker.pystr()}"}
        r = client.get(f"{AUTH_URI}/status", headers=headers)
        assert r.status_code == 401

        # Bearer realm is expected to be case sensitive
        token = self.get("auth_token")
        headers = {"Authorization": f"Bearer {token}"}
        r = client.get(f"{AUTH_URI}/status", headers=headers)
        assert r.status_code == 200

        headers = {"Authorization": f"bearer {token}"}
        r = client.get(f"{AUTH_URI}/status", headers=headers)
        assert r.status_code == 401

        headers = {"Authorization": f"BEARER {token}"}
        r = client.get(f"{AUTH_URI}/status", headers=headers)
        assert r.status_code == 401

        token = self.get("auth_token")
        headers = {"Authorization": f"Bear {token}"}
        r = client.get(f"{AUTH_URI}/status", headers=headers)
        assert r.status_code == 401

        USER = BaseAuthentication.default_user
        PWD = BaseAuthentication.default_password
        # Testing Basic Authentication (not allowed)
        credentials = f"{USER}:{PWD}"
        encoded_credentials = base64.b64encode(
            str.encode(credentials)).decode("utf-8")

        headers = {"Authorization": f"Basic {encoded_credentials}"}

        r = client.post(f"{AUTH_URI}/login", headers=headers)
        # Response is:
        # {
        #     'password': ['Missing data for required field.'],
        #     'username': ['Missing data for required field.']
        # }
        assert r.status_code == 400

        r = client.get(f"{AUTH_URI}/status", headers=headers)
        assert r.status_code == 401
Esempio n. 19
0
    def test_api_family(self, client: FlaskClient, faker: Faker) -> None:
        # setup the test env
        (
            admin_headers,
            uuid_group_A,
            user_A1_uuid,
            user_A1_headers,
            uuid_group_B,
            user_B1_uuid,
            user_B1_headers,
            user_B2_uuid,
            user_B2_headers,
            study1_uuid,
            study2_uuid,
        ) = create_test_env(client, faker, study=True)

        # create new phenotypes
        phenotype_father = {
            "name": faker.pystr(),
            "age": faker.pyint(0, 100),
            "sex": "male",
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype_father,
        )
        assert r.status_code == 200
        phenotype_father_uuid = self.get_content(r)
        assert isinstance(phenotype_father_uuid, str)

        phenotype_mother = {
            "name": faker.pystr(),
            "age": faker.pyint(0, 100),
            "sex": "female",
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype_mother,
        )
        assert r.status_code == 200
        phenotype_mother_uuid = self.get_content(r)
        assert isinstance(phenotype_mother_uuid, str)

        phenotype_son_B = {
            "name": faker.pystr(),
            "age": faker.pyint(0, 100),
            "sex": "female",
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype_son_B,
        )
        assert r.status_code == 200
        phenotype_son_B_uuid = self.get_content(r)
        assert isinstance(phenotype_son_B_uuid, str)

        phenotype_son_A = {
            "name": faker.pystr(),
            "age": faker.pyint(0, 100),
            "sex": "female",
        }
        r = client.post(
            f"{API_URI}/study/{study2_uuid}/phenotypes",
            headers=user_A1_headers,
            data=phenotype_son_A,
        )
        assert r.status_code == 200
        phenotype_son_A_uuid = self.get_content(r)
        assert isinstance(phenotype_son_A_uuid, str)

        # create a relationship
        # father case
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 200
        graph = neo4j.get_instance()
        phenotype_father_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_father_uuid)
        phenotype_son_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_son_B_uuid)
        assert phenotype_father_node.son.is_connected(phenotype_son_node)
        assert phenotype_son_node.father.is_connected(phenotype_father_node)

        # test relationships in get phenotype list response
        r = client.get(f"{API_URI}/study/{study1_uuid}/phenotypes",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        for el in response:
            if el["uuid"] == phenotype_son_B_uuid:
                assert el["relationships"]["father"][
                    "uuid"] == phenotype_father_uuid
            if el["uuid"] == phenotype_father_uuid:
                assert el["relationships"]["sons"][0][
                    "uuid"] == phenotype_son_B_uuid

        # test relationships in get single phenotype response
        r = client.get(f"{API_URI}/phenotype/{phenotype_son_B_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert response["relationships"]["father"][
            "uuid"] == phenotype_father_uuid

        r = client.get(f"{API_URI}/phenotype/{phenotype_father_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert response["relationships"]["sons"][0][
            "uuid"] == phenotype_son_B_uuid

        # create a relationship for two phenotypes in an other study
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=user_A1_headers,
        )
        assert r.status_code == 404

        # admin creates a relationship
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=admin_headers,
        )
        assert r.status_code == 404

        # a user of the same group of the owner create a relationship
        # mother case
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=user_B2_headers,
        )
        assert r.status_code == 200
        graph = neo4j.get_instance()
        phenotype_mother_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_mother_uuid)
        phenotype_son_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_son_B_uuid)
        assert phenotype_mother_node.son.is_connected(phenotype_son_node)
        assert phenotype_son_node.mother.is_connected(phenotype_mother_node)

        # test relationships in get phenotype list response
        r = client.get(f"{API_URI}/study/{study1_uuid}/phenotypes",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        for el in response:
            if el["uuid"] == phenotype_son_B_uuid:
                assert el["relationships"]["mother"][
                    "uuid"] == phenotype_mother_uuid
            if el["uuid"] == phenotype_mother_uuid:
                assert el["relationships"]["sons"][0][
                    "uuid"] == phenotype_son_B_uuid

        # test relationships in get single phenotype response
        r = client.get(f"{API_URI}/phenotype/{phenotype_son_B_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert response["relationships"]["mother"][
            "uuid"] == phenotype_mother_uuid

        # relationship between phenotype from different studies
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_A_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        # relationship with a random phenotype as son
        random_phenotype_uuid = faker.pystr()
        r = client.post(
            f"{API_URI}/phenotype/{random_phenotype_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        # relationship with a random phenotype as father
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{random_phenotype_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        # relationship with itself
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_father_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 400

        # delete a relationship
        # father case
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 204
        graph = neo4j.get_instance()
        phenotype_father_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_father_uuid)
        phenotype_son_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_son_B_uuid)
        assert not phenotype_father_node.son.single()
        assert not phenotype_son_node.father.single()

        # delete a relationship for two phenotypes in an other study
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=user_A1_headers,
        )
        assert r.status_code == 404

        # admin delete a relationship
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=admin_headers,
        )
        assert r.status_code == 404

        # a user of the same group of the owner delete a relationship
        # mother case
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=user_B2_headers,
        )
        assert r.status_code == 204
        graph = neo4j.get_instance()
        phenotype_mother_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_mother_uuid)
        phenotype_son_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_son_B_uuid)
        assert not phenotype_mother_node.son.single()
        assert not phenotype_son_node.mother.single()

        # delete relationship between phenotype from different studies
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_son_A_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        #  delete relationship with a random phenotype as son
        r = client.delete(
            f"{API_URI}/phenotype/{random_phenotype_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        # delete relationship with a random phenotype as father
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{random_phenotype_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 200

        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 200

        # delete a son relationship
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_mother_uuid}/relationships/{phenotype_son_B_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 204
        graph = neo4j.get_instance()
        phenotype_mother_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_mother_uuid)
        phenotype_son_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_son_B_uuid)
        assert not phenotype_mother_node.son.single()
        assert not phenotype_son_node.mother.single()

        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_father_uuid}/relationships/{phenotype_son_B_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 204
        graph = neo4j.get_instance()
        phenotype_father_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_father_uuid)
        phenotype_son_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_son_B_uuid)
        assert not phenotype_father_node.son.single()
        assert not phenotype_son_node.father.single()

        # delete all the elements used by the test
        delete_test_env(
            client,
            user_A1_headers,
            user_B1_headers,
            user_B1_uuid,
            user_B2_uuid,
            user_A1_uuid,
            uuid_group_A,
            uuid_group_B,
            study1_uuid=study1_uuid,
            study2_uuid=study2_uuid,
        )
    def test_registration(self, client: FlaskClient, faker: Faker) -> None:

        # Always enabled during core tests
        if not Env.get_bool("ALLOW_REGISTRATION"):  # pragma: no cover
            log.warning("User registration is disabled, skipping tests")
            return

        project_tile = get_project_configuration("project.title",
                                                 default="YourProject")
        proto = "https" if PRODUCTION else "http"

        # registration, empty input
        r = client.post(f"{AUTH_URI}/profile")
        assert r.status_code == 400

        # registration, missing information
        r = client.post(f"{AUTH_URI}/profile", data={"x": "y"})
        assert r.status_code == 400
        registration_data = {}
        registration_data["password"] = faker.password(5)
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        assert r.status_code == 400
        registration_data["email"] = BaseAuthentication.default_user
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        assert r.status_code == 400
        registration_data["name"] = faker.first_name()
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        assert r.status_code == 400

        registration_data["surname"] = faker.last_name()
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        assert r.status_code == 400

        registration_data["password_confirm"] = faker.password(strong=True)
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        assert r.status_code == 400

        min_pwd_len = Env.get_int("AUTH_MIN_PASSWORD_LENGTH", 9999)

        registration_data["password"] = faker.password(min_pwd_len - 1)
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        assert r.status_code == 400

        registration_data["password"] = faker.password(min_pwd_len)
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        assert r.status_code == 409
        m = f"This user already exists: {BaseAuthentication.default_user}"
        assert self.get_content(r) == m

        registration_data["email"] = faker.ascii_email()
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        assert r.status_code == 409
        assert self.get_content(
            r) == "Your password doesn't match the confirmation"

        registration_data["password"] = faker.password(min_pwd_len,
                                                       low=False,
                                                       up=True)
        registration_data["password_confirm"] = registration_data["password"]
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        assert r.status_code == 409
        m = "Password is too weak, missing lower case letters"
        assert self.get_content(r) == m

        registration_data["password"] = faker.password(min_pwd_len, low=True)
        registration_data["password_confirm"] = registration_data["password"]
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        assert r.status_code == 409
        m = "Password is too weak, missing upper case letters"
        assert self.get_content(r) == m

        registration_data["password"] = faker.password(min_pwd_len,
                                                       low=True,
                                                       up=True)
        registration_data["password_confirm"] = registration_data["password"]
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        assert r.status_code == 409
        m = "Password is too weak, missing numbers"
        assert self.get_content(r) == m

        registration_data["password"] = faker.password(min_pwd_len,
                                                       low=True,
                                                       up=True,
                                                       digits=True)
        registration_data["password_confirm"] = registration_data["password"]
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        assert r.status_code == 409
        m = "Password is too weak, missing special characters"
        assert self.get_content(r) == m

        registration_data["password"] = faker.password(strong=True)
        registration_data["password_confirm"] = registration_data["password"]
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        # now the user is created but INACTIVE, activation endpoint is needed
        assert r.status_code == 200
        registration_message = "We are sending an email to your email address where "
        registration_message += "you will find the link to activate your account"
        assert self.get_content(r) == registration_message

        events = self.get_last_events(1)
        assert events[0].event == Events.create.value
        assert events[0].user == "-"
        assert events[0].target_type == "User"
        assert "name" in events[0].payload
        assert "password" in events[0].payload
        assert events[0].payload["password"] == OBSCURE_VALUE

        mail = self.read_mock_email()
        body = mail.get("body")
        assert body is not None
        assert mail.get("headers") is not None
        # Subject: is a key in the MIMEText
        assert f"Subject: {project_tile} account activation" in mail.get(
            "headers")
        assert f"{proto}://localhost/public/register/" in body

        # This will fail because the user is not active
        _, error = self.do_login(
            client,
            registration_data["email"],
            registration_data["password"],
            status_code=403,
        )
        assert error == "Sorry, this account is not active"

        # Also password reset is not allowed
        data = {"reset_email": registration_data["email"]}
        r = client.post(f"{AUTH_URI}/reset", data=data)
        assert r.status_code == 403
        assert self.get_content(r) == "Sorry, this account is not active"

        events = self.get_last_events(2)
        assert events[0].event == Events.refused_login.value
        assert events[0].payload["username"] == data["reset_email"]
        assert events[0].payload["motivation"] == "account not active"

        assert events[1].event == Events.refused_login.value
        assert events[1].payload["username"] == data["reset_email"]
        assert events[1].payload["motivation"] == "account not active"

        # Activation, missing or wrong information
        r = client.post(f"{AUTH_URI}/profile/activate")
        assert r.status_code == 400
        r = client.post(f"{AUTH_URI}/profile/activate", data=faker.pydict(2))
        assert r.status_code == 400
        # It isn't an email
        invalid = faker.pystr(10)
        r = client.post(f"{AUTH_URI}/profile/activate",
                        data={"username": invalid})
        assert r.status_code == 400

        headers, _ = self.do_login(client, None, None)

        activation_message = "We are sending an email to your email address where "
        activation_message += "you will find the link to activate your account"
        # request activation, wrong username
        r = client.post(f"{AUTH_URI}/profile/activate",
                        data={"username": faker.ascii_email()})
        # return is 200, but no token will be generated and no mail will be sent
        # but it respond with the activation msg and hides the non existence of the user
        assert r.status_code == 200
        assert self.get_content(r) == activation_message

        events = self.get_last_events(1)
        assert events[0].event != Events.activation.value

        assert self.read_mock_email() is None

        # request activation, correct username
        r = client.post(
            f"{AUTH_URI}/profile/activate",
            data={"username": registration_data["email"]},
        )
        assert r.status_code == 200
        assert self.get_content(r) == activation_message

        mail = self.read_mock_email()
        body = mail.get("body")
        assert body is not None
        assert mail.get("headers") is not None
        # Subject: is a key in the MIMEText
        assert f"Subject: {project_tile} account activation" in mail.get(
            "headers")
        assert f"{proto}://localhost/public/register/" in body

        token = self.get_token_from_body(body)
        assert token is not None

        # profile activation
        r = client.put(f"{AUTH_URI}/profile/activate/thisisatoken")
        # this token is not valid
        assert r.status_code == 400

        # profile activation
        r = client.put(f"{AUTH_URI}/profile/activate/{token}")
        assert r.status_code == 200
        assert self.get_content(r) == "Account activated"

        events = self.get_last_events(1)
        assert events[0].event == Events.activation.value
        assert events[0].user == registration_data["email"]
        assert events[0].target_type == "User"

        # Activation token is no longer valid
        r = client.put(f"{AUTH_URI}/profile/activate/{token}")
        assert r.status_code == 400
        assert self.get_content(r) == "Invalid activation token"

        # Token created for another user
        token = self.get_crafted_token("a")
        r = client.put(f"{AUTH_URI}/profile/activate/{token}")
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid activation token"

        # Token created for another user
        token = self.get_crafted_token("a", wrong_algorithm=True)
        r = client.put(f"{AUTH_URI}/profile/activate/{token}")
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid activation token"

        # Token created for another user
        token = self.get_crafted_token("a", wrong_secret=True)
        r = client.put(f"{AUTH_URI}/profile/activate/{token}")
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid activation token"

        headers, _ = self.do_login(client, None, None)
        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 200
        uuid = self.get_content(r).get("uuid")

        token = self.get_crafted_token("x", user_id=uuid)
        r = client.put(f"{AUTH_URI}/profile/activate/{token}")
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid activation token"

        # token created for the correct user, but from outside the system!!
        token = self.get_crafted_token("a", user_id=uuid)
        r = client.put(f"{AUTH_URI}/profile/activate/{token}")
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid activation token"

        # Immature token
        token = self.get_crafted_token("a", user_id=uuid, immature=True)
        r = client.put(f"{AUTH_URI}/profile/activate/{token}")
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid activation token"

        # Expired token
        token = self.get_crafted_token("a", user_id=uuid, expired=True)
        r = client.put(f"{AUTH_URI}/profile/activate/{token}")
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid activation token: this request is expired"

        # Testing the following use case:
        # 1 - user registration
        # 2 - user activation using unconventional channel, e.g. by admins
        # 3 - user tries to activate and fails because already active

        registration_data["email"] = faker.ascii_email()
        r = client.post(f"{AUTH_URI}/profile", data=registration_data)
        # now the user is created but INACTIVE, activation endpoint is needed
        assert r.status_code == 200

        mail = self.read_mock_email()
        body = mail.get("body")
        assert body is not None
        assert mail.get("headers") is not None
        assert f"{proto}://localhost/public/register/" in body

        token = self.get_token_from_body(body)
        assert token is not None

        headers, _ = self.do_login(client, None, None)

        r = client.get(f"{API_URI}/admin/users", headers=headers)
        assert r.status_code == 200
        users = self.get_content(r)
        uuid = None
        for u in users:
            if u.get("email") == registration_data["email"]:
                uuid = u.get("uuid")
                break

        assert uuid is not None
        r = client.put(f"{API_URI}/admin/users/{uuid}",
                       data={"is_active": True},
                       headers=headers)
        assert r.status_code == 204

        r = client.put(f"{AUTH_URI}/profile/activate/{token}")
        assert r.status_code == 400
        c = self.get_content(r)
        assert c == "Invalid activation token: this request is no longer valid"

        r = client.get(f"{API_URI}/admin/tokens", headers=headers)
        content = self.get_content(r)

        for t in content:
            if t.get("token") == token:  # pragma: no cover
                pytest.fail(
                    "Token not properly invalidated, still bount to user {}",
                    t.get(id))
Esempio n. 21
0
    def test_websockets(self, client: FlaskClient, faker: Faker) -> None:

        if not Connector.check_availability("pushpin"):
            log.warning("Skipping websockets test: pushpin service not available")
            return

        log.info("Executing websockets tests")

        channel = faker.pystr()
        r = client.post(f"{API_URI}/socket/{channel}")
        assert r.status_code == 401

        r = client.put(f"{API_URI}/socket/{channel}/1")
        assert r.status_code == 401

        headers, _ = self.do_login(client, None, None)
        assert headers is not None
        headers["Content-Type"] = "application/websocket-events"

        r = client.post(f"{API_URI}/socket/{channel}", headers=headers)
        assert r.status_code == 400
        error = "Cannot decode websocket request: invalid in_event"
        assert self.get_content(r) == error

        data = b"\r\n"
        r = client.post(f"{API_URI}/socket/{channel}", data=data, headers=headers)
        assert r.status_code == 400
        error = "Cannot understand websocket request"
        assert self.get_content(r) == error

        data = b"OPEN"
        r = client.post(f"{API_URI}/socket/{channel}", data=data, headers=headers)
        assert r.status_code == 400
        error = "Cannot decode websocket request: invalid format"
        assert self.get_content(r) == error

        data = b"XYZ\r\n"
        r = client.post(f"{API_URI}/socket/{channel}", data=data, headers=headers)
        assert r.status_code == 400
        error = "Cannot understand websocket request"
        assert self.get_content(r) == error

        data = b"OPEN\r\n"
        r = client.post(f"{API_URI}/socket/{channel}", data=data, headers=headers)
        assert r.status_code == 200
        content = r.data.decode("utf-8").split("\n")
        assert len(content) >= 3
        assert content[0] == "OPEN\r"
        assert content[1] == "TEXT 3a\r"
        assert content[2] == 'c:{"channel": "%s", "type": "subscribe"}\r' % channel
        assert "Sec-WebSocket-Extensions" in r.headers
        assert r.headers.get("Sec-WebSocket-Extensions") == "grip"

        r = client.put(f"{API_URI}/socket/{channel}/1", headers=headers)
        assert r.status_code == 200
        assert self.get_content(r) == "Message received: True (sync=True)"

        r = client.put(f"{API_URI}/socket/{channel}/0", headers=headers)
        assert r.status_code == 200
        assert self.get_content(r) == "Message received: True (sync=False)"

        # send message on a different channel
        channel = faker.pystr()
        r = client.put(f"{API_URI}/socket/{channel}/1", headers=headers)
        assert r.status_code == 200
        assert self.get_content(r) == "Message received: True (sync=True)"

        r = client.post(f"{API_URI}/stream/{channel}", headers=headers)
        assert r.status_code == 200
        content = r.data.decode("utf-8")
        assert content == "Stream opened, prepare yourself!\n"
        assert "Grip-Hold" in r.headers
        assert r.headers["Grip-Hold"] == "stream"
        assert "Grip-Channel" in r.headers

        r = client.put(f"{API_URI}/stream/{channel}/1", headers=headers)
        assert r.status_code == 200
        assert self.get_content(r) == "Message received: True (sync=True)"

        r = client.put(f"{API_URI}/stream/{channel}/0", headers=headers)
        assert r.status_code == 200
        assert self.get_content(r) == "Message received: True (sync=False)"
Esempio n. 22
0
        def test_01_failed_login_ban(self, client: FlaskClient) -> None:

            if not Env.get_bool("MAIN_LOGIN_ENABLE"):  # pragma: no cover
                log.warning("Skipping admin/users tests")
                return

            uuid, data = self.create_user(client)

            self.delete_mock_email()

            for _ in range(0, max_login_attempts):
                self.do_login(client, data["email"], "wrong", status_code=401)

            events = self.get_last_events(1)
            assert events[0].event == Events.failed_login.value
            assert events[0].payload["username"] == data["email"]
            assert events[0].url == "/auth/login"

            self.verify_credentials_ban_notification()

            # This should fail
            headers, _ = self.do_login(
                client, data["email"], data["password"], status_code=403
            )
            assert headers is None

            events = self.get_last_events(1)
            assert events[0].event == Events.refused_login.value
            assert events[0].payload["username"] == data["email"]
            assert (
                events[0].payload["motivation"]
                == "account blocked due to too many failed logins"
            )
            assert events[0].url == "/auth/login"

            reset_data = {"reset_email": data["email"]}
            r = client.post(f"{AUTH_URI}/reset", json=reset_data)
            assert r.status_code == 403
            assert self.get_content(r) == BAN_MESSAGE

            events = self.get_last_events(1)
            assert events[0].event == Events.refused_login.value
            assert events[0].payload["username"] == data["email"]
            assert (
                events[0].payload["motivation"]
                == "account blocked due to too many failed logins"
            )
            assert events[0].url == "/auth/reset"

            time.sleep(ban_duration)

            headers, _ = self.do_login(client, data["email"], data["password"])
            assert headers is not None

            events = self.get_last_events(1)
            assert events[0].event == Events.login.value
            assert events[0].user == data["email"]
            assert events[0].url == "/auth/login"

            # Verify that already emitted tokens are not blocked
            # 1) Block again the account
            for _ in range(0, max_login_attempts):
                self.do_login(client, data["email"], "wrong", status_code=401)

            # 2) Verify that the account is blocked
            self.do_login(client, data["email"], data["password"], status_code=403)

            # 3) Verify that the previously emitted token is still valid
            r = client.get(f"{AUTH_URI}/status", headers=headers)
            assert r.status_code == 200

            # Goodbye temporary user
            self.delete_user(client, uuid)
Esempio n. 23
0
    def test_users_custom_fields(self, client: FlaskClient) -> None:

        output_fields = mem.customizer.get_custom_output_fields(None)

        profile_inputs = mem.customizer.get_custom_input_fields(
            request=None, scope=mem.customizer.PROFILE)
        registration_inputs = mem.customizer.get_custom_input_fields(
            request=None, scope=mem.customizer.REGISTRATION)
        admin_inputs = mem.customizer.get_custom_input_fields(
            request=None, scope=mem.customizer.ADMIN)
        uuid, data = self.create_user(client)
        headers, _ = self.do_login(client, data["email"], data["password"])

        # Verify custom output fields (if defined) included in the profile response
        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)

        for field in output_fields:
            assert field in response

        # Verify custom input fields (if defined) included in the profile input schema
        r = client.patch(f"{AUTH_URI}/profile",
                         json={"get_schema": 1},
                         headers=headers)
        response = self.get_content(r)
        assert isinstance(response, list)
        for field in profile_inputs.keys():
            for expected in response:
                if expected["key"] == field:
                    break
            else:  # pragma: no cover
                pytest.fail(
                    f"Input field {field} not found in profile input schema")

        # Verify custom registration fields (if defined) included in the reg. schema
        r = client.post(f"{AUTH_URI}/profile", json={"get_schema": 1})
        response = self.get_content(r)
        assert isinstance(response, list)
        for field in registration_inputs.keys():
            for expected in response:
                if expected["key"] == field:
                    break
            else:  # pragma: no cover
                pytest.fail(
                    f"Input field {field} not found in registration input schema"
                )

        headers, _ = self.do_login(client, None, None)
        # Verify custom admin input fields (if defined) included in admin users schema
        r = client.post(f"{API_URI}/admin/users",
                        json={"get_schema": 1},
                        headers=headers)
        response = self.get_content(r)
        assert isinstance(response, list)
        for field in admin_inputs.keys():
            for expected in response:
                if expected["key"] == field:
                    break
            else:  # pragma: no cover
                pytest.fail(
                    f"Input field {field} not found in admin users input schema"
                )

        # Verify custom admin output fields (if defined) included in admin users output
        r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers)
        response = self.get_content(r)
        assert isinstance(response, dict)
        for field in output_fields:
            # This will fail
            assert field in response
Esempio n. 24
0
    def test_sendmail(self, client: FlaskClient, faker: Faker) -> None:

        headers, _ = self.do_login(client, None, None)

        r = client.get(f"{API_URI}/admin/mail", headers=headers)
        assert r.status_code == 405

        r = client.put(f"{API_URI}/admin/mail", headers=headers)
        assert r.status_code == 405

        r = client.patch(f"{API_URI}/admin/mail", headers=headers)
        assert r.status_code == 405

        r = client.delete(f"{API_URI}/admin/mail", headers=headers)
        assert r.status_code == 405

        data: Dict[str, Any] = {"dry_run": False}
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["subject"] = faker.pystr()
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["body"] = faker.text()
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["to"] = faker.pystr()
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["to"] = faker.ascii_email()
        data["body"] = "TEST EMAIL BODY"
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 204

        mail = self.read_mock_email()
        body = mail.get("body", "")
        assert "TEST EMAIL BODY" in body

        data["dry_run"] = True
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 200

        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "html_body" in response
        assert "plain_body" in response
        assert "subject" in response
        assert "to" in response
        assert "cc" in response
        assert "bcc" in response

        data["dry_run"] = False

        data["body"] = "TEST EMAIL <b>HTML</b> BODY"
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 204
        mail = self.read_mock_email()
        body = mail.get("body", "")
        assert "TEST EMAIL <b>HTML</b> BODY" in body

        data["dry_run"] = True
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 200

        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "html_body" in response
        assert "plain_body" in response
        assert "subject" in response
        assert "to" in response
        assert "cc" in response
        assert "bcc" in response

        data["dry_run"] = False

        data["body"] = faker.text()
        data["cc"] = faker.pystr()
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["cc"] = faker.ascii_email()
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 204

        data["cc"] = f"{faker.ascii_email()},{faker.pystr()}"
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["cc"] = f"{faker.ascii_email()},{faker.ascii_email()}"
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 204

        data["bcc"] = faker.pystr()
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["bcc"] = f"{faker.ascii_email()},{faker.pystr()}"
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["bcc"] = f"{faker.ascii_email()},{faker.ascii_email()}"
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 204

        mail = self.read_mock_email()

        body = mail.get("body", "")
        email_headers = mail.get("headers", "")
        assert body is not None
        assert email_headers is not None
        # Subject: is a key in the MIMEText
        assert f"Subject: {data['subject']}" in email_headers
        ccs = mail.get("cc", [])
        assert ccs[0] == data["to"]
        assert ccs[1] == data["cc"].split(",")
        assert ccs[2] == data["bcc"].split(",")
Esempio n. 25
0
    def test_GET_specs(self, client: FlaskClient) -> None:

        r = client.get(f"{API_URI}/tests/pagination")
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 20
        assert content[0] == 1
        assert content[19] == 20

        r = client.get(f"{API_URI}/tests/pagination",
                       query_string={"get_total": True})
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, int)
        assert content == 150

        # Check precedence: get_total wins
        data = {"get_total": True, "page": 1, "size": 20}
        r = client.get(f"{API_URI}/tests/pagination", query_string=data)
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, int)
        assert content == 150

        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, int)
        assert content == 150

        r = client.get(f"{API_URI}/tests/pagination", query_string={"page": 2})
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 20
        assert content[0] == 21
        assert content[19] == 40

        data = {"page": 2, "size": 10}
        r = client.get(f"{API_URI}/tests/pagination", query_string=data)
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 10
        assert content[0] == 11
        assert content[9] == 20

        data = {"page": 2, "size": 100}
        r = client.get(f"{API_URI}/tests/pagination", query_string=data)
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 50
        assert content[0] == 101
        assert content[49] == 150

        r = client.get(f"{API_URI}/tests/pagination",
                       query_string={"page": 20})
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 0

        r = client.get(f"{API_URI}/tests/pagination",
                       query_string={"size": 101})
        assert r.status_code == 400

        r = client.get(f"{API_URI}/tests/pagination",
                       query_string={"page": -5})
        assert r.status_code == 400

        r = client.get(f"{API_URI}/tests/pagination",
                       query_string={"size": -5})
        assert r.status_code == 400

        data = {"page": -5, "size": -5}
        r = client.get(f"{API_URI}/tests/pagination", query_string=data)
        assert r.status_code == 400

        r = client.post(f"{API_URI}/tests/pagination", json={})
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 20
        assert content[0] == 1
        assert content[19] == 20

        r = client.post(f"{API_URI}/tests/pagination",
                        json={"get_total": True})
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, int)
        assert content == 150

        # Check precedence: get_total wins
        data = {"get_total": True, "page": 1, "size": 20}
        r = client.post(f"{API_URI}/tests/pagination", json=data)
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, int)
        assert content == 150

        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, int)
        assert content == 150

        r = client.post(f"{API_URI}/tests/pagination", json={"page": 2})
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 20
        assert content[0] == 21
        assert content[19] == 40

        r = client.post(f"{API_URI}/tests/pagination",
                        json={
                            "page": 2,
                            "size": 10
                        })
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 10
        assert content[0] == 11
        assert content[9] == 20

        r = client.post(f"{API_URI}/tests/pagination",
                        json={
                            "page": 2,
                            "size": 100
                        })
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 50
        assert content[0] == 101
        assert content[49] == 150

        r = client.post(f"{API_URI}/tests/pagination", json={"page": 20})
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 0

        r = client.post(f"{API_URI}/tests/pagination", json={"size": 101})
        assert r.status_code == 400

        r = client.post(f"{API_URI}/tests/pagination", json={"page": -5})
        assert r.status_code == 400

        r = client.post(f"{API_URI}/tests/pagination", json={"size": -5})
        assert r.status_code == 400

        r = client.post(f"{API_URI}/tests/pagination",
                        json={
                            "page": -5,
                            "size": -5
                        })
        assert r.status_code == 400

        # Final check:
        # get only accept query parameters
        # post only accept body parameters

        r = client.get(f"{API_URI}/tests/pagination", json={"get_total": True})
        assert r.status_code == 200
        content = self.get_content(r)
        # Request get_total as body parameter but is ignored => sent a list of elements
        assert isinstance(content, list)

        # Request get_total as query parameter but is ignored => sent a list of elements
        r = client.post(f"{API_URI}/tests/pagination",
                        json={},
                        query_string={"get_total": True})
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
Esempio n. 26
0
        def test_02_unlock_token(self, client: FlaskClient) -> None:

            if not Env.get_bool("MAIN_LOGIN_ENABLE"):  # pragma: no cover
                log.warning("Skipping admin/users tests")
                return

            uuid, data = self.create_user(client)

            self.delete_mock_email()

            for _ in range(0, max_login_attempts):
                self.do_login(client, data["email"], "wrong", status_code=401)

            token = self.verify_credentials_ban_notification()

            # This should fail
            headers, _ = self.do_login(
                client, data["email"], data["password"], status_code=403
            )
            assert headers is None

            auth = Connector.get_authentication_instance()
            logins = auth.get_logins(data["email"])
            login = logins[-1]
            assert login.username == data["email"]
            assert login.failed
            assert not login.flushed

            logins = auth.get_logins(data["email"], only_unflushed=True)
            assert len(logins) > 0

            # Check if token is valid
            r = client.post(f"{AUTH_URI}/login/unlock/{token}")
            assert r.status_code == 200

            events = self.get_last_events(1)
            assert events[0].event == Events.login_unlock.value
            assert events[0].user == data["email"]
            assert events[0].target_type == "User"
            assert events[0].url == f"/auth/login/unlock/{token}"

            logins = auth.get_logins(data["email"])
            login = logins[-1]
            assert login.username == data["email"]
            assert login.failed
            assert login.flushed

            logins = auth.get_logins(data["email"], only_unflushed=True)
            assert len(logins) == 0

            # Now credentials are unlock again :-)
            headers, _ = self.do_login(client, data["email"], data["password"])
            assert headers is not None

            # Unlock token can be used twice
            r = client.post(f"{AUTH_URI}/login/unlock/{token}")
            assert r.status_code == 400

            # Verify that unlock tokens can't be used if the user is already unlocked
            for _ in range(0, max_login_attempts):
                self.do_login(client, data["email"], "wrong", status_code=401)

            token = self.verify_credentials_ban_notification()

            # This should fail
            headers, _ = self.do_login(
                client, data["email"], data["password"], status_code=403
            )
            assert headers is None

            time.sleep(ban_duration)

            r = client.post(f"{AUTH_URI}/login/unlock/{token}")
            assert r.status_code == 400

            # Verify that unlock tokens are invalidated by new tokens
            for _ in range(0, max_login_attempts):
                self.do_login(client, data["email"], "wrong", status_code=401)

            first_token = self.verify_credentials_ban_notification()

            # This should fail
            headers, _ = self.do_login(
                client, data["email"], data["password"], status_code=403
            )
            assert headers is None

            time.sleep(ban_duration)

            for _ in range(0, max_login_attempts):
                self.do_login(client, data["email"], "wrong", status_code=401)

            second_token = self.verify_credentials_ban_notification()

            assert first_token != second_token

            r = client.post(f"{AUTH_URI}/login/unlock/{first_token}")
            assert r.status_code == 400

            r = client.post(f"{AUTH_URI}/login/unlock/{second_token}")
            assert r.status_code == 200

            # Test invalid tokens

            # Token created for another user
            token = self.get_crafted_token("u")
            r = client.post(f"{AUTH_URI}/login/unlock/{token}")
            assert r.status_code == 400
            c = self.get_content(r)
            assert c == "Invalid unlock token"

            # Token created with a wrong algorithm
            token = self.get_crafted_token("u", wrong_algorithm=True)
            r = client.post(f"{AUTH_URI}/login/unlock/{token}")
            assert r.status_code == 400
            c = self.get_content(r)
            assert c == "Invalid unlock token"

            # Token created with a wrong secret
            token = self.get_crafted_token("u", wrong_secret=True)
            r = client.post(f"{AUTH_URI}/login/unlock/{token}")
            assert r.status_code == 400
            c = self.get_content(r)
            assert c == "Invalid unlock token"

            # Token created for another user
            headers, _ = self.do_login(client, None, None)
            r = client.get(f"{AUTH_URI}/profile", headers=headers)
            assert r.status_code == 200
            response = self.get_content(r)
            assert isinstance(response, dict)
            uuid = str(response.get("uuid"))

            token = self.get_crafted_token("x", user_id=uuid)
            r = client.post(f"{AUTH_URI}/login/unlock/{token}")
            assert r.status_code == 400
            c = self.get_content(r)
            assert c == "Invalid unlock token"

            # token created for the correct user, but from outside the system!!
            token = self.get_crafted_token("u", user_id=uuid)
            r = client.post(f"{AUTH_URI}/login/unlock/{token}")
            assert r.status_code == 400
            c = self.get_content(r)
            assert c == "Invalid unlock token"

            # Immature token
            token = self.get_crafted_token("u", user_id=uuid, immature=True)
            r = client.post(f"{AUTH_URI}/login/unlock/{token}")
            assert r.status_code == 400
            c = self.get_content(r)
            assert c == "Invalid unlock token"

            # Expired token
            token = self.get_crafted_token("u", user_id=uuid, expired=True)
            r = client.post(f"{AUTH_URI}/login/unlock/{token}")
            assert r.status_code == 400
            c = self.get_content(r)
            assert c == "Invalid unlock token: this request is expired"
        def test_07_totp_failures(self, client: FlaskClient,
                                  faker: Faker) -> None:

            uuid, data = self.create_user(client)

            username = data["email"]
            password = data["password"]
            new_password = faker.password(strong=True)

            invalid_totp = (
                str(faker.pyint(min_value=0, max_value=9)),
                str(faker.pyint(min_value=10, max_value=99)),
                str(faker.pyint(min_value=100, max_value=999)),
                str(faker.pyint(min_value=1000, max_value=9999)),
                str(faker.pyint(min_value=10000, max_value=99999)),
                str(faker.pyint(min_value=1000000, max_value=9999999)),
                faker.pystr(6),
            )
            ###################################
            # Test first password change
            ###################################

            data = {
                "username": username,
                "password": password,
                "new_password": new_password,
                "password_confirm": new_password,
            }

            r = client.post(f"{AUTH_URI}/login", data=data)
            assert r.status_code == 403
            resp = self.get_content(r)

            assert "actions" in resp
            assert "errors" in resp
            assert "FIRST LOGIN" in resp["actions"]
            assert "TOTP" in resp["actions"]
            assert "Please change your temporary password" in resp["errors"]
            assert "You do not provided a valid verification code" in resp[
                "errors"]

            # validate that the QR code is a valid PNG image
            # ... not implemented

            data["totp_code"] = "000000"
            r = client.post(f"{AUTH_URI}/login", data=data)
            assert r.status_code == 401
            assert self.get_content(r) == "Verification code is not valid"

            events = self.get_last_events(1)
            assert events[0].event == Events.failed_login.value
            assert events[0].user == username
            assert "totp" in events[0].payload
            assert events[0].payload["totp"] == OBSCURE_VALUE

            for totp in invalid_totp:
                data["totp_code"] = totp
                r = client.post(f"{AUTH_URI}/login", data=data)
                assert r.status_code == 400
                resp = self.get_content(r)
                assert "totp_code" in resp
                assert "Invalid TOTP format" in resp["totp_code"]

            data["totp_code"] = self.generate_totp(username)
            r = client.post(f"{AUTH_URI}/login", data=data)
            assert r.status_code == 200

            events = self.get_last_events(1)
            assert events[0].event == Events.login.value
            assert events[0].user == username

            password = new_password

            ###################################
            # Test login
            ###################################

            data = {
                "username": username,
                "password": password,
            }
            r = client.post(f"{AUTH_URI}/login", data=data)
            assert r.status_code == 403
            resp = self.get_content(r)
            assert "actions" in resp
            assert "errors" in resp
            assert "TOTP" in resp["actions"]
            assert "You do not provided a valid verification code" in resp[
                "errors"]

            data["totp_code"] = "000000"
            r = client.post(f"{AUTH_URI}/login", data=data)
            assert r.status_code == 401
            assert self.get_content(r) == "Verification code is not valid"

            events = self.get_last_events(1)
            assert events[0].event == Events.failed_login.value
            assert events[0].user == username
            assert "totp" in events[0].payload
            assert events[0].payload["totp"] == OBSCURE_VALUE

            for totp in invalid_totp:
                data["totp_code"] = totp
                r = client.post(f"{AUTH_URI}/login", data=data)
                assert r.status_code == 400
                resp = self.get_content(r)
                assert "totp_code" in resp
                assert "Invalid TOTP format" in resp["totp_code"]

            data["totp_code"] = self.generate_totp(username)
            r = client.post(f"{AUTH_URI}/login", data=data)
            assert r.status_code == 200

            events = self.get_last_events(1)
            assert events[0].event == Events.login.value
            assert events[0].user == username

            ###################################
            # Test password change
            ###################################
            new_password = faker.password(strong=True)
            headers, _ = self.do_login(client, username, password)

            data = {
                "password": password,
                "new_password": new_password,
                "password_confirm": new_password,
            }

            r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
            assert r.status_code == 401
            assert self.get_content(r) == "Verification code is missing"

            data["totp_code"] = "000000"
            r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
            assert r.status_code == 401
            assert self.get_content(r) == "Verification code is not valid"

            events = self.get_last_events(1)
            assert events[0].event == Events.failed_login.value
            assert events[0].user == username
            assert "totp" in events[0].payload
            assert events[0].payload["totp"] == OBSCURE_VALUE

            for totp in invalid_totp:
                data["totp_code"] = totp
                r = client.put(f"{AUTH_URI}/profile",
                               data=data,
                               headers=headers)
                assert r.status_code == 400
                resp = self.get_content(r)
                assert "totp_code" in resp
                assert "Invalid TOTP format" in resp["totp_code"]

            data["totp_code"] = self.generate_totp(username)
            r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
            assert r.status_code == 204

            # After a change password a spam of delete Token is expected
            # Reverse the list and skip all delete tokens to find the change pwd event
            events = self.get_last_events(100)
            events.reverse()
            for event in events:
                if event.event == Events.delete.value:
                    assert event.target_type == "Token"
                    continue

                assert event.event == Events.change_password.value
                assert event.user == username
                break

            # verify the new password
            headers, _ = self.do_login(client, username, new_password)

            assert headers is not None

            ###################################
            # Goodbye temporary user
            ###################################

            self.delete_user(client, uuid)
Esempio n. 28
0
    def test_api_stats(self, client: FlaskClient, faker: Faker) -> None:
        # setup the test env
        (
            admin_headers,
            uuid_group_A,
            user_A1_uuid,
            user_A1_headers,
            uuid_group_B,
            user_B1_uuid,
            user_B1_headers,
            user_B2_uuid,
            user_B2_headers,
            study1_uuid,
            study2_uuid,
        ) = create_test_env(client, faker, study=True)
        # create a dataset for group A
        dataset_A = {"name": faker.pystr(), "description": faker.pystr()}
        r = client.post(
            f"{API_URI}/study/{study2_uuid}/datasets",
            headers=user_A1_headers,
            data=dataset_A,
        )
        assert r.status_code == 200
        dataset_A_uuid = self.get_content(r)
        assert isinstance(dataset_A_uuid, str)
        # create a dataset for group B
        dataset_B = {"name": faker.pystr(), "description": faker.pystr()}
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/datasets",
            headers=user_B1_headers,
            data=dataset_B,
        )
        assert r.status_code == 200
        dataset_B_uuid = self.get_content(r)
        assert isinstance(dataset_B_uuid, str)

        # init an upload in dataset A
        fake_filename = f"{faker.pystr()}_R1"
        fake_file = {
            "name": f"{fake_filename}.fastq.gz",
            "mimeType": "application/gzip",
            "size": faker.pyint(),
            "lastModified": faker.pyint(),
        }
        r = client.post(
            f"{API_URI}/dataset/{dataset_A_uuid}/files/upload",
            headers=user_A1_headers,
            data=fake_file,
        )
        assert r.status_code == 201
        # init an upload in dataset B

        r = client.post(
            f"{API_URI}/dataset/{dataset_B_uuid}/files/upload",
            headers=user_B1_headers,
            data=fake_file,
        )
        assert r.status_code == 201

        # test without group filter
        # public stats
        NIGEndpoint.GROUPS_TO_FILTER = []
        r = client.get(f"{API_URI}/stats/public", )
        assert r.status_code == 200
        full_public_stats = self.get_content(r)
        assert isinstance(full_public_stats, dict)
        assert full_public_stats["num_users"] > 0
        assert full_public_stats["num_studies"] > 0
        assert full_public_stats["num_datasets"] > 0
        assert full_public_stats["num_files"] > 0

        # private stats
        r = client.get(
            f"{API_URI}/stats/private",
            headers=user_B1_headers,
        )
        private_stats = self.get_content(r)
        assert isinstance(private_stats, dict)
        assert private_stats["num_users"] > 0
        assert private_stats["num_studies"] > 0
        assert private_stats["num_datasets"] > 0
        assert private_stats["num_files"] > 0

        # exclude test group
        NIGEndpoint.GROUPS_TO_FILTER = ["Default group"]
        # test public stats
        r = client.get(f"{API_URI}/stats/public", )
        assert r.status_code == 200
        public_stats = self.get_content(r)
        assert isinstance(public_stats, dict)
        assert public_stats["num_users"] == 3
        assert public_stats["num_studies"] == 2
        assert public_stats["num_datasets"] == 2
        assert public_stats["num_files"] == 2

        # test authentication for private stats
        r = client.get(f"{API_URI}/stats/private", )
        assert r.status_code == 401

        # test private stats
        r = client.get(
            f"{API_URI}/stats/private",
            headers=user_B1_headers,
        )
        private_stats = self.get_content(r)
        assert isinstance(private_stats, dict)
        assert private_stats["num_users"] == 3
        assert private_stats["num_studies"] == 2
        assert private_stats["num_datasets"] == 2
        assert private_stats["num_files"] == 2
        # get group fullnames
        graph = neo4j.get_instance()
        group_A = graph.Group.nodes.get_or_none(uuid=uuid_group_A)
        group_A_fullname = group_A.fullname
        group_B = graph.Group.nodes.get_or_none(uuid=uuid_group_B)
        group_B_fullname = group_B.fullname
        assert private_stats["num_datasets_per_group"][group_A_fullname] == 1
        assert private_stats["num_datasets_per_group"][group_B_fullname] == 1
        # check the excluded group not in responses
        assert "Default group" not in private_stats["num_datasets_per_group"]

        # test empty stats
        NIGEndpoint.GROUPS_TO_FILTER.append(group_A_fullname)
        NIGEndpoint.GROUPS_TO_FILTER.append(group_B_fullname)
        # public
        r = client.get(f"{API_URI}/stats/public", )
        assert r.status_code == 200
        public_stats = self.get_content(r)
        assert isinstance(public_stats, dict)
        assert public_stats["num_users"] == 0
        assert public_stats["num_studies"] == 0
        assert public_stats["num_datasets"] == 0
        assert public_stats["num_files"] == 0
        # private
        r = client.get(
            f"{API_URI}/stats/private",
            headers=user_B1_headers,
        )
        private_stats = self.get_content(r)
        assert isinstance(private_stats, dict)
        assert private_stats["num_users"] == 0
        assert private_stats["num_studies"] == 0
        assert private_stats["num_datasets"] == 0
        assert private_stats["num_files"] == 0
        assert group_A_fullname not in private_stats["num_datasets_per_group"]
        assert group_B_fullname not in private_stats["num_datasets_per_group"]

        # delete all the elements used by the test
        delete_test_env(
            client,
            user_A1_headers,
            user_B1_headers,
            user_B1_uuid,
            user_B2_uuid,
            user_A1_uuid,
            uuid_group_A,
            uuid_group_B,
            study1_uuid=study1_uuid,
            study2_uuid=study2_uuid,
        )
Esempio n. 29
0
    def test_chunked(self, client: FlaskClient, faker: Faker) -> None:

        self.fname = self.get("fname")
        self.fcontent = self.get("fcontent")

        r = client.post(f"{API_URI}/tests/upload", data={"force": True})
        assert r.status_code == 400

        data = {
            "force": True,
            "name": "fixed.filename",
            "size": "999",
            "mimeType": "application/zip",
            "lastModified": 1590302749209,
        }
        r = client.post(f"{API_URI}/tests/upload", data=data)
        assert r.status_code == 201
        assert self.get_content(r) == ""

        with io.StringIO(faker.text()) as f:
            r = client.put(f"{API_URI}/tests/upload/chunked", data=f)
        assert r.status_code == 400
        assert self.get_content(r) == "Invalid request"

        with io.StringIO(faker.text()) as f:
            r = client.put(
                f"{API_URI}/tests/upload/chunked",
                data=f,
                headers={"Content-Range": "!"},
            )
        assert r.status_code == 400
        assert self.get_content(r) == "Invalid request"

        up_data = faker.pystr(min_chars=24, max_chars=48)
        STR_LEN = len(up_data)
        with io.StringIO(up_data[0:5]) as f:
            r = client.put(
                f"{API_URI}/tests/upload/chunked",
                data=f,
                headers={"Content-Range": f"bytes 0-5/{STR_LEN}"},
            )
        assert r.status_code == 206
        assert self.get_content(r) == "partial"

        with io.StringIO(up_data[5:]) as f:
            r = client.put(
                f"{API_URI}/tests/upload/chunked",
                data=f,
                headers={"Content-Range": f"bytes 5-{STR_LEN}/{STR_LEN}"},
            )
        assert r.status_code == 200
        c = self.get_content(r)
        assert c.get("filename") is not None
        uploaded_filename = c.get("filename")
        meta = c.get("meta")
        assert meta is not None
        assert meta.get("charset") == "us-ascii"
        assert meta.get("type") == "text/plain"

        r = client.get(f"{API_URI}/tests/download/{uploaded_filename}")
        assert r.status_code == 200
        content = r.data.decode("utf-8")
        assert content == up_data

        r = client.get(f"{API_URI}/tests/download/{uploaded_filename}")
        assert r.status_code == 200
        content = r.data.decode("utf-8")
        assert content == up_data

        r = client.get(f"{API_URI}/tests/download/{uploaded_filename}",
                       headers={"Range": ""})
        assert r.status_code == 416

        r = client.get(
            f"{API_URI}/tests/download/{uploaded_filename}",
            headers={"Range": f"0-{STR_LEN - 1}"},
        )
        assert r.status_code == 416

        r = client.get(
            f"{API_URI}/tests/download/{uploaded_filename}",
            headers={"Range": "bytes=0-9999999999999999"},
        )

        from werkzeug import __version__ as werkzeug_version

        # Back-compatibility check for B2STAGE
        if werkzeug_version == "0.16.1":  # pragma: no cover
            assert r.status_code == 200
        else:
            assert r.status_code == 206

        r = client.get(
            f"{API_URI}/tests/download/{uploaded_filename}",
            headers={"Range": "bytes=0-4"},
        )
        assert r.status_code == 206
        content = r.data.decode("utf-8")
        assert content == up_data[0:5]

        r = client.get(
            f"{API_URI}/tests/download/{uploaded_filename}",
            headers={"Range": f"bytes=5-{STR_LEN - 1}"},
        )
        assert r.status_code == 206
        content = r.data.decode("utf-8")
        assert content == up_data[5:]

        r = client.get(
            f"{API_URI}/tests/download/{uploaded_filename}",
            headers={"Range": f"bytes=0-{STR_LEN - 1}"},
        )
        # Back-compatibility check for B2STAGE
        if werkzeug_version == "0.16.1":  # pragma: no cover
            assert r.status_code == 200
        else:
            assert r.status_code == 206
        content = r.data.decode("utf-8")
        assert content == up_data

        # Send a new string as content file. Will be appended as prefix
        up_data2 = faker.pystr(min_chars=24, max_chars=48)
        STR_LEN = len(up_data2)
        with io.StringIO(up_data2) as f:
            r = client.put(
                f"{API_URI}/tests/upload/chunked",
                data=f,
                headers={"Content-Range": f"bytes */{STR_LEN}"},
            )
        assert r.status_code == 200
        # c = self.get_content(r)
        # assert c.get('filename') is not None
        # uploaded_filename = c.get('filename')
        # meta = c.get('meta')
        # assert meta is not None
        # assert meta.get('charset') == 'us-ascii'
        # assert meta.get('type') == 'text/plain'

        # r = client.get(f'{API_URI}/tests/download/{uploaded_filename}')
        # assert r.status_code == 200
        # content = r.data.decode('utf-8')
        # # Uhmmm... should not be up_data2 + up_data ??
        # assert content == up_data + up_data2

        data["force"] = False
        r = client.post(f"{API_URI}/tests/upload", data=data)
        assert r.status_code == 400
        err = f"File '{uploaded_filename}' already exists"
        assert self.get_content(r) == err

        data["force"] = True
        r = client.post(f"{API_URI}/tests/upload", data=data)
        assert r.status_code == 201
        assert self.get_content(r) == ""
Esempio n. 30
0
    def test_admin_users(self, client: FlaskClient, faker: Faker) -> None:

        if not Env.get_bool("MAIN_LOGIN_ENABLE"):  # pragma: no cover
            log.warning("Skipping admin/users tests")
            return

        project_tile = get_project_configuration("project.title",
                                                 default="YourProject")

        headers, _ = self.do_login(client, None, None)
        r = client.get(f"{API_URI}/admin/users", headers=headers)
        assert r.status_code == 200

        schema = self.getDynamicInputSchema(client, "admin/users", headers)
        data = self.buildData(schema)
        data["email_notification"] = True
        data["is_active"] = True
        data["expiration"] = None

        # Event 1: create
        r = client.post(f"{API_URI}/admin/users", data=data, headers=headers)
        assert r.status_code == 200
        uuid = self.get_content(r)

        mail = self.read_mock_email()
        # Subject: is a key in the MIMEText
        assert mail.get("body") is not None
        assert mail.get("headers") is not None
        assert f"Subject: {project_tile}: new credentials" in mail.get(
            "headers")
        assert f"Username: {data.get('email', 'MISSING').lower()}" in mail.get(
            "body")
        assert f"Password: {data.get('password')}" in mail.get("body")

        # Event 2: read
        r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers)
        assert r.status_code == 200
        users_list = self.get_content(r)
        assert len(users_list) > 0
        # email is saved lowercase
        assert users_list[0].get("email") == data.get("email",
                                                      "MISSING").lower()

        # Check duplicates
        r = client.post(f"{API_URI}/admin/users", data=data, headers=headers)
        assert r.status_code == 409

        # Create another user
        data2 = self.buildData(schema)
        data2["email_notification"] = True
        data2["is_active"] = True
        data2["expiration"] = None

        # Event 3: create
        r = client.post(f"{API_URI}/admin/users", data=data2, headers=headers)
        assert r.status_code == 200
        uuid2 = self.get_content(r)

        mail = self.read_mock_email()
        # Subject: is a key in the MIMEText
        assert mail.get("body") is not None
        assert mail.get("headers") is not None
        assert f"Subject: {project_tile}: new credentials" in mail.get(
            "headers")
        assert f"Username: {data2.get('email', 'MISSING').lower()}" in mail.get(
            "body")
        assert f"Password: {data2.get('password')}" in mail.get("body")

        # send and invalid user_id
        r = client.put(
            f"{API_URI}/admin/users/invalid",
            data={"name": faker.name()},
            headers=headers,
        )
        assert r.status_code == 404

        # Event 4: modify
        r = client.put(
            f"{API_URI}/admin/users/{uuid}",
            data={"name": faker.name()},
            headers=headers,
        )
        assert r.status_code == 204

        # email cannot be modified
        new_data = {"email": data.get("email")}
        r = client.put(f"{API_URI}/admin/users/{uuid2}",
                       data=new_data,
                       headers=headers)
        # from webargs >= 6 this endpoint no longer return a 204 but a 400
        # because email is an unknown field
        # assert r.status_code == 204
        assert r.status_code == 400

        # Event 5: read
        r = client.get(f"{API_URI}/admin/users/{uuid2}", headers=headers)
        assert r.status_code == 200
        users_list = self.get_content(r)
        assert len(users_list) > 0
        # email is not modified -> still equal to data2, not data1
        assert users_list[0].get("email") != data.get("email",
                                                      "MISSING").lower()
        assert users_list[0].get("email") == data2.get("email",
                                                       "MISSING").lower()

        r = client.delete(f"{API_URI}/admin/users/invalid", headers=headers)
        assert r.status_code == 404

        # Event 6: delete
        r = client.delete(f"{API_URI}/admin/users/{uuid}", headers=headers)
        assert r.status_code == 204

        r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers)
        assert r.status_code == 404

        # change password of user2
        # Event 7: modify
        newpwd = faker.password(strong=True)
        data = {"password": newpwd, "email_notification": True}
        r = client.put(f"{API_URI}/admin/users/{uuid2}",
                       data=data,
                       headers=headers)
        assert r.status_code == 204

        mail = self.read_mock_email()
        # Subject: is a key in the MIMEText
        assert mail.get("body") is not None
        assert mail.get("headers") is not None
        assert f"Subject: {project_tile}: password changed" in mail.get(
            "headers")
        assert f"Username: {data2.get('email', 'MISSING').lower()}" in mail.get(
            "body")
        assert f"Password: {newpwd}" in mail.get("body")

        # login with a newly created user
        headers2, _ = self.do_login(client, data2.get("email"), newpwd)

        # normal users cannot access to this endpoint
        r = client.get(f"{API_URI}/admin/users", headers=headers2)
        assert r.status_code == 401

        r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers2)
        assert r.status_code == 401

        r = client.post(f"{API_URI}/admin/users", data=data, headers=headers2)
        assert r.status_code == 401

        r = client.put(
            f"{API_URI}/admin/users/{uuid}",
            data={"name": faker.name()},
            headers=headers2,
        )
        assert r.status_code == 401

        r = client.delete(f"{API_URI}/admin/users/{uuid}", headers=headers2)
        assert r.status_code == 401

        # Users are not authorized to /admin/tokens
        # These two tests should be moved in test_endpoints_tokens.py
        r = client.get(f"{API_URI}/admin/tokens", headers=headers2)
        assert r.status_code == 401
        r = client.delete(f"{API_URI}/admin/tokens/xyz", headers=headers2)
        assert r.status_code == 401

        # let's delete the second user
        # Event 8: delete
        r = client.delete(f"{API_URI}/admin/users/{uuid2}", headers=headers)
        assert r.status_code == 204

        # Restore the default password, if it changed due to FORCE_FIRST_PASSWORD_CHANGE
        # or MAX_PASSWORD_VALIDITY errors
        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 200
        uuid = self.get_content(r).get("uuid")

        data = {
            "password": BaseAuthentication.default_password,
            # very important, otherwise the default user will lose its admin role
            "roles": json.dumps(["admin_root"]),
        }
        # Event 9: modify
        r = client.put(f"{API_URI}/admin/users/{uuid}",
                       data=data,
                       headers=headers)
        assert r.status_code == 204

        r = client.get(f"{AUTH_URI}/logout", headers=headers)
        assert r.status_code == 204