aws-ses 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/CHANGELOG ADDED
@@ -0,0 +1,6 @@
1
+ head:
2
+ - Initial creation that supports:
3
+
4
+ * addresses.list
5
+ * addresses.verify(email)
6
+ * addresses.delete(email)
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "shoulda", ">= 0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.2"
12
+ gem "rcov", ">= 0"
13
+ gem 'flexmock', '~> 0.8.11'
14
+ end
15
+
16
+ gem 'xml-simple'
17
+ gem 'builder'
18
+ gem 'mime-types'
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ builder (3.0.0)
5
+ flexmock (0.8.11)
6
+ git (1.2.5)
7
+ jeweler (1.5.2)
8
+ bundler (~> 1.0.0)
9
+ git (>= 1.2.5)
10
+ rake
11
+ mime-types (1.16)
12
+ rake (0.8.7)
13
+ rcov (0.9.9)
14
+ shoulda (2.11.3)
15
+ xml-simple (1.0.13)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ builder
22
+ bundler (~> 1.0.0)
23
+ flexmock (~> 0.8.11)
24
+ jeweler (~> 1.5.2)
25
+ mime-types
26
+ rcov
27
+ shoulda
28
+ xml-simple
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+
2
+ Copyright (c) 2011 Drew V. Blas <drew.blas@gmail.com>
3
+ Copyright (c) 2006-2009 Marcel Molina Jr. <marcel@vernix.org>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in the
7
+ Software without restriction, including without limitation the rights to use,
8
+ copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
9
+ Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
19
+ AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,47 @@
1
+ = AWS::SES
2
+
3
+ AWS::SES is a Ruby library for Amazon's Simple Email Service's REST API (http://aws.amazon.com/ses).
4
+
5
+ == Getting started
6
+
7
+ To get started you need to require 'aws/ses':
8
+
9
+ % irb -rubygems
10
+ irb(main):001:0> require 'aws/ses'
11
+ # => true
12
+
13
+ Before you can do anything, you must establish a connection using Base.new. A basic connection would look something like this:
14
+
15
+ ses = AWS::SES::Base.new(
16
+ :access_key_id => 'abc',
17
+ :secret_access_key => '123'
18
+ )
19
+
20
+ The minimum connection options that you must specify are your access key id and your secret access key.
21
+
22
+
23
+ = Addresses
24
+
25
+ AWS::SES::Addresses provides for:
26
+ * Listing verified e-mail addresses
27
+ * Adding new e-mail addresses to verify
28
+ * Deleting verified e-mail addresses
29
+
30
+
31
+ == Contributing to aws-ses
32
+
33
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
34
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
35
+ * Fork the project
36
+ * Start a feature/bugfix branch
37
+ * Commit and push until you are happy with your contribution
38
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
39
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
40
+
41
+ == Copyright
42
+
43
+ Copyright (c) 2011 Drew Blas. See LICENSE for further details.
44
+
45
+ == Thanks
46
+
47
+ Special thanks to Marcel Molina Jr. for his creation of AWS::S3 which I used portions of to get things working.
data/README.erb ADDED
@@ -0,0 +1,25 @@
1
+ = AWS::SES
2
+
3
+ <%= docs_for['AWS::SES'] %>
4
+
5
+ = Addresses
6
+
7
+ <%= docs_for['AWS::SES::Addresses'] %>
8
+
9
+ == Contributing to aws-ses
10
+
11
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
12
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
13
+ * Fork the project
14
+ * Start a feature/bugfix branch
15
+ * Commit and push until you are happy with your contribution
16
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
17
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
18
+
19
+ == Copyright
20
+
21
+ Copyright (c) 2011 Drew Blas. See LICENSE for further details.
22
+
23
+ == Thanks
24
+
25
+ Special thanks to Marcel Molina Jr. for his creation of AWS::S3 which I used portions of to get things working.
data/Rakefile ADDED
@@ -0,0 +1,85 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'erb'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "aws-ses"
18
+ gem.homepage = "http://github.com/drewblas/aws-ses"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Client library for Amazon's Simple Email Service's REST API}
21
+ gem.description = gem.summary
22
+ gem.email = "drew.blas@gmail.com"
23
+ gem.authors = ["Drew Blas", 'Marcel Molina Jr.']
24
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
25
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
26
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
27
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
28
+ gem.add_dependency 'xml-simple'
29
+ gem.add_dependency 'builder'
30
+ gem.add_dependency 'mime-types'
31
+ end
32
+ Jeweler::RubygemsDotOrgTasks.new
33
+
34
+ require 'rake/testtask'
35
+ Rake::TestTask.new(:test) do |test|
36
+ test.libs << 'lib' << 'test'
37
+ test.pattern = 'test/**/*_test.rb'
38
+ test.verbose = true
39
+ end
40
+
41
+ require 'rcov/rcovtask'
42
+ Rcov::RcovTask.new do |test|
43
+ test.libs << 'test'
44
+ test.pattern = 'test/**/*_test.rb'
45
+ test.verbose = true
46
+ end
47
+
48
+ task :default => :test
49
+
50
+ require 'rake/rdoctask'
51
+ require File.dirname(__FILE__) + '/lib/aws/ses'
52
+
53
+ namespace :doc do
54
+ Rake::RDocTask.new do |rdoc|
55
+ rdoc.rdoc_dir = 'doc'
56
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
57
+ rdoc.title = "AWS::SES -- Support for Amazon SES's REST api #{version}"
58
+ rdoc.options << '--line-numbers' << '--inline-source'
59
+ rdoc.rdoc_files.include('README')
60
+ rdoc.rdoc_files.include('COPYING')
61
+ rdoc.rdoc_files.include('lib/**/*.rb')
62
+ end
63
+
64
+ task :rdoc => 'doc:readme'
65
+
66
+ task :refresh => :rerdoc do
67
+ system 'open doc/index.html'
68
+ end
69
+
70
+ task :readme do
71
+ require 'support/rdoc/code_info'
72
+ RDoc::CodeInfo.parse('lib/**/*.rb')
73
+
74
+ strip_comments = lambda {|comment| comment.gsub(/^# ?/, '')}
75
+ docs_for = lambda do |location|
76
+ info = RDoc::CodeInfo.for(location)
77
+ raise RuntimeError, "Couldn't find documentation for `#{location}'" unless info
78
+ strip_comments[info.comment]
79
+ end
80
+
81
+ open('README', 'w') do |file|
82
+ file.write ERB.new(IO.read('README.erb')).result(binding)
83
+ end
84
+ end
85
+ end
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ * Implement the rest of the API
2
+ * Use a better XML parser (and be consistent)
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/aws/ses.rb ADDED
@@ -0,0 +1,22 @@
1
+ %w[ base64 cgi openssl digest/sha1 net/https net/http rexml/document time ostruct ].each { |f| require f }
2
+
3
+ begin
4
+ require 'URI' unless defined? URI
5
+ rescue Exception => e
6
+ # nothing
7
+ end
8
+
9
+ begin
10
+ require 'xmlsimple' unless defined? XmlSimple
11
+ rescue Exception => e
12
+ require 'xml-simple' unless defined? XmlSimple
13
+ end
14
+
15
+ $:.unshift(File.dirname(__FILE__))
16
+ require 'ses/extensions'
17
+
18
+ require 'ses/base'
19
+ require 'ses/response'
20
+ require 'ses/exceptions'
21
+ require 'ses/version'
22
+ require 'ses/addresses'
@@ -0,0 +1,61 @@
1
+ module AWS
2
+ module SES
3
+ # AWS::SES::Addresses provides for:
4
+ # * Listing verified e-mail addresses
5
+ # * Adding new e-mail addresses to verify
6
+ # * Deleting verified e-mail addresses
7
+ class Addresses < Base
8
+ def initialize(ses)
9
+ @ses = ses
10
+ end
11
+
12
+ # List all verified e-mail addresses
13
+ #
14
+ # Usage:
15
+ # ses.addresses.list.result
16
+ # =>
17
+ # ['email1@example.com', email2@example.com']
18
+ def list
19
+ @ses.request('ListVerifiedEmailAddresses')
20
+ end
21
+
22
+ def verify(email)
23
+ @ses.request('VerifyEmailAddress',
24
+ 'EmailAddress' => email
25
+ )
26
+ end
27
+
28
+ def delete(email)
29
+ @ses.request('DeleteVerifiedEmailAddress',
30
+ 'EmailAddress' => email
31
+ )
32
+ end
33
+ end
34
+
35
+ class ListVerifiedEmailAddressesResponse < AWS::SES::Response
36
+ def result
37
+ if members = parsed['VerifiedEmailAddresses']
38
+ [members['member']].flatten
39
+ else
40
+ []
41
+ end
42
+ end
43
+ end
44
+
45
+ class VerifyEmailAddressResponse < AWS::SES::Response
46
+ end
47
+
48
+ class DeleteVerifiedEmailAddressResponse < AWS::SES::Response
49
+ def result
50
+ success?
51
+ end
52
+ end
53
+
54
+ class Base
55
+ def addresses
56
+ @addresses ||= Addresses.new(self)
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,213 @@
1
+ module AWS #:nodoc:
2
+ # AWS::SES is a Ruby library for Amazon's Simple Email Service's REST API (http://aws.amazon.com/ses).
3
+ #
4
+ # == Getting started
5
+ #
6
+ # To get started you need to require 'aws/ses':
7
+ #
8
+ # % irb -rubygems
9
+ # irb(main):001:0> require 'aws/ses'
10
+ # # => true
11
+ #
12
+ # Before you can do anything, you must establish a connection using Base.new. A basic connection would look something like this:
13
+ #
14
+ # ses = AWS::SES::Base.new(
15
+ # :access_key_id => 'abc',
16
+ # :secret_access_key => '123'
17
+ # )
18
+ #
19
+ # The minimum connection options that you must specify are your access key id and your secret access key.
20
+ module SES
21
+
22
+ API_VERSION = '2010-12-01'
23
+
24
+ DEFAULT_HOST = 'email.us-east-1.amazonaws.com'
25
+
26
+ # Encodes the given string with the secret_access_key by taking the
27
+ # hmac-sha1 sum, and then base64 encoding it. Optionally, it will also
28
+ # url encode the result of that to protect the string if it's going to
29
+ # be used as a query string parameter.
30
+ #
31
+ # @param [String] secret_access_key the user's secret access key for signing.
32
+ # @param [String] str the string to be hashed and encoded.
33
+ # @param [Boolean] urlencode whether or not to url encode the result., true or false
34
+ # @return [String] the signed and encoded string.
35
+ def SES.encode(secret_access_key, str, urlencode=true)
36
+ digest = OpenSSL::Digest::Digest.new('sha256')
37
+ b64_hmac =
38
+ Base64.encode64(
39
+ OpenSSL::HMAC.digest(digest, secret_access_key, str)).gsub("\n","")
40
+
41
+ if urlencode
42
+ return CGI::escape(b64_hmac)
43
+ else
44
+ return b64_hmac
45
+ end
46
+ end
47
+
48
+ # Generates the HTTP Header String that Amazon looks for
49
+ #
50
+ # @param [String] key the AWS Access Key ID
51
+ # @param [String] alg the algorithm used for the signature
52
+ # @param [String] sig the signature itself
53
+ def SES.authorization_header(key, alg, sig)
54
+ "AWS3-HTTPS AWSAccessKeyId=#{key}, Algorithm=#{alg}, Signature=#{sig}"
55
+ end
56
+
57
+ # AWS::SES::Base is the abstract super class of all classes who make requests against SES
58
+ class Base
59
+
60
+ attr_reader :use_ssl, :server, :proxy_server, :port
61
+
62
+ # @option options [String] :access_key_id ("") The user's AWS Access Key ID
63
+ # @option options [String] :secret_access_key ("") The user's AWS Secret Access Key
64
+ # @option options [Boolean] :use_ssl (true) Connect using SSL?
65
+ # @option options [String] :server ("email.us-east-1.amazonaws.com") The server API endpoint host
66
+ # @option options [String] :proxy_server (nil) An HTTP proxy server FQDN
67
+ # @return [Object] the object.
68
+ def initialize( options = {} )
69
+
70
+ options = { :access_key_id => "",
71
+ :secret_access_key => "",
72
+ :use_ssl => true,
73
+ :server => DEFAULT_HOST,
74
+ :path => "/",
75
+ :proxy_server => nil
76
+ }.merge(options)
77
+
78
+ @server = options[:server]
79
+ @proxy_server = options[:proxy_server]
80
+ @use_ssl = options[:use_ssl]
81
+ @path = options[:path]
82
+
83
+ raise ArgumentError, "No :access_key_id provided" if options[:access_key_id].nil? || options[:access_key_id].empty?
84
+ raise ArgumentError, "No :secret_access_key provided" if options[:secret_access_key].nil? || options[:secret_access_key].empty?
85
+ raise ArgumentError, "No :use_ssl value provided" if options[:use_ssl].nil?
86
+ raise ArgumentError, "Invalid :use_ssl value provided, only 'true' or 'false' allowed" unless options[:use_ssl] == true || options[:use_ssl] == false
87
+ raise ArgumentError, "No :server provided" if options[:server].nil? || options[:server].empty?
88
+
89
+ if options[:port]
90
+ # user-specified port
91
+ @port = options[:port]
92
+ elsif @use_ssl
93
+ # https
94
+ @port = 443
95
+ else
96
+ # http
97
+ @port = 80
98
+ end
99
+
100
+ @access_key_id = options[:access_key_id]
101
+ @secret_access_key = options[:secret_access_key]
102
+
103
+ # Use proxy server if defined
104
+ # Based on patch by Mathias Dalheimer. 20070217
105
+ proxy = @proxy_server ? URI.parse(@proxy_server) : OpenStruct.new
106
+ @http = Net::HTTP::Proxy( proxy.host,
107
+ proxy.port,
108
+ proxy.user,
109
+ proxy.password).new(options[:server], @port)
110
+
111
+ @http.use_ssl = @use_ssl
112
+
113
+ # Don't verify the SSL certificates. Avoids SSL Cert warning in log on every GET.
114
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
115
+
116
+ end
117
+
118
+ def connection
119
+ @http
120
+ end
121
+
122
+ # Make the connection to AWS EC2 passing in our request.
123
+ # allow us to have a one line call in each method which will do all of the work
124
+ # in making the actual request to AWS.
125
+ def request(action, params = {})
126
+
127
+ connection.start do
128
+ # remove any keys that have nil or empty values
129
+ params.reject! { |key, value| value.nil? or value.empty?}
130
+
131
+ timestamp = Time.now.getutc
132
+
133
+ params.merge!( {"Action" => action,
134
+ "SignatureVersion" => "2",
135
+ "SignatureMethod" => 'HmacSHA256',
136
+ "AWSAccessKeyId" => @access_key_id,
137
+ "Version" => API_VERSION,
138
+ "Timestamp" => timestamp.iso8601 } )
139
+
140
+ query = params.sort.collect do |param|
141
+ CGI::escape(param[0]) + "=" + CGI::escape(param[1])
142
+ end.join("&")
143
+
144
+ req = Net::HTTP::Post.new(@path)
145
+ req.content_type = 'application/x-www-form-urlencoded'
146
+ req['X-Amzn-Authorization'] = get_aws_auth_param(timestamp.httpdate, @secret_access_key)
147
+ req['Date'] = timestamp.httpdate
148
+ req['User-Agent'] = "github-aws-ses-ruby-gem"
149
+
150
+ response = connection.request(req, query)
151
+
152
+ # Make a call to see if we need to throw an error based on the response given by EC2
153
+ # All error classes are defined in EC2/exceptions.rb
154
+ aws_error?(response)
155
+
156
+ response_class = AWS::SES.const_get( "#{action}Response" )
157
+ return response_class.new(action, response)
158
+ end
159
+
160
+ end
161
+
162
+ # Set the Authorization header using AWS signed header authentication
163
+ def get_aws_auth_param(timestamp, secret_access_key)
164
+ encoded_canonical = SES.encode(secret_access_key, timestamp, false)
165
+ SES.authorization_header(@access_key_id, 'HmacSHA256', encoded_canonical)
166
+ end
167
+
168
+ # Raises the appropriate error if the specified Net::HTTPResponse object
169
+ # contains an AWS error; returns +false+ otherwise.
170
+ def aws_error?(response)
171
+ # return false if we got a HTTP 200 code,
172
+ # otherwise there is some type of error (40x,50x) and
173
+ # we should try to raise an appropriate exception
174
+ # from one of our exception classes defined in
175
+ # exceptions.rb
176
+ return false if response.is_a?(Net::HTTPSuccess)
177
+
178
+ raise AWS::Error, "Unexpected server error. response.body is: #{response.body}" if response.is_a?(Net::HTTPServerError)
179
+
180
+ # parse the XML document so we can walk through it
181
+ doc = REXML::Document.new(response.body)
182
+
183
+ # Check that the Error element is in the place we would expect.
184
+ # and if not raise a generic error exception
185
+ unless doc.root.elements['Error'].name == 'Error'
186
+ raise Error, "Unexpected error format. response.body is: #{response.body}"
187
+ end
188
+
189
+ # An valid error response looks like this:
190
+ # <?xml version="1.0"?><Response><Errors><Error><Code>InvalidParameterCombination</Code><Message>Unknown parameter: foo</Message></Error></Errors><RequestID>291cef62-3e86-414b-900e-17246eccfae8</RequestID></Response>
191
+ # AWS throws some exception codes that look like Error.SubError. Since we can't name classes this way
192
+ # we need to strip out the '.' in the error 'Code' and we name the error exceptions with this
193
+ # non '.' name as well.
194
+ error_code = doc.root.elements['Error'].elements['Code'].text.gsub('.', '')
195
+ error_message = doc.root.elements['Error'].elements['Message'].text
196
+
197
+ # Raise one of our specific error classes if it exists.
198
+ # otherwise, throw a generic EC2 Error with a few details.
199
+ if AWS.const_defined?(error_code)
200
+ raise AWS.const_get(error_code), error_message
201
+ else
202
+ raise AWS::Error, error_message
203
+ end
204
+
205
+ end
206
+
207
+ class << self
208
+
209
+
210
+ end # class methods
211
+ end # class Base
212
+ end # Module SES
213
+ end # Module AWS