Too many things to remember all of them

Finally got the mail queue working
Oh yeah, queueing
Added a TOTP provider which doesn't do anything much yet
Probably a hell of a lot of other things that I just can't remember
This commit is contained in:
Kumi 2020-05-13 12:38:37 +02:00
parent bd0519146c
commit d549897186
20 changed files with 171 additions and 14 deletions

View file

@ -21,15 +21,16 @@ class BaseMailProvider:
def send_mail(self, subject, content, recipients, cc=[], bcc=[], headers={}, sender=getValue("core.mail.sender", "expephalon@localhost")): def send_mail(self, subject, content, recipients, cc=[], bcc=[], headers={}, sender=getValue("core.mail.sender", "expephalon@localhost")):
message = email.message_from_string(content) message = email.message_from_string(content)
headers["From"] = sender headers["From"] = sender
headers["To"] = ",".join(recipients) headers["To"] = recipients if type(recipients) == str else ",".join(recipients)
headers["Cc"] = ",".join(cc) headers["Cc"] = cc if type(cc) == str else ",".join(cc)
headers["Bcc"] = ",".join(bcc) headers["Bcc"] = bcc if type(bcc) == str else ",".join(bcc)
headers["Subject"] = subject headers["Subject"] = subject
headers["Message-ID"] = email.utils.make_msgid("expephalon", urlparse(getValue("core.base_url", "http://localhost/").split(":")[1]).netloc) headers["Message-ID"] = email.utils.make_msgid("expephalon", urlparse(getValue("core.base_url", "http://localhost/").split(":")[1]).netloc)
headers["Date"] = email.utils.formatdate() headers["Date"] = email.utils.formatdate()
for header, value in headers.items(): for header, value in headers.items():
message.add_header(header, value) if value:
self.send_message(message) message.add_header(header, value)
return self.send_message(message)
class SMTPMailProvider(BaseMailProvider): class SMTPMailProvider(BaseMailProvider):
def __init__(self, host=getValue("core.smtp.host", "localhost"), port=int(getValue("core.smtp.port", 0)), username=getValue("core.smtp.username", "") or None, password=getValue("core.smtp.password", "")): def __init__(self, host=getValue("core.smtp.host", "localhost"), port=int(getValue("core.smtp.port", 0)), username=getValue("core.smtp.username", "") or None, password=getValue("core.smtp.password", "")):
@ -42,4 +43,4 @@ class SMTPMailProvider(BaseMailProvider):
return "SMTP Mail" return "SMTP Mail"
def send_message(self, message): def send_message(self, message):
self.smtp.send_message(message) return self.smtp.send_message(message)

View file

@ -17,4 +17,7 @@ class OTPVerificationForm(Form):
class PWResetForm(Form): class PWResetForm(Form):
password1 = CharField(widget=PasswordInput) password1 = CharField(widget=PasswordInput)
password2 = CharField(widget=PasswordInput) password2 = CharField(widget=PasswordInput)
class PWRequestForm(Form):
email = EmailField()

2
core/helpers/auth.py Normal file
View file

@ -0,0 +1,2 @@
def generate_pwreset_mail(user, token):
pass

View file

@ -1,4 +1,5 @@
from core.modules.mail import providers from core.modules.mail import providers
from core.tasks.mail import send_mail as send_mail_task
from dbsettings.functions import getValue from dbsettings.functions import getValue
@ -8,5 +9,12 @@ def get_provider_by_name(name, fallback=True):
def get_default_provider(fallback=True): def get_default_provider(fallback=True):
return get_provider_by_name(getValue("core.email.provider", "smtp"), fallback) return get_provider_by_name(getValue("core.email.provider", "smtp"), fallback)
def send_mail(provider=None, *args): def send_mail(provider=get_default_provider(), **kwargs):
return get_provider_by_name(provider)().mail(*args) provider = get_provider_by_name(provider) if type(provider) == str else provider
return send_mail_task.delay(provider, **kwargs)
def simple_send_mail(subject, content, recipients, cc=[], bcc=[], headers={}):
return send_mail(subject=subject, content=content, recipients=recipients, cc=cc, bcc=bcc, headers=headers)
def fetch_templates(template_name):
pass

1
core/tasks/__init__.py Normal file
View file

@ -0,0 +1 @@
from core.tasks.mail import *

5
core/tasks/mail.py Normal file
View file

@ -0,0 +1,5 @@
from celery import shared_task
@shared_task
def send_mail(provider=None, **kwargs):
return provider().send_mail(**kwargs)

View file

@ -1,14 +1,16 @@
from django.conf import settings from django.conf import settings
from django.views.generic import FormView, View from django.views.generic import FormView, View
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout, get_user_model
from django.shortcuts import redirect from django.shortcuts import redirect
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.contrib import messages from django.contrib import messages
from django.utils import timezone from django.utils import timezone
from core.forms import LoginForm, OTPSelectorForm, OTPVerificationForm, PWResetForm from core.forms import LoginForm, OTPSelectorForm, OTPVerificationForm, PWResetForm, PWRequestForm
from core.models.auth import LoginSession, PWResetToken from core.models.auth import LoginSession, PWResetToken
from core.helpers.otp import get_user_otps, get_otp_choices, get_otp_by_name from core.helpers.otp import get_user_otps, get_otp_choices, get_otp_by_name
from core.helpers.mail import send_mail
from core.helpers.auth import generate_pwreset_mail
from dbsettings.functions import getValue from dbsettings.functions import getValue
@ -152,4 +154,17 @@ class PWResetView(FormView):
user.set_password(form.cleaned_data["password1"]) user.set_password(form.cleaned_data["password1"])
user.save() user.save()
messages.success(self.request, "Your password has been changed. You can now login with your new password.") messages.success(self.request, "Your password has been changed. You can now login with your new password.")
return redirect("login") return redirect("login")
class PWRequestView(FormView):
template_name = f"{settings.EXPEPHALON_BACKEND}/auth/pwrequest.html"
form_class = PWRequestForm
def form_valid(self, form):
try:
user = get_user_model().objects.get(username=form.cleaned_data["email"])
token = PWResetToken.objects.create(user=user)
finally:
messages.success(self.request, "If a matching account was found, you should shortly receive an email containing password reset instructions. If you have not received this message after five minutes, please verify that you have entered the correct email address, or contact support.")
return redirect("login")

