Skip to content

Commit

Permalink
Merge pull request #1283 from ScilifelabDataCentre/DDS-1349-new-endpo…
Browse files Browse the repository at this point in the history
…int-for-sending-motd-as-email

New endpoint: Send MOTD as email
  • Loading branch information
i-oden authored Sep 19, 2022
2 parents 040f263 + b6124f3 commit bf0db18
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,7 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe
- Change docker image to alpine ([#1272](https://github.com/ScilifelabDataCentre/dds_web/pull/1272))
- Added trivy when publishing to dockerhub ([#1276](https://github.com/ScilifelabDataCentre/dds_web/pull/1276))
- Bug fix: Cost value displayed by the --usage flag fixed ([#1274](https://github.com/ScilifelabDataCentre/dds_web/pull/1274))

## Sprint (2022-09-16 - 2022-09-30)

- New endpoint: SendMOTD - send important information to users ([#1283](https://github.com/ScilifelabDataCentre/dds_web/pull/1283))
1 change: 1 addition & 0 deletions dds_web/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def output_json(data, code, headers=None):

api.add_resource(superadmin_only.AllUnits, "/unit/info/all", endpoint="all_units")
api.add_resource(superadmin_only.MOTD, "/motd", endpoint="motd")
api.add_resource(superadmin_only.SendMOTD, "/motd/send", endpoint="send_motd")
api.add_resource(superadmin_only.FindUser, "/user/find", endpoint="find_user")
api.add_resource(
superadmin_only.ResetTwoFactor, "/user/totp/deactivate", endpoint="reset_user_hotp"
Expand Down
63 changes: 62 additions & 1 deletion dds_web/api/superadmin_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@
####################################################################################################

# Standard library
import os

# Installed
import flask_restful
import flask
import structlog
import flask_mail

# Own modules
from dds_web import auth, db
from dds_web import auth, db, mail
from dds_web.database import models
from dds_web.api.dds_decorators import json_required, logging_bind_request, handle_db_error
from dds_web import utils
import dds_web.errors as ddserr
from dds_web.api.user import AddUser


# initiate bound logger
Expand Down Expand Up @@ -136,6 +139,64 @@ def put(self):
return {"message": "The MOTD was successfully deactivated in the database."}


class SendMOTD(flask_restful.Resource):
"""Send a MOTD to all users in database."""

@auth.login_required(role=["Super Admin"])
@logging_bind_request
@json_required
@handle_db_error
def post(self):
"""Send MOTD as email to users."""
# Get MOTD ID
motd_id: int = flask.request.json.get("motd_id")
if not motd_id or not isinstance(motd_id, int): # The id starts at 1 - ok to not accept 0
raise ddserr.DDSArgumentError(
message="Please specify the ID of the MOTD you want to send."
)

# Get MOTD object
motd_obj: models.MOTD = models.MOTD.query.get(motd_id)
if not motd_obj or not motd_obj.active:
raise ddserr.DDSArgumentError(message=f"There is no active MOTD with ID '{motd_id}'.")

# Create email content
# put motd_obj.message etc in there etc
subject: str = "DDS Important Information"
body: str = flask.render_template(f"mail/motd.txt", motd=motd_obj.message)
html = flask.render_template(f"mail/motd.html", motd=motd_obj.message)

# Setup email connection
with mail.connect() as conn:
# Email users
for user in utils.page_query(db.session.query(models.User)):
primary_email = user.primary_email
if not primary_email:
flask.current_app.logger.warning(
f"No primary email found for user '{user.username}'."
)
continue
msg = flask_mail.Message(
subject=subject, recipients=[primary_email], body=body, html=html
)
msg.attach(
"scilifelab_logo.png",
"image/png",
open(
os.path.join(flask.current_app.static_folder, "img/scilifelab_logo.png"),
"rb",
).read(),
"inline",
headers=[
["Content-ID", "<Logo>"],
],
)
# Send email
utils.send_email_with_retry(msg=msg, obj=conn)

return {"message": f"MOTD '{motd_id}' has been sent to the users."}


class FindUser(flask_restful.Resource):
"""Get all users or check if there a specific user in the database."""

Expand Down
20 changes: 3 additions & 17 deletions dds_web/api/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,20 +296,6 @@ def invite_user(email, new_user_role, project=None, unit=None):
"errors": projects_not_shared,
}

@staticmethod
def send_email_with_retry(msg, times_retried=0):
"""Send email with retry on exception"""

try:
mail.send(msg)
except smtplib.SMTPException as err:
# Wait a little bit
time.sleep(10)
# Retry twice
if times_retried < 2:
retry = times_retried + 1
AddUser.send_email_with_retry(msg, retry)

@staticmethod
@logging_bind_request
def add_to_project(whom, project, role, send_email=True):
Expand Down Expand Up @@ -493,7 +479,7 @@ def compose_and_send_email_to_user(userobj, mail_type, link=None, project=None):
deadline=deadline,
)

AddUser.send_email_with_retry(msg)
dds_web.utils.send_email_with_retry(msg)


class RetrieveUserInfo(flask_restful.Resource):
Expand Down Expand Up @@ -1040,7 +1026,7 @@ def post(self):
link=link,
)

AddUser.send_email_with_retry(msg)
dds_web.utils.send_email_with_retry(msg)
return {
"message": "Please check your email and follow the attached link to activate two-factor with authenticator app."
}
Expand Down Expand Up @@ -1106,7 +1092,7 @@ def post(self):
link=link,
)

AddUser.send_email_with_retry(msg)
dds_web.utils.send_email_with_retry(msg)
return {
"message": "Please check your email and follow the attached link to activate two-factor with email."
}
Expand Down
27 changes: 27 additions & 0 deletions dds_web/templates/mail/motd.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{% extends 'mail/mail_base.html' %}
{% block body %}
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
<tr>
<td>
<p style="text-align: center;">
<img src="cid:Logo" alt="Logo" border="0" style="border:0; outline:none; text-decoration:none; margin-bottom: 15px; max-width: 400px; max-height: 87px;">
</p>
</td>
</tr>
<tr>
<td style=" font-family: arial, sans-serif; font-size: 14px; vertical-align: top;">
<p style="font-family: arial, sans-serif; font-size: 14px; font-weight: bold; margin: 0; margin-bottom: 5px;">
Important information to all DDS users:
</p>
</br>
<p style="font-family: arial, sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-top: 0px;">
{{ motd }}
</p>
</td>
</tr>
</table>
</br></br>
<p style="font-family: arial, sans-serif; font-size: 12px; font-weight: normal; margin: 0; Margin-top: 0px;">
This email cannot be replied to. If you need to get in contact with us regarding the DDS, please send an email to [email protected].
</p>
{% endblock %}
5 changes: 5 additions & 0 deletions dds_web/templates/mail/motd.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Important information to all DDS users:

{{ motd }}

This email cannot be replied to. If you need to get in contact with us regarding the DDS, please send an email to [email protected].
17 changes: 17 additions & 0 deletions dds_web/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import re
import typing
import urllib.parse
import time
import smtplib

# Installed
from contextlib import contextmanager
Expand Down Expand Up @@ -448,6 +450,21 @@ def page_query(q):
break


def send_email_with_retry(msg, times_retried=0, obj=None):
"""Send email with retry on exception"""
if obj is None:
obj = mail
try:
obj.send(msg)
except smtplib.SMTPException as err:
# Wait a little bit
time.sleep(10)
# Retry twice
if times_retried < 2:
retry = times_retried + 1
send_email_with_retry(msg, times_retried=retry, obj=obj)


def create_one_time_password_email(user, hotp_value):
"""Create HOTP email."""
msg = flask_mail.Message(
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ class DDSEndpoint:
# Superadmins only
LIST_UNITS_ALL = BASE_ENDPOINT + "/unit/info/all"
MOTD = BASE_ENDPOINT + "/motd"
MOTD_SEND = BASE_ENDPOINT + "/motd/send"
USER_FIND = BASE_ENDPOINT + "/user/find"
TOTP_DEACTIVATE = BASE_ENDPOINT + "/user/totp/deactivate"

Expand Down
Loading

0 comments on commit bf0db18

Please sign in to comment.