omniauth_openid_federation 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -116,7 +116,7 @@ module OmniauthOpenidFederation
116
116
  subject_entity_id = request.params["sub"]
117
117
 
118
118
  unless subject_entity_id
119
- return error_response(400, {error: "invalid_request", error_description: "Missing required parameter: sub"}.to_json)
119
+ return error_response(400, {error: "invalid_request", error_description: "Missing required parameter: sub"})
120
120
  end
121
121
 
122
122
  # Security: Validate entity identifier per OpenID Federation 1.0 spec
@@ -125,22 +125,22 @@ module OmniauthOpenidFederation
125
125
  # Validate and get trimmed value
126
126
  subject_entity_id = OmniauthOpenidFederation::Validators.validate_entity_identifier!(subject_entity_id)
127
127
  rescue SecurityError => e
128
- return error_response(400, {error: "invalid_request", error_description: "Invalid subject entity ID: #{e.message}"}.to_json)
128
+ return error_response(400, {error: "invalid_request", error_description: "Invalid subject entity ID: #{e.message}"})
129
129
  rescue => e
130
- return error_response(400, {error: "invalid_request", error_description: "Subject entity ID validation failed: #{e.message}"}.to_json)
130
+ return error_response(400, {error: "invalid_request", error_description: "Subject entity ID validation failed: #{e.message}"})
131
131
  end
132
132
 
133
133
  # Validate that subject is not the issuer (invalid request per spec)
134
134
  config = OmniauthOpenidFederation::FederationEndpoint.configuration
135
135
  if subject_entity_id == config.issuer
136
- return error_response(400, {error: "invalid_request", error_description: "Subject cannot be the issuer"}.to_json)
136
+ return error_response(400, {error: "invalid_request", error_description: "Subject cannot be the issuer"})
137
137
  end
138
138
 
139
139
  # Get Subordinate Statement
140
140
  subordinate_statement = OmniauthOpenidFederation::FederationEndpoint.get_subordinate_statement(subject_entity_id)
141
141
 
142
142
  unless subordinate_statement
143
- return error_response(404, {error: "not_found", error_description: "Subordinate Statement not found for subject: #{subject_entity_id}"}.to_json)
143
+ return error_response(404, {error: "not_found", error_description: "Subordinate Statement not found for subject: #{subject_entity_id}"})
144
144
  end
145
145
 
146
146
  headers = {
@@ -188,11 +188,23 @@ module OmniauthOpenidFederation
188
188
  # Return error response
189
189
  #
190
190
  # @param status [Integer] HTTP status code
191
- # @param message [String] Error message
191
+ # @param message [String, Hash] Error message (string) or error hash (will be converted to JSON)
192
192
  # @return [Array] Rack response
193
193
  def error_response(status, message)
194
194
  content_type = (status == 503) ? "text/plain" : "application/json"
195
- body = (status == 503) ? message : {error: message}.to_json
195
+ body = if status == 503
196
+ message
197
+ elsif message.is_a?(Hash)
198
+ message.to_json
199
+ else
200
+ # If message is already JSON string, parse and re-encode to ensure proper format
201
+ begin
202
+ parsed = JSON.parse(message)
203
+ parsed.to_json
204
+ rescue JSON::ParserError
205
+ {error: message}.to_json
206
+ end
207
+ end
196
208
 
197
209
  [status, {"Content-Type" => content_type}, [body]]
198
210
  end
@@ -285,7 +285,19 @@ module OmniauthOpenidFederation
285
285
  end
286
286
  http = Net::HTTP.new(uri.host, uri.port)
287
287
  http.use_ssl = (uri.scheme == "https")
288
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if defined?(Rails) && Rails.respond_to?(:env) && Rails.env.development?
288
+ if uri.scheme == "https"
289
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
290
+
291
+ # Set ca_file directly - this is the simplest and most reliable approach
292
+ # Try SSL_CERT_FILE first, then default cert file
293
+ ca_file = if ENV["SSL_CERT_FILE"] && File.file?(ENV["SSL_CERT_FILE"])
294
+ ENV["SSL_CERT_FILE"]
295
+ elsif File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
296
+ OpenSSL::X509::DEFAULT_CERT_FILE
297
+ end
298
+
299
+ http.ca_file = ca_file if ca_file
300
+ end
289
301
 
290
302
  request_path = uri.path
291
303
  request_path += "?#{uri.query}" if uri.query
@@ -466,6 +478,9 @@ module OmniauthOpenidFederation
466
478
  }
