Class: Safire::Protocols::SmartMetadata
- Defined in:
- lib/safire/protocols/smart_metadata.rb
Overview
SMART Metadata obtained from SMART discovery endpoint. Attributes are defined as per SMART App Launch specification
Constant Summary collapse
- REQUIRED_ATTRIBUTES =
%i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze
- OPTIONAL_ATTRIBUTES =
%i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze
- ATTRIBUTES =
(REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze
- SUPPORTED_ASYMMETRIC_ALGORITHMS =
Supported asymmetric signing algorithms (required by SMART spec)
%w[RS384 ES384].freeze
Instance Attribute Summary collapse
-
#associated_endpoints ⇒ Array<Hash>
readonly
List of objects for endpoints that share the same authorization mechanism as this FHIR endpoint, each with a “url” and “capabilities” array.
-
#authorization_endpoint ⇒ String
readonly
URL of the server’s OAuth2 Authorization Endpoint.
-
#capabilities ⇒ Array<String>
readonly
List of SMART capabilities supported by the server.
-
#code_challenge_methods_supported ⇒ Array<String>
readonly
List of PKCE code challenge methods supported.
-
#grant_types_supported ⇒ Array<String>
readonly
List of OAuth2 grant types supported at the token endpoint.
-
#introspection_endpoint ⇒ String
readonly
URL to a server’s introspection endpoint that can be used to validate a token.
-
#issuer ⇒ String
readonly
Conveying this system’s OpenID Connect Issuer URL.
-
#jwks_uri ⇒ String
readonly
URL of the server’s JSON Web Key Set endpoint.
-
#management_endpoint ⇒ String
readonly
URL where an end-user can view which applications currently have access to data and can make adjustments to these access rights.
-
#registration_endpoint ⇒ String
readonly
URL of the server’s OAuth2 Dynamic Client Registration Endpoint.
-
#response_types_supported ⇒ Array<String>
readonly
List of OAuth2 response types supported.
-
#revocation_endpoint ⇒ String
readonly
URL to a server’s revocation endpoint that can be used to revoke a token.
-
#scopes_supported ⇒ Array<String>
readonly
List of scopes a client may request.
-
#token_endpoint ⇒ String
readonly
URL of the server’s OAuth2 Token Endpoint.
-
#token_endpoint_auth_methods_supported ⇒ Array<String>
readonly
List of client authentication methods supported at the token endpoint.
-
#token_endpoint_auth_signing_alg_values_supported ⇒ Array<String>
readonly
List of signing algorithms supported for JWT-based client authentication.
-
#user_access_brand_bundle ⇒ String
readonly
URL for a Brand Bundle for user-facing applications.
-
#user_access_brand_identifier ⇒ String
readonly
Identifier for the primary entry in a Brand Bundle.
Instance Method Summary collapse
-
#asymmetric_signing_algorithms_supported ⇒ Array<String>
Returns the asymmetric signing algorithms supported by both client and server.
-
#ehr_launch_capability? ⇒ Boolean
Capability-only checks (does not verify required fields are present).
-
#initialize(metadata) ⇒ SmartMetadata
constructor
A new instance of SmartMetadata.
- #openid_connect_capability? ⇒ Boolean
- #standalone_launch_capability? ⇒ Boolean
-
#supports_asymmetric_auth? ⇒ Boolean
Checks if the server supports confidential asymmetric authentication.
-
#supports_backend_services? ⇒ Boolean
Checks if the server supports the SMART Backend Services workflow.
-
#supports_ehr_launch? ⇒ Boolean
Launch type support checks - requires both capability and authorization_endpoint.
- #supports_openid_connect? ⇒ Boolean
-
#supports_post_based_authorization? ⇒ Boolean
Feature support checks.
-
#supports_public_auth? ⇒ Boolean
Checks if the server supports public client authentication.
- #supports_standalone_launch? ⇒ Boolean
-
#supports_symmetric_auth? ⇒ Boolean
Checks if the server supports confidential symmetric authentication.
-
#valid? ⇒ Boolean
Checks whether the server’s SMART metadata is valid according to SMART App Launch 2.2.0.
Methods inherited from Entity
Constructor Details
#initialize(metadata) ⇒ SmartMetadata
Returns a new instance of SmartMetadata.
82 83 84 |
# File 'lib/safire/protocols/smart_metadata.rb', line 82 def initialize() super(, ATTRIBUTES) end |
Instance Attribute Details
#associated_endpoints ⇒ Array<Hash> (readonly)
Returns list of objects for endpoints that share the same authorization mechanism as this FHIR endpoint, each with a “url” and “capabilities” array. Optionally provided.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#authorization_endpoint ⇒ String (readonly)
Returns URL of the server’s OAuth2 Authorization Endpoint. Required if the server’s capabilities include launch-standalone or launch-ehr-launch.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#capabilities ⇒ Array<String> (readonly)
Returns list of SMART capabilities supported by the server.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#code_challenge_methods_supported ⇒ Array<String> (readonly)
Returns list of PKCE code challenge methods supported. Should include “S256”. Should not include “plain”. See #valid? for compliance checks.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#grant_types_supported ⇒ Array<String> (readonly)
Returns list of OAuth2 grant types supported at the token endpoint.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#introspection_endpoint ⇒ String (readonly)
Returns URL to a server’s introspection endpoint that can be used to validate a token. Optionally provided.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#issuer ⇒ String (readonly)
Returns conveying this system’s OpenID Connect Issuer URL. Required if the server’s capabilities include sso-openid-connect.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#jwks_uri ⇒ String (readonly)
Returns URL of the server’s JSON Web Key Set endpoint. Required if the server’s capabilities include sso-openid-connect.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#management_endpoint ⇒ String (readonly)
Returns URL where an end-user can view which applications currently have access to data and can make adjustments to these access rights. Optionally provided.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#registration_endpoint ⇒ String (readonly)
Returns URL of the server’s OAuth2 Dynamic Client Registration Endpoint. Optionally provided.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#response_types_supported ⇒ Array<String> (readonly)
Returns list of OAuth2 response types supported. Optionally provided.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#revocation_endpoint ⇒ String (readonly)
Returns URL to a server’s revocation endpoint that can be used to revoke a token. Optionally provided.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#scopes_supported ⇒ Array<String> (readonly)
Returns list of scopes a client may request. Optionally provided.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#token_endpoint ⇒ String (readonly)
Returns URL of the server’s OAuth2 Token Endpoint.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#token_endpoint_auth_methods_supported ⇒ Array<String> (readonly)
Returns list of client authentication methods supported at the token endpoint. Optionally provided.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#token_endpoint_auth_signing_alg_values_supported ⇒ Array<String> (readonly)
Returns list of signing algorithms supported for JWT-based client authentication. Optionally provided. Used for confidential asymmetric authentication.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#user_access_brand_bundle ⇒ String (readonly)
Returns URL for a Brand Bundle for user-facing applications. Optionally provided.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
#user_access_brand_identifier ⇒ String (readonly)
Returns Identifier for the primary entry in a Brand Bundle. Optionally provided.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/safire/protocols/smart_metadata.rb', line 52 class SmartMetadata < Safire::Entity REQUIRED_ATTRIBUTES = %i[ grant_types_supported token_endpoint capabilities code_challenge_methods_supported ].freeze OPTIONAL_ATTRIBUTES = %i[ issuer jwks_uri authorization_endpoint token_endpoint_auth_methods_supported token_endpoint_auth_signing_alg_values_supported registration_endpoint associated_endpoints user_access_brand_bundle user_access_brand_identifier scopes_supported response_types_supported management_endpoint introspection_endpoint revocation_endpoint ].freeze ATTRIBUTES = (REQUIRED_ATTRIBUTES | OPTIONAL_ATTRIBUTES).freeze # Supported asymmetric signing algorithms (required by SMART spec) SUPPORTED_ASYMMETRIC_ALGORITHMS = %w[RS384 ES384].freeze attr_reader(*ATTRIBUTES) def initialize() super(, ATTRIBUTES) end # Checks whether the server's SMART metadata is valid according to SMART App Launch 2.2.0. # # This is a user-callable helper. Safire performs discovery without automatically # asserting server compliance — it is the caller's responsibility to invoke this # method when they wish to verify conformance. # # Checks performed: # - All required fields are present # (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) # - Conditional fields present when their capability is advertised # (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) # - `code_challenge_methods_supported` includes 'S256' # (SMART App Launch 2.2.0, §Conformance — SHALL be included) # - `code_challenge_methods_supported` does NOT include 'plain' # (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included) # # A warning is logged for each SMART 2.2.0 violation detected. # # @return [Boolean] true if all checks pass, false if any violation is found def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end # Launch type support checks - requires both capability and authorization_endpoint def supports_ehr_launch? ehr_launch_capability? && .present? end def supports_standalone_launch? standalone_launch_capability? && .present? end # Authentication type support checks # Checks if the server supports public client authentication. # @return [Boolean] true if server has client-public capability def supports_public_auth? capability?('client-public') end # Checks if the server supports confidential symmetric authentication. # @return [Boolean] true if server has capability and auth methods not advertised or includes client_secret_basic def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end # Checks if the server supports the SMART Backend Services workflow. # @return [Boolean] true if the server advertises the client_credentials grant type # and supports private_key_jwt authentication (via {#supports_asymmetric_auth?}) def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end # Checks if the server supports confidential asymmetric authentication. # @return [Boolean] true if server has capability, auth methods not advertised or includes private_key_jwt, # and has supported algorithms def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end # Returns the asymmetric signing algorithms supported by both client and server. # If the server doesn't advertise algorithms, assumes it supports the required ones (RS384, ES384). # @return [Array<String>] list of supported algorithms def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end # Feature support checks def capability?('authorize-post') end def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end # Capability-only checks (does not verify required fields are present) def ehr_launch_capability? capability?('launch-ehr') end def standalone_launch_capability? capability?('launch-standalone') end def openid_connect_capability? capability?('sso-openid-connect') end private def capability?(name) capabilities&.include?(name) end def issuer_and_jwks_uri_required? openid_connect_capability? end def ehr_launch_capability? || standalone_launch_capability? end # Validates PKCE code challenge methods per SMART App Launch 2.2.0: # - 'S256' SHALL be included # - 'plain' SHALL NOT be included # # @return [Boolean] true if both conditions are satisfied def validate_pkce_methods! methods = code_challenge_methods_supported valid = true unless methods&.include?('S256') Safire.logger.warn( "SMART metadata non-compliance: 'S256' is missing from code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 requires S256)' ) valid = false end if methods&.include?('plain') Safire.logger.warn( "SMART metadata non-compliance: 'plain' is present in code_challenge_methods_supported " \ '(SMART App Launch 2.2.0 prohibits plain)' ) valid = false end valid end end |
Instance Method Details
#asymmetric_signing_algorithms_supported ⇒ Array<String>
Returns the asymmetric signing algorithms supported by both client and server. If the server doesn’t advertise algorithms, assumes it supports the required ones (RS384, ES384).
168 169 170 171 |
# File 'lib/safire/protocols/smart_metadata.rb', line 168 def asymmetric_signing_algorithms_supported server_algs = token_endpoint_auth_signing_alg_values_supported.presence (server_algs || SUPPORTED_ASYMMETRIC_ALGORITHMS) & SUPPORTED_ASYMMETRIC_ALGORITHMS end |
#ehr_launch_capability? ⇒ Boolean
Capability-only checks (does not verify required fields are present)
185 186 187 |
# File 'lib/safire/protocols/smart_metadata.rb', line 185 def ehr_launch_capability? capability?('launch-ehr') end |
#openid_connect_capability? ⇒ Boolean
193 194 195 |
# File 'lib/safire/protocols/smart_metadata.rb', line 193 def openid_connect_capability? capability?('sso-openid-connect') end |
#standalone_launch_capability? ⇒ Boolean
189 190 191 |
# File 'lib/safire/protocols/smart_metadata.rb', line 189 def standalone_launch_capability? capability?('launch-standalone') end |
#supports_asymmetric_auth? ⇒ Boolean
Checks if the server supports confidential asymmetric authentication.
158 159 160 161 162 163 |
# File 'lib/safire/protocols/smart_metadata.rb', line 158 def supports_asymmetric_auth? capability?('client-confidential-asymmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('private_key_jwt')) && asymmetric_signing_algorithms_supported.any? end |
#supports_backend_services? ⇒ Boolean
Checks if the server supports the SMART Backend Services workflow.
149 150 151 152 153 |
# File 'lib/safire/protocols/smart_metadata.rb', line 149 def supports_backend_services? grant_types_supported.present? && grant_types_supported.include?('client_credentials') && supports_asymmetric_auth? end |
#supports_ehr_launch? ⇒ Boolean
Launch type support checks - requires both capability and authorization_endpoint
122 123 124 |
# File 'lib/safire/protocols/smart_metadata.rb', line 122 def supports_ehr_launch? ehr_launch_capability? && .present? end |
#supports_openid_connect? ⇒ Boolean
179 180 181 |
# File 'lib/safire/protocols/smart_metadata.rb', line 179 def supports_openid_connect? openid_connect_capability? && issuer.present? && jwks_uri.present? end |
#supports_post_based_authorization? ⇒ Boolean
Feature support checks
175 176 177 |
# File 'lib/safire/protocols/smart_metadata.rb', line 175 def capability?('authorize-post') end |
#supports_public_auth? ⇒ Boolean
Checks if the server supports public client authentication.
134 135 136 |
# File 'lib/safire/protocols/smart_metadata.rb', line 134 def supports_public_auth? capability?('client-public') end |
#supports_standalone_launch? ⇒ Boolean
126 127 128 |
# File 'lib/safire/protocols/smart_metadata.rb', line 126 def supports_standalone_launch? standalone_launch_capability? && .present? end |
#supports_symmetric_auth? ⇒ Boolean
Checks if the server supports confidential symmetric authentication.
140 141 142 143 144 |
# File 'lib/safire/protocols/smart_metadata.rb', line 140 def supports_symmetric_auth? capability?('client-confidential-symmetric') && (token_endpoint_auth_methods_supported.blank? || token_endpoint_auth_methods_supported.include?('client_secret_basic')) end |
#valid? ⇒ Boolean
Checks whether the server’s SMART metadata is valid according to SMART App Launch 2.2.0.
This is a user-callable helper. Safire performs discovery without automatically asserting server compliance — it is the caller’s responsibility to invoke this method when they wish to verify conformance.
Checks performed: - All required fields are present (token_endpoint, grant_types_supported, capabilities, code_challenge_methods_supported) - Conditional fields present when their capability is advertised (issuer + jwks_uri for sso-openid-connect; authorization_endpoint for launch types) - code_challenge_methods_supported includes ‘S256’ (SMART App Launch 2.2.0, §Conformance — SHALL be included) - code_challenge_methods_supported does NOT include ‘plain’ (SMART App Launch 2.2.0, §Conformance — SHALL NOT be included)
A warning is logged for each SMART 2.2.0 violation detected.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/safire/protocols/smart_metadata.rb', line 105 def valid? required_attrs = [*REQUIRED_ATTRIBUTES] required_attrs.push(:issuer, :jwks_uri) if issuer_and_jwks_uri_required? required_attrs.push(:authorization_endpoint) if missing_attrs = required_attrs.reject { |attr| public_send(attr) } missing_attrs.each do |attr| Safire.logger.warn("SMART metadata non-compliance: required field '#{attr}' is missing") end pkce_valid = validate_pkce_methods! missing_attrs.empty? && pkce_valid end |