Exemple #1
0
    def __init__(self):
        self.commands = {}
        self.variables = Env.load_variables_group(prefix="telegram")
        if not self.variables.get("api_key"):  # pragma: no cover
            raise ServiceUnavailable("Missing API KEY")
        self.updater = Updater(
            self.variables.get("api_key"),
            # Starting from v13 use_context is True by default
            # use_context=True,
            workers=Env.to_int(self.variables.get("workers"), default=1),
        )

        # Inline keyboard callback
        self.updater.dispatcher.add_handler(
            CallbackQueryHandler(self.inline_keyboard_button))

        # Errors
        self.updater.dispatcher.add_error_handler(self.error_callback)

        self.admins = Bot.get_ids(self.variables.get("admins"))
        if not self.admins:  # pragma: no cover
            print_and_exit("No admin list")

        self.users = Bot.get_ids(self.variables.get("users"))

        self.api = BotApiClient(self.variables)
Exemple #2
0
def launch() -> None:  # pragma: no cover
    """Launch the RAPyDo-based HTTP API server"""

    mywait()

    if initializing():
        print_and_exit(
            "Please wait few more seconds: initialization is still in progress"
        )

    current_app = Env.get("FLASK_APP", "").strip()
    if not current_app:
        os.environ["FLASK_APP"] = f"{current_package}.__main__"

    args = [
        "run",
        "--host",
        BIND_INTERFACE,
        "--port",
        Env.get("FLASK_PORT", "8080"),
        "--reload",
        "--no-debugger",
        "--eager-loading",
        "--with-threads",
    ]

    # Call to untyped function "FlaskGroup" in typed context
    fg_cli = FlaskGroup()  # type: ignore
    # Call to untyped function "main" in typed context
    fg_cli.main(prog_name="restapi", args=args)  # type: ignore
    log.warning("Server shutdown")
Exemple #3
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
Exemple #4
0
    def load_default_user() -> None:

        BaseAuthentication.default_user = Env.get("AUTH_DEFAULT_USERNAME", "")
        BaseAuthentication.default_password = Env.get("AUTH_DEFAULT_PASSWORD",
                                                      "")
        if (not BaseAuthentication.default_user or
                not BaseAuthentication.default_password):  # pragma: no cover
            print_and_exit("Default credentials are unavailable!")
Exemple #5
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
Exemple #6
0
def send_notification(
    subject: str,
    template: str,
    # if None will be sent to the administrator
    to_address: Optional[str] = None,
    data: Optional[Dict[str, Any]] = None,
    user: Optional[User] = None,
    send_async: bool = False,
) -> bool:

    # Always enabled during tests
    if not Connector.check_availability("smtp"):  # pragma: no cover
        return False

    title = get_project_configuration("project.title", default="Unkown title")
    reply_to = Env.get("SMTP_NOREPLY", Env.get("SMTP_ADMIN", ""))

    if data is None:
        data = {}

    data.setdefault("project", title)
    data.setdefault("reply_to", reply_to)

    if user:
        data.setdefault("username", user.email)
        data.setdefault("name", user.name)
        data.setdefault("surname", user.surname)

    html_body, plain_body = get_html_template(template, data)

    if not html_body:  # pragma: no cover
        log.error("Can't load {}", template)
        return False

    subject = f"{title}: {subject}"

    if send_async:
        Mail.send_async(
            subject=subject,
            body=html_body,
            to_address=to_address,
            plain_body=plain_body,
        )
        return False

    smtp_client = smtp.get_instance()
    return smtp_client.send(
        subject=subject,
        body=html_body,
        to_address=to_address,
        plain_body=plain_body,
    )
