Add responses config option which allows customizing the response message templates

This commit is contained in:
s3lph 2021-10-03 02:32:33 +02:00
parent adc699aeed
commit f9ddba422c
6 changed files with 138 additions and 86 deletions

View file

@ -1,5 +1,18 @@
# EasyWKS Changelog # EasyWKS Changelog
<!-- BEGIN RELEASE v0.1.5 -->
## Version 0.1.5
The messages sent by EasyWKS can now be customized.
### Changes
<!-- BEGIN CHANGES 0.1.5 -->
- Add `responses` config option.
<!-- END CHANGES 0.1.5 -->
<!-- END RELEASE v0.1.5 -->
<!-- BEGIN RELEASE v0.1.4 --> <!-- BEGIN RELEASE v0.1.4 -->
## Version 0.1.4 ## Version 0.1.4

View file

@ -112,10 +112,34 @@ smtp:
# Omit username/password if authentication is not needed. # Omit username/password if authentication is not needed.
username: webkey username: webkey
password: SuperS3curePassword123 password: SuperS3curePassword123
# Configure the LMTP server # Configure the LMTP server
lmtpds: lmtpd:
host: "::1" host: "::1"
port: 8024 port: 8024
# You can override the mail response templates with your own text.
# The following templates can be overridden:
# - "header": Placed in front of every message.
# - "footer": Appended to every message.
# - "confirm": Sent with the confirmation request.
# - "done": Sent after a key was published.
# - "error": Sent when an error occurs.
# The following placeholders can be used (enclosed in curly braces):
# - {domain}: The email domain for with the request is processed.
# - {sender}: The submitter's mail address.
# - {submission}: The submission address.
# When overriding the "error" template, theres an additional
# placeholder you can use:
# - {error}: The error message.
#responses:
# error: |
# An error has occurred while processing your request:
#
# {error}
#
# If this error persists, please contact admin@example.org for help.
# Every domain served by EasyWKS must be listed here # Every domain served by EasyWKS must be listed here
domains: domains:
example.org: example.org:

View file

@ -1,2 +1,2 @@
__version__ = '0.1.4' __version__ = '0.1.5'

View file

@ -79,6 +79,58 @@ def _validate_policy_flags(value):
return f'has invalid key {flag}' return f'has invalid key {flag}'
def _validate_responses(value):
if not isinstance(value, dict):
return f'must be a map, got {type(value)}'
if 'header' not in value:
value['header'] = '''Hi there!
This is the EasyWKS system at {domain}.
'''
if 'footer' not in value:
value['footer'] = '''For more information on WKD and WKS see:
https://gnupg.org/faq/wkd.html
https://gnupg.org/faq/wks.html
Regards
EasyWKS
--
Dance like nobody is watching.
Encrypt live everybody is.
'''
if 'confirm' not in value:
value['confirm'] = '''You appear to have submitted your key for publication in the Web Key
Directory. There's one more step you need to complete. If you did not
request this, you can simply ignore this message.
If your email client doesn't automatically complete this challenge, you
can perform this step manually: Please verify that you can decrypt the
second part of this message and that the fingerprint listed in the
encrypted part matches your key. If everything looks ok, please reply
to this message with an **encrypted and signed PGP/MIME message** with
the following content (without the <> brackets)
type: confirmation-response
sender: <your email address>
nonce: <copy the nonce from the encrypted part of this message>
'''
if 'done' not in value:
value['done'] = '''Your key has been published to the Web Key Directory.
You can test WKD key retrieval e.g. with:
gpg --auto-key-locate=wkd,nodefault --locate-key {sender}
'''
if 'error' not in value:
value['error'] = '''An error has occurred while processing your request:
{error}
If this error persists, please contact your administrator for help.'''
class _ConfigOption: class _ConfigOption:
def __init__(self, key, typ, default, validator=None): def __init__(self, key, typ, default, validator=None):
@ -165,4 +217,12 @@ Config = _GlobalConfig(
'host': 'localhost', 'host': 'localhost',
'port': 25, 'port': 25,
}, validator=_validate_lmtpd_config), }, validator=_validate_lmtpd_config),
responses=_ConfigOption('responses', dict, {}, validator=_validate_responses),
) )
def render_message(key, **kwargs):
header = Config.responses['header'].format(**kwargs)
content = Config.responses[key].format(**kwargs)
footer = Config.responses['footer'].format(**kwargs)
return f'{header}\n{content}\n{footer}'

View file