467
479
 
468
480
  # HTTP client helper for custom requests
481
+ build_http_client = lambda do |connect_timeout: 10, read_timeout: 10|
482
+ HTTP.timeout(connect: connect_timeout, read: read_timeout)
483
+ end
469
484
 
470
485
  # Step 1: Fetch login page for CSRF token and cookies
471
486
  results[:steps_completed] << "fetch_csrf_token"
@@ -476,7 +491,8 @@ module OmniauthOpenidFederation
476
491
  cookies = []
477
492
 
478
493
  begin
479
- login_response = build_http_client(connect_timeout: 10, read_timeout: 10).get(login_page_url)
494
+ http_client = build_http_client.call(connect_timeout: 10, read_timeout: 10)
495
+ login_response = http_client.get(login_page_url)
480
496
 
481
497
  unless login_response.status.success?
482
498
  raise "Failed to fetch login page: #{login_response.status.code} #{login_response.status.reason}"
@@ -578,7 +594,8 @@ module OmniauthOpenidFederation
578
594
  common_paths.each do |path|
579
595
  test_url = URI.join(base_url, path).to_s
580
596
  begin
581
- test_response = build_http_client(connect_timeout: 5, read_timeout: 5).get(test_url)
597
+ http_client = build_http_client.call(connect_timeout: 5, read_timeout: 5)
598
+ test_response = http_client.get(test_url)
582
599
  if test_response.status.code >= 300 && test_response.status.code < 400
583
600
  auth_endpoint = test_url
584
601
  break
@@ -613,7 +630,8 @@ module OmniauthOpenidFederation
613
630
  # Include acr_values if provided (must be configured in request_object_params to be included in JWT)
614
631
  form_data[:acr_values] = provider_acr if StringHelpers.present?(provider_acr)
615
632
 
616
- auth_response = build_http_client(connect_timeout: 10, read_timeout: 10)
633
+ http_client = build_http_client.call(connect_timeout: 10, read_timeout: 10)
634
+ auth_response = http_client
617
635
  .headers(headers)
618
636
  .post(auth_endpoint, form: form_data)
619
637
 
@@ -692,7 +710,7 @@ module OmniauthOpenidFederation
692
710
  require "cgi"
693
711
  require "json"
694
712
  require "base64"
695
- require_relative "../strategy"
713
+ require_relative "strategy"
696
714
 