Exemple #7
0
def mywait() -> None:
    """
    Wait for a service on his host:port configuration
    This check is merely based on a socket connection
    """
    for name, variables in Connector.services.items():

        if name == "smtp" or name == "ftp":
            log.info("Service {} is enabled but not tested at startup time",
                     name)
            continue

        if name == "celery":

            broker = variables.get("broker_service", "N/A")

            if broker == "RABBIT":
                service_vars = Env.load_variables_group(prefix="rabbitmq")
            elif broker == "REDIS":
                service_vars = Env.load_variables_group(prefix="redis")
            else:
                print_and_exit("Invalid celery broker: {}",
                               broker)  # pragma: no cover

            label = f"{broker.lower()} as celery broker"
            host, port = get_service_address(service_vars, "host", "port",
                                             label)

            wait_socket(host, port, label)

            backend = variables.get("backend_service", "N/a")
            if backend == "RABBIT":
                service_vars = Env.load_variables_group(prefix="rabbitmq")
            elif backend == "REDIS":
                service_vars = Env.load_variables_group(prefix="redis")
            else:
                print_and_exit("Invalid celery backend: {}",
                               backend)  # pragma: no cover

            label = f"{backend.lower()} as celery backend"
            host, port = get_service_address(service_vars, "host", "port",
                                             label)

            wait_socket(host, port, label)

        else:
            host, port = get_service_address(variables, "host", "port", name)

            if host != "nohost":
                wait_socket(host, port, name)
Exemple #8
0
def get_frontend_url() -> str:

    FRONTEND_URL = Env.get("FRONTEND_URL", "")

    if FRONTEND_URL:
        return FRONTEND_URL

    FRONTEND_PREFIX = Env.get("FRONTEND_PREFIX", "").strip("/")
    if FRONTEND_PREFIX:
        FRONTEND_PREFIX = f"/{FRONTEND_PREFIX}"

    protocol = "https" if PRODUCTION else "http"

    return f"{protocol}://{DOMAIN}{FRONTEND_PREFIX}"
Exemple #9
0
def mywait():
    """
    Wait for a service on his host:port configuration
    basing the check on a socket connection.
    """
    for name, variables in Connector.services.items():

        if name == "smtp":
            continue

        if name == "celery":

            broker = variables.get("broker", "N/A")

            if broker == "RABBIT":
                service_vars = Env.load_variables_group(prefix="rabbitmq")
            elif broker == "REDIS":
                service_vars = Env.load_variables_group(prefix="redis")
            else:
                print_and_exit("Invalid celery broker: {}", broker)  # pragma: no cover

            label = f"{broker.lower()} as celery broker"
            host, port = get_service_address(service_vars, "host", "port", label)

            wait_socket(host, port, label)

            backend = variables.get("backend", "N/a")
            # Rabbit is no longer used as backend due to the strong limitations
            if backend == "RABBIT":  # pragma: no cover
                service_vars = Env.load_variables_group(prefix="rabbitmq")
            elif backend == "REDIS":
                service_vars = Env.load_variables_group(prefix="redis")
            elif backend == "MONGODB":
                service_vars = Env.load_variables_group(prefix="mongo")
            else:
                print_and_exit(
                    "Invalid celery backend: {}", backend
                )  # pragma: no cover

            label = f"{backend.lower()} as celery backend"
            host, port = get_service_address(service_vars, "host", "port", label)

            wait_socket(host, port, label)

        else:
            host, port = get_service_address(variables, "host", "port", name)

            wait_socket(host, port, name)
        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)
Exemple #11
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
Exemple #12
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
Exemple #13
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)
Exemple #14
0
    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
Exemple #15
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
Exemple #16
0
    def api(
        path: str,
        method: str,
        base: str = "api",
        payload: Optional[Dict[str, Any]] = None,
    ) -> Any:
        host = BotApiClient.variables.get("backend_host")
        port = Env.get("FLASK_PORT", "8080")
        url = f"http://{host}:{port}/{base}/{path}"

        log.debug("Calling {} on {}", method, url)

        try:
            data: Optional[str] = None
            if payload:
                data = orjson.dumps(payload).decode("UTF8")

            response = requests.request(method, url=url, data=data, timeout=10)

            out = response.json()
        # Never raised during tests: how to test it?
        except Exception as e:  # pragma: no cover
            log.error(f"API call failed: {e}")
            raise ServerError(str(e))

        if response.status_code >= 300:
            raise RestApiException(out, status_code=response.status_code)

        return out
