Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
272 additions
and
232 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
!coverage.py: This is a private format, don't read it directly!{"lines":{"/Users/christopherdavies/maildown/maildown/__init__.py":[1,4,5],"/Users/christopherdavies/maildown/maildown/application.py":[1,2,4,6,7,8],"/Users/christopherdavies/maildown/maildown/commands.py":[1,2,5,14,16,39,45,47,61,72,74,17,18,19,20,21,23,24,26,27,29,30,32,33,35,36,48,49,51,52,55,56,75,76,78,79,80,81,83,87,94,95,96,97,98,99,101,102,104,105,84,85,88,89,90,92],"/Users/christopherdavies/maildown/maildown/utilities.py":[1,2,3,4,5,6,7,8,11,25,46,47,71,83,100,101,102,103,104,171,15,17,18,19,20,21,35,36,38,39,41,42,58,59,60,61,62,64,65,66,67,68,75,76,77,78,79,80,92,93,94,95,96,131,132,133,135,136,137,138,139,140,147,153,154,157,158,159,160,161,186,187,189,193,210,211,190,191,194,196,197,198,199,200,201,202,203,205],"/Users/christopherdavies/maildown/maildown/renderer.py":[1,2,3,4,5,6,7,8,11,14,16,17,28,18,19,20,21,22,41,42,44,45,47,48,50,52,53,54,55,56,57,60,61]}} | ||
!coverage.py: This is a private format, don't read it directly!{"lines":{"/Users/christopherdavies/maildown/maildown/__init__.py":[1,4,5],"/Users/christopherdavies/maildown/maildown/application.py":[1,2,4,6,7,8],"/Users/christopherdavies/maildown/maildown/commands.py":[1,2,5,8,16,18,36,43,45,67,80,82,19,20,26,27,28,29,30,32,33,21,22,23,46,47,48,53,55,57,58,61,62,49,50,51,83,84,89,91,92,94,95,96,97,99,100,101,105,109,116,117,118,119,120,121,122,124,125,127,128,106,107,102,103,110,111,112,114,85,86,87],"/Users/christopherdavies/maildown/maildown/backends/__init__.py":[1],"/Users/christopherdavies/maildown/maildown/backends/aws.py":[1,2,3,4,5,6,9,10,14,15,16,17,18,54,68,69,80,100,102,103,73,74,75,76,77,90,91,94,95,97,98,114,115,116,117,118,120,121,122,123,124,23,24,25,27,28,29,30,31,32,39,45,46,49,50,51,56,57,58,59,60,61,62,64],"/Users/christopherdavies/maildown/maildown/backends/base.py":[1,2,5,6,9,14,20,28,29,30,32,35,38,42,43,54,55,33,7,15,16,10,11,12,17,18,21,22,23,24,25,57,58,60,64,70,71,61,62,65,67],"/Users/christopherdavies/maildown/maildown/utilities.py":[1,2,3,4,7,19,11,12,13,14,15,16,28,29,30,31,32],"/Users/christopherdavies/maildown/maildown/renderer.py":[1,2,3,4,5,6,7,8,11,14,16,17,26,18,19,20,21,22,39,40,42,43,45,46,48,50,51,52,53,54,55,58,59]}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from maildown.backends.aws import AwsBackend # noqa: F401 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
from typing import Optional | ||
import os | ||
import configparser | ||
from maildown.backends.base import BaseBackend | ||
import boto3 | ||
from botocore.exceptions import ClientError | ||
|
||
|
||
class AwsBackend(BaseBackend): | ||
name = "aws" | ||
|
||
def login( # type: ignore | ||
self, | ||
access_key: Optional[str] = None, | ||
secret_key: Optional[str] = None, | ||
region_name: str = "us-east-1", | ||
aws_config_file: str = os.path.expanduser("~/.aws/credentials"), | ||
) -> None: | ||
""" | ||
Retrieves, checks and stores AWS credentials to the maildown config file. Credentials are either | ||
taken from the direct parameters, the environmental variables or the .aws/credentials file | ||
""" | ||
if not any([access_key, secret_key]): | ||
access_key = os.environ.get("AWS_ACCESS_KEY_ID") | ||
secret_key = os.environ.get("AWS_SECRET_ACCESS_KEY") | ||
|
||
if not any([access_key, secret_key]): | ||
config = configparser.ConfigParser() | ||
config.read(aws_config_file) | ||
try: | ||
access_key = config["default"].get("aws_access_key_id") | ||
secret_key = config["default"].get("aws_secret_access_key") | ||
|
||
except KeyError: | ||
raise KeyError( | ||
f"Cannot find expected keys in config file stored at {aws_config_file}" | ||
) | ||
|
||
if not all([access_key, secret_key]): | ||
raise AttributeError( | ||
"No credentials supplied - you must either provide the `access_key`, and `secret_key` " | ||
"values, set the environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`, or run " | ||
"`aws configure` and try again" | ||
) | ||
elif access_key and secret_key: | ||
if not self.verify_auth(access_key, secret_key, region_name): | ||
raise AttributeError("The supplied credentials are not valid") | ||
|
||
self.config["access_key"] = access_key # type: ignore | ||
self.config["secret_key"] = secret_key # type: ignore | ||
self.config["region_name"] = region_name # type: ignore | ||
|
||
def send_message( | ||
self, to: list, sender: str, html: str, content: str, subject: str | ||
): | ||
return self.client.send_email( | ||
Source=sender, | ||
Destination=dict(ToAddresses=to), | ||
Message=dict( | ||
Body=dict( | ||
Html=dict(Charset="utf-8", Data=html), | ||
Text=dict(Charset="utf-8", Data=content), | ||
), | ||
Subject=dict(Charset="utf-8", Data=subject), | ||
), | ||
) | ||
|
||
@property | ||
def client(self) -> boto3.client: | ||
""" | ||
Returns an authenticated boto3.ses client | ||
""" | ||
return boto3.client( | ||
"ses", | ||
aws_access_key_id=self.config.get("access_key"), # type: ignore | ||
aws_secret_access_key=self.config.get("secret_key"), # type: ignore | ||
region_name=self.config.get("region", "us-east-1"), # type: ignore | ||
) | ||
|
||
def verify_address(self, email: str) -> bool: | ||
""" | ||
Asks Amazon to send an email to a given email address to verify the user's ownership of that address. | ||
Email addresses must be verified by Amazon before you can send emails from them with SES | ||
### Parameters: | ||
- `email`: The email address to be verified | ||
""" | ||
addresses = self.client.list_verified_email_addresses().get( | ||
"VerifiedEmailAddresses" | ||
) | ||
|
||
if email in addresses: | ||
return True | ||
|
||
self.client.verify_email_address(EmailAddress=email) | ||
return False | ||
|
||
@staticmethod | ||
def verify_auth( | ||
access_key: str, secret_key: str, region_name: str = "us-east-1" | ||
) -> bool: | ||
""" | ||
Checks that the given credentials are valid by performing a simple call on the SES API | ||
### Parameters: | ||
- `access_key`: AWS access key | ||
- `secret_key`: AWS secret key | ||
- `region_name`: The AWS region name. Defaults to `us-east-1` | ||
""" | ||
client = boto3.client( | ||
"ses", | ||
aws_access_key_id=access_key, | ||
aws_secret_access_key=secret_key, | ||
region_name=region_name, | ||
) | ||
try: | ||
client.list_configuration_sets() | ||
return True | ||
except ClientError: | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
from typing import Optional, Any | ||
from maildown import utilities, renderer | ||
|
||
|
||
class BaseConfig(object): | ||
def __init__(self, backend): | ||
self.backend = backend | ||
|
||
def __getitem__(self, item): | ||
config = utilities.get_config() | ||
backend_config = config.get(self.backend.name, {}) | ||
return backend_config[item] | ||
|
||
def get(self, item, default: Optional[Any] = None): | ||
try: | ||
return self.__getitem__(item) | ||
except KeyError: | ||
return default | ||
|
||
def __setitem__(self, key, value): | ||
config = utilities.get_config() | ||
backend_config = config.get(self.backend.name, {}) | ||
backend_config[key] = value | ||
config[self.backend.name] = backend_config | ||
utilities.write_config(**config) | ||
|
||
|
||
class BaseBackend(object): | ||
name = "base" | ||
config = BaseConfig | ||
|
||
def __init__(self): | ||
self.config = BaseConfig(self) | ||
|
||
def login(self, *args, **kwargs): | ||
raise NotImplementedError() | ||
|
||
def verify_address(self, email: str) -> bool: | ||
raise NotImplementedError() | ||
|
||
def send_message( | ||
self, to: list, sender: str, html: str, content: str, subject: str | ||
) -> None: | ||
raise NotImplementedError() | ||
|
||
def send( | ||
self, | ||
sender: str, | ||
subject: str, | ||
to: list, | ||
content: Optional[str] = None, | ||
file_path: Optional[str] = None, | ||
context: Optional[dict] = None, | ||
theme=None, | ||
) -> None: | ||
|
||
if not context: | ||
context = {} | ||
|
||
if file_path: | ||
with open(file_path) as f: | ||
content = f.read() | ||
|
||
if content: | ||
html = renderer.generate_content(content, context=context, theme=theme) | ||
|
||
self.send_message(to, sender, html, content, subject) | ||
|
||
else: | ||
raise AttributeError( | ||
"You must provide either the content or filepath attribute" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.