Example #1
0
    def test_auth(self, client: FlaskClient) -> None:

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

        r = client.get(f"{API_URI}/tests/authentication")
        assert r.status_code == 401

        r = client.get(
            f"{API_URI}/tests/authentication",
            headers={"Authorization": "Bearer invalid"},
        )
        assert r.status_code == 401

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

        r = client.get(f"{API_URI}/tests/authentication", headers=headers)
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, dict)
        assert len(content) == 1
        assert "email" in content
        assert content["email"] == BaseAuthentication.default_user

        if not Env.get_bool("ALLOW_ACCESS_TOKEN_PARAMETER"):
            # access token parameter is not allowed by default
            r = client.get(f"{API_URI}/tests/authentication",
                           query_string={"access_token": token})
            assert r.status_code == 401
Example #2
0
    def test_GET_status(self, client: FlaskClient) -> None:
        """Test that the flask server is running and reachable"""

        # Check success
        alive_message = "Server is alive"

        log.info("*** VERIFY if API is online")
        r = client.get(f"{API_URI}/status")
        assert r.status_code == 200
        output = self.get_content(r)
        assert output == alive_message

        # Check failure
        log.info("*** VERIFY if invalid endpoint gives Not Found")
        r = client.get(API_URI)
        assert r.status_code == 404

        if Env.get_bool("AUTH_ENABLE"):
            # Check /auth/status with no token or invalid token
            r = client.get(f"{AUTH_URI}/status")
            assert r.status_code == 401

            r = client.get(f"{AUTH_URI}/status",
                           headers={"Authorization": "Bearer ABC"})
            assert r.status_code == 401
        else:
            r = client.get(f"{AUTH_URI}/status")
            assert r.status_code == 404
Example #3
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
Example #4
0
    def test_auth(self, client: FlaskClient) -> None:

        r = client.get(f"{API_URI}/tests/authentication")
        assert r.status_code == 401

        r = client.get(
            f"{API_URI}/tests/authentication",
            headers={"Authorization": "Bearer invalid"},
        )
        assert r.status_code == 401

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

        r = client.get(f"{API_URI}/tests/authentication", headers=headers)
        assert r.status_code == 200
        content = self.get_content(r)
        assert len(content) == 2
        assert "token" in content
        assert "user" in content
        assert content["token"] == token
        assert content["user"] == BaseAuthentication.default_user

        if not Env.get_bool("ALLOW_ACCESS_TOKEN_PARAMETER"):
            # access token parameter is not allowed by default
            r = client.get(
                f"{API_URI}/tests/authentication", query_string={"access_token": token}
            )
            assert r.status_code == 401
Example #5
0
    def test_04_logout(self, client: FlaskClient) -> None:
        """Check that you can logout with a valid token"""

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

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

        events = self.get_last_events(2)

        assert events[0].event == Events.delete.value
        assert events[0].user == "-"
        assert events[0].target_type == "Token"
        assert events[0].url == "/auth/logout"

        assert events[1].event == Events.logout.value
        assert events[1].user == BaseAuthentication.default_user
        assert events[1].url == "/auth/logout"

        # Check failure
        log.info("*** VERIFY invalid token")
        r = client.get(f"{AUTH_URI}/logout")
        assert r.status_code == 401
Example #6
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
    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