Exemple #17
0
def get_backend_url() -> str:

    BACKEND_URL = Env.get("BACKEND_URL", "")

    if BACKEND_URL:
        return BACKEND_URL

    BACKEND_PREFIX = Env.get("BACKEND_PREFIX", "").strip("/")
    if BACKEND_PREFIX:
        BACKEND_PREFIX = f"/{BACKEND_PREFIX}"

    if PRODUCTION:
        return f"https://{DOMAIN}{BACKEND_PREFIX}"

    port = Env.get("FLASK_PORT", "8080")
    return f"http://{DOMAIN}{BACKEND_PREFIX}:{port}"
Exemple #18
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)
Exemple #19
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
Exemple #20
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
Exemple #21
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
Exemple #22
0
    def get_redis_url(variables: Dict[str, str], protocol: str) -> str:
        host = variables.get("host")
        port = Env.to_int(variables.get("port"))
        pwd = variables.get("password", "")
        creds = ""
        if pwd:
            creds = f":{pwd}@"

        return f"{protocol}://{creds}{host}:{port}/0"
Exemple #23
0
    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
Exemple #24
0
def send_activation_link(user: User, url: str) -> bool:

    return send_notification(
        subject=Env.get("EMAIL_ACTIVATION_SUBJECT", "Account activation"),
        template="activate_account.html",
        to_address=user.email,
        data={"url": url},
        user=user,
    )
Exemple #25
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
Exemple #26
0
    def get_mongodb_url(variables: Dict[str, str], protocol: str) -> str:
        host = variables.get("host")
        port = Env.to_int(variables.get("port"))
        user = variables.get("user", "")
        pwd = variables.get("password", "")

        creds = ""
        if user and pwd:
            creds = f"{user}:{pwd}@"

        return f"{protocol}://{creds}{host}:{port}"
Exemple #27
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
Exemple #28
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
Exemple #29
0
    def get_bindings(self, exchange: str) -> Optional[List[Dict[str, str]]]:
        if not self.exchange_exists(exchange):
            log.critical("Does not exist")
            return None

        host = self.variables.get("host", "")
        schema = ""
        if not host.startswith("http"):
            if Env.to_bool(self.variables.get("ssl_enabled")):
                schema = "https://"
            else:
                schema = "http://"

        port = self.variables.get("management_port")
        # url-encode unsafe characters by also including / (thanks to safe parameter)
        # / -> %2F
        vhost = urllib.parse.quote(self.variables.get("vhost", "/"), safe="")
        user = self.variables.get("user")
        password = self.variables.get("password")
        # API Reference:
        # A list of all bindings in which a given exchange is the source.
        r = requests.get(
            f"{schema}{host}:{port}/api/exchanges/{vhost}/{exchange}/bindings/source",
            auth=HTTPBasicAuth(user, password),
            verify=False,
        )
        response = r.json()
        if r.status_code != 200:  # pragma: no cover
            raise RestApiException(
                {"RabbitMQ": response.get("error", "Unknown error")},
                status_code=r.status_code,
            )

        bindings = []
        for row in response:
            # row == {
            #   'source': exchange-name,
            #   'vhost': probably '/',
            #   'destination': queue-or-dest-exchange-name,
            #   'destination_type': 'queue' or 'exchange',
            #   'routing_key': routing_key,
            #   'arguments': Dict,
            #   'properties_key': ?? as routing_key?
            # }

            bindings.append({
                "exchange": row["source"],
                "routing_key": row["routing_key"],
                "queue": row["destination"],
            })

        return bindings
Exemple #30
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")