crusade-apns 0.5.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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +9 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +29 -0
  7. data/Rakefile +8 -0
  8. data/crusade-apns.gemspec +28 -0
  9. data/lib/crusade/apns.rb +3 -0
  10. data/lib/crusade/apns/configuration.rb +52 -0
  11. data/lib/crusade/apns/crypto/user_token_generator.rb +23 -0
  12. data/lib/crusade/apns/push_package/directory_structure_generator.rb +38 -0
  13. data/lib/crusade/apns/push_package/manifest_generator.rb +42 -0
  14. data/lib/crusade/apns/push_package/signature_generator.rb +45 -0
  15. data/lib/crusade/apns/push_package/website_file_generator.rb +37 -0
  16. data/lib/crusade/apns/push_package/zip_file_generator.rb +31 -0
  17. data/lib/crusade/apns/push_package_generator.rb +50 -0
  18. data/lib/crusade/apns/version.rb +5 -0
  19. data/signature +0 -0
  20. data/test/fixtures/config.yml +23 -0
  21. data/test/fixtures/icons/icon_128x128.png +0 -0
  22. data/test/fixtures/icons/icon_128x128@2x.png +0 -0
  23. data/test/fixtures/icons/icon_16x16.png +0 -0
  24. data/test/fixtures/icons/icon_16x16@2x.png +0 -0
  25. data/test/fixtures/icons/icon_32x32.png +0 -0
  26. data/test/fixtures/icons/icon_32x32@2x.png +0 -0
  27. data/test/fixtures/ssl/ca.crt +33 -0
  28. data/test/fixtures/ssl/ca.key +51 -0
  29. data/test/fixtures/ssl/certificate.p12 +0 -0
  30. data/test/fixtures/ssl/hash +1 -0
  31. data/test/fixtures/ssl/ia.crt +29 -0
  32. data/test/fixtures/ssl/ia.csr +27 -0
  33. data/test/fixtures/ssl/ia.key +51 -0
  34. data/test/fixtures/ssl/manifest_example.json +4 -0
  35. data/test/fixtures/ssl/signature +0 -0
  36. data/test/fixtures/ssl/signature_example +0 -0
  37. data/test/integration/push_package_generator_test.rb +40 -0
  38. data/test/integration/user_token_generator_test.rb +19 -0
  39. data/test/support/fixtures.rb +18 -0
  40. data/test/support/ssl_support.rb +19 -0
  41. data/test/support/zip_support.rb +13 -0
  42. data/test/test_helper.rb +9 -0
  43. data/test/unit/configuration_test.rb +155 -0
  44. data/test/unit/push_package/directory_structure_generator_test.rb +39 -0
  45. data/test/unit/push_package/manifest_generator_test.rb +37 -0
  46. data/test/unit/push_package/signature_generator_test.rb +41 -0
  47. data/test/unit/push_package/website_file_generator_test.rb +41 -0
  48. data/test/unit/push_package/zip_file_generator_test.rb +51 -0
  49. data/test/unit/push_package_generator_test.rb +6 -0
  50. metadata +206 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b99d15d9bd5de7752db570201d229fe1ff16e6bd
