-
-
Notifications
You must be signed in to change notification settings - Fork 929
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Guard against OIDC Provider configuration being stale #5296
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,51 @@ | ||||||||||
module JwtValidation | ||||||||||
extend ActiveSupport::Concern | ||||||||||
|
||||||||||
class UnsupportedIssuer < StandardError; end | ||||||||||
class UnverifiedJWT < StandardError; end | ||||||||||
class InvalidJWT < StandardError; end | ||||||||||
|
||||||||||
included do | ||||||||||
rescue_from InvalidJWT, with: :render_bad_request | ||||||||||
|
||||||||||
rescue_from( | ||||||||||
UnsupportedIssuer, UnverifiedJWT, | ||||||||||
JSON::JWT::VerificationFailed, JSON::JWK::Set::KidNotFound, | ||||||||||
OIDC::AccessPolicy::AccessError, | ||||||||||
with: :render_not_found | ||||||||||
) | ||||||||||
end | ||||||||||
|
||||||||||
def jwt_key_or_secret | ||||||||||
raise NotImplementedError | ||||||||||
end | ||||||||||
|
||||||||||
def decode_jwt | ||||||||||
@jwt = JSON::JWT.decode_compact_serialized(params.permit(:jwt).require(:jwt), jwt_key_or_secret) | ||||||||||
rescue JSON::JWT::InvalidFormat, JSON::ParserError, ArgumentError => e | ||||||||||
# invalid base64 raises ArgumentError | ||||||||||
render_bad_request(e) | ||||||||||
end | ||||||||||
|
||||||||||
def validate_jwt_format | ||||||||||
%w[nbf iat exp].each do |claim| | ||||||||||
raise InvalidJWT, "Missing/invalid #{claim}" unless @jwt[claim].is_a?(Integer) | ||||||||||
end | ||||||||||
%w[iss jti].each do |claim| | ||||||||||
raise InvalidJWT, "Missing/invalid #{claim}" unless @jwt[claim].is_a?(String) | ||||||||||
end | ||||||||||
Comment on lines
+31
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: could extract to constant |
||||||||||
end | ||||||||||
|
||||||||||
def validate_provider | ||||||||||
raise UnsupportedIssuer, "Provider is missing jwks" if @provider.jwks.blank? | ||||||||||
# TODO: delete &. after all providers have updated their configuration | ||||||||||
return unless @provider.configuration_updated_at&.before?(1.day.ago) | ||||||||||
Comment on lines
+41
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something about this is very hard to reason about for me. I think it almost reads like a double negative.
Suggested change
|
||||||||||
raise UnsupportedIssuer, "Configuration last updated too long ago: #{@provider.configuration_updated_at}" | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When this error happens there's nothing the user can do about it, no? Should we indicate this with a 503, in effect saying "it's not you it's me". |
||||||||||
end | ||||||||||
|
||||||||||
def verify_jwt_time | ||||||||||
now = Time.now.to_i | ||||||||||
return if @jwt["nbf"] <= now && now < @jwt["exp"] | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤷♂️
Suggested change
It creates a range so maybe slightly less efficient, your call. |
||||||||||
raise UnverifiedJWT, "Invalid time" | ||||||||||
end | ||||||||||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
class AddConfigurationUpdatedAtToOIDCProviders < ActiveRecord::Migration[7.1] | ||
def change | ||
add_column :oidc_providers, :configuration_updated_at, :timestamp | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if you set a default here of epoch just so we don't have to guard for nil? I'm wondering if new providers will ever be updated immediately anyway or will there be a time gap while they crash the check above? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. they'll all be updated within 30 minutes |
||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is
:skip_verification
handled by the JWT lib?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes