Comparing changes
v1.0.23
→
v1.0.24
12 commits
10 files changed
Commits
Changed files (10)
lib
saml
kit
builders
concerns
locales
spec
saml
kit
lib/saml/kit/builders/assertion.rb
@@ -7,17 +7,21 @@ module Saml
# {include:file:lib/saml/kit/builders/templates/assertion.builder}
class Assertion
include XmlTemplatable
- extend Forwardable
-
- def_delegators :@response_builder,
- :request, :issuer, :reference_id, :now, :configuration, :user,
- :version, :destination
+ attr_reader :user, :request, :configuration
+ attr_accessor :reference_id
+ attr_accessor :now, :destination
+ attr_accessor :issuer, :version
attr_accessor :default_name_id_format
- def initialize(response_builder, embed_signature)
- @response_builder = response_builder
- self.embed_signature = embed_signature
+ def initialize(user, request, configuration: Saml::Kit.configuration)
+ @user = user
+ @request = request
+ @configuration = configuration
+ @issuer = configuration.entity_id
+ @reference_id = ::Xml::Kit::Id.generate
+ @version = '2.0'
+ @now = Time.now.utc
self.default_name_id_format = Saml::Kit::Namespaces::UNSPECIFIED_NAMEID
end
@@ -34,8 +38,8 @@ module Saml
user.assertion_attributes_for(request)
end
- def signing_key_pair
- super || @response_builder.signing_key_pair
+ def build
+ Saml::Kit::Assertion.new(to_xml, configuration: configuration)
end
private
lib/saml/kit/builders/response.rb
@@ -8,7 +8,7 @@ module Saml
class Response
include XmlTemplatable
attr_reader :user, :request
- attr_accessor :id, :reference_id, :now
+ attr_accessor :id, :now
attr_accessor :version, :status_code, :status_message
attr_accessor :issuer, :destination
attr_reader :configuration
@@ -19,7 +19,6 @@ module Saml
@user = user
@request = request
@id = ::Xml::Kit::Id.generate
- @reference_id = ::Xml::Kit::Id.generate
@now = Time.now.utc
@version = '2.0'
@status_code = Namespaces::SUCCESS
@@ -46,14 +45,13 @@ module Saml
def assertion
@assertion ||=
begin
- assertion = Saml::Kit::Builders::Assertion.new(
- self, embed_signature
- )
- if encrypt
- Saml::Kit::Builders::EncryptedAssertion.new(self, assertion)
- else
- assertion
- end
+ assertion = Assertion.new(user, request, configuration: configuration)
+ assertion.sign_with(@signing_key_pair) if @signing_key_pair
+ assertion.embed_signature = embed_signature unless embed_signature.nil?
+ assertion.now = now
+ assertion.destination = destination
+ assertion.issuer = issuer
+ encrypt ? EncryptedAssertion.new(self, assertion) : assertion
end
end
lib/saml/kit/concerns/xsd_validatable.rb
@@ -19,7 +19,7 @@ module Saml
Dir.chdir(File.dirname(xsd)) do
xsd = Nokogiri::XML::Schema(IO.read(xsd))
- xsd.validate(to_nokogiri).each do |error|
+ xsd.validate(to_nokogiri.document).each do |error|
errors[:base] << error.message
end
end
lib/saml/kit/locales/en.yml
@@ -5,8 +5,12 @@ en:
Assertion:
cannot_decrypt: "cannot be decrypted."
expired: "must not be expired."
- must_match_issuer: "must match entityId."
+ invalid: "must contain Assertion."
+ invalid_fingerprint: "is not registered."
+ invalid_version: "must be 2.0."
must_contain_single_assertion: "must contain single Assertion."
+ must_match_issuer: "must match entityId."
+ unregistered: "is unregistered."
AuthnRequest:
invalid: "must contain AuthnRequest."
invalid_fingerprint: "is not registered."
lib/saml/kit/assertion.rb
@@ -5,10 +5,7 @@ module Saml
# This class validates the Assertion
# element nested in a Response element
# of a SAML document.
- class Assertion
- include ActiveModel::Validations
- include Translatable
- include XmlParseable
+ class Assertion < Document
extend Forwardable
XPATH = [
'/samlp:Response/saml:Assertion',
@@ -21,26 +18,35 @@ module Saml
validate :must_match_issuer, if: :decryptable?
validate :must_be_active_session, if: :decryptable?
validate :must_have_valid_signature, if: :decryptable?
- attr_reader :name
+ attr_reader :name, :configuration
attr_accessor :occurred_at
def initialize(
node, configuration: Saml::Kit.configuration, private_keys: []
)
@name = 'Assertion'
- @to_nokogiri = node
+ @to_nokogiri = node.is_a?(String) ? Nokogiri::XML(node).root : node
@configuration = configuration
@occurred_at = Time.current
@cannot_decrypt = false
@encrypted = false
keys = configuration.private_keys(use: :encryption) + private_keys
decrypt(::Xml::Kit::Decryption.new(private_keys: keys.uniq))
+ super(to_s, name: 'Assertion', configuration: configuration)
+ end
+
+ def id
+ at_xpath('./@ID').try(:value)
end
def issuer
at_xpath('./saml:Issuer').try(:text)
end
+ def version
+ at_xpath('./@Version').try(:value)
+ end
+
def name_id
at_xpath('./saml:Subject/saml:NameID').try(:text)
end
@@ -66,9 +72,12 @@ module Saml
now > drifted_started_at && !expired?(now)
end
- def attribute_statement
- @attribute_statement ||=
- AttributeStatement.new(search('./saml:AttributeStatement'))
+ def expected_type?
+ at_xpath('../saml:Assertion|../saml:EncryptedAssertion').present?
+ end
+
+ def attribute_statement(xpath = './saml:AttributeStatement')
+ @attribute_statement ||= AttributeStatement.new(search(xpath))
end
def conditions
@@ -90,8 +99,6 @@ module Saml
private
- attr_reader :configuration
-
def decrypt(decryptor)
encrypted_assertion = at_xpath('./xmlenc:EncryptedData')
@encrypted = encrypted_assertion.present?
lib/saml/kit/document.rb
@@ -83,10 +83,11 @@ module Saml
# @!visibility private
def builder_class # :nodoc:
{
- Response.to_s => Saml::Kit::Builders::Response,
- LogoutResponse.to_s => Saml::Kit::Builders::LogoutResponse,
+ Assertion.to_s => Saml::Kit::Builders::Assertion,
AuthenticationRequest.to_s => Saml::Kit::Builders::AuthenticationRequest,
LogoutRequest.to_s => Saml::Kit::Builders::LogoutRequest,
+ LogoutResponse.to_s => Saml::Kit::Builders::LogoutResponse,
+ Response.to_s => Saml::Kit::Builders::Response,
}[name] || (raise ArgumentError, "Unknown SAML Document #{name}")
end
end
lib/saml/kit/version.rb
@@ -2,6 +2,6 @@
module Saml
module Kit
- VERSION = '1.0.23'.freeze
+ VERSION = '1.0.24'.freeze
end
end
spec/saml/kit/builders/assertion_builder_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+RSpec.describe Saml::Kit::Builders::Assertion do
+ describe '#build' do
+ subject { described_class.new(user, authn_request, configuration: configuration) }
+
+ let(:email) { FFaker::Internet.email }
+ let(:assertion_consumer_service_url) { FFaker::Internet.uri('https') }
+ let(:user) { User.new(attributes: { email: email, created_at: Time.now.utc.iso8601 }) }
+ let(:authn_request) { instance_double(Saml::Kit::AuthenticationRequest, id: Xml::Kit::Id.generate, assertion_consumer_service_url: assertion_consumer_service_url, issuer: issuer, name_id_format: Saml::Kit::Namespaces::EMAIL_ADDRESS, provider: provider, trusted?: true, signed?: true) }
+ let(:provider) { instance_double(Saml::Kit::ServiceProviderMetadata, want_assertions_signed: false, encryption_certificates: [configuration.certificates(use: :encryption).last]) }
+ let(:issuer) { FFaker::Internet.uri('https') }
+ let(:registry) { instance_double(Saml::Kit::DefaultRegistry) }
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.entity_id = issuer
+ config.registry = registry
+ config.generate_key_pair_for(use: :signing)
+ config.generate_key_pair_for(use: :encryption)
+ end
+ end
+ let(:metadata) do
+ Saml::Kit::Metadata.build(configuration: configuration, &:build_identity_provider)
+ end
+
+ before { allow(registry).to receive(:metadata_for).and_return(metadata) }
+
+ specify { expect(subject.build).to be_valid }
+ specify { expect(subject.build.issuer).to eql(issuer) }
+ specify { expect(subject.build.name_id).to eql(user.name_id) }
+ specify { expect(subject.build.name_id_format).to eql(Saml::Kit::Namespaces::EMAIL_ADDRESS) }
+ specify { expect(subject.build).to be_signed }
+ specify { expect(subject.build).not_to be_expired }
+ specify { expect(subject.build).to be_active }
+ specify { expect(subject.build).not_to be_encrypted }
+ specify { expect(subject.build.conditions.audiences).to include(issuer) }
+ specify { expect(subject.build.attributes).to eql('email' => user.attributes[:email], 'created_at' => user.attributes[:created_at]) }
+ end
+end
spec/saml/kit/assertion_spec.rb
@@ -2,9 +2,9 @@
RSpec.describe Saml::Kit::Assertion do
subject do
- Saml::Kit::Response.build(user, request) do |x|
+ described_class.build(user, request) do |x|
x.issuer = entity_id
- end.assertion
+ end
end
let(:request) { instance_double(Saml::Kit::AuthenticationRequest, id: ::Xml::Kit::Id.generate, issuer: FFaker::Internet.uri('https'), assertion_consumer_service_url: FFaker::Internet.uri('https'), name_id_format: Saml::Kit::Namespaces::PERSISTENT, provider: nil, signed?: true, trusted?: true) }
@@ -240,4 +240,27 @@ RSpec.describe Saml::Kit::Assertion do
expect(response.assertion).to be_valid
end
end
+
+ describe '.new' do
+ let(:user) { instance_double(User, name_id_for: SecureRandom.uuid, assertion_attributes_for: {}) }
+ let(:saml_request) { instance_double(Saml::Kit::AuthenticationRequest, id: Xml::Kit::Id.generate, issuer: configuration.entity_id) }
+ let(:registry) { instance_double(Saml::Kit::DefaultRegistry) }
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |x|
+ x.entity_id = FFaker::Internet.uri('https')
+ x.registry = registry
+ x.generate_key_pair_for(use: :signing)
+ end
+ end
+ let(:metadata) do
+ Saml::Kit::Metadata.build(configuration: configuration, &:build_identity_provider)
+ end
+
+ before { allow(registry).to receive(:metadata_for).with(configuration.entity_id).and_return(metadata) }
+
+ it 'parses a raw xml assertion' do
+ saml = described_class.build_xml(user, saml_request, configuration: configuration)
+ expect(described_class.new(saml, configuration: configuration)).to be_valid
+ end
+ end
end
spec/saml/kit/response_spec.rb
@@ -595,4 +595,24 @@ RSpec.describe Saml::Kit::Response do
expect(result.attributes).to be_empty
end
end
+
+ describe '.new' do
+ let(:registry) { instance_double(Saml::Kit::DefaultRegistry) }
+ let(:metadata) { instance_double(Saml::Kit::IdentityProviderMetadata) }
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.entity_id = request.issuer
+ config.registry = registry
+ config.generate_key_pair_for(use: :signing)
+ end
+ end
+
+ it 'parses a raw response' do
+ allow(registry).to receive(:metadata_for).and_return(metadata)
+ allow(metadata).to receive(:matches?).and_return(true)
+
+ saml = described_class.build(user, request, configuration: configuration)
+ expect(described_class.new(saml.to_xml, configuration: configuration)).to be_valid
+ end
+ end
end