add E-Mail support via celery task, untested, closes #17

This commit is contained in:
Jens Langhammer 2019-02-26 14:07:47 +01:00
parent e7fb48eba2
commit ad96f7dbb8
13 changed files with 388 additions and 13 deletions

View File

@ -16,7 +16,6 @@ variables:
POSTGRES_DB: passbook POSTGRES_DB: passbook
POSTGRES_USER: passbook POSTGRES_USER: passbook
POSTGRES_PASSWORD: 'EK-5jnKfjrGRm<77' POSTGRES_PASSWORD: 'EK-5jnKfjrGRm<77'
SUPERVISR_ENV: ci
include: include:
- /allauth/.gitlab-ci.yml - /allauth/.gitlab-ci.yml

View File

@ -5,7 +5,7 @@ from django.contrib import messages
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.forms.utils import ErrorList from django.forms.utils import ErrorList
from django.shortcuts import redirect from django.shortcuts import redirect, reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import FormView from django.views.generic import FormView
@ -13,6 +13,7 @@ from passbook.core.auth.factor import AuthenticationFactor
from passbook.core.auth.view import AuthenticationView from passbook.core.auth.view import AuthenticationView
from passbook.core.forms.authentication import PasswordFactorForm from passbook.core.forms.authentication import PasswordFactorForm
from passbook.core.models import Nonce from passbook.core.models import Nonce
from passbook.core.tasks import send_email
from passbook.lib.config import CONFIG from passbook.lib.config import CONFIG
LOGGER = getLogger(__name__) LOGGER = getLogger(__name__)
@ -32,7 +33,16 @@ class PasswordFactor(FormView, AuthenticationFactor):
if 'password-forgotten' in request.GET: if 'password-forgotten' in request.GET:
nonce = Nonce.objects.create(user=self.pending_user) nonce = Nonce.objects.create(user=self.pending_user)
LOGGER.debug("DEBUG %s", str(nonce.uuid)) LOGGER.debug("DEBUG %s", str(nonce.uuid))
# TODO: Send email to user # Send mail to user
send_email.delay(self.pending_user.email, _('Forgotten password'),
'email/account_password_reset.html', {
'url': self.request.build_absolute_uri(
reverse('passbook_core:passbook_core:auth-password-reset',
kwargs={
'nonce': nonce.uuid
})
)
})
self.authenticator.cleanup() self.authenticator.cleanup()
messages.success(request, _('Check your E-Mails for a password reset link.')) messages.success(request, _('Check your E-Mails for a password reset link.'))
return redirect('passbook_core:auth-login') return redirect('passbook_core:auth-login')

17
passbook/core/tasks.py Normal file
View File

@ -0,0 +1,17 @@
"""passbook core tasks"""
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from passbook.core.celery import CELERY_APP
from passbook.lib.config import CONFIG
@CELERY_APP.task()
def send_email(to_address, subject, template, context):
"""Send Email to user(s)"""
html_content = render_to_string(template, context=context)
text_content = strip_tags(html_content)
msg = EmailMultiAlternatives(subject, text_content, CONFIG.y('email.from'), [to_address])
msg.attach_alternative(html_content, "text/html")
msg.send()

View File

@ -0,0 +1,84 @@
{% extends 'email/base.html' %}
{% load inline %}
{% load i18n %}
{% block pre_header %}
{% trans "We're thrilled to have you here! Get ready to dive into your new account." %}
{% endblock %}
{% block content %}
<!-- HERO -->
<tr>
<td bgcolor="#3625b7" align="center" style="padding: 0px 10px 0px 10px;">
<table border="0" cellpadding="0" cellspacing="0" width="480">
<tr>
<td bgcolor="#566572" align="center" valign="top"
style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #8F9BA3; font-family: 'Metropolis', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<h1 style="font-size: 32px; font-weight: 400; margin: 0; color: #E9ECEF;">{% trans 'Welcome!' %}
</h1>
</td>
</tr>
</table>
</td>
</tr>
<!-- COPY BLOCK -->
<tr>
<td bgcolor="#1b2a32" align="center" style="padding: 0px 10px 0px 10px;">
<table border="0" cellpadding="0" cellspacing="0" width="480">
<!-- COPY -->
<tr>
<td bgcolor="#566572" align="left"
style="padding: 20px 30px 40px 30px; color: #E9ECEF; font-family: 'Metropolis', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
<p style="margin: 0;">
{% trans "We're excited to have you get started. First, you need to confirm your account. Just press the button below."%}
</p>
</td>
</tr>
<!-- BULLETPROOF BUTTON -->
<tr>
<td bgcolor="#566572" align="left">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#566572" align="center" style="padding: 20px 30px 60px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#3625b7"><a
href="{{ url }}" target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #3625b7; display: inline-block;">{% trans 'Confirm Account' %}</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- COPY -->
<tr>
<td bgcolor="#566572" align="left"
style="padding: 0px 30px 0px 30px; color: #E9ECEF; font-family: 'Metropolis', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
<p style="margin: 0;">
{% trans "If that doesn't work, copy and paste the following link in your browser:" %}</p>
</td>
</tr>
<!-- COPY -->
<tr>
<td bgcolor="#566572" align="left"
style="padding: 20px 30px 20px 30px; color: #E9ECEF; font-family: 'Metropolis', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
<p style="margin: 0;"><a href="{{ url }}" target="_blank" style="color: #3625b7;">{{ url }}</a></p>
</td>
</tr>
<!-- COPY -->
<tr>
<td bgcolor="#566572" align="left"
style="padding: 0px 30px 20px 30px; color: #E9ECEF; font-family: 'Metropolis', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
<p style="margin: 0;">
{% trans "If you have any questions, just reply to this email—we're always happy to help out." %}
</p>
</td>
</tr>
</table>
</td>
</tr>
{% endblock %}

