Beispiel #1
0
def test_no_smtp() -> None:  # pragma: no cover

    with pytest.raises(ServiceUnavailable):
        connector.get_instance()

    log.warning("Skipping {} tests: service not available", CONNECTOR)
    return None
Beispiel #2
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,
    )
Beispiel #3
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")
Beispiel #4
0
    def post(self, username: str) -> Response:

        self.auth.verify_blocked_username(username)

        user = self.auth.get_user(username=username)

        # if user is None this endpoint does nothing but the response
        # remain the same to prevent any user guessing
        if user is not None:
            smtp_client = smtp.get_instance()
            send_activation_link(smtp_client, self.auth, user)
        msg = ("We are sending an email to your email address where "
               "you will find the link to activate your account")
        return self.response(msg)
Beispiel #5
0
    def post(
        self,
        user: User,
        subject: str,
        body: str,
        to: str,
        cc: Optional[List[str]] = None,
        bcc: Optional[List[str]] = None,
        dry_run: bool = False,
    ) -> Response:

        replaces: Dict[str, Any] = {}

        header_html = _get_html_template("email_header.html", replaces)
        footer_html = _get_html_template("email_footer.html", replaces)

        body = body.replace("\n", "<br/>")

        html_body = f"{header_html}{body}{footer_html}"
        plain_body = convert_html2text(html_body)

        if dry_run:
            return self.response(
                {
                    "html_body": html_body,
                    "plain_body": plain_body,
                    "subject": subject,
                    "to": to,
                    "cc": cc,
                    "bcc": bcc,
                }
            )

        smtp_client = smtp.get_instance()
        smtp_client.send(
            body=html_body,
            subject=subject,
            to_address=to,
            from_address=None,
            cc=cc,
            bcc=bcc,
            plain_body=plain_body,
        )
        return self.empty_response()
Beispiel #6
0
        def post(self, reset_email: str) -> Response:

            reset_email = reset_email.lower()

            self.auth.verify_blocked_username(reset_email)

            user = self.auth.get_user(username=reset_email)

            if user is None:
                raise Forbidden(
                    f"Sorry, {reset_email} is not recognized as a valid username",
                )

            self.auth.verify_user_status(user)

            title = get_project_configuration("project.title",
                                              default="Unkown title")

            reset_token, payload = self.auth.create_temporary_token(
                user, self.auth.PWD_RESET)

            server_url = get_frontend_url()

            rt = reset_token.replace(".", "+")

            uri = Env.get("RESET_PASSWORD_URI", "/public/reset")
            complete_uri = f"{server_url}{uri}/{rt}"

            smtp_client = smtp.get_instance()
            send_password_reset_link(smtp_client, complete_uri, title,
                                     reset_email)

            ##################
            # Completing the reset task
            self.auth.save_token(user,
                                 reset_token,
                                 payload,
                                 token_type=self.auth.PWD_RESET)

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

            self.log_event(self.events.reset_password_request, user=user)
            return self.response(msg)
Beispiel #7
0
    def post(self, **kwargs: Any) -> Response:

        roles: List[str] = kwargs.pop("roles", [])
        payload = kwargs.copy()
        group_id = kwargs.pop("group")

        email_notification = kwargs.pop("email_notification", False)

        unhashed_password = kwargs["password"]

        # If created by admins users must accept privacy at first login
        kwargs["privacy_accepted"] = False

        try:
            user = self.auth.create_user(kwargs, roles)
            self.auth.save_user(user)
        except DatabaseDuplicatedEntry as e:
            if Connector.authentication_service == "sqlalchemy":
                self.auth.db.session.rollback()
            raise Conflict(str(e))

        group = self.auth.get_group(group_id=group_id)
        if not group:
            # Can't be reached because grup_id is prefiltered by marshmallow
            raise NotFound("This group cannot be found")  # pragma: no cover

        self.auth.add_user_to_group(user, group)

        if email_notification and unhashed_password is not None:
            smtp_client = smtp.get_instance()
            send_notification(smtp_client,
                              user,
                              unhashed_password,
                              is_update=False)

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

        return self.response(user.uuid)
Beispiel #8
0
    def wrapper(self, *args, **kwargs):

        try:
            return func(self, *args, **kwargs)

        except BaseException:

            task_id = self.request.id
            task_name = self.request.task

            log.error("Celery task {} failed ({})", task_id, task_name)
            arguments = str(self.request.args)
            log.error("Failed task arguments: {}", arguments[0:256])
            log.error("Task error: {}", traceback.format_exc())

            if Connector.check_availability("smtp"):
                log.info("Sending error report by email", task_id, task_name)

                body = f"""
Celery task {task_id} failed

Name: {task_name}

Arguments: {self.request.args}

Error: {traceback.format_exc()}
"""

                project = get_project_configuration(
                    "project.title",
                    default="Unkown title",
                )
                subject = f"{project}: task {task_name} failed"
                from restapi.connectors import smtp

                smtp_client = smtp.get_instance()
                smtp_client.send(body, subject)
