crusade-apns 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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