-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
247 additions
and
0 deletions.
There are no files selected for viewing
25 changes: 25 additions & 0 deletions
25
app/domain/authentication/authn_jwt/signing_key/fetch_public_keys_signing_key.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
module Authentication | ||
module AuthnJwt | ||
module SigningKey | ||
# This class is responsible for parsing JWK set from public-keys configuration value | ||
class FetchPublicKeysSigningKey | ||
|
||
def initialize( | ||
public_keys:, | ||
logger: Rails.logger | ||
) | ||
@logger = logger | ||
@public_keys = public_keys | ||
end | ||
|
||
def call(*) | ||
@logger.info(LogMessages::Authentication::AuthnJwt::ParsingStaticSigningKeys) | ||
signing_keys = Authentication::AuthnJwt::SigningKey::PublicSigningKeys.new(JSON.parse(@public_keys)) | ||
signing_keys.validate! | ||
@logger.debug(LogMessages::Authentication::AuthnJwt::ParsedStaticSigningKeys) | ||
{ keys: JSON::JWK::Set.new(signing_keys.value) } | ||
end | ||
end | ||
end | ||
end | ||
end |
43 changes: 43 additions & 0 deletions
43
app/domain/authentication/authn_jwt/signing_key/public_signing_keys.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
module Authentication | ||
module AuthnJwt | ||
module SigningKey | ||
# This class is a POJO class presents public-keys structure | ||
class PublicSigningKeys | ||
include ActiveModel::Validations | ||
include AttrRequired | ||
|
||
VALID_TYPES = %w[jwks].freeze | ||
|
||
attr_required(:type, :value) | ||
|
||
validates(*required_attributes, presence: true) | ||
validates(:type, inclusion: { in: VALID_TYPES, message: "'%{value}' is not a valid public-keys type" }) | ||
validate(:validate_value_is_jwks, if: -> { @type == "jwks" }) | ||
|
||
def initialize(hash) | ||
raise Errors::Authentication::AuthnJwt::InvalidPublicKeys.new("the value is not in valid JSON format") unless | ||
hash.is_a?(Hash) | ||
|
||
hash = hash.with_indifferent_access | ||
required_attributes.each do |key| | ||
send("#{key}=", hash[key]) | ||
end | ||
end | ||
|
||
def validate! | ||
raise Errors::Authentication::AuthnJwt::InvalidPublicKeys.new(errors.full_messages.to_sentence) unless valid? | ||
end | ||
|
||
private | ||
|
||
def validate_value_is_jwks | ||
errors.add(:value, "is not a valid JWKS (RFC7517)") unless | ||
@value.is_a?(Hash) && | ||
@value.key?(:keys) && | ||
@value[:keys].is_a?(Array) && | ||
!@value[:keys].empty? | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
spec/app/domain/authentication/authn-jwt/signing_key/fetch_public_keys_signing_key_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'spec_helper' | ||
|
||
RSpec.describe('Authentication::AuthnJwt::SigningKey::FetchPublicKeysSigningKey') do | ||
|
||
let(:string_value) { "string value" } | ||
let(:valid_jwks) { | ||
Net::HTTP.get_response(URI("https://www.googleapis.com/oauth2/v3/certs")).body | ||
} | ||
let(:invalid_public_keys_value) { | ||
"{\"type\":\"invalid\", \"value\": #{valid_jwks} }" | ||
} | ||
let(:valid_public_keys_value) { | ||
"{\"type\":\"jwks\", \"value\": #{valid_jwks} }" | ||
} | ||
|
||
# ____ _ _ ____ ____ ____ ___ ____ ___ | ||
# (_ _)( )_( )( ___) (_ _)( ___)/ __)(_ _)/ __) | ||
# )( ) _ ( )__) )( )__) \__ \ )( \__ \ | ||
# (__) (_) (_)(____) (__) (____)(___/ (__) (___/ | ||
|
||
context "FetchPublicKeysSigningKey call" do | ||
context "fails when the value is not a JSON" do | ||
subject do | ||
::Authentication::AuthnJwt::SigningKey::FetchPublicKeysSigningKey.new( | ||
public_keys: string_value | ||
).call(force_fetch: false) | ||
end | ||
|
||
it "raises error" do | ||
expect { subject } | ||
.to raise_error(JSON::ParserError) | ||
end | ||
end | ||
|
||
context "fails when the value is not valid" do | ||
subject do | ||
::Authentication::AuthnJwt::SigningKey::FetchPublicKeysSigningKey.new( | ||
public_keys: invalid_public_keys_value | ||
).call(force_fetch: false) | ||
end | ||
|
||
it "raises error" do | ||
expect { subject } | ||
.to raise_error(Errors::Authentication::AuthnJwt::InvalidPublicKeys) | ||
end | ||
end | ||
|
||
context "returns a JWKS object" do | ||
subject do | ||
::Authentication::AuthnJwt::SigningKey::FetchPublicKeysSigningKey.new( | ||
public_keys: valid_public_keys_value | ||
).call(force_fetch: false) | ||
end | ||
|
||
it "JWKS object has one key" do | ||
expect(subject.length).to eql(1) | ||
end | ||
|
||
it "JWKS object key is keys" do | ||
expect(subject.key?(:keys)).to be true | ||
end | ||
|
||
it "JWKS object value be a JWK Set" do | ||
expect(subject[:keys]).to be_a(JSON::JWK::Set) | ||
end | ||
end | ||
end | ||
end |
93 changes: 93 additions & 0 deletions
93
spec/app/domain/authentication/authn-jwt/signing_key/public_signing_keys_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'spec_helper' | ||
|
||
RSpec.describe('Authentication::AuthnJwt::SigningKey::PublicSigningKeys') do | ||
|
||
invalid_cases = { | ||
"When public-keys value is a string": | ||
["blah", | ||
"the value is not in valid JSON format"], | ||
"When public-keys value is an array": | ||
[%w[a b], | ||
"the value is not in valid JSON format"], | ||
"When public-keys value is an empty object": | ||
[{}, | ||
"Type can't be blank, Type '' is not a valid public-keys type, and Value can't be blank"], | ||
"When public-keys does not contain needed fields": | ||
[{:key => "value", :key2 => { :key3 => "valve" }}, | ||
"Type can't be blank, Type '' is not a valid public-keys type, and Value can't be blank"], | ||
"When public-keys type is empty and value is absent": | ||
[{:type => ""}, | ||
"Type can't be blank, Type '' is not a valid public-keys type, and Value can't be blank"], | ||
"When public-keys type has wrong value and value is absent": | ||
[{:type => "yes"}, | ||
"Value can't be blank and Type 'yes' is not a valid public-keys type"], | ||
"When public-keys type is valid and value is a string": | ||
[{:type => "jwks", :value => "string"}, | ||
"Value is not a valid JWKS (RFC7517)"], | ||
"When public-keys type is valid and value is an empty object": | ||
[{:type => "jwks", :value => { } }, | ||
"Value can't be blank and Value is not a valid JWKS (RFC7517)"], | ||
"When public-keys type is valid and value is an object with some key": | ||
[{:type => "jwks", :value => { :some_key => "some_value" } }, | ||
"Value is not a valid JWKS (RFC7517)"], | ||
"When public-keys type is valid and value is an object with `keys` key and string keys value": | ||
[{:type => "jwks", :value => { :keys => "some_value" } }, | ||
"Value is not a valid JWKS (RFC7517)"], | ||
"When public-keys type is valid and value is an object with `keys` key and empty array keys value": | ||
[{:type => "jwks", :value => { :keys => [ ] } }, | ||
"Value is not a valid JWKS (RFC7517)"], | ||
"When public-keys type is invalid and value is an object with `keys` key and none empty array keys value": | ||
[{:type => "invalid", :value => { :keys => [ "some_value" ] } }, | ||
"Type 'invalid' is not a valid public-keys type"] | ||
} | ||
|
||
let(:valid_jwks) { | ||
{:type => "jwks", :value => { :keys => [ "some_value" ] } } | ||
} | ||
|
||
context "Public-keys value validation" do | ||
context "Invalid examples" do | ||
invalid_cases.each do |description, (hash, expected_error_message) | | ||
context "#{description}" do | ||
subject do | ||
Authentication::AuthnJwt::SigningKey::PublicSigningKeys.new(hash) | ||
end | ||
|
||
it "raises an error" do | ||
|
||
expect { subject.validate! } | ||
.to raise_error( | ||
Errors::Authentication::AuthnJwt::InvalidPublicKeys, | ||
"CONJ00120E Failed to parse 'public-keys': #{expected_error_message}") | ||
end | ||
end | ||
end | ||
end | ||
|
||
context "Public-keys value validation" do | ||
context "Valid examples" do | ||
context "When public-keys type is jwks and value meets minimal jwks requirements" do | ||
subject do | ||
Authentication::AuthnJwt::SigningKey::PublicSigningKeys.new(valid_jwks) | ||
end | ||
|
||
it "validates! does not raise error" do | ||
expect { subject.validate! } | ||
.not_to raise_error | ||
end | ||
|
||
it "type is jwks" do | ||
expect(subject.type).to eql("jwks") | ||
end | ||
|
||
it "can create JWKS from value" do | ||
expect { JSON::JWK::Set.new(subject.value) } | ||
.not_to raise_error | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |