Class: OmniauthOpenidFederation::Federation::EntityStatementParser

Inherits:
Object
  • Object
show all
Defined in:
lib/omniauth_openid_federation/federation/entity_statement_parser.rb

Overview

Entity Statement Parser for OpenID Federation 1.0

Examples:

Parse an entity statement

parser = EntityStatementParser.new(jwt_string, validate_signature: true)
 = parser.parse

Constant Summary collapse

ParseError =

Compatibility alias for backward compatibility

OmniauthOpenidFederation::ValidationError
JWT_PARTS_COUNT =

Standard JWT has 3 parts: header.payload.signature

3

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(jwt_string, validate_signature: false, validate_full: true, issuer_entity_configuration: nil) ⇒ EntityStatementParser

Initialize parser

Parameters:

  • jwt_string (String)

    The JWT string to parse

  • validate_signature (Boolean) (defaults to: false)

    Whether to validate the signature

  • validate_full (Boolean) (defaults to: true)

    Whether to perform full OpenID Federation validation

  • issuer_entity_configuration (Hash, EntityStatement, nil) (defaults to: nil)

    Optional: Issuer’s Entity Configuration



51
52
53
54
55
56
# File 'lib/omniauth_openid_federation/federation/entity_statement_parser.rb', line 51

def initialize(jwt_string, validate_signature: false, validate_full: true, issuer_entity_configuration: nil)
  @jwt_string = jwt_string
  @validate_signature = validate_signature
  @validate_full = validate_full
  @issuer_entity_configuration = issuer_entity_configuration
end

Class Method Details

.parse(jwt_string, validate_signature: false, validate_full: true, issuer_entity_configuration: nil) ⇒ Hash

Parse entity statement JWT

Parameters:

  • jwt_string (String)

    The JWT string to parse

  • validate_signature (Boolean) (defaults to: false)

    Whether to validate the signature (default: false)

  • validate_full (Boolean) (defaults to: true)

    Whether to perform full OpenID Federation validation (default: true)

  • issuer_entity_configuration (Hash, EntityStatement, nil) (defaults to: nil)

    Optional: Issuer’s Entity Configuration for Subordinate Statement validation

Returns:

  • (Hash)

    Parsed entity statement with header, claims, and metadata

Raises:



41
42
43
# File 'lib/omniauth_openid_federation/federation/entity_statement_parser.rb', line 41

def self.parse(jwt_string, validate_signature: false, validate_full: true, issuer_entity_configuration: nil)
  new(jwt_string, validate_signature: validate_signature, validate_full: validate_full, issuer_entity_configuration: issuer_entity_configuration).parse
end

Instance Method Details

#parseHash

Parse the entity statement

Returns:

  • (Hash)

    Parsed entity statement with header, claims, and metadata

Raises:



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
# File 'lib/omniauth_openid_federation/federation/entity_statement_parser.rb', line 62

def parse
  # Perform full OpenID Federation validation if requested
  if @validate_full
    validator = EntityStatementValidator.new(
      jwt_string: @jwt_string,
      issuer_entity_configuration: @issuer_entity_configuration
    )
    validated = validator.validate!
    @header = validated[:header]
    @payload = validated[:claims]
  else
    # Basic parsing without full validation (for backward compatibility)
    jwt_parts = @jwt_string.split(".")
    raise ParseError, "Invalid JWT format: expected #{JWT_PARTS_COUNT} parts, got #{jwt_parts.length}" if jwt_parts.length != JWT_PARTS_COUNT

    # Decode header
    @header = JSON.parse(Base64.urlsafe_decode64(jwt_parts[0]))

    # Validate typ header per OpenID Federation 1.0 Section 3.1 and 3.2.1
    # Entity Statement JWTs MUST have typ: "entity-statement+jwt"
    typ = @header["typ"] || @header[:typ]
    unless typ == "entity-statement+jwt"
      raise ValidationError, "Invalid entity statement type: expected 'entity-statement+jwt', got '#{typ}'. Entity statements without the correct typ header MUST be rejected per OpenID Federation 1.0 Section 3.1."
    end

    # Decode payload
    @payload = JSON.parse(Base64.urlsafe_decode64(jwt_parts[1]))
  end

  header = @header
  payload = @payload

  # Extract JWKS from entity statement for signature validation
  entity_jwks = payload.fetch("jwks", {}).fetch("keys", [])

  if @validate_signature && entity_jwks.any?
    # For signature validation, we need the JWT parts
    jwt_parts = @jwt_string.split(".")
    validate_signature(jwt_parts, entity_jwks, header)
  end

  # Extract metadata for all entity types
   = payload.fetch("metadata", {})
   = .fetch("openid_provider", {})
   = .fetch("openid_relying_party", {})

  result = {
    header: header,
    claims: payload,
    issuer: payload["iss"],
    sub: payload["sub"],
    exp: payload["exp"],
    iat: payload["iat"],
    jwks: payload.fetch("jwks", {}),
    metadata: {},
    # Advanced claims (Entity Configuration specific)
    authority_hints: payload["authority_hints"] || payload[:authority_hints],
    trust_marks: payload["trust_marks"] || payload[:trust_marks],
    trust_mark_issuers: payload["trust_mark_issuers"] || payload[:trust_mark_issuers],
    trust_mark_owners: payload["trust_mark_owners"] || payload[:trust_mark_owners],
    # Advanced claims (Subordinate Statement specific)
    metadata_policy: payload["metadata_policy"] || payload[:metadata_policy],
    metadata_policy_crit: payload["metadata_policy_crit"] || payload[:metadata_policy_crit],
    constraints: payload["constraints"] || payload[:constraints],
    source_endpoint: payload["source_endpoint"] || payload[:source_endpoint],
    # Other claims
    crit: payload["crit"] || payload[:crit],
    # Determine statement type
    is_entity_configuration: (payload["iss"] == payload["sub"]),
    is_subordinate_statement: (payload["iss"] != payload["sub"])
  }

  # Extract OpenID Provider metadata if present
  if .any?
    result[:metadata][:openid_provider] = {
      issuer: ["issuer"],
      authorization_endpoint: ["authorization_endpoint"],
      token_endpoint: ["token_endpoint"],
      userinfo_endpoint: ["userinfo_endpoint"],
      jwks_uri: ["jwks_uri"],
      signed_jwks_uri: ["signed_jwks_uri"],
      end_session_endpoint: ["end_session_endpoint"],
      client_registration_types_supported: ["client_registration_types_supported"],
      federation_registration_endpoint: ["federation_registration_endpoint"]
    }
  end

  # Extract OpenID Relying Party metadata if present
  if .any?
    result[:metadata][:openid_relying_party] = {
      application_type: ["application_type"],
      redirect_uris: ["redirect_uris"],
      client_registration_types: ["client_registration_types"],
      signed_jwks_uri: ["signed_jwks_uri"],
      jwks_uri: ["jwks_uri"],
      organization_name: ["organization_name"],
      logo_uri: ["logo_uri"],
      grant_types: ["grant_types"],
      response_types: ["response_types"],
      scope: ["scope"]
    }
  end

  result
rescue JSON::ParserError => e
  raise ValidationError, "Failed to parse entity statement: #{e.message}"
rescue ArgumentError => e
  raise ValidationError, "Failed to decode entity statement: #{e.message}"
end