Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customizable send_mail (opr #730) #64

Merged
merged 5 commits into from
May 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions docs/customizing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ decorator like so::
send_security_email.delay(msg)

If factory method is going to be used for initialization, use ``_SecurityState``
object returned by ``init_app`` method to initialize Celery tasks intead of using
object returned by ``init_app`` method to initialize Celery tasks instead of using
``security.send_mail_task`` directly like so::

from flask import Flask
Expand Down Expand Up @@ -194,7 +194,7 @@ object returned by ``init_app`` method to initialize Celery tasks intead of usin
def delay_flask_security_mail(msg):
send_flask_mail.delay(msg)

# A shortcurt.
# A shortcut.
security_ctx.send_mail_task(send_flask_mail.delay)

return app
Expand All @@ -215,6 +215,42 @@ Celery. The practical way with custom serialization may look like so::
.. _Celery: http://www.celeryproject.org/


Custom send_mail method
-----------------------

It's also possible to completely override the ``security.send_mail`` method to
implement your own logic.

For example, you might want to use an alternative email library like `Flask-Emails`::

from flask import Flask
from flask_security import Security, SQLAlchemyUserDatastore
from flask_emails import Message

def create_app(config):
"""Initialize Flask instance."""

app = Flask(__name__)
app.config.from_object(config)

def custom_send_mail(subject, recipient, template, **context):
ctx = ('security/email', template)
message = Message(
subject=subject,
html=_security.render_template('%s/%s.html' % ctx, **context))
message.send(mail_to=[recipient])

datastore = SQLAlchemyUserDatastore(db, User, Role)
Security(app, datastore, send_mail=custom_send_mail)

return app

.. note::

The above ``security.send_mail_task`` override will be useless if you
override the entire ``send_mail`` method.


Authorization with OAuth2
-------------------------

Expand Down
4 changes: 2 additions & 2 deletions flask_security/changeable.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from werkzeug.local import LocalProxy

from .signals import password_changed
from .utils import config_value, hash_password, send_mail
from .utils import config_value, hash_password

# Convenient references
_security = LocalProxy(lambda: current_app.extensions['security'])
Expand All @@ -29,7 +29,7 @@ def send_password_changed_notice(user):
"""
if config_value('SEND_PASSWORD_CHANGE_EMAIL'):
subject = config_value('EMAIL_SUBJECT_PASSWORD_CHANGE_NOTICE')
send_mail(subject, user.email, 'change_notice', user=user)
_security.send_mail(subject, user.email, 'change_notice', user=user)


def change_user_password(user, password):
Expand Down
8 changes: 4 additions & 4 deletions flask_security/confirmable.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from werkzeug.local import LocalProxy

from .signals import confirm_instructions_sent, user_confirmed
from .utils import config_value, get_token_status, hash_data, send_mail, \
from .utils import config_value, get_token_status, hash_data, \
url_for_security, verify_hash

# Convenient references
Expand All @@ -39,9 +39,9 @@ def send_confirmation_instructions(user):

confirmation_link, token = generate_confirmation_link(user)

send_mail(config_value('EMAIL_SUBJECT_CONFIRM'), user.email,
'confirmation_instructions', user=user,
confirmation_link=confirmation_link)
_security.send_mail(config_value('EMAIL_SUBJECT_CONFIRM'), user.email,
'confirmation_instructions', user=user,
confirmation_link=confirmation_link)

confirm_instructions_sent.send(app._get_current_object(), user=user,
token=token)
Expand Down
13 changes: 10 additions & 3 deletions flask_security/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@
ResetPasswordForm, SendConfirmationForm
from .utils import _
from .utils import config_value as cv
from .utils import get_config, hash_data, localize_callback, \
from .utils import get_config, hash_data, localize_callback, send_mail,\
string_types, url_for_security, verify_and_update_password, verify_hash

from .views import create_blueprint

# Convenient references
Expand Down Expand Up @@ -491,6 +492,8 @@ class Security(object):
:param send_confirmation_form: set form for the send confirmation view
:param passwordless_login_form: set form for the passwordless login view
:param anonymous_user: class to use for anonymous user
:param render_template: function to use to render templates
:param send_mail: function to use to send email
"""

def __init__(self, app=None, datastore=None, register_blueprint=True,
Expand All @@ -506,7 +509,9 @@ def __init__(self, app=None, datastore=None, register_blueprint=True,
change_password_form=None,
send_confirmation_form=None,
passwordless_login_form=None,
anonymous_user=None)
anonymous_user=None,
render_template=self.render_template,
send_mail=self.send_mail)
self._kwargs.update(kwargs)

self._state = None # set by init_app
Expand Down Expand Up @@ -547,7 +552,6 @@ def _register_i18n():
if '_' not in app.jinja_env.globals:
app.jinja_env.globals['_'] = state.i18n_domain.gettext

state.render_template = self.render_template
app.extensions['security'] = state

if hasattr(app, 'cli'):
Expand All @@ -562,5 +566,8 @@ def _register_i18n():
def render_template(self, *args, **kwargs):
return render_template(*args, **kwargs)

def send_mail(self, *args, **kwargs):
return send_mail(*args, **kwargs)

def __getattr__(self, name):
return getattr(self._state, name, None)
6 changes: 3 additions & 3 deletions flask_security/passwordless.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from werkzeug.local import LocalProxy

from .signals import login_instructions_sent
from .utils import config_value, get_token_status, send_mail, url_for_security
from .utils import config_value, get_token_status, url_for_security

# Convenient references
_security = LocalProxy(lambda: app.extensions['security'])
Expand All @@ -30,8 +30,8 @@ def send_login_instructions(user):
token = generate_login_token(user)
login_link = url_for_security('token_login', token=token, _external=True)

send_mail(config_value('EMAIL_SUBJECT_PASSWORDLESS'), user.email,
'login_instructions', user=user, login_link=login_link)
_security.send_mail(config_value('EMAIL_SUBJECT_PASSWORDLESS'), user.email,
'login_instructions', user=user, login_link=login_link)

login_instructions_sent.send(app._get_current_object(), user=user,
login_token=token)
Expand Down
12 changes: 6 additions & 6 deletions flask_security/recoverable.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from .signals import password_reset, reset_password_instructions_sent
from .utils import config_value, get_token_status, hash_data, hash_password, \
send_mail, url_for_security, verify_hash
url_for_security, verify_hash

# Convenient references
_security = LocalProxy(lambda: app.extensions['security'])
Expand All @@ -33,9 +33,9 @@ def send_reset_password_instructions(user):
)