Example #8
0
    def test_cached_authenticated_param_endpoint(self,
                                                 client: FlaskClient) -> None:

        if not Env.get_bool("AUTH_ENABLE"):
            log.warning("Skipping cache with authentication tests")
            return

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

        r = client.get(f"{API_URI}/tests/cache/paramauth", headers=headers1)
        assert r.status_code == 200
        resp1 = self.get_content(r)
        assert isinstance(resp1, list)
        # counter is 1 because this is the first request to this endpoint
        assert resp1[COUNTER] == 1

        r = client.get(f"{API_URI}/tests/cache/paramauth", headers=headers1)
        assert r.status_code == 200
        resp2 = self.get_content(r)
        assert isinstance(resp2, list)
        assert resp2[UUID] == resp1[UUID]
        # Same counter as above, because the response is replied from the cache
        assert resp2[COUNTER] == resp1[COUNTER]
        assert resp2[COUNTER] == 1

        headers2, token2 = self.do_login(client, None, None)
        # Test by using access_token parameter instead of Headers
        r = client.get(f"{API_URI}/tests/cache/paramauth",
                       query_string={"access_token": token2})
        assert r.status_code == 200
        resp3 = self.get_content(r)
        assert isinstance(resp3, list)
        # This is the same user, uuid is unchanged
        assert resp3[UUID] == resp1[UUID]
        # but counter changed, because the response is not replied from the cache
        assert resp3[COUNTER] != resp1[COUNTER]
        assert resp3[COUNTER] == 2

        r = client.get(f"{API_URI}/tests/cache/paramauth",
                       query_string={"access_token": token2})
        assert r.status_code == 200
        resp4 = self.get_content(r)
        assert isinstance(resp4, list)
        assert resp4[UUID] == resp1[UUID]
        # Same counter as above, because the response is replied from the cache
        assert resp4[COUNTER] == resp3[COUNTER]
        assert resp4[COUNTER] == 2

        # Cache is stored starting from the access_token parameter,
        # but the token is the same also if provided as header
        r = client.get(f"{API_URI}/tests/cache/paramauth", headers=headers2)
        assert r.status_code == 200
        resp5 = self.get_content(r)
        assert isinstance(resp5, list)
        assert resp5[UUID] == resp1[UUID]
        # Same counter as above, because the response is replied from the cache
        assert resp5[COUNTER] == resp3[COUNTER]
        assert resp5[COUNTER] == 2
Example #9
0
    def test_optional_auth(self, client: FlaskClient) -> None:

        # Optional authentication can accept missing tokens
        r = client.get(f"{API_URI}/tests/optionalauthentication")
        assert r.status_code == 200
        content = self.get_content(r)
        assert len(content) == 2
        assert "token" in content
        assert "user" in content
        assert content["token"] is None
        assert content["user"] is None

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

        # Or valid tokens
        r = client.get(f"{API_URI}/tests/optionalauthentication", headers=headers)
        assert r.status_code == 200
        content = self.get_content(r)
        assert len(content) == 2
        assert "token" in content
        assert "user" in content
        assert content["token"] == token
        assert content["user"] == BaseAuthentication.default_user

        # But not invalid tokens, i.e. if presented the tokens is always validated
        r = client.get(
            f"{API_URI}/tests/authentication",
            headers={"Authorization": "Bearer invalid"},
        )
        assert r.status_code == 401

        if not Env.get_bool("ALLOW_ACCESS_TOKEN_PARAMETER"):
            # access token parameter is not allowed by default
            r = client.get(
                f"{API_URI}/tests/optionalauthentication",
                query_string={"access_token": token},
            )
            # query token is ignored but the endpoint accepts missing tokens
            assert r.status_code == 200
            content = self.get_content(r)
            assert len(content) == 2
            assert "token" in content
            assert "user" in content
            assert content["token"] is None
            assert content["user"] is None

            r = client.get(
                f"{API_URI}/tests/optionalauthentication",
                query_string={"access_token": "invalid"},
            )
            # invalid tokens should be rejected, but query token is ignored
            assert r.status_code == 200
            content = self.get_content(r)
            assert len(content) == 2
            assert "token" in content
            assert "user" in content
            assert content["token"] is None
            assert content["user"] is None
