Comparing changes
v1.0.11
→
v1.0.12
20 commits
36 files changed
Commits
Changed files (36)
bin
lib
saml
kit
builders
spec
bin/lint
@@ -8,4 +8,4 @@ echo [$(date "+%H:%M:%S")] "==> Running setup…"
bin/setup
echo [$(date "+%H:%M:%S")] "==> Running linters…"
-bundle exec rake rubocop
+bundle exec rake lint
lib/saml/kit/bindings/binding.rb
@@ -3,7 +3,7 @@
module Saml
module Kit
module Bindings
- # {include:file:spec/saml/bindings/binding_spec.rb}
+ # {include:file:spec/saml/kit/bindings/binding_spec.rb}
class Binding
attr_reader :binding, :location
lib/saml/kit/bindings/http_post.rb
@@ -3,7 +3,7 @@
module Saml
module Kit
module Bindings
- # {include:file:spec/saml/bindings/http_post_spec.rb}
+ # {include:file:spec/saml/kit/bindings/http_post_spec.rb}
class HttpPost < Binding
include Serializable
lib/saml/kit/bindings/http_redirect.rb
@@ -3,7 +3,7 @@
module Saml
module Kit
module Bindings
- # {include:file:spec/saml/bindings/http_redirect_spec.rb}
+ # {include:file:spec/saml/kit/bindings/http_redirect_spec.rb}
class HttpRedirect < Binding
include Serializable
lib/saml/kit/bindings/url_builder.rb
@@ -3,7 +3,7 @@
module Saml
module Kit
module Bindings
- # {include:file:spec/saml/bindings/url_builder_spec.rb}
+ # {include:file:spec/saml/kit/bindings/url_builder_spec.rb}
class UrlBuilder
include Serializable
attr_reader :configuration
lib/saml/kit/builders/templates/assertion.builder
@@ -10,8 +10,10 @@ xml.Assertion(assertion_options) do
end
end
xml.Conditions conditions_options do
- xml.AudienceRestriction do
- xml.Audience request.issuer
+ if request.present?
+ xml.AudienceRestriction do
+ xml.Audience request.issuer
+ end
end
end
xml.AuthnStatement authn_statement_options do
lib/saml/kit/builders/templates/authentication_request.builder
@@ -4,5 +4,5 @@ xml.instruct!
xml.tag!('samlp:AuthnRequest', request_options) do
xml.tag!('saml:Issuer', issuer)
signature_for(reference_id: id, xml: xml)
- xml.tag!('samlp:NameIDPolicy', Format: name_id_format)
+ xml.tag!('samlp:NameIDPolicy', Format: name_id_format) if name_id_format.present?
end
lib/saml/kit/builders/templates/identity_provider_metadata.builder
@@ -1,10 +1,7 @@
# frozen_string_literal: true
xml.IDPSSODescriptor descriptor_options do
- configuration.certificates(use: :signing).each do |certificate|
- render certificate, xml: xml
- end
- configuration.certificates(use: :encryption).each do |certificate|
+ configuration.certificates.each do |certificate|
render certificate, xml: xml
end
logout_urls.each do |item|
lib/saml/kit/builders/templates/service_provider_metadata.builder
@@ -1,10 +1,7 @@
# frozen_string_literal: true
xml.SPSSODescriptor descriptor_options do
- configuration.certificates(use: :signing).each do |certificate|
- render certificate, xml: xml
- end
- configuration.certificates(use: :encryption).each do |certificate|
+ configuration.certificates.each do |certificate|
render certificate, xml: xml
end
logout_urls.each do |item|
lib/saml/kit/builders/assertion.rb
@@ -16,7 +16,7 @@ module Saml
end
def name_id_format
- request.name_id_format
+ request.try(:name_id_format) || Saml::Kit::Namespaces::PERSISTENT
end
def name_id
@@ -24,6 +24,7 @@ module Saml
end
def assertion_attributes
+ return {} unless user.respond_to?(:assertion_attributes_for)
user.assertion_attributes_for(request)
end
@@ -43,11 +44,10 @@ module Saml
end
def subject_confirmation_data_options
- {
- InResponseTo: request.id,
- NotOnOrAfter: 3.hours.since(now).utc.iso8601,
- Recipient: destination,
- }
+ options = { NotOnOrAfter: 3.hours.since(now).utc.iso8601 }
+ options[:Recipient] = destination if destination.present?
+ options[:InResponseTo] = request.id if request.present?
+ options
end
def conditions_options
lib/saml/kit/builders/authentication_request.rb
@@ -4,7 +4,7 @@ module Saml
module Kit
module Builders
# {include:file:lib/saml/kit/builders/templates/authentication_request.builder}
- # {include:file:spec/saml/builders/authentication_request_spec.rb}
+ # {include:file:spec/saml/kit/builders/authentication_request_spec.rb}
class AuthenticationRequest
include XmlTemplatable
attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :destination
lib/saml/kit/builders/identity_provider_metadata.rb
@@ -4,7 +4,7 @@ module Saml
module Kit
module Builders
# {include:file:lib/saml/kit/builders/templates/identity_provider_metadata.builder}
- # {include:file:spec/saml/builders/identity_provider_metadata_spec.rb}
+ # {include:file:spec/saml/kit/builders/identity_provider_metadata_spec.rb}
class IdentityProviderMetadata
include XmlTemplatable
extend Forwardable
lib/saml/kit/builders/logout_request.rb
@@ -4,7 +4,7 @@ module Saml
module Kit
module Builders
# {include:file:lib/saml/kit/builders/templates/logout_request.builder}
- # {include:file:spec/saml/builders/logout_request_spec.rb}
+ # {include:file:spec/saml/kit/builders/logout_request_spec.rb}
class LogoutRequest
include XmlTemplatable
attr_accessor :id, :destination, :issuer, :name_id_format, :now
lib/saml/kit/builders/logout_response.rb
@@ -4,7 +4,7 @@ module Saml
module Kit
module Builders
# {include:file:lib/saml/kit/builders/templates/logout_response.builder}
- # {include:file:spec/saml/builders/logout_response_spec.rb}
+ # {include:file:spec/saml/kit/builders/logout_response_spec.rb}
class LogoutResponse
include XmlTemplatable
attr_accessor :id, :issuer, :version, :status_code, :now, :destination
lib/saml/kit/builders/metadata.rb
@@ -4,7 +4,7 @@ module Saml
module Kit
module Builders
# {include:file:lib/saml/kit/builders/templates/metadata.builder}
- # {include:file:spec/saml/builders/metadata_spec.rb}
+ # {include:file:spec/saml/kit/builders/metadata_spec.rb}
class Metadata
include XmlTemplatable
lib/saml/kit/builders/response.rb
@@ -4,7 +4,7 @@ module Saml
module Kit
module Builders
# {include:file:lib/saml/kit/builders/templates/response.builder}
- # {include:file:spec/saml/builders/response_spec.rb}
+ # {include:file:spec/saml/kit/builders/response_spec.rb}
class Response
include XmlTemplatable
attr_reader :user, :request
@@ -13,7 +13,7 @@ module Saml
attr_accessor :issuer, :destination
attr_reader :configuration
- def initialize(user, request, configuration: Saml::Kit.configuration)
+ def initialize(user, request = nil, configuration: Saml::Kit.configuration)
@user = user
@request = request
@id = ::Xml::Kit::Id.generate
@@ -28,7 +28,7 @@ module Saml
end
def build
- Saml::Kit::Response.new(to_xml, request_id: request.id, configuration: configuration)
+ Saml::Kit::Response.new(to_xml, request_id: request.try(:id), configuration: configuration)
end
def assertion
@@ -46,15 +46,16 @@ module Saml
private
def response_options
- {
+ options = {
ID: id,
Version: version,
IssueInstant: now.iso8601,
- Destination: destination,
Consent: Namespaces::UNSPECIFIED,
- InResponseTo: request.id,
xmlns: Namespaces::PROTOCOL,
}
+ options[:Destination] = destination if destination.present?
+ options[:InResponseTo] = request.id if request.present?
+ options
end
end
end
lib/saml/kit/builders/service_provider_metadata.rb
@@ -4,7 +4,7 @@ module Saml
module Kit
module Builders
# {include:file:lib/saml/kit/builders/templates/service_provider_metadata.builder}
- # {include:file:spec/saml/builders/service_provider_metadata_spec.rb}
+ # {include:file:spec/saml/kit/builders/service_provider_metadata_spec.rb}
class ServiceProviderMetadata
include XmlTemplatable
extend Forwardable
lib/saml/kit/assertion.rb
@@ -109,6 +109,7 @@ module Saml
end
def must_match_issuer
+ return if audiences.empty?
return if audiences.include?(configuration.entity_id)
errors[:audience] << error_message(:must_match_issuer)
end
lib/saml/kit/default_registry.rb
@@ -29,7 +29,7 @@ module Saml
# configuration.logger = Rails.logger
# end
#
- # {include:file:spec/saml/default_registry_spec.rb}
+ # {include:file:spec/saml/kit/default_registry_spec.rb}
class DefaultRegistry
include Enumerable
lib/saml/kit/document.rb
@@ -17,6 +17,7 @@ module Saml
"samlp": ::Saml::Kit::Namespaces::PROTOCOL,
'xmlenc' => ::Xml::Kit::Namespaces::XMLENC,
}.freeze
+ attr_accessor :registry
validates_presence_of :content
validates_presence_of :id
validate :must_match_xsd
@@ -25,6 +26,7 @@ module Saml
def initialize(xml, name:, configuration: Saml::Kit.configuration)
@configuration = configuration
+ @registry = configuration.registry
@content = xml
@name = name
end
lib/saml/kit/invalid_document.rb
@@ -2,7 +2,7 @@
module Saml
module Kit
- # {include:file:spec/saml/invalid_document_spec.rb}
+ # {include:file:spec/saml/kit/invalid_document_spec.rb}
class InvalidDocument < Document
validate do |model|
model.errors[:base] << model.error_message(:invalid)
lib/saml/kit/signature.rb
@@ -69,8 +69,8 @@ module Saml
node
end
- def to_xml
- node.to_s
+ def to_xml(pretty: false)
+ pretty ? node.to_xml(indent: 2) : node.to_s
end
private
@@ -84,6 +84,8 @@ module Saml
dsignature.errors.each do |attribute|
errors.add(attribute, error_message(attribute))
end
+ rescue Xmldsig::SchemaError => error
+ errors.add(:base, error.message)
end
def validate_certificate(now = Time.now.utc)
lib/saml/kit/trustable.rb
@@ -30,7 +30,7 @@ module Saml
# @!visibility private
def provider
- configuration.registry.metadata_for(issuer)
+ registry.metadata_for(issuer)
end
# @!visibility private
lib/saml/kit/version.rb
@@ -2,6 +2,6 @@
module Saml
module Kit
- VERSION = '1.0.11'.freeze
+ VERSION = '1.0.12'.freeze
end
end
lib/saml/kit/xml_templatable.rb
@@ -21,11 +21,6 @@ module Saml
(embed_signature && signing_key_pair.present?)
end
- def encrypt_with(key_pair)
- self.encrypt = true
- self.encryption_certificate = key_pair.certificate
- end
-
def digest_method
configuration.digest_method
end
spec/fixtures/response_node_text_attack.xml.base64
@@ -0,0 +1,1 @@
+PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIFZlcnNpb249IjIuMCIgSUQ9InBmeGE0NjU3NGRmLWIzYjAtYTA2YS0yM2M4LTYzNjQxMzE5ODc3MiIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiI+DQogICAgPHNhbWw6SXNzdWVyPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhLzEzNTkwPC9zYW1sOklzc3Vlcj4NCiAgICA8ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgICAgIDxkczpTaWduZWRJbmZvPg0KICAgICAgICA8ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgICAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogICAgICAgIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4YTQ2NTc0ZGYtYjNiMC1hMDZhLTIzYzgtNjM2NDEzMTk4NzcyIj4NCiAgICAgICAgICA8ZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICAgIDxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPg0KICAgICAgICAgICAgPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgICAgICAgIDwvZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICA8ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz4NCiAgICAgICAgICA8ZHM6RGlnZXN0VmFsdWU+cEpRN01TL2VrNEtSUldHbXYvSDQzUmVIWU1zPTwvZHM6RGlnZXN0VmFsdWU+DQogICAgICAgIDwvZHM6UmVmZXJlbmNlPg0KICAgICAgPC9kczpTaWduZWRJbmZvPg0KICAgICAgPGRzOlNpZ25hdHVyZVZhbHVlPnlpdmVLY1BkRHB1RE5qNnNoclEzQUJ3ci9jQTNDcnlEMnBoRy94TFpzektXeFU1L21sYUt0OGV3YlpPZEtLdnRPczJwSEJ5NUR1YTNrOTRBRnp4R3llbDVnT293bW95WEpyQU9ya1BPMHZsaTFWOG8zaFBQVVp3UmdTWDZROXBTMUNxUWdoS2lFYXNSeXlscXFKVWFQWXptT3pPRTgvWGxNa3dpV21PMD08L2RzOlNpZ25hdHVyZVZhbHVlPg0KICAgICAgPGRzOktleUluZm8+DQogICAgICAgIDxkczpYNTA5RGF0YT4NCiAgICAgICAgICA8ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUJyVENDQWFHZ0F3SUJBZ0lCQVRBREJnRUFNR2N4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUlV3RXdZRFZRUUhEQXhUWVc1MFlTQk5iMjVwWTJFeEVUQVBCZ05WQkFvTUNFOXVaVXh2WjJsdU1Sa3dGd1lEVlFRRERCQmhjSEF1YjI1bGJHOW5hVzR1WTI5dE1CNFhEVEV3TURNd09UQTVOVGcwTlZvWERURTFNRE13T1RBNU5UZzBOVm93WnpFTE1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ01Da05oYkdsbWIzSnVhV0V4RlRBVEJnTlZCQWNNREZOaGJuUmhJRTF2Ym1sallURVJNQThHQTFVRUNnd0lUMjVsVEc5bmFXNHhHVEFYQmdOVkJBTU1FR0Z3Y0M1dmJtVnNiMmRwYmk1amIyMHdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JBT2pTdTFmalB5OGQ1dzRReUwxemQ0aEl3MU1ra2ZmNFdZL1RMRzhPWmtVNVlUU1dtbUhQRDVrdllINXVvWFMvNnFRODFxWHBSMndWOENUb3daSlVMZzA5ZGRSZFJuOFFzcWoxRnlPQzVzbEUzeTJiWjJvRnVhNzJvZi80OWZwdWpuRlQ2S25RNjFDQk1xbERvVFFxT1Q2MnZHSjhuUDZNWld2QTZzeHF1ZDVBZ01CQUFFd0F3WUJBQU1CQUE9PTwvZHM6WDUwOUNlcnRpZmljYXRlPg0KICAgICAgICA8L2RzOlg1MDlEYXRhPg0KICAgICAgPC9kczpLZXlJbmZvPg0KICAgIDwvZHM6U2lnbmF0dXJlPg0KICAgIDxzYW1sOlN1YmplY3Q+DQogICAgICA8c2FtbDpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnN1cHBvcnQ8IS0tIGF0dGFjayEgLS0+QG9uZWxvZ2luLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiIFJlY2lwaWVudD0ie3JlY2lwaWVudH0iLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0xMS0xOFQyMTo1MjozN1oiIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+e2F1ZGllbmNlfTwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMC0xMS0xOFQyMTo1NzozN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMTlUMjE6NTc6MzdaIiBTZXNzaW9uSW5kZXg9Il81MzFjMzJkMjgzYmRmZjdlMDRlNDg3YmNkYmM0ZGQ4ZCI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9InN1cm5hbWUiPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnM8IS0tIGF0dGFjayEgLS0+bWl0aDwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0iYW5vdGhlcl92YWx1ZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+dmFsdWUxPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnZhbHVlMjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0icm9sZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+cm9sZTE8L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9ImZpcnN0bmFtZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+Ym9iPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4gIA0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9ImF0dHJpYnV0ZV93aXRoX25pbF92YWx1ZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOm5pbD0idHJ1ZSIvPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJhdHRyaWJ1dGVfd2l0aF9uaWxzX2FuZF9lbXB0eV9zdHJpbmdzIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUvPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZT52YWx1ZVByZXNlbnQ8L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOm5pbD0idHJ1ZSIvPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTpuaWw9IjEiLz4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
spec/saml/kit/authentication_request_spec.rb
@@ -189,7 +189,7 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
let(:url) { FFaker::Internet.uri('https') }
let(:entity_id) { FFaker::Internet.uri('https') }
- it 'provides a nice API for building metadata' do
+ it 'provides a nice API for building a request' do
result = described_class.build do |builder|
builder.issuer = entity_id
builder.assertion_consumer_service_url = url
@@ -199,6 +199,18 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
expect(result.issuer).to eql(entity_id)
expect(result.assertion_consumer_service_url).to eql(url)
end
+
+ it 'can build a authnrequest without a nameid policy' do
+ result = described_class.build do |x|
+ x.issuer = entity_id
+ x.assertion_consumer_service_url = url
+ x.name_id_format = nil
+ end
+ expect(result).to be_instance_of(described_class)
+ result.registry = instance_double(Saml::Kit::DefaultRegistry, metadata_for: Saml::Kit::ServiceProviderMetadata.build)
+ expect(result).to be_valid
+ expect(result.to_xml).not_to include('NameIDPolicy')
+ end
end
describe '#response_for' do
spec/saml/kit/metadata_spec.rb
@@ -84,4 +84,36 @@ RSpec.describe Saml::Kit::Metadata do
expect(subject.signature).to be_present
end
end
+
+ describe 'validations' do
+ it 'is invalid, when the digest value is invalid' do
+ xml = described_class.build_xml do |x|
+ x.entity_id = 'original'
+ x.sign_with(::Xml::Kit::KeyPair.generate(use: :signing))
+ x.build_identity_provider do |y|
+ y.add_single_sign_on_service(FFaker::Internet.uri('https'), binding: :http_post)
+ end
+ end
+
+ subject = described_class.from(xml.gsub('original', 'altered'))
+ expect(subject).not_to be_valid
+ expect(subject.errors[:digest_value]).to include('is invalid.')
+ end
+
+ it 'is invalid when the signature is invalid' do
+ xml = described_class.build_xml do |x|
+ x.sign_with(::Xml::Kit::KeyPair.generate(use: :signing))
+ x.build_identity_provider do |y|
+ y.add_single_sign_on_service(FFaker::Internet.uri('https'), binding: :http_post)
+ end
+ end
+ document = Nokogiri::XML(xml)
+ node = document.at_xpath('/*/ds:Signature/ds:SignatureValue', ds: Xml::Kit::Namespaces::XMLDSIG)
+ node.content = Base64.encode64('invalid')
+
+ subject = described_class.from(document.to_s)
+ expect(subject).not_to be_valid
+ expect(subject.errors[:signature]).to include('is invalid.')
+ end
+ end
end
spec/saml/kit/response_spec.rb
@@ -530,6 +530,20 @@ XML
expect(subject.name_id).to eql(user.name_id)
end
+ it 'excludes comments from the name id' do
+ user.name_id = 'shiro@voltron.com<!-- CVE-2017-11428 -->.evil.com'
+ subject = described_class.build(user, request)
+ expect(subject.name_id).to eql('shiro@voltron.com<!-- CVE-2017-11428 -->.evil.com')
+ expect(subject.name_id).not_to eql('shiro@voltron.com')
+ end
+
+ it 'parses the name id safely (CVE-2017-11428)' do
+ raw = IO.read('spec/fixtures/response_node_text_attack.xml.base64')
+ subject = Saml::Kit::Bindings::HttpPost.new(location: '').deserialize('SAMLResponse' => raw)
+ expect(subject.name_id).to eql('support@onelogin.com')
+ expect(subject.attributes[:surname]).to eql('smith')
+ end
+
it 'returns the single attributes' do
subject = described_class.build(user, request)
expect(subject.attributes).to eql('name' => 'mo')
@@ -541,4 +555,31 @@ XML
expect(subject.attributes).to eql('name' => 'mo', 'age' => '33')
end
end
+
+ describe '#build' do
+ it 'can build a response without a request' do
+ configuration = Saml::Kit::Configuration.new do |config|
+ config.entity_id = FFaker::Internet.uri('https')
+ end
+ sp = Saml::Kit::Metadata.build(&:build_service_provider)
+ allow(configuration.registry).to receive(:metadata_for).with(configuration.entity_id).and_return(sp)
+ result = described_class.build(user, configuration: configuration)
+ expect(result).to be_instance_of(described_class)
+ expect(result).to be_valid
+ end
+
+ it 'can build a response without the need for the user to provide attributes' do
+ configuration = Saml::Kit::Configuration.new do |config|
+ config.entity_id = FFaker::Internet.uri('https')
+ end
+ sp = Saml::Kit::Metadata.build(&:build_service_provider)
+ allow(configuration.registry).to receive(:metadata_for).with(configuration.entity_id).and_return(sp)
+ user = UserWithoutAttributes.new
+
+ result = described_class.build(user, configuration: configuration)
+ expect(result).to be_instance_of(described_class)
+ expect(result).to be_valid
+ expect(result.attributes).to be_empty
+ end
+ end
end
spec/saml/kit/signature_spec.rb
@@ -39,6 +39,15 @@ RSpec.describe Saml::Kit::Signature do
expect(subject.errors[:base]).to match_array(['is missing.'])
end
+ it 'is invalid when the schema of the signature is invalid' do
+ signature_element = signed_document.at_xpath('//ds:Signature')
+ element = signature_element.at_xpath('./ds:SignedInfo', ds: Xml::Kit::Namespaces::XMLDSIG)
+ element.name = 'BLAH'
+ subject = described_class.new(signature_element)
+ expect(subject).not_to be_valid
+ expect(subject.errors[:base]).to include("1:0: ERROR: Element '{http://www.w3.org/2000/09/xmldsig#}BLAH': This element is not expected. Expected is ( {http://www.w3.org/2000/09/xmldsig#}SignedInfo ).")
+ end
+
describe 'certificate validation' do
let(:key_pair) { ::Xml::Kit::KeyPair.new(expired_certificate, private_key, nil, :signing) }
let(:private_key) { OpenSSL::PKey::RSA.new(2048) }
spec/support/user.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class User
- attr_reader :name_id, :attributes
+ attr_accessor :name_id, :attributes
def initialize(name_id: SecureRandom.uuid, attributes: {})
@name_id = name_id
spec/support/user_without_attributes.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class UserWithoutAttributes
+ attr_accessor :name_id
+
+ def initialize(name_id: SecureRandom.uuid)
+ @name_id = name_id
+ end
+
+ def name_id_for(_format)
+ name_id
+ end
+end
.rubocop_todo.yml
@@ -15,7 +15,7 @@ Lint/UnusedMethodArgument:
# Offense count: 2
Metrics/AbcSize:
- Max: 16
+ Max: 20
# Offense count: 3
# Configuration parameters: CountComments.
.travis.yml
@@ -1,5 +1,6 @@
sudo: false
language: ruby
+cache: bundler
rvm:
- 2.2.9
- 2.3.6
Rakefile
@@ -8,3 +8,8 @@ task default: :spec
require 'rubocop/rake_task'
RuboCop::RakeTask.new(:rubocop)
+
+require 'bundler/audit/task'
+Bundler::Audit::Task.new
+
+task lint: [:rubocop, 'bundle:audit']
saml-kit.gemspec
@@ -30,8 +30,9 @@ Gem::Specification.new do |spec|
spec.require_paths = ['lib']
spec.add_dependency 'activemodel', '>= 4.2.0'
- spec.add_dependency 'xml-kit', '>= 0.1.10', '<= 1.0.0'
+ spec.add_dependency 'xml-kit', '>= 0.1.12', '<= 1.0.0'
spec.add_development_dependency 'bundler', '~> 1.15'
+ spec.add_development_dependency 'bundler-audit', '~> 0.6'
spec.add_development_dependency 'ffaker', '~> 2.7'
spec.add_development_dependency 'rake', '~> 10.0'
spec.add_development_dependency 'rspec', '~> 3.0'