Esempio n. 1
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. 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
Esempio n. 3
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
Esempio n. 4
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. 5
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
Esempio n. 6
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
Esempio n. 7
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
Esempio n. 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
Esempio n. 9
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"]
Esempio n. 10
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
Esempio n. 11
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
Esempio n. 12
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"]
Esempio n. 13
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
Esempio n. 14
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
Esempio n. 15
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
Esempio n. 16
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
Esempio n. 17
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"
Esempio n. 18
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")
Esempio n. 19
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
Esempio n. 20
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
Esempio n. 21
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
    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
Esempio n. 23
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
Esempio n. 24
0
    def test_parameter_injection(self, client: FlaskClient) -> None:

        if Env.get_bool("AUTH_ENABLE"):
            headers, _ = self.do_login(client, None, None)
            r = client.get(f"{API_URI}/tests/inject/myparam", headers=headers)
            assert r.status_code == 200

            response = self.get_content(r)
            assert isinstance(response, list)
            assert len(response) == 3

            # User is injected by the authentication decorator
            assert response[0] == BaseAuthentication.default_user
            # myparam is injected as url parameter
            assert response[1] == "myparam"
            # default_value is injected only because it has a... default value
            assert response[2] == "default_value"
Esempio n. 25
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. 26
0
    def test_authentication_with_multiple_roles(self, client: FlaskClient) -> None:

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

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

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

        r = client.get(
            f"{API_URI}/tests/manyrolesauthentication", headers=admin_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"] == admin_token
        assert content["user"] == BaseAuthentication.default_user

        r = client.get(
            f"{API_URI}/tests/unknownroleauthentication", headers=admin_headers
        )
        assert r.status_code == 401

        if Env.get_bool("MAIN_LOGIN_ENABLE"):
            uuid, data = self.create_user(client, roles=[Role.USER])
            user_header, user_token = self.do_login(
                client, data.get("email"), data.get("password")
            )

            r = client.get(
                f"{API_URI}/tests/manyrolesauthentication", headers=user_header
            )
            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"] == user_token
            assert content["user"] == data.get("email")

            r = client.get(
                f"{API_URI}/tests/unknownroleauthentication", headers=user_header
            )
            assert r.status_code == 401

            self.delete_user(client, uuid)
Esempio n. 27
0
    def test_authentication_with_multiple_roles(self,
                                                client: FlaskClient) -> None:

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

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

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

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

        r = client.get(f"{API_URI}/tests/manyrolesauthentication",
                       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"] == BaseAuthentication.default_user

        r = client.get(f"{API_URI}/tests/unknownroleauthentication",
                       headers=admin_headers)
        assert r.status_code == 401

        if Env.get_bool("MAIN_LOGIN_ENABLE"):
            uuid, data = self.create_user(client, roles=[Role.USER])
            user_header, _ = self.do_login(client, data.get("email"),
                                           data.get("password"))

            r = client.get(f"{API_URI}/tests/manyrolesauthentication",
                           headers=user_header)
            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"] == data.get("email")

            r = client.get(f"{API_URI}/tests/unknownroleauthentication",
                           headers=user_header)
            assert r.status_code == 401

            self.delete_user(client, uuid)
Esempio n. 28
0
    def test_download(self, client: FlaskClient) -> None:

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

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

        # no filename provided
        r = client.get(f"{API_URI}/tests/download")
        assert r.status_code == 400

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

        new_content = "new content"
        r = client.put(
            f"{API_URI}/tests/upload",
            data={
                "file": (io.BytesIO(str.encode(new_content)), self.fname),
                "force": True,
            },
        )
        assert r.status_code == 200

        r = client.get(f"{API_URI}/tests/download/{self.fname}")
        assert r.status_code == 200
        content = r.data.decode("utf-8")
        assert content != self.fcontent
        assert content == new_content

        r = client.get(f"{API_URI}/tests/download/{self.fname}",
                       query_string={"stream": True})
        assert r.status_code == 200
        content = r.data.decode("utf-8")
        assert content == new_content

        r = client.get(f"{API_URI}/tests/download/doesnotexist",
                       query_string={"stream": True})
        assert r.status_code == 400
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) == ""
    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