passbook 0.2.1 → 0.3.1

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.
@@ -0,0 +1,33 @@
1
+ {
2
+ "formatVersion" : 1,
3
+ "passTypeIdentifier" : "pass.com.example.event-ticket",
4
+ "description" : "Example Event Ticket",
5
+ "teamIdentifier": "Example",
6
+ "organizationName": "Example",
7
+ "serialNumber" : "123456",
8
+ "foregroundColor": "#FFFFFF",
9
+ "backgroundColor": "#FF5453",
10
+ "generic" : {
11
+ "primaryFields" : [
12
+
13
+ ],
14
+ "secondaryFields" : [
15
+
16
+ ],
17
+ "auxiliaryFields" : [
18
+
19
+ ],
20
+ "barcode" : {
21
+ "message" : "ABCD 123 EFGH 456 IJKL 789 MNOP",
22
+ "format" : "PKBarcodeFormatPDF417",
23
+ "messageEncoding" : "iso-8859-1"
24
+ },
25
+ "backFields" : [
26
+ {
27
+ "key" : "terms",
28
+ "label" : "Terms and Conditions",
29
+ "value" : "T's and C's apply"
30
+ }
31
+ ]
32
+ }
33
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "formatVersion" : 1,
3
+ "passTypeIdentifier" : "pass.com.example.generic",
4
+ "description" : "Example Generic Pass",
5
+ "teamIdentifier": "Example",
6
+ "organizationName": "Example",
7
+ "serialNumber" : "123456",
8
+ "foregroundColor": "#FFFFFF",
9
+ "backgroundColor": "#444444",
10
+ "generic" : {
11
+ "primaryFields" : [
12
+
13
+ ],
14
+ "secondaryFields" : [
15
+
16
+ ],
17
+ "auxiliaryFields" : [
18
+
19
+ ],
20
+ "barcode" : {
21
+ "message" : "ABCD 123 EFGH 456 IJKL 789 MNOP",
22
+ "format" : "PKBarcodeFormatPDF417",
23
+ "messageEncoding" : "iso-8859-1"
24
+ },
25
+ "backFields" : [
26
+ {
27
+ "key" : "terms",
28
+ "label" : "Terms and Conditions",
29
+ "value" : "Put your terms here"
30
+ }
31
+ ]
32
+ }
33
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "formatVersion" : 1,
3
+ "passTypeIdentifier" : "pass.com.example.store-card",
4
+ "description" : "Example Store Card",
5
+ "teamIdentifier": "Example",
6
+ "organizationName": "Example",
7
+ "serialNumber" : "123456",
8
+ "foregroundColor": "#FFFFFF",
9
+ "backgroundColor": "#AFC1E3",
10
+ "generic" : {
11
+ "primaryFields" : [
12
+
13
+ ],
14
+ "secondaryFields" : [
15
+
16
+ ],
17
+ "auxiliaryFields" : [
18
+
19
+ ],
20
+ "barcode" : {
21
+ "message" : "ABCD 123 EFGH 456 IJKL 789 MNOP",
22
+ "format" : "PKBarcodeFormatPDF417",
23
+ "messageEncoding" : "iso-8859-1"
24
+ },
25
+ "backFields" : [
26
+ {
27
+ "key" : "terms",
28
+ "label" : "Terms and Conditions",
29
+ "value" : "T's and C's apply"
30
+ }
31
+ ]
32
+ }
33
+ }
@@ -1,12 +1,14 @@
1
1
  require 'digest/sha1'
2
2
  require 'openssl'
3
- require 'zip/zip'
3
+ require 'zip'
4
4
  require 'base64'
5
5
 
6
6
  module Passbook
7
7
  class PKPass
8
8
  attr_accessor :pass, :manifest_files
9
9
 
10
+ TYPES = ['boarding-pass', 'coupon', 'event-ticket', 'store-card', 'generic']
11
+
10
12
  def initialize pass
11
13
  @pass = pass
12
14
  @manifest_files = []
@@ -67,9 +69,9 @@ module Passbook
67
69
  key_hash[:cert] = OpenSSL::X509::Certificate.new File.read(Passbook.p12_certificate)
68
70
  else
69
71
  p12 = OpenSSL::PKCS12.new File.read(Passbook.p12_cert), Passbook.p12_password
