From 3d9adde448de9bef271bc890a29a94889ee89a84 Mon Sep 17 00:00:00 2001 From: Bart de Water <496367+bdewater@users.noreply.github.com> Date: Sat, 28 Dec 2019 19:57:55 -0500 Subject: [PATCH 1/5] Set `expires_in` and `race_condition_ttl` options during caching --- CHANGELOG.md | 5 +++++ lib/fido_metadata.rb | 7 ++++++- lib/fido_metadata/store.rb | 13 +++++++++++-- lib/fido_metadata/table_of_contents.rb | 4 ++++ spec/store_spec.rb | 8 +++++++- spec/table_of_contents_spec.rb | 22 ++++++++++++++++++++++ 6 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 spec/table_of_contents_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index fa01243..9833c0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.0] - 2019-12-28 +### Added +- Set `expires_in` and `race_condition_ttl` options during caching. + ## [0.3.0] - 2019-11-24 ### Changed - Made `FidoMetada::TestCacheStore` available for gem users. It is not required by default. @@ -26,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extracted from [webauthn-ruby PR 208](https://github.com/cedarcode/webauthn-ruby/pull/208) after discussion with the maintainers. Thanks for the feedback @grzuy and @brauliomartinezlm! [Unreleased]: https://github.com/bdewater/fido_metadata/compare/v0.2.0...HEAD +[0.4.0]: https://github.com/bdewater/fido_metadata/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/bdewater/fido_metadata/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/bdewater/fido_metadata/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/bdewater/fido_metadata/releases/tag/v0.1.0 diff --git a/lib/fido_metadata.rb b/lib/fido_metadata.rb index 569b2c5..490b77b 100644 --- a/lib/fido_metadata.rb +++ b/lib/fido_metadata.rb @@ -5,7 +5,11 @@ module FidoMetadata def self.configuration - @configuration ||= Configuration.new + @configuration ||= begin + c = Configuration.new + c.race_condition_ttl = 1 + c + end end def self.configure @@ -15,5 +19,6 @@ def self.configure class Configuration attr_accessor :metadata_token attr_accessor :cache_backend + attr_accessor :race_condition_ttl end end diff --git a/lib/fido_metadata/store.rb b/lib/fido_metadata/store.rb index d469afb..0d822b5 100644 --- a/lib/fido_metadata/store.rb +++ b/lib/fido_metadata/store.rb @@ -16,7 +16,7 @@ def table_of_contents json = client.download_toc(METADATA_ENDPOINT) toc = FidoMetadata::TableOfContents.from_json(json) - cache_backend.write(key, toc) + cache_backend.write(key, toc, expires_in: toc.expires_in, race_condition_ttl: race_condition_ttl) toc end end @@ -51,7 +51,12 @@ def fetch_statement(aaguid: nil, attestation_certificate_key_id: nil) json = client.download_entry(entry.url, expected_hash: entry.hash) statement = FidoMetadata::Statement.from_json(json) - cache_backend.write(key, statement) + cache_backend.write( + key, + statement, + expires_in: table_of_contents.expires_in, + race_condition_ttl: race_condition_ttl + ) statement end @@ -75,6 +80,10 @@ def metadata_token FidoMetadata.configuration.metadata_token || raise("no metadata_token configured") end + def race_condition_ttl + FidoMetadata.configuration.race_condition_ttl + end + def client @client ||= FidoMetadata::Client.new(metadata_token) end diff --git a/lib/fido_metadata/table_of_contents.rb b/lib/fido_metadata/table_of_contents.rb index 5c32f33..d3f6848 100644 --- a/lib/fido_metadata/table_of_contents.rb +++ b/lib/fido_metadata/table_of_contents.rb @@ -13,5 +13,9 @@ class TableOfContents json_accessor("nextUpdate", Coercer::Date) json_accessor("entries", Coercer::Objects.new(Entry)) json_accessor("no") + + def expires_in + next_update.to_time.to_i - Time.now.to_i + end end end diff --git a/spec/store_spec.rb b/spec/store_spec.rb index 28d96ce..2d91af0 100644 --- a/spec/store_spec.rb +++ b/spec/store_spec.rb @@ -13,9 +13,11 @@ end let(:toc_entries) { [entry] } + let(:toc_next_update) { Date.today + 1 } let(:toc) do toc = FidoMetadata::TableOfContents.new toc.entries = toc_entries + toc.next_update = toc_next_update toc end @@ -63,7 +65,10 @@ before { FidoMetadata.configuration.cache_backend.clear } it "downloads and returns the TOC" do - expect(client).to receive(:download_toc).and_return("entries" => [{ "aaguid" => aaguid }]) + expect(client).to receive(:download_toc).and_return( + "nextUpdate" => toc_next_update, + "entries" => [{ "aaguid" => aaguid }] + ) expect(subject.aaguid).to eq(aaguid) end end @@ -95,6 +100,7 @@ it "downloads and returns the TOC" do expect(client).to receive(:download_toc).and_return( + "nextUpdate" => toc_next_update, "entries" => [{ "attestationCertificateKeyIdentifiers" => [attestation_certificate_key_id] }] ) expect(subject.attestation_certificate_key_identifiers).to eq([attestation_certificate_key_id]) diff --git a/spec/table_of_contents_spec.rb b/spec/table_of_contents_spec.rb new file mode 100644 index 0000000..4949de9 --- /dev/null +++ b/spec/table_of_contents_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "spec_helper" +require "fido_metadata/statement" + +RSpec.describe FidoMetadata::TableOfContents do + let(:file) { File.read(SUPPORT_PATH.join("mds_toc.txt")) } + let(:current_time) { Time.utc(2019, 12, 28) } + + before(:each) do + allow(Time).to receive(:now).and_return(current_time) + end + + subject do + json, _ = JWT.decode(file, nil, false, algorithms: ["ES256"]) + described_class.from_json(json) + end + + it "#expires_in calculates in how much seconds it will expire" do + expect(subject.expires_in).to eq(9172800) + end +end From 6b0af1c955c895b113b22bcef27b2d9f4b504553 Mon Sep 17 00:00:00 2001 From: Bart de Water <496367+bdewater@users.noreply.github.com> Date: Sat, 28 Dec 2019 20:04:19 -0500 Subject: [PATCH 2/5] Use constants for cache keys --- lib/fido_metadata/store.rb | 6 ++++-- spec/store_spec.rb | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/fido_metadata/store.rb b/lib/fido_metadata/store.rb index 0d822b5..42c9408 100644 --- a/lib/fido_metadata/store.rb +++ b/lib/fido_metadata/store.rb @@ -7,10 +7,12 @@ module FidoMetadata class Store METADATA_ENDPOINT = URI("https://mds2.fidoalliance.org/") + TOC_CACHE_KEY = "metadata_toc" + STATEMENT_CACHE_KEY = "statement_%s" def table_of_contents @table_of_contents ||= begin - key = "metadata_toc" + key = TOC_CACHE_KEY toc = cache_backend.read(key) return toc if toc @@ -38,7 +40,7 @@ def fetch_entry(aaguid: nil, attestation_certificate_key_id: nil) def fetch_statement(aaguid: nil, attestation_certificate_key_id: nil) verify_arguments(aaguid: aaguid, attestation_certificate_key_id: attestation_certificate_key_id) - key = "statement_#{aaguid || attestation_certificate_key_id}" + key = STATEMENT_CACHE_KEY % (aaguid || attestation_certificate_key_id) statement = cache_backend.read(key) return statement if statement diff --git a/spec/store_spec.rb b/spec/store_spec.rb index 2d91af0..f40d009 100644 --- a/spec/store_spec.rb +++ b/spec/store_spec.rb @@ -24,7 +24,7 @@ let(:client) { instance_double(FidoMetadata::Client) } before do - FidoMetadata.configuration.cache_backend.write("metadata_toc", toc) + FidoMetadata.configuration.cache_backend.write(described_class::TOC_CACHE_KEY, toc) FidoMetadata.configuration.metadata_token = "foo" allow(FidoMetadata::Client).to receive(:new).and_return(client) end @@ -116,7 +116,7 @@ statement.aaguid = aaguid statement end - let(:statement_cache_key) { "statement_#{aaguid}" } + let(:statement_cache_key) { described_class::STATEMENT_CACHE_KEY % aaguid } before do FidoMetadata.configuration.cache_backend.write(statement_cache_key, statement) @@ -156,7 +156,7 @@ statement.attestation_certificate_key_identifiers = [attestation_certificate_key_id] statement end - let(:statement_cache_key) { "statement_#{attestation_certificate_key_id}" } + let(:statement_cache_key) { described_class::STATEMENT_CACHE_KEY % attestation_certificate_key_id } before do FidoMetadata.configuration.cache_backend.write(statement_cache_key, statement) From b24e47f8ace33a157201fc3abad5f94a576ded1d Mon Sep 17 00:00:00 2001 From: Bart de Water <496367+bdewater@users.noreply.github.com> Date: Sat, 28 Dec 2019 20:05:06 -0500 Subject: [PATCH 3/5] Release 0.4.0 --- Gemfile.lock | 2 +- lib/fido_metadata/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5d16b36..e3fcb36 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - fido_metadata (0.3.0) + fido_metadata (0.4.0) jwt (~> 2.0) GEM diff --git a/lib/fido_metadata/version.rb b/lib/fido_metadata/version.rb index c789363..317fef9 100644 --- a/lib/fido_metadata/version.rb +++ b/lib/fido_metadata/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module FidoMetadata - VERSION = "0.3.0" + VERSION = "0.4.0" end From ee00ca19ae210061df42355361aa2e0b98233e29 Mon Sep 17 00:00:00 2001 From: Nicolas Temciuc Date: Fri, 12 Sep 2025 10:59:37 -0300 Subject: [PATCH 4/5] test: improve expires_in specs with UTC handling --- spec/table_of_contents_spec.rb | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/spec/table_of_contents_spec.rb b/spec/table_of_contents_spec.rb index 4949de9..aaa8128 100644 --- a/spec/table_of_contents_spec.rb +++ b/spec/table_of_contents_spec.rb @@ -1,22 +1,32 @@ # frozen_string_literal: true require "spec_helper" -require "fido_metadata/statement" +require "fido_metadata/table_of_contents" RSpec.describe FidoMetadata::TableOfContents do - let(:file) { File.read(SUPPORT_PATH.join("mds_toc.txt")) } - let(:current_time) { Time.utc(2019, 12, 28) } + let(:current_time) { Time.utc(2025, 1, 1, 0, 0, 0) } - before(:each) do - allow(Time).to receive(:now).and_return(current_time) + # Set timezone to UTC for the duration of the tests + around do |ex| + orig_tz = ENV["TZ"] + ENV["TZ"] = "UTC" + ex.run + ENV["TZ"] = orig_tz end - subject do - json, _ = JWT.decode(file, nil, false, algorithms: ["ES256"]) - described_class.from_json(json) + before do + allow(Time).to receive(:now).and_return(current_time) end - it "#expires_in calculates in how much seconds it will expire" do - expect(subject.expires_in).to eq(9172800) + describe "#expires_in" do + it "returns the number of seconds until nextUpdate" do + toc = described_class.from_json("nextUpdate" => "2025-01-02T00:00:00Z") + expect(toc.expires_in).to eq(86_400) + end + + it "can return a negative number when nextUpdate is in the past" do + toc = described_class.from_json("nextUpdate" => "2024-12-31T23:59:00Z") + expect(toc.expires_in).to eq(-86_400) + end end end From 808beb668733907ab3ade4a0b408606120daa2ff Mon Sep 17 00:00:00 2001 From: Nicolas Temciuc Date: Fri, 12 Sep 2025 14:39:06 -0300 Subject: [PATCH 5/5] test: reword expires_in test descriptions --- spec/table_of_contents_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/table_of_contents_spec.rb b/spec/table_of_contents_spec.rb index aaa8128..b2fdbfc 100644 --- a/spec/table_of_contents_spec.rb +++ b/spec/table_of_contents_spec.rb @@ -19,12 +19,12 @@ end describe "#expires_in" do - it "returns the number of seconds until nextUpdate" do + it "calculates in how much seconds it will expire" do toc = described_class.from_json("nextUpdate" => "2025-01-02T00:00:00Z") expect(toc.expires_in).to eq(86_400) end - it "can return a negative number when nextUpdate is in the past" do + it "calculates negative seconds if already expired" do toc = described_class.from_json("nextUpdate" => "2024-12-31T23:59:00Z") expect(toc.expires_in).to eq(-86_400) end