Beispiel #9
0
def test_smtp(app: Flask, faker: Faker) -> None:

    obj = connector.get_instance()
    assert obj is not None
    assert obj.smtp is not None

    obj = connector.get_instance(port="465")
    assert obj is not None
    assert obj.smtp is not None

    obj = connector.get_instance(port="587")
    assert obj is not None
    assert obj.smtp is not None

    assert obj.send("body", "subject")
    assert obj.send("body", "subject", "to_addr")
    assert obj.send("body", "subject", "to_addr", "from_addr")

    obj = connector.get_instance()

    mail = BaseTests.read_mock_email()
    body = mail.get("body")
    headers = mail.get("headers")
    assert body is not None
    assert headers is not None
    # Subject: is a key in the MIMEText
    assert "Subject: subject" in headers
    assert mail.get("from") == "from_addr"
    assert mail.get("cc") == ["to_addr"]
    assert mail.get("bcc") is None

    assert obj.send("body", "subject", "to_addr", "from_addr", cc="test1", bcc="test2")

    mail = BaseTests.read_mock_email()
    body = mail.get("body")
    headers = mail.get("headers")
    assert body is not None
    assert headers is not None
    # Subject: is a key in the MIMEText
    assert "Subject: subject" in headers
    assert mail.get("from") == "from_addr"
    # format is [to, [cc...], [bcc...]]
    assert mail.get("cc") == ["to_addr", ["test1"], ["test2"]]

    assert obj.send(
        "body",
        "subject",
        "to_addr",
        "from_addr",
        cc=["test1", "test2"],
        bcc=["test3", "test4"],
    )

    mail = BaseTests.read_mock_email()
    body = mail.get("body")
    headers = mail.get("headers")
    assert body is not None
    assert headers is not None
    # Subject: is a key in the MIMEText
    assert "Subject: subject" in headers
    assert mail.get("from") == "from_addr"
    # format is [to, [cc...], [bcc...]]
    assert mail.get("cc") == ["to_addr", ["test1", "test2"], ["test3", "test4"]]

    # This is a special from_address, used to raise SMTPException
    assert not obj.send("body", "subject", "to_addr", "invalid1")
    # This is a special from_address, used to raise Exception
    obj = connector.get_instance()
    assert not obj.send("body", "subject", "to_addr", "invalid2")
    # This is NOT a special from_address
    obj = connector.get_instance()
    assert obj.send("body", "subject", "to_addr", "invalid3")

    # Test that cc and bcc with wrong types are ignored
    assert obj.send(
        "body",
        "subject",
        "to_addr",
        "from_addr",
        cc=10,  # type: ignore
        bcc=20,  # type: ignore
    )

    mail = BaseTests.read_mock_email()
    body = mail.get("body")
    headers = mail.get("headers")
    assert body is not None
    assert headers is not None
    # Subject: is a key in the MIMEText
    assert "Subject: subject" in headers
    # cc and bcc with wrong type (int in this case!) are ignored
    assert mail.get("from") == "from_addr"
    # format is [to, [cc...], [bcc...]]
    assert mail.get("cc") == ["to_addr"]

    with connector.get_instance() as obj:
        assert obj is not None
        assert obj.smtp is not None
    # assert obj.smtp is None

    with connector.get_instance(noreply="", admin="") as obj:
        assert not obj.send("body", "subject")
        assert not obj.send("body", "subject", "to_addr")
        assert obj.send("body", "subject", "to_addr", "from_addr")

    obj = connector.get_instance()
    assert obj.is_connected()
    obj.disconnect()

    # a second disconnect should not raise any error
    obj.disconnect()

    assert not obj.is_connected()
Beispiel #10
0
    def put(self, user_id: str, **kwargs: Any) -> Response:

        user = self.auth.get_user(user_id=user_id)

        if user is None:
            raise NotFound(
                "This user cannot be found or you are not authorized")

        if "password" in kwargs:
            unhashed_password = kwargs["password"]
            kwargs["password"] = BaseAuthentication.get_password_hash(
                kwargs["password"])
        else:
            unhashed_password = None

        payload = kwargs.copy()
        roles: List[str] = kwargs.pop("roles", [])

        group_id = kwargs.pop("group", None)

        email_notification = kwargs.pop("email_notification", False)

        self.auth.link_roles(user, roles)

        userdata, extra_userdata = self.auth.custom_user_properties_pre(kwargs)

        prev_expiration = user.expiration

        self.auth.db.update_properties(user, userdata)

        self.auth.custom_user_properties_post(user, userdata, extra_userdata,
                                              self.auth.db)

        self.auth.save_user(user)

        if group_id is not None:
            group = self.auth.get_group(group_id=group_id)
            if not group:
                # Can't be reached because grup_id is prefiltered by marshmallow
                raise NotFound(
                    "This group cannot be found")  # pragma: no cover

            self.auth.add_user_to_group(user, group)

        if email_notification and unhashed_password is not None:
            smtp_client = smtp.get_instance()
            send_notification(smtp_client,
                              user,
                              unhashed_password,
                              is_update=True)

        if user.expiration:
            # Set expiration on a previously non-expiring account
            # or update the expiration by reducing the validity period
            # In both cases tokens should be invalited to prevent to have tokens
            # with TTL > account validity

            # dt_lower (alias for date_lower_than) is a comparison fn that ignores tz
            if prev_expiration is None or dt_lower(user.expiration,
                                                   prev_expiration):
                for token in self.auth.get_tokens(user=user):
                    # Invalidate all tokens with expiration after the account expiration
                    if dt_lower(user.expiration, token["expiration"]):
                        self.auth.invalidate_token(token=token["token"])

        self.log_event(self.events.modify, user, payload)

        return self.empty_response()
