Add responses
config option which allows customizing the response message templates
This commit is contained in:
parent
adc699aeed
commit
f9ddba422c
6 changed files with 138 additions and 86 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -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
|
||||||
|
|
||||||
|
|
26
README.md
26
README.md
|
@ -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:
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
|
|
||||||
__version__ = '0.1.4'
|
__version__ = '0.1.5'
|
||||||
|
|
|
@ -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}'
|
||||||
|
|
101
easywks/types.py
101
easywks/types.py
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue