Skip to content

Commit

Permalink
Merge pull request #157 from multiflexi/e-mail_publisher
Browse files Browse the repository at this point in the history
Improvements of email publisher - EMAIL UPDATE 2/2
  • Loading branch information
milankowww authored Sep 27, 2023
2 parents 019f13b + 06749ca commit e26e000
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 139 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ src/.env
*.key
*.log
*.crt
*.asc
local/

# settings of editors
Expand Down
9 changes: 7 additions & 2 deletions docker/Dockerfile.publishers
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.7-alpine3.14 AS build_shared
FROM python:3.9-alpine3.17 AS build_shared

WORKDIR /build_shared/

Expand All @@ -8,7 +8,7 @@ RUN python -m build



FROM python:3.7-alpine3.14 AS production
FROM python:3.9-alpine3.17 AS production

WORKDIR /app/

Expand All @@ -24,6 +24,10 @@ RUN pip install --no-cache-dir ./custom_packages/taranis_ng_shared-*.whl && rm -
# install dependencies

COPY ./src/publishers/requirements.txt /app/requirements.txt
RUN apk add --no-cache \
swig\
gnupg

RUN \
apk add --no-cache --virtual .build-deps build-base \
gcc \
Expand All @@ -32,6 +36,7 @@ RUN \
musl-dev \
python3-dev \
libffi-dev \
openssl-dev \
rust && \
pip install --no-cache-dir -r /app/requirements.txt && \
apk --purge del .build-deps
Expand Down
48 changes: 44 additions & 4 deletions src/core/managers/publishers_manager.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
"""Manager for publishers.
Returns:
_type_: _description_
"""
from model.publishers_node import PublishersNode
from model.publisher import Publisher
from model.publisher_preset import PublisherPreset
Expand All @@ -7,6 +12,14 @@


def add_publishers_node(data):
"""_summary_.
Args:
data (_type_): _description_
Returns:
_type_: _description_
"""
node = PublishersNodeSchema.create(data)
publishers_info, status_code = PublishersApi(node.api_url, node.api_key).get_publishers_info()
if status_code == 200:
Expand All @@ -17,6 +30,15 @@ def add_publishers_node(data):


def update_publishers_node(node_id, data):
"""_summary_.
Args:
node_id (_type_): _description_
data (_type_): _description_
Returns:
_type_: _description_
"""
node = PublishersNodeSchema.create(data)
publishers_info, status_code = PublishersApi(node.api_url, node.api_key).get_publishers_info()
if status_code == 200:
Expand All @@ -27,20 +49,38 @@ def update_publishers_node(node_id, data):


def add_publisher_preset(data):
"""_summary_.
Args:
data (_type_): _description_
"""
PublisherPreset.add_new(data)


def publish(preset, data, message_title, message_body, recipients):
"""_summary_.
Args:
preset (_type_): _description_
data (_type_): _description_
message_title (_type_): _description_
message_body (_type_): _description_
recipients (_type_): _description_
Returns:
_type_: _description_
"""
publisher = preset.publisher
node = publisher.node
data_data = None
data_mime = None
if data is not None:
data_data = data['data']
data_mime = data['mime_type']
data_data = data["data"]
data_mime = data["mime_type"]
message_title = data["message_title"]
message_body = data["message_body"]

input_data = PublisherInput(publisher.type, preset.parameter_values, data_mime, data_data, message_title,
message_body, recipients)
input_data = PublisherInput(publisher.type, preset.parameter_values, data_mime, data_data, message_title, message_body, recipients)
input_schema = PublisherInputSchema()

return PublishersApi(node.api_url, node.api_key).publish(input_schema.dump(input_data))
5 changes: 5 additions & 0 deletions src/publishers/crypto/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# A directory for certificates and private keys

If you wish to sign or encrypt messages of Taranis NG, place the certificates here. Use either PEM file with both certificate and key for S/MIME or armored PGP for signing.
The signing file might be encrypted, you can specify the password in Taranis NG configuration.
For encryption include S/MIME certificate or armored PGP public key.
3 changes: 3 additions & 0 deletions src/publishers/managers/publishers_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from publishers.wordpress_publisher import WORDPRESSPublisher
from publishers.misp_publisher import MISPPublisher
from shared.schema.publisher import PublisherInputSchema
from managers import log_manager

publishers = {}

Expand All @@ -24,6 +25,8 @@ def get_registered_publishers_info():
publishers_info = []
for key in publishers:
publishers_info.append(publishers[key].get_info())
log_manager.log_critical(publishers_info)


return publishers_info

Expand Down
222 changes: 94 additions & 128 deletions src/publishers/publishers/email_publisher.py
Original file line number Diff line number Diff line change
@@ -1,154 +1,120 @@
import datetime
import smtplib
from email.message import Message
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import gnupg