697
715
  results = {
698
716
  steps_completed: [],
@@ -0,0 +1,60 @@
1
+ # Time helper utilities for compatibility with ActiveSupport
2
+ # Provides time methods that work with or without Time.zone
3
+ module OmniauthOpenidFederation
4
+ module TimeHelpers
5
+ # Get current time, using Time.zone if available, otherwise Time
6
+ #
7
+ # @return [Time] Current time
8
+ def self.now
9
+ if time_zone_available?
10
+ Time.zone.now
11
+ else
12
+ # rubocop:disable Rails/TimeZone
13
+ Time.now
14
+ # rubocop:enable Rails/TimeZone
15
+ end
16
+ end
17
+
18
+ # Convert a timestamp to Time, using Time.zone if available, otherwise Time
19
+ #
20
+ # @param timestamp [Integer, Float] Unix timestamp
21
+ # @return [Time] Time object representing the timestamp
22
+ def self.at(timestamp)
23
+ if time_zone_available?
24
+ Time.zone.at(timestamp)
25
+ else
26
+ # rubocop:disable Rails/TimeZone
27
+ Time.at(timestamp)
28
+ # rubocop:enable Rails/TimeZone
29
+ end
30
+ end
31
+
32
+ # Parse a time string, using Time.zone if available, otherwise Time
33
+ #
34
+ # @param time_string [String] Time string to parse
35
+ # @return [Time] Parsed time object
36
+ def self.parse(time_string)
37
+ if time_zone_available?
38
+ Time.zone.parse(time_string)
39
+ else
40
+ # rubocop:disable Rails/TimeZone
41
+ Time.parse(time_string)
42
+ # rubocop:enable Rails/TimeZone
43
+ end
44
+ end
45
+
46
+ # Check if Time.zone is available and configured
47
+ #
48
+ # @return [Boolean] true if Time.zone is available and not nil
49
+ def self.time_zone_available?
50
+ return false unless defined?(ActiveSupport)
51
+ return false unless Time.respond_to?(:zone)
52
+
53
+ begin
54
+ !Time.zone.nil?
55
+ rescue
56
+ false
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,4 +1,6 @@
1
1
  # Utility functions for omniauth_openid_federation
2
+ require_relative "string_helpers"
3
+
2
4
  module OmniauthOpenidFederation
3
5
  module Utils
4
6
  # Convert hash to HashWithIndifferentAccess if available
@@ -18,7 +20,7 @@ module OmniauthOpenidFederation
18
20
  # @param path [String, nil] The file path
19
21
  # @return [String] Sanitized path (filename only)
20
22
  def self.sanitize_path(path)
21
- return "[REDACTED]" if path.nil? || path.empty?
23
+ return "[REDACTED]" if StringHelpers.blank?(path)
22
24
  File.basename(path)
23
25
  end
24
26
 
@@ -27,7 +29,7 @@ module OmniauthOpenidFederation
27
29
  # @param uri [String, nil] The URI
28
30
  # @return [String] Sanitized URI
29
31
  def self.sanitize_uri(uri)
30
- return "[REDACTED]" if uri.nil? || uri.empty?
32
+ return "[REDACTED]" if StringHelpers.blank?(uri)
31
33
  begin
32
34
  parsed = URI.parse(uri)
33
35
  "#{parsed.scheme}://#{parsed.host}/[REDACTED]"
@@ -70,16 +72,13 @@ module OmniauthOpenidFederation
70
72
  def self.validate_file_path!(path, allowed_dirs: nil)
71
73
  raise SecurityError, "File path cannot be nil" if path.nil?
72
74
 
73
- # Convert Pathname to string if needed
74
75
  path_str = path.to_s
75
76
  raise SecurityError, "File path cannot be empty" if path_str.empty?
76
77
 
77
- # Check for path traversal attempts
78
78
  if path_str.include?("..") || path_str.include?("~")
79
79
  raise SecurityError, "Path traversal detected in: #{sanitize_path(path_str)}"
80
80
  end
81
81
 
82
- # Resolve to absolute path
83
82
  resolved = File.expand_path(path_str)
84
83
 
85
84
  # Validate it's within allowed directories if specified
@@ -120,7 +119,6 @@ module OmniauthOpenidFederation
120
119
  n = Base64.urlsafe_encode64(key.n.to_s(2), padding: false)
121
120
  e = Base64.urlsafe_encode64(key.e.to_s(2), padding: false)
122
121
 
123
- # Generate kid (key ID) from public key
124
122
  public_key_pem = key.public_key.to_pem
125
123
  kid = Digest::SHA256.hexdigest(public_key_pem)[0, 16]
126
124
 
@@ -131,7 +129,6 @@ module OmniauthOpenidFederation
131
129
  e: e
132
130
  }
133
131
 
134
- # Add use field if specified
135
132
  jwk[:use] = use if use
136
133
 
137
134
  jwk
@@ -1,6 +1,7 @@
1
1
  # Input validation utilities for omniauth_openid_federation
2
2
  require_relative "constants"
3
3
  require_relative "configuration"
4
+ require_relative "string_helpers"
4
5
 
5
6
  module OmniauthOpenidFederation
6
7
  module Validators
@@ -13,7 +14,6 @@ module OmniauthOpenidFederation
13
14
  raise ConfigurationError, "Private key is required for signed request objects"
14
15
  end
15
16
 
