safire 0.1.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 (72) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +62 -0
  4. data/.tool-versions +1 -0
  5. data/CHANGELOG.md +35 -0
  6. data/CODE_OF_CONDUCT.md +17 -0
  7. data/CONTRIBUTION.md +283 -0
  8. data/Gemfile +26 -0
  9. data/Gemfile.lock +186 -0
  10. data/LICENSE +201 -0
  11. data/README.md +159 -0
  12. data/ROADMAP.md +54 -0
  13. data/Rakefile +26 -0
  14. data/docs/.gitignore +5 -0
  15. data/docs/404.html +25 -0
  16. data/docs/Gemfile +37 -0
  17. data/docs/Gemfile.lock +195 -0
  18. data/docs/_config.yml +103 -0
  19. data/docs/_includes/footer_custom.html +6 -0
  20. data/docs/_includes/head_custom.html +14 -0
  21. data/docs/_sass/custom/custom.scss +108 -0
  22. data/docs/adr/ADR-001-activesupport-dependency.md +50 -0
  23. data/docs/adr/ADR-002-facade-and-forwardable.md +79 -0
  24. data/docs/adr/ADR-003-protocol-vs-client-type.md +67 -0
  25. data/docs/adr/ADR-004-clientconfig-immutability-and-entity-masking.md +59 -0
  26. data/docs/adr/ADR-005-per-client-http-ownership.md +58 -0
  27. data/docs/adr/ADR-006-lazy-discovery.md +83 -0
  28. data/docs/adr/ADR-007-https-only-redirects-and-localhost-exception.md +59 -0
  29. data/docs/adr/ADR-008-warn-return-false-for-compliance-validation.md +74 -0
  30. data/docs/adr/index.md +22 -0
  31. data/docs/advanced.md +284 -0
  32. data/docs/configuration/client-setup.md +158 -0
  33. data/docs/configuration/index.md +60 -0
  34. data/docs/configuration/logging.md +86 -0
  35. data/docs/index.md +64 -0
  36. data/docs/installation.md +96 -0
  37. data/docs/security.md +256 -0
  38. data/docs/smart-on-fhir/confidential-asymmetric/authorization.md +72 -0
  39. data/docs/smart-on-fhir/confidential-asymmetric/index.md +162 -0
  40. data/docs/smart-on-fhir/confidential-asymmetric/token-exchange.md +250 -0
  41. data/docs/smart-on-fhir/confidential-symmetric/authorization.md +75 -0
  42. data/docs/smart-on-fhir/confidential-symmetric/index.md +69 -0
  43. data/docs/smart-on-fhir/confidential-symmetric/token-exchange.md +215 -0
  44. data/docs/smart-on-fhir/discovery/capability-checks.md +142 -0
  45. data/docs/smart-on-fhir/discovery/index.md +96 -0
  46. data/docs/smart-on-fhir/discovery/metadata.md +147 -0
  47. data/docs/smart-on-fhir/index.md +72 -0
  48. data/docs/smart-on-fhir/post-based-authorization.md +190 -0
  49. data/docs/smart-on-fhir/public-client/authorization.md +112 -0
  50. data/docs/smart-on-fhir/public-client/index.md +80 -0
  51. data/docs/smart-on-fhir/public-client/token-exchange.md +249 -0
  52. data/docs/troubleshooting/auth-errors.md +124 -0
  53. data/docs/troubleshooting/client-errors.md +130 -0
  54. data/docs/troubleshooting/index.md +99 -0
  55. data/docs/troubleshooting/token-errors.md +99 -0
  56. data/docs/udap.md +78 -0
  57. data/lib/safire/client.rb +195 -0
  58. data/lib/safire/client_config.rb +169 -0
  59. data/lib/safire/client_config_builder.rb +72 -0
  60. data/lib/safire/entity.rb +26 -0
  61. data/lib/safire/errors.rb +247 -0
  62. data/lib/safire/http_client.rb +87 -0
  63. data/lib/safire/jwt_assertion.rb +237 -0
  64. data/lib/safire/middleware/https_only_redirects.rb +39 -0
  65. data/lib/safire/pkce.rb +39 -0
  66. data/lib/safire/protocols/behaviours.rb +54 -0
  67. data/lib/safire/protocols/smart.rb +378 -0
  68. data/lib/safire/protocols/smart_metadata.rb +231 -0
  69. data/lib/safire/version.rb +4 -0
  70. data/lib/safire.rb +54 -0
  71. data/safire.gemspec +36 -0
  72. metadata +184 -0