4
+ data.tar.gz: 04bfd747ca356f60c9a03cddf688a5013d203f8c
5
+ SHA512:
6
+ metadata.gz: a0447cf0e5df77f18b0ce32df590f95b7ab90be81bea9790f46d60bf87958d4b572cee2af05c9fa2a5d44ff8ea7597e58f5ffbd74a76a1c4bc6832707f2cbe8a
7
+ data.tar.gz: 7213bfce39dce624752ab0439b26eb78c3d2283921568b31a69c1fd834e458846eb05e5db813d73b185b2ff515330252f5b7ce4247afae4909fd0a3064195396
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - jruby-19mode
5
+ - rbx-19mode
6
+ - 2.0.0
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: jruby-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in crusade-apns.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 David Rouchy
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Crusade::Apns
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'crusade-apns'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install crusade-apns
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ task :default => :test
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.pattern = "test/**/*_test.rb"
8
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'crusade/apns/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "crusade-apns"
8
+ spec.version = Crusade::APNS::VERSION
9
+ spec.authors = ["David Rouchy"]
10
+ spec.email = ["drouchy@gmail.com"]
11
+ spec.description = %q{APNS pluging for crusade}
12
+ spec.summary = %q{send push notification to APNS}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "rubyzip", "~> 1.0.0"
22
+
23
+ spec.add_development_dependency "json_expressions", "~> 0.8.2"
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "minitest", "~> 5.0.7"
26
+ spec.add_development_dependency "minitest-great_expectations", "~> 0.0.5"
27
+ spec.add_development_dependency "rake"
28
+ end
@@ -0,0 +1,3 @@
1
+ require "crusade/apns/version"
2
+
3
+ Dir[File.dirname(__FILE__) + '/apns/**/*.rb'].each {|file| require file }
@@ -0,0 +1,52 @@
1
+ require 'tmpdir'
2
+ require 'yaml'
3
+
4
+ module Crusade
5
+ module APNS
6
+ class Configuration
7
+ attr_accessor :site_name, :push_id, :url_format,
8
+ :webservice_url, :allowed_domains, :iconset_dir,
9
+ :certificate, :certificate_password
10
+
11
+ def initialize(attributes)
12
+ self.site_name = attributes[:site_name]
13
+ self.push_id = attributes[:push_id]
14
+ self.url_format = attributes[:url_format]
15
+ self.webservice_url = attributes[:webservice_url]
16
+ self.allowed_domains = attributes[:allowed_domains]
17
+ self.iconset_dir = attributes[:iconset_dir]
18
+
19
+ self.certificate = attributes[:certificate] || 'config/push_certificate.p12'
20
+ self.certificate_password = attributes[:certificate_password] || nil
21
+ end
22
+
23
+ def iconset_files
24
+ %w(16x16 16x16@2x 32x32 32x32@2x 128x128 128x128@2x).map do |size|
25
+ File.join iconset_dir, "icon_#{size}.png"
26
+ end
27
+ end
28
+
29
+ def temp_dir
30
+ @temp_dir ||= Dir.mktmpdir
31
+ end
32
+
33
+ def self.load config_file, env = 'development'
34
+ yaml = load_yaml config_file
35
+ config = symbolize_keys yaml[env.to_s]
36
+ new(config)
37
+ end
38
+
39
+ private
40
+
41
+ def self.symbolize_keys h
42
+ h.inject({}) do |memo, (k,v) |
43
+ memo.update k.to_sym => v
44
+ end
45
+ end
46
+
47
+ def self.load_yaml config_file
48
+ YAML.load_file config_file
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ require 'securerandom'
2
+
3
+ module Crusade
4
+ module APNS
5
+ class UserTokenGenerator
6
+ def initialize(user_id)
7
+ self.user_id = String(user_id)
8
+ end
9
+
10
+ def generate
11
+ Digest::SHA1.hexdigest("#{salt}--#{user_id}")
12
+ end
13
+
14
+ private
15
+
16
+ attr_accessor :user_id
17
+
18
+ def salt
19
+ SecureRandom.hex(16)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ require 'tmpdir'
2
+ require 'fileutils'
3
+ require 'digest'
4
+
5
+ module Crusade
6
+ module APNS
7
+ class DirectoryStructureGenerator
8
+ def initialize configuration
9
+ self.configuration = configuration
10
+ end
11
+
12
+ def generate
13
+ copy_icons
14
+
15
+ configuration.temp_dir
16
+ end
17
+
18
+ def clean
19
+ FileUtils.remove_entry_secure configuration.temp_dir
20
+ end
21
+
22
+ private
23
+
24
+ attr_accessor :configuration
25
+
26
+ def copy_icons
27
+ icon_dir = File.join configuration.temp_dir, 'icon.iconset'
28
+
29
+ FileUtils.mkdir icon_dir
30
+ %w(16x16 16x16@2x 32x32 32x32@2x 128x128 128x128@2x).each do |size|
31
+ FileUtils.copy File.join(configuration.iconset_dir, "icon_#{size}.png"),
32
+ icon_dir
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ require 'json'
2
+ require 'pathname'
3
+ require 'digest'
4
+
5
+ module Crusade
6
+ module APNS
7
+ class ManifestGenerator
8
+ def initialize directory
9
+ self.directory = directory
10
+ end
11
+
12
+ def generate
13
+ to_hash.to_json
14
+ end
15
+
16
+ private
17
+
18
+ attr_accessor :directory
19
+
20
+ def to_hash
21
+ Dir.glob("#{directory}/**/*").inject({}) do |memo,file_path|
22
+ if should_digest? file_path
23
+ memo[relative_path(file_path)] = digest file_path
24
+ end
25
+ memo
26
+ end
27
+ end
28
+
29
+ def relative_path absolute_path
30
+ Pathname.new(absolute_path).relative_path_from(Pathname.new(directory))
31
+ end
32
+
33
+ def should_digest? absolute_path
34
+ File.file? absolute_path
35
+ end
36
+
37
+ def digest file_path
38
+ Digest::SHA1.hexdigest File.read(file_path)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module Crusade
5
+ module APNS
6
+ class SignatureGenerator
7
+ FLAG = OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY
8
+
9
+ def initialize configuration
10
+ self.configuration = configuration
11
+ end
12
+
13
+ def sign manifest
14
+ dir = File.dirname manifest
15
+ signature_file = File.join(dir, 'signature')
16
+
17
+ File.open(signature_file, 'wb') do |file|
18
+ file.write generate_signature manifest
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ attr_accessor :configuration
25
+
26
+ def generate_signature manifest
27
+ crt = certificate
28
+
29
+ signature = OpenSSL::PKCS7::sign(crt.certificate, crt.key, File.read(manifest), [], FLAG )
30
+ signature = signature.to_s.split("\n")
31
+ signature = signature[1..signature.length-2].join("\n")
32
+
33
+ Base64.decode64 signature.to_s
34
+ end
35
+
36
+ def certificate
37
+ crt = OpenSSL::PKCS12.new(File.read(configuration.certificate), certificate_password)
38
+ end
39
+
40
+ def certificate_password
41
+ configuration.certificate_password
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,37 @@
1
+ require 'json'
2
+
3
+ require 'crusade/apns/crypto/user_token_generator'
4
+
5
+ module Crusade
6
+ module APNS
7
+ class WebsiteFileGenerator
8
+ def initialize user_id, configuration
9
+ self.configuration = configuration
10
+ self.user_id = user_id
11
+ end
12
+
13
+ def generate
14
+ to_hash.to_json
15
+ end
16
+
17
+ private
18
+
19
+ attr_accessor :configuration, :user_id
20
+
21
+ def to_hash
22
+ {
23
+ "websiteName" => configuration.site_name,
24
+ "websitePushID" => configuration.push_id,
25
+ "allowedDomains" => configuration.allowed_domains,
26
+ "urlFormatString" => configuration.url_format,
27
+ "authenticationToken" => user_token_generator.generate,
28
+ "webServiceURL" => configuration.webservice_url
29
+ }
30
+ end
31
+
32
+ def user_token_generator
33
+ Crusade::APNS::UserTokenGenerator.new(user_id)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ require 'zip'
2
+ require 'fileutils'
3
+
4
+ module Crusade
5
+ module APNS
6
+ class ZipFileGenerator
7
+ def initialize configuration, directory, destination
8
+ self.configuration = configuration
9
+ self.directory = directory
10
+ self.destination = destination
11
+ end
12
+
13
+ def generate
14
+ Zip::File.open(destination, Zip::File::CREATE) do |zipfile|
15
+ Dir.glob("#{directory}/**/*").each do |file|
16
+ title = Pathname.new(file).relative_path_from(Pathname.new(directory))
17
+ zipfile.add(title, file)
18
+ end
19
+ end
20
+ end
21
+
22
+ def clean
23
+ FileUtils.remove_entry_secure destination
24
+ end
25
+
26
+ private
27
+
28
+ attr_accessor :configuration, :directory, :destination
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,50 @@
1
+ require 'crusade/apns/push_package/directory_structure_generator'
2
+ require 'crusade/apns/push_package/website_file_generator'
3
+ require 'crusade/apns/push_package/manifest_generator'
4
+ require 'crusade/apns/push_package/signature_generator'
5
+ require 'crusade/apns/push_package/zip_file_generator'
6
+
7
+ module Crusade
8
+ module APNS
9
+ class PushPackageGenerator
10
+ def initialize configuration
11
+ self.configuration = configuration
12
+ end
13
+
14
+ def generate user_id
15
+ push_package = File.join(configuration.temp_dir, file_name(user_id))
16
+
17
+ DirectoryStructureGenerator.new(configuration).generate
18
+
19
+ website = WebsiteFileGenerator.new(user_id, configuration).generate
20
+ user_token = user_token website
21
+ File.open(File.join(configuration.temp_dir, 'website.json'), 'w') { |f| f.write website }
22
+
23
+ manifest = Crusade::APNS::ManifestGenerator.new(configuration.temp_dir).generate
24
+ File.open(File.join(configuration.temp_dir, 'manifest.json'), 'w') { |f| f.write manifest }
25
+
26
+ Crusade::APNS::SignatureGenerator.new(configuration).sign File.join(configuration.temp_dir, 'manifest.json')
27
+
28
+ Crusade::APNS::ZipFileGenerator.new(configuration, configuration.temp_dir, push_package).generate
29
+
30
+ [ user_token, push_package ]
31
+ end
32
+
33
+ def clean
34
+ # FileUtils.remove_entry_secure destination
35
+ end
36
+
37
+ private
38
+
39
+ attr_accessor :configuration
40
+
41
+ def file_name(user_token)
42
+ "#{Time.now.to_i}-#{user_token}_push_package.zip"
43
+ end
44
+
45
+ def user_token json
46
+ JSON.parse(json)['authenticationToken']
47
+ end
48
+ end
49
+ end
50
+ end