Example #10
0
    def test_caching_general_clearing(self, client: FlaskClient) -> None:

        if Env.get_bool("AUTH_ENABLE"):
            headers, _ = self.do_login(client, None, None)
        else:
            headers = None

        # get method is cached for 200 seconds

        # First response is not cached
        r = client.get(f"{API_URI}/tests/cache/long")
        assert r.status_code == 200
        counter1 = self.get_content(r)

        # Second response is cached
        r = client.get(f"{API_URI}/tests/cache/long")
        assert r.status_code == 200
        assert self.get_content(r) == counter1

        # Empty all the cache
        Cache.clear()

        # Third response is no longer cached
        r = client.get(f"{API_URI}/tests/cache/long")
        assert r.status_code == 200
        counter2 = self.get_content(r)
        assert counter2 != counter1

        # Response is still cached
        r = client.get(f"{API_URI}/tests/cache/long")
        assert r.status_code == 200
        assert self.get_content(r) == counter2

        # Empty the endpoint cache
        client.delete(f"{API_URI}/tests/cache/long")

        # Second response is no longer cached
        r = client.get(f"{API_URI}/tests/cache/long")
        assert r.status_code == 200
        counter3 = self.get_content(r)
        assert counter3 != counter2

        # Response is still cached
        r = client.get(f"{API_URI}/tests/cache/long")
        assert r.status_code == 200
        assert self.get_content(r) == counter3

        # Endpoint is unauthenticated, headers are ignored when building the cache key
        r = client.get(f"{API_URI}/tests/cache/long", headers=headers)
        assert r.status_code == 200
        assert self.get_content(r) == counter3

        # Tokens are ignored even if invalid
        r = client.get(f"{API_URI}/tests/cache/long",
                       headers={"Authorization": "Bearer invalid"})
        assert r.status_code == 200
        assert self.get_content(r) == counter3
Example #11
0
    def test_GET_specs(self, client: FlaskClient) -> None:

        # Alias for /api/specs
        r = client.get(f"{API_URI}/swagger")
        assert r.status_code == 200

        r = client.get(f"{API_URI}/specs")
        assert r.status_code == 200
        content = self.get_content(r)
        assert "host" in content
        assert "info" in content
        assert "swagger" in content
        assert "schemes" in content
        assert "paths" in content
        assert "definitions" in content
        assert "/api/admin/users" not in content["paths"]

        # Not available in new spec... to be introduced?
        assert "basePath" not in content
        assert "consumes" not in content
        assert "produces" not in content
        # assert "application/json" in content["consumes"]
        # assert "application/json" in content["produces"]
        assert "tags" in content
        # This is no longer in root definition
        # Now it is set for each endpoint, when required
        assert "security" not in content
        # assert "Bearer" in content["security"][0]
        assert "securityDefinitions" in content
        assert "Bearer" in content["securityDefinitions"]

        headers, _ = self.do_login(client, None, None)
        r = client.get(f"{API_URI}/swagger", headers=headers)
        assert r.status_code == 200
        content = self.get_content(r)
        assert "host" in content
        assert "info" in content
        assert "swagger" in content
        assert "schemes" in content
        assert "paths" in content
        assert "definitions" in content
        assert "/auth/logout" in content["paths"]

        # Not available in new spec... to be introduced?
        assert "basePath" not in content
        assert "consumes" not in content
        assert "produces" not in content
        # assert "application/json" in content["consumes"]
        # assert "application/json" in content["produces"]
        assert "tags" in content
        # This is no longer in root definition
        # Now it is set for each endpoint, when required
        assert "security" not in content
        # assert "Bearer" in content["security"][0]
        assert "securityDefinitions" in content
        assert "Bearer" in content["securityDefinitions"]
Example #12
0
    def test_authentication_with_auth_callback(self,
                                               client: FlaskClient) -> None:

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

        auth = Connector.get_authentication_instance()
        user = auth.get_user(username=BaseAuthentication.default_user)

        assert user is not None

        VALID = f"/tests/preloadcallback/{user.uuid}"
        INVALID = "/tests/preloadcallback/12345678-90ab-cdef-1234-567890abcdef"
        admin_headers, _ = self.do_login(client, None, None)

        # Verify both endpoint ...

        r = client.get(f"{API_URI}{VALID}",
                       query_string={"test": True},
                       headers=admin_headers)
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, dict)
        assert len(content) == 1
        assert "email" in content
        assert content["email"] == user.email

        r = client.get(f"{API_URI}{INVALID}",
                       query_string={"test": True},
                       headers=admin_headers)
        assert r.status_code == 401

        # and get_schema!

        r = client.get(
            f"{API_URI}{VALID}",
            query_string={"get_schema": True},
            headers=admin_headers,
        )
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 1
        assert content[0]["key"] == "test"
        assert content[0]["type"] == "boolean"

        r = client.get(
            f"{API_URI}{INVALID}",
            query_string={"get_schema": True},
            headers=admin_headers,
        )
        assert r.status_code == 401
