コード例 #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
コード例 #2
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
コード例 #3
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
コード例 #4
0
ファイル: tests.py プロジェクト: joskid/http-api
    def create_user(
        cls,
        client: FlaskClient,
        data: Optional[Dict[str, Any]] = None,
        roles: Optional[List[Union[str, Role]]] = None,
    ) -> Tuple[str, Dict[str, Any]]:

        assert Env.get_bool("MAIN_LOGIN_ENABLE")

        admin_headers, _ = cls.do_login(client, None, None)
        assert admin_headers is not None
        schema = cls.getDynamicInputSchema(client, "admin/users",
                                           admin_headers)
        user_data = cls.buildData(schema)
        if Connector.check_availability("smtp"):
            user_data["email_notification"] = False
        user_data["is_active"] = True
        user_data["expiration"] = None

        if roles:
            for idx, role in enumerate(roles):
                if isinstance(role, Role):
                    roles[idx] = role.value

            user_data["roles"] = json.dumps(roles)

        if data:
            user_data.update(data)
        r = client.post(f"{API_URI}/admin/users",
                        data=user_data,
                        headers=admin_headers)
        assert r.status_code == 200
        uuid = cls.get_content(r)

        return uuid, user_data
コード例 #5
0
        def test_01_login_ban_not_enabled(self, client: FlaskClient) -> None:

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

            uuid, data = self.create_user(client)
            # 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"]

            # 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"]

            # Goodbye temporary user
            self.delete_user(client, uuid)
コード例 #6
0
        def test_05_no_notification_email_for_wrong_usernames(
            self, client: FlaskClient, faker: Faker
        ) -> None:

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

            uuid, data = self.create_user(client)

            self.delete_mock_email()

            # Just to verify that email is deleted
            with pytest.raises(FileNotFoundError):
                self.read_mock_email()

            email = faker.ascii_email()
            # Wrong credentials with a non existing email
            # -> No notification will be sent
            for _ in range(0, max_login_attempts):
                self.do_login(client, email, data["password"], status_code=401)

            # Verify the ban (i.e. status 403)
            headers, _ = self.do_login(client, email, data["password"], status_code=403)
            assert headers is None

            # Verify that there are no mocked email
            with pytest.raises(FileNotFoundError):
                self.read_mock_email()

            # Goodbye temporary user
            self.delete_user(client, uuid)
コード例 #7
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
コード例 #8
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
コード例 #9
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)
コード例 #10
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
コード例 #11
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
コード例 #12
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
コード例 #13
0
ファイル: tests.py プロジェクト: joskid/http-api
    def delete_user(cls, client: FlaskClient, uuid: str) -> None:

        assert Env.get_bool("MAIN_LOGIN_ENABLE")

        admin_headers, _ = cls.do_login(client, None, None)
        assert admin_headers is not None
        r = client.delete(f"{API_URI}/admin/users/{uuid}",
                          headers=admin_headers)
        assert r.status_code == 204
コード例 #14
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
コード例 #15
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
コード例 #16
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
コード例 #17
0
        def post(self, **kwargs: Any) -> Response:
            """ Register new user """

            email = kwargs.get("email")
            user = self.auth.get_user(username=email)
            if user is not None:
                raise Conflict(f"This user already exists: {email}")

            password_confirm = kwargs.pop("password_confirm")
            if kwargs.get("password") != password_confirm:
                raise Conflict("Your password doesn't match the confirmation")

            if self.auth.VERIFY_PASSWORD_STRENGTH:

                check, msg = self.auth.verify_password_strength(
                    kwargs.get("password"), None)

                if not check:
                    raise Conflict(msg)

            kwargs["is_active"] = False
            user = self.auth.create_user(kwargs, [self.auth.default_role])

            default_group = self.auth.get_group(name=DEFAULT_GROUP_NAME)
            self.auth.add_user_to_group(user, default_group)
            self.auth.save_user(user)

            self.log_event(self.events.create, user, kwargs)

            try:
                smtp_client = smtp.get_instance()
                if Env.get_bool("REGISTRATION_NOTIFICATIONS"):
                    # Sending an email to the administrator
                    title = get_project_configuration("project.title",
                                                      default="Unkown title")
                    subject = f"{title} New credentials requested"
                    body = f"New credentials request from {user.email}"

                    smtp_client.send(body, subject)

                send_activation_link(smtp_client, self.auth, user)

            except BaseException as e:  # pragma: no cover
                self.auth.delete_user(user)
                raise ServiceUnavailable(
                    f"Errors during account registration: {e}")

            return self.response(
                "We are sending an email to your email address where "
                "you will find the link to activate your account")