2
debian-packages.txt Normal file
View file

@ -0,0 +1,2 @@
rabbitmq-server
memcached

View file

@ -0,0 +1,3 @@
from expephalon.celery import app as celery_app
__all__ = ('celery_app',)

22
expephalon/celery.py Normal file
View file

@ -0,0 +1,22 @@
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'expephalon.settings')
app = Celery('expephalon')
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))

View file

@ -29,11 +29,24 @@ DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
# To add frontend or backend templates, move them to the /templates subdirectory, then insert their name (i.e. the directory name) in the appropriate field. Move any required statics to the /static subdirectory. # To add frontend or backend templates, move them to the /templates subdirectory, then insert their name (i.e. the directory name) in the
# appropriate field. Move any required statics to the /static subdirectory
EXPEPHALON_FRONTEND = "frontend" EXPEPHALON_FRONTEND = "frontend"
EXPEPHALON_BACKEND = "backend" EXPEPHALON_BACKEND = "backend"
# To add Expephalon modules, move them to the Expephalon root directory, then add them to this list # To add Expephalon modules, move them to the Expephalon root directory, then add them to this list
EXPEPHALON_MODULES = [] EXPEPHALON_MODULES = []
# To use memcached for caching, add IP:PORT or unix:PATH - default setting should be good for an unmodified local setup of memcached
MEMCACHED_LOCATION = ["127.0.0.1:11211"]
# RabbitMQ is required for queues to work - default settings should be good for an unmodified local setup of RabbitMQ,
# but you might still want to configure it to use a password
RABBITMQ_LOCATION = "127.0.0.1:5672"
RABBITMQ_VHOST = ""
RABBITMQ_USER = "guest"
RABBITMQ_PASS = "guest"

View file

@ -18,6 +18,8 @@ INSTALLED_APPS = [
'bootstrap4', 'bootstrap4',
'core', 'core',
'dbsettings', 'dbsettings',
'django_celery_results',
'django_celery_beat',
] + EXPEPHALON_MODULES ] + EXPEPHALON_MODULES
MIDDLEWARE = [ MIDDLEWARE = [
@ -123,3 +125,22 @@ PASSWORD_HASHERS = [
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_DEFAULT_ACL = None AWS_DEFAULT_ACL = None
# Caching
if MEMCACHED_LOCATION:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': MEMCACHED_LOCATION,
}
}
# Queue
CELERY_TASK_SERIALIZER = "pickle"
CELERY_RESULT_SERIALIZER = "pickle"
CELERY_ACCEPT_CONTENT = ['pickle']
CELERY_CACHE_BACKEND = 'default'
CELERY_BROKER_URL = f"amqp://{RABBITMQ_USER}:{RABBITMQ_PASS}@{RABBITMQ_LOCATION}/{RABBITMQ_VHOST}"
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"

View file

View file

@ -0,0 +1,11 @@
Hi {{ first_name }},
Somebody (hopefully you) requested a new password for your {{ sitename }} account. If this was you, please click the following link to reset your password:
{{ link }}
If it was not you, you can ignore this message. The link will expire in 24 hours.
Best regards
Your {{ sitename }} Team

View file

@ -8,3 +8,6 @@ django-phonenumber-field[phonenumbers]
django-bootstrap4 django-bootstrap4
wheel wheel
git+https://kumig.it/kumisystems/django-dbsettings.git git+https://kumig.it/kumisystems/django-dbsettings.git
celery
django-celery-results
django-celery-beat

0
totp/__init__.py Normal file
View file

5
totp/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class TotpConfig(AppConfig):
name = 'totp'

12
totp/models.py Normal file
View file

@ -0,0 +1,12 @@
from django.db.models import Model, CharField, ForeignKey, CASCADE, DateTimeField
from django.contrib.auth import get_user_model
from dbsettings.functions import getValue
import pyotp
# Create your models here.
class TOTPUser(Model):
secret = CharField(max_length=32, default=pyotp.random_base32())
user = ForeignKey(get_user_model(), CASCADE)

29
totp/otp.py Normal file
View file

@ -0,0 +1,29 @@
from core.classes.otp import BaseOTPProvider
from totp.models import TOTPUser
from dbsettings.functions import getValue
from django.utils import timezone
import pyotp
class TOTP(BaseOTPProvider):
@property
def get_name(self):
return "Time-based OTP"
@property
def is_active(self):
return True
def start_authentication(self, user):
return "Please enter the token displayed in your app."
def validate_token(self, user, token):
try:
otpuser = TOTPUser.objects.get(user=user)
return pyotp.TOTP(otpuser.secret).verify(token)
except OTPUser.DoesNotExist:
return False
OTPPROVIDERS = {"totp": TOTP}

1
totp/requirements.txt Normal file
View file

@ -0,0 +1 @@
pyotp