70
- key_hash[:key], key_hash[:cert] = p12.key, p12.certificate
72
+ key_hash[:key], key_hash[:cert] = p12.key, p12.certificate
71
73
  end
72
- key_hash
74
+ key_hash
73
75
  end
74
76
 
75
77
  def createSignature manifest
@@ -99,6 +101,7 @@ module Passbook
99
101
  raise 'Serial Number missing' unless @pass.include?('serialNumber')
100
102
  raise 'Organization Name Identifier missing' unless @pass.include?('organizationName')
101
103
  raise 'Format Version' unless @pass.include?('formatVersion')
104
+ raise 'Format Version should be a numeric' unless JSON.parse(@pass)['formatVersion'].is_a?(Numeric)
102
105
  raise 'Description' unless @pass.include?('description')
103
106
  end
104
107
 
@@ -119,7 +122,7 @@ module Passbook
119
122
 
120
123
  def outputZip manifest, signature
121
124
 
122
- Zip::ZipOutputStream.write_buffer do |zip|
125
+ Zip::OutputStream.write_buffer do |zip|
123
126
  zip.put_next_entry 'pass.json'
124
127
  zip.write @pass
125
128
  zip.put_next_entry 'manifest.json'
@@ -3,9 +3,12 @@ module Rack
3
3
 
4
4
  def initialize(app)
5
5
  @app = app
6
+ @parameters = {}
6
7
  end
7
8
 
8
9
  def call(env)
10
+ @parameters['authToken'] = env['HTTP_AUTHORIZATION'].gsub(/ApplePass /,'') if env['HTTP_AUTHORIZATION']
11
+ @parameters.merge!(Rack::Utils.parse_nested_query(env['QUERY_STRING']))
9
12
  method_and_params = find_method env['PATH_INFO']
10
13
  if method_and_params
11
14
  case method_and_params[:method]
@@ -30,7 +33,7 @@ module Rack
30
33
  [204, {}, {}]
31
34
  end
32
35
  when 'log'
33
- Passbook::PassbookNotification.log JSON.parse(env['rack.input'].read 10000)
36
+ Passbook::PassbookNotification.passbook_log JSON.parse(env['rack.input'].read 10000)
34
37
  [200, {}, {}]
35
38
  else
36
39
  end
@@ -76,15 +79,16 @@ module Rack
76
79
  url_beginning = parsed_path.index 'v1'
77
80
  if method == 'latest_pass'
78
81
  {:method => 'latest_pass',
79
- :params => {'passTypeIdentifier' => parsed_path[url_beginning + 2],
80
- 'serialNumber' => parsed_path[url_beginning + 3]}}
81
- else
82
- return_hash = {:method => method, :params =>
83
- {'deviceLibraryIdentifier' => parsed_path[url_beginning + 2],
84
- 'passTypeIdentifier' => parsed_path[url_beginning + 4]}}
85
- return_hash[:params]['serialNumber'] = parsed_path[url_beginning + 5] if
86
- method == 'device_register_delete'
87
- return_hash
82
+ :params => @parameters.merge!({'passTypeIdentifier' => parsed_path[url_beginning + 2],
83
+ 'serialNumber' => parsed_path[url_beginning + 3]})}
84
+ else
85
+ return_hash = {:method => method, :params =>
86
+ @parameters.merge!({'deviceLibraryIdentifier' => parsed_path[url_beginning + 2],
87
+ 'passTypeIdentifier' => parsed_path[url_beginning + 4]})}
88
+ if method == 'device_register_delete'
89
+ return_hash[:params]['serialNumber'] = parsed_path[url_beginning + 5]
90
+ end
91
+ return_hash
88
92
  end
89
93
  end
90
94
 
@@ -0,0 +1,12 @@
1
+ # this was added for testability because I couldn't figure out something better.
2
+ class CommandUtils
3
+ def self.get_assets(directory)
4
+ Dir[File.join(directory, '*')]
5
+ end
6
+
7
+ def self.get_current_directory
8
+ File.dirname(__FILE__)
9
+ end
10
+ end
11
+
12
+
data/passbook.gemspec CHANGED
@@ -2,16 +2,18 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
+ # stub: passbook 0.3.1 ruby lib
5
6
 
6
7
  Gem::Specification.new do |s|
7
8
  s.name = "passbook"
8
- s.version = "0.2.1"
9
+ s.version = "0.3.1"
9
10
 
