diff --git a/app/models/apple/api.rb b/app/models/apple/api.rb index 2c6f746db..622ab571b 100644 --- a/app/models/apple/api.rb +++ b/app/models/apple/api.rb @@ -18,16 +18,16 @@ def self.from_env end def self.from_apple_config(apple_config) - if apple_config.no_apple_credentials? + if apple_config.key.blank? Rails.logger.info("No Apple API keys in config object, falling back to environment default keys", {apple_credential_id: apple_config.id, podcast_id: apple_config.podcast_id, podcast_title: apple_config.podcast_title}) from_env else - new(provider_id: apple_config.apple_provider_id, - key_id: apple_config.apple_key_id, - key: apple_config.apple_key) + new(provider_id: apple_config.key.provider_id, + key_id: apple_config.key.key_id, + key: apple_config.key.key_pem) end end diff --git a/app/models/apple/config.rb b/app/models/apple/config.rb index 7c5284022..8feff016f 100644 --- a/app/models/apple/config.rb +++ b/app/models/apple/config.rb @@ -5,34 +5,30 @@ class Config < ApplicationRecord belongs_to :public_feed, class_name: "Feed" belongs_to :private_feed, class_name: "Feed" + belongs_to :key, class_name: "Apple::Key", optional: true + has_one :podcast, through: :public_feed delegate :title, to: :podcast, prefix: "podcast" delegate :id, to: :podcast, prefix: "podcast" + delegate :provider_id, to: :key + delegate :key_id, to: :key + delegate :key_pem, to: :key + delegate :key_pem_b64, to: :key + validates_presence_of :public_feed validates_presence_of :private_feed validates_associated :public_feed validates_associated :private_feed - validates_presence_of :apple_provider_id, if: :any_apple_credentials_exist? - validates_presence_of :apple_key_id, if: :any_apple_credentials_exist? - validates_presence_of :apple_key_pem_b64, if: :any_apple_credentials_exist? + validates :public_feed, uniqueness: {scope: :private_feed, message: "can only have one credential per public and private feed"} validates :public_feed, exclusion: {in: ->(apple_credential) { [apple_credential.private_feed] }} - validate :apple_provider_id_is_valid, if: :apple_provider_id? - - def apple_provider_id_is_valid - # ensure that it does not have an underscore - if apple_provider_id.include?("_") - errors.add(:apple_provider_id, "cannot contain an underscore") - end - end - def publish_to_apple? - return false unless apple_credentials? + return false unless key&.valid? public_feed.publish_to_apple?(self) end @@ -41,18 +37,6 @@ def build_publisher Apple::Publisher.from_apple_config(self) end - def apple_credentials? - apple_provider_id.present? && apple_key_id.present? && apple_key_pem_b64.present? - end - - def any_apple_credentials_exist? - apple_provider_id.present? || apple_key_id.present? || apple_key_pem_b64.present? - end - - def no_apple_credentials? - !any_apple_credentials_exist? - end - def apple_key Base64.decode64(apple_key_pem_b64) end diff --git a/app/models/apple/key.rb b/app/models/apple/key.rb new file mode 100644 index 000000000..790d124db --- /dev/null +++ b/app/models/apple/key.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Apple + class Key < ApplicationRecord + validates_presence_of :provider_id + validates_presence_of :key_id + validates_presence_of :key_pem_b64 + + validate :provider_id_is_valid, if: :provider_id? + + def provider_id_is_valid + if provider_id.include?("_") + errors.add(:provider_id, "cannot contain an underscore") + end + end + + def key_pem + Base64.decode64(key_pem_b64) + end + end +end diff --git a/db/migrate/20230719174404_extract_credentials.rb b/db/migrate/20230719174404_extract_credentials.rb new file mode 100644 index 000000000..a266f6c7b --- /dev/null +++ b/db/migrate/20230719174404_extract_credentials.rb @@ -0,0 +1,42 @@ +class ExtractCredentials < ActiveRecord::Migration[7.0] + def change + create_table :apple_keys do |t| + t.string :provider_id + t.string :key_id + t.text :key_pem_b64 + t.timestamps + end + + add_reference :apple_configs, :key, index: true + + reversible do |direction| + direction.up do + Apple::Config.distinct.pluck(:apple_provider_id, :apple_key_id, :apple_key_pem_b64).each do |(apple_provider_id, apple_key_id, apple_key_pem_b64)| + cred = Apple::Key.create!( + provider_id: apple_provider_id, + key_id: apple_key_id, + key_pem_b64: apple_key_pem_b64 + ) + + Apple::Config.where(apple_provider_id: apple_provider_id, apple_key_id: apple_key_id, apple_key_pem_b64: apple_key_pem_b64).each do |config| + config.update!(key_id: cred.id) + end + end + end + + direction.down do + Apple::Config.all.each do |config| + config.update!( + apple_key_id: config.key.key_id, + apple_provider_id: config.key.provider_id, + apple_key_pem_b64: config.key.key_pem_b64 + ) + end + end + end + + remove_column :apple_configs, :apple_provider_id, :text + remove_column :apple_configs, :apple_key_id, :text + remove_column :apple_configs, :apple_key_pem_b64, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 7a806c943..ac4b6c887 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_06_02_185438) do +ActiveRecord::Schema[7.0].define(version: 2023_07_19_174404) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" enable_extension "uuid-ossp" @@ -18,17 +18,24 @@ create_table "apple_configs", force: :cascade do |t| t.bigint "public_feed_id", null: false t.bigint "private_feed_id", null: false - t.string "apple_provider_id" - t.string "apple_key_id" - t.text "apple_key_pem_b64" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.boolean "publish_enabled", default: false, null: false t.boolean "sync_blocks_rss", default: false, null: false + t.bigint "key_id" + t.index ["key_id"], name: "index_apple_configs_on_key_id" t.index ["private_feed_id"], name: "index_apple_configs_on_private_feed_id" t.index ["public_feed_id"], name: "index_apple_configs_on_public_feed_id" end + create_table "apple_keys", force: :cascade do |t| + t.string "provider_id" + t.string "key_id" + t.text "key_pem_b64" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "apple_podcast_containers", force: :cascade do |t| t.integer "episode_id" t.string "external_id" diff --git a/ops/bin/dump_db.sh b/ops/bin/dump_db.sh index a1e60820d..8bf7e15bd 100755 --- a/ops/bin/dump_db.sh +++ b/ops/bin/dump_db.sh @@ -48,6 +48,7 @@ time pg_dump --verbose -Fc -h 127.0.0.1 \ --exclude-table-data 'public.say_when_job_executions' \ --exclude-table-data 'sessions' \ --exclude-table-data 'tasks' \ + --exclude-table-data 'apple_keys' \ -W -d "${DUMP_REMOTE_POSTGRES_DATABASE}" -U "$DUMP_REMOTE_POSTGRES_USER" -f "$OUTPUT_FILE" echo "Wrote: $OUTPUT_FILE" @@ -59,4 +60,4 @@ echo "" echo "DONE" echo "" echo "Now run 'setup_clone_db.sh' followed by 'load_db.sh'" -echo "" \ No newline at end of file +echo "" diff --git a/test/factories/apple_config_factory.rb b/test/factories/apple_config_factory.rb index cc1f0f248..e0e813f66 100644 --- a/test/factories/apple_config_factory.rb +++ b/test/factories/apple_config_factory.rb @@ -1,10 +1,8 @@ FactoryBot.define do factory :apple_config, class: Apple::Config do - apple_key_id { "some_key_id" } - apple_key_pem_b64 { Base64.encode64(test_file("/fixtures/apple_podcasts_connect_keyfile.pem")) } - apple_provider_id { SecureRandom.uuid } publish_enabled { true } sync_blocks_rss { true } + key { build(:apple_key) } transient do podcast { build(:podcast) } diff --git a/test/factories/apple_key_factory.rb b/test/factories/apple_key_factory.rb new file mode 100644 index 000000000..9a1c0c379 --- /dev/null +++ b/test/factories/apple_key_factory.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :key, class: Apple::Key, aliases: [:apple_key] do + key_id { "some_key_id" } + key_pem_b64 { Base64.encode64(test_file("/fixtures/apple_podcasts_connect_keyfile.pem")) } + provider_id { SecureRandom.uuid } + end +end diff --git a/test/models/apple/api_test.rb b/test/models/apple/api_test.rb index 60120a99a..0b2b60709 100644 --- a/test/models/apple/api_test.rb +++ b/test/models/apple/api_test.rb @@ -119,13 +119,13 @@ creds = build(:apple_config) api = Apple::Api.from_apple_config(creds) - assert_equal api.provider_id, creds.apple_provider_id - assert_equal api.key_id, creds.apple_key_id - assert_equal api.key, creds.apple_key + assert_equal api.provider_id, creds.provider_id + assert_equal api.key_id, creds.key_id + assert_equal api.key, creds.key_pem end it "falls back on the environment if the apple credential attributes are not set" do - creds = create(:apple_config, apple_provider_id: nil, apple_key_id: nil, apple_key_pem_b64: nil) + creds = create(:apple_config, key: nil) api = Apple::Api.from_apple_config(creds) assert_equal api.key_id, "apple key id from env" diff --git a/test/models/apple/config_test.rb b/test/models/apple/config_test.rb index bbcd31191..fca234492 100644 --- a/test/models/apple/config_test.rb +++ b/test/models/apple/config_test.rb @@ -16,20 +16,6 @@ refute c3.valid? end - it "requires apple key fields" do - pub = create(:feed, private: false) - priv = create(:feed, private: true) - c = build(:apple_config, public_feed: pub, private_feed: priv) - assert c.valid? - - c.apple_key_id = nil - refute c.valid? - - c.apple_key_id = "pears are better" - c.apple_key_pem_b64 = nil - refute c.valid? - end - it "is unique to a public and private feed" do f1 = create(:feed) f2 = create(:feed) @@ -51,42 +37,5 @@ c5 = build(:apple_config, public_feed: f1, private_feed: f1) refute c5.valid? end - - it "requires all apple credentials to have a value or be nil" do - f1 = create(:feed) - f2 = create(:feed) - - v1 = build(:apple_config, public_feed: f1, private_feed: f2, apple_provider_id: nil, apple_key_id: "blood", - apple_key_pem_b64: "orange") - refute v1.valid? - - v2 = build(:apple_config, public_feed: f1, private_feed: f2, apple_provider_id: "barlett", apple_key_id: nil, - apple_key_pem_b64: "pear") - refute v2.valid? - - v3 = build(:apple_config, public_feed: f1, private_feed: f2, apple_provider_id: "cotton candy", - apple_key_id: "grapes", apple_key_pem_b64: nil) - refute v3.valid? - - v4 = build(:apple_config, public_feed: f1, private_feed: f2, apple_provider_id: nil, apple_key_id: nil, - apple_key_pem_b64: nil) - assert v4.valid? - end - - it "requires the apple provider id to not have an underscore" do - f1 = create(:feed) - f2 = create(:feed) - - v1 = build(:apple_config, public_feed: f1, private_feed: f2, apple_provider_id: "foo_bar") - refute v1.valid? - assert_equal ["cannot contain an underscore"], v1.errors[:apple_provider_id] - end - end - - describe "apple_key" do - it "base64 decodes the apple key" do - c = Apple::Config.new(apple_key_pem_b64: Base64.encode64("hello")) - assert_equal c.apple_key, "hello" - end end end diff --git a/test/models/apple/key_test.rb b/test/models/apple/key_test.rb new file mode 100644 index 000000000..c7918b3d4 --- /dev/null +++ b/test/models/apple/key_test.rb @@ -0,0 +1,32 @@ +require "test_helper" + +describe Apple::Config do + describe "#valid?" do + it "requires all apple credentials to have a value or be nil" do + v1 = build(:apple_key, provider_id: nil, key_id: "blood", key_pem_b64: "orange") + refute v1.valid? + + v2 = build(:apple_key, provider_id: "barlett", key_id: nil, key_pem_b64: "pear") + refute v2.valid? + + v3 = build(:apple_key, provider_id: "cotton candy", key_id: "grapes", key_pem_b64: nil) + refute v3.valid? + + v4 = build(:apple_key, provider_id: nil, key_id: nil, key_pem_b64: nil) + refute v4.valid? + end + + it "requires the apple provider id to not have an underscore" do + v1 = build(:apple_key, provider_id: "foo_bar") + refute v1.valid? + assert_equal ["cannot contain an underscore"], v1.errors[:provider_id] + end + end + + describe "apple_key" do + it "base64 decodes the apple key" do + c = Apple::Key.new(key_pem_b64: Base64.encode64("hello")) + assert_equal c.key_pem, "hello" + end + end +end diff --git a/test/models/publishing_pipeline_state_test.rb b/test/models/publishing_pipeline_state_test.rb index de027e701..e0485f597 100644 --- a/test/models/publishing_pipeline_state_test.rb +++ b/test/models/publishing_pipeline_state_test.rb @@ -247,16 +247,12 @@ create(:apple_config, public_feed: f1, private_feed: f2, - publish_enabled: true, - apple_key_id: "valencia", - apple_key_pem_b64: "orange") + publish_enabled: true) create(:apple_config, public_feed: f1, private_feed: f3, - publish_enabled: true, - apple_key_id: "blood", - apple_key_pem_b64: "orange") + publish_enabled: true) end it "can publish via the apple configs" do