safire 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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/release-safire/SKILL.md +153 -0
  3. data/.rubocop.yml +11 -1
  4. data/.tool-versions +1 -1
  5. data/CHANGELOG.md +107 -1
  6. data/CONTRIBUTION.md +6 -1
  7. data/Gemfile +1 -2
  8. data/Gemfile.lock +47 -37
  9. data/README.md +60 -5
  10. data/ROADMAP.md +61 -12
  11. data/docs/Gemfile.lock +49 -45
  12. data/docs/_config.yml +2 -2
  13. data/docs/adr/ADR-001-activesupport-dependency.md +2 -2
  14. data/docs/adr/ADR-002-facade-and-forwardable.md +21 -6
  15. data/docs/adr/ADR-003-protocol-vs-client-type.md +13 -6
  16. data/docs/adr/ADR-004-clientconfig-immutability-and-entity-masking.md +34 -8
  17. data/docs/adr/ADR-006-lazy-discovery.md +64 -5
  18. data/docs/adr/ADR-007-https-only-redirects-and-localhost-exception.md +40 -10
  19. data/docs/adr/ADR-009-oauth-error-hierarchy.md +131 -0
  20. data/docs/adr/ADR-010-optional-client-id-dcr-temp-client.md +90 -0
  21. data/docs/adr/ADR-011-udap-stu2-discovery-conformance.md +113 -0
  22. data/docs/adr/ADR-012-udap-signed-metadata-validation.md +104 -0
  23. data/docs/adr/ADR-013-udap-registration-request-model.md +106 -0
  24. data/docs/adr/ADR-014-udap-software-statement-signing.md +122 -0
  25. data/docs/adr/index.md +9 -3
  26. data/docs/advanced.md +22 -25
  27. data/docs/configuration/client-setup.md +126 -10
  28. data/docs/configuration/index.md +11 -7
  29. data/docs/index.md +13 -6
  30. data/docs/installation.md +3 -2
  31. data/docs/security.md +44 -5
  32. data/docs/smart-on-fhir/backend-services/index.md +2 -2
  33. data/docs/smart-on-fhir/backend-services/token-request.md +1 -1
  34. data/docs/smart-on-fhir/confidential-asymmetric/index.md +2 -2
  35. data/docs/smart-on-fhir/confidential-symmetric/index.md +1 -1
  36. data/docs/smart-on-fhir/discovery/capability-checks.md +7 -0
  37. data/docs/smart-on-fhir/dynamic-client-registration/index.md +103 -0
  38. data/docs/smart-on-fhir/dynamic-client-registration/registration.md +160 -0
  39. data/docs/smart-on-fhir/dynamic-client-registration/response.md +161 -0
  40. data/docs/smart-on-fhir/index.md +2 -1
  41. data/docs/smart-on-fhir/post-based-authorization.md +1 -1
  42. data/docs/smart-on-fhir/public-client/index.md +1 -1
  43. data/docs/troubleshooting/auth-errors.md +20 -0
  44. data/docs/troubleshooting/client-errors.md +143 -0
  45. data/docs/troubleshooting/index.md +61 -4
  46. data/docs/udap/dynamic-client-registration/index.md +198 -0
  47. data/docs/udap/dynamic-client-registration/lifecycle.md +115 -0
  48. data/docs/udap/dynamic-client-registration/registration-metadata.md +133 -0
  49. data/docs/udap/dynamic-client-registration/software-statement.md +115 -0
  50. data/docs/udap.md +187 -64
  51. data/gemfiles/activesupport_71.gemfile +30 -0
  52. data/gemfiles/activesupport_71.gemfile.lock +291 -0
  53. data/lib/safire/client.rb +141 -44
  54. data/lib/safire/client_config.rb +114 -35
  55. data/lib/safire/client_config_builder.rb +18 -0
  56. data/lib/safire/errors.rb +100 -44
  57. data/lib/safire/http_client.rb +7 -2
  58. data/lib/safire/middleware/https_only_redirects.rb +10 -4
  59. data/lib/safire/protocols/behaviours.rb +12 -1
  60. data/lib/safire/protocols/oauth_response_handling.rb +48 -0
  61. data/lib/safire/protocols/smart.rb +108 -36
  62. data/lib/safire/protocols/smart_metadata.rb +6 -0
  63. data/lib/safire/protocols/udap.rb +534 -0
  64. data/lib/safire/protocols/udap_metadata.rb +429 -0
  65. data/lib/safire/protocols/udap_registration_metadata.rb +368 -0
  66. data/lib/safire/protocols/udap_signed_metadata_validator.rb +349 -0
  67. data/lib/safire/protocols/udap_software_statement.rb +348 -0
  68. data/lib/safire/protocols.rb +6 -0
  69. data/lib/safire/uri_validation.rb +102 -0
  70. data/lib/safire/version.rb +1 -1
  71. data/lib/safire.rb +2 -0
  72. data/safire.gemspec +7 -8
  73. metadata +42 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9d2b84cc65eff523a4f1a8c86c5c2fe4f2089917062c115a12fb840e52369b4