16
- # Try to parse if it's a string
17
17
  if private_key.is_a?(String)
18
18
  begin
19
19
  OpenSSL::PKey::RSA.new(private_key)
@@ -82,10 +82,8 @@ module OmniauthOpenidFederation
82
82
  def self.validate_client_options!(client_options)
83
83
  client_options ||= {}
84
84
 
85
- # Normalize hash keys to symbols
86
85
  normalized = normalize_hash(client_options)
87
86
 
88
- # Validate required fields (structure validation, not security)
89
87
  if StringHelpers.blank?(normalized[:identifier])
90
88
  raise ConfigurationError, "Client identifier is required"
91
89
  end
@@ -94,8 +92,6 @@ module OmniauthOpenidFederation
94
92
  raise ConfigurationError, "Redirect URI is required"
95
93
  end
96
94
 
97
- # Basic format check for redirect URI (config validation, not security)
98
- # Note: Config values are trusted, we only check format to catch config errors
99
95
  begin
100
96
  parsed = URI.parse(normalized[:redirect_uri].to_s)
101
97
  unless parsed.is_a?(URI::HTTP) || parsed.is_a?(URI::HTTPS)
@@ -105,10 +101,8 @@ module OmniauthOpenidFederation
105
101
  raise ConfigurationError, "Invalid redirect URI format: #{e.message}"
106
102
  end
107
103
 
108
- # Validate private key (required for security)
109
104
  validate_private_key!(normalized[:private_key])
110
105
 
111
- # Basic format check for endpoints (config validation, not security)
112
106
  %i[authorization_endpoint token_endpoint jwks_uri].each do |endpoint|
113
107
  if normalized.key?(endpoint) && !StringHelpers.blank?(normalized[endpoint])
114
108
  # Endpoints can be paths or full URLs
@@ -145,7 +139,6 @@ module OmniauthOpenidFederation
145
139
  str = value.to_s.strip
146
140
  return nil if str.length > max_length
147
141
 
148
- # Only allow printable ASCII (whitelist approach)
149
142
  unless allow_control_chars
150
143
  str = str.gsub(/[^\x20-\x7E]/, "")
151
144
  end
@@ -159,15 +152,16 @@ module OmniauthOpenidFederation
159
152
  max_length ||= ::OmniauthOpenidFederation::Configuration.config.max_string_length
160
153
  raise SecurityError, "URI cannot be nil" if uri_str.nil?
161
154
 
162
- str = uri_str.to_s.strip
155
+ original_str = uri_str.to_s
156
+ sanitized = original_str.gsub(/[^\x20-\x7E]/, "")
157
+ raise SecurityError, "URI contains invalid characters (only printable ASCII allowed)" if sanitized != original_str
158
+
159
+ str = sanitized.strip
163
160
  raise SecurityError, "URI cannot be empty" if str.empty?
164
161
  raise SecurityError, "URI exceeds maximum length of #{max_length} characters" if str.length > max_length
165
162
 
166
- sanitized = str.gsub(/[^\x20-\x7E]/, "")
167
- raise SecurityError, "URI contains invalid characters (only printable ASCII allowed)" if sanitized != str
168
-
169
163
  begin
170
- parsed = URI.parse(sanitized)
164
+ parsed = URI.parse(str)
171
165
  rescue URI::InvalidURIError => e
172
166
  raise SecurityError, "Invalid URI format: #{e.message}"
173
167
  end
@@ -180,7 +174,7 @@ module OmniauthOpenidFederation
180
174
  raise SecurityError, "URI must be HTTP or HTTPS"
181
175
  end
182
176
 
183
- raise SecurityError, "URI host cannot be empty" if parsed.host.nil? || parsed.host.empty?
177
+ raise SecurityError, "URI host cannot be empty" if StringHelpers.blank?(parsed.host)
184
178
 
185
179
  parsed
186
180
  end
@@ -199,22 +193,16 @@ module OmniauthOpenidFederation
199
193
 
200
194
  case acr_values
201
195
  when Array
