From 27e74ca0c2336baa834f405890b705fc28213338 Mon Sep 17 00:00:00 2001 From: Raymond Jessop Date: Fri, 5 Dec 2025 01:45:49 -0600 Subject: [PATCH] feat: add IAM role support and remove django.contrib.sites dependency - Add optional IAM role authentication for AWS SES backend - Remove AwsSesSettings model and sites framework dependency - Maintain backward compatibility with access key authentication - Update backend to conditionally use credentials when provided - Remove sites framework from installation requirements - Update documentation with IAM role configuration examples BREAKING CHANGE: Removes django.contrib.sites dependency and AwsSesSettings model --- README.md | 14 +++++++++----- django_aws_ses/backends.py | 26 ++++++++++++++++---------- django_aws_ses/models.py | 13 +------------ django_aws_ses/settings.py | 2 +- django_aws_ses/tests.py | 2 +- django_aws_ses/views.py | 20 +++++++++++++------- 6 files changed, 41 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index a545915..241c7d2 100644 --- a/README.md +++ b/README.md @@ -70,14 +70,18 @@ SITE_ID = 1 Configure AWS SES credentials and the email backend: +### Option 1: IAM Role (Recommended for AWS environments) ```python -AWS_SES_ACCESS_KEY_ID = 'your-access-key-id' # Replace with your AWS IAM credentials -AWS_SES_SECRET_ACCESS_KEY = 'your-secret-access-key' -AWS_SES_REGION_NAME = 'us-east-1' # Adjust to your AWS SES region +# No AWS credentials needed in settings +AWS_SES_REGION_NAME = 'us-east-1' AWS_SES_REGION_ENDPOINT = 'https://email.us-east-1.amazonaws.com' - EMAIL_BACKEND = 'django_aws_ses.backends.SESBackend' -DEFAULT_FROM_EMAIL = 'no-reply@yourdomain.com' # Verified in AWS SES +DEFAULT_FROM_EMAIL = 'no-reply@yourdomain.com' +``` +### Option 2: Access Keys +``` +AWS_SES_ACCESS_KEY_ID = 'your-access-key-id' +AWS_SES_SECRET_ACCESS_KEY = 'your-secret-access-key' ``` Optional: Enable debugging logs for troubleshooting: diff --git a/django_aws_ses/backends.py b/django_aws_ses/backends.py index d53c52c..e53e3d6 100644 --- a/django_aws_ses/backends.py +++ b/django_aws_ses/backends.py @@ -89,9 +89,6 @@ class SESBackend(BaseEmailBackend): self.dkim_selector = dkim_selector or settings.DKIM_SELECTOR self.dkim_headers = dkim_headers or settings.DKIM_HEADERS - if not (self._access_key_id and self._access_key): - raise ImproperlyConfigured("AWS SES credentials are required.") - self.connection = None def open(self): @@ -104,13 +101,22 @@ class SESBackend(BaseEmailBackend): return False try: - self.connection = boto3.client( - 'ses', - aws_access_key_id=self._access_key_id, - aws_secret_access_key=self._access_key, - region_name=self._region_name, - endpoint_url=self._endpoint_url, - ) + + # Build client kwargs conditionally + client_kwargs = { + 'service_name': 'ses', + 'region_name': self._region_name, + 'endpoint_url': self._endpoint_url, + } + + # Only add credentials if provided + if self._access_key_id and self._access_key: + client_kwargs.update({ + 'aws_access_key_id': self._access_key_id, + 'aws_secret_access_key': self._access_key, + }) + + self.connection = boto3.client(**client_kwargs) return True except Exception as e: logger.error(f"Failed to connect to SES: {e}") diff --git a/django_aws_ses/models.py b/django_aws_ses/models.py index 854e001..16c7184 100644 --- a/django_aws_ses/models.py +++ b/django_aws_ses/models.py @@ -4,7 +4,7 @@ import logging from django.conf import settings from django.contrib.auth import get_user_model -from django.contrib.sites.models import Site + from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver @@ -16,16 +16,6 @@ from django.core.signing import Signer, BadSignature User = get_user_model() logger = logging.getLogger(__name__) -@receiver(post_save, sender=Site, dispatch_uid="update_awsses_settings") -def update_awsses_settings(sender, instance, created, **kwargs): - """Create or update AwsSesSettings when a Site is saved.""" - try: - if created: - AwsSesSettings.objects.create(site=instance) - instance.awssessettings.save() - except Exception as e: - logger.error(f"Failed to save AwsSesSettings for site {instance.id}: {e}") - @receiver(post_save, sender=User, dispatch_uid="update_awsses_user") def update_awsses_user(sender, instance, created, **kwargs): """Create or update AwsSesUserAddon when a User is saved.""" @@ -38,7 +28,6 @@ def update_awsses_user(sender, instance, created, **kwargs): class AwsSesSettings(models.Model): """AWS SES configuration settings for a site.""" - site = models.OneToOneField(Site, on_delete=models.CASCADE, related_name='awssessettings') access_key = models.CharField(max_length=255, blank=True, null=True) secret_key = models.CharField(max_length=255, blank=True, null=True) region_name = models.CharField(max_length=255, blank=True, null=True) diff --git a/django_aws_ses/settings.py b/django_aws_ses/settings.py index 9f2e378..248da5e 100644 --- a/django_aws_ses/settings.py +++ b/django_aws_ses/settings.py @@ -2,7 +2,7 @@ import logging import os from django.conf import settings as django_settings -from django.contrib.sites.models import Site + from django.core.exceptions import ImproperlyConfigured from django.db.utils import DatabaseError diff --git a/django_aws_ses/tests.py b/django_aws_ses/tests.py index 20627d8..b0b25aa 100644 --- a/django_aws_ses/tests.py +++ b/django_aws_ses/tests.py @@ -1,7 +1,7 @@ from django.test import TestCase, RequestFactory, override_settings from django.core.mail import EmailMessage from django.contrib.auth import get_user_model -from django.contrib.sites.models import Site + from django.urls import reverse from django.utils.http import urlsafe_base64_encode from django.utils.encoding import force_bytes diff --git a/django_aws_ses/views.py b/django_aws_ses/views.py index 84e6993..3930177 100644 --- a/django_aws_ses/views.py +++ b/django_aws_ses/views.py @@ -105,13 +105,19 @@ def dashboard(request): if cached_view: return cached_view - ses_conn = boto3.client( - 'ses', - aws_access_key_id=settings.ACCESS_KEY, - aws_secret_access_key=settings.SECRET_KEY, - region_name=settings.AWS_SES_REGION_NAME, - endpoint_url=settings.AWS_SES_REGION_ENDPOINT, - ) + client_kwargs = { + 'service_name': 'ses', + 'region_name': settings.AWS_SES_REGION_NAME, + 'endpoint_url': settings.AWS_SES_REGION_ENDPOINT, + } + + if settings.ACCESS_KEY and settings.SECRET_KEY: + client_kwargs.update({ + 'aws_access_key_id': settings.ACCESS_KEY, + 'aws_secret_access_key': settings.SECRET_KEY, + }) + + ses_conn = boto3.client(**client_kwargs) try: quota_dict = ses_conn.get_send_quota()