Example #13
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])
Example #14
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
Example #15
0
    def test_optional_access_token_parameter(self, client: FlaskClient) -> None:

        # Optional authentication can accept missing tokens
        r = client.get(f"{API_URI}/tests/optionalqueryauthentication")
        assert r.status_code == 200
        content = self.get_content(r)
        assert len(content) == 2
        assert "token" in content
        assert "user" in content
        assert content["token"] is None
        assert content["user"] is None

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

        # Or valid tokens
        r = client.get(f"{API_URI}/tests/optionalqueryauthentication", headers=headers)
        assert r.status_code == 200
        content = self.get_content(r)
        assert len(content) == 2
        assert "token" in content
        assert "user" in content
        assert content["token"] == token
        assert content["user"] == BaseAuthentication.default_user

        # But not invalid tokens, i.e. if presented the tokens is always validated
        r = client.get(
            f"{API_URI}/tests/optionalqueryauthentication",
            headers={"Authorization": "Bearer invalid"},
        )
        assert r.status_code == 401

        r = client.get(
            f"{API_URI}/tests/optionalqueryauthentication",
            query_string={"access_token": token},
        )
        assert r.status_code == 200
        content = self.get_content(r)
        assert len(content) == 2
        assert "token" in content
        assert "user" in content
        assert content["token"] == token
        assert content["user"] == BaseAuthentication.default_user

        r = client.get(
            f"{API_URI}/tests/optionalqueryauthentication",
            query_string={"access_token": "invalid"},
        )
        # invalid tokens should be rejected, but query token is ignored
        assert r.status_code == 401
Example #16
0
        def test_endpoint(self, client: FlaskClient) -> None:
            r = client.get(f"{API_URI}/tests/neo4j/1")
            assert r.status_code == 200

            r = client.get(f"{API_URI}/tests/neo4j/2")
            assert r.status_code == 400

            r = client.get(f"{API_URI}/tests/neo4j/3")
            assert r.status_code == 200
            data = self.get_content(r)
            data["created"] = dateutil.parser.parse(data["created"])
            data["modified1"] = dateutil.parser.parse(data["modified1"])
            data["modified2"] = dateutil.parser.parse(data["modified2"])
            assert data["created"] < data["modified1"]
            assert data["modified1"] < data["modified2"]
Example #17
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)
Example #18
0
    def test_optional_access_token_parameter(self,
                                             client: FlaskClient) -> None:

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

        # Optional authentication can accept missing tokens
        r = client.get(f"{API_URI}/tests/optionalqueryauthentication")
        assert r.status_code == 204

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

        # Or valid tokens
        r = client.get(f"{API_URI}/tests/optionalqueryauthentication",
                       headers=headers)
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, dict)
        assert len(content) == 1
        assert "email" in content
        assert content["email"] == BaseAuthentication.default_user

        # But not invalid tokens, i.e. if presented the tokens is always validated
        r = client.get(
            f"{API_URI}/tests/optionalqueryauthentication",
            headers={"Authorization": "Bearer invalid"},
        )
        assert r.status_code == 401

        r = client.get(
            f"{API_URI}/tests/optionalqueryauthentication",
            query_string={"access_token": token},
        )
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, dict)
        assert len(content) == 1
        assert "email" in content
        assert content["email"] == BaseAuthentication.default_user

        r = client.get(
            f"{API_URI}/tests/optionalqueryauthentication",
            query_string={"access_token": "invalid"},
        )
        # invalid tokens should be rejected, but query token is ignored
        assert r.status_code == 401
Example #19
0
    def test_depends_on(self, client: FlaskClient) -> None:

        if Connector.check_availability("neo4j"):

            r = client.get(f"{API_URI}/tests/depends_on/neo4j")
            assert r.status_code == 200

            r = client.get(f"{API_URI}/tests/depends_on_not/neo4j")
            assert r.status_code == 404

        else:

            r = client.get(f"{API_URI}/tests/depends_on/neo4j")
            assert r.status_code == 404

            r = client.get(f"{API_URI}/tests/depends_on_not/neo4j")
            assert r.status_code == 200
