Exemple #1
0
    def set_password(self, password: str) -> None:
        """
            Hash and set the given password.

            :param password: The plaintext password.
        """

        if not password:
            return

        # If the password stayed the same do not do anything; especially, do not send an email.
        if self.check_password(password):
            return

        # If the user does not have a password at the moment, their account has been newly created. Do not send an email
        # in this case.
        if self.password_hash is not None:
            application = get_app()

            support_address = application.config.get('SUPPORT_ADDRESS', None)

            email = Email(_('Your Password Has Been Changed'), 'userprofile/emails/reset_password_confirmation')
            email.prepare(name=self.name, support_email=support_address)
            email.send(self.get_email())

        self.password_hash = bcrypt.generate_password_hash(password)
Exemple #2
0
    def test_prepare(self, mock_renderer: MagicMock):
        """
            Test preparing the email body.

            Expected result: The previously set body is rendered with the given arguments.
        """
        def _renderer_side_effect(template, **_kwargs):
            """
                Return the template path for testing.
            """
            return template

        mock_renderer.side_effect = _renderer_side_effect

        subject = 'Test Subject'
        body_path = 'email/test'
        sender = '*****@*****.**'
        email = Email(subject, body_path, sender)

        title = 'Test Title'
        body_path_plain = body_path + '.txt'
        body_path_html = body_path + '.html'
        email.prepare(title=title)

        self.assertEqual(body_path_plain, email._body_plain)
        self.assertEqual(body_path_html, email._body_html)
        self.assertEqual(2, mock_renderer.call_count)
        self.assertTupleEqual(call(body_path_plain, title=title),
                              mock_renderer.call_args_list[0])
        self.assertTupleEqual(call(body_path_html, title=title),
                              mock_renderer.call_args_list[1])
Exemple #3
0
    def set_email(self, email: str) -> bool:
        """
            Change the user's email address to the new given one.

            :param email: The user's new email address. Must not be used by a different user.
            :return: ``False`` if the email address already is in use by another user, ``True`` otherwise.
        """

        old_email = self.get_email()
        if old_email == email:
            return True

        user = User.load_from_email(email)
        if user is not None and user != self:
            return False

        self._email = email
        if not old_email:
            # If there is no old email the user has just been created. Do not send an email in this case.
            return True

        application = get_app()
        support_address = application.config.get('SUPPORT_ADDRESS', None)

        email_obj = Email(_('Your Email Address Has Been Changed'),
                          'userprofile/emails/change_email_address_confirmation')
        email_obj.prepare(name=self.name, new_email=email, support_email=support_address)
        email_obj.send(old_email)

        return True
Exemple #4
0
    def test_prepare(self, mock_renderer: MagicMock):
        """
            Test preparing the email body.

            Expected result: The previously set body is rendered with the given arguments.
        """

        def _renderer_side_effect(template, **_kwargs):
            """
                Return the template path for testing.
            """
            return template

        mock_renderer.side_effect = _renderer_side_effect

        subject = 'Test Subject'
        body_path = 'email/test'
        sender = '*****@*****.**'
        email = Email(subject, body_path, sender)

        title = 'Test Title'
        body_path_plain = body_path + '.txt'
        body_path_html = body_path + '.html'
        email.prepare(title=title)

        self.assertEqual(body_path_plain, email._body_plain)
        self.assertEqual(body_path_html, email._body_html)
        self.assertEqual(2, mock_renderer.call_count)
        self.assertTupleEqual(call(body_path_plain, title=title), mock_renderer.call_args_list[0])
        self.assertTupleEqual(call(body_path_html, title=title), mock_renderer.call_args_list[1])
Exemple #5
0
    def test_send_success_multiple_recipients(self):
        """
            Test sending an email to multiple recipients.

            Expected result: The email is sent successfully.
        """

        subject = 'Test Subject'
        body_path = 'email/test'
        sender = '*****@*****.**'
        recipients = ['*****@*****.**', '*****@*****.**']
        body_plain = 'Plain Body'
        body_html = 'HTML Body'

        email = Email(subject, body_path, sender)
        email._body_plain = body_plain
        email._body_html = body_html

        with mail.record_messages() as outgoing:
            email.send(recipients)

            self.assertEqual(1, len(outgoing))
            self.assertIn(subject, outgoing[0].subject)
            self.assertEqual(sender, outgoing[0].sender)
            self.assertListEqual(recipients, outgoing[0].recipients)
            self.assertEqual(body_plain, outgoing[0].body)
            self.assertEqual(body_html, outgoing[0].html)