View File

@ -0,0 +1,78 @@
{% extends "email/base.html" %}
{% load utils %}
{% load i18n %}
{% block pre_header %}
{% trans "Looks like you tried signing in a few too many times. Let's see if we can get you back into your account." %}
{% endblock %}
{% block content %}
{% config 'passbook.branding' as branding %}
<!-- HERO -->
<tr>
<td bgcolor="#7c72dc" align="center" style="padding: 0px 10px 0px 10px;">
<table border="0" cellpadding="0" cellspacing="0" width="600" class="wrapper">
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">{% trans 'Trouble signing in?' %}</h1>
</td>
</tr>
</table>
</td>
</tr>
<!-- COPY BLOCK -->
<tr>
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 10px 0px 10px;">
<table border="0" cellpadding="0" cellspacing="0" width="600" class="wrapper">
<!-- COPY -->
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
<p style="margin: 0;">{% trans "Resetting your password is easy. Just press the button below and follow the instructions. We'll have you up and running in no time." %}</p>
</td>
</tr>
<!-- BULLETPROOF BUTTON -->
<tr>
<td bgcolor="#ffffff" align="left">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 20px 30px 60px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#7c72dc"><a href="{{ url }}" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #7c72dc; display: inline-block;">{% trans 'Reset Password' %}</a></td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- COPY CALLOUT -->
<tr>
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 10px 0px 10px;">
<table border="0" cellpadding="0" cellspacing="0" width="600" class="wrapper">
<!-- HEADLINE -->
<tr>
<td bgcolor="#111111" align="left" style="padding: 40px 30px 20px 30px; color: #ffffff; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
<h2 style="font-size: 24px; font-weight: 400; margin: 0;">{% trans 'Want a more secure account?' %}</h2>
</td>
</tr>
<!-- COPY -->
<tr>
<td bgcolor="#111111" align="left" style="padding: 0px 30px 20px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
<p style="margin: 0;">{% trans 'We support two-factor authentication to help keep your information private.' %}</p>
</td>
</tr>
<!-- COPY -->
<tr>
<td bgcolor="#111111" align="left" style="padding: 0px 30px 40px 30px; border-radius: 0px 0px 4px 4px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
<p style="margin: 0;"><a href="http://litmus.com" target="_blank" style="color: #7c72dc;">{% trans 'See how easy it is to get started' %}</a></p>
</td>
</tr>
</table>
</td>
</tr>
{% endblock %}

View File