@ -10,7 +10,7 @@ from pgpy import PGPKey, PGPMessage, PGPUID
from pgpy.types import SignatureVerification from pgpy.types import SignatureVerification
from .crypto import pgp_sign from .crypto import pgp_sign
from .config import Config from .config import Config, render_message
from .util import create_nonce, fingerprint from .util import create_nonce, fingerprint
@ -42,39 +42,6 @@ class SubmissionRequest:
class ConfirmationRequest: class ConfirmationRequest:
MAIL_TEXT = '''Hi there!
This is the EasyWKS system at {domain}.
You appear to have submitted your key for publication in the Web Key
Directory. There's one more step you need to complete. If you did not
request this, you can simply ignore this message.
If your email client doesn't automatically complete this challenge, you
can perform this step manually: Please verify that you can decrypt the
second part of this message and that the fingerprint listed in the
encrypted part matches your key. If everything looks ok, please reply
to this message with an **encrypted and signed PGP/MIME message** with
the following content (without the <> brackets)
type: confirmation-response
sender: <your email address>
nonce: <copy the nonce from the encrypted part of this message>
For more information on WKD and WKS see:
https://gnupg.org/faq/wkd.html
https://gnupg.org/faq/wks.html
Regards
EasyWKS
--
Dance like nobody is watching.
Encrypt live everybody is.
'''
def __init__(self, submitter_addr: str, submission_addr: str, key: PGPKey, nonce: str = None): def __init__(self, submitter_addr: str, submission_addr: str, key: PGPKey, nonce: str = None):
self._domain = submitter_addr.split('@')[1] self._domain = submitter_addr.split('@')[1]
self._submitter_addr = submitter_addr self._submitter_addr = submitter_addr
@ -103,7 +70,11 @@ Encrypt live everybody is.
return self._nonce return self._nonce
def create_signed_message(self): def create_signed_message(self):
mpplain = MIMEText(ConfirmationRequest.MAIL_TEXT.format(domain=self.domain), _subtype='plain') mail_text = render_message('confirm',
domain=self.domain,
sender=self.submitter_address,
submission=self.submission_address)
mpplain = MIMEText(mail_text, _subtype='plain')
ps = '\r\n'.join([ ps = '\r\n'.join([
'type: confirmation-request', 'type: confirmation-request',
f'sender: {self._submission_addr}', f'sender: {self._submission_addr}',
@ -181,27 +152,6 @@ class ConfirmationResponse:
class PublishResponse: class PublishResponse:
MAIL_TEXT = '''Hi there!
This is the EasyWKS system at {domain}.
Your key has been published to the Web Key Directory.
You can test WKD key retrieval e.g. with:
gpg --auto-key-locate=wkd,nodefault --locate-key {uid}
For more information on WKD and WKS see:
https://gnupg.org/faq/wkd.html
https://gnupg.org/faq/wks.html
Regards
EasyWKS
--
Dance like nobody is watching.
Encrypt live everybody is.
'''
def __init__(self, submitter_addr: str, submission_addr: str, key: PGPKey): def __init__(self, submitter_addr: str, submission_addr: str, key: PGPKey):
self._domain = submitter_addr.split('@')[1] self._domain = submitter_addr.split('@')[1]
@ -226,8 +176,11 @@ Encrypt live everybody is.
return self._domain return self._domain
def create_signed_message(self): def create_signed_message(self):
mpplain = MIMEText(PublishResponse.MAIL_TEXT.format(domain=self.domain, uid=self.submitter_address), mail_text = render_message('done',
_subtype='plain') domain=self.domain,
sender=self.submitter_address,
submission=self.submission_address)
mpplain = MIMEText(mail_text, _subtype='plain')
to_encrypt = PGPMessage.new(mpplain.as_string(policy=default)) to_encrypt = PGPMessage.new(mpplain.as_string(policy=default))
encrypted: PGPMessage = self.key.encrypt(to_encrypt) encrypted: PGPMessage = self.key.encrypt(to_encrypt)
encrypted |= pgp_sign(self.domain, encrypted) encrypted |= pgp_sign(self.domain, encrypted)
@ -248,30 +201,6 @@ Encrypt live everybody is.
class EasyWksError(BaseException): class EasyWksError(BaseException):
MAIL_TEXT = '''Hi there!
This is the EasyWKS system at {domain}.
An error has occurred while processing your request.
{message}
If this error persists, please contact your administrator for help.
For more information on WKD and WKS see:
https://gnupg.org/faq/wkd.html
https://gnupg.org/faq/wks.html
Regards
EasyWKS
--
Dance like nobody is watching.
Encrypt live everybody is.
'''
def __init__(self, msg: str, ): def __init__(self, msg: str, ):
super().__init__() super().__init__()
self._msg = msg self._msg = msg
@ -281,8 +210,12 @@ Encrypt live everybody is.
def create_message(self, submitter_addr: str, submission_addr: str) -> MIMEText: def create_message(self, submitter_addr: str, submission_addr: str) -> MIMEText:
domain = submission_addr.split('@', 1)[1] domain = submission_addr.split('@', 1)[1]
payload = EasyWksError.MAIL_TEXT.format(domain=domain, message=self._msg) mail_text = render_message('error',
email = MIMEText(payload) domain=domain,
sender=submitter_addr,
submission=submission_addr,
error=self._msg)
email = MIMEText(mail_text)
email['Subject'] = 'An error has occurred while processing your request' email['Subject'] = 'An error has occurred while processing your request'
email['From'] = submission_addr email['From'] = submission_addr
email['To'] = submitter_addr email['To'] = submitter_addr

View file

@ -42,6 +42,28 @@ lmtpd:
host: "::1" host: "::1"
port: 8024 port: 8024
# You can override the mail response templates with your own text.
# The following templates can be overridden:
# - "header": Placed in front of every message.
# - "footer": Appended to every message.
# - "confirm": Sent with the confirmation request.
# - "done": Sent after a key was published.
# - "error": Sent when an error occurs.
# The following placeholders can be used (enclosed in curly braces):
# - {domain}: The email domain for with the request is processed.
# - {sender}: The submitter's mail address.
# - {submission}: The submission address.
# When overriding the "error" template, theres an additional
# placeholder you can use:
# - {error}: The error message.
#responses:
# error: |
# An error has occurred while processing your request:
#
# {error}
#
# If this error persists, please contact admin@example.org for help.
# Every domain served by EasyWKS must be listed here # Every domain served by EasyWKS must be listed here
domains: domains:
# Defaults are gpgwks@<domain> and no password protection. # Defaults are gpgwks@<domain> and no password protection.