Beispiel #11
0
def test_smtp(app: Flask, faker: Faker) -> None:

    # mailmock is always enabled during core tests
    if not Connector.check_availability(CONNECTOR):  # pragma: no cover

        try:
            obj = connector.get_instance()
            pytest.fail("No exception raised")  # pragma: no cover
        except ServiceUnavailable:
            pass

        log.warning("Skipping {} tests: service not available", CONNECTOR)
        return None

    # try:
    #     connector.get_instance(host="invalidhostname", port=123)
    #     pytest.fail("No exception raised on unavailable service")  # pragma: no cover
    # except ServiceUnavailable:
    #     pass

    obj = connector.get_instance()
    assert obj is not None
    assert obj.smtp is not None

    obj = connector.get_instance(port=465)
    assert obj is not None
    assert obj.smtp is not None

    obj = connector.get_instance(port=587)
    assert obj is not None
    assert obj.smtp is not None

    assert obj.send("body", "subject")
    assert obj.send("body", "subject", "to_addr")
    assert obj.send("body", "subject", "to_addr", "from_addr")

    obj = connector.get_instance()

    mail = BaseTests.read_mock_email()
    body = mail.get("body")
    headers = mail.get("headers")
    assert body is not None
    assert headers is not None
    # Subject: is a key in the MIMEText
    assert "Subject: subject" in headers
    assert mail.get("from") == "from_addr"
    assert mail.get("cc") == ["to_addr"]
    assert mail.get("bcc") is None

    assert obj.send("body",
                    "subject",
                    "to_addr",
                    "from_addr",
                    cc="test1",
                    bcc="test2")

    mail = BaseTests.read_mock_email()
    body = mail.get("body")
    headers = mail.get("headers")
    assert body is not None
    assert headers is not None
    # Subject: is a key in the MIMEText
    assert "Subject: subject" in headers
    assert mail.get("from") == "from_addr"
    # format is [to, [cc...], [bcc...]]
    assert mail.get("cc") == ["to_addr", ["test1"], ["test2"]]

    assert obj.send(
        "body",
        "subject",
        "to_addr",
        "from_addr",
        cc=["test1", "test2"],
        bcc=["test3", "test4"],
    )

    mail = BaseTests.read_mock_email()
    body = mail.get("body")
    headers = mail.get("headers")
    assert body is not None
    assert headers is not None
    # Subject: is a key in the MIMEText
    assert "Subject: subject" in headers
    assert mail.get("from") == "from_addr"
    # format is [to, [cc...], [bcc...]]
    assert mail.get("cc") == [
        "to_addr", ["test1", "test2"], ["test3", "test4"]
    ]

    # This is a special from_address, used to raise SMTPException
    assert not obj.send("body", "subject", "to_addr", "invalid1")
    # This is a special from_address, used to raise BaseException
    assert not obj.send("body", "subject", "to_addr", "invalid2")
    # This is NOT a special from_address
    assert obj.send("body", "subject", "to_addr", "invalid3")

    assert obj.send("body", "subject", "to_addr", "from_addr", cc=10, bcc=20)

    mail = BaseTests.read_mock_email()
    body = mail.get("body")
    headers = mail.get("headers")
    assert body is not None
    assert headers is not None
    # Subject: is a key in the MIMEText
    assert "Subject: subject" in headers
    # cc and bcc with wrong type (int in this case!) are ignored
    assert mail.get("from") == "from_addr"
    # format is [to, [cc...], [bcc...]]
    assert mail.get("cc") == ["to_addr"]

    with connector.get_instance() as obj:
        assert obj is not None
        assert obj.smtp is not None
    # assert obj.smtp is None

    with connector.get_instance(noreply=None, admin=None) as obj:
        assert not obj.send("body", "subject")
        assert not obj.send("body", "subject", "to_addr")
        assert obj.send("body", "subject", "to_addr", "from_addr")

    obj = connector.get_instance()
    assert obj.is_connected()
    obj.disconnect()

    # a second disconnect should not raise any error
    obj.disconnect()

    assert not obj.is_connected()