@ -0,0 +1,129 @@
{% load inline %}
{% load utils %}
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<title>{% config passbook.branding %}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style type="text/css">
/* CLIENT-SPECIFIC STYLES */
body, table, td, a {
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table, td {
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
-ms-interpolation-mode: bicubic;
}
/* RESET STYLES */
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
}
table {
border-collapse: collapse !important;
}
body {
height: 100% !important;
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
}
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* ANDROID CENTER FIX */
div[style*="margin: 16px 0;"] {
margin: 0 !important;
}
</style>
</head>
<body style="background-color: #1b2a32; margin: 0 !important; padding: 0 !important;">
<!-- HIDDEN PREHEADER TEXT -->
<div style="display: none; font-size: 1px; color: #fefefe; line-height: 1px; font-family: 'Metropolis', Helvetica, Arial, sans-serif; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden;">
{% block pre_header %}
{% endblock %}
</div>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<!-- LOGO -->
<tr>
<td bgcolor="#3625b7" align="center">
<table border="0" cellpadding="0" cellspacing="0" width="480">
<tr>
<td align="center" valign="top" style="padding: 40px 10px 40px 10px;">
<a href="" target="_blank">
<img alt="Logo" src="{% inline_static 'assets/dark.svg' %}" width="64" height="64"
style="display: block; width: 64px; max-width: 64px; min-width: 64px; font-family: 'Metropolis', Helvetica, Arial, sans-serif; color: #ffffff; font-size: 18px;"
border="0">
</a>
</td>
</tr>
</table>
</td>
</tr>
{% block content %}
{% endblock %}
<!-- SUPPORT CALLOUT -->
<!-- <tr>
<td bgcolor="#1b2a32" align="center" style="padding: 30px 10px 0px 10px;">
<table border="0" cellpadding="0" cellspacing="0" width="480">
HEADLINE
<tr>
<td bgcolor="#566572" align="center" style="padding: 30px 30px 30px 30px; border-radius: 4px 4px 4px 4px; color: #E9ECEF; font-family: 'Metropolis', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
<h2 style="font-size: 20px; font-weight: 400; color: ##E9ECEF; margin: 0;">Need more help?</h2>
<p style="margin: 0;"><a href="http://litmus.com" target="_blank" style="color: #3625b7;">We&rsquo;re
here, ready to talk</a></p>
</td>
</tr>
</table>
</td>
</tr> -->
<!-- FOOTER -->
<tr>
<td bgcolor="#1b2a32" align="center" style="padding: 0px 10px 0px 10px;">
<table border="0" cellpadding="0" cellspacing="0" width="480">
<!-- NAVIGATION -->
<tr>
<td bgcolor="#1b2a32" align="left" style="padding: 30px 30px 30px 30px; color: #E9ECEF; font-family: 'Metropolis', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;">
<p style="margin: 0;">
</p>
</td>
</tr>
<!-- ADDRESS -->
<tr>
<td bgcolor="#1b2a32" align="left" style="padding: 0px 30px 30px 30px; color: #E9ECEF; font-family: 'Metropolis', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;">
<p style="margin: 0;"><a href="{% config 'passbook.branding' %}">{% config 'passbook.branding' %}</a></p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,26 @@
{% extends "email/base.html" %}
{% block content %}
<tr>
<td bgcolor="#3625b7" align="center" style="padding: 0px 10px 0px 10px;">
<table border="0" cellpadding="0" cellspacing="0" width="480">
<tr>
<td bgcolor="#566572" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #8F9BA3; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<h1 style="font-size: 32px; font-weight: 400; margin: 0; color: #E9ECEF;">{{ title }}!</h1>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#1b2a32" align="center" style="padding: 0px 10px 0px 10px;">
<table border="0" cellpadding="0" cellspacing="0" width="480">
<tr>
<td bgcolor="#566572" align="left" style="padding: 20px 30px 40px 30px; color: #E9ECEF; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
<p style="margin: 0;">{{ body }}</p>
</td>
</tr>
</table>
</td>
</tr>
{% endblock %}

View File

@ -15,10 +15,12 @@ from passbook.core.auth.view import AuthenticationView
from passbook.core.forms.authentication import LoginForm, SignUpForm from passbook.core.forms.authentication import LoginForm, SignUpForm
from passbook.core.models import Invitation, Nonce, Source, User from passbook.core.models import Invitation, Nonce, Source, User
from passbook.core.signals import invitation_used, user_signed_up from passbook.core.signals import invitation_used, user_signed_up
from passbook.core.tasks import send_email
from passbook.lib.config import CONFIG from passbook.lib.config import CONFIG
LOGGER = getLogger(__name__) LOGGER = getLogger(__name__)
class LoginView(UserPassesTestMixin, FormView): class LoginView(UserPassesTestMixin, FormView):
"""Allow users to sign in""" """Allow users to sign in"""
@ -76,6 +78,7 @@ class LoginView(UserPassesTestMixin, FormView):
messages.error(request, _('Failed to authenticate.')) messages.error(request, _('Failed to authenticate.'))
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
class LogoutView(LoginRequiredMixin, View): class LogoutView(LoginRequiredMixin, View):
"""Log current user out""" """Log current user out"""
@ -145,7 +148,15 @@ class SignUpView(UserPassesTestMixin, FormView):
if needs_confirmation: if needs_confirmation:
nonce = Nonce.objects.create(user=self._user) nonce = Nonce.objects.create(user=self._user)
LOGGER.debug(str(nonce.uuid)) LOGGER.debug(str(nonce.uuid))
# TODO: Send E-Mail to user # Send email to user
send_email.delay(self._user.email, _('Confirm your account.'),
'email/account_confirm.html', {
'url': self.request.build_absolute_uri(
reverse('passbook_core:auth-sign-up-confirm', kwargs={
'nonce': nonce.uuid
})
)
})
self._user.is_active = False self._user.is_active = False
self._user.save() self._user.save()
self.consume_invitation() self.consume_invitation()
@ -196,6 +207,7 @@ class SignUpView(UserPassesTestMixin, FormView):
request=request) request=request)
return new_user return new_user
class SignUpConfirmView(View): class SignUpConfirmView(View):
"""Confirm registration from Nonce""" """Confirm registration from Nonce"""