202
- # Filter out blanks (arrays may already be sanitized)
203
196
  values = acr_values.map(&:to_s).map(&:strip).reject { |v| StringHelpers.blank?(v) }
204
197
  when String
205
- # Trim and split by whitespace and validate each value using allowed characters
206
- # Security: Use simple space split (no regexp to avoid ReDoS)
207
198
  trimmed = acr_values.strip
208
199
  values = trimmed.split(" ").map(&:strip).reject { |v| StringHelpers.blank?(v) }
209
200
  else
210
- # Convert to string, trim and split
211
201
  str = acr_values.to_s.strip
212
202
  return nil if str.length > max_length
213
- # Security: Use simple space split (no regexp to avoid ReDoS)
214
203
  values = str.split(" ").map(&:strip).reject { |v| StringHelpers.blank?(v) }
215
204
  end
216
205
 
217
- # Sanitize each value unless already sanitized
218
206
  unless skip_sanitization
219
207
  values = values.map { |v| sanitize_request_param(v) }.compact
220
208
  end
@@ -243,9 +231,8 @@ module OmniauthOpenidFederation
243
231
  raise ConfigurationError, "client_id cannot be empty after trimming"
244
232
  end
245
233
 
246
- # Sanitize using allowed characters (printable ASCII only)
247
234
  sanitized = sanitize_request_param(str)
248
- if sanitized.nil? || sanitized.empty?
235
+ if StringHelpers.blank?(sanitized)
249
236
  raise ConfigurationError, "client_id contains invalid characters"
250
237
  end
251
238
 
@@ -268,7 +255,6 @@ module OmniauthOpenidFederation
268
255
  raise ConfigurationError, "redirect_uri cannot be empty after trimming"
269
256
  end
270
257
 
271
- # Validate as URI (includes allowed characters validation)
272
258
  validated = validate_uri_safe!(str, allowed_schemes: ["http", "https"])
273
259
  validated.to_s
274
260
  end
@@ -289,7 +275,6 @@ module OmniauthOpenidFederation
289
275
  return nil
290
276
  end
291
277
 
292
- # Normalize to array
293
278
  scopes = case scope
294
279
  when Array
295
280
  scope.map(&:to_s).map(&:strip).reject { |s| StringHelpers.blank?(s) }
@@ -299,14 +284,12 @@ module OmniauthOpenidFederation
299
284
  scope.to_s.strip.split(" ").map(&:strip).reject { |s| StringHelpers.blank?(s) }
300
285
  end
301
286
 
302
- # Validate each scope value (allowed: printable ASCII)
303
287
  scopes = scopes.map { |s| sanitize_request_param(s) }.compact
304
288
 
305
289
  if scopes.empty?
306
290
  raise ConfigurationError, "scope cannot be empty after validation"
307
291
  end
308
292
 
309
- # Check for "openid" scope if required
310
293
  if require_openid && !scopes.include?("openid")
311
294
  raise ConfigurationError, "scope MUST include 'openid' per OIDC Core 1.0 spec"
312
295
  end
@@ -336,9 +319,8 @@ module OmniauthOpenidFederation
336
319
  raise ConfigurationError, "state cannot be empty after trimming"
337
320
  end
338
321
 
339
- # Sanitize using allowed characters (printable ASCII only)
340
322
  sanitized = sanitize_request_param(str)
341
- if sanitized.nil? || sanitized.empty?
323
+ if StringHelpers.blank?(sanitized)
342
324
  raise ConfigurationError, "state contains invalid characters"
343
325
  end
344
326
 
@@ -363,10 +345,8 @@ module OmniauthOpenidFederation
363
345
  return nil
364
346
  end
365
347
 
366
- # Sanitize using allowed characters (printable ASCII only)
367
- # OIDC Core 1.0: nonce value is a case-sensitive string
368
348
  sanitized = sanitize_request_param(str)
369
- if sanitized.nil? || sanitized.empty?
349
+ if StringHelpers.blank?(sanitized)
370
350
  if required
371
351
  raise ConfigurationError, "nonce contains invalid characters"
372
352
  end
@@ -392,13 +372,11 @@ module OmniauthOpenidFederation
392
372
  raise ConfigurationError, "response_type cannot be empty after trimming"