4
- data.tar.gz: 0f9cb348d913947546dff15f2d5d7a7904ca62dfdbce01dd8117598811ce5ec9
3
+ metadata.gz: dc7c5bf60b5d43365009f5073aae66d535f8e5345825f8ca08b638fb26587417
4
+ data.tar.gz: f47a4a2c4a84eefa8427e05a59d9e5e2cfca4adbce2a989f43cd53ead47e42bf
5
5
  SHA512:
6
- metadata.gz: 955b05bf392bebc9202d4b4c623e7223a398a969e72a2ed399bf0d25b57436cf3b2bbc086c3cdc2902f22742c36139089ec59d0389f4708185920746575134b3
7
- data.tar.gz: 146ef00d7041a656c401e580e24cd309901b28a9b0584b37074f7067a2c76fe2f6548369feaa7692940af3aafc6fd3c36d045623e835b0bfdb33c0b468350d07
6
+ metadata.gz: 625c96ea6424f41590d5396fb9d455ec0d89477474f484cbda236ec55ef13aed76d756ce88e014f76c1556ee8fa8e6e9d92a5a37743af2279e30c59456d74a58
7
+ data.tar.gz: 8c572a8974dfabdad87fd9d672013ca9bf64276a604db1492d11d6ad19e0351b30ecc2485b9262a366e3fce21af8eff656e2d0060077295c1b1127d6315c5ae2
@@ -0,0 +1,153 @@
1
+ ---
2
+ name: release-safire
3
+ description: Run the full Safire gem release workflow
4
+ argument-hint: Optional target version (e.g. 0.3.0); omit to auto-determine from CHANGELOG
5
+ ---
6
+
7
+ # Safire Release Workflow
8
+
9
+ You are guiding the user through a complete Safire gem release. Follow each phase in order. **Never modify files or run commands without explicit user approval.** All commits must use `-s` (Signed-off-by) and one-line subjects.
10
+
11
+ ## Context
12
+
13
+ Before proceeding, gather context by reading these files directly (do not shell out):
14
+ - Read `lib/safire/version.rb` to determine the current version
15
+ - Read `CHANGELOG.md` to find the `## [Unreleased]` section and its entries
16
+ - Run `git branch --show-current` to confirm the current branch
17
+ - Run `git status --short` to check for any uncommitted changes
18
+
19
+ ---
20
+
21
+ ## Phase 1: Determine Target Version
22
+
23
+ Target version argument: $ARGUMENTS
24
+
25
+ If `$ARGUMENTS` is blank, analyze the [Unreleased] CHANGELOG section and recommend a version bump:
26
+ - **PATCH** (X.Y.Z+1): bug fixes only
27
+ - **MINOR** (X.Y+1.0): new backward-compatible features
28
+ - **MAJOR** (X+1.0.0): breaking changes
29
+
30
+ Present your recommendation with reasoning. Ask the user to confirm or provide a different version before proceeding.
31
+
32
+ ---
33
+
34
+ ## Phase 2: Pre-Release Checks
35
+
36
+ Present this checklist and ask the user to approve running all checks before proceeding:
37
+
38
+ 1. `bundle exec rspec` — all tests must pass
39
+ 2. `bundle exec rubocop` — zero offenses
40
+ 3. `bundle exec bundler-audit check --update` — no known vulnerabilities
41
+ 4. `cd docs && bundle exec jekyll build` — docs must build clean
42
+
43
+ Run each check sequentially and report results. If any check fails, stop and clearly describe what needs to be fixed. Do not proceed to Phase 3 until all checks pass.
44
+
45
+ ---
46
+
47
+ ## Phase 3: Create Release Branch
48
+
49
+ Ask the user to approve creating the release branch, then run:
50
+
51
+ ```
52
+ git checkout main
53
+ git pull origin main
54
+ git checkout -b release-X.Y.Z
55
+ ```
56
+
57
+ Replace `X.Y.Z` with the confirmed target version.
58
+
59
+ ---
60
+
61
+ ## Phase 4: Update CHANGELOG.md (docs commit)
62
+
63
+ Show the user the exact diff you will make:
64
+ - Rename `## [Unreleased]` → `## [X.Y.Z] - YYYY-MM-DD` (use today's date)
65
+ - Add a fresh empty `## [Unreleased]` section above the new versioned entry
66
+
67
+ Wait for approval, then edit `CHANGELOG.md`.
68
+
69
+ After editing, ask the user to approve this commit:
70
+ ```
71
+ git add CHANGELOG.md
72
+ git commit -s -m "Update CHANGELOG for vX.Y.Z"
73
+ ```
74
+
75
+ Stage `CHANGELOG.md` only — no other files.
76
+
77
+ ---
78
+
79
+ ## Phase 5: Bump Version, Update Gemfile.lock and ROADMAP (release commit)
80
+
81
+ Show the user the exact change to `lib/safire/version.rb`:
82
+ ```ruby
83
+ VERSION = 'X.Y.Z'.freeze
84
+ ```
85
+
86
+ Wait for approval, then edit the file and run `bundle install` to regenerate `Gemfile.lock`.
87
+
88
+ Update the `Latest Published Release` in the Roadmap.md file
89
+
90
+ Ask the user to approve this commit:
91
+ ```
92
+ git add lib/safire/version.rb Gemfile.lock ROADMAP.md
93
+ git commit -s -m "Bump version to X.Y.Z"
94
+ ```
95
+
96
+ Stage `lib/safire/version.rb`, `Gemfile.lock`, and `ROADMAP.md` only — no other files.
97
+
98
+ ---
99
+
100
+ ## Phase 6: Local Gem Verification
101
+
102
+ Ask the user to approve running local verification (no files will be committed):
103
+
104
+ ```bash
105
+ gem build safire.gemspec
106
+ gem install ./safire-X.Y.Z.gem
107
+ ruby -e "require 'safire'; puts Safire::VERSION"
108
+ rm safire-X.Y.Z.gem
109
+ ```
110
+
111
+ The `ruby -e` line must print `X.Y.Z`. If it does not, stop and report the issue. Never commit the `.gem` file.
112
+
113
+ ---
114
+
115
+ ## Phase 7: Push Release Branch
116
+
117
+ Ask the user to approve:
118
+ ```
119
+ git push -u origin release-X.Y.Z
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Phase 8: Open Release PR
125
+
126
+ Show the proposed PR and ask for approval before running `gh pr create`:
127
+
128
+ - **Title:** `Release vX.Y.Z`
129
+ - **Body:** the full CHANGELOG entry for this version (the `## [X.Y.Z] - YYYY-MM-DD` block)
130
+
131
+ ---
132
+
133
+ ## Phase 9: Post-Merge Instructions
134
+
135
+ After the PR is created, tell the user the remaining manual steps:
136
+
137
+ 1. **Merge the PR** once CI passes and it is approved.
138
+ 2. **Create a GitHub Release** after merge:
139
+ - Tag: `vX.Y.Z` on `main`
140
+ - Title: `Safire vX.Y.Z`
141
+ - Notes: the CHANGELOG entry for this version
142
+ 3. **Publishing is automated** — `.github/workflows/publish-gem.yml` triggers on release creation.
143
+ 4. **Verify** once published: `gem info safire -r`
144
+
145
+ ---
146
+
147
+ ## Rules (never violate)
148
+
149
+ - All commits use `-s`; subjects are one-line only
150
+ - Two-commit structure on the release branch: docs commit (CHANGELOG) then release commit (version.rb + Gemfile.lock + ROADMAP.md)
151
+ - Never commit a `.gem` file
152
+ - Separate doc changes from code changes into distinct commits
153
+ - Always get explicit user approval before modifying files or running commands
data/.rubocop.yml CHANGED
@@ -2,7 +2,7 @@ plugins:
2
2
  - rubocop-rspec
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 4.0
5
+ TargetRubyVersion: 3.2
6
6
  NewCops: enable
7
7
  Exclude:
8
8
  - 'vendor/**/*'
@@ -32,6 +32,10 @@ Metrics/BlockLength:
32
32
  - 'spec/**/*'
33
33
  - '*.gemspec'
34
34
 
35
+ Metrics/AbcSize:
36
+ Exclude:
37
+ - 'spec/**/*'
38
+
35
39
  Metrics/ParameterLists:
36
40
  CountKeywordArgs: false
37
41
 
@@ -51,6 +55,12 @@ RSpec/MultipleMemoizedHelpers:
51
55
  RSpec/NestedGroups:
52
56
  Enabled: false
53
57
 
58
+ RSpec/SpecFilePathFormat:
59
+ CustomTransform:
60
+ OAuthResponseHandling: oauth_response_handling
61
+ RuboCop: rubocop
62
+ RSpec: rspec
63
+
54
64
  # Integration tests describe workflows, not classes
55
65
  RSpec/DescribeClass:
56
66
  Exclude:
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 4.0.2
1
+ ruby 4.0.6
data/CHANGELOG.md CHANGED
@@ -1,12 +1,118 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to this project will be documented in this file.
3
+ All notable changes to the packaged Safire gem will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-08-07
11
+
12
+ ### Added
13
+
14
+ - `Safire::Client#register_client` and `Safire::Client#cancel_registration` now
15
+ support the UDAP Security STU2 Dynamic Client Registration lifecycle when
16
+ initialized with `protocol: :udap`. Safire performs discovery-bound
17
+ registration against authoritative signed endpoints, checks structural DCR
18
+ capability, posts the fixed UDAP envelope with optional certification or
19
+ endorsement JWTs, accepts new-registration `201` and update-style `200`
20
+ responses with a valid `client_id`, and confirms cancellation through a
21
+ successful response containing a valid `client_id` and an empty `grant_types`
22
+ array. OAuth-style failures preserve UDAP error codes such as
23
+ `invalid_software_statement` and `unapproved_software_statement`.
24
+ - `Safire::Protocols::UdapRegistrationMetadata` validates and normalizes
25
+ caller-controlled UDAP Security STU2 registration and cancellation metadata
26
+ before software-statement signing. It enforces exact grant shapes, HTTPS
27
+ redirect and logo URIs, required `mailto:` contact data, protocol-owned
28
+ fields, JSON-compatible extensions, and immutable canonical output. An
29
+ explicit `allow_insecure_localhost: true` option permits development-only
30
+ HTTP loopback URIs without allowing remote HTTP. Registration software
31
+ statements use minimal `alg`/`x5c` headers, exact `iss`/`sub`/`aud` claims, a
32
+ five-minute lifetime, fresh `jti`, key-compatible algorithm negotiation, and
33
+ local certificate/key/SAN checks. `ClientConfig` accepts and masks the
34
+ non-empty, leaf-first `certificate_chain` of PEM strings or
35
+ `OpenSSL::X509::Certificate` instances required for UDAP registration.
36
+ - UDAP Security STU2 discovery is now available with
37
+ `Safire::Client.new(..., protocol: :udap).server_metadata`. Safire fetches
38
+ `/.well-known/udap`, supports community-scoped discovery via `community:`, accepts
39
+ `trusted_anchors:`, `crls:`, `revocation_checker:`, and `verify_chain:` for
40
+ signed metadata trust validation (`verify_chain: false` is for development/test
41
+ only), parses metadata into `Safire::Protocols::UdapMetadata`, and raises
42
+ `DiscoveryError` for HTTP errors, 204 responses, a response body that is not a
43
+ JSON object, or failed signed metadata validation.
44
+ - `Safire::Protocols::UdapMetadata` provides STU2 structural validation and helper
45
+ predicates for advertised UDAP profiles and capabilities.
46
+ - `Safire::Protocols::UdapSignedMetadataValidator` validates the `signed_metadata`
47
+ JWT per UDAP Security STU2, including RS256, `x5c`, JWT signature, certificate
48
+ chain and revocation checks, issuer/subject/time claims, `jti`, and signed
49
+ endpoint claims.
50
+ - UDAP signed endpoint claims are merged over unsigned discovery metadata after
51
+ successful validation. Cached UDAP metadata is revalidated before reuse and
52
+ refetched if the signed JWT, certificate chain, or revocation policy no longer
53
+ validates.
54
+ - `UdapMetadata#signed_metadata_valid?` allows explicit cryptographic re-validation
55
+ against caller-provided trust anchors and revocation material.
56
+ - `Safire::Errors::DiscoveryError` accepts a `label:` keyword argument (default:
57
+ `'SMART configuration'`) and exposes it as a readable attribute so callers can
58
+ identify which protocol's discovery failed.
59
+
60
+ ### Breaking Changes
61
+
62
+ - SMART and shared HTTP URI handling now require an explicit
63
+ `allow_insecure_localhost: true` opt-in before accepting HTTP loopback URIs
64
+ or redirects. This aligns SMART local-development behavior with UDAP DCR
65
+ metadata validation while keeping production defaults HTTPS-only.
66
+
67
+ ### Changed
68
+
69
+ - SMART Dynamic Client Registration now requires successful RFC 7591 responses
70
+ to contain a non-blank string `client_id`. Malformed identifiers that were
71
+ previously accepted now raise `Safire::Errors::RegistrationError`; valid
72
+ registration responses are unchanged.
73
+ - Ruby requirement relaxed from `>= 4.0.4` to `>= 3.2` to support Rails 7.1+ apps still
74
+ running on Ruby 3.x. The minimum is 3.2 because the gem uses anonymous keyword splat
75
+ forwarding (`**` without a name), which was introduced in Ruby 3.2.
76
+ - ActiveSupport requirement relaxed from `~> 8.0.0` to `>= 7.1, < 9`, resolving the
77
+ bundler conflict that prevented the gem from being used in Rails 8.1 apps or any app
78
+ pinning ActiveSupport 8.1.x.
79
+ - `Safire::Client` now raises `ConfigurationError` when `client_type:` is passed explicitly for
80
+ `protocol: :udap`, both at construction and via `client_type=`; previously the value was
81
+ ignored silently.
82
+
83
+ ## [0.3.0] - 2026-04-15
84
+
85
+ ### Added
86
+
87
+ - `Safire::Client#register_client` implements the OAuth 2.0 Dynamic Client Registration
88
+ Protocol (RFC 7591): POSTs client metadata to the server's registration endpoint and
89
+ returns the response as a Hash containing at minimum a `client_id`
90
+ - Endpoint is resolved from SMART discovery (`registration_endpoint` field) when not
91
+ supplied explicitly via the `registration_endpoint:` keyword argument; HTTPS is
92
+ enforced on the endpoint regardless of source
93
+ - Supports an optional initial access token via the `authorization:` keyword argument
94
+ (full `Authorization` header value including token type prefix)
95
+ - Raises `Safire::Errors::DiscoveryError` when no registration endpoint is available,
96
+ `Safire::Errors::RegistrationError` on server error or a 2xx response missing
97
+ `client_id`, and `Safire::Errors::NetworkError` on transport failure
98
+ - `Safire::Errors::RegistrationError` — new error class for Dynamic Client Registration
99
+ failures; inherits from `Safire::Errors::OAuthError` with `status`, `error_code`,
100
+ `error_description`, and `received_fields` attributes
101
+ - `Safire::Errors::OAuthError` — new shared base class for `RegistrationError`,
102
+ `TokenError`, and `AuthError`; provides `status`, `error_code`, and
103
+ `error_description` attributes and can be used as a single rescue point for any
104
+ server-side OAuth protocol error
105
+
106
+ ### Changed
107
+
108
+ - `client_id` is now optional at `ClientConfig` and `Protocols::Smart` initialization;
109
+ all authorization flows (`authorization_url`, `request_access_token`, `refresh_token`,
110
+ `request_backend_token`) validate its presence at call time and raise
111
+ `Safire::Errors::ConfigurationError` if it is absent
112
+ - `Protocols::Smart#token_endpoint` now raises `Safire::Errors::DiscoveryError` when
113
+ the discovery response does not include a `token_endpoint` field, rather than silently
114
+ passing `nil` to the HTTP client
115
+
10
116
  ## [0.2.0] - 2026-04-04
11
117
 
12
118
  ### Added
data/CONTRIBUTION.md CHANGED
@@ -20,6 +20,9 @@ This project adheres to a [Code of Conduct](CODE_OF_CONDUCT.md). By participatin
20
20
  ## Getting Started
21
21
 
22
22
  1. Fork the repository on GitHub
23
+
24
+ Note: Codex automated review currently runs only for same-repository branches because it requires repository secrets and reviews untrusted PR content. Fork-based PRs are still welcome, but maintainers will review them manually.
25
+
23
26
  2. Clone your fork locally:
24
27
  ```bash
25
28
  git clone https://github.com/YOUR_USERNAME/safire.git
@@ -34,7 +37,7 @@ This project adheres to a [Code of Conduct](CODE_OF_CONDUCT.md). By participatin
34
37
 
35
38
  ### Requirements
36
39
 
37
- - Ruby 4.0.2 or later
40
+ - Ruby 3.2 or later
38
41
  - Bundler
39
42
 
40
43
  ### Setup
@@ -231,6 +234,8 @@ end
231
234
 
232
235
  2. Open a Pull Request against `main`
233
236
 
237
+ Codex automated review only runs for same-repository branches. Pull requests from forks will receive maintainer review instead.
238
+
234
239
  3. Fill out the PR template with:
235
240
  - Summary of changes
236
241
  - Related issues
data/Gemfile CHANGED
@@ -1,7 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- ruby '4.0.2'
4
-
5
3
  gemspec
6
4
 
7
5
  group :development, :test do
@@ -23,5 +21,6 @@ group :test do
23
21
  gem 'dotenv', '~> 3.0'
24
22
  gem 'simplecov', require: false
25
23
  gem 'simplecov-cobertura', require: false
24
+ gem 'sinatra', '~> 4.0'
26
25
  gem 'webmock', '~> 3.18'
27
26
  end
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- safire (0.2.0)
5
- activesupport (~> 8.0.0)
4
+ safire (0.4.0)
5
+ activesupport (>= 7.1, < 9)
6
6
  addressable (~> 2.8)
7
7
  faraday (~> 2.14)
8
8
  faraday-follow_redirects (~> 0.4)
@@ -11,7 +11,7 @@ PATH
11
11
  GEM
12
12
  remote: https://rubygems.org/
13
13
  specs:
14
- activesupport (8.0.2.1)
14
+ activesupport (8.0.5)
15
15
  base64
16
16
  benchmark (>= 0.3)
17
17
  bigdecimal
@@ -24,20 +24,20 @@ GEM
24
24
  securerandom (>= 0.3)
25
25
  tzinfo (~> 2.0, >= 2.0.5)
26
26
  uri (>= 0.13.1)
27
- addressable (2.8.9)
27
+ addressable (2.9.0)
28
28
  public_suffix (>= 2.0.2, < 8.0)
29
29
  ast (2.4.3)
30
30
  base64 (0.3.0)
31
- benchmark (0.4.1)
32
- bigdecimal (3.3.1)
31
+ benchmark (0.5.0)
32
+ bigdecimal (4.1.2)
33
33
  bundler-audit (0.9.3)
34
34
  bundler (>= 1.2.0)
35
35
  thor (~> 1.0)
36
36
  byebug (13.0.0)
37
37
  reline (>= 0.6.0)
38
38
  coderay (1.1.3)
39
- concurrent-ruby (1.3.5)
40
- connection_pool (2.5.4)
39
+ concurrent-ruby (1.3.7)
40
+ connection_pool (3.0.2)
41
41
  crack (1.0.1)
42
42
  bigdecimal
43
43
  rexml
@@ -46,39 +46,40 @@ GEM
46
46
  irb (~> 1.10)
47
47
  reline (>= 0.3.8)
48
48
  diff-lcs (1.6.2)
49
- docile (1.4.1)
50
49
  dotenv (3.2.0)
51
50
  drb (2.2.3)
52
- erb (5.0.3)
53
- faraday (2.14.1)
51
+ erb (6.0.4)
52
+ faraday (2.14.3)
54
53
  faraday-net_http (>= 2.0, < 3.5)
55
54
  json
56
55
  logger
57
56
  faraday-follow_redirects (0.5.0)
58
57
  faraday (>= 1, < 3)
59
- faraday-net_http (3.4.2)
58
+ faraday-net_http (3.4.4)
60
59
  net-http (~> 0.5)
61
60
  hashdiff (1.2.1)
62
- i18n (1.14.7)
61
+ i18n (1.14.8)
63
62
  concurrent-ruby (~> 1.0)
64
63
  io-console (0.8.2)
65
64
  irb (1.15.3)
66
65
  pp (>= 0.6.0)
67
66
  rdoc (>= 4.0.0)
68
67
  reline (>= 0.4.2)
69
- json (2.19.2)
70
- jwt (2.10.2)
68
+ json (2.21.2)
69
+ jwt (2.10.3)
71
70
  base64
72
- language_server-protocol (3.17.0.5)
71
+ language_server-protocol (3.17.0.6)
73
72
  lint_roller (1.1.0)
74
73
  logger (1.7.0)
75
74
  method_source (1.1.0)
76
- minitest (6.0.1)
75
+ minitest (6.0.6)
76
+ drb (~> 2.0)
77
77
  prism (~> 1.5)
78
+ mustermann (3.1.1)
78
79
  net-http (0.9.1)
79
80
  uri (>= 0.11.1)
80
- parallel (1.27.0)
81
- parser (3.3.10.2)
81
+ parallel (1.28.0)
82
+ parser (3.3.12.0)
82
83
  ast (~> 2.4.1)
83
84
  racc
84
85
  pp (0.6.3)
@@ -95,13 +96,21 @@ GEM
95
96
  psych (5.2.6)
96
97
  date
97
98
  stringio
98
- public_suffix (6.0.2)
99
+ public_suffix (7.0.5)
99
100
  racc (1.8.1)
101
+ rack (3.2.6)
102
+ rack-protection (4.2.1)
103
+ base64 (>= 0.1.0)
104
+ logger (>= 1.6.0)
105
+ rack (>= 3.0.0, < 4)
106
+ rack-session (2.1.2)
107
+ base64 (>= 0.1.0)
108
+ rack (>= 3.0.0)
100
109
  rainbow (3.1.1)
101
110
  rdoc (6.14.2)
102
111
  erb
103
112
  psych (>= 4.0.0)
104
- regexp_parser (2.11.3)
113
+ regexp_parser (2.12.0)
105
114
  reline (0.6.3)
106
115
  io-console (~> 0.5)
107
116
  rexml (3.4.4)
@@ -118,18 +127,18 @@ GEM
118
127
  diff-lcs (>= 1.2.0, < 2.0)
119
128
  rspec-support (~> 3.13.0)
120
129
  rspec-support (3.13.6)
121
- rubocop (1.86.0)
130
+ rubocop (1.89.0)
122
131
  json (~> 2.3)
123
132
  language_server-protocol (~> 3.17.0.2)
124
133
  lint_roller (~> 1.1.0)
125
- parallel (~> 1.10)
134
+ parallel (>= 1.10)
126
135
  parser (>= 3.3.0.2)
127
136
  rainbow (>= 2.2.2, < 4.0)
128
137
  regexp_parser (>= 2.9.3, < 3.0)
129
138
  rubocop-ast (>= 1.49.0, < 2.0)
130
139
  ruby-progressbar (~> 1.7)
131
140
  unicode-display_width (>= 2.4.0, < 4.0)
132
- rubocop-ast (1.49.1)
141
+ rubocop-ast (1.50.0)
133
142
  parser (>= 3.3.7.2)
134
143
  prism (~> 1.7)
135
144
  rubocop-rspec (3.9.0)
@@ -137,18 +146,21 @@ GEM
137
146
  rubocop (~> 1.81)
138
147
  ruby-progressbar (1.13.0)
139
148
  securerandom (0.4.1)
140
- simplecov (0.22.0)
141
- docile (~> 1.1)
142
- simplecov-html (~> 0.11)
143
- simplecov_json_formatter (~> 0.1)
144
- simplecov-cobertura (3.1.0)
149
+ simplecov (1.0.3)
150
+ simplecov-cobertura (4.0.0)
145
151
  rexml
146
- simplecov (~> 0.19)
147
- simplecov-html (0.13.2)
148
- simplecov_json_formatter (0.1.4)
152
+ simplecov (~> 1.0)
153
+ sinatra (4.2.1)
154
+ logger (>= 1.6.0)
155
+ mustermann (~> 3.0)
156
+ rack (>= 3.0.0, < 4)
157
+ rack-protection (= 4.2.1)
158
+ rack-session (>= 2.0.0, < 3)
159
+ tilt (~> 2.0)
149
160
  stringio (3.1.9)
150
161
  thor (1.4.0)
151
- timecop (0.9.10)
162
+ tilt (2.7.0)
163
+ timecop (0.9.11)
152
164
  tzinfo (2.0.6)
153
165
  concurrent-ruby (~> 1.0)
154
166
  unicode-display_width (3.2.0)
@@ -159,7 +171,7 @@ GEM
159
171
  addressable (>= 2.8.0)
160
172
  crack (>= 0.3.2)
161
173
  hashdiff (>= 0.4.0, < 2.0.0)
162
- yard (0.9.38)
174
+ yard (0.9.45)
163
175
 
164
176
  PLATFORMS
165
177
  ruby
@@ -177,12 +189,10 @@ DEPENDENCIES
177
189
  safire!
178
190
  simplecov
179
191
  simplecov-cobertura
192
+ sinatra (~> 4.0)
180
193
  timecop (~> 0.9)
181
194
  webmock (~> 3.18)
182
195
  yard (~> 0.9)
183
196
 
184
- RUBY VERSION
185
- ruby 4.0.2p0
186
-
187
197
  BUNDLED WITH
188
198
  2.7.1
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Coverage](https://codecov.io/gh/vanessuniq/safire/branch/main/graph/badge.svg)](https://codecov.io/gh/vanessuniq/safire)
6
6
  [![Documentation](https://img.shields.io/badge/docs-yard-blue.svg)](https://vanessuniq.github.io/safire)
7
7
 
8
- Safire is a Ruby gem implementing the [SMART App Launch 2.2.0](https://hl7.org/fhir/smart-app-launch/) specification and the [UDAP Security](https://hl7.org/fhir/us/udap-security/) protocol for healthcare client applications. It handles OAuth 2.0 authorization against HL7 FHIR servers, covering PKCE, private key JWT assertions, and the Backend Services system-to-system flow, so you can focus on your application rather than protocol plumbing.
8
+ Safire is a Ruby gem for healthcare client applications that implements [SMART App Launch 2.2.0](https://hl7.org/fhir/smart-app-launch/) and [UDAP Security STU2 / v2.0.0](https://hl7.org/fhir/us/udap-security/STU2/index.html) discovery plus certificate-backed Dynamic Client Registration. It handles SMART OAuth 2.0 authorization against HL7 FHIR servers, covering PKCE, private key JWT assertions, and the Backend Services system-to-system flow, so you can focus on your application rather than protocol plumbing.
9
9
 
10
10
  ---
11
11
 
@@ -13,6 +13,7 @@ Safire is a Ruby gem implementing the [SMART App Launch 2.2.0](https://hl7.org/f
13
13
 
14
14
  ### SMART App Launch (v2.2.0)
15
15
 
16
+ - Dynamic Client Registration (RFC 7591): obtain a `client_id` at runtime by POSTing client metadata to the server's registration endpoint
16
17
  - Discovery (`/.well-known/smart-configuration`)
17
18
  - Public Client (PKCE)
18
19
  - Confidential Symmetric Client (`client_secret` + HTTP Basic Auth)
@@ -20,15 +21,69 @@ Safire is a Ruby gem implementing the [SMART App Launch 2.2.0](https://hl7.org/f
20
21
  - POST-Based Authorization
21
22
  - Backend Services (`client_credentials` grant, JWT assertion, no user interaction or PKCE; scope defaults to `system/*.rs`)
22
23
 
23
- ### UDAP
24
+ ### UDAP Security (STU2)
24
25
 
25
- > Planned. See [ROADMAP.md](https://github.com/vanessuniq/safire/blob/main/ROADMAP.md) for details.
26
+ Server metadata discovery is implemented. Pass `protocol: :udap` to fetch `/.well-known/udap`:
27
+
28
+ ```ruby
29
+ client = Safire::Client.new(
30
+ { base_url: 'https://fhir.example.com' },
31
+ protocol: :udap
32
+ )
33
+
34
+ metadata = client.server_metadata(verify_chain: false) # development/test only
35
+ # => #<Safire::Protocols::UdapMetadata ...>
36
+
37
+ # Community-scoped discovery
38
+ metadata = client.server_metadata(community: 'https://udap.example.org/community1', verify_chain: false)
39
+ ```
40
+
41
+ Production UDAP discovery requires trust anchors plus an explicit certificate revocation policy
42
+ (`crls:` or `revocation_checker:`). Use `verify_chain: false` only for development or tests.
43
+
44
+ Dynamic Client Registration is implemented for certificate-backed UDAP clients:
45
+
46
+ ```ruby
47
+ udap_client = Safire::Client.new(
48
+ {
49
+ base_url: 'https://fhir.example.com',
50
+ private_key: File.read('client-key.pem'),
51
+ certificate_chain: [File.read('client-cert.pem')]
52
+ },
53
+ protocol: :udap
54
+ )
55
+
56
+ registration = udap_client.register_client(
57
+ {
58
+ client_name: 'Example Backend Service',
59
+ contacts: ['mailto:security@example.com'],
60
+ grant_types: ['client_credentials'],
61
+ scope: 'system/Patient.rs'
62
+ },
63
+ client_uri: 'https://client.example.com',
64
+ trusted_anchors: [ca_cert],
65
+ crls: [ca_crl]
66
+ )
67
+
68
+ cancellation = udap_client.cancel_registration(
69
+ {
70
+ client_name: 'Example Backend Service',
71
+ contacts: ['mailto:security@example.com'],
72
+ scope: 'system/Patient.rs'
73
+ },
74
+ client_uri: 'https://client.example.com',
75
+ trusted_anchors: [ca_cert],
76
+ crls: [ca_crl]
77
+ )
78
+ ```
79
+
80
+ UDAP JWT client authentication and Tiered OAuth are planned. See [ROADMAP.md](https://github.com/vanessuniq/safire/blob/main/ROADMAP.md) for details.
26
81
 
27
82
  ---
28
83
 
29
84
  ## Installation
30
85
 
31
- Requires Ruby ≥ 4.0.2.
86
+ Requires Ruby ≥ 3.2.
32
87
 
33
88
  ```ruby
34
89
  gem 'safire'
@@ -154,7 +209,7 @@ bin/demo
154
209
  # Visit http://localhost:4567
155
210
  ```
156
211
 
157
- Demonstrates SMART discovery, all authorization flows, token refresh, and backend services token requests. See [`examples/sinatra_app/README.md`](examples/sinatra_app/README.md) for details.
212
+ Demonstrates SMART Dynamic Client Registration, SMART discovery, UDAP discovery with `signed_metadata` trust validation, UDAP Dynamic Client Registration and cancellation, all authorization flows, token refresh, and backend services token requests. See [`examples/sinatra_app/README.md`](examples/sinatra_app/README.md) for details.
158
213
 
159
214
  ---
160
215