Exemple #6
0
    def test_send_failure(self):
        """
            Test sending an email to a recipient given in a wrong type.

            Expected result: The email is not sent.
        """

        subject = 'Test Subject'
        body_path = 'email/test'
        sender = '*****@*****.**'
        recipients = None
        body_plain = 'Plain Body'
        body_html = 'HTML Body'

        email = Email(subject, body_path, sender)
        email._body_plain = body_plain
        email._body_html = body_html

        with self.assertRaises(TypeError) as message:
            with mail.record_messages() as outgoing:
                # noinspection PyTypeChecker
                email.send(recipients)

                self.assertEqual(0, len(outgoing))
                self.assertEqual('Argument "recipients" must be a string or a list of strings.', message)
Exemple #7
0
    def test_prepare_and_send(self):
        """
            Test preparing and sending a mail with a single call.

            Expected result: The two respective methods are called.
        """

        subject = 'Test Subject'
        body_path = 'email/test'
        sender = '*****@*****.**'
        email = Email(subject, body_path, sender)

        email.prepare = Mock()
        email.send = Mock()

        title = 'Test Title'
        recipient = '*****@*****.**'
        email.prepare_and_send(recipient, title=title)

        # noinspection PyUnresolvedReferences
        self.assertEqual(1, email.prepare.call_count)

        # noinspection PyUnresolvedReferences
        self.assertTupleEqual(call(title=title), email.prepare.call_args)

        # noinspection PyUnresolvedReferences
        self.assertEqual(1, email.send.call_count)

        # noinspection PyUnresolvedReferences
        self.assertTupleEqual(call(recipient), email.send.call_args)
Exemple #8
0
 def setUp(self):
     db.create_all()
     user1 = User("Elie", "Schoppik", "image")
     user2 = User("Tim", "Garcia", "image")
     user3 = User("Matt", "Lane", "image")
     db.session.add_all([user1, user2, user3])
     message1 = Message("Hello Elie!!", 1)
     message2 = Message("Goodbye Elie!!", 1)
     message3 = Message("Hello Tim!!", 2)
     message4 = Message("Goodbye Tim!!", 2)
     db.session.add_all([message1, message2, message3, message4])
     email1 = Email("*****@*****.**", 1)
     email2 = Email("*****@*****.**", 2)
     db.session.add_all([email1, email2])
     db.session.commit()
Exemple #9
0
class AccountForm(FlaskForm):
    NAME = StringField(u'Name',
                       validators=[InputRequired(),
                                   Length(min=3, max=30)])
    SURNAME = StringField(u'Surname',
                          validators=[InputRequired(),
                                      Length(min=1, max=45)])
    CITY = StringField(u'City',
                       validators=[InputRequired(),
                                   Length(min=1, max=45)])
    ZIP_CODE = StringField(u'Zip-code',
                           validators=[InputRequired(),
                                       Length(min=1, max=7)])
    STREET = StringField(u'Street',
                         validators=[InputRequired(),
                                     Length(min=1, max=45)])
    STREET_NUMBER = StringField(
        u'Street number', validators=[InputRequired(),
                                      Length(min=1, max=5)])
    PHONE_NUMBER = StringField(
        u'Phone number 1', validators=[InputRequired(),
                                       Length(min=6, max=20)])
    PHONE_NUMBER2 = StringField(u'Phone number 2 (optional)',
                                validators=[Length(min=6, max=20)])
    E_MAIL = StringField(u'Email Address',
                         validators=[
                             InputRequired(),
                             Length(min=5, max=40),
                             Email(message=u'Invalid email address.')
                         ])