10
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
12
  s.authors = ["Thomas Lauro", "Lance Gleason"]
12
- s.date = "2013-03-08"
13
+ s.date = "2013-10-29"
13
14
  s.description = "This gem allows you to create IOS Passbooks. Unlike some, this works with Rails but does not require it."
14
15
  s.email = ["thomas@lauro.fr", "lgleason@polyglotprogramminginc.com"]
16
+ s.executables = ["pk"]
15
17
  s.extra_rdoc_files = [
16
18
  "LICENSE",
17
19
  "README.md"
@@ -24,6 +26,15 @@ Gem::Specification.new do |s|
24
26
  "README.md",
25
27
  "Rakefile",
26
28
  "VERSION",
29
+ "bin/pk",
30
+ "lib/commands/build.rb",
31
+ "lib/commands/commands.rb",
32
+ "lib/commands/generate.rb",
33
+ "lib/commands/templates/boarding-pass.json",
34
+ "lib/commands/templates/coupon.json",
35
+ "lib/commands/templates/event-ticket.json",
36
+ "lib/commands/templates/generic.json",
37
+ "lib/commands/templates/store-card.json",
27
38
  "lib/passbook.rb",
28
39
  "lib/passbook/pkpass.rb",
29
40
  "lib/passbook/push_notification.rb",
@@ -31,11 +42,16 @@ Gem::Specification.new do |s|
31
42
  "lib/rack/passbook_rack.rb",
32
43
  "lib/rails/generators/passbook/config/config_generator.rb",
33
44
  "lib/rails/generators/passbook/config/templates/initializer.rb",
45
+ "lib/utils/command_utils.rb",
34
46
  "passbook.gemspec",
35
47
  "spec/data/icon.png",
36
48
  "spec/data/icon@2x.png",
37
49
  "spec/data/logo.png",
38
50
  "spec/data/logo@2x.png",
51
+ "spec/lib/commands/build_spec.rb",
52
+ "spec/lib/commands/commands_spec.rb",
53
+ "spec/lib/commands/commands_spec_helper.rb",
54
+ "spec/lib/commands/generate_spec.rb",
39
55
  "spec/lib/passbook/pkpass_spec.rb",
40
56
  "spec/lib/passbook/push_notification_spec.rb",
41
57
  "spec/lib/rack/passbook_rack_spec.rb",
@@ -44,15 +60,17 @@ Gem::Specification.new do |s|
44
60
  s.homepage = "http://github.com/frozon/passbook"
45
61
  s.licenses = ["MIT"]
46
62
  s.require_paths = ["lib"]
47
- s.rubygems_version = "1.8.24"
63
+ s.rubygems_version = "2.1.10"
48
64
  s.summary = "A IOS Passbook generator."
49
65
 
50
66
  if s.respond_to? :specification_version then
51
- s.specification_version = 3
67
+ s.specification_version = 4
52
68
 
53
69
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
54
- s.add_runtime_dependency(%q<rubyzip>, [">= 0"])
70
+ s.add_runtime_dependency(%q<rubyzip>, ["~> 1.0.0"])
55
71
  s.add_runtime_dependency(%q<grocer>, [">= 0"])
72
+ s.add_runtime_dependency(%q<commander>, [">= 0"])
73
+ s.add_runtime_dependency(%q<terminal-table>, [">= 0"])
56
74
  s.add_development_dependency(%q<rack-test>, [">= 0"])
57
75
  s.add_development_dependency(%q<activesupport>, [">= 0"])
58
76
  s.add_development_dependency(%q<jeweler>, [">= 0"])
@@ -61,8 +79,10 @@ Gem::Specification.new do |s|
61
79
  s.add_development_dependency(%q<rake>, [">= 0"])
62
80
  s.add_development_dependency(%q<yard>, [">= 0"])
63
81
  else
64
- s.add_dependency(%q<rubyzip>, [">= 0"])
82
+ s.add_dependency(%q<rubyzip>, ["~> 1.0.0"])
65
83
  s.add_dependency(%q<grocer>, [">= 0"])
84
+ s.add_dependency(%q<commander>, [">= 0"])
85
+ s.add_dependency(%q<terminal-table>, [">= 0"])
66
86
  s.add_dependency(%q<rack-test>, [">= 0"])
67
87
  s.add_dependency(%q<activesupport>, [">= 0"])
68
88
  s.add_dependency(%q<jeweler>, [">= 0"])
@@ -72,8 +92,10 @@ Gem::Specification.new do |s|
72
92
  s.add_dependency(%q<yard>, [">= 0"])
73
93
  end
74
94
  else
75
- s.add_dependency(%q<rubyzip>, [">= 0"])
95
+ s.add_dependency(%q<rubyzip>, ["~> 1.0.0"])
76
96
  s.add_dependency(%q<grocer>, [">= 0"])
97
+ s.add_dependency(%q<commander>, [">= 0"])
98
+ s.add_dependency(%q<terminal-table>, [">= 0"])
77
99
  s.add_dependency(%q<rack-test>, [">= 0"])
78
100
  s.add_dependency(%q<activesupport>, [">= 0"])
79
101
  s.add_dependency(%q<jeweler>, [">= 0"])
@@ -0,0 +1,92 @@
1
+ require 'lib/commands/commands_spec_helper'
2
+ require 'passbook'
3
+
4
+ describe 'Build' do
5
+
6
+ before :each do
7
+ $stderr = StringIO.new
8
+ mock_terminal
9
+ end
10
+
11
+ context 'command' do
12
+ specify 'missing directory' do
13
+ run_command 'build' do
14
+ @output.string.should eq "\e[31mMissing argument\e[0m\n"
15
+ end
16
+ end
17
+
18
+ context 'good directory' do
19
+
20
+ before :each do
21
+ File.should_receive(:directory?).with('scraps').and_return true
22
+ File.should_receive(:exist?).once.with('scraps/pass.json').and_return true
23
+ File.should_receive(:exist?).once.with('scraps.pkpass').and_return false
24
+ end
25
+
26
+ specify 'no certificate file' do
27
+ run_command 'build', 'scraps' do
28
+ @output.string.should eq "\e[31mMissing or invalid certificate file\e[0m\n"
29
+ end
30
+ end
31
+
32
+ context 'good certificate file' do
33
+ before :each do
34
+ File.should_receive(:exist?).with('jackels').and_return true
35
+ end
36
+
37
+ specify 'no certificate password entered' do
38
+ run_command 'build', 'scraps', '-w', 'jackels' do
39
+ @output.string.should eq "Enter certificate password:\n\n"
40
+ end
41
+ end
42
+
43
+ context 'all required values' do
44
+
45
+ let(:pass_json) {'{"this":"is awesome json"}'}
46
+ let(:pass_assets) {['pass.json', 'something.jpeg']}
47
+
48
+ before :each do
49
+ Passbook.should_receive(:wwdc_cert=).with 'jackels'
50
+ Passbook.should_receive(:p12_key=).with 'badger_key'
51
+ Passbook.should_receive(:p12_certificate=).with 'badger_cert'
52
+ Passbook.should_receive(:p12_password=).with 'bees'
53
+ CommandUtils.should_receive(:get_assets).with('scraps').and_return pass_assets
54
+ @pk_pass = double 'pk pass'
55
+ File.should_receive(:read).with('pass.json').and_return pass_json
56
+ Passbook::PKPass.should_receive(:new).with(pass_json).and_return @pk_pass
57
+ @pk_pass.should_receive(:addFiles).with pass_assets
58
+ end
59
+
60
+ specify 'are set from command line' do
61
+ pass_stream = double 'passbook stream'
62
+ pass_stream.should_receive(:string).and_return 'my badass pass'
63
+ @pk_pass.should_receive(:stream).and_return pass_stream
64
+ File.should_receive(:open).with('scraps.pkpass', 'w')
65
+
66
+ run_command 'build', 'scraps', '-w', 'jackels',
67
+ '-p', 'bees', '-k', 'badger_key', '-c', 'badger_cert' do
68
+ @output.string.should eq ""
69
+ end
70
+ end
71
+
72
+ specify 'should catch a general error' do
73
+ @pk_pass.should_receive(:stream).and_raise(StandardError.new('I have failed'))
74
+ run_command 'build', 'scraps', '-w', 'jackels',
75
+ '-p', 'bees', '-k', 'badger_key', '-c', 'badger_cert' do
76
+ @output.string.should eq "\e[31mError: I have failed\e[0m\n"
77
+ end
78
+ end
79
+
80
+ specify 'should catch a general error' do
81
+ @pk_pass.should_receive(:stream).and_raise(OpenSSL::PKCS12::PKCS12Error.new('I am a failure'))
82
+ run_command 'build', 'scraps', '-w', 'jackels',
83
+ '-p', 'bees', '-k', 'badger_key', '-c', 'badger_cert' do
84
+ @output.string.should eq "\e[31mError: I am a failure\e[0m\n\e[33mYou may be getting this error because the certificate password is either incorrect or missing\e[0m\n"
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ end
@@ -0,0 +1,102 @@
1
+ require 'lib/commands/commands_spec_helper'
2
+
3
+ describe 'Commands' do
4
+
5
+ before :each do
6
+ program :version, '1.2.3'
7
+ program :description, "Honey Badger Don't Care"
8
+ end
9
+
10
+ context 'determine directory' do
11
+
12
+ specify 'no directories' do
13
+ Dir.should_receive(:[]).and_return []
14
+ determine_directory!
15
+ @directory.should eq nil
16
+ end
17
+
18
+ specify 'one directory present' do
19
+ Dir.should_receive(:[]).and_return ['ass']
20
+ File.should_receive(:dirname).with('ass').and_return('/honey/badger/is/bad/ass')
21
+ determine_directory!
22
+ @directory.should eq '/honey/badger/is/bad/ass'
23
+ end
24
+
25
+ specify 'multiple directories' do
26
+ Dir.should_receive(:[]).and_return ['cobras', 'bee_larvae']
27
+ File.should_receive(:dirname).with('cobras').and_return('/yummy/cobras')
28
+ File.should_receive(:dirname).with('bee_larvae').and_return('/disgusting/bee_larvae')
29
+ self.should_receive(:choose).with('Select a directory:',
30
+ '/yummy/cobras', '/disgusting/bee_larvae').and_return '/yummy/cobras'
31
+ determine_directory!
32
+ @directory.should eq '/yummy/cobras'
33
+ end
34
+
35
+ end
36
+
37
+ context 'validate directory' do
38
+
39
+ specify 'missing directory' do
40
+ self.should_receive(:say_error).with('Missing argument').and_return true
41
+ lambda {
42
+ @directory = nil
43
+ validate_directory!
44
+ }.should exit_with_code(1)
45
+ end
46
+
47
+ specify 'directory does not exist' do
48
+ self.should_receive(:say_error).with("Directory scraps does not exist").and_return true
49
+ File.should_receive(:directory?).with('scraps').and_return false
50
+ lambda {
51
+ @directory = 'scraps'
52
+ validate_directory!
53
+ }.should exit_with_code(1)
54
+ end
55
+
56
+ specify 'directory does not have a valid pass' do
57
+ self.should_receive(:say_error).with("Directory scraps is not a valid pass").and_return true
58
+ File.should_receive(:directory?).with('scraps').and_return true
59
+ File.should_receive(:exist?).with('scraps/pass.json').and_return false
60
+ lambda {
61
+ @directory = 'scraps'
62
+ validate_directory!
63
+ }.should exit_with_code(1)
64
+ end
65
+
66
+ specify 'directory has valid pass' do
67
+ File.should_receive(:directory?).with('scraps').and_return true
68
+ File.should_receive(:exist?).with('scraps/pass.json').and_return true
69
+ lambda {
70
+ @directory = 'scraps'
71
+ validate_directory!
72
+ }.should_not exit_with_code(1)
73
+ end
74
+ end
75
+
76
+ context 'validate certificate' do
77
+ specify 'nil certificate' do
78
+ self.should_receive(:say_error).with("Missing or invalid certificate file").and_return true
79
+ lambda {
80
+ @certificate = nil
81
+ validate_certificate!
82
+ }.should exit_with_code(1)
83
+ end
84
+
85
+ specify 'certificate file does not exist' do
86
+ self.should_receive(:say_error).with("Missing or invalid certificate file").and_return true
87
+ File.should_receive(:exist?).with('jackels').and_return false
88
+ lambda {
89
+ @certificate = 'jackels'
90
+ validate_certificate!
91
+ }.should exit_with_code(1)
92
+ end
93
+
94
+ specify 'certificate file exists' do
95
+ File.should_receive(:exist?).with('jackels').and_return true
96
+ lambda {
97
+ @certificate = 'jackels'
98
+ validate_certificate!
99
+ }.should_not exit_with_code(1)
100
+ end
101
+ end
102
+ end