@@ -0,0 +1,231 @@
1
+ module Safire
2
+ module Protocols
3
+ # SMART Metadata obtained from SMART on FHIR discovery endpoint. Attributes are defined
4
+ # as per [SMART on FHIR specification](https://build.fhir.org/ig/HL7/smart-app-launch/conformance.html#using-well-known)
5
+ #
6
+ # @!attribute [r] issuer
7
+ # @return [String] conveying this system’s OpenID Connect Issuer URL. Required if the server’s
8
+ # capabilities include sso-openid-connect.
9
+ # @!attribute [r] jwks_uri
10
+ # @return [String] URL of the server’s JSON Web Key Set endpoint. Required if the server’s capabilities
11
+ # include sso-openid-connect.
12
+ # @!attribute [r] authorization_endpoint
13
+ # @return [String] URL of the server’s OAuth2 Authorization Endpoint. Required if the server’s capabilities
14
+ # include launch-standalone or launch-ehr-launch.
15
+ # @!attribute [r] grant_types_supported
16
+ # @return [Array<String>] list of OAuth2 grant types supported at the token endpoint.
17
+ # @!attribute [r] token_endpoint
18
+ # @return [String] URL of the server’s OAuth2 Token Endpoint.
19
+ # @!attribute [r] token_endpoint_auth_methods_supported
20
+ # @return [Array<String>] list of client authentication methods supported at the token endpoint.
21
+ # Optionally provided.
22
+ # @!attribute [r] token_endpoint_auth_signing_alg_values_supported
23
+ # @return [Array<String>] list of signing algorithms supported for JWT-based client authentication.
24
+ # Optionally provided. Used for confidential asymmetric authentication.
25
+ # @!attribute [r] registration_endpoint
26
+ # @return [String] URL of the server’s OAuth2 Dynamic Client Registration Endpoint. Optionally provided.
27
+ # @!attribute [r] associated_endpoints
28
+ # @return [Array<Hash>] list of objects for endpoints that share the same authorization mechanism
29
+ # as this FHIR endpoint, each with a “url” and “capabilities” array. Optionally provided.
30
+ # @!attribute [r] user_access_brand_bundle
31
+ # @return [String] URL for a Brand Bundle for user-facing applications. Optionally provided.
32
+ # @!attribute [r] user_access_brand_identifier
33
+ # @return [String] Identifier for the primary entry in a Brand Bundle. Optionally provided.
34
+ # @!attribute [r] scopes_supported
35
+ # @return [Array<String>] list of scopes a client may request. Optionally provided.
36
+ # @!attribute [r] response_types_supported
37
+ # @return [Array<String>] list of OAuth2 response types supported. Optionally provided.
38
+ # @!attribute [r] management_endpoint
39
+ # @return [String] URL where an end-user can view which applications currently have access to data
40
+ # and can make adjustments to these access rights. Optionally provided.
41
+ # @!attribute [r] introspection_endpoint
42
+ # @return [String] URL to a server’s introspection endpoint that can be used to validate a token.
43
+ # Optionally provided.
44
+ # @!attribute [r] revocation_endpoint
45
+ # @return [String] URL to a server’s revocation endpoint that can be used to revoke a token.
46
+ # Optionally provided.
47
+ # @!attribute [r] capabilities
48
+ # @return [Array<String>] list of SMART capabilities supported by the server.
49
+ # @!attribute [r] code_challenge_methods_supported
50
+ # @return [Array<String>] list of PKCE code challenge methods supported. Should include "S256".
51
+ # Should not include "plain". See {#valid?} for compliance checks.
52
+ class SmartMetadata < Safire::Entity
53
+ REQUIRED_ATTRIBUTES = %i[
54
+ grant_types_supported token_endpoint capabilities
55
+ code_challenge_methods_supported
56
+ ].freeze
57
+
58
+ OPTIONAL_ATTRIBUTES = %i[
59
+ issuer
60
+ jwks_uri
61
+ authorization_endpoint
62
+ token_endpoint_auth_methods_supported
63
+ token_endpoint_auth_signing_alg_values_supported
64
+ registration_endpoint
65
+ associated_endpoints
66
+ user_access_brand_bundle
67
+ user_access_brand_identifier
68
+ scopes_supported
69
+ response_types_supported
70
+ management_endpoint
71
+ introspection_endpoint
72
+ revocation_endpoint
73
+ ].freeze
74
+
75
+ ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze
76
+
77
+ # Supported asymmetric signing algorithms (required by SMART spec)
78
+ SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze
79
+
80
+ attr_reader(*ATTRIBUTES)
81
+
82
+ def initialize(metadata)
83
+ super(metadata, ATTRIBUTES)
84
+ end
85
+
86
+ # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0.
87
+ #
88
+ # This is a user-callable helper. Safire performs discovery without automatically
89
+ # asserting server compliance — it is the caller's responsibility to invoke this
90
+ # method when they wish to verify conformance.
91
+ #
92
+ # Checks performed:
93
+ # - All required fields are present
94
+ # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported)
95
+ # - Conditional fields present when their capability is advertised
96
+ # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types)
97
+ # - `code_challenge_methods_supported` includes 'S256'
98
+ # (SMART App Launch 2.2.0, §Conformance — SHALL be included)
99
+ # - `code_challenge_methods_supported` does NOT include 'plain'
100
+ # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included)
101
+ #
102
+ # A warning is logged for each SMART 2.2.0 violation detected.
103
+ #
104
+ # @return [Boolean] true if all checks pass, false if any violation is found
105
+ def valid?
106
+ required_attrs = [*REQUIRED_ATTRIBUTES]
107
+ required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required?
108
+ required_attrs.push(:authorization_endpoint) if authorization_endpoint_required?
109
+
110
+ missing_attrs = required_attrs.reject { |attr| public_send(attr) }
111
+ missing_attrs.each do |attr|
112
+ Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing")
113
+ end
114
+
115
+ pkce_valid = validate_pkce_methods!
116
+
117
+ missing_attrs.empty? && pkce_valid
118
+ end
119
+
120
+ # Launch type support checks - requires both capability and authorization_endpoint
121
+
122
+ def supports_ehr_launch?
123
+ ehr_launch_capability? && authorization_endpoint.present?
124
+ end
125
+
126
+ def supports_standalone_launch?
127
+ standalone_launch_capability? && authorization_endpoint.present?
128
+ end
129
+
130
+ # Authentication type support checks
131
+
132
+ # Checks if the server supports public client authentication.
133
+ # @return [Boolean] true if server has client-public capability
134
+ def supports_public_auth?
135
+ capability?('client-public')
136
+ end
137
+
138
+ # Checks if the server supports confidential symmetric authentication.
139
+ # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic
140
+ def supports_symmetric_auth?
141
+ capability?('client-confidential-symmetric') &&
142
+ (token_endpoint_auth_methods_supported.blank? ||
143
+ token_endpoint_auth_methods_supported.include?('client_secret_basic'))
144
+ end
145
+
146
+ # Checks if the server supports confidential asymmetric authentication.
147
+ # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt,
148
+ # and has supported algorithms
149
+ def supports_asymmetric_auth?
150
+ capability?('client-confidential-asymmetric') &&
151
+ (token_endpoint_auth_methods_supported.blank? ||
152
+ token_endpoint_auth_methods_supported.include?('private_key_jwt')) &&
153
+ asymmetric_signing_algorithms_supported.any?
154
+ end
155
+
156
+ # Returns the asymmetric signing algorithms supported by both client and server.
157
+ # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384).
158
+ # @return [Array<String>] list of supported algorithms
159
+ def asymmetric_signing_algorithms_supported
160
+ server_algs = token_endpoint_auth_signing_alg_values_supported.presence
161
+ (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS
162
+ end
163
+
164
+ # Feature support checks
165
+
166
+ def supports_post_based_authorization?
167
+ capability?('authorize-post')
168
+ end
169
+
170
+ def supports_openid_connect?
171
+ openid_connect_capability? && issuer.present? && jwks_uri.present?
172
+ end
173
+
174
+ # Capability-only checks (does not verify required fields are present)
175
+
176
+ def ehr_launch_capability?
177
+ capability?('launch-ehr')
178
+ end
179
+
180
+ def standalone_launch_capability?
181
+ capability?('launch-standalone')
182
+ end
183
+
184
+ def openid_connect_capability?
185
+ capability?('sso-openid-connect')
186
+ end
187
+
188
+ private
189
+
190
+ def capability?(name)
191
+ capabilities&.include?(name)
192
+ end
193
+
194
+ def issuer_and_jwks_uri_required?
195
+ openid_connect_capability?
196
+ end
197
+
198
+ def authorization_endpoint_required?
199
+ ehr_launch_capability? || standalone_launch_capability?
200
+ end
201
+
202
+ # Validates PKCE code challenge methods per SMART App Launch 2.2.0:
203
+ # - 'S256' SHALL be included
204
+ # - 'plain' SHALL NOT be included
205
+ #
206
+ # @return [Boolean] true if both conditions are satisfied
207
+ def validate_pkce_methods!
208
+ methods = code_challenge_methods_supported
209
+ valid = true
210
+
211
+ unless methods&.include?('S256')
212
+ Safire.logger.warn(
213
+ "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \
214
+ '(SMART App Launch 2.2.0 requires S256)'
215
+ )
216
+ valid = false
217
+ end
218
+
219
+ if methods&.include?('plain')
220
+ Safire.logger.warn(
221
+ "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \
222
+ '(SMART App Launch 2.2.0 prohibits plain)'
223
+ )
224
+ valid = false
225
+ end
226
+
227
+ valid
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,4 @@
1
+ # lib/safire/version.rb
2
+ module Safire
3
+ VERSION = '0.1.0'.freeze
4
+ end
data/lib/safire.rb ADDED
@@ -0,0 +1,54 @@
1
+ require 'logger'
2
+ require 'active_support/all'
3
+ require 'addressable/uri'
4
+ require 'base64'
5
+
6
+ require_relative 'safire/version'
7
+ require_relative 'safire/errors'
8
+ require_relative 'safire/middleware/https_only_redirects'
9
+ require_relative 'safire/http_client'
10
+ require_relative 'safire/entity'
11
+ require_relative 'safire/pkce'
12
+ require_relative 'safire/jwt_assertion'
13
+
14
+ root = File.expand_path '.', File.dirname(File.absolute_path(__FILE__))
15
+ Dir.glob(File.join(root, 'safire', 'protocols', '**', '*.rb')).each do |file|
16
+ require file
17
+ end
18
+
19
+ require_relative 'safire/client_config_builder'
20
+ require_relative 'safire/client_config'
21
+ require_relative 'safire/client'
22
+
23
+ # Main module for Safire gem
24
+ module Safire
25
+ class << self
26
+ attr_reader :configuration
27
+
28
+ def configure
29
+ @configuration ||= Configuration.new
30
+ yield(configuration)
31
+ end
32
+
33
+ def logger
34
+ log = configuration&.logger || default_logger
35
+ log.level = configuration.log_level if configuration&.log_level && log.respond_to?(:level=)
36
+ log
37
+ end
38
+
39
+ def default_logger
40
+ @default_logger ||= Logger.new(ENV['SAFIRE_LOGGER'] || $stdout).tap do |l|
41
+ l.level = Logger::INFO
42
+ end
43
+ end
44
+ end
45
+
46
+ class Configuration
47
+ attr_accessor :logger, :log_level, :user_agent, :log_http
48
+
49
+ def initialize
50
+ @user_agent = "Safire v#{Safire::VERSION}"
51
+ @log_http = true
52
+ end
53
+ end
54
+ end
data/safire.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # safire.gemspec
2
+ require_relative 'lib/safire/version'
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'safire'
6
+ spec.version = Safire::VERSION
7
+ spec.authors = ['Vanessa Fotso']
8
+ spec.email = ['vanessuniq@gmail.com']
9
+ spec.summary = 'SMART on FHIR and UDAP implementation for Ruby'
10
+ spec.description = 'A Ruby gem implementing SMART on FHIR and UDAP protocols for clients.'
11
+ spec.homepage = 'https://github.com/vanessuniq/safire'
12
+ spec.license = 'Apache-2.0'
13
+ spec.required_ruby_version = Gem::Requirement.new('>= 4.0.2')
14
+
15
+ spec.metadata['source_code_uri'] = spec.homepage
16
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
17
+ spec.metadata['documentation_uri'] = 'https://vanessuniq.github.io/safire'
18
+ spec.metadata['rubygems_mfa_required'] = 'true'
19
+
20
+ spec.files = Dir.chdir(__dir__) do
21
+ `git ls-files -z`.split("\x0").reject do |f|
22
+ (File.expand_path(f) == __FILE__) ||
23
+ f.start_with?(*%w[bin/ test/ spec/ features/ examples/ .git .github appveyor])
24
+ end
25
+ end
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+
30
+ # Runtime deps
31
+ spec.add_dependency 'activesupport', '~> 8.0.0'
32
+ spec.add_dependency 'addressable', '~> 2.8'
33
+ spec.add_dependency 'faraday', '~> 2.14'
34
+ spec.add_dependency 'faraday-follow_redirects', '~> 0.4'
35
+ spec.add_dependency 'jwt', '~> 2.8'
36
+ end
metadata ADDED
@@ -0,0 +1,184 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: safire
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vanessa Fotso
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 8.0.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 8.0.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: addressable
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.8'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.8'
40
+ - !ruby/object:Gem::Dependency
41
+ name: faraday
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.14'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.14'
54
+ - !ruby/object:Gem::Dependency
55
+ name: faraday-follow_redirects
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.4'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.4'
68
+ - !ruby/object:Gem::Dependency
69
+ name: jwt
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '2.8'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '2.8'
82
+ description: A Ruby gem implementing SMART on FHIR and UDAP protocols for clients.
83
+ email:
84
+ - vanessuniq@gmail.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - ".rspec"
90
+ - ".rubocop.yml"
91
+ - ".tool-versions"
92
+ - CHANGELOG.md
93
+ - CODE_OF_CONDUCT.md
94
+ - CONTRIBUTION.md
95
+ - Gemfile
96
+ - Gemfile.lock
97
+ - LICENSE
98
+ - README.md
99
+ - ROADMAP.md
100
+ - Rakefile
101
+ - docs/.gitignore
102
+ - docs/404.html
103
+ - docs/Gemfile
104
+ - docs/Gemfile.lock
105
+ - docs/_config.yml
106
+ - docs/_includes/footer_custom.html
107
+ - docs/_includes/head_custom.html
108
+ - docs/_sass/custom/custom.scss
109
+ - docs/adr/ADR-001-activesupport-dependency.md
110
+ - docs/adr/ADR-002-facade-and-forwardable.md
111
+ - docs/adr/ADR-003-protocol-vs-client-type.md
112
+ - docs/adr/ADR-004-clientconfig-immutability-and-entity-masking.md
113
+ - docs/adr/ADR-005-per-client-http-ownership.md
114
+ - docs/adr/ADR-006-lazy-discovery.md
115
+ - docs/adr/ADR-007-https-only-redirects-and-localhost-exception.md
116
+ - docs/adr/ADR-008-warn-return-false-for-compliance-validation.md
117
+ - docs/adr/index.md
118
+ - docs/advanced.md
119
+ - docs/configuration/client-setup.md
120
+ - docs/configuration/index.md
121
+ - docs/configuration/logging.md
122
+ - docs/index.md
123
+ - docs/installation.md
124
+ - docs/security.md
125
+ - docs/smart-on-fhir/confidential-asymmetric/authorization.md
126
+ - docs/smart-on-fhir/confidential-asymmetric/index.md
127
+ - docs/smart-on-fhir/confidential-asymmetric/token-exchange.md
128
+ - docs/smart-on-fhir/confidential-symmetric/authorization.md
129
+ - docs/smart-on-fhir/confidential-symmetric/index.md
130
+ - docs/smart-on-fhir/confidential-symmetric/token-exchange.md
131
+ - docs/smart-on-fhir/discovery/capability-checks.md
132
+ - docs/smart-on-fhir/discovery/index.md
133
+ - docs/smart-on-fhir/discovery/metadata.md
134
+ - docs/smart-on-fhir/index.md
135
+ - docs/smart-on-fhir/post-based-authorization.md
136
+ - docs/smart-on-fhir/public-client/authorization.md
137
+ - docs/smart-on-fhir/public-client/index.md
138
+ - docs/smart-on-fhir/public-client/token-exchange.md
139
+ - docs/troubleshooting/auth-errors.md
140
+ - docs/troubleshooting/client-errors.md
141
+ - docs/troubleshooting/index.md
142
+ - docs/troubleshooting/token-errors.md
143
+ - docs/udap.md
144
+ - lib/safire.rb
145
+ - lib/safire/client.rb
146
+ - lib/safire/client_config.rb
147
+ - lib/safire/client_config_builder.rb
148
+ - lib/safire/entity.rb
149
+ - lib/safire/errors.rb
150
+ - lib/safire/http_client.rb
151
+ - lib/safire/jwt_assertion.rb
152
+ - lib/safire/middleware/https_only_redirects.rb
153
+ - lib/safire/pkce.rb
154
+ - lib/safire/protocols/behaviours.rb
155
+ - lib/safire/protocols/smart.rb
156
+ - lib/safire/protocols/smart_metadata.rb
157
+ - lib/safire/version.rb
158
+ - safire.gemspec
159
+ homepage: https://github.com/vanessuniq/safire
160
+ licenses:
161
+ - Apache-2.0
162
+ metadata:
163
+ source_code_uri: https://github.com/vanessuniq/safire
164
+ changelog_uri: https://github.com/vanessuniq/safire/blob/main/CHANGELOG.md
165
+ documentation_uri: https://vanessuniq.github.io/safire
166
+ rubygems_mfa_required: 'true'
167
+ rdoc_options: []
168
+ require_paths:
169
+ - lib
170
+ required_ruby_version: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: 4.0.2
175
+ required_rubygems_version: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ requirements: []
181
+ rubygems_version: 4.0.6
182
+ specification_version: 4
183
+ summary: SMART on FHIR and UDAP implementation for Ruby
184
+ test_files: []