grocer-pushpackager 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dd2d3abb604660531db0cd55d1d7344028266a36
4
+ data.tar.gz: fe2f900f5262f35434e1822661cea84d4ba5ae0f
5
+ SHA512:
6
+ metadata.gz: 001ebdf1bf63f8bbefb4153fac23b0cda17bef0d0aeaf6513e646a91cc9f1fb2b39b10a55fe9e0d4e3046d1da348b7d413ddb435bac6d5f8a3311ce8d9330ce9
7
+ data.tar.gz: a9b7748647b89a8699c277c475a270fe142765f6eeb1fd01376922ce2e8f71a891ee87e44abaae7fd0afc22e453fb936001b5e901feac20fcccce36fe801baa9
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
@@ -0,0 +1,17 @@
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
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1 @@
1
+ pushpackager
@@ -0,0 +1 @@
1
+ 2.0.0-p195
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ script: rspec spec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in grocer-pushpackager.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Kurt Nelson
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.
@@ -0,0 +1,33 @@
1
+ # Grocer::Pushpackager
2
+ [![Build Status](https://secure.travis-ci.org/grocer/grocer-pushpackager.png)](http://travis-ci.org/grocer/grocer-pushpackager)
3
+ [![Gem Version](https://badge.fury.io/rb/grocer-pushpackager.png)](http://badge.fury.io/rb/grocer-pushpackager)
4
+ [![Code Climate](https://codeclimate.com/github/grocer/grocer-pushpackager.png)](https://codeclimate.com/github/grocer/grocer-pushpackager)
5
+ [![Coverage Status](https://coveralls.io/repos/grocer/grocer-pushpackager/badge.png?branch=master)](https://coveralls.io/r/grocer/grocer-pushpackager)
6
+ [Documentation](http://rubydoc.info/gems/grocer-pushpackager/)
7
+
8
+ Builds a PushPackage for Apple's APN for use with Safari/Mavericks
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'grocer-pushpackager'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install grocer-pushpackager
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'grocer/pushpackager/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "grocer-pushpackager"
8
+ spec.version = Grocer::Pushpackager::VERSION
9
+ spec.authors = ["Kurt Nelson"]
10
+ spec.email = ["kurtisnelson@gmail.com"]
11
+ spec.description = %q{Builds push packages for OS X Mavericks web notifications}
12
+ spec.summary = <<-DESC
13
+ To use push notifications to Safari/OS X with the APN service,
14
+ you must build a signed push package that is generated by your web server
15
+ for every user. This gem handles building that package.
16
+ DESC
17
+ spec.homepage = "https://github.com/grocer/grocer-pushpackager"
18
+ spec.license = "MIT"
19
+
20
+ spec.files = `git ls-files`.split($/)
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency 'pry'
27
+ spec.add_development_dependency 'coveralls'
28
+ spec.add_development_dependency 'rspec'
29
+ spec.add_development_dependency 'rake'
30
+ spec.add_dependency 'rubyzip'
31
+ end
@@ -0,0 +1,2 @@
1
+ require "grocer/pushpackager/version"
2
+ require "grocer/pushpackager/package"
@@ -0,0 +1,23 @@
1
+ module Grocer
2
+ module Pushpackager
3
+ class Icon
4
+ attr_reader :name, :contents
5
+ def initialize(size)
6
+ @name = "icon_#{size}.png"
7
+ self
8
+ end
9
+
10
+ def from_file file
11
+ @contents = file.read
12
+ file.close
13
+ self
14
+ end
15
+
16
+ def valid?
17
+ raise ArgumentError, "invalid icon name" unless @name
18
+ raise ArgumentError, "missing contents" unless @contents
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ require 'forwardable'
2
+ require_relative 'icon'
3
+
4
+ module Grocer
5
+ module Pushpackager
6
+ class IconSet
7
+ include Enumerable
8
+ extend Forwardable
9
+ def_delegators :@icons, :each, :<<
10
+
11
+ def initialize(config = {})
12
+ raise ArgumentError, "Missing icon set" unless config[:iconSet]
13
+ @icons = []
14
+ config[:iconSet].each do |size, file|
15
+ @icons << Icon.new(size).from_file(file)
16
+ end
17
+ end
18
+
19
+ def valid?
20
+ fail unless @icons.select{|i| i.valid?}.count == @icons.count
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,84 @@
1
+ require 'zip/zip'
2
+ require 'digest/sha1'
3
+ require 'openssl'
4
+ require_relative 'icon_set'
5
+ require_relative 'website'
6
+
7
+ module Grocer
8
+ module Pushpackager
9
+ class Package
10
+ attr_accessor :key, :certificate, :icon_set, :website
11
+
12
+ def initialize(config = {})
13
+ @icon_set = IconSet.new(config)
14
+ @website = Website.new(config)
15
+ @certificate = config[:certificate]
16
+ @key = config[:key]
17
+ end
18
+
19
+ def authentication_token= value
20
+ @website.authentication_token = value
21
+ @signature = nil
22
+ @website_json = nil
23
+ @manifest_json = nil
24
+ end
25
+
26
+ def valid?
27
+ @icon_set.valid?
28
+ @website.valid?
29
+ raise ArgumentError, "Missing private key" unless @key
30
+ raise ArgumentError, "Missing certificate" unless @certificate
31
+ true
32
+ end
33
+
34
+ def file
35
+ raise unless self.valid?
36
+ package = Tempfile.new('package')
37
+ package.write(build_zip.string)
38
+ package.close
39
+ package
40
+ end
41
+
42
+ private
43
+ def website_json
44
+ @website_json ||= @website.to_json
45
+ end
46
+
47
+ def manifest_json
48
+ return @manifest_json if @manifest_json
49
+ @manifest_json = {
50
+ :"website.json" => Digest::SHA1.hexdigest(website_json)
51
+ }.merge(icon_set_manifest).to_json
52
+ end
53
+
54
+ def icon_set_manifest
55
+ manifest = { }
56
+ @icon_set.each do |icon|
57
+ manifest[:"icon.iconset\/#{icon.name}"] = Digest::SHA1.hexdigest(icon.contents)
58
+ end
59
+ manifest
60
+ end
61
+
62
+ def signature
63
+ return @signature if @signature
64
+ @signature = OpenSSL::PKCS7::sign(@certificate, @key, manifest_json, [], OpenSSL::PKCS7::DETACHED)
65
+ end
66
+
67
+ def build_zip
68
+ buffer = Zip::ZipOutputStream.write_buffer do |out|
69
+ @icon_set.each do |icon|
70
+ out.put_next_entry("icon.iconset/#{icon.name}")
71
+ out.write icon.contents
72
+ end
73
+ out.put_next_entry('website.json')
74
+ out.write website_json
75
+ out.put_next_entry('manifest.json')
76
+ out.write manifest_json
77
+ out.put_next_entry('signature')
78
+ out.write signature.to_der
79
+ end
80
+ buffer
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,5 @@
1
+ module Grocer
2
+ module Pushpackager
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,44 @@
1
+ require 'uri'
2
+ require 'json'
3
+
4
+ module Grocer
5
+ module Pushpackager
6
+ class Website
7
+ attr_accessor :authentication_token
8
+ attr_reader :website_name, :push_id, :allowed_domains, :url_format_string, :web_service_url
9
+
10
+ def initialize(config = {})
11
+ @website_name = config[:websiteName]
12
+ @push_id = config[:websitePushID]
13
+ @allowed_domains = []
14
+ if config[:allowedDomains]
15
+ @allowed_domains = config[:allowedDomains].map{|domain| URI(domain)}
16
+ end
17
+ @url_format_string = config[:urlFormatString]
18
+ @web_service_url = URI(config[:webServiceURL])
19
+ @authentication_token = config[:authenticationToken]
20
+ end
21
+
22
+ def valid?
23
+ raise ArgumentError if not @website_name or @website_name.empty?
24
+ raise ArgumentError if not @push_id or @push_id.empty?
25
+ raise ArgumentError if not @url_format_string or @url_format_string.empty?
26
+ raise ArgumentError if not @web_service_url or @web_service_url.to_s.empty? or not ['http', 'https'].include?(@web_service_url.scheme)
27
+ raise ArgumentError if not @authentication_token or @authentication_token.empty?
28
+ raise ArgumentError, "Allowed domains" unless @allowed_domains.count > 0
29
+ true
30
+ end
31
+
32
+ def to_json
33
+ raise "Invalid website" unless valid?
34
+ { websiteName: @website_name.to_s,
35
+ websitePushID: @push_id.to_s,
36
+ allowedDomains: @allowed_domains.map{|domain| domain.to_s},
37
+ urlFormatString: @url_format_string.to_s,
38
+ authenticationToken: @authentication_token.to_s,
39
+ webServiceURL: @web_service_url.to_s,
40
+ }.to_json
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grocer::Pushpackager::Package do
4
+ subject {Grocer::Pushpackager::Package}
5
+ it "can be initialized as a valid object" do
6
+ package = subject.new({
7
+ websiteName: "Bay Airlines",
8
+ websitePushID: "web.com.example.domain",
9
+ allowedDomains: ["http://domain.example.com"],
10
+ urlFormatString: "http://domain.example.com/%@/?flight=%@",
11
+ webServiceURL: "https://example.com/push",
12
+ iconSet: { },
13
+ certificate: '',
14
+ key: ''
15
+ })
16
+ expect{package.valid?}.to raise_error ArgumentError
17
+ package.authentication_token = "19f8d7a6e9fb8a7f6d9330dabe"
18
+ package.should be_valid
19
+ end
20
+
21
+ context "complete, valid package" do
22
+ before :all do
23
+ @pair = test_ssl_pair
24
+ end
25
+
26
+ subject do
27
+ Grocer::Pushpackager::Package.new({
28
+ websiteName: "Bay Airlines",
29
+ websitePushID: "web.com.example.domain",
30
+ allowedDomains: ["http://domain.example.com"],
31
+ urlFormatString: "http://domain.example.com/%@/?flight=%@",
32
+ authenticationToken: "19f8d7a6e9fb8a7f6d9330dabe",
33
+ webServiceURL: "https://example.com/push",
34
+ certificate: @pair[:certificate],
35
+ key: @pair[:key],
36
+ iconSet: {
37
+ :'16x16' => test_icon,
38
+ :'16x16@2x' => test_icon,
39
+ :'32x32' => test_icon,
40
+ :'32x32@2x' => test_icon,
41
+ :'128x128' => test_icon,
42
+ :'128x128@2x' => test_icon
43
+ }
44
+ })
45
+ end
46
+
47
+ it{should be_valid}
48
+
49
+ it "generates a non-zero file" do
50
+ subject.send(:build_zip).string.should have_at_least(8).length
51
+ end
52
+
53
+ it "writes out file" do
54
+ package = subject.file
55
+ file = File.new('tmp/package.zip', 'w')
56
+ file.write(package.open.read)
57
+ package.unlink
58
+ file.should have_at_least(1).length
59
+ file.close
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'coveralls'
4
+ Coveralls.wear!
5
+ require 'pry'
6
+ require 'rspec'
7
+
8
+ require 'grocer/pushpackager'
9
+
10
+ # Requires supporting files with custom matchers and macros, etc,
11
+ # # in ./support/ and its subdirectories.
12
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
13
+ RSpec.configure do |config|
14
+
15
+ end
@@ -0,0 +1,25 @@
1
+ def test_icon
2
+ file = Tempfile.new('icon')
3
+ file.write("hello world")
4
+ file.rewind
5
+ file.unlink
6
+ file
7
+ end
8
+
9
+ def test_ssl_pair
10
+ key = OpenSSL::PKey::RSA.new(512)
11
+ name = OpenSSL::X509::Name.parse("CN=auralis/DC=topalis/DC=com")
12
+ cert = OpenSSL::X509::Certificate.new()
13
+ cert.version = 2
14
+ cert.serial = 0
15
+ cert.not_before = Time.new()
16
+ cert.not_after = cert.not_before + (60*60*24*365)
17
+ cert.public_key = key.public_key
18
+ cert.subject = name
19
+
20
+ # Sign cert
21
+ cert.issuer = name
22
+ cert.sign(key, OpenSSL::Digest::SHA1.new())
23
+
24
+ {certificate: cert, key: key}
25
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grocer-pushpackager
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Kurt Nelson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-06-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: coveralls
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubyzip
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Builds push packages for OS X Mavericks web notifications
98
+ email:
99
+ - kurtisnelson@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .document
105
+ - .gitignore
106
+ - .rspec
107
+ - .ruby-gemset
108
+ - .ruby-version
109
+ - .travis.yml
110
+ - Gemfile
111
+ - LICENSE.txt
112
+ - README.md
113
+ - Rakefile
114
+ - grocer-pushpackager.gemspec
115
+ - lib/grocer/pushpackager.rb
116
+ - lib/grocer/pushpackager/icon.rb
117
+ - lib/grocer/pushpackager/icon_set.rb
118
+ - lib/grocer/pushpackager/package.rb
119
+ - lib/grocer/pushpackager/version.rb
120
+ - lib/grocer/pushpackager/website.rb
121
+ - spec/package_spec.rb
122
+ - spec/spec_helper.rb
123
+ - spec/support/test_files.rb
124
+ homepage: https://github.com/grocer/grocer-pushpackager
125
+ licenses:
126
+ - MIT
127
+ metadata: {}
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubyforge_project:
144
+ rubygems_version: 2.0.3
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: To use push notifications to Safari/OS X with the APN service, you must build
148
+ a signed push package that is generated by your web server for every user. This
149
+ gem handles building that package.
150
+ test_files:
151
+ - spec/package_spec.rb
152
+ - spec/spec_helper.rb
153
+ - spec/support/test_files.rb