"""Publisher for publishing by email."""
from datetime import datetime
from base64 import b64decode
import os
from managers import log_manager
from .base_publisher import BasePublisher
from shared.schema.parameter import Parameter, ParameterType
from envelope import Envelope
import mimetypes


class EMAILPublisher(BasePublisher):
"""_summary_.
Arguments:
BasePublisher -- _description_
Returns:
_description_
"""

type = "EMAIL_PUBLISHER"
name = "EMAIL Publisher"
description = "Publisher for publishing by email"

parameters = [
Parameter(0, "SMTP_SERVER", "SMTP server", "SMTP server for sending emails", ParameterType.STRING),
Parameter(0, "SMTP_SERVER_PORT", "SMTP server port", "SMTP server port for sending emails",
ParameterType.STRING),
Parameter(0, "SMTP_SERVER_PORT", "SMTP server port", "SMTP server port for sending emails", ParameterType.STRING),
Parameter(0, "EMAIL_USERNAME", "Email username", "Username for email account", ParameterType.STRING),
Parameter(0, "EMAIL_PASSWORD", "Email password", "Password for email account", ParameterType.STRING),
Parameter(0, "EMAIL_RECIPIENT", "Email recipient", "Email address of recipient", ParameterType.STRING),
Parameter(0, "EMAIL_SENDER", "Email sender", "Email address of the sender", ParameterType.STRING),
Parameter(0, "EMAIL_RECIPIENT", "Email recipient", "Email address of the recipient", ParameterType.STRING),
Parameter(0, "EMAIL_SUBJECT", "Email subject", "Text of email subject", ParameterType.STRING),
Parameter(0, "EMAIL_MESSAGE", "Email message", "Text of email message", ParameterType.STRING),
Parameter(0, "EMAIL_ENCRYPTION", "Do you want use email encrypt (yes/no)", "Turn ON/OFF email encryption",
ParameterType.STRING)
Parameter(0, "EMAIL_SIGN", "Email signature", "File used for signing or auto", ParameterType.STRING),
Parameter(0, "EMAIL_SIGN_PASSWORD", "Email signature password", "Password for signing file", ParameterType.STRING),
Parameter(0, "EMAIL_ENCRYPT", "Email encryption", "File used for encryption or auto", ParameterType.STRING),
]

parameters.extend(BasePublisher.parameters)

def publish(self, publisher_input):

smtp_server = publisher_input.parameter_values_map['SMTP_SERVER']
smtp_server_port = publisher_input.parameter_values_map['SMTP_SERVER_PORT']
email_user = publisher_input.parameter_values_map['EMAIL_USERNAME']
email_password = publisher_input.parameter_values_map['EMAIL_PASSWORD']
email_recipients = publisher_input.parameter_values_map['EMAIL_RECIPIENT']
email_subject = publisher_input.parameter_values_map['EMAIL_SUBJECT']
email_message = publisher_input.parameter_values_map['EMAIL_MESSAGE']
email_encryption = publisher_input.parameter_values_map['EMAIL_ENCRYPTION']

file = 'file_' + datetime.datetime.now().strftime("%d-%m-%Y_%H:%M") + '.pdf'

if publisher_input.data is not None:
data = publisher_input.data[:]
"""_summary_.
Arguments:
publisher_input -- _description_
Returns:
_description_
"""
smtp_server = publisher_input.parameter_values_map["SMTP_SERVER"]
smtp_server_port = publisher_input.parameter_values_map["SMTP_SERVER_PORT"]
user = publisher_input.parameter_values_map["EMAIL_USERNAME"]
password = publisher_input.parameter_values_map["EMAIL_PASSWORD"]
sender = publisher_input.parameter_values_map["EMAIL_SENDER"]
recipients = publisher_input.parameter_values_map["EMAIL_RECIPIENT"]
subject = publisher_input.parameter_values_map["EMAIL_SUBJECT"]
message = publisher_input.parameter_values_map["EMAIL_MESSAGE"]
sign = publisher_input.parameter_values_map["EMAIL_SIGN"]
sign_password = publisher_input.parameter_values_map["EMAIL_SIGN_PASSWORD"]
encrypt = publisher_input.parameter_values_map["EMAIL_ENCRYPT"]

now = datetime.now().strftime("%Y%m%d%H%M%S")

smtp = {"host": smtp_server, "port": smtp_server_port, "user": user, "password": password}

envelope = Envelope()

# if attachment data available from presenter
if publisher_input.mime_type and publisher_input.data:
attachment_mimetype = publisher_input.mime_type
attachment_extension = mimetypes.guess_extension(attachment_mimetype)
attachment_data = publisher_input.data[:]
attachment_list = [
(
b64decode(attachment_data),
attachment_mimetype,
f"file_{now}{attachment_extension}",
False,
)
]
# it is possible to attach multiple files
envelope.attach(attachment_list)