if config_value('SEND_PASSWORD_RESET_EMAIL'):
send_mail(config_value('EMAIL_SUBJECT_PASSWORD_RESET'), user.email,
'reset_instructions',
user=user, reset_link=reset_link)
_security.send_mail(config_value('EMAIL_SUBJECT_PASSWORD_RESET'),
user.email, 'reset_instructions',
user=user, reset_link=reset_link)

reset_password_instructions_sent.send(
app._get_current_object(), user=user, token=token
Expand All @@ -48,8 +48,8 @@ def send_password_reset_notice(user):
:param user: The user to send the notice to
"""
if config_value('SEND_PASSWORD_RESET_NOTICE_EMAIL'):
send_mail(config_value('EMAIL_SUBJECT_PASSWORD_NOTICE'), user.email,
'reset_notice', user=user)
_security.send_mail(config_value('EMAIL_SUBJECT_PASSWORD_NOTICE'),
user.email, 'reset_notice', user=user)


def generate_reset_password_token(user):
Expand Down
8 changes: 4 additions & 4 deletions flask_security/registerable.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@

from .confirmable import generate_confirmation_link
from .signals import user_registered
from .utils import config_value, do_flash, get_message, hash_password, \
send_mail
from .utils import config_value, do_flash, get_message, hash_password

# Convenient references
_security = LocalProxy(lambda: app.extensions['security'])
Expand All @@ -37,7 +36,8 @@ def register_user(**kwargs):
user=user, confirm_token=token)

if config_value('SEND_REGISTER_EMAIL'):
send_mail(config_value('EMAIL_SUBJECT_REGISTER'), user.email,
'welcome', user=user, confirmation_link=confirmation_link)
_security.send_mail(config_value('EMAIL_SUBJECT_REGISTER'), user.email,
'welcome', user=user,
confirmation_link=confirmation_link)

return user
17 changes: 17 additions & 0 deletions tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,23 @@ def send_email(msg):
assert app.mail_sent is True


@pytest.mark.recoverable()
def test_alt_send_mail(app, sqlalchemy_datastore):
""" Verify that can override the send_mail method. """
app.mail_sent = False

def send_email(subject, email, template, **kwargs):
app.mail_sent = True

init_app_with_options(app, sqlalchemy_datastore, **{
'security_args': {'send_mail': send_email}
})
client = app.test_client()

client.post('/reset', data=dict(email='matt@lp.com'))
assert app.mail_sent is True


def test_register_blueprint_flag(app, sqlalchemy_datastore):
app.security = Security(app, datastore=Security, register_blueprint=False)
client = app.test_client()
Expand Down