Exemple #10
0
    def send_delete_account_email(self) -> DeleteAccountToken:
        """
            Send a token to the user to confirm their intention to delete their account.

            :return: The token sent in the mail.
        """
        token_obj = DeleteAccountToken()
        token_obj.user_id = self.id

        token = token_obj.create()
        validity = token_obj.get_validity(in_minutes=True)

        link = url_for('userprofile.delete_profile', token=token, _external=True)

        email = Email(_('Delete Your User Profile'), 'userprofile/emails/delete_account_request')
        email.prepare(name=self.name, link=link, validity=validity)
        email.send(self.get_email())

        return token_obj
Exemple #11
0
    def delete(self) -> None:
        """
            Delete the user's account. Log them out first if necessary. Notify them via mail.

            This action will directly be committed to the database.
        """
        if self == current_user:
            self.logout()

        # Notify the user via email.
        application = get_app()
        support_address = application.config.get('SUPPORT_ADDRESS', None)

        email = Email(_('Your User Profile Has Been Deleted'), 'userprofile/emails/delete_account_confirmation')
        email.prepare(name=self.name, new_email=self.get_email(), support_email=support_address)
        email.send(self.get_email())

        db.session.delete(self)
        db.session.commit()
Exemple #12
0
    def test_send_success_multiple_recipients(self):
        """
            Test sending an email to multiple recipients.

            Expected result: The email is sent successfully.
        """

        subject = 'Test Subject'
        body_path = 'email/test'
        sender = '*****@*****.**'
        recipients = ['*****@*****.**', '*****@*****.**']
        body_plain = 'Plain Body'
        body_html = 'HTML Body'

        email = Email(subject, body_path, sender)
        email._body_plain = body_plain
        email._body_html = body_html

        with mail.record_messages() as outgoing:
            email.send(recipients)

            self.assertEqual(1, len(outgoing))
            self.assertIn(subject, outgoing[0].subject)
            self.assertEqual(sender, outgoing[0].sender)
            self.assertListEqual(recipients, outgoing[0].recipients)
            self.assertEqual(body_plain, outgoing[0].body)
            self.assertEqual(body_html, outgoing[0].html)
Exemple #13
0
    def request_account_deletion(self) -> Optional[DeleteAccountToken]:
        """
            Send a token via email to the user to confirm their intention to delete their account.

            :return: The token sent in the mail. `None` if the user has no email.
        """

        token_obj = DeleteAccountToken()
        token_obj.user_id = self.id

        token = token_obj.create()
        validity = timedelta_to_minutes(token_obj.get_validity())

        link = url_for('userprofile.delete_profile',
                       token=token,
                       _external=True)

        if self.email is None:
            return None

        email = Email(_('Delete Your User Profile'),
                      'userprofile/emails/delete_account_request')
        email.prepare(name=self.name, link=link, validity=validity)
        email.send(self.email)

        return token_obj
Exemple #14
0
    def request_password_reset(self) -> Optional[ResetPasswordToken]:
        """
            Send a mail for resetting the user's password to their email address.

            :return: The token send in the mail.
        """

        if self.email is None:
            return None

        token_obj = ResetPasswordToken()
        token_obj.user_id = self.id
        token = token_obj.create()

        validity = timedelta_to_minutes(token_obj.get_validity())

        link = url_for('userprofile.reset_password',
                       token=token,
                       _external=True)

        email = Email(_('Reset Your Password'),
                      'userprofile/emails/reset_password_request')
        email.prepare(name=self.name, link=link, validity=validity)
        email.send(self.email)

        return token_obj
Exemple #15
0
    def set_password(self, password: str) -> None:
        """
            Hash and set the given password.

            :param password: The plaintext password.
        """

        if not password:
            return

        # If the password stayed the same do not do anything; especially, do not send an email.
        if self.check_password(password):
            return

        # If the user does not have a password at the moment, their account has been newly created. Do not send an email
        # in this case.
        if self._password_hash is not None and self.email is not None:
            application = get_app()

            support_address = application.config.get('SUPPORT_ADDRESS', None)

            email = Email(_('Your Password Has Been Changed'),
                          'userprofile/emails/reset_password_confirmation')
            email.prepare(name=self.name, support_email=support_address)
            email.send(self.email)

        self._password_hash = bcrypt.generate_password_hash(password)
