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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +8 -0
- data/crusade-apns.gemspec +28 -0
- data/lib/crusade/apns.rb +3 -0
- data/lib/crusade/apns/configuration.rb +52 -0
- data/lib/crusade/apns/crypto/user_token_generator.rb +23 -0
- data/lib/crusade/apns/push_package/directory_structure_generator.rb +38 -0
- data/lib/crusade/apns/push_package/manifest_generator.rb +42 -0
- data/lib/crusade/apns/push_package/signature_generator.rb +45 -0
- data/lib/crusade/apns/push_package/website_file_generator.rb +37 -0
- data/lib/crusade/apns/push_package/zip_file_generator.rb +31 -0
- data/lib/crusade/apns/push_package_generator.rb +50 -0
- data/lib/crusade/apns/version.rb +5 -0
- data/signature +0 -0
- data/test/fixtures/config.yml +23 -0
- data/test/fixtures/icons/icon_128x128.png +0 -0
- data/test/fixtures/icons/icon_128x128@2x.png +0 -0
- data/test/fixtures/icons/icon_16x16.png +0 -0
- data/test/fixtures/icons/icon_16x16@2x.png +0 -0
- data/test/fixtures/icons/icon_32x32.png +0 -0
- data/test/fixtures/icons/icon_32x32@2x.png +0 -0
- data/test/fixtures/ssl/ca.crt +33 -0
- data/test/fixtures/ssl/ca.key +51 -0
- data/test/fixtures/ssl/certificate.p12 +0 -0
- data/test/fixtures/ssl/hash +1 -0
- data/test/fixtures/ssl/ia.crt +29 -0
- data/test/fixtures/ssl/ia.csr +27 -0
- data/test/fixtures/ssl/ia.key +51 -0
- data/test/fixtures/ssl/manifest_example.json +4 -0
- data/test/fixtures/ssl/signature +0 -0
- data/test/fixtures/ssl/signature_example +0 -0
- data/test/integration/push_package_generator_test.rb +40 -0
- data/test/integration/user_token_generator_test.rb +19 -0
- data/test/support/fixtures.rb +18 -0
- data/test/support/ssl_support.rb +19 -0
- data/test/support/zip_support.rb +13 -0
- data/test/test_helper.rb +9 -0
- data/test/unit/configuration_test.rb +155 -0
- data/test/unit/push_package/directory_structure_generator_test.rb +39 -0
- data/test/unit/push_package/manifest_generator_test.rb +37 -0
- data/test/unit/push_package/signature_generator_test.rb +41 -0
- data/test/unit/push_package/website_file_generator_test.rb +41 -0
- data/test/unit/push_package/zip_file_generator_test.rb +51 -0
- data/test/unit/push_package_generator_test.rb +6 -0
- 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
data/.travis.yml
ADDED
data/Gemfile
ADDED
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,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
|
data/lib/crusade/apns.rb
ADDED
@@ -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
|