Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Updating

  • Loading branch information...
commit dd73f16a6c54a79744a0c383162881f67b9eda03 1 parent fb1b4e5
@mikel authored
View
16 app/models/delivery.rb
@@ -1,7 +1,23 @@
class Delivery < ActiveRecord::Base
+ require 'cgi'
+
belongs_to :recipient
belongs_to :user
belongs_to :mailout
+ def unique_path
+ path = Crypto::AES256.encrypt("#{recipient_id}|#{user_id}|#{mailout_id}|#{Time.now.to_i}")
+ CGI.escape(path)
+ end
+
+ def Delivery.for(unique_path)
+ path = CGI.unescape(unique_path)
+ string = Crypto::AES256.decrypt(path)
+ recipient_id, user_id, mailout_id, time = string.split("|")
+ Delivery.find(:first, :conditions => {:recipient_id => recipient_id,
+ :user_id => user_id,
+ :mailout_id => mailout_id})
+ end
+
end
View
4 app/models/mailer.rb
@@ -27,10 +27,10 @@ def mailout(recipient, mailout)
if mailout.multipart?
content_type "multipart/alternative"
part "text/html" do |p|
- p.body = render_message("welcome_email_html", :message => mailout.html_part)
+ p.body = render_message("mailout_html", :message => mailout.html_part)
end
part "text/plain" do |p|
- p.body = render_message("welcome_email_plain", :message => mailout.plain_part)
+ p.body = render_message("mailout_plain", :message => mailout.plain_part)
end
else
body = mailout.plain_part
View
6 app/models/mailout.rb
@@ -96,7 +96,11 @@ def subscribers
recipient_ids.flatten!
recipient_ids.uniq!
delivered_ids = Delivery.find(:all, :conditions => {:mailout_id => self.id}, :select => 'recipient_id').map { |a| a.recipient_id}
- Recipient.find(:all, :conditions => ['id IN (?) and id NOT IN (?)', recipient_ids, delivered_ids], :order => "domain")
+ if delivered_ids.empty?
+ Recipient.find(:all, :conditions => ['id IN (?)', recipient_ids], :order => "domain")
+ else
+ Recipient.find(:all, :conditions => ['id IN (?) and id NOT IN (?)', recipient_ids, delivered_ids], :order => "domain")
+ end
end
def from
View
0  app/views/mailer/mailout_html.erb
No changes.
View
0  app/views/mailer/mailout_plain.erb
No changes.
View
1  config/initializers/crypto_config.rb
@@ -0,0 +1 @@
+Crypto::AES256.key = 'This is a stupidly short AES 256 string'
View
16 features/recipients_view_email_in_browser.feature
@@ -9,7 +9,7 @@ Feature: Viewing an email via a web server
And there is one plain text mailout called "My Mailout" to be sent immediately
And the mailout "My Mailout" has the recipient "Mikel Lindsaar"
When I tell the system to send
- Then there should be one delivery
+ Then there should be 1 delivery
And the delivery body should have "View this email in your web browser"
Scenario: Multipart email
@@ -17,8 +17,10 @@ Feature: Viewing an email via a web server
And there is a recipient I added in the system called "Mikel Lindsaar"
And there is one multipart mailout called "My Mailout" to be sent immediately
And the mailout "My Mailout" has the recipient "Mikel Lindsaar"
+ And the mailout "My Mailout" has the text body "This is an email."
+ And the mailout "My Mailout" has the html body "<p>This is an email.</p>"
When I tell the system to send
- Then there should be one delivery
+ Then there should be 1 delivery
And the delivery body should have "View this email in your web browser"
Scenario: Sending an email to one person
@@ -27,8 +29,8 @@ Feature: Viewing an email via a web server
And there is one plain text mailout called "My Mailout" to be sent immediately
And the mailout "My Mailout" has the recipient "Mikel Lindsaar"
When I tell the system to send
- Then there should be one delivery
- And the delivery body should have a url to view the email especially for "Mikel Lindsaar"
+ Then there should be 1 delivery
+ And the delivery for "Mikel Lindsaar" should have a url to view the email especially for "Mikel Lindsaar"
Scenario: Sending an email to more than one person
Given I am logged in
@@ -38,6 +40,6 @@ Feature: Viewing an email via a web server
And the mailout "My Mailout" has the recipient "Mikel Lindsaar"
And the mailout "My Mailout" has the recipient "Ada Lindsaar"
When I tell the system to send
- Then there should be one delivery
- And the delivery body should have a url to view the email especially for "Mikel Lindsaar"
- And the delivery body should have a url to view the email especially for "Ada Lindsaar"
+ Then there should be 2 deliveries
+ And the delivery for "Mikel Lindsaar" should have a url to view the email especially for "Mikel Lindsaar"
+ And the delivery for "Ada Lindsaar" should have a url to view the email especially for "Ada Lindsaar"
View
4 features/sending_mailout.feature
@@ -47,9 +47,9 @@ Feature: Sending a Mailout
Given I am logged in
And there is a recipient I added in the system called "Mikel Lindsaar"
And there is one mailout called "My Mailout" to be sent immediately
- And the mailout "My Mailout" has a recipient called "Mikel Lindsaar"
+ And the mailout "My Mailout" has the recipient "Mikel Lindsaar"
And there is one mailout called "My Other Mailout" to be sent one day from now
- And the mailout "My Other Mailout" has a recipient called "Mikel Lindsaar"
+ And the mailout "My Other Mailout" has the recipient "Mikel Lindsaar"
When I tell the system to send
Then there should be 1 delivery
And the recipient "Mikel Lindsaar" should have been delivered one email
View
17 features/step_definitions/recipient_viewing_tracking_steps.rb
@@ -0,0 +1,17 @@
+Given /^there is one plain text mailout called "([^\"]*)" to be sent immediately$/ do |title|
+ message = Factory(:message, :title => title, :source => 'plain', :plain_part => "This is a plain text email")
+ Factory(:mailout, :message => message, :title => title, :date_scheduled => 1.day.ago, :aasm_state => 'confirmed')
+end
+
+When /^I go and visit the url for the "([^\"]*)" email for "([^\"]*)"$/ do |title, name|
+ pending
+end
+
+Then /^the system should record that "([^\"]*)" read the email$/ do |name|
+ pending
+end
+
+Given /^there is one multipart mailout called "([^\"]*)" to be sent immediately$/ do |title|
+ message = Factory(:message, :title => title, :source => 'html', :multipart => true, :html_part => "<h1>This is a html part</h1>", :plain_part => "This is a plain text email")
+ Factory(:mailout, :message => message, :title => title, :date_scheduled => 1.day.ago, :aasm_state => 'confirmed')
+end
View
9 features/step_definitions/recipients_view_email_in_browser_steps.rb
@@ -0,0 +1,9 @@
+Then /^the delivery body should have "([^\"]*)"$/ do |text|
+ ActionMailer::Base.deliveries.first.to_s.should =~ /#{text}/m
+
+end
+
+Then /^the delivery body should have a url to view the email especially for "([^\"]*)"$/ do |name|
+ pending
+end
+
View
11 features/step_definitions/sending_mailout_steps.rb
@@ -18,8 +18,11 @@
mailout = Factory(:mailout, :title => title, :date_scheduled => 1.day.ago, :aasm_state => 'confirmed')
end
-Given /^the mailout "([^\"]*)" has the recipient "([^\"]*)"$/ do |arg1, arg2|
- pending
+Given /^the mailout "([^\"]*)" has the recipient "([^\"]*)"$/ do |title, name|
+ mailout = Mailout.find_by_title(title)
+ given, family = name.split(' ')
+ recipient = Recipient.find(:first, :conditions => {:given_name => given, :family_name => family})
+ mailout.recipients << recipient
end
Then /^there should be (\d+) delivery$/ do |number|
@@ -65,6 +68,6 @@
mailout.save!
end
-Given /^there is one mailout called "([^\"]*)" to be sent one day from now$/ do |arg1|
- pending
+Given /^there is one mailout called "([^\"]*)" to be sent one day from now$/ do |title|
+ mailout = Factory(:mailout, :title => title, :date_scheduled => 1.day.from_now, :aasm_state => 'confirmed')
end
View
3  features/step_definitions/unsubscribe_steps.rb
@@ -0,0 +1,3 @@
+Then /^there should be an unsubscribe link for "([^\"]*)" in the email$/ do |arg1|
+ pending
+end
View
70 lib/crypto/aes256.rb
@@ -0,0 +1,70 @@
+require "openssl"
+
+module Crypto
+ # Copyright (c) 2007 Ben Johnson of Binary Logic (binarylogic.com)
+ #
+ # Permission is hereby granted, free of charge, to any person obtaining
+ # a copy of this software and associated documentation files (the
+ # "Software"), to deal in the Software without restriction, including
+ # without limitation the rights to use, copy, modify, merge, publish,
+ # distribute, sublicense, and/or sell copies of the Software, and to
+ # permit persons to whom the Software is furnished to do so, subject to
+ # the following conditions:
+ #
+ # The above copyright notice and this permission notice shall be
+ # included in all copies or substantial portions of the Software.
+ #
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ # This encryption method is reversible if you have the supplied key. So in order to use this encryption method you must supply it with a key first.
+ # In an initializer, or before your application initializes, you should do the following:
+ #
+ # Authlogic::CryptoProviders::AES256.key = "my really long and unique key, preferrably a bunch of random characters"
+ #
+ # My final comment is that this is a strong encryption method, but its main weakness is that its reversible. If you do not need to reverse the hash
+ # then you should consider Sha512 or BCrypt instead.
+ #
+ # Keep your key in a safe place, some even say the key should be stored on a separate server.
+ # This won't hurt performance because the only time it will try and access the key on the separate server is during initialization, which only
+ # happens once. The reasoning behind this is if someone does compromise your server they won't have the key also. Basically, you don't want to
+ # store the key with the lock.
+ class AES256
+ class << self
+ attr_writer :key
+
+ def encrypt(*tokens)
+ aes.encrypt
+ aes.key = @key
+ [aes.update(tokens.join) + aes.final].pack("m").chomp
+ end
+
+ def decrypt(crypted)
+ aes.decrypt
+ aes.key = @key
+ (aes.update(crypted.unpack("m").first) + aes.final)
+ rescue OpenSSL::CipherError
+ false
+ end
+
+ def matches?(crypted, *tokens)
+ aes.decrypt
+ aes.key = @key
+ (aes.update(crypted.unpack("m").first) + aes.final) == tokens.join
+ rescue OpenSSL::CipherError
+ false
+ end
+
+ private
+ def aes
+ raise ArgumentError.new("You must provide a key like #{name}.key = my_key before using the #{name}") if @key.blank?
+ @aes ||= OpenSSL::Cipher::Cipher.new("AES-256-ECB")
+ end
+ end
+ end
+end
View
36 spec/models/delivery_spec.rb
@@ -33,4 +33,40 @@
@delivery.user.should == @user
end
end
+
+ describe "providing unique URLs for each delivery" do
+
+ it "should provide a hash for each user" do
+ delivery = Delivery.new(:user_id => 1,
+ :recipient_id => 2,
+ :mailout_id => 3)
+ delivery.unique_path.should_not be_blank
+ end
+
+ it "should provide a unique path that is URL safe" do
+ delivery = Delivery.new(:user_id => 1,
+ :recipient_id => 2,
+ :mailout_id => 3)
+ (delivery.unique_path =~ /[\\\/\s]/).should be_nil
+ end
+
+ it "should provide an unique hash" do
+ delivery1 = Delivery.new(:user_id => 1,
+ :recipient_id => 2,
+ :mailout_id => 3)
+ delivery2 = Delivery.new(:user_id => 1,
+ :recipient_id => 3,
+ :mailout_id => 3)
+ delivery1.unique_path.should_not == delivery2.unique_path
+ end
+
+ it "should be able return the delivery from a supplied path" do
+ delivery = Delivery.create(:user_id => 1,
+ :recipient_id => 2,
+ :mailout_id => 3)
+ found = Delivery.for(delivery.unique_path)
+ found.should == delivery
+ end
+ end
+
end
View
1  spec/spec.opts
@@ -2,4 +2,3 @@
--format progress
--loadby mtime
--reverse
---drb
Please sign in to comment.
Something went wrong with that request. Please try again.