apple_shove 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -37,6 +37,21 @@ Willing to give it a try? Onward...
37
37
 
38
38
  ## Usage
39
39
 
40
+ ### Quick Note on Certificate/Key Handling
41
+
42
+ AppleShove expects certificates and keys to be bundled together in PKCS#12 PEM format. If you want to test the validity of your PKCS#12 file, you can do something similar to this:
43
+
44
+ apns_p12 = File.read('my_cert.p12')
45
+ begin
46
+ AppleShove.try_p12(apns_p12)
47
+ rescue Exception => e
48
+ puts "the PKCS#12 file is invalid: #{e.message}"
49
+ else
50
+ puts "it's valid!"
51
+ end
52
+
53
+ If you send a notification with an invalid PKCS#12 file, the notification will fail downstream.
54
+
40
55
  ### Sending Notifications
41
56
 
42
57
  Sending a notification request looks like this:
@@ -81,6 +96,8 @@ We also have a feedback mechanism in place:
81
96
 
82
97
  ## Installation
83
98
 
99
+ If you haven't already, install redis. It's normally available via brew, apt-get, and yum, but you can also build from [source](http://redis.io/download).
100
+
84
101
  Add this line to your application's Gemfile:
85
102
 
86
103
  gem 'apple_shove'
@@ -6,10 +6,12 @@ module AppleShove
6
6
 
7
7
  attr_reader :last_used
8
8
 
9
- def initialize(opts = {})
10
- @host = opts[:host]
11
- @port = opts[:port]
12
- @certificate = opts[:certificate]
9
+ def initialize(host, port, p12_string)
10
+ @host = host
11
+ @port = port
12
+ @p12_string = p12_string
13
+
14
+ @p12 = nil
13
15
  end
14
16
 
15
17
  # lazy connect the socket
@@ -31,12 +33,13 @@ module AppleShove
31
33
  private
32
34
 
33
35
  def connect
34
- @sock = TCPSocket.new(@host, @port)
35
- @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
36
-
36
+ @p12 ||= OpenSSLHelper.pkcs12_from_pem(@p12_string)
37
37
  context = ::OpenSSL::SSL::SSLContext.new
38
- context.cert = ::OpenSSL::X509::Certificate.new(@certificate)
39
- context.key = ::OpenSSL::PKey::RSA.new(@certificate)
38
+ context.cert = @p12.certificate
39
+ context.key = @p12.key
40
+
41
+ @sock = TCPSocket.new(@host, @port)
42
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
40
43
  @ssl_sock = ::OpenSSL::SSL::SSLSocket.new(@sock, context)
41
44
  @ssl_sock.sync = true
42
45
 
@@ -49,4 +52,4 @@ module AppleShove
49
52
 
50
53
  end
51
54
  end
52
- end
55
+ end
@@ -2,12 +2,10 @@ module AppleShove
2
2
  module APNS
3
3
  class FeedbackConnection < Connection
4
4
 
5
- def initialize(opts = {})
5
+ def initialize(p12, sandbox)
6
6
  host = "feedback.#{opts[:sandbox] ? 'sandbox.' : ''}push.apple.com"
7
7
 
8
- super certificate: opts[:certificate],
9
- host: host,
10
- port: 2196
8
+ super host, 2196, p12
11
9
  end
12
10
 
13
11
  def device_tokens
@@ -8,20 +8,18 @@ module AppleShove
8
8
  attr_accessor :pending_notifications
9
9
  attr_reader :name
10
10
 
11
- def initialize(opts = {})
12
- @name = self.class.generate_name(opts[:certificate], opts[:sandbox])
11
+ def initialize(p12, sandbox)
12
+ @name = self.class.generate_name(p12, sandbox)
13
13
  @last_message = nil
14
14
  @pending_notifications = 0
15
15
 
16
- host = "gateway.#{opts[:sandbox] ? 'sandbox.' : ''}push.apple.com"
16
+ host = "gateway.#{sandbox ? 'sandbox.' : ''}push.apple.com"
17
17
 
18
- super certificate: opts[:certificate],
19
- host: host,
20
- port: 2195
18
+ super host, 2195, p12
21
19
  end
22
20
 
23
- def self.generate_name(certificate, sandbox)
24
- Digest::SHA1.hexdigest("#{certificate}#{sandbox}")
21
+ def self.generate_name(p12, sandbox)
22
+ Digest::SHA1.hexdigest("#{p12}#{sandbox}")
25
23
  end
26
24
 
27
25
  exclusive
@@ -35,34 +33,37 @@ module AppleShove
35
33
  message = notification.binary_message
36
34
 
37
35
  if @last_used && Time.now - @last_used > CONFIG[:reconnect_timer] * 60
38
- Logger.info("#{@name}\trefreshing connection")
36
+ Logger.info("refreshing connection", self, notification)
39
37
  reconnect
40
38
  end
41
39
 
42
40
  begin
43
41
  socket.write message
44
42
  rescue Errno::EPIPE
45
- Logger.warn("#{@name}\tbroken pipe. reconnecting.")
43
+ Logger.warn("broken pipe. reconnecting.", self, notification)
46
44
  reconnect
47
45
  # EPIPE raises on the second write to a closed pipe. We need to resend
48
46
  # the previous notification that didn't make it through.
49
47
  socket.write @last_message if @last_message
50
48
  retry
51
49
  rescue Errno::ETIMEDOUT
52
- Logger.warn("#{@name}\ttimeout. reconnecting.")
50
+ Logger.warn("timeout. reconnecting.", self, notification)
53
51
  reconnect
54
52
  retry
53
+ rescue Exception => e
54
+ Logger.error("error sending notification: #{e.message}", self, notification)
55
+ else
56
+ Logger.info("delivered notification", self, notification)
55
57
  end
56
-
58
+
57
59
  @last_message = message
58
60
  @last_used = Time.now
59
61
  @pending_notifications -= 1
60
- Logger.info("#{@name}\tdelivered notification")
61
62
  end
62
63
 
63
64
  def shutdown
64
65
  while @pending_notifications > 0
65
- Logger.info("#{@name}\twaiting to shut down. #{@pending_notifications} job(s) remaining.")
66
+ Logger.info("waiting to shut down. #{@pending_notifications} job(s) remaining.", self)
66
67
  sleep 1
67
68
  end
68
69
 
@@ -72,4 +73,4 @@ module AppleShove
72
73
 
73
74
  end
74
75
  end
75
- end
76
+ end
@@ -1,7 +1,7 @@
1
1
  module AppleShove
2
2
 
3
- def self.notify(certificate, device_token, payload, sandbox = false)
4
- notification = Notification.new certificate: certificate,
3
+ def self.notify(p12, device_token, payload, sandbox = false)
4
+ notification = Notification.new p12: p12,
5
5
  device_token: device_token,
6
6
  payload: payload,
7
7
  sandbox: sandbox
@@ -12,9 +12,8 @@ module AppleShove
12
12
  true
13
13
  end
14
14
 
15
- def self.feedback_tokens(certificate, sandbox = false)
16
- conn = APNS::FeedbackConnection.new certificate: certificate,
17
- sandbox: sandbox
15
+ def self.feedback_tokens(p12, sandbox = false)
16
+ conn = APNS::FeedbackConnection.new p12, sandbox
18
17
 
19
18
  conn.device_tokens
20
19
  end
@@ -30,4 +29,10 @@ module AppleShove
30
29
  "queue size:\t#{size}"
31
30
  end
32
31
 
32
+ # raises an exception if the p12 string is invalid
33
+ def self.try_p12(p12)
34
+ OpenSSLHelper.pkcs12_from_pem(p12)
35
+ true
36
+ end
37
+
33
38
  end
@@ -31,16 +31,15 @@ module AppleShove
31
31
  private
32
32
 
33
33
  def get_connection(notification)
34
- key = APNS::NotifyConnection.generate_name(notification.certificate, notification.sandbox)
34
+ key = APNS::NotifyConnection.generate_name(notification.p12, notification.sandbox)
35
35
  connection = @connections[key]
36
36
 
37
37
  unless connection
38
38
  retire_oldest_connection if @connections.count >= @max_connections
39
39
 
40
- connection = APNS::NotifyConnection.new certificate: notification.certificate,
41
- sandbox: notification.sandbox
40
+ connection = APNS::NotifyConnection.new notification.p12, notification.sandbox
42
41
  @connections[key] = connection
43
- Logger.info "#{connection.name}\tcreated connection to APNS (#{@connections.count} total)"
42
+ Logger.info "created connection to APNS (#{@connections.count} total)", connection
44
43
  end
45
44
 
46
45
  connection
@@ -53,7 +52,7 @@ module AppleShove
53
52
  @connections.delete key
54
53
  conn.shutdown
55
54
 
56
- Logger.info "#{conn_name}\tdestroyed connection to APNS (#{@connections.count} total)"
55
+ Logger.info "destroyed connection to APNS (#{@connections.count} total)", connection
57
56
  end
58
57
  end
59
58
 
@@ -19,11 +19,35 @@ module AppleShove
19
19
  self
20
20
  end
21
21
 
22
- def self.error(msg); instance.error(msg) end
23
- def self.debug(msg); instance.debug(msg) end
24
- def self.fatal(msg); instance.fatal(msg) end
25
- def self.info(msg); instance.info(msg) end
26
- def self.warn(msg); instance.warn(msg) end
27
-
22
+ def self.error(msg, connection = nil, notification = nil)
23
+ log('error', msg, connection, notification)
24
+ end
25
+
26
+ def self.debug(msg, connection = nil, notification = nil)
27
+ log('debug', msg, connection, notification)
28
+ end
29
+
30
+ def self.fatal(msg, connection = nil, notification = nil)
31
+ log('fatal', msg, connection, notification)
32
+ end
33
+
34
+ def self.info(msg, connection = nil, notification = nil)
35
+ log('info', msg, connection, notification)
36
+ end
37
+
38
+ def self.warn(msg, connection = nil, notification = nil)
39
+ log('warn', msg, connection, notification)
40
+ end
41
+
42
+ private
43
+
44
+ def self.log(severity, msg, connection, notification)
45
+ output = ''
46
+ output += "#{connection.name}\t" if connection && connection.respond_to?("name")
47
+ output += "#{notification.device_token}\t" if notification
48
+ output += msg
49
+
50
+ instance.send(severity, output)
51
+ end
28
52
  end
29
53
  end
@@ -1,7 +1,7 @@
1
1
  module AppleShove
2
2
  class Notification
3
3
 
4
- attr_accessor :certificate, :sandbox, :device_token, :payload
4
+ attr_accessor :p12, :sandbox, :device_token, :payload
5
5
 
6
6
  def initialize(attributes = {})
7
7
  attributes.each { |k, v| self.send("#{k}=", v) }
@@ -31,4 +31,4 @@ module AppleShove
31
31
  end
32
32
 
33
33
  end
34
- end
34
+ end
@@ -0,0 +1,15 @@
1
+ require 'openssl'
2
+
3
+ module AppleShove
4
+ class OpenSSLHelper
5
+
6
+ def self.pkcs12_from_pem(p12_pem)
7
+ key = ::OpenSSL::PKey::RSA.new p12_pem
8
+ cert = ::OpenSSL::X509::Certificate.new p12_pem
9
+ p12 = ::OpenSSL::PKCS12.create nil, nil, key, cert
10
+
11
+ p12
12
+ end
13
+
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module AppleShove
2
- VERSION = "1.0.2"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -0,0 +1,10 @@
1
+ require 'apple_shove'
2
+
3
+ describe AppleShove do
4
+
5
+ it 'detects an invalid p12' do
6
+ p12 = 'this is an invalid p12'
7
+ expect { AppleShove.try_p12(p12) }.to raise_error
8
+ end
9
+
10
+ end
@@ -1,12 +1,12 @@
1
1
  module NotificationHelper
2
2
 
3
3
  def generate_notification
4
- certificate = "DummyCertificate"
4
+ p12 = "DummyP12"
5
5
  sandbox = false
6
6
  device_token = hex(64)
7
7
  payload = { mdm: "#{hex(8)}-#{hex(4)}-#{hex(4)}-#{hex(4)}-#{hex(12)}".downcase }
8
8
 
9
- AppleShove::Notification.new certificate: certificate,
9
+ AppleShove::Notification.new p12: p12,
10
10
  sandbox: sandbox,
11
11
  device_token: device_token,
12
12
  payload: payload
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apple_shove
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-12 00:00:00.000000000 Z
12
+ date: 2013-05-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -130,10 +130,12 @@ files:
130
130
  - lib/apple_shove/logger.rb
131
131
  - lib/apple_shove/notification.rb
132
132
  - lib/apple_shove/notification_queue.rb
133
+ - lib/apple_shove/openssl_helper.rb
133
134
  - lib/apple_shove/tasks.rb
134
135
  - lib/apple_shove/version.rb
135
136
  - lib/tasks/apple_shove.rake
136
137
  - script/daemon
138
+ - spec/apple_shove_spec.rb
137
139
  - spec/demultiplexer_spec.rb
138
140
  - spec/notification_helper.rb
139
141
  - spec/notification_queue_spec.rb
@@ -153,7 +155,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
153
155
  version: '0'
154
156
  segments:
155
157
  - 0
156
- hash: 3074046605150142324
158
+ hash: 2826984217209674541
157
159
  required_rubygems_version: !ruby/object:Gem::Requirement
158
160
  none: false
159
161
  requirements:
@@ -162,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
162
164
  version: '0'
163
165
  segments:
164
166
  - 0
165
- hash: 3074046605150142324
167
+ hash: 2826984217209674541
166
168
  requirements: []
167
169
  rubyforge_project:
168
170
  rubygems_version: 1.8.24
@@ -170,6 +172,7 @@ signing_key:
170
172
  specification_version: 3
171
173
  summary: ''
172
174
  test_files:
175
+ - spec/apple_shove_spec.rb
173
176
  - spec/demultiplexer_spec.rb
174
177
  - spec/notification_helper.rb
175
178
  - spec/notification_queue_spec.rb