393
373
  end
394
374
 
395
- # Sanitize using allowed characters (printable ASCII only)
396
375
  sanitized = sanitize_request_param(str)
397
- if sanitized.nil? || sanitized.empty?
376
+ if StringHelpers.blank?(sanitized)
398
377
  raise ConfigurationError, "response_type contains invalid characters"
399
378
  end
400
379
 
401
- # Validate it's a known response type (space-separated list)
402
380
  valid_types = ["code", "id_token", "token", "id_token token", "code id_token", "code token", "code id_token token"]
403
381
  types = sanitized.split(" ").map(&:strip)
404
382
  unless types.all? { |t| valid_types.include?(t) || t.match?(/^[a-z_]+$/) }
@@ -426,10 +404,8 @@ module OmniauthOpenidFederation
426
404
  raise SecurityError, "Entity identifier cannot be empty after trimming"
427
405
  end
428
406
 
429
- # Validate as URI (includes allowed characters and length validation)
430
407
  validate_uri_safe!(str, max_length: max_length, allowed_schemes: ["http", "https"])
431
408
 
432
- # Return trimmed and validated value
433
409
  str
434
410
  end
435
411
  end
@@ -1,3 +1,3 @@
1
1
  module OmniauthOpenidFederation
2
- VERSION = "1.3.0".freeze
2
+ VERSION = "1.3.2".freeze
3
3
  end
@@ -28,6 +28,7 @@ end
28
28
 
29
29
  require_relative "omniauth_openid_federation/version"
30
30
  require_relative "omniauth_openid_federation/string_helpers"
31
+ require_relative "omniauth_openid_federation/time_helpers"
31
32
  require_relative "omniauth_openid_federation/logger"
32
33
  require_relative "omniauth_openid_federation/configuration"
33
34
  require_relative "omniauth_openid_federation/errors"
@@ -1,5 +1,6 @@
1
1
  # Rake tasks for OmniAuth OpenID Federation
2
2
  # Thin wrappers around TasksHelper for CLI interface
3
+ require_relative "../omniauth_openid_federation/time_helpers"
3
4
 
4
5
  namespace :openid_federation do
5
6
  desc "Fetch entity statement from OpenID Federation provider"
@@ -262,8 +263,8 @@ namespace :openid_federation do
262
263
  end
263
264
  puts " Issuer: #{metadata[:issuer]}"
264
265
  puts " Subject: #{metadata[:sub]}"
265
- puts " Expires: #{Time.at(metadata[:exp])}" if metadata[:exp]
266
- puts " Issued At: #{Time.at(metadata[:iat])}" if metadata[:iat]
266
+ puts " Expires: #{OmniauthOpenidFederation::TimeHelpers.at(metadata[:exp])}" if metadata[:exp]
267
+ puts " Issued At: #{OmniauthOpenidFederation::TimeHelpers.at(metadata[:iat])}" if metadata[:iat]
267
268
  puts
268
269
 
269
270
  # Key status information
@@ -594,7 +595,7 @@ namespace :openid_federation do
594
595
  if value
595
596
  if [:exp, :iat, :auth_time].include?(key)
596
597
  time_value = begin
597
- Time.at(value)
598
+ OmniauthOpenidFederation::TimeHelpers.at(value)
598
599
  rescue
599
600
  value
600
601
  end
@@ -183,6 +183,12 @@ module OmniauthOpenidFederation
183
183
  def self.blank?: (untyped value) -> bool
184
184
  end
185
185
 
186
+ module TimeHelpers
187
+ def self.now: () -> Time
188
+ def self.at: (Integer | Float timestamp) -> Time
189
+ def self.parse: (String time_string) -> Time
190
+ end
191
+
186
192
  module Validators
187
193
  def self.validate_private_key!: (untyped private_key) -> void
188
194
  def self.normalize_hash: (untyped hash) -> Hash[Symbol, untyped]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth_openid_federation
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -197,6 +197,104 @@ dependencies:
197
197
  - - "~>"
198
198
  - !ruby/object:Gem::Version
