-
Notifications
You must be signed in to change notification settings - Fork 21.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Amazon SES/SNS ingress to ActionMailbox #39364
Conversation
ce92975
to
93c49bf
Compare
I've just deployed an app running Rails from this branch and set up an SES/SNS topic. Confirmation and receipt both appear to be working fine. |
93c49bf
to
78e4914
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! Here’s the first round of feedback.
actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb
Outdated
Show resolved
Hide resolved
actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb
Outdated
Show resolved
Hide resolved
actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb
Outdated
Show resolved
Hide resolved
actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb
Outdated
Show resolved
Hide resolved
def notification | ||
@notification ||= JSON.parse(request.body.read) | ||
rescue JSON::ParserError => e | ||
Rails.logger.warn("Unable to parse SNS notification: #{e}") | ||
nil | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to parse the request body manually, or can we rely on Rails parsing into params
based on Content-Type
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for this is that the signature verification requires the exact structure provided in the post body. The params
object contains extra parameters (controller
, action
) that invalidate the signature. I opted to use the same object in all places rather than mixing params
and my parsed object.
Is there a way to extract just the post body JSON-extracted params from the params
object ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
params.except(:controller, :action)
think that would be clearer too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kaspth I considered this but it seems quite flaky to me. If any extra "special" parameters are added to Rails then this code will need to be updated. Since the SNS verifier depends on the exact input parameters included in the POST body, this [my implementation] seems the most correct way to do it, albeit with an extra JSON.parse
.
I'm happy to be overruled !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't recall us having added any extra default params in the last …10 years? My memory is a bit spotty beyond that, but don't think it's happened. I think the clarity of not sidestepping the default params
parsing is overall better.
Is this something we could exercise in a test? Perhaps even with just a no-op action with render json: params
and does assert_empty params.except(:controller, :action)
?
actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb
Outdated
Show resolved
Hide resolved
646b903
to
058b88d
Compare
Thanks a lot for the feedback. I have addressed all of your points. I think there is only one item outstanding (pending your feedback). Let me know if you prefer squashed commits when the PR is ready. |
I still like @georgeclaghorn's idea of a separate ConfirmationsController. I also think that would force us to consider a model extraction. Here's my take on that.# frozen_string_literal: true
module ActionMailbox
module Ingresses
class Amazon::BaseController < ActionMailbox::BaseController
before_action :set_notification
private
def set_notification
@notification = SNSNotification.new params.except(:controller, :action)
end
class SNSNotification
def initialize(params)
@params = params
end
def subscription_confirmed?
Net::HTTP.get_response(URI(params[:SubscribeURL])).code&.start_with?("2")
end
def verified?
require "aws-sdk-sns"
Aws::SNS::MessageVerifier.new.authentic?(params.to_json)
end
def topic
params[:TopicArn]
end
def message_content
params.dig(:Message, :content) if receipt?
end
private
attr_reader :params
def receipt?
params[:Type] == "Notification" || params[:notificationType] == "Received"
end
end
end
class Amazon::ConfirmationsController < Amazon::BaseController
def create
if @notification.subscription_confirmed?
head :ok
else
Rails.logger.error "SNS subscription confirmation request rejected."
head :unprocessable_entity
end
end
end
class Amazon::InboundEmailsController < Amazon::BaseController
before_action :ensure_message_content, :ensure_valid_topic, :ensure_verified
def create
ActionMailbox::InboundEmail.create_and_extract_message_id! @notification.message_content
end
private
def ensure_message_content
head :bad_request if @notification.message_content.blank?
end
def ensure_valid_topic
unless @notification.topic.in? Array(ActionMailbox.amazon.subscribed_topics)
Rails.logger.warn "Ignoring unknown topic: #{@notification.topic}"
head :unauthorized
end
end
def ensure_verified
head :unathorized unless @notification.verified?
end
end
end
end |
ab3448f
to
cf07c19
Compare
@kaspth Thanks - agree this is much nicer. One minor point is that I think @georgeclaghorn suggested separate actions and not necessarily controllers (unless I have missed a message). I think this is better either way but wanted to highlight just in case this made any difference. I'll update to reflect your suggested changes tomorrow if there's no further discussion needed. |
George suggested a separate controller in #39364 (comment) that's where I got the name from 😄 |
Sorry, I think “the different actions” in my comment may have confused. I meant “the two different actions in the separate controllers.” |
9c0d17c
to
a1e7579
Compare
@kaspth @georgeclaghorn Thanks both for the feedback. I have adapted Kasper's refactor and addressed a couple of issues. A couple of things I think worth noting:
Let me know if any changes are needed. |
@bobf, I'd suggest adding some form of documentation, alerting users not to enable the Here is the Amazon documntation link that finally explained this. |
The change is great! For what it's worth, I can confirm that the code worked as expected 🥇. Thank you for implementing this. |
Really looking forward to this being released! Thanks for your great work @bobf |
@georgeclaghorn Is there anything here that needs changing ? I have a conflict with the main branch but would rather wait until I had some feedback before fixing in case this gets left out in the cold. The conflict is only tiny in this case so will be no trouble to resolve but some of the tests (e.g. routing) can be a bit of a pain to deal with if things start to diverge. Let me know how you want to proceed. |
@zzak Are you able to see exactly what the error is for https://github.com/rails/rails/actions/runs/8265465928/job/22611174061?pr=39364 ? I'm not familiar with how to find the exact logs/issue is. I tried but found zilch in terms of trace. I wonder if this does belong with aws-sdk-rails rather than Rails itself 🤔 What determines what belongs inside Rails vs. not? Thanks by the way! |
@@ -1,5 +1,8 @@ | |||
# frozen_string_literal: true | |||
|
|||
gem "aws-sdk-s3" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
based on this on the storage s3 service
raise LoadError, | ||
"Could not load the 'aws-sdk-sns' gem. Ensure that you've added the gem to your Gemfile.", e.backtrace | ||
end | ||
require "aws-sdk-sns" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed this since it seemed like it wasn't being done elsewhere with these type of requirements
@ssunday Thanks again for picking this up, and apologies for the drive-by collab invite - saw that you were active and blocked by what looked like a very unamusing PR so wanted to unblock ASAP but was in the middle of a crisis at work so could only spare a few seconds to do the bare minimum. Looking forward to getting this merged, wherever it ends up ! |
@bobf Oh jeez, no worries, you gave me what I needed, haha. Thank you! Also eager to get this merged too. |
commit bf2df3a5f5cb3490213b63cf714deceb95b5a8cd Author: Bob Farrell <git@bob.frl> Date: Sun May 28 11:58:13 2023 +0100 Add Amazon SES/SNS ingress to ActionMailbox Squashed commit of the following: commit f055de1216dc56e3a0a7c664984d2c781d2650ae (HEAD -> actionmailbox-amazon-ingress) Author: Bob Farrell <git@bob.frl> Date: Sun Jun 11 08:21:37 2023 +0100 Rubocop fixes commit 2925d4b8f895f833c85fb966e250d74763355701 (actionmailbox-amazon-ingress) Author: Bob Farrell <git@bob.frl> Date: Sun Jun 11 08:09:36 2023 +0100 Apply changes from PR feedback from @rafaelfranca Fix scoping of documentation comment for AmazonSes module. Remove duplicate entry iin documentation. Provide description of AWS SES fixture signing Rake task. Remove obsolete configuration definition. Move `aws-sdk-s3` loader to test helper. Move `aws-sdk-sns` loader to top of `ActionMailbox::Ingresses::AmazonSes::SnsNotification` file and add LoadError message. commit 1453a5f Merge: a56e22d d75fdd1 Author: Bob Farrell <git@bob.frl> Date: Sun Jan 22 14:23:43 2023 +0000 Merge branch 'main' into actionmailbox-amazon-ingress commit a56e22d Merge: ff285d5 6d91791 Author: Bob Farrell <git@bob.frl> Date: Sat Jan 21 20:17:08 2023 +0000 Merge branch 'main' into actionmailbox-amazon-ingress commit ff285d5 Author: Bob Farrell <git@bob.frl> Date: Sat Jan 21 16:47:00 2023 +0000 Code review feedback from @jeremy Rename ingress from `:amazon` to `:amazon_ses` for futureproofing. Misc. refactoring (create separate controller for subscriptions, handle unsubscribe notifications, improve robustness of JSON parsing, etc.). Improve documentation and CHANGELOG for clarity. Require SES emails to be saved to S3 to allow > 150k email content size. commit 6102329 Merge: 10f2a49 6bace37 Author: Bob Farrell <git@bob.frl> Date: Sat Jan 21 13:39:58 2023 +0000 Merge branch 'main' into actionmailbox-amazon-ingress commit 10f2a49 Author: Chris Ortman <chris-ortman@uiowa.edu> Date: Wed Dec 2 14:15:12 2020 -0600 Fixes style offenses commit a637f7d Author: Chris Ortman <chris-ortman@uiowa.edu> Date: Wed Dec 2 10:19:00 2020 -0600 Prevent constant caching due to usage in routes. This fixes the zietwerk tests that checks for unloadable constants. commit 168bcd6 Author: Chris Ortman <chris-ortman@uiowa.edu> Date: Tue Nov 17 11:22:25 2020 -0600 Adds support for SES message delivery via S3 SES supports embedding messages in the SNS notification or storing the message contents in S3 for later retrieval. Messages embedded in SNS notifications have a maximum size of 150kb [source](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-action-sns.html) Messages embedded in SNS also do not support attachments. In order to support large messages and attachments the mail message can be stored in S3 and retrieved using the url embedded in the SNS notification. commit 15127be Author: Chris Ortman <chris-ortman@uiowa.edu> Date: Tue Nov 17 11:18:23 2020 -0600 Add rake task for signing AWS fixtures `rake sign_aws_fixtures` Will update the signature in the AWS fixtures using our own public and private key files This allows the fixture files to be created by hand rather than captured from real AWS traffic because the signature no longer 'needs amazon's private key Keyfiles can be rotated by deleting them and re-running the task commit 4c0108a Author: Chris Ortman <chris-ortman@uiowa.edu> Date: Wed Aug 26 15:03:18 2020 -0500 Deal with text/plain content type from AWS Because AWS SNS only allows you to specify a single endpoint for a subscription, the same URL must be used to handle both confirmation and message handling. In order to correctly route the request to the right controller for each of these scenarios we need to look at the type of the message that AWS is giving us. Unfortunately, AWS sends these messages as text/plain so we can't rely on rails to parse the JSON and need to instead look at headers. Because the mimetype is text/plain we must need to manually parse the SNS body instead of relying on rails params commit 30de80d Author: Bob Farrell <git@bob.frl> Date: Sun Jun 21 10:32:17 2020 +0100 Remove misplaced markdown commit 25f9255 Author: Bob Farrell <git@bob.frl> Date: Sun Jun 21 10:28:31 2020 +0100 Warn about Amazon SES "raw message delivery" option Add documentation to explain that "raw message delivery" in AWS should not be enabled for Amazon SES/SNS ActionMailbox integration. As suggested by @mwesigwa: rails#39364 (comment) commit 2e43d84 Author: Bob Farrell <git@bob.frl> Date: Thu May 28 15:04:29 2020 +0100 Refactor Amazon ingress logic to separate controllers Fix misc. routes-related Railties tests. commit 62b931a Author: Bob Farrell <git@bob.frl> Date: Mon May 25 21:12:39 2020 +0100 Fix Railties routing tests Include new Amazon ActionMailbox `subscribe` action. commit ddc2a05 Author: Bob Farrell <git@bob.frl> Date: Mon May 25 20:32:42 2020 +0100 Simplify Amazon ActionMailbox testing Use Rails `as: :json` instead of manually encoding/setting headers. commit 5881ba7 Author: Bob Farrell <git@bob.frl> Date: Sat May 23 15:51:38 2020 +0100 Use constraints to separate Amazon notification types commit c0e9867 Author: Bob Farrell <git@bob.frl> Date: Sat May 23 14:59:56 2020 +0100 Misc documentation updates Addresses PR feedback: rails#39364 (comment) rails#39364 (comment) commit 8dddbe7 Author: bobf <robertanthonyfarrell@gmail.com> Date: Sat May 23 14:33:16 2020 +0100 Update actionmailbox/config/routes.rb Co-authored-by: George Claghorn <george.claghorn@gmail.com> commit cfd13e8 Author: bobf <robertanthonyfarrell@gmail.com> Date: Sat May 23 14:32:58 2020 +0100 Update actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb Co-authored-by: George Claghorn <george.claghorn@gmail.com> commit a9020f4 Author: bobf <robertanthonyfarrell@gmail.com> Date: Sat May 23 14:32:39 2020 +0100 Update actionmailbox/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb Co-authored-by: George Claghorn <george.claghorn@gmail.com> commit 266203c Author: bobf <robertanthonyfarrell@gmail.com> Date: Sat May 23 14:32:23 2020 +0100 Update actionmailbox/actionmailbox.gemspec Co-authored-by: George Claghorn <george.claghorn@gmail.com> commit 8ae4bad Author: Chris Ortman <chris-ortman@uiowa.edu> Date: Thu Dec 3 08:14:04 2020 -0600 Add Amazon SES/SNS ingress to ActionMailbox
a722b28
to
0bf8300
Compare
0bf8300
to
571e623
Compare
I moved the non-controller files to lib and lazy-loaded the file so rails doesn't melt on launch if the I haven't ever tested this code in the wild to make sure it works e2e, assuming test and previous work covers that. Added myself as part of the authors for this work 😂 |
Failure looks flaky, when I rebase next I assume it will resolve. |
@bobf Okay, I have confirmed this is working in the real world haha (without doing a rebase). Can you re-request review from folks? I can figure out how to contact people if not. |
@bobf No problem! For reviewers: the CI failure definitely looks flaky/unrelated. I can rebase if everything else looks OK. |
Thank you for the pull request and sorry for the delay on reaching a decision. After talking with other members of the Rails Core, I decided to not accept this pull request. We don't use Amazon SES/SNS in our applications and I don't feel we are the best maintainers for this code. This could live in a plugin for Rails like the one the author of this pull request already published.. We are happy to improve the system to allow plugins like those to exist if it isn't possible right now, but looking at the linked library it seems it already is. Thank you again for you contributions to the community. |
Just under a week short of the pull request's 4th birthday. :) Thanks for considering the PR and the explanation, I understand you have to limit inflation to keep the framework manageable. ActionMailbox is indeed quite well suited for extending through plugins so I'm happy to go that route. Have a nice week ! |
@bobf I'd love to get this into aws-sdk-rails. Would you like to open a PR and I can help shepherd that in? |
@mullermp I think that would be a sensible path forward here, and I have no objections at all. Maybe pick this up in this conversation: bobf/action_mailbox_amazon_ingress#14 (comment) - @ssunday has access to that repository so I believe would be able to make PRs, but I'm happy to help if needed, just might be a little slow to respond this week so trying to prevent myself being a blocker. Thanks for your interest and enthusiasm, it sounds like ActionMailbox SES integration will find a good home. :) |
Summary
Provides an ingress for Amazon SES/SNS.
Includes:
/rails/action_mailbox/amazon/inbound_emails
ingress routeTests for all of the above are included.
Other Information
This commit (f480cfa) from @georgeclaghorn states that SES integration was removed and will be re-written for 6.1.
I needed this sooner so I wrote a gem here: https://github.com/bobf/action_mailbox_amazon_ingress
I emailed George to discuss and he suggested a PR. I have adapted the tests from RSpec to Minitest.
Please let me know if any adjustments are needed.