コード例 #18
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
コード例 #19
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)
コード例 #20
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
コード例 #21
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
コード例 #22
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"
コード例 #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"
コード例 #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")
コード例 #25
0
ファイル: tests.py プロジェクト: joskid/http-api
    def create_group(
            cls,
            client: FlaskClient,
            data: Optional[Dict[str,
                                Any]] = None) -> Tuple[str, Dict[str, Any]]:

        assert Env.get_bool("MAIN_LOGIN_ENABLE")

        admin_headers, _ = cls.do_login(client, None, None)
        assert admin_headers is not None
        schema = cls.getDynamicInputSchema(client, "admin/groups",
                                           admin_headers)
        group_data = cls.buildData(schema)
        if data:
            group_data.update(data)
        r = client.post(f"{API_URI}/admin/groups",
                        data=group_data,
                        headers=admin_headers)
        assert r.status_code == 200
        uuid = cls.get_content(r)

        return uuid, group_data
コード例 #26
0
    def skip_endpoint(depends_on):
        for var in depends_on:
            pieces = var.strip().split(" ")
            pieces_num = len(pieces)
            if pieces_num == 1:
                dependency = pieces.pop()
                negate = False
            elif pieces_num == 2:
                neg, dependency = pieces
                negate = neg.lower() == "not"
            else:  # pragma: no cover
                print_and_exit("Wrong depends_on parameter: {}", var)

            check = Env.get_bool(dependency)
            if negate:
                check = not check

            # Skip if not meeting the requirements of the dependency
            if not check:
                return True, dependency

        return False, None
コード例 #27
0
    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