199
199
  version: '1.52'
200
+ - !ruby/object:Gem::Dependency
201
+ name: standard-custom
202
+ requirement: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - "~>"
205
+ - !ruby/object:Gem::Version
206
+ version: '1.0'
207
+ type: :development
208
+ prerelease: false
209
+ version_requirements: !ruby/object:Gem::Requirement
210
+ requirements:
211
+ - - "~>"
212
+ - !ruby/object:Gem::Version
213
+ version: '1.0'
214
+ - !ruby/object:Gem::Dependency
215
+ name: standard-performance
216
+ requirement: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - "~>"
219
+ - !ruby/object:Gem::Version
220
+ version: '1.8'
221
+ type: :development
222
+ prerelease: false
223
+ version_requirements: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - "~>"
226
+ - !ruby/object:Gem::Version
227
+ version: '1.8'
228
+ - !ruby/object:Gem::Dependency
229
+ name: standard-rails
230
+ requirement: !ruby/object:Gem::Requirement
231
+ requirements:
232
+ - - "~>"
233
+ - !ruby/object:Gem::Version
234
+ version: '1.5'
235
+ type: :development
236
+ prerelease: false
237
+ version_requirements: !ruby/object:Gem::Requirement
238
+ requirements:
239
+ - - "~>"
240
+ - !ruby/object:Gem::Version
241
+ version: '1.5'
242
+ - !ruby/object:Gem::Dependency
243
+ name: standard-rspec
244
+ requirement: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - "~>"
247
+ - !ruby/object:Gem::Version
248
+ version: '0.3'
249
+ type: :development
250
+ prerelease: false
251
+ version_requirements: !ruby/object:Gem::Requirement
252
+ requirements:
253
+ - - "~>"
254
+ - !ruby/object:Gem::Version
255
+ version: '0.3'
256
+ - !ruby/object:Gem::Dependency
257
+ name: rubocop-rails
258
+ requirement: !ruby/object:Gem::Requirement
259
+ requirements:
260
+ - - "~>"
261
+ - !ruby/object:Gem::Version
262
+ version: '2.33'
263
+ type: :development
264
+ prerelease: false
265
+ version_requirements: !ruby/object:Gem::Requirement
266
+ requirements:
267
+ - - "~>"
268
+ - !ruby/object:Gem::Version
269
+ version: '2.33'
270
+ - !ruby/object:Gem::Dependency
271
+ name: rubocop-rspec
272
+ requirement: !ruby/object:Gem::Requirement
273
+ requirements:
274
+ - - "~>"
275
+ - !ruby/object:Gem::Version
276
+ version: '3.8'
277
+ type: :development
278
+ prerelease: false
279
+ version_requirements: !ruby/object:Gem::Requirement
280
+ requirements:
281
+ - - "~>"
282
+ - !ruby/object:Gem::Version
283
+ version: '3.8'
284
+ - !ruby/object:Gem::Dependency
285
+ name: rubocop-thread_safety
286
+ requirement: !ruby/object:Gem::Requirement
287
+ requirements:
288
+ - - "~>"
289
+ - !ruby/object:Gem::Version
290
+ version: '0.7'
291
+ type: :development
292
+ prerelease: false
293
+ version_requirements: !ruby/object:Gem::Requirement
294
+ requirements:
295
+ - - "~>"
296
+ - !ruby/object:Gem::Version
297
+ version: '0.7'
200
298
  - !ruby/object:Gem::Dependency
201
299
  name: appraisal
202
300
  requirement: !ruby/object:Gem::Requirement
@@ -323,6 +421,7 @@ files:
323
421
  - lib/omniauth_openid_federation/strategy.rb
324
422
  - lib/omniauth_openid_federation/string_helpers.rb
325
423
  - lib/omniauth_openid_federation/tasks_helper.rb
424
+ - lib/omniauth_openid_federation/time_helpers.rb
326
425
  - lib/omniauth_openid_federation/utils.rb
327
426
  - lib/omniauth_openid_federation/validators.rb
328
427
  - lib/omniauth_openid_federation/version.rb