Comparing changes
v1.0.15
→
v1.0.16
31 commits
60 files changed
Commits
Changed files (60)
lib
saml
kit
builders
concerns
rspec
spec
exe/saml-kit-create-self-signed-certificate
@@ -3,11 +3,15 @@
require 'saml/kit'
-Saml::Kit.deprecate("Use the 'saml-kit-cli' gem instead. saml-kit-create-self-signed-certificate")
+message = "Use the 'saml-kit-cli' gem instead."\
+ ' saml-kit-create-self-signed-certificate'
+Saml::Kit.deprecate(message)
puts 'Enter Passphrase:'
passphrase = STDIN.read.strip
-certificate, private_key = ::Xml::Kit::SelfSignedCertificate.new.create(passphrase: passphrase)
+certificate, private_key = ::Xml::Kit::SelfSignedCertificate.new.create(
+ passphrase: passphrase
+)
puts '** BEGIN File Format **'
print certificate
exe/saml-kit-decode-http-post
@@ -3,7 +3,8 @@
require 'saml/kit'
-Saml::Kit.deprecate("Use the 'saml-kit-cli' gem instead. saml-kit-decode-http-post")
+message = "Use the 'saml-kit-cli' gem instead. saml-kit-decode-http-post"
+Saml::Kit.deprecate(message)
saml = STDIN.read
binding = Saml::Kit::Bindings::HttpPost.new(location: '')
exe/saml-kit-decode-http-redirect
@@ -3,7 +3,8 @@
require 'saml/kit'
-Saml::Kit.deprecate("Use the 'saml-kit-cli' gem instead. saml-kit-decode-http-redirect*")
+message = "Use the 'saml-kit-cli' gem instead. saml-kit-decode-http-redirect*"
+Saml::Kit.deprecate(message)
input = STDIN.read
binding = Saml::Kit::Bindings::HttpRedirect.new(location: '')
lib/saml/kit/bindings/binding.rb
@@ -56,13 +56,10 @@ module Saml
SAMLRequest: params[:SAMLRequest] || params['SAMLRequest'],
SAMLResponse: params[:SAMLResponse] || params['SAMLResponse'],
}
- if parameters[:SAMLRequest].present?
- parameters[:SAMLRequest]
- elsif parameters[:SAMLResponse].present?
- parameters[:SAMLResponse]
- else
- raise ArgumentError, 'SAMLRequest or SAMLResponse parameter is required.'
- end
+ return parameters[:SAMLRequest] if parameters[:SAMLRequest].present?
+ return parameters[:SAMLResponse] if parameters[:SAMLResponse].present?
+ message = 'SAMLRequest or SAMLResponse parameter is required.'
+ raise ArgumentError, message
end
end
end
lib/saml/kit/bindings/http_post.rb
@@ -19,8 +19,9 @@ module Saml
def serialize(builder, relay_state: nil)
builder.destination = location
document = builder.build
+ xml = document.to_xml
saml_params = {
- document.query_string_parameter => Base64.strict_encode64(document.to_xml),
+ document.query_string_parameter => Base64.strict_encode64(xml),
}
saml_params['RelayState'] = relay_state if relay_state.present?
[location, saml_params]
@@ -28,7 +29,10 @@ module Saml
def deserialize(params, configuration: Saml::Kit.configuration)
xml = decode(saml_param_from(params))
- Saml::Kit::Document.to_saml_document(xml, configuration: configuration)
+ Saml::Kit::Document.to_saml_document(
+ xml,
+ configuration: configuration
+ )
end
end
end
lib/saml/kit/bindings/http_redirect.rb
@@ -20,7 +20,8 @@ module Saml
builder.embed_signature = false
builder.destination = location
document = builder.build
- [UrlBuilder.new(configuration: builder.configuration).build(document, relay_state: relay_state), {}]
+ url_builder = UrlBuilder.new(configuration: builder.configuration)
+ [url_builder.build(document, relay_state: relay_state), {}]
end
def deserialize(params, configuration: Saml::Kit.configuration)
@@ -34,18 +35,20 @@ module Saml
def deserialize_document_from(params, configuration)
xml = inflate(decode(unescape(saml_param_from(params))))
- Saml::Kit::Document.to_saml_document(xml, configuration: configuration)
+ Saml::Kit::Document.to_saml_document(
+ xml,
+ configuration: configuration
+ )
end
def ensure_valid_signature(params, document)
signature = params[:Signature]
- algorithm = params[:SigAlg]
provider = document.provider
- return if signature.blank? || algorithm.blank?
+ return if signature.blank? || params[:SigAlg].blank?
return if provider.nil?
return document.signature_verified! if provider.verify(
- algorithm_for(algorithm),
+ algorithm_for(params[:SigAlg]),
decode(signature),
canonicalize(params)
)
lib/saml/kit/bindings/url_builder.rb
@@ -17,17 +17,16 @@ module Saml
@configuration = configuration
end
- def build(saml_document, relay_state: nil)
- destination = saml_document.destination
+ def build(document, relay_state: nil)
+ destination = document.destination
if configuration.sign?
- payload = canonicalize(saml_document, relay_state)
+ payload = canonicalize(document, relay_state)
"#{destination}?#{payload}&Signature=#{signature_for(payload)}"
else
- payload = to_query_string(
- saml_document.query_string_parameter => serialize(saml_document.to_xml),
+ "#{destination}?" + to_query_string(
+ document.query_string_parameter => serialize(document.to_xml),
'RelayState' => relay_state
)
- "#{destination}?#{payload}"
end
end
@@ -39,8 +38,9 @@ module Saml
end
def canonicalize(saml_document, relay_state)
+ xml = saml_document.to_xml
to_query_string(
- saml_document.query_string_parameter => serialize(saml_document.to_xml),
+ saml_document.query_string_parameter => serialize(xml),
'RelayState' => relay_state,
'SigAlg' => ::Xml::Kit::Namespaces::SHA256
)
lib/saml/kit/builders/assertion.rb
@@ -9,7 +9,9 @@ module Saml
include XmlTemplatable
extend Forwardable
- def_delegators :@response_builder, :request, :issuer, :reference_id, :now, :configuration, :user, :version, :destination
+ def_delegators :@response_builder,
+ :request, :issuer, :reference_id, :now, :configuration, :user,
+ :version, :destination
def initialize(response_builder, embed_signature)
@response_builder = response_builder
@@ -17,7 +19,7 @@ module Saml
end
def name_id_format
- request.try(:name_id_format) || Saml::Kit::Namespaces::PERSISTENT
+ request.try(:name_id_format)
end
def name_id
@@ -62,7 +64,8 @@ module Saml
{
AuthnInstant: now.iso8601,
SessionIndex: reference_id,
- SessionNotOnOrAfter: configuration.session_timeout.since(now).utc.iso8601,
+ SessionNotOnOrAfter:
+ configuration.session_timeout.since(now).utc.iso8601,
}
end
end
lib/saml/kit/builders/authentication_request.rb
@@ -7,7 +7,8 @@ module Saml
# {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
+ attr_accessor :id, :now, :issuer, :assertion_consumer_service_url
+ attr_accessor :name_id_format, :destination
attr_accessor :version
attr_reader :configuration
@@ -36,7 +37,8 @@ module Saml
Destination: destination,
}
if assertion_consumer_service_url.present?
- options[:AssertionConsumerServiceURL] = assertion_consumer_service_url
+ options[:AssertionConsumerServiceURL] =
+ assertion_consumer_service_url
end
options
end
lib/saml/kit/builders/encrypted_assertion.rb
@@ -10,7 +10,9 @@ module Saml
extend Forwardable
attr_reader :assertion
- def_delegators :@response_builder, :configuration, :encryption_certificate
+ def_delegators :@response_builder,
+ :configuration,
+ :encryption_certificate
def initialize(response_builder, assertion)
@response_builder = response_builder
lib/saml/kit/builders/identity_provider_metadata.rb
@@ -13,7 +13,9 @@ module Saml
attr_reader :logout_urls, :single_sign_on_urls
attr_reader :configuration
attr_reader :metadata
- def_delegators :metadata, :id, :id=, :entity_id, :entity_id=, :organization_name, :organization_name=, :organization_url, :organization_url=, :contact_email, :contact_email=, :to_xml
+ def_delegators :metadata, :id, :id=, :entity_id, :entity_id=,
+ :organization_name, :organization_name=, :organization_url,
+ :organization_url=, :contact_email, :contact_email=, :to_xml
def initialize(configuration: Saml::Kit.configuration)
@attributes = []
@@ -24,16 +26,24 @@ module Saml
@name_id_formats = [Namespaces::PERSISTENT]
@single_sign_on_urls = []
@want_authn_requests_signed = true
- @metadata = Saml::Kit::Builders::Metadata.new(configuration: configuration)
+ @metadata = Saml::Kit::Builders::Metadata.new(
+ configuration: configuration
+ )
@metadata.identity_provider = self
end
def add_single_sign_on_service(url, binding: :http_post)
- @single_sign_on_urls.push(location: url, binding: Bindings.binding_for(binding))
+ @single_sign_on_urls.push(
+ location: url,
+ binding: Bindings.binding_for(binding)
+ )
end
def add_single_logout_service(url, binding: :http_post)
- @logout_urls.push(location: url, binding: Bindings.binding_for(binding))
+ @logout_urls.push(
+ location: url,
+ binding: Bindings.binding_for(binding)
+ )
end
def build
lib/saml/kit/builders/metadata.rb
@@ -22,14 +22,18 @@ module Saml
end
def build_service_provider
- @service_provider = Saml::Kit::ServiceProviderMetadata.builder(configuration: configuration) do |xx|
- yield xx if block_given?
+ @service_provider = Saml::Kit::ServiceProviderMetadata.builder(
+ configuration: configuration
+ ) do |x|
+ yield x if block_given?
end
end
def build_identity_provider
- @identity_provider = Saml::Kit::IdentityProviderMetadata.builder(configuration: configuration) do |xx|
- yield xx if block_given?
+ @identity_provider = Saml::Kit::IdentityProviderMetadata.builder(
+ configuration: configuration
+ ) do |x|
+ yield x if block_given?
end
end
lib/saml/kit/builders/null.rb
@@ -1,4 +1,3 @@
-
# frozen_string_literal: true
module Saml
lib/saml/kit/builders/response.rb
@@ -13,7 +13,9 @@ module Saml
attr_accessor :issuer, :destination
attr_reader :configuration
- def initialize(user, request = nil, configuration: Saml::Kit.configuration)
+ def initialize(
+ user, request = nil, configuration: Saml::Kit.configuration
+ )
@user = user
@request = request
@id = ::Xml::Kit::Id.generate
@@ -23,23 +25,30 @@ module Saml
@status_code = Namespaces::SUCCESS
@status_message = nil
@issuer = configuration.entity_id
- @encryption_certificate = request.try(:provider).try(:encryption_certificates).try(:last)
+ @encryption_certificate = request.try(:provider)
+ .try(:encryption_certificates).try(:last)
@encrypt = encryption_certificate.present?
@configuration = configuration
end
def build
- Saml::Kit::Response.new(to_xml, request_id: request.try(:id), configuration: configuration)
+ Saml::Kit::Response.new(
+ to_xml,
+ request_id: request.try(:id),
+ configuration: configuration
+ )
end
def assertion=(value)
- @assertion = value ? value : Null.new
+ @assertion = value || Null.new
end
def assertion
@assertion ||=
begin
- assertion = Saml::Kit::Builders::Assertion.new(self, embed_signature)
+ assertion = Saml::Kit::Builders::Assertion.new(
+ self, embed_signature
+ )
if encrypt
Saml::Kit::Builders::EncryptedAssertion.new(self, assertion)
else
lib/saml/kit/builders/service_provider_metadata.rb
@@ -12,7 +12,9 @@ module Saml
attr_accessor :want_assertions_signed
attr_reader :configuration
attr_reader :metadata
- def_delegators :metadata, :id, :id=, :entity_id, :entity_id=, :organization_name, :organization_name=, :organization_url, :organization_url=, :contact_email, :contact_email=, :to_xml
+ def_delegators :metadata, :id, :id=, :entity_id, :entity_id=,
+ :organization_name, :organization_name=, :organization_url,
+ :organization_url=, :contact_email, :contact_email=, :to_xml
def initialize(configuration: Saml::Kit.configuration)
@acs_urls = []
@@ -20,7 +22,9 @@ module Saml
@logout_urls = []
@name_id_formats = [Namespaces::PERSISTENT]
@want_assertions_signed = true
- @metadata = Saml::Kit::Builders::Metadata.new(configuration: configuration)
+ @metadata = Saml::Kit::Builders::Metadata.new(
+ configuration: configuration
+ )
@metadata.service_provider = self
end
@@ -29,7 +33,10 @@ module Saml
end
def add_single_logout_service(url, binding: :http_post)
- @logout_urls.push(location: url, binding: Bindings.binding_for(binding))
+ @logout_urls.push(
+ location: url,
+ binding: Bindings.binding_for(binding)
+ )
end
def build
lib/saml/kit/buildable.rb → lib/saml/kit/concerns/buildable.rb
File renamed without changes
lib/saml/kit/requestable.rb → lib/saml/kit/concerns/requestable.rb
File renamed without changes
lib/saml/kit/respondable.rb → lib/saml/kit/concerns/respondable.rb
File renamed without changes
lib/saml/kit/serializable.rb → lib/saml/kit/concerns/serializable.rb
File renamed without changes
lib/saml/kit/translatable.rb → lib/saml/kit/concerns/translatable.rb
File renamed without changes
lib/saml/kit/trustable.rb → lib/saml/kit/concerns/trustable.rb
@@ -9,14 +9,15 @@ module Saml
extend ActiveSupport::Concern
included do
- validate :must_have_valid_signature, unless: :signature_manually_verified
+ validate :must_have_valid_signature, unless: :signature_verified
validate :must_be_registered
validate :must_be_trusted
end
- # Returns true when the document has an embedded XML Signature or has been verified externally.
+ # Returns true when the document has an embedded XML Signature or has
+ # been verified externally.
def signed?
- signature_manually_verified || signature.present?
+ signature_verified || signature.present?
end
# @!visibility private
@@ -24,9 +25,10 @@ module Saml
@signature ||= Signature.new(at_xpath("/samlp:#{name}/ds:Signature"))
end
- # Returns true when documents is signed and the signing certificate belongs to a known service entity.
+ # Returns true when documents is signed and the signing certificate
+ # belongs to a known service entity.
def trusted?
- return true if signature_manually_verified
+ return true if signature_verified
return false unless signed?
signature.trusted?(provider)
end
@@ -38,12 +40,12 @@ module Saml
# @!visibility private
def signature_verified!
- @signature_manually_verified = true
+ @signature_verified = true
end
private
- attr_reader :signature_manually_verified
+ attr_reader :signature_verified
def must_have_valid_signature
return if to_xml.blank?
lib/saml/kit/concerns/xml_parseable.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'saml/kit/namespaces'
+
+module Saml
+ module Kit
+ module XmlParseable
+ NAMESPACES = {
+ NameFormat: ::Saml::Kit::Namespaces::ATTR_SPLAT,
+ ds: ::Xml::Kit::Namespaces::XMLDSIG,
+ md: ::Saml::Kit::Namespaces::METADATA,
+ saml: ::Saml::Kit::Namespaces::ASSERTION,
+ samlp: ::Saml::Kit::Namespaces::PROTOCOL,
+ xmlenc: ::Xml::Kit::Namespaces::XMLENC,
+ }.freeze
+
+ # Returns the SAML document returned as a Hash.
+ def to_h
+ @to_h ||= Hash.from_xml(to_s) || {}
+ end
+
+ # Returns the XML document as a String.
+ #
+ # @param pretty [Boolean] true to return a human friendly version
+ # of the XML.
+ def to_xml(pretty: nil)
+ pretty ? to_nokogiri.to_xml(indent: 2) : to_s
+ end
+
+ # Returns the SAML document as an XHTML string.
+ # This is useful for rendering in a web page.
+ def to_xhtml
+ Nokogiri::XML(to_xml, &:noblanks).to_xhtml
+ end
+
+ def present?
+ to_s.present?
+ end
+
+ # @!visibility private
+ def to_nokogiri
+ @to_nokogiri ||= Nokogiri::XML(to_s)
+ end
+
+ # @!visibility private
+ def at_xpath(xpath)
+ return unless present?
+ to_nokogiri.at_xpath(xpath, NAMESPACES)
+ end
+
+ # @!visibility private
+ def search(xpath)
+ to_nokogiri.search(xpath, NAMESPACES)
+ end
+
+ # Returns the XML document as a [String].
+ def to_s
+ content
+ end
+ end
+ end
+end
lib/saml/kit/xml_templatable.rb → lib/saml/kit/concerns/xml_templatable.rb
@@ -5,7 +5,7 @@ module Saml
# This module is responsible for
# generating converting templates to xml.
module XmlTemplatable
- TEMPLATES_DIR = Pathname.new(File.join(__dir__, 'builders/templates/'))
+ TEMPLATES_DIR = Pathname.new(File.join(__dir__, '../builders/templates/'))
include ::Xml::Kit::Templatable
def template_path
@@ -16,7 +16,8 @@ module Saml
"#{self.class.name.split('::').last.underscore}.builder"
end
- # Returns true if an embedded signature is requested and at least one signing certificate is available via the configuration.
+ # Returns true if an embedded signature is requested and at least one
+ # signing certificate is available via the configuration.
def sign?
return configuration.sign? if embed_signature.nil?
(embed_signature && configuration.sign?) ||
lib/saml/kit/xsd_validatable.rb → lib/saml/kit/concerns/xsd_validatable.rb
@@ -5,8 +5,18 @@ module Saml
# This module is responsible for validating
# xml documents against the SAML XSD's
module XsdValidatable
+ PROTOCOL_XSD = File.expand_path(
+ '../xsd/saml-schema-protocol-2.0.xsd', File.dirname(__FILE__)
+ ).freeze
+
+ METADATA_XSD = File.expand_path(
+ '../xsd/saml-schema-metadata-2.0.xsd', File.dirname(__FILE__)
+ ).freeze
+
# @!visibility private
def matches_xsd?(xsd)
+ return unless to_nokogiri.present?
+
Dir.chdir(File.dirname(xsd)) do
xsd = Nokogiri::XML::Schema(IO.read(xsd))
xsd.validate(to_nokogiri).each do |error|
lib/saml/kit/rspec/have_xpath.rb
@@ -6,11 +6,13 @@ RSpec::Matchers.define :have_xpath do |xpath|
end
failure_message do |actual|
- "Expected xpath: #{xpath.inspect} to match in:\n #{xml_pretty_print(actual)}"
+ xml = xml_pretty_print(actual)
+ "Expected xpath: #{xpath.inspect} to match in:\n #{xml}"
end
failure_message_when_negated do |actual|
- "Expected xpath: #{xpath.inspect} not to match in:\n #{xml_pretty_print(actual)}"
+ xml = xml_pretty_print(actual)
+ "Expected xpath: #{xpath.inspect} not to match in:\n #{xml}"
end
def xml_pretty_print(raw_xml)
lib/saml/kit/assertion.rb
@@ -8,10 +8,14 @@ module Saml
class Assertion
include ActiveModel::Validations
include Translatable
+ include XmlParseable
+ extend Forwardable
XPATH = [
'/samlp:Response/saml:Assertion',
'/samlp:Response/saml:EncryptedAssertion'
].join('|')
+ def_delegators :conditions, :started_at, :expired_at, :audiences
+ def_delegators :attribute_statement, :attributes
validate :must_be_decryptable
validate :must_match_issuer, if: :decryptable?
@@ -20,15 +24,17 @@ module Saml
attr_reader :name
attr_accessor :occurred_at
- def initialize(node, configuration: Saml::Kit.configuration, private_keys: [])
+ def initialize(
+ node, configuration: Saml::Kit.configuration, private_keys: []
+ )
@name = 'Assertion'
- @node = node
+ @to_nokogiri = node
@configuration = configuration
@occurred_at = Time.current
@cannot_decrypt = false
@encrypted = false
- private_keys = (configuration.private_keys(use: :encryption) + private_keys).uniq
- decrypt(::Xml::Kit::Decryption.new(private_keys: private_keys))
+ keys = configuration.private_keys(use: :encryption) + private_keys
+ decrypt(::Xml::Kit::Decryption.new(private_keys: keys.uniq))
end
def issuer
@@ -39,6 +45,10 @@ module Saml
at_xpath('./saml:Subject/saml:NameID').try(:text)
end
+ def name_id_format
+ at_xpath('./saml:Subject/saml:NameID').attribute('Format').try(:value)
+ end
+
def signed?
signature.present?
end
@@ -56,23 +66,13 @@ module Saml
now > drifted_started_at && !expired?(now)
end
- def attributes
- @attributes ||= search('./saml:AttributeStatement/saml:Attribute').inject({}) do |memo, item|
- memo[item.attribute('Name').value] = item.at_xpath('./saml:AttributeValue', Saml::Kit::Document::NAMESPACES).try(:text)
- memo
- end.with_indifferent_access
+ def attribute_statement
+ @attribute_statement ||=
+ AttributeStatement.new(search('./saml:AttributeStatement'))
end
- def started_at
- parse_date(at_xpath('./saml:Conditions/@NotBefore').try(:value))
- end
-
- def expired_at
- parse_date(at_xpath('./saml:Conditions/@NotOnOrAfter').try(:value))
- end
-
- def audiences
- search('./saml:Conditions/saml:AudienceRestriction/saml:Audience').map(&:text)
+ def conditions
+ @conditions ||= Conditions.new(search('./saml:Conditions'))
end
def encrypted?
@@ -84,16 +84,8 @@ module Saml
!@cannot_decrypt
end
- def present?
- @node.present?
- end
-
- def to_xml(pretty: nil)
- pretty ? @node.to_xml(indent: 2) : to_s
- end
-
def to_s
- @node.to_s
+ @to_nokogiri.to_s
end
private
@@ -104,22 +96,14 @@ module Saml
encrypted_assertion = at_xpath('./xmlenc:EncryptedData')
@encrypted = encrypted_assertion.present?
return unless @encrypted
- @node = decryptor.decrypt_node(encrypted_assertion)
+ @to_nokogiri = decryptor.decrypt_node(encrypted_assertion)
rescue Xml::Kit::DecryptionError => error
@cannot_decrypt = true
Saml::Kit.logger.error(error)
end
- def parse_date(value)
- DateTime.parse(value)
- rescue StandardError => error
- Saml::Kit.logger.error(error)
- Time.at(0).to_datetime
- end
-
def must_match_issuer
- return if audiences.empty?
- return if audiences.include?(configuration.entity_id)
+ return if audiences.empty? || audiences.include?(configuration.entity_id)
errors[:audience] << error_message(:must_match_issuer)
end
@@ -130,7 +114,6 @@ module Saml
def must_have_valid_signature
return if !signed? || signature.valid?
-
signature.errors.each do |attribute, message|
errors.add(attribute, message)
end
@@ -139,15 +122,6 @@ module Saml
def must_be_decryptable
errors.add(:base, error_message(:cannot_decrypt)) unless decryptable?
end
-
- def at_xpath(xpath)
- return unless @node
- @node.at_xpath(xpath, Saml::Kit::Document::NAMESPACES)
- end
-
- def search(xpath)
- @node.search(xpath, Saml::Kit::Document::NAMESPACES)
- end
end
end
end
lib/saml/kit/attribute_statement.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Saml
+ module Kit
+ class AttributeStatement
+ include XmlParseable
+
+ attr_reader :content
+
+ def initialize(node)
+ @to_nokogiri = node
+ @content = node.to_s
+ end
+
+ def attributes
+ @attributes ||= search('./saml:Attribute').inject({}) do |memo, item|
+ namespace = Saml::Kit::Document::NAMESPACES
+ attribute = item.at_xpath('./saml:AttributeValue', namespace)
+ memo[item.attribute('Name').value] = attribute.try(:text)
+ memo
+ end.with_indifferent_access
+ end
+ end
+ end
+end
lib/saml/kit/authentication_request.rb
@@ -11,9 +11,17 @@ module Saml
# end
#
# <?xml version="1.0" encoding="UTF-8"?>
- # <samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_ca3a0e72-9530-41f1-9518-c53716de88b2" Version="2.0" IssueInstant="2017-12-19T16:27:44Z" Destination="http://hartmann.info" AssertionConsumerServiceURL="https://carroll.com/acs">
+ # <samlp:AuthnRequest
+ # xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
+ # xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+ # ID="_ca3a0e72-9530-41f1-9518-c53716de88b2"
+ # Version="2.0"
+ # IssueInstant="2017-12-19T16:27:44Z"
+ # Destination="http://hartmann.info"
+ # AssertionConsumerServiceURL="https://carroll.com/acs">
# <saml:Issuer>Day of the Dangerous Cousins</saml:Issuer>
- # <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
+ # <samlp:NameIDPolicy
+ # Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
# </samlp:AuthnRequest>
#
# Example:
@@ -25,13 +33,16 @@ module Saml
# Create an instance of an AuthnRequest document.
#
# @param xml [String] the raw xml.
- # @param configuration [Saml::Kit::Configuration] defaults to the global configuration.
+ # @param configuration [Saml::Kit::Configuration] defaults to the global
+ # configuration.
def initialize(xml, configuration: Saml::Kit.configuration)
super(xml, name: 'AuthnRequest', configuration: configuration)
end
# Extract the AssertionConsumerServiceURL from the AuthnRequest
- # <samlp:AuthnRequest AssertionConsumerServiceURL="https://carroll.com/acs"></samlp:AuthnRequest>
+ # <samlp:AuthnRequest
+ # AssertionConsumerServiceURL="https://carroll.com/acs">
+ # </samlp:AuthnRequest>
def assertion_consumer_service_url
at_xpath('./*/@AssertionConsumerServiceURL').try(:value)
end
@@ -42,23 +53,31 @@ module Saml
# Extract the NameIDPolicy from the AuthnRequest
# <samlp:AuthnRequest>
- # <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
+ # <samlp:NameIDPolicy
+ # Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
# </samlp:AuthnRequest>
def name_id_policy
at_xpath('./*/samlp:NameIDPolicy/@Format').try(:value)
end
# Generate a Response for a specific user.
- # @param user [Object] this is a custom user object that can be used for generating a nameid and assertion attributes.
- # @param binding [Symbol] the SAML binding to use `:http_post` or `:http_redirect`.
- # @param configuration [Saml::Kit::Configuration] the configuration to use to build the response.
- def response_for(user, binding:, relay_state: nil, configuration: Saml::Kit.configuration)
- response_binding = provider.assertion_consumer_service_for(binding: binding)
- response = Saml::Kit::Response.builder(user, self, configuration: configuration) do |builder|
- builder.embed_signature = provider.want_assertions_signed
- yield builder if block_given?
- end
- response_binding.serialize(response, relay_state: relay_state)
+ # @param user [Object] this is a custom user object that can be used for
+ # generating a nameid and assertion attributes.
+ # @param binding [Symbol] the SAML binding to use
+ # `:http_post` or `:http_redirect`.
+ # @param configuration [Saml::Kit::Configuration] the configuration to
+ # use to build the response.
+ def response_for(
+ user, binding:, relay_state: nil, configuration: Saml::Kit.configuration
+ )
+ response =
+ Response.builder(user, self, configuration: configuration) do |x|
+ x.embed_signature = provider.want_assertions_signed
+ yield x if block_given?
+ end
+ provider
+ .assertion_consumer_service_for(binding: binding)
+ .serialize(response, relay_state: relay_state)
end
end
end
lib/saml/kit/bindings.rb
@@ -11,9 +11,10 @@ module Saml
# the different SAML bindings that are
# supported by this gem.
module Bindings
- HTTP_ARTIFACT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact'.freeze
- HTTP_POST = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'.freeze
- HTTP_REDIRECT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'.freeze
+ BINDINGS_2_0 = 'urn:oasis:names:tc:SAML:2.0:bindings'.freeze
+ HTTP_ARTIFACT = "#{BINDINGS_2_0}:HTTP-Artifact".freeze
+ HTTP_POST = "#{BINDINGS_2_0}:HTTP-POST".freeze
+ HTTP_REDIRECT = "#{BINDINGS_2_0}:HTTP-Redirect".freeze
ALL = {
http_post: HTTP_POST,
http_redirect: HTTP_REDIRECT,
lib/saml/kit/builders.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-require 'saml/kit/xml_templatable'
require 'saml/kit/builders/assertion'
require 'saml/kit/builders/authentication_request'
require 'saml/kit/builders/encrypted_assertion'
lib/saml/kit/composite_metadata.rb
@@ -9,6 +9,7 @@ module Saml
# and SPSSODescriptor element.
class CompositeMetadata < Metadata # :nodoc:
include Enumerable
+
attr_reader :service_provider, :identity_provider
def initialize(xml)
@@ -19,8 +20,22 @@ module Saml
]
end
+ def organization
+ find { |x| x.organization.present? }.try(:organization)
+ end
+
+ def organization_name
+ organization.name
+ end
+
+ def organization_url
+ organization.url
+ end
+
def services(type)
- xpath = map { |xxx| "//md:EntityDescriptor/md:#{xxx.name}/md:#{type}" }.join('|')
+ xpath = map do |x|
+ "//md:EntityDescriptor/md:#{x.name}/md:#{type}"
+ end.join('|')
search(xpath).map do |item|
binding = item.attribute('Binding').value
location = item.attribute('Location').value
@@ -37,7 +52,7 @@ module Saml
end
def method_missing(name, *args)
- if (target = find { |xxx| xxx.respond_to?(name) })
+ if (target = find { |x| x.respond_to?(name) })
target.public_send(name, *args)
else
super
@@ -45,7 +60,7 @@ module Saml
end
def respond_to_missing?(method, *)
- find { |xxx| xxx.respond_to?(method) }
+ find { |x| x.respond_to?(method) }
end
end
end
lib/saml/kit/conditions.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Saml
+ module Kit
+ class Conditions
+ include XmlParseable
+
+ attr_reader :content
+
+ def initialize(node)
+ @to_nokogiri = node
+ @content = node.to_s
+ end
+
+ def started_at
+ parse_iso8601(at_xpath('./@NotBefore').try(:value))
+ end
+
+ def expired_at
+ parse_iso8601(at_xpath('./@NotOnOrAfter').try(:value))
+ end
+
+ def audiences
+ search('./saml:AudienceRestriction/saml:Audience').map(&:text)
+ end
+
+ private
+
+ def parse_iso8601(value)
+ DateTime.parse(value)
+ rescue StandardError => error
+ Saml::Kit.logger.error(error)
+ Time.at(0).to_datetime
+ end
+ end
+ end
+end
lib/saml/kit/configuration.rb
@@ -2,7 +2,8 @@
module Saml
module Kit
- # This class represents the main configuration that is use for generating SAML documents.
+ # This class represents the main configuration that is use for generating
+ # SAML documents.
#
# Saml::Kit::Configuration.new do |config|
# config.entity_id = "com:saml:kit"
@@ -19,17 +20,25 @@ module Saml
# Saml::Kit.configure do |configuration|
# configuration.entity_id = "https://www.example.com/saml/metadata"
# configuration.generate_key_pair_for(use: :signing)
- # configuration.add_key_pair(ENV["X509_CERTIFICATE"], ENV["PRIVATE_KEY"], passphrase: ENV['PRIVATE_KEY_PASSPHRASE'], use: :encryption)
+ # configuration.add_key_pair(
+ # ENV["X509_CERTIFICATE"],
+ # ENV["PRIVATE_KEY"],
+ # passphrase: ENV['PRIVATE_KEY_PASSPHRASE'],
+ # use: :encryption
+ # )
# end
class Configuration
USES = %i[signing encryption].freeze
# The issuer to use in requests or responses from this entity to use.
attr_accessor :entity_id
- # The signature method to use when generating signatures (See {Saml::Kit::Builders::XmlSignature::SIGNATURE_METHODS})
+ # The signature method to use when generating signatures
+ # (See {Saml::Kit::Builders::XmlSignature::SIGNATURE_METHODS})
attr_accessor :signature_method
- # The digest method to use when generating signatures (See {Saml::Kit::Builders::XmlSignature::DIGEST_METHODS})
+ # The digest method to use when generating signatures
+ # (See {Saml::Kit::Builders::XmlSignature::DIGEST_METHODS})
attr_accessor :digest_method
- # The metadata registry to use for searching for metadata associated with an issuer.
+ # The metadata registry to use for searching for metadata associated
+ # with an issuer.
attr_accessor :registry
# The session timeout to use when generating an Assertion.
attr_accessor :session_timeout
@@ -57,7 +66,11 @@ module Saml
# @param use [Symbol] the type of key pair, `:signing` or `:encryption`
def add_key_pair(certificate, private_key, passphrase: nil, use: :signing)
ensure_proper_use(use)
- @key_pairs.push(::Xml::Kit::KeyPair.new(certificate, private_key, passphrase, use.to_sym))
+ @key_pairs.push(
+ ::Xml::Kit::KeyPair.new(
+ certificate, private_key, passphrase, use.to_sym
+ )
+ )
end
# Generates a unique key pair that can be used for signing or encryption.
@@ -66,27 +79,32 @@ module Saml
# @param passphrase [String] the private key passphrase to use.
def generate_key_pair_for(use:, passphrase: SecureRandom.uuid)
ensure_proper_use(use)
- certificate, private_key = ::Xml::Kit::SelfSignedCertificate.new.create(passphrase: passphrase)
+ certificate, private_key = ::Xml::Kit::SelfSignedCertificate.new.create(
+ passphrase: passphrase
+ )
add_key_pair(certificate, private_key, passphrase: passphrase, use: use)
end
# Return each key pair for a specific use.
#
- # @param use [Symbol] the type of key pair to return `nil`, `:signing` or `:encryption`
+ # @param use [Symbol] the type of key pair to return
+ # `nil`, `:signing` or `:encryption`
def key_pairs(use: nil)
use.present? ? @key_pairs.find_all { |xxx| xxx.for?(use) } : @key_pairs
end
# Return each certificate for a specific use.
#
- # @param use [Symbol] the type of key pair to return `nil`, `:signing` or `:encryption`
+ # @param use [Symbol] the type of key pair to return
+ # `nil`, `:signing` or `:encryption`
def certificates(use: nil)
key_pairs(use: use).flat_map(&:certificate)
end
# Return each private for a specific use.
#
- # @param use [Symbol] the type of key pair to return `nil`, `:signing` or `:encryption`
+ # @param use [Symbol] the type of key pair to return
+ # `nil`, `:signing` or `:encryption`
def private_keys(use: nil)
key_pairs(use: use).flat_map(&:private_key)
end
lib/saml/kit/default_registry.rb
@@ -2,8 +2,10 @@
module Saml
module Kit
- # The default metadata registry is used to fetch the metadata associated with an issuer or entity id.
- # The metadata associated with an issuer is used to verify trust for any SAML documents that are received.
+ # The default metadata registry is used to fetch the metadata associated
+ # with an issuer or entity id.
+ # The metadata associated with an issuer is used to verify trust for any
+ # SAML documents that are received.
#
# You can replace the default registry with your own at startup.
#
@@ -41,12 +43,14 @@ module Saml
#
# @param metadata [Saml::Kit::Metadata] the metadata to register.
def register(metadata)
+ ensure_valid_metadata(metadata)
Saml::Kit.logger.debug(metadata.to_xml(pretty: true))
@items[metadata.entity_id] = metadata
end
# Register metadata via a remote URL.
- # This will attempt to connect to the remove URL to download the metadata and register it in the registry.
+ # This will attempt to connect to the remove URL to download the
+ # metadata and register it in the registry.
#
# @param url [String] the url to download the metadata from.
# @param verify_ssl [Boolean] enable/disable SSL peer verification.
@@ -57,7 +61,8 @@ module Saml
# Returns the metadata document associated with an issuer or entityID.
#
- # @param entity_id [String] the unique entityID/Issuer associated with metadata.
+ # @param entity_id [String] unique entityID/Issuer associated with
+ # metadata.
def metadata_for(entity_id)
@items[entity_id]
end
@@ -69,6 +74,16 @@ module Saml
end
end
+ private
+
+ def ensure_valid_metadata(metadata)
+ error = ArgumentError.new('Cannot register invalid metadata')
+ raise error if
+ metadata.nil? ||
+ !metadata.respond_to?(:entity_id) ||
+ metadata.invalid?
+ end
+
# This class is responsible for
# making HTTP requests to fetch metadata
# from remote locations.
lib/saml/kit/document.rb
@@ -5,19 +5,12 @@ module Saml
# This class is a base class for SAML documents.
class Document
include ActiveModel::Validations
- include XsdValidatable
+ include Buildable
include Translatable
include Trustable
- include Buildable
- PROTOCOL_XSD = File.expand_path('./xsd/saml-schema-protocol-2.0.xsd', File.dirname(__FILE__)).freeze
- NAMESPACES = {
- "NameFormat": ::Saml::Kit::Namespaces::ATTR_SPLAT,
- "ds": ::Xml::Kit::Namespaces::XMLDSIG,
- "md": ::Saml::Kit::Namespaces::METADATA,
- "saml": ::Saml::Kit::Namespaces::ASSERTION,
- "samlp": ::Saml::Kit::Namespaces::PROTOCOL,
- 'xmlenc' => ::Xml::Kit::Namespaces::XMLENC,
- }.freeze
+ include XmlParseable
+ include XsdValidatable
+
attr_accessor :registry
attr_reader :name
validates_presence_of :content
@@ -58,44 +51,13 @@ module Saml
Time.parse(at_xpath('./*/@IssueInstant').try(:value))
end
- # Returns the SAML document returned as a Hash.
- def to_h
- @to_h ||= Hash.from_xml(content) || {}
- end
-
- # Returns the SAML document as an XML string.
- #
- # @param pretty [Boolean] formats the xml or returns the raw xml.
- def to_xml(pretty: nil)
- pretty ? to_nokogiri.to_xml(indent: 2) : to_s
- end
-
- # Returns the SAML document as an XHTML string.
- # This is useful for rendering in a web page.
- def to_xhtml
- Nokogiri::XML(to_xml, &:noblanks).to_xhtml
- end
-
- # @!visibility private
- def to_nokogiri
- @to_nokogiri ||= Nokogiri::XML(to_s)
- end
-
- # @!visibility private
- def at_xpath(xpath)
- to_nokogiri.at_xpath(xpath, NAMESPACES)
- end
-
- # @!visibility private
- def search(xpath)
- to_nokogiri.search(xpath, NAMESPACES)
- end
-
- def to_s
- content
- end
-
class << self
+ CONSTRUCTORS = {
+ 'AuthnRequest' => -> { Saml::Kit::AuthenticationRequest },
+ 'LogoutRequest' => -> { Saml::Kit::LogoutRequest },
+ 'LogoutResponse' => -> { Saml::Kit::LogoutResponse },
+ 'Response' => -> { Saml::Kit::Response },
+ }.freeze
XPATH = [
'/samlp:AuthnRequest',
'/samlp:LogoutRequest',
@@ -106,14 +68,12 @@ module Saml
# Returns the raw xml as a Saml::Kit SAML document.
#
# @param xml [String] the raw xml string.
- # @param configuration [Saml::Kit::Configuration] the configuration to use for unpacking the document.
+ # @param configuration [Saml::Kit::Configuration] configuration to use
+ # for unpacking the document.
def to_saml_document(xml, configuration: Saml::Kit.configuration)
- constructor = {
- 'AuthnRequest' => Saml::Kit::AuthenticationRequest,
- 'LogoutRequest' => Saml::Kit::LogoutRequest,
- 'LogoutResponse' => Saml::Kit::LogoutResponse,
- 'Response' => Saml::Kit::Response,
- }[Nokogiri::XML(xml).at_xpath(XPATH, "samlp": ::Saml::Kit::Namespaces::PROTOCOL).name] || InvalidDocument
+ namespaces = { samlp: Namespaces::PROTOCOL }
+ element = Nokogiri::XML(xml).at_xpath(XPATH, namespaces)
+ constructor = CONSTRUCTORS[element.name].try(:call) || InvalidDocument
constructor.new(xml, configuration: configuration)
rescue StandardError => error
Saml::Kit.logger.error(error)
@@ -122,18 +82,12 @@ module Saml
# @!visibility private
def builder_class # :nodoc:
- case name
- when Saml::Kit::Response.to_s
- Saml::Kit::Builders::Response
- when Saml::Kit::LogoutResponse.to_s
- Saml::Kit::Builders::LogoutResponse
- when Saml::Kit::AuthenticationRequest.to_s
- Saml::Kit::Builders::AuthenticationRequest
- when Saml::Kit::LogoutRequest.to_s
- Saml::Kit::Builders::LogoutRequest
- else
- raise ArgumentError, "Unknown SAML Document #{name}"
- end
+ {
+ Response.to_s => Saml::Kit::Builders::Response,
+ LogoutResponse.to_s => Saml::Kit::Builders::LogoutResponse,
+ AuthenticationRequest.to_s => Saml::Kit::Builders::AuthenticationRequest,
+ LogoutRequest.to_s => Saml::Kit::Builders::LogoutRequest,
+ }[name] || (raise ArgumentError, "Unknown SAML Document #{name}")
end
end
lib/saml/kit/identity_provider_metadata.rb
@@ -2,16 +2,31 @@
module Saml
module Kit
- # This class is used to parse the IDPSSODescriptor from a SAML metadata document.
+ # This class parses the IDPSSODescriptor from a SAML metadata document.
#
# raw_xml = <<-XML
# <?xml version="1.0" encoding="UTF-8"?>
- # <EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_cfa24e2f-0ec0-4ee3-abb8-b2fcfe394c1c" entityID="">
- # <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
- # <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/logout"/>
- # <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
- # <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/login"/>
- # <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://www.example.com/login"/>
+ # <EntityDescriptor
+ # xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+ # xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+ # xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+ # ID="_cfa24e2f-0ec0-4ee3-abb8-b2fcfe394c1c"
+ # entityID="my-entity-id">
+ # <IDPSSODescriptor
+ # WantAuthnRequestsSigned="true"
+ # protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+ # <SingleLogoutService
+ # Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ # Location="https://www.example.com/logout" />
+ # <NameIDFormat>
+ # urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+ # </NameIDFormat>
+ # <SingleSignOnService
+ # Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ # Location="https://www.example.com/login" />
+ # <SingleSignOnService
+ # Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ # Location="https://www.example.com/login" />
# <saml:Attribute Name="id"/>
# </IDPSSODescriptor>
# </EntityDescriptor>
@@ -70,14 +85,18 @@ module Saml
# Creates a AuthnRequest document for the specified binding.
#
# @param binding [Symbol] `:http_post` or `:http_redirect`.
- # @param relay_state [Object] The RelayState to include the returned SAML params.
- # @param configuration [Saml::Kit::Configuration] the configuration to use for generating the request.
- # @return [Array] The url and saml params encoded using the rules for the specified binding.
- def login_request_for(binding:, relay_state: nil, configuration: Saml::Kit.configuration)
- builder = Saml::Kit::AuthenticationRequest.builder(configuration: configuration) do |xxx|
- xxx.embed_signature = want_authn_requests_signed
- yield xxx if block_given?
- end
+ # @param relay_state [Object] RelayState to include the returned params.
+ # @param configuration [Saml::Kit::Configuration] the configuration to
+ # use for generating the request.
+ # @return [Array] Url and params encoded using rules for binding.
+ def login_request_for(
+ binding:, relay_state: nil, configuration: Saml::Kit.configuration
+ )
+ builder =
+ AuthenticationRequest.builder(configuration: configuration) do |x|
+ x.embed_signature = want_authn_requests_signed
+ yield x if block_given?
+ end
request_binding = single_sign_on_service_for(binding: binding)
request_binding.serialize(builder, relay_state: relay_state)
end
lib/saml/kit/invalid_document.rb
@@ -10,7 +10,7 @@ module Saml
model.errors[:base] << model.error_message(:invalid)
end
- def initialize(xml, configuration: nil)
+ def initialize(xml, *)
super(xml, name: 'InvalidDocument')
end
lib/saml/kit/logout_request.rb
@@ -16,7 +16,8 @@ module Saml
#
# See {Saml::Kit::Builders::LogoutRequest} for a list of available settings.
#
- # This class can also be used to generate the correspondong LogoutResponse for a LogoutRequest.
+ # This class can also be used to generate the correspondong LogoutResponse
+ # for a LogoutRequest.
#
# document = Saml::Kit::LogoutRequest.new(raw_xml)
# url, saml_params = document.response_for(binding: :http_post)
@@ -31,7 +32,7 @@ module Saml
# A new instance of LogoutRequest
#
# @param xml [String] The raw xml string.
- # @param configuration [Saml::Kit::Configuration] the configuration to use.
+ # @param configuration [Saml::Kit::Configuration] configuration to use.
def initialize(xml, configuration: Saml::Kit.configuration)
super(xml, name: 'LogoutRequest', configuration: configuration)
end
@@ -45,11 +46,15 @@ module Saml
at_xpath('./*/saml:NameID/@Format').try(:value)
end
- # Generates a Serialized LogoutResponse using the encoding rules for the specified binding.
+ # Generates a Serialized LogoutResponse using the encoding rules for
+ # the specified binding.
#
- # @param binding [Symbol] The binding to use `:http_redirect` or `:http_post`.
- # @param relay_state [Object] The RelayState to include in the RelayState param.
- # @return [Array] Returns an array with a url and Hash of parameters to return to the requestor.
+ # @param binding [Symbol] The binding to use `:http_redirect` or
+ # `:http_post`.
+ # @param relay_state [Object] The RelayState to include in the
+ # RelayState param.
+ # @return [Array] Returns an array with a url and Hash of parameters to
+ # return to the requestor.
def response_for(binding:, relay_state: nil)
builder = Saml::Kit::LogoutResponse.builder(self) do |xxx|
yield xxx if block_given?
lib/saml/kit/logout_response.rb
@@ -10,7 +10,9 @@ module Saml
class LogoutResponse < Document
include Respondable
- def initialize(xml, request_id: nil, configuration: Saml::Kit.configuration)
+ def initialize(
+ xml, request_id: nil, configuration: Saml::Kit.configuration
+ )
@request_id = request_id
super(xml, name: 'LogoutResponse', configuration: configuration)
end
lib/saml/kit/metadata.rb
@@ -8,46 +8,43 @@ module Saml
#
# It can also be used to generate a new metadata string.
#
- # metadata = Saml::Kit::Metadata.build do |builder|
- # builder.entity_id = "my-issuer"
- # builder.build_service_provider do |x|
- # x.add_assertion_consumer_service(assertions_url, binding: :http_post)
- # x.add_single_logout_service(logout_url, binding: :http_post)
- # end
- # builder.build_identity_provider do |x|
- # x.add_single_sign_on_service(login_url, binding: :http_redirect)
- # x.add_single_logout_service(logout_url, binding: :http_post)
- # end
- # end
- # puts metadata.to_xml(pretty: true)
+ # metadata = Saml::Kit::Metadata.build do |builder|
+ # builder.entity_id = "my-issuer"
+ # builder.build_service_provider do |x|
+ # x.add_assertion_consumer_service(assertions_url, binding: :http_post)
+ # x.add_single_logout_service(logout_url, binding: :http_post)
+ # end
+ # builder.build_identity_provider do |x|
+ # x.add_single_sign_on_service(login_url, binding: :http_redirect)
+ # x.add_single_logout_service(logout_url, binding: :http_post)
+ # end
+ # end
+ # puts metadata.to_xml(pretty: true)
#
- # See {Saml::Kit::Builders::ServiceProviderMetadata} and {Saml::Kit::Builders::IdentityProviderMetadata}
+ # See {Saml::Kit::Builders::ServiceProviderMetadata} and
+ # {Saml::Kit::Builders::IdentityProviderMetadata}
# for a list of options that can be specified.
# {include:file:spec/examples/metadata_spec.rb}
class Metadata
include ActiveModel::Validations
- include XsdValidatable
- include Translatable
include Buildable
- METADATA_XSD = File.expand_path('./xsd/saml-schema-metadata-2.0.xsd', File.dirname(__FILE__)).freeze
- NAMESPACES = {
- NameFormat: Namespaces::ATTR_SPLAT,
- ds: ::Xml::Kit::Namespaces::XMLDSIG,
- md: Namespaces::METADATA,
- saml: Namespaces::ASSERTION,
- samlp: Namespaces::PROTOCOL,
- }.freeze
+ include Translatable
+ include XmlParseable
+ include XsdValidatable
+ extend Forwardable
+
+ def_delegator :organization, :organization_name, :organization_url
validates_presence_of :metadata
validate :must_contain_descriptor
validate :must_match_xsd
validate :must_have_valid_signature
- attr_reader :name
+ attr_reader :name, :content
- def initialize(name, xml)
+ def initialize(name, content)
@name = name
- @xml = xml
+ @content = content
end
# Returns the /EntityDescriptor/@entityID
@@ -60,14 +57,8 @@ module Saml
search("/md:EntityDescriptor/md:#{name}/md:NameIDFormat").map(&:text)
end
- # Returns the Organization Name
- def organization_name
- at_xpath('/md:EntityDescriptor/md:Organization/md:OrganizationName').try(:text)
- end
-
- # Returns the Organization URL
- def organization_url
- at_xpath('/md:EntityDescriptor/md:Organization/md:OrganizationURL').try(:text)
+ def organization(xpath = '/md:EntityDescriptor/md:Organization')
+ @organization ||= Organization.new(at_xpath(xpath))
end
# Returns the Company
@@ -76,10 +67,15 @@ module Saml
end
# Returns each of the X509 certificates.
- def certificates
- @certificates ||= search("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item|
- cert = item.at_xpath('./ds:KeyInfo/ds:X509Data/ds:X509Certificate', 'ds' => ::Xml::Kit::Namespaces::XMLDSIG).try(:text)
- ::Xml::Kit::Certificate.new(cert, use: item.attribute('use').try(:value))
+ def certificates(
+ xpath = "/md:EntityDescriptor/md:#{name}/md:KeyDescriptor"
+ )
+ @certificates ||= search(xpath).map do |item|
+ xpath = './ds:KeyInfo/ds:X509Data/ds:X509Certificate'
+ namespaces = { 'ds' => ::Xml::Kit::Namespaces::XMLDSIG }
+ cert = item.at_xpath(xpath, namespaces).try(:text)
+ use_attribute = item.attribute('use')
+ ::Xml::Kit::Certificate.new(cert, use: use_attribute.try(:value))
end
end
@@ -95,7 +91,8 @@ module Saml
# Returns each of the service endpoints supported by this metadata.
#
- # @param type [String] the type of service. .E.g. `AssertionConsumerServiceURL`
+ # @param type [String] the type of service.
+ # .E.g. `AssertionConsumerServiceURL`
def services(type)
search("/md:EntityDescriptor/md:#{name}/md:#{type}").map do |item|
binding = item.attribute('Binding').value
@@ -107,10 +104,12 @@ module Saml
# Returns a specifing service binding.
#
# @param binding [Symbol] can be `:http_post` or `:http_redirect`.
- # @param type [Symbol] can be on the service element like `AssertionConsumerServiceURL`, `SingleSignOnService` or `SingleLogoutService`.
+ # @param type [Symbol] can be on the service element like
+ # `AssertionConsumerServiceURL`, `SingleSignOnService` or
+ # `SingleLogoutService`.
def service_for(binding:, type:)
binding = Saml::Kit::Bindings.binding_for(binding)
- services(type).find { |xxx| xxx.binding?(binding) }
+ services(type).find { |x| x.binding?(binding) }
end
# Returns each of the SingleLogoutService bindings
@@ -127,99 +126,56 @@ module Saml
# Creates a serialized LogoutRequest.
#
- # @param user [Object] a user object that responds to `name_id_for` and `assertion_attributes_for`.
+ # @param user [Object] a user object that responds to `name_id_for` and
+ # `assertion_attributes_for`.
# @param binding [Symbol] can be `:http_post` or `:http_redirect`.
# @param relay_state [String] the relay state to have echo'd back.
- # @return [Array] Returns an array with a url and Hash of parameters to send to the other party.
+ # @return [Array] Returns an array with a url and Hash of parameters to
+ # send to the other party.
def logout_request_for(user, binding: :http_post, relay_state: nil)
- builder = Saml::Kit::LogoutRequest.builder(user) { |xxx| yield xxx if block_given? }
+ builder = LogoutRequest.builder(user) { |x| yield x if block_given? }
request_binding = single_logout_service_for(binding: binding)
request_binding.serialize(builder, relay_state: relay_state)
end
# Returns the certificate that matches the fingerprint
#
- # @param fingerprint [Saml::Kit::Fingerprint] the fingerprint to search for.
- # @param use [Symbol] the type of certificates to look at. Can be `:signing` or `:encryption`.
- # @return [Xml::Kit::Certificate] returns the matching `{Xml::Kit::Certificate}`
+ # @param fingerprint [Saml::Kit::Fingerprint] the fingerprint to search.
+ # @param use [Symbol] the type of certificates to look at.
+ # Can be `:signing` or `:encryption`.
+ # @return [Xml::Kit::Certificate] returns the matching
+ # `{Xml::Kit::Certificate}`
def matches?(fingerprint, use: :signing)
- certificates.find { |xxx| xxx.for?(use) && xxx.fingerprint == fingerprint }
- end
-
- # Returns the XML document converted to a Hash.
- def to_h
- @to_h ||= Hash.from_xml(to_xml)
- end
-
- # Returns the XML document as a String.
- #
- # @param pretty [Boolean] true to return a human friendly version of the XML.
- def to_xml(pretty: nil)
- pretty ? to_nokogiri.to_xml(indent: 2) : to_s
- end
-
- # Returns the XML document as a [String].
- def to_s
- @xml
+ certificates.find { |x| x.for?(use) && x.fingerprint == fingerprint }
end
# Verifies the signature and data using the signing certificates.
#
- # @param algorithm [OpenSSL::Digest] the digest algorithm to use. E.g. `OpenSSL::Digest::SHA256`
+ # @param algorithm [OpenSSL::Digest] the digest algorithm to use.
+ # E.g. `OpenSSL::Digest::SHA256`
# @param signature [String] the signature to verify
# @param data [String] the data that is used to produce the signature.
- # @return [Xml::Kit::Certificate] the certificate that was used to produce the signature.
+ # @return [Xml::Kit::Certificate] the certificate that was used to
+ # produce the signature.
def verify(algorithm, signature, data)
signing_certificates.find do |certificate|
certificate.public_key.verify(algorithm, signature, data)
end
end
- def signature
- @signature ||= Signature.new(at_xpath('/md:EntityDescriptor/ds:Signature'))
- end
-
- class << self
- # Creates a `{Saml::Kit::Metadata}` object from a raw XML [String].
- #
- # @param content [String] the raw metadata XML.
- # @return [Saml::Kit::Metadata] the metadata document or subclass.
- def from(content)
- document = Nokogiri::XML(content)
- return unless document.at_xpath('/md:EntityDescriptor', NAMESPACES)
- sp = document.at_xpath('/md:EntityDescriptor/md:SPSSODescriptor', NAMESPACES)
- idp = document.at_xpath('/md:EntityDescriptor/md:IDPSSODescriptor', NAMESPACES)
- if sp && idp
- Saml::Kit::CompositeMetadata.new(content)
- elsif sp
- Saml::Kit::ServiceProviderMetadata.new(content)
- elsif idp
- Saml::Kit::IdentityProviderMetadata.new(content)
- end
- end
-
- # @!visibility private
- def builder_class
- Saml::Kit::Builders::Metadata
- end
+ def signature(xpath = '/md:EntityDescriptor/ds:Signature')
+ @signature ||= Signature.new(at_xpath(xpath))
end
- private
-
- attr_reader :xml
-
- # @!visibility private
- def to_nokogiri
- @to_nokogiri ||= Nokogiri::XML(xml)
+ def self.from(content)
+ Saml::Kit::Parser.new.metadata_from(content)
end
- def at_xpath(xpath)
- to_nokogiri.at_xpath(xpath, NAMESPACES)
+ def self.builder_class
+ Saml::Kit::Builders::Metadata
end
- def search(xpath)
- to_nokogiri.search(xpath, NAMESPACES)
- end
+ private
def metadata
at_xpath("/md:EntityDescriptor/md:#{name}").present?
@@ -234,9 +190,7 @@ module Saml
end
def must_have_valid_signature
- return unless signature.present?
-
- signature.valid?
+ return if !signature.present? || signature.valid?
signature.errors.each do |attribute, error|
errors[attribute] << error
end
lib/saml/kit/namespaces.rb
@@ -18,7 +18,8 @@ module Saml
INVALID_NAME_ID_POLICY = "#{STATUS}:InvalidNameIDPolicy".freeze
METADATA = "#{SAML_2_0}:metadata".freeze
PASSWORD = "#{SAML_2_0}:ac:classes:Password".freeze
- PASSWORD_PROTECTED = "#{SAML_2_0}:ac:classes:PasswordProtectedTransport".freeze
+ PASSWORD_PROTECTED =
+ "#{SAML_2_0}:ac:classes:PasswordProtectedTransport".freeze
PERSISTENT = "#{NAME_ID_FORMAT_2_0}:persistent".freeze
PROTOCOL = "#{SAML_2_0}:protocol".freeze
REQUESTER_ERROR = "#{STATUS}:Requester".freeze
lib/saml/kit/organization.rb
@@ -0,0 +1,36 @@
+module Saml
+ module Kit
+ class Organization
+ include XmlParseable
+
+ attr_reader :content
+
+ def initialize(node)
+ @to_nokogiri = node
+ @content = node.to_s
+ end
+
+ # Returns the Organization Name
+ def name
+ at_xpath('./md:OrganizationName').try(:text)
+ end
+
+ # Returns the Organization URL
+ def url
+ at_xpath('./md:OrganizationURL').try(:text)
+ end
+
+ # @deprecated
+ def organization_name
+ Saml::Kit.deprecate('`organization_name` is deprecated. Use `organization.name`')
+ name
+ end
+
+ # @deprecated
+ def organization_url
+ Saml::Kit.deprecate('`organization_url` is deprecated. Use `organization.url`')
+ url
+ end
+ end
+ end
+end
lib/saml/kit/parser.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Saml
+ module Kit
+ class Parser
+ # Creates a `{Saml::Kit::Metadata}` object from a raw XML [String].
+ #
+ # @param content [String] the raw metadata XML.
+ # @return [Saml::Kit::Metadata] the metadata document or subclass.
+ def metadata_from(content)
+ document = Nokogiri::XML(content)
+ return unless document.at_xpath('/md:EntityDescriptor', XmlParseable::NAMESPACES)
+
+ xpath = '/md:EntityDescriptor/md:SPSSODescriptor'
+ sp = document.at_xpath(xpath, XmlParseable::NAMESPACES)
+ xpath = '/md:EntityDescriptor/md:IDPSSODescriptor'
+ idp = document.at_xpath(xpath, XmlParseable::NAMESPACES)
+ if sp && idp
+ Saml::Kit::CompositeMetadata.new(content)
+ elsif sp
+ Saml::Kit::ServiceProviderMetadata.new(content)
+ elsif idp
+ Saml::Kit::IdentityProviderMetadata.new(content)
+ end
+ end
+ end
+ end
+end
lib/saml/kit/response.rb
@@ -14,7 +14,11 @@ module Saml
validate :must_be_valid_assertion
validate :must_contain_single_assertion
- def initialize(xml, request_id: nil, configuration: Saml::Kit.configuration)
+ def initialize(
+ xml,
+ request_id: nil,
+ configuration: Saml::Kit.configuration
+ )
@request_id = request_id
super(xml, name: 'Response', configuration: configuration)
end
@@ -26,7 +30,11 @@ module Saml
if node.nil?
Saml::Kit::NullAssertion.new
else
- Saml::Kit::Assertion.new(node, configuration: @configuration, private_keys: private_keys)
+ Saml::Kit::Assertion.new(
+ node,
+ configuration: @configuration,
+ private_keys: private_keys
+ )
end
end
end
lib/saml/kit/service_provider_metadata.rb
@@ -25,7 +25,8 @@ module Saml
# Returns true when the metadata demands that Assertions must be signed.
def want_assertions_signed
- attribute = at_xpath("/md:EntityDescriptor/md:#{name}").attribute('WantAssertionsSigned')
+ element = at_xpath("/md:EntityDescriptor/md:#{name}")
+ attribute = element.attribute('WantAssertionsSigned')
return true if attribute.nil?
attribute.text.casecmp('true').zero?
end
lib/saml/kit/signature.rb
@@ -21,12 +21,14 @@ module Saml
# Returns the embedded X509 Certificate
def certificate
- value = at_xpath('./ds:KeyInfo/ds:X509Data/ds:X509Certificate').try(:text)
+ xpath = './ds:KeyInfo/ds:X509Data/ds:X509Certificate'
+ value = at_xpath(xpath).try(:text)
return if value.nil?
::Xml::Kit::Certificate.new(value, use: :signing)
end
- # Returns true when the fingerprint of the certificate matches one of the certificates registered in the metadata.
+ # Returns true when the fingerprint of the certificate matches one of
+ # the certificates registered in the metadata.
def trusted?(metadata)
return false if metadata.nil?
metadata.matches?(certificate.fingerprint, use: :signing).present?
@@ -44,7 +46,8 @@ module Saml
end
def digest_method
- at_xpath('./ds:SignedInfo/ds:Reference/ds:DigestMethod/@Algorithm').try(:value)
+ xpath = './ds:SignedInfo/ds:Reference/ds:DigestMethod/@Algorithm'
+ at_xpath(xpath).try(:value)
end
def signature_value
@@ -56,11 +59,20 @@ module Saml
end
def canonicalization_method
- at_xpath('./ds:SignedInfo/ds:CanonicalizationMethod/@Algorithm').try(:value)
+ xpath = './ds:SignedInfo/ds:CanonicalizationMethod/@Algorithm'
+ at_xpath(xpath).try(:value)
end
def transforms
- node.search('./ds:SignedInfo/ds:Reference/ds:Transforms/ds:Transform/@Algorithm', Saml::Kit::Document::NAMESPACES).try(:map, &:value)
+ xpath = xpath_for([
+ '.',
+ 'ds:SignedInfo',
+ 'ds:Reference',
+ 'ds:Transforms',
+ 'ds:Transform',
+ '@Algorithm',
+ ])
+ node.search(xpath, Saml::Kit::Document::NAMESPACES).try(:map, &:value)
end
# Returns the XML Hash.
@@ -115,6 +127,10 @@ module Saml
def dsignature
@dsignature ||= Xmldsig::Signature.new(node, 'ID=$uri or @Id')
end
+
+ def xpath_for(segments)
+ segments.join('/')
+ end
end
end
end
lib/saml/kit/version.rb
@@ -2,6 +2,6 @@
module Saml
module Kit
- VERSION = '1.0.15'.freeze
+ VERSION = '1.0.16'.freeze
end
end
lib/saml/kit.rb
@@ -17,26 +17,33 @@ require 'securerandom'
require 'uri'
require 'xml/kit'
-require 'saml/kit/buildable'
+require 'saml/kit/concerns/buildable'
+require 'saml/kit/concerns/requestable'
+require 'saml/kit/concerns/respondable'
+require 'saml/kit/concerns/serializable'
+require 'saml/kit/concerns/translatable'
+require 'saml/kit/concerns/trustable'
+require 'saml/kit/concerns/xml_parseable'
+require 'saml/kit/concerns/xml_templatable'
+require 'saml/kit/concerns/xsd_validatable'
+
require 'saml/kit/builders'
require 'saml/kit/namespaces'
-require 'saml/kit/serializable'
-require 'saml/kit/xsd_validatable'
-require 'saml/kit/respondable'
-require 'saml/kit/requestable'
-require 'saml/kit/trustable'
-require 'saml/kit/translatable'
require 'saml/kit/document'
require 'saml/kit/assertion'
+require 'saml/kit/attribute_statement'
require 'saml/kit/authentication_request'
require 'saml/kit/bindings'
+require 'saml/kit/conditions'
require 'saml/kit/configuration'
require 'saml/kit/default_registry'
require 'saml/kit/logout_response'
require 'saml/kit/logout_request'
require 'saml/kit/metadata'
require 'saml/kit/null_assertion'
+require 'saml/kit/organization'
+require 'saml/kit/parser'
require 'saml/kit/composite_metadata'
require 'saml/kit/response'
require 'saml/kit/identity_provider_metadata'
spec/saml/kit/bindings/http_post_spec.rb
@@ -5,6 +5,8 @@ RSpec.describe Saml::Kit::Bindings::HttpPost do
let(:location) { FFaker::Internet.uri('https') }
+ specify { expect(subject.binding).to eql(Saml::Kit::Bindings::HTTP_POST) }
+
describe 'equality' do
it 'is referentially equal' do
expect(subject).to eql(subject)
spec/saml/kit/bindings/http_redirect_spec.rb
@@ -5,6 +5,8 @@ RSpec.describe Saml::Kit::Bindings::HttpRedirect do
let(:location) { FFaker::Internet.http_url }
+ specify { expect(subject.binding).to eql(Saml::Kit::Bindings::HTTP_REDIRECT) }
+
describe '#serialize' do
let(:relay_state) { 'ECHO' }
let(:configuration) do
spec/saml/kit/assertion_spec.rb
@@ -13,6 +13,7 @@ RSpec.describe Saml::Kit::Assertion do
specify { expect(subject.issuer).to eql(entity_id) }
specify { expect(subject.name_id).to eql(user.name_id) }
+ specify { expect(subject.name_id_format).to eql(Saml::Kit::Namespaces::PERSISTENT) }
specify { expect(subject.started_at.to_i).to eql(Time.now.utc.to_i) }
specify { expect(subject.expired_at.to_i).to eql(Saml::Kit.configuration.session_timeout.since(Time.now).utc.to_i) }
specify { expect(subject.attributes).to eql('id' => user.attributes[:id]) }
@@ -52,7 +53,7 @@ RSpec.describe Saml::Kit::Assertion do
</AuthnStatement>
</Assertion>
</Response>
-XML
+ XML
document = Nokogiri::XML(xml)
node = document.at_xpath('//saml:Assertion', 'saml' => Saml::Kit::Namespaces::ASSERTION)
subject = described_class.new(node, configuration: configuration)
@@ -89,7 +90,7 @@ XML
</AuthnStatement>
</Assertion>
</Response>
-XML
+ XML
document = Nokogiri::XML(xml)
node = document.at_xpath('//saml:Assertion', 'saml' => Saml::Kit::Namespaces::ASSERTION)
subject = described_class.new(node, configuration: configuration)
@@ -129,7 +130,7 @@ XML
</AuthnStatement>
</Assertion>
</Response>
-XML
+ XML
subject = described_class.new(Nokogiri::XML(xml))
expect(subject).to be_present
end
spec/saml/kit/default_registry_spec.rb
@@ -7,11 +7,13 @@ RSpec.describe Saml::Kit::DefaultRegistry do
let(:service_provider_metadata) do
Saml::Kit::ServiceProviderMetadata.build do |builder|
builder.entity_id = entity_id
+ builder.add_assertion_consumer_service(FFaker::Internet.uri('https'), binding: :http_post)
end
end
let(:identity_provider_metadata) do
Saml::Kit::IdentityProviderMetadata.build do |builder|
builder.entity_id = entity_id
+ builder.add_single_sign_on_service(FFaker::Internet.uri('https'), binding: :http_post)
end
end
@@ -78,13 +80,47 @@ RSpec.describe Saml::Kit::DefaultRegistry do
end
end
+ describe '#register' do
+ it 'registers the metadata' do
+ metadata = Saml::Kit::IdentityProviderMetadata.build do |xxx|
+ xxx.entity_id = FFaker::Internet.uri('https')
+ xxx.add_single_sign_on_service(FFaker::Internet.uri('https'), binding: :http_post)
+ end
+ subject.register(metadata)
+ expect(subject.metadata_for(metadata.entity_id)).to eql(metadata)
+ end
+
+ it 'raises an error when the metadata is invalid' do
+ expect do
+ subject.register(Saml::Kit::IdentityProviderMetadata.build)
+ end.to raise_error(/Cannot register invalid metadata/)
+ end
+
+ it 'raises an error when the document is not a metadata' do
+ authn_request = Saml::Kit::AuthenticationRequest.build
+ allow(authn_request).to receive(:valid?).and_return(true)
+
+ expect do
+ subject.register(authn_request)
+ end.to raise_error(/Cannot register invalid metadata/)
+ end
+
+ it 'raises an error when the document is nil' do
+ expect do
+ subject.register(nil)
+ end.to raise_error(/Cannot register invalid metadata/)
+ end
+ end
+
describe '#each' do
it 'yields each registered metadata' do
- idp = Saml::Kit::IdentityProviderMetadata.build do |config|
- config.entity_id = 'idp'
+ idp = Saml::Kit::IdentityProviderMetadata.build do |xxx|
+ xxx.entity_id = 'idp'
+ xxx.add_single_sign_on_service(FFaker::Internet.uri('https'), binding: :http_post)
end
- sp = Saml::Kit::ServiceProviderMetadata.build do |config|
- config.entity_id = 'sp'
+ sp = Saml::Kit::ServiceProviderMetadata.build do |xxx|
+ xxx.entity_id = 'sp'
+ xxx.add_assertion_consumer_service(FFaker::Internet.uri('https'), binding: :http_post)
end
subject.register(idp)
spec/saml/kit/metadata_spec.rb
@@ -49,10 +49,9 @@ RSpec.describe Saml::Kit::Metadata do
XML
result = subject.from(xml)
expect(result).to be_present
-
- expect(result.single_sign_on_services.count).to be(2)
- expect(result.assertion_consumer_services.count).to be(1)
- expect(result.single_logout_services.count).to be(2)
+ expect(result.single_sign_on_services.count).to eq(2)
+ expect(result.assertion_consumer_services.count).to eq(1)
+ expect(result.single_logout_services.count).to eq(2)
expect(result.organization_name).to eql('Acme, Inc')
expect(result.organization_url).to eql('http://localhost:5000/')
expect(result.contact_person_company).to eql('mailto:hi@example.com')
spec/saml/kit/response_spec.rb
@@ -471,7 +471,7 @@ RSpec.describe Saml::Kit::Response do
</Attribute>
</AttributeStatement>
</Assertion>
-XML
+ XML
end
it 'parses the encrypted assertion' do
@@ -510,7 +510,7 @@ XML
</xenc:EncryptedData>
</saml:EncryptedAssertion>
</samlp:Response>
-XML
+ XML
subject = described_class.new(xml)
expect(subject.attributes).to match_array([
.reek
@@ -1,125 +0,0 @@
----
-Attribute:
- exclude:
- - Saml::Kit::Assertion#occurred_at
- - Saml::Kit::Builders::AuthenticationRequest#assertion_consumer_service_url
- - Saml::Kit::Builders::AuthenticationRequest#destination
- - Saml::Kit::Builders::AuthenticationRequest#id
- - Saml::Kit::Builders::AuthenticationRequest#issuer
- - Saml::Kit::Builders::AuthenticationRequest#name_id_format
- - Saml::Kit::Builders::AuthenticationRequest#now
- - Saml::Kit::Builders::AuthenticationRequest#version
- - Saml::Kit::Builders::IdentityProviderMetadata#attributes
- - Saml::Kit::Builders::IdentityProviderMetadata#name_id_formats
- - Saml::Kit::Builders::IdentityProviderMetadata#want_authn_requests_signed
- - Saml::Kit::Builders::LogoutRequest#destination
- - Saml::Kit::Builders::LogoutRequest#id
- - Saml::Kit::Builders::LogoutRequest#issuer
- - Saml::Kit::Builders::LogoutRequest#name_id_format
- - Saml::Kit::Builders::LogoutRequest#now
- - Saml::Kit::Builders::LogoutRequest#version
- - Saml::Kit::Builders::LogoutResponse#destination
- - Saml::Kit::Builders::LogoutResponse#id
- - Saml::Kit::Builders::LogoutResponse#issuer
- - Saml::Kit::Builders::LogoutResponse#now
- - Saml::Kit::Builders::LogoutResponse#status_code
- - Saml::Kit::Builders::LogoutResponse#version
- - Saml::Kit::Builders::Metadata#contact_email
- - Saml::Kit::Builders::Metadata#entity_id
- - Saml::Kit::Builders::Metadata#id
- - Saml::Kit::Builders::Metadata#identity_provider
- - Saml::Kit::Builders::Metadata#organization_name
- - Saml::Kit::Builders::Metadata#organization_url
- - Saml::Kit::Builders::Metadata#service_provider
- - Saml::Kit::Builders::Response#destination
- - Saml::Kit::Builders::Response#id
- - Saml::Kit::Builders::Response#issuer
- - Saml::Kit::Builders::Response#now
- - Saml::Kit::Builders::Response#reference_id
- - Saml::Kit::Builders::Response#status_code
- - Saml::Kit::Builders::Response#status_message
- - Saml::Kit::Builders::Response#version
- - Saml::Kit::Builders::ServiceProviderMetadata#acs_urls
- - Saml::Kit::Builders::ServiceProviderMetadata#logout_urls
- - Saml::Kit::Builders::ServiceProviderMetadata#name_id_formats
- - Saml::Kit::Builders::ServiceProviderMetadata#want_assertions_signed
- - Saml::Kit::Configuration#clock_drift
- - Saml::Kit::Configuration#digest_method
- - Saml::Kit::Configuration#entity_id
- - Saml::Kit::Configuration#logger
- - Saml::Kit::Configuration#registry
- - Saml::Kit::Configuration#session_timeout
- - Saml::Kit::Configuration#signature_method
- - Saml::Kit::Document#registry
-BooleanParameter:
- exclude:
- - Saml::Kit::DefaultRegistry#register_url
- - Saml::Kit::DefaultRegistry::HttpApi#initialize
-ControlParameter:
- exclude:
- - Saml::Kit::Assertion#to_xml
- - Saml::Kit::Bindings::HttpRedirect#algorithm_for
- - Saml::Kit::Document#to_xml
- - Saml::Kit::Metadata#to_xml
- - Saml::Kit::Signature#to_xml
-TooManyInstanceVariables:
- max_instance_variables: 15
-TooManyMethods:
- max_methods: 30
-TooManyStatements:
- max_statements: 10
-UtilityFunction:
- exclude:
- - Saml::Kit::Assertion#parse_date
- - Saml::Kit::Bindings::HttpRedirect#algorithm_for
- - Saml::Kit::Bindings::HttpRedirect#canonicalize
- - Saml::Kit::Bindings::HttpRedirect#normalize
- - Saml::Kit::Bindings::HttpRedirect#params_to_hash
- - Saml::Kit::NullAssertion#expired_at
- - Saml::Kit::NullAssertion#started_at
- - uri_for
- - xml_document
- - Saml::Kit::Serializable#decode
- - Saml::Kit::Serializable#deflate
- - Saml::Kit::Serializable#encode
- - Saml::Kit::Serializable#escape
- - Saml::Kit::Serializable#inflate
- - Saml::Kit::Serializable#unescape
-LongParameterList:
- max_params: 4
-DuplicateMethodCall:
- exclude:
- - Saml::Kit::Bindings::Binding#saml_param_from
- - Saml::Kit::Builders::Response#initialize
- - Saml::Kit::XmlTemplatable#sign?
-FeatureEnvy:
- exclude:
- - Saml::Kit::Bindings::Binding#saml_param_from
- - Saml::Kit::Bindings::HttpRedirect#serialize
- - Saml::Kit::DefaultRegistry#register
- - Saml::Kit::Metadata#matches?
- - Saml::Kit::Signature#expected_digest_value
- - Saml::Kit::Signature#trusted?
-NilCheck:
- exclude:
- - Saml::Kit::Bindings::HttpRedirect#ensure_valid_signature
- - Saml::Kit::IdentityProviderMetadata#want_authn_requests_signed
- - Saml::Kit::LogoutRequest#single_logout_service
- - Saml::Kit::Respondable#must_match_request_id
- - Saml::Kit::Response#assertion
- - Saml::Kit::ServiceProviderMetadata#want_assertions_signed
- - Saml::Kit::Signature#certificate
- - Saml::Kit::Signature#trusted?
- - Saml::Kit::Signature#validate_signature
- - Saml::Kit::XmlTemplatable#sign?
-ManualDispatch:
- exclude:
- - Saml::Kit::Builders::Assertion#assertion_attributes
- - Saml::Kit::CompositeMetadata#method_missing
- - Saml::Kit::CompositeMetadata#respond_to_missing?
-UnusedParameters:
- exclude:
- - Saml::Kit::InvalidDocument#initialize
-TooManyConstants:
- exclude:
- - Saml::Kit::Namespaces
.rubocop.yml
@@ -14,6 +14,11 @@ AllCops:
- 'vendor/**/*'
TargetRubyVersion: 2.2
+Layout/AlignParameters:
+ Enabled: true
+ EnforcedStyle: with_fixed_indentation
+ IndentationWidth: 2
+
Layout/ClassStructure:
Enabled: true
Categories:
@@ -39,6 +44,10 @@ Layout/IndentArray:
Layout/IndentHeredoc:
EnforcedStyle: active_support
+Layout/MultilineMethodCallIndentation:
+ Enabled: true
+ EnforcedStyle: indented
+
Lint/AmbiguousBlockAssociation:
Exclude:
- 'spec/**/*.rb'
@@ -61,7 +70,10 @@ Metrics/ModuleLength:
Metrics/LineLength:
Exclude:
+ - 'lib/saml/kit/builders/templates/*.builder'
- 'spec/**/*.rb'
+ IgnoredPatterns:
+ - '^#*'
Naming/FileName:
Exclude:
.rubocop_todo.yml
@@ -1,28 +1,16 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
-# on 2018-02-16 22:08:54 -0700 using RuboCop version 0.52.1.
+# on 2018-03-16 13:38:09 -0600 using RuboCop version 0.53.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
-# Offense count: 1
-# Cop supports --auto-correct.
-# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods.
-Lint/UnusedMethodArgument:
- Exclude:
- - 'lib/saml/kit/invalid_document.rb'
-
# Offense count: 2
Metrics/AbcSize:
- Max: 20
-
-# Offense count: 3
-# Configuration parameters: CountComments.
-Metrics/ClassLength:
- Max: 136
+ Max: 19
-# Offense count: 6
+# Offense count: 11
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 13
@@ -30,16 +18,4 @@ Metrics/MethodLength:
# Offense count: 1
Style/DateTime:
Exclude:
- - 'lib/saml/kit/assertion.rb'
-
-# Offense count: 1
-# Cop supports --auto-correct.
-Style/IfUnlessModifier:
- Exclude:
- - 'lib/saml/kit/builders/authentication_request.rb'
-
-# Offense count: 128
-# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
-# URISchemes: http, https
-Metrics/LineLength:
- Max: 313
+ - 'lib/saml/kit/conditions.rb'
.travis.yml
@@ -2,10 +2,10 @@ sudo: false
language: ruby
cache: bundler
rvm:
- - 2.2.9
- - 2.3.6
- - 2.4.3
- - 2.5.0
+ - 2.2.10
+ - 2.3.7
+ - 2.4.4
+ - 2.5.1
script:
- bin/cibuild
- bin/lint
Rakefile
@@ -2,16 +2,12 @@
require 'bundler/audit/task'
require 'bundler/gem_tasks'
-require 'reek/rake/task'
require 'rspec/core/rake_task'
require 'rubocop/rake_task'
RSpec::Core::RakeTask.new(:spec)
RuboCop::RakeTask.new(:rubocop)
Bundler::Audit::Task.new
-Reek::Rake::Task.new(:reek) do |task|
- task.config_file = '.reek'
-end
-task lint: [:rubocop, :reek, 'bundle:audit']
+task lint: [:rubocop, 'bundle:audit']
task default: :spec
saml-kit.gemspec
@@ -34,7 +34,6 @@ Gem::Specification.new do |spec|
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 'reek', '~> 4.8'
spec.add_development_dependency 'rspec', '~> 3.0'
spec.add_development_dependency 'rspec-benchmark', '~> 0.3'
spec.add_development_dependency 'rubocop', '~> 0.52'