コード例 #28
0
class TestApp(BaseTests):
    def test_01_login(self, client: FlaskClient, faker: Faker) -> None:
        """ Check that you can login and receive back your token """

        log.info("*** VERIFY CASE INSENSITIVE LOGIN")
        # BaseAuthentication.load_default_user()
        # BaseAuthentication.load_roles()
        USER = BaseAuthentication.default_user or "just-to-prevent-None"
        PWD = BaseAuthentication.default_password or "just-to-prevent-None"

        # Login by using upper case username
        self.do_login(client, USER.upper(), PWD)

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

        # Wrong credentials
        # Off course PWD cannot be upper :D
        self.do_login(client, USER, PWD.upper(), status_code=401)

        events = self.get_last_events(1)
        assert events[0].event == Events.failed_login.value
        assert events[0].payload["username"] == USER

        log.info("*** VERIFY valid credentials")
        # Login by using normal username (no upper case)
        headers, _ = self.do_login(client, None, None)

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

        time.sleep(5)
        # Verify MAX_PASSWORD_VALIDITY, if set
        headers, token = self.do_login(client, None, None)

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

        self.save("auth_header", headers)
        self.save("auth_token", token)

        # Verify credentials
        r = client.get(f"{AUTH_URI}/status", headers=headers)
        assert r.status_code == 200
        c = self.get_content(r)
        assert isinstance(c, bool) and c

        # this check verifies a BUG with neo4j causing crash of auth module
        # when using a non-email-username to authenticate
        log.info("*** VERIFY with a non-email-username")

        self.do_login(
            client,
            "notanemail",
            "[A-Za-z0-9]+",
            status_code=400,
        )

        # Check failure
        log.info("*** VERIFY invalid credentials")

        random_email = faker.ascii_email()
        self.do_login(
            client,
            random_email,
            faker.password(strong=True),
            status_code=401,
        )

        events = self.get_last_events(1)
        assert events[0].event == Events.failed_login.value
        assert events[0].payload["username"] == random_email

    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

    def test_03_change_profile(self, client: FlaskClient,
                               faker: Faker) -> None:

        # Always enable during core tests
        if not Env.get_bool("MAIN_LOGIN_ENABLE"):  # pragma: no cover
            log.warning("Profile is disabled, skipping tests")
            return

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

        # update profile, no auth
        r = client.put(f"{AUTH_URI}/profile")
        assert r.status_code == 401
        # update profile, no auth
        r = client.patch(f"{AUTH_URI}/profile")
        assert r.status_code == 401

        # update profile, no data
        r = client.patch(f"{AUTH_URI}/profile", data={}, headers=headers)
        assert r.status_code == 204

        events = self.get_last_events(1)
        assert events[0].event == Events.modify.value
        assert events[0].user == BaseAuthentication.default_user
        assert events[0].target_type == "User"
        # It is true in the core, but projects may introduce additional values
        # and expand the input dictionary even if initially empty
        # e.g. meteohub adds here the requests_expiration_days parameter
        # assert len(events[0].payload) == 0

        newname = faker.name()
        newuuid = faker.pystr()

        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 200
        c = self.get_content(r)
        assert c.get("name") is not None
        assert c.get("name") != newname
        assert c.get("uuid") is not None
        assert c.get("uuid") != newuuid

        # update profile
        data = {"name": newname, "uuid": newuuid}
        r = client.patch(f"{AUTH_URI}/profile", data=data, headers=headers)
        # uuid cannot be modified and will raise an unknown field
        assert r.status_code == 400
        data = {"name": newname}
        r = client.patch(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 204

        events = self.get_last_events(1)
        assert events[0].event == Events.modify.value
        assert events[0].user == BaseAuthentication.default_user
        assert events[0].target_type == "User"
        # It is true in the core, but projects may introduce additional values
        # and expand the input dictionary even if initially empty
        # e.g. meteohub adds here the requests_expiration_days parameter
        # assert len(events[0].payload) == 1
        assert "name" in events[0].payload

        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 200
        c = self.get_content(r)
        assert c.get("name") == newname
        assert c.get("uuid") != newuuid

        # change password, no data
        r = client.put(f"{AUTH_URI}/profile", data={}, headers=headers)
        assert r.status_code == 400
        # Sending a new_password and/or password_confirm without a password
        newpassword = faker.password()
        data = {"new_password": newpassword}
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400
        data = {"password_confirm": newpassword}
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400
        data = {"new_password": newpassword, "password_confirm": newpassword}
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400

        data = {}
        data["password"] = faker.password(length=5)
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400

        data["new_password"] = faker.password(length=5)
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400

        data["password_confirm"] = faker.password(length=5)
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400

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

        # Passwords are too short
        data["password_confirm"] = data["new_password"]
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400

        # Trying to set new password == password... it is not permitted!
        data["password_confirm"] = data["password"]
        data["new_password"] = data["password"]

        if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"):
            data["totp_code"] = BaseTests.generate_totp(
                BaseAuthentication.default_user)

        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 409

        # Change the password
        data["new_password"] = faker.password(strong=True)
        data["password_confirm"] = data["new_password"]
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 204

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

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

        # verify the new password
        headers, _ = self.do_login(client, BaseAuthentication.default_user,
                                   data["new_password"])

        # restore the previous password
        data["password"] = data["new_password"]
        data["new_password"] = BaseAuthentication.default_password
        data["password_confirm"] = BaseAuthentication.default_password
        if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"):
            data["totp_code"] = BaseTests.generate_totp(
                BaseAuthentication.default_user)
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 204

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

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

        # verify the new password
        headers, _ = self.do_login(client, BaseAuthentication.default_user,
                                   BaseAuthentication.default_password)

        self.save("auth_header", headers)

    def test_04_logout(self, client: FlaskClient) -> None:
        """ Check that you can logout with a valid token """

        # 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[1].event == Events.logout.value
        assert events[1].user == BaseAuthentication.default_user

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

    def test_05_login_failures(self, client: FlaskClient) -> None:
        if Env.get_bool("MAIN_LOGIN_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

    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

    if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"):

        def test_07_totp_failures(self, client: FlaskClient,
                                  faker: Faker) -> None:

            uuid, data = self.create_user(client)

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

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

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

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

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

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

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

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

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

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

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

            password = new_password

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            assert headers is not None

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

            self.delete_user(client, uuid)
コード例 #29
0
    def test_03_change_profile(self, client: FlaskClient,
                               faker: Faker) -> None:

        # Always enable during core tests
        if not Env.get_bool("MAIN_LOGIN_ENABLE"):  # pragma: no cover
            log.warning("Profile is disabled, skipping tests")
            return

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

        # update profile, no auth
        r = client.put(f"{AUTH_URI}/profile")
        assert r.status_code == 401
        # update profile, no auth
        r = client.patch(f"{AUTH_URI}/profile")
        assert r.status_code == 401

        # update profile, no data
        r = client.patch(f"{AUTH_URI}/profile", data={}, headers=headers)
        assert r.status_code == 204

        events = self.get_last_events(1)
        assert events[0].event == Events.modify.value
        assert events[0].user == BaseAuthentication.default_user
        assert events[0].target_type == "User"
        # It is true in the core, but projects may introduce additional values
        # and expand the input dictionary even if initially empty
        # e.g. meteohub adds here the requests_expiration_days parameter
        # assert len(events[0].payload) == 0

        newname = faker.name()
        newuuid = faker.pystr()

        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 200
        c = self.get_content(r)
        assert c.get("name") is not None
        assert c.get("name") != newname
        assert c.get("uuid") is not None
        assert c.get("uuid") != newuuid

        # update profile
        data = {"name": newname, "uuid": newuuid}
        r = client.patch(f"{AUTH_URI}/profile", data=data, headers=headers)
        # uuid cannot be modified and will raise an unknown field
        assert r.status_code == 400
        data = {"name": newname}
        r = client.patch(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 204

        events = self.get_last_events(1)
        assert events[0].event == Events.modify.value
        assert events[0].user == BaseAuthentication.default_user
        assert events[0].target_type == "User"
        # It is true in the core, but projects may introduce additional values
        # and expand the input dictionary even if initially empty
        # e.g. meteohub adds here the requests_expiration_days parameter
        # assert len(events[0].payload) == 1
        assert "name" in events[0].payload

        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 200
        c = self.get_content(r)
        assert c.get("name") == newname
        assert c.get("uuid") != newuuid

        # change password, no data
        r = client.put(f"{AUTH_URI}/profile", data={}, headers=headers)
        assert r.status_code == 400
        # Sending a new_password and/or password_confirm without a password
        newpassword = faker.password()
        data = {"new_password": newpassword}
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400
        data = {"password_confirm": newpassword}
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400
        data = {"new_password": newpassword, "password_confirm": newpassword}
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400

        data = {}
        data["password"] = faker.password(length=5)
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400

        data["new_password"] = faker.password(length=5)
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400

        data["password_confirm"] = faker.password(length=5)
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400

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

        # Passwords are too short
        data["password_confirm"] = data["new_password"]
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 400

        # Trying to set new password == password... it is not permitted!
        data["password_confirm"] = data["password"]
        data["new_password"] = data["password"]

        if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"):
            data["totp_code"] = BaseTests.generate_totp(
                BaseAuthentication.default_user)

        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 409

        # Change the password
        data["new_password"] = faker.password(strong=True)
        data["password_confirm"] = data["new_password"]
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 204

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

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

        # verify the new password
        headers, _ = self.do_login(client, BaseAuthentication.default_user,
                                   data["new_password"])

        # restore the previous password
        data["password"] = data["new_password"]
        data["new_password"] = BaseAuthentication.default_password
        data["password_confirm"] = BaseAuthentication.default_password
        if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"):
            data["totp_code"] = BaseTests.generate_totp(
                BaseAuthentication.default_user)
        r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers)
        assert r.status_code == 204

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

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

        # verify the new password
        headers, _ = self.do_login(client, BaseAuthentication.default_user,
                                   BaseAuthentication.default_password)

        self.save("auth_header", headers)
コード例 #30
0
    def test_01_login(self, client: FlaskClient, faker: Faker) -> None:
        """Check that you can login and receive back your token"""

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

        log.info("*** VERIFY CASE INSENSITIVE LOGIN")
        # BaseAuthentication.load_default_user()
        # BaseAuthentication.load_roles()
        USER = BaseAuthentication.default_user or "just-to-prevent-None"
        PWD = BaseAuthentication.default_password or "just-to-prevent-None"

        # Login by using upper case username
        self.do_login(client, USER.upper(), PWD)

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

        auth = Connector.get_authentication_instance()
        logins = auth.get_logins(USER)
        login = logins[-1]
        assert login.username == USER

        # Wrong credentials
        # Off course PWD cannot be upper :D
        self.do_login(client, USER, PWD.upper(), status_code=401)

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

        logins = auth.get_logins(USER)
        login = logins[-1]
        assert login.username == USER

        log.info("*** VERIFY valid credentials")
        # Login by using normal username (no upper case)
        headers, _ = self.do_login(client, None, None)

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

        time.sleep(5)
        # Verify MAX_PASSWORD_VALIDITY, if set
        headers, token = self.do_login(client, None, None)

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

        self.save("auth_header", headers)
        self.save("auth_token", token)

        # Verify credentials
        r = client.get(f"{AUTH_URI}/status", headers=headers)
        assert r.status_code == 200
        c = self.get_content(r)
        assert isinstance(c, bool) and c

        # this check verifies a BUG with neo4j causing crash of auth module
        # when using a non-email-username to authenticate
        log.info("*** VERIFY with a non-email-username")

        self.do_login(
            client,
            "notanemail",
            "[A-Za-z0-9]+",
            status_code=400,
        )

        # Check failure
        log.info("*** VERIFY invalid credentials")

        random_email = faker.ascii_email()
        self.do_login(
            client,
            random_email,
            faker.password(strong=True),
            status_code=401,
        )

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