# when title available from presenter
if publisher_input.message_title:
subject = b64decode(publisher_input.message_title).decode("UTF-8")
# when body available from presenter
if publisher_input.message_body:
message = b64decode(publisher_input.message_body).decode("UTF-8")

if not message:
envelope.message(" ")
else:
data = None

def get_attachment(file_name):
msg_attachment = Message()
msg_attachment.add_header(_name="Content-Type", _value='application/pdf', name=file_name)
msg_attachment.add_header(_name="Content-Transfer-Encoding", _value="base64")
msg_attachment.add_header(_name="Content-Disposition", _value="attachment", filename=file_name)
msg_attachment.set_payload(data)
return msg_attachment

def get_body(message):
msg_body = Message()
msg_body.add_header(_name="Content-Type", _value="text/plain", charset="utf-8")
msg_body.add_header(_name="Content-Transfer-Encoding", _value="quoted-printable")
msg_body.set_payload(message + 2 * "\n")
return msg_body

def get_encrypted_email_string(email_address_recipient, file_name, message):
def get_gpg_cipher_text(string, recipient_email_address):
gpg = gnupg.GPG()
encrypted_str = str(gpg.encrypt(string, recipient_email_address))
return encrypted_str

msg = Message()
msg.add_header(_name="Content-Type", _value="multipart/mixed")
msg["From"] = email_user
msg["To"] = email_address_recipient
msg['Subject'] = email_subject

msg_text = Message()
msg_text.add_header(_name="Content-Type", _value="multipart/mixed")
msg_text.add_header(_name="Content-Language", _value="en-US")

msg_body = get_body(message)
msg_attachment = get_attachment(file_name)

msg_text.attach(msg_body)
msg_text.attach(msg_attachment)
msg.attach(msg_text)

pgp_msg = MIMEBase(_maintype="multipart", _subtype="encrypted", protocol="application/pgp-encrypted")
pgp_msg["From"] = email_user
pgp_msg["To"] = email_address_recipient
pgp_msg['Subject'] = email_subject

pgp_msg_part1 = Message()
pgp_msg_part1.add_header(_name="Content-Type", _value="application/pgp-encrypted")
pgp_msg_part1.add_header(_name="Content-Description", _value="PGP/MIME version identification")
pgp_msg_part1.set_payload("Version: 2" + "\n")

pgp_msg_part2 = Message()
pgp_msg_part2.add_header(_name="Content-Type", _value="application/octet-stream", name="encrypted.asc")
pgp_msg_part2.add_header(_name="Content-Description", _value="OpenPGP encrypted message")
pgp_msg_part2.add_header(_name="Content-Disposition", _value="inline", filename="encrypted.asc")
pgp_msg_part2.set_payload(get_gpg_cipher_text(msg.as_string(), email_address_recipient))

pgp_msg.attach(pgp_msg_part1)
pgp_msg.attach(pgp_msg_part2)

return pgp_msg.as_string()
envelope.message(message)
if not subject:
envelope.subject(" ")
else:
envelope.subject(subject)
envelope.from_(sender)
envelope.to(recipients)
envelope.smtp(smtp)

if sign == "auto":
envelope.signature(key=sign)
elif os.path.isfile(sign):
log_manager.log_info(f"Signing email with file {sign}")
envelope.signature(key=open(sign), passphrase=sign_password)

if encrypt == "auto":
envelope.encryption(key=encrypt)
elif os.path.isfile(encrypt):
log_manager.log_info(f"Encrypting email with file {encrypt}")
envelope.encryption(key=open(encrypt))

try:

server = smtplib.SMTP(smtp_server, smtp_server_port)
server.starttls()
server.login(email_user, email_password)

if publisher_input.recipients is not None:
recipients = publisher_input.recipients
else:
recipients = email_recipients.split(',')

if email_encryption.lower() == 'yes':
for recipient in recipients:
email_msg = email_message
email_msg = get_encrypted_email_string(recipient, file, email_msg)
server.sendmail(email_user, recipient, email_msg)
else:
email_msg = MIMEMultipart()
email_msg['From'] = email_user
email_msg['To'] = email_recipients

if publisher_input.message_title is not None:
email_msg['Subject'] = publisher_input.message_title
else:
email_msg['Subject'] = email_subject

if publisher_input.message_body is not None:
body = publisher_input.message_body
else:
body = email_message

email_msg.attach(MIMEText(body + 2 * "\n", 'plain'))

if data is not None:
attachment = get_attachment(file)
email_msg.attach(attachment)

text = email_msg.as_string()

server.sendmail(email_user, recipients, text)

server.quit()
envelope.send()

except Exception as error:
BasePublisher.print_exception(self, error)
Loading

0 comments on commit e26e000

Please sign in to comment.