passbook2 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,75 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module Passbook
5
+ class Signer
6
+ attr_accessor :certificate,
7
+ :password,
8
+ :rsa_private_key,
9
+ :apple_intermediate_cert,
10
+ :p12_cert
11
+ attr_reader :key_hash
12
+
13
+ def initialize(params = {})
14
+
15
+ # Path to your X509 cert. This is downloaded after generating
16
+ # a certificate from your apple Pass Type ID on apple's developer site
17
+ @certificate = params[:certificate] || Passbook.certificate
18
+
19
+ # Path to the .pem file generated from public key of the RSA keypair
20
+ # that was generated when you made a Certificate Signing Request
21
+ # It'll be in your keychain under the "Common Name" you specified
22
+ # for the signing request.
23
+ @rsa_private_key = params[:rsa_private_key] || Passbook.rsa_private_key
24
+
25
+ # this should be the password that goes along with the rsa public key
26
+ @password = params[:password] || Passbook.password
27
+
28
+ # "Apple Intermediate Certificate Worldwide Developer Relations" certificate
29
+ # downloaded from here <https://www.apple.com/certificateauthority/>
30
+ # Path to your Apple Intermediate Certificate Worldwide Developer Relations
31
+ # cert.
32
+ # downloaded from here https://www.apple.com/certificateauthority/
33
+ # download that .cer file (binary)
34
+ @apple_intermediate_cert = params[:apple_intermediate_cert] || Passbook.apple_intermediate_cert
35
+ compute_cert
36
+ end
37
+
38
+ def sign(data)
39
+ apple_cert = OpenSSL::X509::Certificate.new file_data(apple_intermediate_cert)
40
+ # In PKCS#7 SignedData, attached and detached formats are supported… In
41
+ # detached format, data that is signed is not embedded inside the
42
+ # SignedData package instead it is placed at some external location…
43
+
44
+ pk7 = OpenSSL::PKCS7.sign(
45
+ key_hash[:certificate],
46
+ key_hash[:rsa_private_key],
47
+ data.to_s,
48
+ [apple_cert],
49
+ OpenSSL::PKCS7::BINARY | OpenSSL::PKCS7::DETACHED
50
+ )
51
+ pk7_data = OpenSSL::PKCS7.write_smime pk7
52
+
53
+ str_debut = "filename=\"smime.p7s\"\n\n"
54
+ pk7_data = pk7_data[pk7_data.index(str_debut)+str_debut.length..pk7_data.length-1]
55
+ str_end = "\n\n------"
56
+ pk7_data = pk7_data[0..pk7_data.index(str_end)-1]
57
+
58
+ Base64.decode64(pk7_data)
59
+ end
60
+
61
+ def compute_cert
62
+ @key_hash = {
63
+ rsa_private_key: OpenSSL::PKey::RSA.new(file_data(rsa_private_key), password),
64
+ certificate: OpenSSL::X509::Certificate.new(file_data(certificate))
65
+ }
66
+ end
67
+
68
+ def file_data(data)
69
+ raise "file_data passed nil" if data.nil?
70
+ return data if data.is_a? String
71
+
72
+ data.respond_to?(:read) ? data.read : File.read(data)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,3 @@
1
+ module Passbook
2
+ VERSION = "1.0.0"
3
+ end
data/lib/passbook2.rb ADDED
@@ -0,0 +1,21 @@
1
+ require "passbook/version"
2
+ require "passbook/pkpass"
3
+ require "passbook/pk_multi_pass"
4
+ require "passbook/signer"
5
+ require 'active_support/core_ext/module/attribute_accessors'
6
+ require 'passbook/push_notification'
7
+ require 'grocer'
8
+
9
+ module Passbook
10
+ mattr_accessor :certificate,
11
+ :password,
12
+ :apple_intermediate_cert,
13
+ :rsa_private_key,
14
+ :notification_cert,
15
+ :notification_gateway,
16
+ :notification_passphrase
17
+
18
+ def self.configure
19
+ yield self
20
+ end
21
+ end
data/passbook.gemspec ADDED
@@ -0,0 +1,59 @@
1
+ require_relative "lib/passbook/version"
2
+
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "passbook2"
6
+ s.version = Passbook::VERSION
7
+
8
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9
+ s.require_paths = ["lib"]
10
+ s.authors = ["Thomas Lauro", "Lance Gleason", "Kay Rhodes"]
11
+ s.date = "2024-06-11"
12
+ s.description = "This gem allows you to create Apple Passbook files."
13
+ s.email = ["thomas@lauro.fr", "lgleason@polyglotprogramminginc.com", "masukomi@masukomi.org"]
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".travis.yml",
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "LICENSE",
23
+ "README.md",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/passbook2.rb",
27
+ "lib/passbook/pk_multi_pass.rb",
28
+ "lib/passbook/pkpass.rb",
29
+ "lib/passbook/push_notification.rb",
30
+ "lib/passbook/signer.rb",
31
+ "lib/passbook/version.rb",
32
+ "passbook.gemspec",
33
+ "spec/data/icon.png",
34
+ "spec/data/icon@2x.png",
35
+ "spec/data/logo.png",
36
+ "spec/data/logo@2x.png",
37
+ "spec/lib/passbook/pk_multi_pass_spec.rb",
38
+ "spec/lib/passbook/pkpass_spec.rb",
39
+ "spec/lib/passbook/push_notification_spec.rb",
40
+ "spec/lib/passbook/signer_spec.rb",
41
+ "spec/spec_helper.rb"
42
+ ]
43
+ s.homepage = "https://github.com/masukomi/passbook2"
44
+ s.licenses = ["MIT"]
45
+ s.rubygems_version = "3.4.17"
46
+ s.summary = "An Apple Passbook file generator."
47
+
48
+ s.specification_version = 4
49
+
50
+ # runtime dependencies
51
+ s.add_dependency(%q<grocer>, [">= 0"])
52
+ s.add_dependency(%q<rubyzip>, [">= 1.0.0"])
53
+ s.add_dependency(%q<activesupport>.freeze, [">= 0"])
54
+
55
+ # development dependencies
56
+ s.add_development_dependency(%q<debug>, [">= 0"])
57
+ s.add_development_dependency(%q<rspec>, [">= 0"])
58
+ s.add_development_dependency(%q<yard>, [">= 0"])
59
+ end
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'tmpdir'
3
+ require 'zip'
4
+
5
+ describe Passbook do
6
+ let(:content) do
7
+ {
8
+ formatVersion: 1,
9
+ passTypeIdentifier: 'pass.passbook.test',
10
+ serialNumber: '001',
11
+ teamIdentifier: ENV['APPLE_TEAM_ID'],
12
+ organizationName: 'WorldCo',
13
+ description: 'description',
14
+ eventTicket: {
15
+ primaryFields: [
16
+ {
17
+ key: 'date',
18
+ label: 'DATE',
19
+ value: 'date'
20
+ }
21
+ ]
22
+ }
23
+ }
24
+ end
25
+
26
+ let(:signer) { double 'signer' }
27
+ let(:pass) { Passbook::PKPass.new( content.to_json, signer) }
28
+ let(:base_path) { 'spec/data' }
29
+ let(:entries) { ['pass.json', 'manifest.json', 'signature', 'icon.png', 'icon@2x.png', 'logo.png', 'logo@2x.png'] }
30
+ let(:passes){ [pass, pass] }
31
+
32
+ before :each do
33
+ pass.add_files(
34
+ [
35
+ "#{base_path}/icon.png",
36
+ "#{base_path}/icon@2x.png",
37
+ "#{base_path}/logo.png",
38
+ "#{base_path}/logo@2x.png"
39
+ ]
40
+ )
41
+ allow(signer).to(receive(:sign).and_return('Signed by the Honey Badger'))
42
+ end
43
+
44
+ describe ".create_multi_pass" do
45
+ it "should create a file where specified" do
46
+ Dir.mktmpdir do |dir|
47
+ temp_file = Passbook::PKMultiPass.create_multipass(
48
+ passes,
49
+ File.join(dir, "foo.pkpasses")
50
+ )
51
+ expect(File.exist?(temp_file)).to(eq(true))
52
+ end
53
+ end
54
+
55
+ it "should contain 2 files", :aggregate_failures do
56
+ Dir.mktmpdir do |dir|
57
+ temp_file = Passbook::PKMultiPass.create_multipass(
58
+ passes,
59
+ File.join(dir, "foo.pkpasses")
60
+ )
61
+ entries = Zip::File.open(temp_file).entries
62
+ expect(entries.count).to(eq(2))
63
+ expect(entries.map(&:name)).to(match_array(["1.pkpass", "2.pkpass"]))
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,169 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+ require 'tmpdir'
4
+
5
+ describe Passbook do
6
+ let(:content) do
7
+ {
8
+ formatVersion: 1,
9
+ passTypeIdentifier: 'pass.passbook.test',
10
+ serialNumber: '001',
11
+ teamIdentifier: ENV['APPLE_TEAM_ID'],
12
+ relevantDate: '2012-10-02',
13
+ locations: [ # TODO
14
+ {
15
+ longitude: 2.35403,
16
+ latitude: 48.893855
17
+ }
18
+ ],
19
+ organizationName: 'WorldCo',
20
+ description: 'description',
21
+ foregroundColor: 'rgb(227,210,18)',
22
+ backgroundColor: 'rgb(60, 65, 76)',
23
+ logoText: 'Event',
24
+ eventTicket: {
25
+ primaryFields: [
26
+ {
27
+ key: 'date',
28
+ label: 'DATE',
29
+ value: 'date'
30
+ }
31
+ ],
32
+ backFields: [
33
+ {
34
+ key: 'description',
35
+ label: 'DESCRIPTION',
36
+ value: 'description'
37
+ },
38
+ {
39
+ key: 'aboutUs',
40
+ label: 'MORE',
41
+ value: 'about us'
42
+ }
43
+ ]
44
+ }
45
+ }
46
+ end
47
+
48
+ let(:signer) { double 'signer' }
49
+ let(:pass) { Passbook::PKPass.new content.to_json, signer }
50
+ let(:base_path) { 'spec/data' }
51
+ let(:entries) { ['pass.json', 'manifest.json', 'signature', 'icon.png', 'icon@2x.png', 'logo.png', 'logo@2x.png'] }
52
+
53
+ before :each do
54
+ allow(signer).to(receive(:sign).and_return('Signed by the Honey Badger'))
55
+ end
56
+ describe '#file' do
57
+ context 'when adding a file as File' do
58
+ before do
59
+ pass.add_file(File.new("#{base_path}/icon.png"))
60
+ pass.add_files(
61
+ [
62
+ "#{base_path}/icon@2x.png",
63
+ "#{base_path}/logo.png",
64
+ "#{base_path}/logo@2x.png"
65
+ ].map { |x| File.new(x) }
66
+ )
67
+ end
68
+
69
+ it 'should work with no options', :aggregate_failures do
70
+ temp_file = pass.file
71
+ expect(File.basename(temp_file.path)).to(eq('pass.pkpass'))
72
+ expect(File.dirname(temp_file)).to(eq(Dir.tmpdir))
73
+ end
74
+ end
75
+ context 'when adding files as strings' do
76
+ before do
77
+ pass.add_file("#{base_path}/icon.png")
78
+ pass.add_files(
79
+ [
80
+ "#{base_path}/icon@2x.png",
81
+ "#{base_path}/logo.png",
82
+ "#{base_path}/logo@2x.png"
83
+ ]
84
+ )
85
+ end
86
+
87
+ it 'should work with no options', :aggregate_failures do
88
+ temp_file = pass.file
89
+ expect(File.basename(temp_file.path)).to(eq('pass.pkpass'))
90
+ expect(File.dirname(temp_file)).to(eq(Dir.tmpdir))
91
+ end
92
+ it 'should honor the file_name specified' do
93
+ temp_file = pass.file(file_name: 'foo.pkpass')
94
+ expect(File.basename(temp_file)).to(eq('foo.pkpass'))
95
+ end
96
+
97
+ it 'should honor the directory specified as a string' do
98
+ Dir.mktmpdir do |dir| # dir is a String
99
+ temp_file = pass.file(directory: dir)
100
+ expect(File.dirname(temp_file)).to(eq(dir))
101
+ end
102
+ end
103
+
104
+ it 'should honor the directory specified as a Dir' do
105
+ Dir.mktmpdir do |dir|
106
+ temp_file = pass.file(directory: Dir.new(dir))
107
+ expect(File.dirname(temp_file)).to(eq(dir))
108
+ end
109
+ end
110
+ context 'outputs' do
111
+ before do
112
+ @file_entries = []
113
+ Zip::InputStream.open(zip_path) do |io|
114
+ while (entry = io.get_next_entry)
115
+ @file_entries << entry.name
116
+ end
117
+ end
118
+ end
119
+
120
+ context 'zip file' do
121
+ let(:zip_path) { pass.file.path }
122
+
123
+ it 'should have the expected files' do
124
+ expect(entries).to(eq(@file_entries))
125
+ end
126
+ end
127
+
128
+ context 'StringIO' do
129
+ let(:temp_file) { Tempfile.new('pass.pkpass') }
130
+ let(:zip_path) do
131
+ zip_out = pass.stream
132
+ expect(zip_out.class).to(eq(StringIO))
133
+ # creating file, re-reading zip to see if correctly formed
134
+ temp_file.write zip_out.string
135
+ temp_file.close
136
+ temp_file.path
137
+ end
138
+
139
+ it 'should contain the expected files' do
140
+ expect(entries).to(eq(@file_entries))
141
+ end
142
+
143
+ after do
144
+ temp_file.delete
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ # TODO: find a proper way to do this
152
+ context 'Error catcher' do
153
+ context 'formatVersion' do
154
+ let(:base_path) { 'spec/data' }
155
+
156
+ before :each do
157
+ pass.add_files ["#{base_path}/icon.png", "#{base_path}/icon@2x.png", "#{base_path}/logo.png",
158
+ "#{base_path}/logo@2x.png"]
159
+ tpass = JSON.parse(pass.pass)
160
+ tpass['formatVersion'] = 'It should be a numeric'
161
+ pass.pass = tpass.to_json
162
+ end
163
+
164
+ it 'raise an error' do
165
+ expect { pass.build }.to raise_error('Format Version should be a numeric')
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require 'grocer'
3
+
4
+ describe Passbook::PushNotification do
5
+
6
+ context 'send notification' do
7
+ let(:grocer_pusher) {double 'Grocer'}
8
+ let(:notification) {double 'Grocer::Notification'}
9
+ let(:notification_settings) {{:certificate => './notification_cert.pem', :gateway => 'honeybadger.apple.com', :passphrase => 'ah@rdvintAge'}}
10
+
11
+ before :each do
12
+ allow(Passbook).to(receive(:notification_cert).and_return('./notification_cert.pem'))
13
+ allow(Grocer::PassbookNotification).to(receive(:new).with(:device_token => 'my token').and_return(notification))
14
+ allow(grocer_pusher).to(receive(:push).with(notification).and_return(55))
15
+ allow(Grocer).to(receive(:pusher).with(notification_settings).and_return(grocer_pusher))
16
+ allow(Passbook).to(receive(:notification_gateway).and_return('honeybadger.apple.com'))
17
+ allow(Passbook).to(receive(:notification_passphrase).and_return('ah@rdvintAge'))
18
+ end
19
+
20
+ subject {Passbook::PushNotification.send_notification('my token')}
21
+ it {should eq 55}
22
+ end
23
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Signer' do
4
+ context 'signatures' do
5
+
6
+ context 'using default config info' do
7
+ before do
8
+ expect(Passbook).to(receive(:password).and_return 'password')
9
+ expect(Passbook).to(receive(:rsa_private_key).and_return 'my_rsa_key')
10
+ expect(Passbook).to(receive(:certificate).and_return 'my_X509_certificate')
11
+ expect(Passbook).to(
12
+ receive(:apple_intermediate_cert)
13
+ .and_return( 'apple_intermediate_cert_file')
14
+ )
15
+ end
16
+
17
+ context "getting the default config" do
18
+ context "ignoring signing" do
19
+ before do
20
+ # don't care if signing works yet. just dealing with defaults
21
+ allow_any_instance_of(Passbook::Signer).to(receive(:compute_cert))
22
+ end
23
+
24
+ let(:signer){
25
+ Passbook::Signer.new
26
+ }
27
+ it "should contain rsa_private_key" do
28
+ expect(signer.rsa_private_key).to(eq('my_rsa_key'))
29
+ end
30
+ it "should contain certificate" do
31
+ expect(signer.certificate).to(eq('my_X509_certificate'))
32
+ end
33
+ it "should contain apple_intermediate_cert" do
34
+ expect(signer.apple_intermediate_cert).to(eq('apple_intermediate_cert_file'))
35
+ end
36
+ it "should contain password" do
37
+ expect(signer.password).to(eq('password'))
38
+ end
39
+ end
40
+ end
41
+ context "computing cert" do
42
+ before do
43
+ expect(OpenSSL::PKey::RSA).to(
44
+ receive(:new)
45
+ .with('my_rsa_key', 'password')
46
+ .and_return 'my_rsa_key' # bogus example
47
+ )
48
+
49
+ expect(OpenSSL::X509::Certificate).to(
50
+ receive(:new)
51
+ .with('my_X509_certificate')
52
+ .and_return 'my_X509_data' # bogus example
53
+ )
54
+ end
55
+ context "signing" do
56
+ let(:signer){Passbook::Signer.new}
57
+
58
+ # this is just checking that
59
+ # the inputs and outputs of the cert things
60
+ # are all wired together correctly
61
+ it "should do the signing dance" do
62
+
63
+ # 2nd X509 cert loading...
64
+ expect(OpenSSL::X509::Certificate).to(
65
+ receive(:new)
66
+ .with('apple_intermediate_cert_file')
67
+ .and_return 'apples_X509_data' # bogus example
68
+ )
69
+
70
+ expect(OpenSSL::PKCS7).to(
71
+ receive(:sign)
72
+ .with(
73
+ 'my_X509_data', # from above
74
+ 'my_rsa_key', # also from above
75
+ 'passbook_sign_data', # the param to Signer#sign(...)
76
+ ['apples_X509_data'],
77
+ OpenSSL::PKCS7::BINARY | OpenSSL::PKCS7::DETACHED
78
+ )
79
+ .and_return('pk7') # bogus example
80
+ )
81
+ expect(OpenSSL::PKCS7).to(
82
+ receive(:write_smime)
83
+ .with('pk7')
84
+ .and_return("filename=\"smime.p7s\"\n\nsecret_pk7_stuff\n\n------")
85
+ )
86
+ expect(Base64).to(
87
+ receive(:decode64)
88
+ .with('secret_pk7_stuff') #from above
89
+ )
90
+
91
+ # and... kick it all off
92
+ signer.sign('passbook_sign_data')
93
+
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'json'
4
+ require 'grocer'
5
+ require 'debug'
6
+
7
+ Dir['lib/passbook/**/*.rb'].each {|f| require File.join(File.dirname(__FILE__), '..', f.gsub(/.rb/, ''))}
8
+ Dir['lib/rack/**/*.rb'].each {|f| require File.join(File.dirname(__FILE__), '..', f.gsub(/.rb/, ''))}
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: passbook2
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Lauro
8
+ - Lance Gleason
9
+ - Kay Rhodes
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2024-06-11 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: grocer
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rubyzip
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 1.0.0
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.0.0
43
+ - !ruby/object:Gem::Dependency
44
+ name: activesupport
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: debug
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ - !ruby/object:Gem::Dependency
72
+ name: rspec
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ - !ruby/object:Gem::Dependency
86
+ name: yard
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ description: This gem allows you to create Apple Passbook files.
100
+ email:
101
+ - thomas@lauro.fr
102
+ - lgleason@polyglotprogramminginc.com
103
+ - masukomi@masukomi.org
104
+ executables: []
105
+ extensions: []
106
+ extra_rdoc_files:
107
+ - LICENSE
108
+ - README.md
109
+ files:
110
+ - ".travis.yml"
111
+ - Gemfile
112
+ - Gemfile.lock
113
+ - LICENSE
114
+ - README.md
115
+ - Rakefile
116
+ - VERSION
117
+ - lib/passbook/pk_multi_pass.rb
118
+ - lib/passbook/pkpass.rb
119
+ - lib/passbook/push_notification.rb
120
+ - lib/passbook/signer.rb
121
+ - lib/passbook/version.rb
122
+ - lib/passbook2.rb
123
+ - passbook.gemspec
124
+ - spec/data/icon.png
125
+ - spec/data/icon@2x.png
126
+ - spec/data/logo.png
127
+ - spec/data/logo@2x.png
128
+ - spec/lib/passbook/pk_multi_pass_spec.rb
129
+ - spec/lib/passbook/pkpass_spec.rb
130
+ - spec/lib/passbook/push_notification_spec.rb
131
+ - spec/lib/passbook/signer_spec.rb
132
+ - spec/spec_helper.rb
133
+ homepage: https://github.com/masukomi/passbook2
134
+ licenses:
135
+ - MIT
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubygems_version: 3.4.20
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: An Apple Passbook file generator.
156
+ test_files: []