Exemple #16
0
    def test_send_failure(self):
        """
            Test sending an email to a recipient given in a wrong type.

            Expected result: The email is not sent.
        """

        subject = 'Test Subject'
        body_path = 'email/test'
        sender = '*****@*****.**'
        recipients = None
        body_plain = 'Plain Body'
        body_html = 'HTML Body'

        email = Email(subject, body_path, sender)
        email._body_plain = body_plain
        email._body_html = body_html

        with self.assertRaises(TypeError) as message:
            with mail.record_messages() as outgoing:
                # noinspection PyTypeChecker
                email.send(recipients)

                self.assertEqual(0, len(outgoing))
                self.assertEqual(
                    'Argument "recipients" must be a string or a list of strings.',
                    message)
Exemple #17
0
    def _set_email(self, email: str) -> bool:
        """
            Change the user's email address to the new given one.

            An email will be sent to the user's old email address informing them about this change.

            :param email: The user's new email address. Must not be used by a different user.
            :return: `False` if the email address is already in use by another user, `True` otherwise.
        """

        old_email = self.email
        if old_email == email:
            return True

        user = User.load_from_email(email)
        if user is not None and user != self:
            return False

        self._email = email
        if not old_email:
            # If there is no old email the user has just been created. Do not send an email in this case.
            return True

        application = get_app()
        support_address = application.config.get('SUPPORT_ADDRESS', None)

        email_obj = Email(
            _('Your Email Address Has Been Changed'),
            'userprofile/emails/change_email_address_confirmation')
        email_obj.prepare(name=self.name,
                          new_email=email,
                          support_email=support_address)
        email_obj.send(old_email)

        return True
Exemple #18
0
    def test_prepare_and_send(self):
        """
            Test preparing and sending a mail with a single call.

            Expected result: The two respective methods are called.
        """

        subject = 'Test Subject'
        body_path = 'email/test'
        sender = '*****@*****.**'
        email = Email(subject, body_path, sender)

        email.prepare = Mock()
        email.send = Mock()

        title = 'Test Title'
        recipient = '*****@*****.**'
        email.prepare_and_send(recipient, title=title)

        # noinspection PyUnresolvedReferences
        self.assertEqual(1, email.prepare.call_count)

        # noinspection PyUnresolvedReferences
        self.assertTupleEqual(call(title=title), email.prepare.call_args)

        # noinspection PyUnresolvedReferences
        self.assertEqual(1, email.send.call_count)

        # noinspection PyUnresolvedReferences
        self.assertTupleEqual(call(recipient), email.send.call_args)
Exemple #19
0
    def send_password_reset_email(self) -> Optional[ResetPasswordToken]:
        """
            Send a mail for resetting the user's password to their email address.

            :return: The token send in the mail
        """

        if self.get_email() is None:
            return None

        token_obj = ResetPasswordToken()
        token_obj.user_id = self.id
        token = token_obj.create()

        validity = token_obj.get_validity(in_minutes=True)

        link = url_for('userprofile.reset_password', token=token, _external=True)

        email = Email(_('Reset Your Password'), 'userprofile/emails/reset_password_request')
        email.prepare(name=self.name, link=link, validity=validity)
        email.send(self.get_email())

        return token_obj
Exemple #20
0
    def send_change_email_address_email(self, email: str) -> ChangeEmailAddressToken:
        """
            Send a token to the user to change their email address.

            :param email: The email address to which the token will be sent and to which the user's email will be
                          changed upon verification.
            :return: The token send in the mail.
        """

        token_obj = ChangeEmailAddressToken()
        token_obj.user_id = self.id
        token_obj.new_email = email

        token = token_obj.create()
        validity = token_obj.get_validity(in_minutes=True)

        link = url_for('userprofile.change_email', token=token, _external=True)
        email_old = self.get_email()

        email_obj = Email(_('Change Your Email Address'), 'userprofile/emails/change_email_address_request')
        email_obj.prepare(name=self.name, link=link, validity=validity, email_old=email_old, email_new=email)
        email_obj.send(email)

        return token_obj