Example #20
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
Example #21
0
 def test_05_login_failures(self, client: FlaskClient) -> None:
     if Env.get_bool("MAIN_LOGIN_ENABLE") and Env.get_bool("AUTH_ENABLE"):
         # Create a new user on the fly to test the cached endpoint
         _, data = self.create_user(client)
         headers, _ = self.do_login(
             client, data["email"], data["password"], test_failures=True
         )
         r = client.get(f"{AUTH_URI}/logout", headers=headers)
         assert r.status_code == 204
Example #22
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
Example #23
0
    def test_no_auth(self, client: FlaskClient) -> None:

        r = client.get(f"{API_URI}/tests/noauth")
        assert r.status_code == 200
        assert self.get_content(r) == "OK"

        if Env.get_bool("AUTH_ENABLE"):
            headers, _ = self.do_login(client, None, None)

            # Tokens are ignored
            r = client.get(f"{API_URI}/tests/noauth", headers=headers)
            assert r.status_code == 200
            assert self.get_content(r) == "OK"

        # Tokens are ignored even if invalid
        r = client.get(f"{API_URI}/tests/noauth",
                       headers={"Authorization": "Bearer invalid"})
        assert r.status_code == 200
        assert self.get_content(r) == "OK"
Example #24
0
    def test_admin_stats(self, client: FlaskClient, faker: Faker) -> None:

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

        r = client.get(f"{API_URI}/admin/logins")
        assert r.status_code == 401

        random_username = faker.ascii_email()
        self.do_login(client, random_username, faker.pystr(), status_code=401)

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

        r = client.get(f"{API_URI}/admin/logins", headers=headers)
        assert r.status_code == 200
        logins = self.get_content(r)
        assert isinstance(logins, list)
        assert len(logins) > 0
        assert "username" in logins[0]
        assert "date" in logins[0]
        assert "IP" in logins[0]
        assert "location" in logins[0]
        assert "failed" in logins[0]
        assert "flushed" in logins[0]

        for login in logins:
            if login["username"] == BaseAuthentication.default_user:
                break
        else:  # pragma: no cover
            pytest.fail("Default user not found in logins table")

        for login in logins:
            if login["username"] == random_username:
                assert login["failed"] is True
                assert login["flushed"] is False
                break
        else:  # pragma: no cover
            pytest.fail("Random user not found in logins table")
Example #25
0
    def test_access_token_parameter(self, client: FlaskClient) -> None:

        r = client.get(f"{API_URI}/tests/queryauthentication")
        assert r.status_code == 401

        r = client.get(
            f"{API_URI}/tests/queryauthentication",
            headers={"Authorization": "Bearer invalid"},
        )
        assert r.status_code == 401

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

        r = client.get(f"{API_URI}/tests/queryauthentication", headers=headers)
        assert r.status_code == 200
        content = self.get_content(r)
        assert len(content) == 2
        assert "token" in content
        assert "user" in content
        assert content["token"] == token
        assert content["user"] == BaseAuthentication.default_user

        r = client.get(
            f"{API_URI}/tests/queryauthentication", query_string={"access_token": token}
        )
        assert r.status_code == 200
        content = self.get_content(r)
        assert len(content) == 2
        assert "token" in content
        assert "user" in content
        assert content["token"] == token
        assert content["user"] == BaseAuthentication.default_user

        r = client.get(
            f"{API_URI}/tests/queryauthentication",
            query_string={"access_token": "invalid"},
        )
        assert r.status_code == 401
    def test_06_token_ip_validity(self, client: FlaskClient,
                                  faker: Faker) -> None:

        if Env.get_bool("MAIN_LOGIN_ENABLE"):
            if Env.get_int("AUTH_TOKEN_IP_GRACE_PERIOD") < 10:
                headers, _ = self.do_login(client, None, None)

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

                r = client.get(
                    f"{AUTH_URI}/status",
                    headers=headers,
                    environ_base={"REMOTE_ADDR": faker.ipv4()},
                )
                assert r.status_code == 200

                time.sleep(Env.get_int("AUTH_TOKEN_IP_GRACE_PERIOD"))

                r = client.get(
                    f"{AUTH_URI}/status",
                    headers=headers,
                    environ_base={"REMOTE_ADDR": faker.ipv4()},
                )
                log.error(
                    "DEBUG CODE: this 401 should be due to invalid IP address")
                assert r.status_code == 401

                # After the failure the token is still valid if used from the corret IP
                r = client.get(f"{AUTH_URI}/status", headers=headers)
                assert r.status_code == 200

                # Another option to provide IP is through the header passed by nginx
                headers["X-Forwarded-For"] = faker.ipv4()  # type: ignore
                r = client.get(f"{AUTH_URI}/status", headers=headers)
                assert r.status_code == 401