View File

@ -39,7 +39,7 @@ class LDAPSourceForm(forms.ModelForm):
# (MODE_CREATE_USERS, _('Create Users')) # (MODE_CREATE_USERS, _('Create Users'))
# ) # )
# namespace = 'supervisr.mod.auth.ldap' # namespace = 'passbook.ldap'
# settings = ['enabled', 'mode'] # settings = ['enabled', 'mode']
# widgets = { # widgets = {
@ -51,7 +51,7 @@ class LDAPSourceForm(forms.ModelForm):
# class ConnectionSettings(SettingsForm): # class ConnectionSettings(SettingsForm):
# """Connection settings form""" # """Connection settings form"""
# namespace = 'supervisr.mod.auth.ldap' # namespace = 'passbook.ldap'
# settings = ['server', 'server:tls', 'bind:user', 'bind:password', 'domain'] # settings = ['server', 'server:tls', 'bind:user', 'bind:password', 'domain']
# attrs_map = { # attrs_map = {
@ -68,7 +68,7 @@ class LDAPSourceForm(forms.ModelForm):
# class AuthenticationBackendSettings(SettingsForm): # class AuthenticationBackendSettings(SettingsForm):
# """Authentication backend settings""" # """Authentication backend settings"""
# namespace = 'supervisr.mod.auth.ldap' # namespace = 'passbook.ldap'
# settings = ['base'] # settings = ['base']
# attrs_map = { # attrs_map = {
@ -79,7 +79,7 @@ class LDAPSourceForm(forms.ModelForm):
# class CreateUsersSettings(SettingsForm): # class CreateUsersSettings(SettingsForm):
# """Create users settings""" # """Create users settings"""
# namespace = 'supervisr.mod.auth.ldap' # namespace = 'passbook.ldap'
# settings = ['create_base'] # settings = ['create_base']
# attrs_map = { # attrs_map = {

View File

@ -57,7 +57,7 @@ class LDAPSource(Source):
# class LDAPGroupMapping(UUIDModel, CreatedUpdatedModel): # class LDAPGroupMapping(UUIDModel, CreatedUpdatedModel):
# """Model to map an LDAP Group to a supervisr group""" # """Model to map an LDAP Group to a passbook group"""
# ldap_dn = models.TextField() # ldap_dn = models.TextField()
# group = models.ForeignKey(Group, on_delete=models.CASCADE) # group = models.ForeignKey(Group, on_delete=models.CASCADE)

View File

@ -2,7 +2,7 @@
# from django.conf.urls import url # from django.conf.urls import url
# from supervisr.mod.auth.ldap import views # from passbook.mod.auth.ldap import views
# urlpatterns = [ # urlpatterns = [
# url(r'^settings/$', views.admin_settings, name='admin_settings'), # url(r'^settings/$', views.admin_settings, name='admin_settings'),

View File

@ -1,4 +1,4 @@
# """Supervisr Mod LDAP Views""" # """passbook LDAP Views"""
# from django.contrib import messages # from django.contrib import messages
@ -8,7 +8,7 @@
# from django.urls import reverse # from django.urls import reverse
# from django.utils.translation import ugettext as _ # from django.utils.translation import ugettext as _
# from supervisr.mod.auth.ldap.forms import (AuthenticationBackendSettings, # from passbook.ldap.forms import (AuthenticationBackendSettings,
# ConnectionSettings, # ConnectionSettings,
# CreateUsersSettings, # CreateUsersSettings,
# GeneralSettingsForm) # GeneralSettingsForm)
@ -34,5 +34,5 @@
# if form.is_valid(): # if form.is_valid():
# update_count += form.save() # update_count += form.save()
# messages.success(request, _('Successfully updated %d settings.' % update_count)) # messages.success(request, _('Successfully updated %d settings.' % update_count))
# return redirect(reverse('supervisr_mod_auth_ldap:admin_settings')) # return redirect(reverse('passbook_ldap:admin_settings'))
# return render(request, 'ldap/settings.html', render_data) # return render(request, 'ldap/settings.html', render_data)

View File

@ -0,0 +1,20 @@
"""passbook core inlining template tags"""
import os
from django import template
from django.conf import settings
register = template.Library()
@register.simple_tag()
def inline_static(path):
"""Inline static asset. If file is binary, return b64 representation"""
prefix = 'data:image/svg+xml;utf8,'
data = ''
full_path = settings.STATIC_ROOT + '/' + path
if os.path.exists(full_path):
if full_path.endswith('.svg'):
with open(full_path) as _file:
data = _file.read()
return prefix + data