Exemple #21
0
    def test_init_with_sender(self):
        """
            Test initializing an email object with a given sender.

            Expected result: The object is correctly initialized.
        """
        subject = 'Test Subject'
        body_path = 'email/test'
        sender = '*****@*****.**'
        email = Email(subject, body_path, sender)

        subject_prefix = self.app.config['TITLE_SHORT']

        self.assertEqual(f'{subject_prefix} » {subject}', email._subject)
        self.assertEqual(body_path, email._body_template_base_path)
        self.assertIsNone(email._body_plain)
        self.assertIsNone(email._body_html)
        self.assertEqual(sender, email._sender)
Exemple #22
0
    def test_init_without_sender_and_configuration(self):
        """
            Test initializing an email object without a sender if the sender is not specified in the configuration.

            Expected result: The object is not initialized and raises an error.
        """
        mail_from = self.app.config['MAIL_FROM']
        self.app.config['MAIL_FROM'] = None

        subject = 'Test Subject'
        body_path = 'email/test'

        with self.assertRaises(NoMailSenderError) as exception_cm:
            email = Email(subject, body_path)

            self.assertIsNone(email)
            self.assertIn(
                'No sender given and none configured in the app configuration.',
                str(exception_cm.exception))

        self.app.config['MAIL_FROM'] = mail_from
Exemple #23
0
    def test_init_without_sender(self):
        """
            Test initializing an email object without a given sender.

            Expected result: The object is correctly initialized; the sender is taken from the app configuration.
        """
        mail_from = self.app.config['MAIL_FROM']
        self.app.config['MAIL_FROM'] = '*****@*****.**'

        subject = 'Test Subject'
        body_path = 'email/test'
        email = Email(subject, body_path)

        subject_prefix = self.app.config['TITLE_SHORT']

        self.assertEqual(f'{subject_prefix} » {subject}', email._subject)
        self.assertEqual(body_path, email._body_template_base_path)
        self.assertIsNone(email._body_plain)
        self.assertIsNone(email._body_html)
        self.assertEqual(self.app.config['MAIL_FROM'], email._sender)

        self.app.config['MAIL_FROM'] = mail_from
Exemple #24
0
    def _delete(self) -> None:
        """
            Delete the user's account. Log them out first if necessary. Notify them via mail.

            This action will directly be committed to the database.
        """

        if self == current_user:
            self.logout()

        # Notify the user via email.
        application = get_app()
        support_address = application.config.get('SUPPORT_ADDRESS', None)

        if self.email is not None:
            email = Email(_('Your User Profile Has Been Deleted'),
                          'userprofile/emails/delete_account_confirmation')
            email.prepare(name=self.name,
                          new_email=self.email,
                          support_email=support_address)
            email.send(self.email)

        db.session.delete(self)
        db.session.commit()
Exemple #25
0
    def request_email_address_change(self,
                                     new_email_address: str) -> timedelta:
        """
            Request to change the user's email address to the given new email address.

            This method will only create a JWT and send it in an email to the user's new email address. The user will
            then have to verify this token within the token's validity to actually change the email address to the new
            one. Until this verification has taken place, the email address will not have been changed.

            To verify the token and actually set the new email address, execute :meth:`set_email_from_token`.

            :param new_email_address: The email address to which the token will be sent and to which the user's email
                                      will be changed upon verification.
            :return: The validity of the token.
        """

        token_obj = ChangeEmailAddressToken()
        token_obj.user_id = self.id
        token_obj.new_email = new_email_address

        token = token_obj.create()
        validity: timedelta = token_obj.get_validity()

        link = url_for('userprofile.change_email', token=token, _external=True)
        email_old = self.email

        email_obj = Email(_('Change Your Email Address'),
                          'userprofile/emails/change_email_address_request')
        email_obj.prepare(name=self.name,
                          link=link,
                          validity=timedelta_to_minutes(validity),
                          email_old=email_old,
                          email_new=new_email_address)
        email_obj.send(new_email_address)

        return validity