Example #27
0
    def test_06_token_ip_validity(self, client: FlaskClient, faker: Faker) -> None:

        if Env.get_bool("MAIN_LOGIN_ENABLE") and Env.get_bool("AUTH_ENABLE"):
            if Env.get_int("AUTH_TOKEN_IP_GRACE_PERIOD") < 10:
                headers, _ = self.do_login(client, None, None)

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

                r = client.get(
                    f"{AUTH_URI}/status",
                    headers=headers,
                    environ_base={"REMOTE_ADDR": faker.ipv4()},
                )
                assert r.status_code == 200

                time.sleep(Env.get_int("AUTH_TOKEN_IP_GRACE_PERIOD"))

                r = client.get(
                    f"{AUTH_URI}/status",
                    headers=headers,
                    environ_base={"REMOTE_ADDR": faker.ipv4()},
                )
                assert r.status_code == 401

                # After the failure the token is still valid if used from the correct IP
                r = client.get(f"{AUTH_URI}/status", headers=headers)
                assert r.status_code == 200

                # Another option to provide IP is through the header passed by nginx
                # This only works if PROXIED_CONNECTION is on
                # (disabled by default, for security purpose)
                if Env.get_bool("PROXIED_CONNECTION"):
                    headers["X-Forwarded-For"] = faker.ipv4()  # type: ignore
                    r = client.get(f"{AUTH_URI}/status", headers=headers)
                    assert r.status_code == 401
Example #28
0
    def test_cached_semiauthenticated_endpoint(self,
                                               client: FlaskClient) -> None:

        if not Env.get_bool("AUTH_ENABLE"):
            log.warning("Skipping cache with authentication tests")
            return

        r = client.get(f"{API_URI}/tests/cache/optionalauth")
        assert r.status_code == 200
        nonauthenticated1 = self.get_content(r)
        assert isinstance(nonauthenticated1, list)
        assert nonauthenticated1[UUID] == "N/A"
        # counter is 1 because this is the first request to this endpoint
        assert nonauthenticated1[COUNTER] == 1

        r = client.get(f"{API_URI}/tests/cache/optionalauth")
        assert r.status_code == 200
        nonauthenticated2 = self.get_content(r)
        assert isinstance(nonauthenticated2, list)
        assert nonauthenticated2[UUID] == "N/A"
        # Same counter as above, because the response is replied from the cache
        assert nonauthenticated2[COUNTER] == 1

        headers, _ = self.do_login(client, None, None)
        r = client.get(f"{API_URI}/tests/cache/optionalauth", headers=headers)
        assert r.status_code == 200
        authenticated1 = self.get_content(r)
        assert isinstance(authenticated1, list)
        assert authenticated1[UUID] != "N/A"
        # The counter changed, because the response is not replied from the cache
        assert authenticated1[COUNTER] == 2

        # Token cached => cache should be used
        r = client.get(f"{API_URI}/tests/cache/optionalauth", headers=headers)
        assert r.status_code == 200
        authenticated2 = self.get_content(r)
        assert isinstance(authenticated2, list)
        assert authenticated2[UUID] == authenticated1[UUID]
        # Same counter as above, because the response is replied from the cache
        assert authenticated2[COUNTER] == 2

        # New token => no cache
        headers, _ = self.do_login(client, None, None)
        r = client.get(f"{API_URI}/tests/cache/optionalauth", headers=headers)
        assert r.status_code == 200
        authenticated2 = self.get_content(r)
        assert isinstance(authenticated2, list)
        assert authenticated2[UUID] == authenticated1[UUID]
        # Counter changed
        assert authenticated2[COUNTER] == 3

        r = client.get(
            f"{API_URI}/tests/cache/optionalauth",
            headers={"Authorization": "Bearer invalid"},
        )
        assert r.status_code == 401
