diff --git a/README.md b/README.md index da56f66..a390e4b 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,6 @@ you the tables and ActiveRecord models, and also an engine with the necessary AP Now is your turn. Before proceeding, you need to set these ENV variables: * `PASSKIT_WEB_SERVICE_HOST` -* `PASSKIT_CERTIFICATE_KEY` -* `PASSKIT_PRIVATE_P12_CERTIFICATE` * `PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE` * `PASSKIT_APPLE_TEAM_IDENTIFIER` * `PASSKIT_PASS_TYPE_IDENTIFIER` @@ -68,9 +66,24 @@ Now is your turn. Before proceeding, you need to set these ENV variables: We have a [specific guide on how to get all these](docs/passkit_environment_variables.md), please follow it. You cannot start using this library without these variables set, and we cannot do the work for you. +### Access to certificate data + +Use the database or environment variables to access certificate data. +By default, the certificate and its password will be sourced from the database. +If you want to use environment variables, set: +* `PASSKIT_CERTIFICATE_KEY` +* `PASSKIT_CERTIFICATE_PASSWORD` + +and update the configuration as follows: +```ruby +Passkit.configure do |config| + config.use_database_for_certificates = false +end +``` + ## Usage -If you followed the installation steps and you have the ENV variables set, we can start looking at what is provided for you. +If you followed the installation steps, we can start looking at what is provided for you. ### Dashboard @@ -120,7 +133,7 @@ Again, looking at these examples, is the easiest way to get started. ### Create your own Wallet Pass -You can create your own Wallet Passes by creating a new class that inherits from `Passkit::BasePass` and +You can create your own Wallet Passes by creating a new class that inherits from `Passkit::BasePass` and defining the methods that you want to override. You can define colors, fields and texts. You can also define the logo and the background image. @@ -152,11 +165,11 @@ Passkit::UrlGenerator.new(Passkit::UserTicket, User.find(1), :tickets) and then use `.android` or `.ios` to get the URL to serve the Wallet Pass. Again, check the example mailer included in the gem to see how to use it. -## Debug issues +## Debug issues * On Mac, you can open the *.pkpass files with "Pass Viewer". Open the `Console.app` to log possible error messages and filter by "Pass Viewer" process. * Check the logs on http://localhost:3000/passkit/dashboard/logs -* In case of error "The passTypeIdentifier or teamIdentifier provided may not match your certificate, +* In case of error "The passTypeIdentifier or teamIdentifier provided may not match your certificate, or the certificate trust chain could not be verified." the certificate (p12) might be expired. diff --git a/app/controllers/passkit/api/v1/passes_controller.rb b/app/controllers/passkit/api/v1/passes_controller.rb index 302d755..322479a 100644 --- a/app/controllers/passkit/api/v1/passes_controller.rb +++ b/app/controllers/passkit/api/v1/passes_controller.rb @@ -9,12 +9,12 @@ def create if @generator && @payload[:collection_name].present? files = @generator.public_send(@payload[:collection_name]).collect do |collection_item| - Passkit::Factory.create_pass(@payload[:pass_class], collection_item) + Passkit::Factory.create_pass(@payload[:pass_class], generator: collection_item, site_id: params[:site_id]) end file = Passkit::Generator.compress_passes_files(files) send_file(file, type: 'application/vnd.apple.pkpasses', disposition: 'attachment') else - file = Passkit::Factory.create_pass(@payload[:pass_class], @generator) + file = Passkit::Factory.create_pass(@payload[:pass_class], generator: @generator, site_id: params[:site_id]) send_file(file, type: 'application/vnd.apple.pkpass', disposition: 'attachment') end end @@ -35,11 +35,10 @@ def show return end - pass_output_path = Passkit::Generator.new(pass).generate_and_sign - response.headers["last-modified"] = pass.last_update.httpdate if request.headers["If-Modified-Since"].nil? || (pass.last_update.to_i > Time.zone.parse(request.headers["If-Modified-Since"]).to_i) + pass_output_path = Passkit::Generator.new(pass).generate_and_sign send_file(pass_output_path, type: "application/vnd.apple.pkpass", disposition: "attachment") else head :not_modified diff --git a/app/controllers/passkit/api/v1/registrations_controller.rb b/app/controllers/passkit/api/v1/registrations_controller.rb index 99e82c0..add5f02 100644 --- a/app/controllers/passkit/api/v1/registrations_controller.rb +++ b/app/controllers/passkit/api/v1/registrations_controller.rb @@ -7,7 +7,7 @@ module V1 # @see Android: https://walletpasses.io/developer/ class RegistrationsController < ActionController::API before_action :load_pass, only: %i[create destroy] - before_action :load_device, only: %i[show] + before_action :load_device, only: %i[show destroy] # @return If the serial number is already registered for this device, returns HTTP status 200. # @return If registration succeeds, returns HTTP status 201. @@ -48,8 +48,10 @@ def show # @return If the request is not authorized, returns HTTP status 401. # @return Otherwise, returns the appropriate standard HTTP status. def destroy - registrations = @pass.registrations.where(passkit_device_id: params[:device_id]) - registrations.delete_all + render json: {}, status: :ok unless @device + + registrations = @pass.registrations.where(passkit_device_id: @device.id) + registrations.destroy_all render json: {}, status: :ok end diff --git a/app/models/passkit/certificate.rb b/app/models/passkit/certificate.rb new file mode 100644 index 0000000..2011302 --- /dev/null +++ b/app/models/passkit/certificate.rb @@ -0,0 +1,4 @@ +module Passkit + class Certificate < ActiveRecord::Base + end +end diff --git a/lib/passkit.rb b/lib/passkit.rb index 0358c44..78197a4 100644 --- a/lib/passkit.rb +++ b/lib/passkit.rb @@ -27,7 +27,8 @@ class Configuration :private_p12_certificate, :apple_intermediate_certificate, :apple_team_identifier, - :pass_type_identifier + :pass_type_identifier, + :use_database_for_certificates DEFAULT_AUTHENTICATION = proc do authenticate_or_request_with_http_basic("Passkit Dashboard. Login required") do |username, password| @@ -43,11 +44,23 @@ def initialize @available_passes = {"Passkit::ExampleStoreCard" => -> {}} @web_service_host = ENV["PASSKIT_WEB_SERVICE_HOST"] || (raise "Please set PASSKIT_WEB_SERVICE_HOST") raise("PASSKIT_WEB_SERVICE_HOST must start with https://") unless @web_service_host.start_with?("https://") - @certificate_key = ENV["PASSKIT_CERTIFICATE_KEY"] || (raise "Please set PASSKIT_CERTIFICATE_KEY") - @private_p12_certificate = ENV["PASSKIT_PRIVATE_P12_CERTIFICATE"] || (raise "Please set PASSKIT_PRIVATE_P12_CERTIFICATE") @apple_intermediate_certificate = ENV["PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE"] || (raise "Please set PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE") @apple_team_identifier = ENV["PASSKIT_APPLE_TEAM_IDENTIFIER"] || (raise "Please set PASSKIT_APPLE_TEAM_IDENTIFIER") @pass_type_identifier = ENV["PASSKIT_PASS_TYPE_IDENTIFIER"] || (raise "Please set PASSKIT_PASS_TYPE_IDENTIFIER") + @use_database_for_certificates = true + validate_certificates + end + + def use_database_for_certificates=(value) + @use_database_for_certificates = value + validate_certificates + end + + def validate_certificates + unless use_database_for_certificates + @private_p12_certificate = ENV["PASSKIT_PRIVATE_P12_CERTIFICATE"] || (raise "Please set PASSKIT_PRIVATE_P12_CERTIFICATE") + @certificate_key = ENV["PASSKIT_CERTIFICATE_KEY"] || (raise "Please set PASSKIT_CERTIFICATE_KEY") + end end end end diff --git a/lib/passkit/certificate_sources/certificate_source.rb b/lib/passkit/certificate_sources/certificate_source.rb new file mode 100644 index 0000000..e37ec38 --- /dev/null +++ b/lib/passkit/certificate_sources/certificate_source.rb @@ -0,0 +1,9 @@ +module Passkit::CertificateSources::CertificateSource + def certificate + raise NotImplementedError + end + + def password + raise NotImplementedError + end +end diff --git a/lib/passkit/certificate_sources/database.rb b/lib/passkit/certificate_sources/database.rb new file mode 100644 index 0000000..76bf167 --- /dev/null +++ b/lib/passkit/certificate_sources/database.rb @@ -0,0 +1,22 @@ +class Passkit::CertificateSources::Database + include Passkit::CertificateSources::CertificateSource + + def initialize(site_id) + @site_id = site_id + end + + def certificate + certificate_record.certificate + end + + def password + certificate_record.secret + end + + private + + def certificate_record + @certificate_record ||= Passkit::Certificate.find_by!(site_id: @site_id) + end +end + diff --git a/lib/passkit/certificate_sources/env.rb b/lib/passkit/certificate_sources/env.rb new file mode 100644 index 0000000..b42ba34 --- /dev/null +++ b/lib/passkit/certificate_sources/env.rb @@ -0,0 +1,12 @@ +class Passkit::CertificateSources::Env + include Passkit::CertificateSources::CertificateSource + + def certificate + Rails.root.join(ENV["PASSKIT_PRIVATE_P12_CERTIFICATE"]) + end + + def password + ENV["PASSKIT_CERTIFICATE_KEY"] + end +end + diff --git a/lib/passkit/certificate_sources/factory.rb b/lib/passkit/certificate_sources/factory.rb new file mode 100644 index 0000000..2331dae --- /dev/null +++ b/lib/passkit/certificate_sources/factory.rb @@ -0,0 +1,9 @@ +class Passkit::CertificateSources::Factory + def self.find_source(site_id) + if Passkit.configuration.use_database_for_certificates + Passkit::CertificateSources::Database.new(site_id) + else + Passkit::CertificateSources::Env.new + end + end +end diff --git a/lib/passkit/factory.rb b/lib/passkit/factory.rb index 791bc47..0964221 100644 --- a/lib/passkit/factory.rb +++ b/lib/passkit/factory.rb @@ -2,8 +2,11 @@ module Passkit class Factory class << self # generator is an optional ActiveRecord object, the application data for the pass - def create_pass(pass_class, generator = nil) - pass = Passkit::Pass.create!(klass: pass_class, generator: generator) + def create_pass(pass_class, generator: nil, site_id: nil) + attributes = { klass: pass_class, generator: generator } + attributes[:site_id] = site_id if site_id + + pass = Passkit::Pass.find_or_create_by!(attributes) Passkit::Generator.new(pass).generate_and_sign end end diff --git a/lib/passkit/generator.rb b/lib/passkit/generator.rb index fa812a1..0cb30c3 100644 --- a/lib/passkit/generator.rb +++ b/lib/passkit/generator.rb @@ -10,6 +10,7 @@ def initialize(pass) end def generate_and_sign + @pass.instance.prepare_files check_necessary_files create_temporary_directory copy_pass_to_tmp_location @@ -98,6 +99,10 @@ def generate_json_pass pass[:semantics] = @pass.semantics if @pass.semantics pass[:userInfo] = @pass.user_info if @pass.user_info + unless @pass[:sharing_prohibited] + pass[:sharing] = @pass.sharing if @pass.sharing + end + pass[@pass.pass_type] = { headerFields: @pass.header_fields, primaryFields: @pass.primary_fields, @@ -123,13 +128,11 @@ def generate_json_manifest File.write(@manifest_url, manifest.to_json) end - CERTIFICATE = Rails.root.join(ENV["PASSKIT_PRIVATE_P12_CERTIFICATE"]) INTERMEDIATE_CERTIFICATE = Rails.root.join(ENV["PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE"]) - CERTIFICATE_PASSWORD = ENV["PASSKIT_CERTIFICATE_KEY"] # :nocov: def sign_manifest - p12_certificate = OpenSSL::PKCS12.new(File.read(CERTIFICATE), CERTIFICATE_PASSWORD) + p12_certificate = OpenSSL::PKCS12.new(File.read(certificate_source.certificate), certificate_source.password) intermediate_certificate = OpenSSL::X509::Certificate.new(File.read(INTERMEDIATE_CERTIFICATE)) flag = OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY @@ -155,5 +158,9 @@ def compress_pass_file end zip_path end + + def certificate_source + @certificate_source ||= Passkit::CertificateSources::Factory.find_source(@pass.site_id) + end end end