saml-kit 1.0.15 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +1 -5
- data/exe/saml-kit-create-self-signed-certificate +6 -2
- data/exe/saml-kit-decode-http-post +2 -1
- data/exe/saml-kit-decode-http-redirect +2 -1
- data/lib/saml/kit/assertion.rb +22 -48
- data/lib/saml/kit/attribute_statement.rb +25 -0
- data/lib/saml/kit/authentication_request.rb +34 -15
- data/lib/saml/kit/bindings/binding.rb +4 -7
- data/lib/saml/kit/bindings/http_post.rb +6 -2
- data/lib/saml/kit/bindings/http_redirect.rb +8 -5
- data/lib/saml/kit/bindings/url_builder.rb +7 -7
- data/lib/saml/kit/bindings.rb +4 -3
- data/lib/saml/kit/builders/assertion.rb +6 -3
- data/lib/saml/kit/builders/authentication_request.rb +4 -2
- data/lib/saml/kit/builders/encrypted_assertion.rb +3 -1
- data/lib/saml/kit/builders/identity_provider_metadata.rb +14 -4
- data/lib/saml/kit/builders/metadata.rb +8 -4
- data/lib/saml/kit/builders/null.rb +0 -1
- data/lib/saml/kit/builders/response.rb +14 -5
- data/lib/saml/kit/builders/service_provider_metadata.rb +10 -3
- data/lib/saml/kit/builders.rb +0 -1
- data/lib/saml/kit/composite_metadata.rb +18 -3
- data/lib/saml/kit/{buildable.rb → concerns/buildable.rb} +0 -0
- data/lib/saml/kit/{requestable.rb → concerns/requestable.rb} +0 -0
- data/lib/saml/kit/{respondable.rb → concerns/respondable.rb} +0 -0
- data/lib/saml/kit/{serializable.rb → concerns/serializable.rb} +0 -0
- data/lib/saml/kit/{translatable.rb → concerns/translatable.rb} +0 -0
- data/lib/saml/kit/{trustable.rb → concerns/trustable.rb} +9 -7
- data/lib/saml/kit/concerns/xml_parseable.rb +62 -0
- data/lib/saml/kit/{xml_templatable.rb → concerns/xml_templatable.rb} +3 -2
- data/lib/saml/kit/{xsd_validatable.rb → concerns/xsd_validatable.rb} +10 -0
- data/lib/saml/kit/conditions.rb +37 -0
- data/lib/saml/kit/configuration.rb +28 -10
- data/lib/saml/kit/default_registry.rb +19 -4
- data/lib/saml/kit/document.rb +21 -67
- data/lib/saml/kit/identity_provider_metadata.rb +34 -15
- data/lib/saml/kit/invalid_document.rb +1 -1
- data/lib/saml/kit/logout_request.rb +11 -6
- data/lib/saml/kit/logout_response.rb +3 -1
- data/lib/saml/kit/metadata.rb +63 -109
- data/lib/saml/kit/namespaces.rb +2 -1
- data/lib/saml/kit/organization.rb +36 -0
- data/lib/saml/kit/parser.rb +28 -0
- data/lib/saml/kit/response.rb +10 -2
- data/lib/saml/kit/rspec/have_xpath.rb +4 -2
- data/lib/saml/kit/service_provider_metadata.rb +2 -1
- data/lib/saml/kit/signature.rb +21 -5
- data/lib/saml/kit/version.rb +1 -1
- data/lib/saml/kit.rb +14 -7
- data/saml-kit.gemspec +0 -1
- metadata +16 -25
data/lib/saml/kit/builders.rb
CHANGED
@@ -9,6 +9,7 @@ module Saml
|
|
9
9
|
# and SPSSODescriptor element.
|
10
10
|
class CompositeMetadata < Metadata # :nodoc:
|
11
11
|
include Enumerable
|
12
|
+
|
12
13
|
attr_reader :service_provider, :identity_provider
|
13
14
|
|
14
15
|
def initialize(xml)
|
@@ -19,8 +20,22 @@ module Saml
|
|
19
20
|
]
|
20
21
|
end
|
21
22
|
|
23
|
+
def organization
|
24
|
+
find { |x| x.organization.present? }.try(:organization)
|
25
|
+
end
|
26
|
+
|
27
|
+
def organization_name
|
28
|
+
organization.name
|
29
|
+
end
|
30
|
+
|
31
|
+
def organization_url
|
32
|
+
organization.url
|
33
|
+
end
|
34
|
+
|
22
35
|
def services(type)
|
23
|
-
xpath = map
|
36
|
+
xpath = map do |x|
|
37
|
+
"//md:EntityDescriptor/md:#{x.name}/md:#{type}"
|
38
|
+
end.join('|')
|
24
39
|
search(xpath).map do |item|
|
25
40
|
binding = item.attribute('Binding').value
|
26
41
|
location = item.attribute('Location').value
|
@@ -37,7 +52,7 @@ module Saml
|
|
37
52
|
end
|
38
53
|
|
39
54
|
def method_missing(name, *args)
|
40
|
-
if (target = find { |
|
55
|
+
if (target = find { |x| x.respond_to?(name) })
|
41
56
|
target.public_send(name, *args)
|
42
57
|
else
|
43
58
|
super
|
@@ -45,7 +60,7 @@ module Saml
|
|
45
60
|
end
|
46
61
|
|
47
62
|
def respond_to_missing?(method, *)
|
48
|
-
find { |
|
63
|
+
find { |x| x.respond_to?(method) }
|
49
64
|
end
|
50
65
|
end
|
51
66
|
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -9,14 +9,15 @@ module Saml
|
|
9
9
|
extend ActiveSupport::Concern
|
10
10
|
|
11
11
|
included do
|
12
|
-
validate :must_have_valid_signature, unless: :
|
12
|
+
validate :must_have_valid_signature, unless: :signature_verified
|
13
13
|
validate :must_be_registered
|
14
14
|
validate :must_be_trusted
|
15
15
|
end
|
16
16
|
|
17
|
-
# Returns true when the document has an embedded XML Signature or has
|
17
|
+
# Returns true when the document has an embedded XML Signature or has
|
18
|
+
# been verified externally.
|
18
19
|
def signed?
|
19
|
-
|
20
|
+
signature_verified || signature.present?
|
20
21
|
end
|
21
22
|
|
22
23
|
# @!visibility private
|
@@ -24,9 +25,10 @@ module Saml
|
|
24
25
|
@signature ||= Signature.new(at_xpath("/samlp:#{name}/ds:Signature"))
|
25
26
|
end
|
26
27
|
|
27
|
-
# Returns true when documents is signed and the signing certificate
|
28
|
+
# Returns true when documents is signed and the signing certificate
|
29
|
+
# belongs to a known service entity.
|
28
30
|
def trusted?
|
29
|
-
return true if
|
31
|
+
return true if signature_verified
|
30
32
|
return false unless signed?
|
31
33
|
signature.trusted?(provider)
|
32
34
|
end
|
@@ -38,12 +40,12 @@ module Saml
|
|
38
40
|
|
39
41
|
# @!visibility private
|
40
42
|
def signature_verified!
|
41
|
-
@
|
43
|
+
@signature_verified = true
|
42
44
|
end
|
43
45
|
|
44
46
|
private
|
45
47
|
|
46
|
-
attr_reader :
|
48
|
+
attr_reader :signature_verified
|
47
49
|
|
48
50
|
def must_have_valid_signature
|
49
51
|
return if to_xml.blank?
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'saml/kit/namespaces'
|
4
|
+
|
5
|
+
module Saml
|
6
|
+
module Kit
|
7
|
+
module XmlParseable
|
8
|
+
NAMESPACES = {
|
9
|
+
NameFormat: ::Saml::Kit::Namespaces::ATTR_SPLAT,
|
10
|
+
ds: ::Xml::Kit::Namespaces::XMLDSIG,
|
11
|
+
md: ::Saml::Kit::Namespaces::METADATA,
|
12
|
+
saml: ::Saml::Kit::Namespaces::ASSERTION,
|
13
|
+
samlp: ::Saml::Kit::Namespaces::PROTOCOL,
|
14
|
+
xmlenc: ::Xml::Kit::Namespaces::XMLENC,
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
# Returns the SAML document returned as a Hash.
|
18
|
+
def to_h
|
19
|
+
@to_h ||= Hash.from_xml(to_s) || {}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the XML document as a String.
|
23
|
+
#
|
24
|
+
# @param pretty [Boolean] true to return a human friendly version
|
25
|
+
# of the XML.
|
26
|
+
def to_xml(pretty: nil)
|
27
|
+
pretty ? to_nokogiri.to_xml(indent: 2) : to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the SAML document as an XHTML string.
|
31
|
+
# This is useful for rendering in a web page.
|
32
|
+
def to_xhtml
|
33
|
+
Nokogiri::XML(to_xml, &:noblanks).to_xhtml
|
34
|
+
end
|
35
|
+
|
36
|
+
def present?
|
37
|
+
to_s.present?
|
38
|
+
end
|
39
|
+
|
40
|
+
# @!visibility private
|
41
|
+
def to_nokogiri
|
42
|
+
@to_nokogiri ||= Nokogiri::XML(to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @!visibility private
|
46
|
+
def at_xpath(xpath)
|
47
|
+
return unless present?
|
48
|
+
to_nokogiri.at_xpath(xpath, NAMESPACES)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @!visibility private
|
52
|
+
def search(xpath)
|
53
|
+
to_nokogiri.search(xpath, NAMESPACES)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the XML document as a [String].
|
57
|
+
def to_s
|
58
|
+
content
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -5,7 +5,7 @@ module Saml
|
|
5
5
|
# This module is responsible for
|
6
6
|
# generating converting templates to xml.
|
7
7
|
module XmlTemplatable
|
8
|
-
TEMPLATES_DIR = Pathname.new(File.join(__dir__, 'builders/templates/'))
|
8
|
+
TEMPLATES_DIR = Pathname.new(File.join(__dir__, '../builders/templates/'))
|
9
9
|
include ::Xml::Kit::Templatable
|
10
10
|
|
11
11
|
def template_path
|
@@ -16,7 +16,8 @@ module Saml
|
|
16
16
|
"#{self.class.name.split('::').last.underscore}.builder"
|
17
17
|
end
|
18
18
|
|
19
|
-
# Returns true if an embedded signature is requested and at least one
|
19
|
+
# Returns true if an embedded signature is requested and at least one
|
20
|
+
# signing certificate is available via the configuration.
|
20
21
|
def sign?
|
21
22
|
return configuration.sign? if embed_signature.nil?
|
22
23
|
(embed_signature && configuration.sign?) ||
|
@@ -5,8 +5,18 @@ module Saml
|
|
5
5
|
# This module is responsible for validating
|
6
6
|
# xml documents against the SAML XSD's
|
7
7
|
module XsdValidatable
|
8
|
+
PROTOCOL_XSD = File.expand_path(
|
9
|
+
'../xsd/saml-schema-protocol-2.0.xsd', File.dirname(__FILE__)
|
10
|
+
).freeze
|
11
|
+
|
12
|
+
METADATA_XSD = File.expand_path(
|
13
|
+
'../xsd/saml-schema-metadata-2.0.xsd', File.dirname(__FILE__)
|
14
|
+
).freeze
|
15
|
+
|
8
16
|
# @!visibility private
|
9
17
|
def matches_xsd?(xsd)
|
18
|
+
return unless to_nokogiri.present?
|
19
|
+
|
10
20
|
Dir.chdir(File.dirname(xsd)) do
|
11
21
|
xsd = Nokogiri::XML::Schema(IO.read(xsd))
|
12
22
|
xsd.validate(to_nokogiri).each do |error|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Saml
|
4
|
+
module Kit
|
5
|
+
class Conditions
|
6
|
+
include XmlParseable
|
7
|
+
|
8
|
+
attr_reader :content
|
9
|
+
|
10
|
+
def initialize(node)
|
11
|
+
@to_nokogiri = node
|
12
|
+
@content = node.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def started_at
|
16
|
+
parse_iso8601(at_xpath('./@NotBefore').try(:value))
|
17
|
+
end
|
18
|
+
|
19
|
+
def expired_at
|
20
|
+
parse_iso8601(at_xpath('./@NotOnOrAfter').try(:value))
|
21
|
+
end
|
22
|
+
|
23
|
+
def audiences
|
24
|
+
search('./saml:AudienceRestriction/saml:Audience').map(&:text)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def parse_iso8601(value)
|
30
|
+
DateTime.parse(value)
|
31
|
+
rescue StandardError => error
|
32
|
+
Saml::Kit.logger.error(error)
|
33
|
+
Time.at(0).to_datetime
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
module Saml
|
4
4
|
module Kit
|
5
|
-
# This class represents the main configuration that is use for generating
|
5
|
+
# This class represents the main configuration that is use for generating
|
6
|
+
# SAML documents.
|
6
7
|
#
|
7
8
|
# Saml::Kit::Configuration.new do |config|
|
8
9
|
# config.entity_id = "com:saml:kit"
|
@@ -19,17 +20,25 @@ module Saml
|
|
19
20
|
# Saml::Kit.configure do |configuration|
|
20
21
|
# configuration.entity_id = "https://www.example.com/saml/metadata"
|
21
22
|
# configuration.generate_key_pair_for(use: :signing)
|
22
|
-
# configuration.add_key_pair(
|
23
|
+
# configuration.add_key_pair(
|
24
|
+
# ENV["X509_CERTIFICATE"],
|
25
|
+
# ENV["PRIVATE_KEY"],
|
26
|
+
# passphrase: ENV['PRIVATE_KEY_PASSPHRASE'],
|
27
|
+
# use: :encryption
|
28
|
+
# )
|
23
29
|
# end
|
24
30
|
class Configuration
|
25
31
|
USES = %i[signing encryption].freeze
|
26
32
|
# The issuer to use in requests or responses from this entity to use.
|
27
33
|
attr_accessor :entity_id
|
28
|
-
# The signature method to use when generating signatures
|
34
|
+
# The signature method to use when generating signatures
|
35
|
+
# (See {Saml::Kit::Builders::XmlSignature::SIGNATURE_METHODS})
|
29
36
|
attr_accessor :signature_method
|
30
|
-
# The digest method to use when generating signatures
|
37
|
+
# The digest method to use when generating signatures
|
38
|
+
# (See {Saml::Kit::Builders::XmlSignature::DIGEST_METHODS})
|
31
39
|
attr_accessor :digest_method
|
32
|
-
# The metadata registry to use for searching for metadata associated
|
40
|
+
# The metadata registry to use for searching for metadata associated
|
41
|
+
# with an issuer.
|
33
42
|
attr_accessor :registry
|
34
43
|
# The session timeout to use when generating an Assertion.
|
35
44
|
attr_accessor :session_timeout
|
@@ -57,7 +66,11 @@ module Saml
|
|
57
66
|
# @param use [Symbol] the type of key pair, `:signing` or `:encryption`
|
58
67
|
def add_key_pair(certificate, private_key, passphrase: nil, use: :signing)
|
59
68
|
ensure_proper_use(use)
|
60
|
-
@key_pairs.push(
|
69
|
+
@key_pairs.push(
|
70
|
+
::Xml::Kit::KeyPair.new(
|
71
|
+
certificate, private_key, passphrase, use.to_sym
|
72
|
+
)
|
73
|
+
)
|
61
74
|
end
|
62
75
|
|
63
76
|
# Generates a unique key pair that can be used for signing or encryption.
|
@@ -66,27 +79,32 @@ module Saml
|
|
66
79
|
# @param passphrase [String] the private key passphrase to use.
|
67
80
|
def generate_key_pair_for(use:, passphrase: SecureRandom.uuid)
|
68
81
|
ensure_proper_use(use)
|
69
|
-
certificate, private_key = ::Xml::Kit::SelfSignedCertificate.new.create(
|
82
|
+
certificate, private_key = ::Xml::Kit::SelfSignedCertificate.new.create(
|
83
|
+
passphrase: passphrase
|
84
|
+
)
|
70
85
|
add_key_pair(certificate, private_key, passphrase: passphrase, use: use)
|
71
86
|
end
|
72
87
|
|
73
88
|
# Return each key pair for a specific use.
|
74
89
|
#
|
75
|
-
# @param use [Symbol] the type of key pair to return
|
90
|
+
# @param use [Symbol] the type of key pair to return
|
91
|
+
# `nil`, `:signing` or `:encryption`
|
76
92
|
def key_pairs(use: nil)
|
77
93
|
use.present? ? @key_pairs.find_all { |xxx| xxx.for?(use) } : @key_pairs
|
78
94
|
end
|
79
95
|
|
80
96
|
# Return each certificate for a specific use.
|
81
97
|
#
|
82
|
-
# @param use [Symbol] the type of key pair to return
|
98
|
+
# @param use [Symbol] the type of key pair to return
|
99
|
+
# `nil`, `:signing` or `:encryption`
|
83
100
|
def certificates(use: nil)
|
84
101
|
key_pairs(use: use).flat_map(&:certificate)
|
85
102
|
end
|
86
103
|
|
87
104
|
# Return each private for a specific use.
|
88
105
|
#
|
89
|
-
# @param use [Symbol] the type of key pair to return
|
106
|
+
# @param use [Symbol] the type of key pair to return
|
107
|
+
# `nil`, `:signing` or `:encryption`
|
90
108
|
def private_keys(use: nil)
|
91
109
|
key_pairs(use: use).flat_map(&:private_key)
|
92
110
|
end
|
@@ -2,8 +2,10 @@
|
|
2
2
|
|
3
3
|
module Saml
|
4
4
|
module Kit
|
5
|
-
# The default metadata registry is used to fetch the metadata associated
|
6
|
-
#
|
5
|
+
# The default metadata registry is used to fetch the metadata associated
|
6
|
+
# with an issuer or entity id.
|
7
|
+
# The metadata associated with an issuer is used to verify trust for any
|
8
|
+
# SAML documents that are received.
|
7
9
|
#
|
8
10
|
# You can replace the default registry with your own at startup.
|
9
11
|
#
|
@@ -41,12 +43,14 @@ module Saml
|
|
41
43
|
#
|
42
44
|
# @param metadata [Saml::Kit::Metadata] the metadata to register.
|
43
45
|
def register(metadata)
|
46
|
+
ensure_valid_metadata(metadata)
|
44
47
|
Saml::Kit.logger.debug(metadata.to_xml(pretty: true))
|
45
48
|
@items[metadata.entity_id] = metadata
|
46
49
|
end
|
47
50
|
|
48
51
|
# Register metadata via a remote URL.
|
49
|
-
# This will attempt to connect to the remove URL to download the
|
52
|
+
# This will attempt to connect to the remove URL to download the
|
53
|
+
# metadata and register it in the registry.
|
50
54
|
#
|
51
55
|
# @param url [String] the url to download the metadata from.
|
52
56
|
# @param verify_ssl [Boolean] enable/disable SSL peer verification.
|
@@ -57,7 +61,8 @@ module Saml
|
|
57
61
|
|
58
62
|
# Returns the metadata document associated with an issuer or entityID.
|
59
63
|
#
|
60
|
-
# @param entity_id [String]
|
64
|
+
# @param entity_id [String] unique entityID/Issuer associated with
|
65
|
+
# metadata.
|
61
66
|
def metadata_for(entity_id)
|
62
67
|
@items[entity_id]
|
63
68
|
end
|
@@ -69,6 +74,16 @@ module Saml
|
|
69
74
|
end
|
70
75
|
end
|
71
76
|
|
77
|
+
private
|
78
|
+
|
79
|
+
def ensure_valid_metadata(metadata)
|
80
|
+
error = ArgumentError.new('Cannot register invalid metadata')
|
81
|
+
raise error if
|
82
|
+
metadata.nil? ||
|
83
|
+
!metadata.respond_to?(:entity_id) ||
|
84
|
+
metadata.invalid?
|
85
|
+
end
|
86
|
+
|
72
87
|
# This class is responsible for
|
73
88
|
# making HTTP requests to fetch metadata
|
74
89
|
# from remote locations.
|
data/lib/saml/kit/document.rb
CHANGED
@@ -5,19 +5,12 @@ module Saml
|
|
5
5
|
# This class is a base class for SAML documents.
|
6
6
|
class Document
|
7
7
|
include ActiveModel::Validations
|
8
|
-
include
|
8
|
+
include Buildable
|
9
9
|
include Translatable
|
10
10
|
include Trustable
|
11
|
-
include
|
12
|
-
|
13
|
-
|
14
|
-
"NameFormat": ::Saml::Kit::Namespaces::ATTR_SPLAT,
|
15
|
-
"ds": ::Xml::Kit::Namespaces::XMLDSIG,
|
16
|
-
"md": ::Saml::Kit::Namespaces::METADATA,
|
17
|
-
"saml": ::Saml::Kit::Namespaces::ASSERTION,
|
18
|
-
"samlp": ::Saml::Kit::Namespaces::PROTOCOL,
|
19
|
-
'xmlenc' => ::Xml::Kit::Namespaces::XMLENC,
|
20
|
-
}.freeze
|
11
|
+
include XmlParseable
|
12
|
+
include XsdValidatable
|
13
|
+
|
21
14
|
attr_accessor :registry
|
22
15
|
attr_reader :name
|
23
16
|
validates_presence_of :content
|
@@ -58,44 +51,13 @@ module Saml
|
|
58
51
|
Time.parse(at_xpath('./*/@IssueInstant').try(:value))
|
59
52
|
end
|
60
53
|
|
61
|
-
# Returns the SAML document returned as a Hash.
|
62
|
-
def to_h
|
63
|
-
@to_h ||= Hash.from_xml(content) || {}
|
64
|
-
end
|
65
|
-
|
66
|
-
# Returns the SAML document as an XML string.
|
67
|
-
#
|
68
|
-
# @param pretty [Boolean] formats the xml or returns the raw xml.
|
69
|
-
def to_xml(pretty: nil)
|
70
|
-
pretty ? to_nokogiri.to_xml(indent: 2) : to_s
|
71
|
-
end
|
72
|
-
|
73
|
-
# Returns the SAML document as an XHTML string.
|
74
|
-
# This is useful for rendering in a web page.
|
75
|
-
def to_xhtml
|
76
|
-
Nokogiri::XML(to_xml, &:noblanks).to_xhtml
|
77
|
-
end
|
78
|
-
|
79
|
-
# @!visibility private
|
80
|
-
def to_nokogiri
|
81
|
-
@to_nokogiri ||= Nokogiri::XML(to_s)
|
82
|
-
end
|
83
|
-
|
84
|
-
# @!visibility private
|
85
|
-
def at_xpath(xpath)
|
86
|
-
to_nokogiri.at_xpath(xpath, NAMESPACES)
|
87
|
-
end
|
88
|
-
|
89
|
-
# @!visibility private
|
90
|
-
def search(xpath)
|
91
|
-
to_nokogiri.search(xpath, NAMESPACES)
|
92
|
-
end
|
93
|
-
|
94
|
-
def to_s
|
95
|
-
content
|
96
|
-
end
|
97
|
-
|
98
54
|
class << self
|
55
|
+
CONSTRUCTORS = {
|
56
|
+
'AuthnRequest' => -> { Saml::Kit::AuthenticationRequest },
|
57
|
+
'LogoutRequest' => -> { Saml::Kit::LogoutRequest },
|
58
|
+
'LogoutResponse' => -> { Saml::Kit::LogoutResponse },
|
59
|
+
'Response' => -> { Saml::Kit::Response },
|
60
|
+
}.freeze
|
99
61
|
XPATH = [
|
100
62
|
'/samlp:AuthnRequest',
|
101
63
|
'/samlp:LogoutRequest',
|
@@ -106,14 +68,12 @@ module Saml
|
|
106
68
|
# Returns the raw xml as a Saml::Kit SAML document.
|
107
69
|
#
|
108
70
|
# @param xml [String] the raw xml string.
|
109
|
-
# @param configuration [Saml::Kit::Configuration]
|
71
|
+
# @param configuration [Saml::Kit::Configuration] configuration to use
|
72
|
+
# for unpacking the document.
|
110
73
|
def to_saml_document(xml, configuration: Saml::Kit.configuration)
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
'LogoutResponse' => Saml::Kit::LogoutResponse,
|
115
|
-
'Response' => Saml::Kit::Response,
|
116
|
-
}[Nokogiri::XML(xml).at_xpath(XPATH, "samlp": ::Saml::Kit::Namespaces::PROTOCOL).name] || InvalidDocument
|
74
|
+
namespaces = { samlp: Namespaces::PROTOCOL }
|
75
|
+
element = Nokogiri::XML(xml).at_xpath(XPATH, namespaces)
|
76
|
+
constructor = CONSTRUCTORS[element.name].try(:call) || InvalidDocument
|
117
77
|
constructor.new(xml, configuration: configuration)
|
118
78
|
rescue StandardError => error
|
119
79
|
Saml::Kit.logger.error(error)
|
@@ -122,18 +82,12 @@ module Saml
|
|
122
82
|
|
123
83
|
# @!visibility private
|
124
84
|
def builder_class # :nodoc:
|
125
|
-
|
126
|
-
|
127
|
-
Saml::Kit::Builders::
|
128
|
-
|
129
|
-
Saml::Kit::Builders::
|
130
|
-
|
131
|
-
Saml::Kit::Builders::AuthenticationRequest
|
132
|
-
when Saml::Kit::LogoutRequest.to_s
|
133
|
-
Saml::Kit::Builders::LogoutRequest
|
134
|
-
else
|
135
|
-
raise ArgumentError, "Unknown SAML Document #{name}"
|
136
|
-
end
|
85
|
+
{
|
86
|
+
Response.to_s => Saml::Kit::Builders::Response,
|
87
|
+
LogoutResponse.to_s => Saml::Kit::Builders::LogoutResponse,
|
88
|
+
AuthenticationRequest.to_s => Saml::Kit::Builders::AuthenticationRequest,
|
89
|
+
LogoutRequest.to_s => Saml::Kit::Builders::LogoutRequest,
|
90
|
+
}[name] || (raise ArgumentError, "Unknown SAML Document #{name}")
|
137
91
|
end
|
138
92
|
end
|
139
93
|
|
@@ -2,16 +2,31 @@
|
|
2
2
|
|
3
3
|
module Saml
|
4
4
|
module Kit
|
5
|
-
# This class
|
5
|
+
# This class parses the IDPSSODescriptor from a SAML metadata document.
|
6
6
|
#
|
7
7
|
# raw_xml = <<-XML
|
8
8
|
# <?xml version="1.0" encoding="UTF-8"?>
|
9
|
-
# <EntityDescriptor
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
9
|
+
# <EntityDescriptor
|
10
|
+
# xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
11
|
+
# xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
|
12
|
+
# xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
13
|
+
# ID="_cfa24e2f-0ec0-4ee3-abb8-b2fcfe394c1c"
|
14
|
+
# entityID="my-entity-id">
|
15
|
+
# <IDPSSODescriptor
|
16
|
+
# WantAuthnRequestsSigned="true"
|
17
|
+
# protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
18
|
+
# <SingleLogoutService
|
19
|
+
# Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
20
|
+
# Location="https://www.example.com/logout" />
|
21
|
+
# <NameIDFormat>
|
22
|
+
# urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
|
23
|
+
# </NameIDFormat>
|
24
|
+
# <SingleSignOnService
|
25
|
+
# Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
26
|
+
# Location="https://www.example.com/login" />
|
27
|
+
# <SingleSignOnService
|
28
|
+
# Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
29
|
+
# Location="https://www.example.com/login" />
|
15
30
|
# <saml:Attribute Name="id"/>
|
16
31
|
# </IDPSSODescriptor>
|
17
32
|
# </EntityDescriptor>
|
@@ -70,14 +85,18 @@ module Saml
|
|
70
85
|
# Creates a AuthnRequest document for the specified binding.
|
71
86
|
#
|
72
87
|
# @param binding [Symbol] `:http_post` or `:http_redirect`.
|
73
|
-
# @param relay_state [Object]
|
74
|
-
# @param configuration [Saml::Kit::Configuration] the configuration to
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
88
|
+
# @param relay_state [Object] RelayState to include the returned params.
|
89
|
+
# @param configuration [Saml::Kit::Configuration] the configuration to
|
90
|
+
# use for generating the request.
|
91
|
+
# @return [Array] Url and params encoded using rules for binding.
|
92
|
+
def login_request_for(
|
93
|
+
binding:, relay_state: nil, configuration: Saml::Kit.configuration
|
94
|
+
)
|
95
|
+
builder =
|
96
|
+
AuthenticationRequest.builder(configuration: configuration) do |x|
|
97
|
+
x.embed_signature = want_authn_requests_signed
|
98
|
+
yield x if block_given?
|
99
|
+
end
|
81
100
|
request_binding = single_sign_on_service_for(binding: binding)
|
82
101
|
request_binding.serialize(builder, relay_state: relay_state)
|
83
102
|
end
|
@@ -16,7 +16,8 @@ module Saml
|
|
16
16
|
#
|
17
17
|
# See {Saml::Kit::Builders::LogoutRequest} for a list of available settings.
|
18
18
|
#
|
19
|
-
# This class can also be used to generate the correspondong LogoutResponse
|
19
|
+
# This class can also be used to generate the correspondong LogoutResponse
|
20
|
+
# for a LogoutRequest.
|
20
21
|
#
|
21
22
|
# document = Saml::Kit::LogoutRequest.new(raw_xml)
|
22
23
|
# url, saml_params = document.response_for(binding: :http_post)
|
@@ -31,7 +32,7 @@ module Saml
|
|
31
32
|
# A new instance of LogoutRequest
|
32
33
|
#
|
33
34
|
# @param xml [String] The raw xml string.
|
34
|
-
# @param configuration [Saml::Kit::Configuration]
|
35
|
+
# @param configuration [Saml::Kit::Configuration] configuration to use.
|
35
36
|
def initialize(xml, configuration: Saml::Kit.configuration)
|
36
37
|
super(xml, name: 'LogoutRequest', configuration: configuration)
|
37
38
|
end
|
@@ -45,11 +46,15 @@ module Saml
|
|
45
46
|
at_xpath('./*/saml:NameID/@Format').try(:value)
|
46
47
|
end
|
47
48
|
|
48
|
-
# Generates a Serialized LogoutResponse using the encoding rules for
|
49
|
+
# Generates a Serialized LogoutResponse using the encoding rules for
|
50
|
+
# the specified binding.
|
49
51
|
#
|
50
|
-
# @param binding [Symbol] The binding to use `:http_redirect` or
|
51
|
-
#
|
52
|
-
# @
|
52
|
+
# @param binding [Symbol] The binding to use `:http_redirect` or
|
53
|
+
# `:http_post`.
|
54
|
+
# @param relay_state [Object] The RelayState to include in the
|
55
|
+
# RelayState param.
|
56
|
+
# @return [Array] Returns an array with a url and Hash of parameters to
|
57
|
+
# return to the requestor.
|
53
58
|
def response_for(binding:, relay_state: nil)
|
54
59
|
builder = Saml::Kit::LogoutResponse.builder(self) do |xxx|
|
55
60
|
yield xxx if block_given?
|
@@ -10,7 +10,9 @@ module Saml
|
|
10
10
|
class LogoutResponse < Document
|
11
11
|
include Respondable
|
12
12
|
|
13
|
-
def initialize(
|
13
|
+
def initialize(
|
14
|
+
xml, request_id: nil, configuration: Saml::Kit.configuration
|
15
|
+
)
|
14
16
|
@request_id = request_id
|
15
17
|
super(xml, name: 'LogoutResponse', configuration: configuration)
|
16
18
|
end
|