Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional configuration without environment variables #36

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ If you followed the installation steps, you already saw that Passkit provides
you the tables and ActiveRecord models, and also an engine with the necessary APIs already implemented.

Now is your turn. Before proceeding, you need to set these ENV variables:

* `PASSKIT_WEB_SERVICE_HOST`
* `PASSKIT_CERTIFICATE_KEY`
* `PASSKIT_PRIVATE_P12_CERTIFICATE`
Expand All @@ -68,6 +69,26 @@ 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.

Alternatively, you can configure passkit with an initializer, where you can use environment variables, Rails secrets,
or any other source for the required credentials:

```ruby
Passkit.configure do |config|
# Required, no defaults
config.apple_team_identifier = "dummy ID"
config.certificate_key = "dummy key"
config.private_p12_certificate = "path/to/file"
config.apple_intermediate_certificate = "path/to/file"
config.pass_type_identifier = "pass.com.some.id"

# Optional, defaults shown
config.dashboard_username = nil
config.dashboard_password = nil
config.skip_verification = false # Unless true, throws exceptions on startup when a required configuration is missing
config.web_service_host = "https://localhost:3000"
config.available_passes = { "Passkit::ExampleStoreCard" => -> {} }
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.
Expand Down
58 changes: 48 additions & 10 deletions lib/passkit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class << self
def self.configure
self.configuration ||= Configuration.new
yield(configuration) if block_given?
configuration.verify!
end

def self.configured?
self.configuration&.configured?
end
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've thrown that in here since we use it in our app to conditionally display passkit-related view code only if the gem is properly configured.


class Configuration
Expand All @@ -27,11 +32,24 @@ class Configuration
:private_p12_certificate,
:apple_intermediate_certificate,
:apple_team_identifier,
:pass_type_identifier
:pass_type_identifier,
:dashboard_username,
:dashboard_password,
:format_version,
:skip_verification

REQUIRED_ATTRIBUTES = %i[
web_service_host
certificate_key
private_p12_certificate
apple_intermediate_certificate
apple_team_identifier
pass_type_identifier
]

DEFAULT_AUTHENTICATION = proc do
authenticate_or_request_with_http_basic("Passkit Dashboard. Login required") do |username, password|
username == ENV["PASSKIT_DASHBOARD_USERNAME"] && password == ENV["PASSKIT_DASHBOARD_PASSWORD"]
username == Passkit.configuration.dashboard_username && password == Passkit.configuration.dashboard_password
end
end
def authenticate_dashboard_with(&block)
Expand All @@ -40,14 +58,34 @@ def authenticate_dashboard_with(&block)
end

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")
# Required
@certificate_key = ENV["PASSKIT_CERTIFICATE_KEY"]
@private_p12_certificate = ENV["PASSKIT_PRIVATE_P12_CERTIFICATE"]
@apple_intermediate_certificate = ENV["PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE"]
@apple_team_identifier = ENV["PASSKIT_APPLE_TEAM_IDENTIFIER"]
@pass_type_identifier = ENV["PASSKIT_PASS_TYPE_IDENTIFIER"]

# Optional
@skip_verification = false
@web_service_host = ENV["PASSKIT_WEB_SERVICE_HOST"] || "https://localhost:3000"
@available_passes = { "Passkit::ExampleStoreCard" => -> {} }
@format_version = ENV["PASSKIT_FORMAT_VERSION"] || 1
@dashboard_username = ENV["PASSKIT_DASHBOARD_USERNAME"]
@dashboard_password = ENV["PASSKIT_DASHBOARD_PASSWORD"]
end

def configured?
REQUIRED_ATTRIBUTES.all? { |attr| send(attr).present? }
end

def verify!
return if skip_verification

REQUIRED_ATTRIBUTES.each do |attr|
raise Error, "Please set #{attr.upcase}" unless send(attr).present?
end

raise Error, "PASSKIT_WEB_SERVICE_HOST must start with https://" unless web_service_host.start_with?("https://")
end
end
end
9 changes: 4 additions & 5 deletions lib/passkit/base_pass.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ def initialize(generator = nil)
end

def format_version
ENV["PASSKIT_FORMAT_VERSION"] || 1
Passkit.configuration.format_version
end

def apple_team_identifier
ENV["PASSKIT_APPLE_TEAM_IDENTIFIER"] || raise(Error.new("Missing environment variable: PASSKIT_APPLE_TEAM_IDENTIFIER"))
Passkit.configuration.apple_team_identifier
end

def pass_type_identifier
ENV["PASSKIT_PASS_TYPE_IDENTIFIER"] || raise(Error.new("Missing environment variable: PASSKIT_PASS_TYPE_IDENTIFIER"))
Passkit.configuration.pass_type_identifier
end

def language
Expand Down Expand Up @@ -43,8 +43,7 @@ def pass_type
end

def web_service_url
raise Error.new("Missing environment variable: PASSKIT_WEB_SERVICE_HOST") unless ENV["PASSKIT_WEB_SERVICE_HOST"]
"#{ENV["PASSKIT_WEB_SERVICE_HOST"]}/passkit/api"
"#{Passkit.configuration.web_service_host}/passkit/api"
end

# The foreground color, used for the values of fields shown on the front of the pass.
Expand Down
16 changes: 9 additions & 7 deletions lib/passkit/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@ def generate_json_pass
File.write(@temporary_path.join("pass.json"), pass.to_json)
end

# rubocop:enable Metrics/AbcSize

def generate_json_manifest
manifest = {}
Dir.glob(@temporary_path.join("**")).each do |file|
Expand All @@ -123,14 +121,18 @@ 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"])
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constants are evaluated at load time, not runtime, so if those envs are nil, this will raise an error at startup of a rails app with eager_load = true. This bit us in production no less, because in staging we had everything configured correctly, and in local environment classes are not eager loaded, so it didn't manifest there.

CERTIFICATE_PASSWORD = ENV["PASSKIT_CERTIFICATE_KEY"]
def certificate_path
Rails.root.join(Passkit.configuration.private_p12_certificate)
end

def intermediate_certificate_path
Rails.root.join(Passkit.configuration.apple_intermediate_certificate)
end

# :nocov:
def sign_manifest
p12_certificate = OpenSSL::PKCS12.new(File.read(CERTIFICATE), CERTIFICATE_PASSWORD)
intermediate_certificate = OpenSSL::X509::Certificate.new(File.read(INTERMEDIATE_CERTIFICATE))
p12_certificate = OpenSSL::PKCS12.new(File.read(certificate_path), Passkit.configuration.certificate_key)
intermediate_certificate = OpenSSL::X509::Certificate.new(File.read(intermediate_certificate_path))

flag = OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY
signed = OpenSSL::PKCS7.sign(p12_certificate.certificate,
Expand Down
2 changes: 1 addition & 1 deletion lib/passkit/url_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class UrlGenerator
include Passkit::Engine.routes.url_helpers

def initialize(pass_class, generator = nil, collection_name = nil)
@url = passes_api_url(host: ENV["PASSKIT_WEB_SERVICE_HOST"],
@url = passes_api_url(host: Passkit.configuration.web_service_host,
payload: PayloadGenerator.encrypted(pass_class, generator, collection_name))
end

Expand Down