shc_vaccination_test_kit 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/shc_vaccination_test_kit/igs/README.md +21 -0
- data/lib/shc_vaccination_test_kit/igs/hl7.fhir.uv.smarthealthcards-vaccination-0.5.0-rc.tgz +0 -0
- data/lib/shc_vaccination_test_kit/metadata.rb +54 -0
- data/lib/shc_vaccination_test_kit/shc_vaccination_validation_test.rb +60 -0
- data/lib/shc_vaccination_test_kit/version.rb +4 -0
- data/lib/shc_vaccination_test_kit.rb +31 -10
- metadata +20 -21
- data/lib/covid19_vci/fhir_operation.rb +0 -105
- data/lib/covid19_vci/file_download.rb +0 -109
- data/lib/covid19_vci/vc_fhir_validation.rb +0 -75
- data/lib/covid19_vci/vc_headers.rb +0 -21
- data/lib/covid19_vci/vc_payload_verification.rb +0 -115
- data/lib/covid19_vci/vc_signature_verification.rb +0 -69
- data/lib/covid19_vci/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fba6c400d4a9ac873d20b71a7ee8265e162d63e3862d63d6b596c9c457dc2c91
|
4
|
+
data.tar.gz: dbf2ac6446acf88efe2826f5293f921ca1e6b4bfafec745a284bb8fefbf55da2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 481be6a8e337ff9bc9c419c64c3b3bc0f087ab89e813b0578a67b6b78d9113e79c8e78894d468d3510bf46a612599297b73d392b1c0366406bacbaf09f977145
|
7
|
+
data.tar.gz: c27435d742c29237945d94c54f2acc0416e5d06e3d4b10d2aaa457a6f000cbf58fe8e52d53e13a932d31397059c47f050aeafa6538f693f98d50ed79c915d712
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Note on this IGs folder
|
2
|
+
|
3
|
+
There are three reasons why it would be necessary to put an IG package.tgz in this folder. If none of these apply, you do not need to put any files here, or can consider removing any existing files to make it clear they are unused.
|
4
|
+
|
5
|
+
## 1. Generated Test Suites
|
6
|
+
Some test kits use a "generator" to automatically generate the contents of a test suite for an IG. The IG files are required every time the test suites need to be regenerated. Examples of test kits that use this approach are the US Core Test Kit and CARIN IG for Blue Button® Test Kit.
|
7
|
+
|
8
|
+
|
9
|
+
## 2. Non-published IG
|
10
|
+
If your IG, or the specific version of the IG you want to test against, is not published, then the validator service needs to load the IG from file in order to be able to validate resources with it. The IG must be referenced in the `fhir_resource_validator` block in the test suite definition by filename, ie:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
fhir_resource_validator do
|
14
|
+
igs 'igs/filename.tgz'
|
15
|
+
|
16
|
+
...
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
## 3. Inferno Validator UI
|
21
|
+
The Inferno Validator UI is configured to auto-load any IGs present in the igs folder and include them in all validations. The Inferno Validator UI is currently disabled by default, so this is only relevant if you choose to re-enable it. In general, the Inferno team is currently leaving IGs in this folder even if not otherwise necessary to make it easy to re-enable the validator UI.
|
Binary file
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require_relative 'version'
|
2
|
+
|
3
|
+
module SHCVaccinationTestKit
|
4
|
+
class Metadata < Inferno::TestKit
|
5
|
+
id :shc_vaccination_test_kit
|
6
|
+
title 'SMART Health Cards: Vaccination and Testing Test Kit'
|
7
|
+
description <<~DESCRIPTION
|
8
|
+
The SMART Health Cards Vaccination: Vaccination and Testing Test Kit provides an
|
9
|
+
executable set of tests for the [SMART Health Cards: Vaccinations and Testing Implementation Guide v0.5.0-rc](https://www.hl7.org/fhir/uv/shc-vaccination/2021Sep/). This test kit
|
10
|
+
simulates downloading and validating a SMART Health Card.
|
11
|
+
<!-- break -->
|
12
|
+
|
13
|
+
This test kit is [open source](https://github.com/inferno-framework/shc-vaccination-test-kit#license) and freely available for use or
|
14
|
+
adoption by the health IT community including EHR vendors, health app
|
15
|
+
developers, and testing labs. It is built using the [Inferno Framework](https://inferno-framework.github.io/inferno-core/). The Inferno Framework is
|
16
|
+
designed for reuse and aims to make it easier to build test kits for any
|
17
|
+
FHIR-based data exchange.
|
18
|
+
|
19
|
+
## Status
|
20
|
+
|
21
|
+
These tests are intended to allow server implementers to perform checks of their server against SMART Health Card: Vaccination & Testing requrirements.
|
22
|
+
|
23
|
+
The test kit currently tests the following requirements:
|
24
|
+
- Download and validate a health card via file download
|
25
|
+
- Download and validate a health card via FHIR $health-cards-issue operation
|
26
|
+
|
27
|
+
See the test descriptions within the test kit for detail on the specific validations performed as part of testing these requirements.
|
28
|
+
|
29
|
+
This test kit does not test:
|
30
|
+
- Decoding of QR codes
|
31
|
+
|
32
|
+
## Repository
|
33
|
+
|
34
|
+
The SMART Health Cards Vaccination Test Kit GitHub repository can be [found here](https://github.com/inferno-framework/shc-vaccination-test-kit).
|
35
|
+
|
36
|
+
## Providing Feedback and Reporting Issues
|
37
|
+
|
38
|
+
We welcome feedback on the tests, including but not limited to the following areas:
|
39
|
+
|
40
|
+
- Validation logic, such as potential bugs, lax checks, and unexpected failures.
|
41
|
+
- Requirements coverage, such as requirements that have been missed, tests that necessitate features that the IG does not require, or other issues with the interpretation of the IG's requirements.
|
42
|
+
- User experience, such as confusing or missing information in the test UI.
|
43
|
+
|
44
|
+
Please report any issues with this set of tests in the [issues section](https://github.com/inferno-framework/shc-vaccination-test-kit/issues) of the repository.
|
45
|
+
DESCRIPTION
|
46
|
+
suite_ids [:shc_vaccination]
|
47
|
+
tags ['SMART Health Cards']
|
48
|
+
last_updated LAST_UPDATED
|
49
|
+
version VERSION
|
50
|
+
maturity 'Low'
|
51
|
+
authors ['Stephen MacVicar']
|
52
|
+
repo 'https://github.com/inferno-framework/smart-health-cards-test-kit'
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'inferno'
|
2
|
+
|
3
|
+
module SHCVaccinationTestKit
|
4
|
+
class SHCVaccinationFHIRValidation < Inferno::Test
|
5
|
+
include SmartHealthCardsTestKit::HealthCard
|
6
|
+
|
7
|
+
id :shc_vaccination_validation_test
|
8
|
+
title 'test Health Card payloads conform to the Vaccination Credential Bundle Profiles'
|
9
|
+
description %(
|
10
|
+
SMART Health Card (SHC) for vaccination records payload SHALL be a valid FHIR Bundle resource
|
11
|
+
)
|
12
|
+
input :fhir_bundles
|
13
|
+
|
14
|
+
run do
|
15
|
+
skip_if fhir_bundles.blank?, 'No FHIR bundles received'
|
16
|
+
|
17
|
+
assert_valid_json(fhir_bundles)
|
18
|
+
bundle_array = JSON.parse(fhir_bundles)
|
19
|
+
|
20
|
+
skip_if bundle_array.blank?, 'No FHIR bundles received'
|
21
|
+
|
22
|
+
bundle_array.each do |bundle|
|
23
|
+
validate_fhir_bundle(FHIR::Bundle.new(bundle))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate_fhir_bundle(bundle)
|
28
|
+
# assert bundle.entry.any? { |r| r.resource.is_a?(FHIR::Immunization) } || bundle.entry.any? { |r| r.resource.is_a?(FHIR::Observation) },
|
29
|
+
# "Bundle must have either Immunization entries or Observation entries"
|
30
|
+
|
31
|
+
# if bundle.entry.any? { |r| r.resource.is_a?(FHIR::Immunization) }
|
32
|
+
assert_valid_resource(
|
33
|
+
resource: bundle,
|
34
|
+
profile_url: 'http://hl7.org/fhir/uv/smarthealthcards-vaccination/StructureDefinition/vaccination-credential-bundle'
|
35
|
+
)
|
36
|
+
|
37
|
+
warning do
|
38
|
+
assert_valid_resource(
|
39
|
+
resource: bundle,
|
40
|
+
profile_url: 'http://hl7.org/fhir/uv/smarthealthcards-vaccination/StructureDefinition/vaccination-credential-bundle-dm'
|
41
|
+
)
|
42
|
+
end
|
43
|
+
# end
|
44
|
+
|
45
|
+
if bundle.entry.any? { |r| r.resource.is_a?(FHIR::Observation) }
|
46
|
+
assert_valid_resource(
|
47
|
+
resource: bundle,
|
48
|
+
profile_url: 'http://hl7.org/fhir/uv/smarthealthcards-vaccination/StructureDefinition/covid19-laboratory-bundle'
|
49
|
+
)
|
50
|
+
|
51
|
+
warning do
|
52
|
+
assert_valid_resource(
|
53
|
+
resource: bundle,
|
54
|
+
profile_url: 'http://hl7.org/fhir/uv/smarthealthcards-vaccination/StructureDefinition/covid19-laboratory-bundle-dm'
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -1,29 +1,50 @@
|
|
1
|
-
require '
|
2
|
-
require_relative '
|
3
|
-
require_relative '
|
1
|
+
require 'smart_health_cards_test_kit'
|
2
|
+
require_relative 'shc_vaccination_test_kit/shc_vaccination_validation_test'
|
3
|
+
require_relative 'shc_vaccination_test_kit/metadata'
|
4
4
|
|
5
|
-
module
|
6
|
-
class
|
7
|
-
id '
|
5
|
+
module SHCVaccinationTestKit
|
6
|
+
class SHCVaccinationSuite < Inferno::TestSuite
|
7
|
+
id 'shc_vaccination'
|
8
8
|
title 'SMART Health Cards: Vaccination & Testing'
|
9
9
|
description %(
|
10
10
|
This test suite evaluates the ability of a system to provide
|
11
|
-
access to [SMART Health Cards](https://
|
11
|
+
access to [SMART Health Cards Vaccination and Testing](https://hl7.org/fhir/uv/shc-vaccination/2021Sep/index.html)
|
12
|
+
resources via file download, HL7® FHIR® API, or QR Scanning.
|
12
13
|
)
|
14
|
+
source_code_url('https://github.com/inferno-framework/shc-vaccination-test-kit')
|
15
|
+
download_url('https://github.com/inferno-framework/shc-vaccination-test-kit/releases')
|
16
|
+
report_issue_url('https://github.com/inferno-framework/shc-vaccination-test-kit/issues')
|
13
17
|
|
14
18
|
VALIDATION_MESSAGE_FILTERS = [
|
15
19
|
/\A\S+: \S+: URL value '.*' does not resolve/,
|
16
20
|
].freeze
|
17
21
|
|
18
22
|
fhir_resource_validator do
|
19
|
-
igs
|
23
|
+
igs('igs/hl7.fhir.uv.smarthealthcards-vaccination-0.5.0-rc.tgz')
|
20
24
|
|
21
25
|
exclude_message do |message|
|
22
26
|
VALIDATION_MESSAGE_FILTERS.any? { |filter| filter.match? message.message }
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
26
|
-
|
27
|
-
|
30
|
+
# Tests and TestGroups
|
31
|
+
# SmartHealthCardsTestKit::SmartHealthCardsTestSuite.groups.each do |group|
|
32
|
+
# test_group = group.ancestors[1]
|
33
|
+
|
34
|
+
# test_group.children.reject! { |test| test.id.include?('shc_fhir_validation_test') }
|
35
|
+
# test_group.test(from: :shc_vaccination_validation_test)
|
36
|
+
|
37
|
+
# group(from: test_group.id)
|
38
|
+
# end
|
39
|
+
|
40
|
+
def self.add_shc_group(group_id)
|
41
|
+
group from: group_id do
|
42
|
+
children.reject! { |test| test.id.include?('shc_fhir_validation_test') }
|
43
|
+
test from: :shc_vaccination_validation_test
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
add_shc_group :shc_file_download_group
|
48
|
+
add_shc_group :shc_fhir_operation_group
|
28
49
|
end
|
29
50
|
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shc_vaccination_test_kit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen MacVicar
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: inferno_core
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.6.4
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.6.4
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: smart_health_cards_test_kit
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.10.1
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: 0.10.1
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: database_cleaner-sequel
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -103,18 +103,17 @@ extensions: []
|
|
103
103
|
extra_rdoc_files: []
|
104
104
|
files:
|
105
105
|
- LICENSE
|
106
|
-
- lib/covid19_vci/fhir_operation.rb
|
107
|
-
- lib/covid19_vci/file_download.rb
|
108
|
-
- lib/covid19_vci/vc_fhir_validation.rb
|
109
|
-
- lib/covid19_vci/vc_headers.rb
|
110
|
-
- lib/covid19_vci/vc_payload_verification.rb
|
111
|
-
- lib/covid19_vci/vc_signature_verification.rb
|
112
|
-
- lib/covid19_vci/version.rb
|
113
106
|
- lib/shc_vaccination_test_kit.rb
|
107
|
+
- lib/shc_vaccination_test_kit/igs/README.md
|
108
|
+
- lib/shc_vaccination_test_kit/igs/hl7.fhir.uv.smarthealthcards-vaccination-0.5.0-rc.tgz
|
109
|
+
- lib/shc_vaccination_test_kit/metadata.rb
|
110
|
+
- lib/shc_vaccination_test_kit/shc_vaccination_validation_test.rb
|
111
|
+
- lib/shc_vaccination_test_kit/version.rb
|
114
112
|
homepage: https://github.com/inferno-framework/shc-vaccination-test-kit
|
115
113
|
licenses:
|
116
114
|
- Apache-2.0
|
117
115
|
metadata:
|
116
|
+
inferno_test_kit: 'true'
|
118
117
|
homepage_uri: https://github.com/inferno-framework/shc-vaccination-test-kit
|
119
118
|
source_code_uri: https://github.com/inferno-framework/shc-vaccination-test-kit
|
120
119
|
post_install_message:
|
@@ -125,14 +124,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
125
124
|
requirements:
|
126
125
|
- - ">="
|
127
126
|
- !ruby/object:Gem::Version
|
128
|
-
version: 3.
|
127
|
+
version: 3.3.6
|
129
128
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
129
|
requirements:
|
131
130
|
- - ">="
|
132
131
|
- !ruby/object:Gem::Version
|
133
132
|
version: '0'
|
134
133
|
requirements: []
|
135
|
-
rubygems_version: 3.5.
|
134
|
+
rubygems_version: 3.5.22
|
136
135
|
signing_key:
|
137
136
|
specification_version: 4
|
138
137
|
summary: 'A collection of tests for the SMART Health Cards: Vaccination & Testing
|
@@ -1,105 +0,0 @@
|
|
1
|
-
require_relative 'vc_fhir_validation'
|
2
|
-
require_relative 'vc_headers'
|
3
|
-
require_relative 'vc_payload_verification'
|
4
|
-
require_relative 'vc_signature_verification'
|
5
|
-
|
6
|
-
module Covid19VCI
|
7
|
-
class FHIROperation < Inferno::TestGroup
|
8
|
-
id 'vci_fhir_operation'
|
9
|
-
title 'Download and validate a health card via FHIR $health-cards-issue operation'
|
10
|
-
|
11
|
-
input :base_fhir_url, :patient_id
|
12
|
-
|
13
|
-
fhir_client do
|
14
|
-
url :base_fhir_url
|
15
|
-
end
|
16
|
-
|
17
|
-
test do
|
18
|
-
title 'Server advertises health card support in its SMART configuration'
|
19
|
-
id 'vci-fhir-01'
|
20
|
-
|
21
|
-
run do
|
22
|
-
get("#{base_fhir_url}/.well-known/smart-configuration")
|
23
|
-
|
24
|
-
assert_response_status(200)
|
25
|
-
assert_valid_json(response[:body])
|
26
|
-
|
27
|
-
smart_configuration = JSON.parse(response[:body])
|
28
|
-
|
29
|
-
assert smart_configuration['capabilities']&.include?('health-cards'),
|
30
|
-
"SMART configuration does not list support for 'health-cards' capability"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
test do
|
35
|
-
title 'Server advertises $health-cards-issue operation support in its CapabilityStatement'
|
36
|
-
id 'vci-fhir-02'
|
37
|
-
|
38
|
-
run do
|
39
|
-
fhir_get_capability_statement
|
40
|
-
|
41
|
-
assert_response_status(200)
|
42
|
-
|
43
|
-
operations = resource.rest&.flat_map do |rest|
|
44
|
-
rest.resource
|
45
|
-
&.select { |r| r.type == 'Patient' && r.respond_to?(:operation) }
|
46
|
-
&.flat_map(&:operation)
|
47
|
-
end&.compact
|
48
|
-
|
49
|
-
operation_defined = operations.any? { |operation| operation.name == 'health-cards-issue' }
|
50
|
-
|
51
|
-
assert operation_defined,
|
52
|
-
'Server CapabilityStatement did not declare support for $health-cards-issue operation ' \
|
53
|
-
'on the Patient resource.'
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
test do
|
58
|
-
title 'Server returns a health card from the $health-cards-issue operation'
|
59
|
-
id 'vci-fhir-03'
|
60
|
-
output :credential_strings
|
61
|
-
|
62
|
-
run do
|
63
|
-
request_params = FHIR::Parameters.new(
|
64
|
-
parameter: [
|
65
|
-
{
|
66
|
-
name: 'credentialType',
|
67
|
-
valueUri: 'https://smarthealth.cards#covid19'
|
68
|
-
}
|
69
|
-
]
|
70
|
-
)
|
71
|
-
fhir_operation("/Patient/#{patient_id}/$health-cards-issue", body: request_params)
|
72
|
-
|
73
|
-
assert_response_status((200..207).to_a)
|
74
|
-
assert_resource_type(:parameters)
|
75
|
-
|
76
|
-
hc_parameters = resource.parameter.select { |parameter| parameter.name == 'verifiableCredential' }
|
77
|
-
|
78
|
-
assert hc_parameters.present?, 'No COVID-19 health cards were returned'
|
79
|
-
credential_strings = hc_parameters.map(&:value).join(',')
|
80
|
-
|
81
|
-
output credential_strings: credential_strings
|
82
|
-
|
83
|
-
count = hc_parameters.length
|
84
|
-
|
85
|
-
pass "#{count} verifiable #{'credential'.pluralize(count)} received"
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
test from: :vc_headers do
|
90
|
-
id 'vci-fhir-04'
|
91
|
-
end
|
92
|
-
|
93
|
-
test from: :vc_signature_verification do
|
94
|
-
id 'vci-fhir-05'
|
95
|
-
end
|
96
|
-
|
97
|
-
test from: :vc_payload_verification do
|
98
|
-
id 'vci-fhir-06'
|
99
|
-
end
|
100
|
-
|
101
|
-
test from: :vc_fhir_verification do
|
102
|
-
id 'vci-fhir-07'
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
@@ -1,109 +0,0 @@
|
|
1
|
-
require_relative 'vc_fhir_validation'
|
2
|
-
require_relative 'vc_headers'
|
3
|
-
require_relative 'vc_payload_verification'
|
4
|
-
require_relative 'vc_signature_verification'
|
5
|
-
|
6
|
-
module Covid19VCI
|
7
|
-
class FileDownload < Inferno::TestGroup
|
8
|
-
id 'vci_file_download'
|
9
|
-
title 'Download and validate a health card via file download'
|
10
|
-
|
11
|
-
input :file_download_url
|
12
|
-
|
13
|
-
test do
|
14
|
-
id 'vci-file-01'
|
15
|
-
title 'Health card can be downloaded'
|
16
|
-
description 'The health card can be downloaded and is a valid JSON object'
|
17
|
-
makes_request :vci_file_download
|
18
|
-
|
19
|
-
run do
|
20
|
-
get(file_download_url, name: :vci_file_download)
|
21
|
-
|
22
|
-
assert_response_status(200)
|
23
|
-
assert_valid_json(response[:body])
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
test do
|
28
|
-
id 'vci-file-02'
|
29
|
-
title 'Response contains correct Content-Type of application/smart-health-card'
|
30
|
-
uses_request :vci_file_download
|
31
|
-
|
32
|
-
run do
|
33
|
-
skip_if request.status != 200, 'Health card not successfully downloaded'
|
34
|
-
|
35
|
-
content_type = request.response_header('Content-Type')
|
36
|
-
|
37
|
-
assert content_type.present?, 'Response did not include a Content-Type header'
|
38
|
-
assert content_type.value.match?(%r{\Aapplication/smart-health-card(\z|\W)}),
|
39
|
-
"Content-Type header was '#{content_type.value}' instead of 'application/smart-health-card'"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
test do
|
44
|
-
id 'vci-file-03'
|
45
|
-
title 'Health card is provided as a file download with a .smart-health-card extension'
|
46
|
-
uses_request :vci_file_download
|
47
|
-
|
48
|
-
run do
|
49
|
-
skip_if request.status != 200, 'Health card not successfully downloaded'
|
50
|
-
|
51
|
-
pass_if request.url.ends_with?('.smart-health-card')
|
52
|
-
|
53
|
-
content_disposition = request.response_header('Content-Disposition')
|
54
|
-
assert content_disposition.present?,
|
55
|
-
"Url did not end with '.smart-health-card' and response did not include a Content-Disposition header"
|
56
|
-
|
57
|
-
attachment_pattern = /\Aattachment;/
|
58
|
-
assert content_disposition.value.match?(attachment_pattern),
|
59
|
-
"Url did not end with '.smart-health-card' and " \
|
60
|
-
"Content-Disposition header does not indicate file should be downloaded: '#{content_disposition}'"
|
61
|
-
|
62
|
-
extension_pattern = /filename=".*\.smart-health-card"/
|
63
|
-
assert content_disposition.value.match?(extension_pattern),
|
64
|
-
"Url did not end with '.smart-health-card' and Content-Disposition header does not indicate " \
|
65
|
-
"file should have a '.smart-health-card' extension: '#{content_disposition}'"
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
test do
|
70
|
-
id 'vci-file-04'
|
71
|
-
title 'Response contains an array of Verifiable Credential strings'
|
72
|
-
uses_request :vci_file_download
|
73
|
-
output :credential_strings
|
74
|
-
|
75
|
-
run do
|
76
|
-
skip_if request.status != 200, 'Health card not successfully downloaded'
|
77
|
-
|
78
|
-
body = JSON.parse(response[:body])
|
79
|
-
assert body.include?('verifiableCredential'),
|
80
|
-
"Health card does not contain 'verifiableCredential' field"
|
81
|
-
|
82
|
-
vc = body['verifiableCredential']
|
83
|
-
|
84
|
-
assert vc.is_a?(Array), "'verifiableCredential' field must contain an Array"
|
85
|
-
assert vc.length.positive?, "'verifiableCredential' field must contain at least one verifiable credential"
|
86
|
-
|
87
|
-
output credential_strings: vc.join(',')
|
88
|
-
|
89
|
-
pass "Received #{vc.length} verifiable #{'credential'.pluralize(vc.length)}"
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
test from: :vc_headers do
|
94
|
-
id 'vci-file-05'
|
95
|
-
end
|
96
|
-
|
97
|
-
test from: :vc_signature_verification do
|
98
|
-
id 'vci-file-06'
|
99
|
-
end
|
100
|
-
|
101
|
-
test from: :vc_payload_verification do
|
102
|
-
id 'vci-file-07'
|
103
|
-
end
|
104
|
-
|
105
|
-
test from: :vc_fhir_verification do
|
106
|
-
id 'vci-file-08'
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
@@ -1,75 +0,0 @@
|
|
1
|
-
module Covid19VCI
|
2
|
-
class VCFHIRVerification < Inferno::Test
|
3
|
-
title 'Health Card payloads conform to the Vaccination Credential Bundle Profiles'
|
4
|
-
input :credential_strings
|
5
|
-
|
6
|
-
id :vc_fhir_verification
|
7
|
-
|
8
|
-
run do
|
9
|
-
skip_if credential_strings.blank?, 'No Verifiable Credentials received'
|
10
|
-
|
11
|
-
credential_strings.split(',').each do |credential|
|
12
|
-
raw_payload = HealthCards::JWS.from_jws(credential).payload
|
13
|
-
assert raw_payload&.length&.positive?, 'No payload found'
|
14
|
-
|
15
|
-
decompressed_payload =
|
16
|
-
begin
|
17
|
-
Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(raw_payload)
|
18
|
-
rescue Zlib::DataError
|
19
|
-
assert false, 'Payload compression error. Unable to inflate payload.'
|
20
|
-
end
|
21
|
-
|
22
|
-
assert decompressed_payload.length.positive?, 'Payload compression error. Unable to inflate payload.'
|
23
|
-
|
24
|
-
payload_length = decompressed_payload.length
|
25
|
-
health_card = HealthCards::COVIDHealthCard.from_jws(credential)
|
26
|
-
health_card_length = health_card.to_json.length
|
27
|
-
|
28
|
-
assert_valid_json decompressed_payload, 'Payload is not valid JSON'
|
29
|
-
|
30
|
-
payload = JSON.parse(decompressed_payload)
|
31
|
-
vc = payload['vc']
|
32
|
-
assert vc.is_a?(Hash), "Expected 'vc' claim to be a JSON object, but found #{vc.class}"
|
33
|
-
|
34
|
-
subject = vc['credentialSubject']
|
35
|
-
assert subject.is_a?(Hash), "Expected 'vc.credentialSubject' to be a JSON object, but found #{subject.class}"
|
36
|
-
|
37
|
-
raw_bundle = subject['fhirBundle']
|
38
|
-
assert raw_bundle.is_a?(Hash), "Expected 'vc.fhirBundle' to be a JSON object, but found #{raw_bundle.class}"
|
39
|
-
|
40
|
-
bundle = FHIR::Bundle.new(raw_bundle)
|
41
|
-
|
42
|
-
# assert bundle.entry.any? { |r| r.resource.is_a?(FHIR::Immunization) } || bundle.entry.any? { |r| r.resource.is_a?(FHIR::Observation) },
|
43
|
-
# "Bundle must have either Immunization entries or Observation entries"
|
44
|
-
|
45
|
-
# if bundle.entry.any? { |r| r.resource.is_a?(FHIR::Immunization) }
|
46
|
-
assert_valid_resource(
|
47
|
-
resource: bundle,
|
48
|
-
profile_url: 'http://hl7.org/fhir/uv/smarthealthcards-vaccination/StructureDefinition/vaccination-credential-bundle'
|
49
|
-
)
|
50
|
-
|
51
|
-
warning do
|
52
|
-
assert_valid_resource(
|
53
|
-
resource: bundle,
|
54
|
-
profile_url: 'http://hl7.org/fhir/uv/smarthealthcards-vaccination/StructureDefinition/vaccination-credential-bundle-dm'
|
55
|
-
)
|
56
|
-
end
|
57
|
-
# end
|
58
|
-
|
59
|
-
if bundle.entry.any? { |r| r.resource.is_a?(FHIR::Observation) }
|
60
|
-
assert_valid_resource(
|
61
|
-
resource: bundle,
|
62
|
-
profile_url: 'http://hl7.org/fhir/uv/smarthealthcards-vaccination/StructureDefinition/covid19-laboratory-bundle'
|
63
|
-
)
|
64
|
-
|
65
|
-
warning do
|
66
|
-
assert_valid_resource(
|
67
|
-
resource: bundle,
|
68
|
-
profile_url: 'http://hl7.org/fhir/uv/smarthealthcards-vaccination/StructureDefinition/covid19-laboratory-bundle-dm'
|
69
|
-
)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module Covid19VCI
|
2
|
-
class VCHeaders < Inferno::Test
|
3
|
-
title 'Health Cards contain the correct headers'
|
4
|
-
input :credential_strings
|
5
|
-
|
6
|
-
id :vc_headers
|
7
|
-
|
8
|
-
run do
|
9
|
-
skip_if credential_strings.blank?, 'No Verifiable Credentials received'
|
10
|
-
credential_strings.split(',').each do |credential|
|
11
|
-
header = HealthCards::JWS.from_jws(credential).header
|
12
|
-
|
13
|
-
assert header['zip'] == 'DEF', "Expected 'zip' header to equal 'DEF', but found '#{header['zip']}'"
|
14
|
-
assert header['alg'] == 'ES256', "Expected 'alg' header to equal 'ES256', but found '#{header['alg']}'"
|
15
|
-
assert header['kid'].present?, "No 'kid' header was present"
|
16
|
-
rescue StandardError => e
|
17
|
-
assert false, "Error decoding credential: #{e.message}"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,115 +0,0 @@
|
|
1
|
-
module Covid19VCI
|
2
|
-
class VCPayloadVerification < Inferno::Test
|
3
|
-
title 'Health Card payloads follow the spec requirements'
|
4
|
-
input :credential_strings
|
5
|
-
|
6
|
-
id :vc_payload_verification
|
7
|
-
|
8
|
-
run do
|
9
|
-
skip_if credential_strings.blank?, 'No Verifiable Credentials received'
|
10
|
-
|
11
|
-
credential_strings.split(',').each do |credential|
|
12
|
-
raw_payload = HealthCards::JWS.from_jws(credential).payload
|
13
|
-
assert raw_payload&.length&.positive?, 'No payload found'
|
14
|
-
|
15
|
-
decompressed_payload =
|
16
|
-
begin
|
17
|
-
Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(raw_payload)
|
18
|
-
rescue Zlib::DataError
|
19
|
-
assert false, 'Payload compression error. Unable to inflate payload.'
|
20
|
-
end
|
21
|
-
|
22
|
-
assert decompressed_payload.length.positive?, 'Payload compression error. Unable to inflate payload.'
|
23
|
-
|
24
|
-
payload_length = decompressed_payload.length
|
25
|
-
health_card = HealthCards::HealthCard.from_jws(credential)
|
26
|
-
health_card_length = health_card.to_json.length
|
27
|
-
|
28
|
-
warning do
|
29
|
-
assert payload_length <= health_card_length,
|
30
|
-
"Payload may not be properly minified. Received a payload with length #{payload_length}, " \
|
31
|
-
"but was able to generate a payload with length #{health_card_length}"
|
32
|
-
end
|
33
|
-
|
34
|
-
assert_valid_json decompressed_payload, 'Payload is not valid JSON'
|
35
|
-
|
36
|
-
payload = JSON.parse(decompressed_payload)
|
37
|
-
|
38
|
-
warning do
|
39
|
-
nbf = payload['nbf']
|
40
|
-
assert nbf.present?, "Payload does not include an 'nbf' claim"
|
41
|
-
assert nbf.is_a?(Numeric), "Expected 'nbf' claim to be Numeric, but found #{nbf.class}"
|
42
|
-
issue_time = Time.at(nbf).to_datetime
|
43
|
-
assert issue_time < DateTime.now, "'nbf' is in the future: #{issue_time.rfc822}"
|
44
|
-
end
|
45
|
-
|
46
|
-
vc = payload['vc']
|
47
|
-
assert vc.is_a?(Hash), "Expected 'vc' claim to be a JSON object, but found #{vc.class}"
|
48
|
-
type = vc['type']
|
49
|
-
|
50
|
-
warning do
|
51
|
-
assert type.is_a?(Array), "Expected 'vc.type' to be an array, but found #{type.class}"
|
52
|
-
assert type.include?('https://smarthealth.cards#health-card'),
|
53
|
-
"'vc.type' does not include 'https://smarthealth.cards#health-card'"
|
54
|
-
end
|
55
|
-
|
56
|
-
subject = vc['credentialSubject']
|
57
|
-
assert subject.is_a?(Hash), "Expected 'vc.credentialSubject' to be a JSON object, but found #{subject.class}"
|
58
|
-
|
59
|
-
warning do
|
60
|
-
assert subject['fhirVersion'].present?, "'vc.credentialSubject.fhirVersion' not provided"
|
61
|
-
end
|
62
|
-
|
63
|
-
raw_bundle = subject['fhirBundle']
|
64
|
-
assert raw_bundle.is_a?(Hash), "Expected 'vc.fhirBundle' to be a JSON object, but found #{raw_bundle.class}"
|
65
|
-
|
66
|
-
resource_scheme_regex = /\Aresource:\d+\z/
|
67
|
-
warning do
|
68
|
-
urls = raw_bundle['entry'].map { |entry| entry['fullUrl'] }
|
69
|
-
bad_urls = urls.reject { |url| url.match?(resource_scheme_regex) }
|
70
|
-
assert bad_urls.empty?,
|
71
|
-
"The following Bundle entry urls do not use short resource-scheme URIs: #{bad_urls.join(', ')}"
|
72
|
-
end
|
73
|
-
|
74
|
-
bundle = FHIR::Bundle.new(raw_bundle)
|
75
|
-
resources = bundle.entry.map(&:resource)
|
76
|
-
bundle.entry.each { |entry| entry.resource = nil }
|
77
|
-
resources << bundle
|
78
|
-
|
79
|
-
resources.each do |resource|
|
80
|
-
warning { assert resource.id.nil?, "#{resource.resourceType} resource should not have an 'id' element" }
|
81
|
-
|
82
|
-
if resource.respond_to? :text
|
83
|
-
warning { assert resource.text.nil?, "#{resource.resourceType} resource should not have a 'text' element" }
|
84
|
-
end
|
85
|
-
|
86
|
-
resource.each_element(resource) do |value, meta, path|
|
87
|
-
case meta['type']
|
88
|
-
when 'CodeableConcept'
|
89
|
-
warning { assert value.text.nil?, "#{resource.resourceType} should not have a #{path}.text element" }
|
90
|
-
when 'Coding'
|
91
|
-
warning do
|
92
|
-
assert value.display.nil?, "#{resource.resourceType} should not have a #{path}.display element"
|
93
|
-
end
|
94
|
-
when 'Reference'
|
95
|
-
warning do
|
96
|
-
next if value.reference.nil?
|
97
|
-
|
98
|
-
assert value.reference.match?(resource_scheme_regex),
|
99
|
-
"#{resource.resourceType}.#{path}.reference is not using the short resource URI scheme: " \
|
100
|
-
"#{value.reference}"
|
101
|
-
end
|
102
|
-
when 'Meta'
|
103
|
-
hash = value.to_hash
|
104
|
-
warning do
|
105
|
-
assert hash.length == 1 && hash.include?('security'),
|
106
|
-
"If present, Bundle 'meta' field should only include 'security', " \
|
107
|
-
"but found: #{hash.keys.join(', ')}"
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
@@ -1,69 +0,0 @@
|
|
1
|
-
module Covid19VCI
|
2
|
-
class VCSignatureVerification < Inferno::Test
|
3
|
-
title 'Verifiable Credential signatures can be verified'
|
4
|
-
input :credential_strings
|
5
|
-
|
6
|
-
id :vc_signature_verification
|
7
|
-
|
8
|
-
run do
|
9
|
-
skip_if credential_strings.blank?, 'No Verifiable Credentials received'
|
10
|
-
credential_strings.split(',').each do |credential|
|
11
|
-
card = HealthCards::HealthCard.from_jws(credential)
|
12
|
-
iss = card.issuer
|
13
|
-
|
14
|
-
jws = HealthCards::JWS.from_jws(credential)
|
15
|
-
|
16
|
-
assert iss.present?, 'Credential contains no `iss`'
|
17
|
-
warning { assert iss.start_with?('https://'), "`iss` SHALL use the `https` scheme: #{iss}" }
|
18
|
-
assert !iss.end_with?('/'), "`iss` SHALL NOT include a trailing `/`: #{iss}"
|
19
|
-
|
20
|
-
key_set_url = "#{card.issuer}/.well-known/jwks.json"
|
21
|
-
|
22
|
-
get(key_set_url)
|
23
|
-
|
24
|
-
assert_response_status(200)
|
25
|
-
assert_valid_json(response[:body])
|
26
|
-
|
27
|
-
cors_header = request.response_header('Control-Allow-Origin')
|
28
|
-
warning do
|
29
|
-
assert cors_header.present?,
|
30
|
-
'No CORS header received. Issuers SHALL publish their public keys with CORS enabled'
|
31
|
-
assert cors_header.value == '*',
|
32
|
-
"Expected CORS header value of `*`, but actual value was `#{cors_header.value}`"
|
33
|
-
end
|
34
|
-
|
35
|
-
key_set = JSON.parse(response[:body])
|
36
|
-
|
37
|
-
public_key = key_set['keys'].find { |key| key['kid'] == jws.kid }
|
38
|
-
key_object = HealthCards::Key.from_jwk(public_key)
|
39
|
-
|
40
|
-
assert public_key.present?, "Key set did not contain a key with a `kid` of #{jws.kid}"
|
41
|
-
|
42
|
-
warning { assert public_key['kty'] == 'EC', "Key had a `kty` value of `#{public_key['kty']}` instead of `EC`" }
|
43
|
-
warning do
|
44
|
-
assert public_key['use'] == 'sig', "Key had a `use` value of `#{public_key['use']}` instead of `sig`"
|
45
|
-
end
|
46
|
-
warning do
|
47
|
-
assert public_key['alg'] == 'ES256', "Key had an `alg` value of `#{public_key['alg']}` instead of `ES256`"
|
48
|
-
end
|
49
|
-
warning do
|
50
|
-
assert public_key['crv'] == 'P-256', "Key had a `crv` value of `#{public_key['crv']}` instead of `P-256`"
|
51
|
-
end
|
52
|
-
warning { assert !public_key.include?('d'), 'Key SHALL NOT have the private key parameter `d`' }
|
53
|
-
warning do
|
54
|
-
assert public_key['kid'] == key_object.kid,
|
55
|
-
"'kid' SHALL be equal to the base64url-encoded SHA-256 JWK Thumbprint of the key. " \
|
56
|
-
"Received: '#{public_key['kid']}', Expected: '#{key_object.kid}'"
|
57
|
-
end
|
58
|
-
|
59
|
-
verifier = HealthCards::Verifier.new(keys: key_object, resolve_keys: false)
|
60
|
-
|
61
|
-
begin
|
62
|
-
assert verifier.verify(jws), 'JWS signature invalid'
|
63
|
-
rescue StandardError => e
|
64
|
-
assert false, "Error decoding credential: #{e.message}"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
data/lib/covid19_vci/version.rb
DELETED