Example #29
0
def delete_test_env(
    client: FlaskClient,
    user_A1_headers: Tuple[Optional[Dict[str, str]], str],
    user_B1_headers: Tuple[Optional[Dict[str, str]], str],
    user_B1_uuid: str,
    user_B2_uuid: str,
    user_A1_uuid: str,
    uuid_group_A: str,
    uuid_group_B: str,
    study1_uuid: Optional[str] = None,
    study2_uuid: Optional[str] = None,
) -> None:

    admin_headers, _ = BaseTests.do_login(client, None, None)
    # delete all the elements used by the test
    if study1_uuid:
        r = client.delete(f"{API_URI}/study/{study1_uuid}",
                          headers=user_B1_headers)
        assert r.status_code == 204
    if study2_uuid:
        r = client.delete(f"{API_URI}/study/{study2_uuid}",
                          headers=user_A1_headers)
        assert r.status_code == 204
    # first user
    r = client.delete(f"{API_URI}/admin/users/{user_B1_uuid}",
                      headers=admin_headers)
    assert r.status_code == 204
    # second user
    r = client.delete(f"{API_URI}/admin/users/{user_B2_uuid}",
                      headers=admin_headers)
    assert r.status_code == 204
    # other user
    r = client.delete(f"{API_URI}/admin/users/{user_A1_uuid}",
                      headers=admin_headers)
    assert r.status_code == 204

    # group A directory
    INPUT_ROOT_path = INPUT_ROOT.joinpath(uuid_group_A)
    shutil.rmtree(INPUT_ROOT_path)
    # group A
    r = client.delete(f"{API_URI}/admin/groups/{uuid_group_A}",
                      headers=admin_headers)
    assert r.status_code == 204
    # group B directory
    INPUT_ROOT_path = INPUT_ROOT.joinpath(uuid_group_B)
    shutil.rmtree(INPUT_ROOT_path)
    # group B
    r = client.delete(f"{API_URI}/admin/groups/{uuid_group_B}",
                      headers=admin_headers)
    assert r.status_code == 204
Example #30
0
    def test_caching_autocleaning(self, client: FlaskClient) -> None:

        if Env.get_bool("AUTH_ENABLE"):
            headers, _ = self.do_login(client, None, None)
        else:
            headers = None

        # Syncronize this test to start at the beginning of the next second and
        # prevent the test to overlap a change of second
        # Since the caching is rounded to the second, few milliseconds cann make the
        # difference, for example:
        # A first request at 00:00:00.997 is cached
        # A second request at 00:00:01.002 is no longer cached, even if only 5 millisec
        # elapsed because the second changed
        # Added 0.01 just to avoid to exactly start at the beginning of the second
        t = 1.01 - datetime.now().microsecond / 1000000.0
        log.critical("Sleeping {} sec", t)
        time.sleep(t)

        # the GET method is cached for 1 second

        # First response is not cached
        r = client.get(f"{API_URI}/tests/cache/short")
        assert r.status_code == 200
        counter1 = self.get_content(r)

        # Second response is cached
        r = client.get(f"{API_URI}/tests/cache/short")
        assert r.status_code == 200
        assert self.get_content(r) == counter1

        # Third response is no longer cached
        time.sleep(1)

        r = client.get(f"{API_URI}/tests/cache/short")
        assert r.status_code == 200
        counter2 = self.get_content(r)
        assert counter2 != counter1

        # Fourth response is cached again
        r = client.get(f"{API_URI}/tests/cache/short")
        assert r.status_code == 200
        assert self.get_content(r) == counter2

        # Endpoint is unauthenticated, headers are ignored when building the cache key
        r = client.get(f"{API_URI}/tests/cache/short", headers=headers)
        assert r.status_code == 200
        assert self.get_content(r) == counter2

        # Tokens are ignored even if invalid
        r = client.get(f"{API_URI}/tests/cache/short",
                       headers={"Authorization": "Bearer invalid"})
        assert r.status_code == 200
        assert self.get_content(r) == counter2