saml2 1.0.10 → 1.1.0

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -7
  3. data/lib/saml2.rb +2 -0
  4. data/lib/saml2/attribute.rb +2 -0
  5. data/lib/saml2/attribute_consuming_service.rb +1 -0
  6. data/lib/saml2/authn_request.rb +19 -47
  7. data/lib/saml2/base.rb +5 -2
  8. data/lib/saml2/bindings.rb +7 -0
  9. data/lib/saml2/bindings/http_redirect.rb +141 -0
  10. data/lib/saml2/contact.rb +14 -16
  11. data/lib/saml2/endpoint.rb +5 -6
  12. data/lib/saml2/entity.rb +23 -18
  13. data/lib/saml2/identity_provider.rb +4 -4
  14. data/lib/saml2/indexed_object.rb +7 -3
  15. data/lib/saml2/key.rb +19 -1
  16. data/lib/saml2/logout_request.rb +43 -0
  17. data/lib/saml2/logout_response.rb +23 -0
  18. data/lib/saml2/message.rb +109 -0
  19. data/lib/saml2/name_id.rb +16 -8
  20. data/lib/saml2/organization_and_contacts.rb +2 -2
  21. data/lib/saml2/request.rb +8 -0
  22. data/lib/saml2/response.rb +7 -23
  23. data/lib/saml2/role.rb +2 -3
  24. data/lib/saml2/service_provider.rb +24 -2
  25. data/lib/saml2/sso.rb +2 -2
  26. data/lib/saml2/status.rb +28 -0
  27. data/lib/saml2/status_response.rb +33 -0
  28. data/lib/saml2/version.rb +1 -1
  29. data/spec/fixtures/identity_provider.xml +1 -0
  30. data/spec/fixtures/response_signed.xml +1 -1
  31. data/spec/fixtures/response_with_attribute_signed.xml +1 -1
  32. data/spec/lib/attribute_consuming_service_spec.rb +37 -37
  33. data/spec/lib/attribute_spec.rb +17 -17
  34. data/spec/lib/authn_request_spec.rb +15 -71
  35. data/spec/lib/bindings/http_redirect_spec.rb +151 -0
  36. data/spec/lib/conditions_spec.rb +10 -10
  37. data/spec/lib/entity_spec.rb +12 -12
  38. data/spec/lib/identity_provider_spec.rb +4 -4
  39. data/spec/lib/indexed_object_spec.rb +38 -7
  40. data/spec/lib/logout_request_spec.rb +31 -0
  41. data/spec/lib/logout_response_spec.rb +31 -0
  42. data/spec/lib/message_spec.rb +21 -0
  43. data/spec/lib/response_spec.rb +8 -9
  44. data/spec/lib/service_provider_spec.rb +29 -8
  45. data/spec/spec_helper.rb +0 -1
  46. metadata +41 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7c19f162d8dcc43d0a264bc4c1b1f1f0e497b476
4
- data.tar.gz: e0d34ccb811ce49f36449d7ec843f2f58516a733
3
+ metadata.gz: 382418cbc200bcf81a58ff09ebadd551d0c16101
4
+ data.tar.gz: 0f7318a0d65eb9b80afbe0405d6dc768cc4e3f0d
5
5
  SHA512:
6
- metadata.gz: 70083ff8f78e8eaf3302c548babff43b54478d7bc5dc45346612e18b378695570dbcae619c1776150d80bee59f60dcf658b7848fd78fe0e9e3b592f7e6c7468a
7
- data.tar.gz: 863e91a34172a34526f20935b092160185e2189edf3881d98e7cb82d577c89d16ff1d5a4c21ad0b08eee602348523b74326409161b5d0aa59e84191f6baaef1d
6
+ metadata.gz: 0b0462631aa2d2c0208655bf85e8a104555034176df8825f42029f1356a745d07f561f15a5c78719e8a1400eb70063cc89c6014abaf226a3294d5a6a0aa319c1
7
+ data.tar.gz: c1ad828d647e905e3e92e4e34c2e4856a370fe32abf9bfe337e344dd34e46a95b9d344f5c3cd7542a1636a068febad623af0a83ee2cc524d7c0e74eb7cb8a61e
data/Rakefile CHANGED
@@ -2,12 +2,7 @@ require 'rubygems'
2
2
  require 'bundler'
3
3
  Bundler::GemHelper.install_tasks
4
4
 
5
- require 'rake'
6
- require 'rake/testtask'
7
-
8
- Rake::TestTask.new do |t|
9
- t.name = "spec"
10
- t.pattern = "spec/**/*_spec.rb"
11
- end
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new
12
7
 
13
8
  task :default => :spec
@@ -1,5 +1,7 @@
1
1
  require 'saml2/authn_request'
2
2
  require 'saml2/entity'
3
+ require 'saml2/logout_request'
4
+ require 'saml2/logout_response'
3
5
  require 'saml2/response'
4
6
  require 'saml2/version'
5
7
 
@@ -65,6 +65,7 @@ module SAML2
65
65
  end
66
66
 
67
67
  def from_xml(node)
68
+ super
68
69
  @name = node['Name']
69
70
  @friendly_name = node['FriendlyName']
70
71
  @name_format = node['NameFormat']
@@ -124,6 +125,7 @@ module SAML2
124
125
  end
125
126
 
126
127
  def from_xml(node)
128
+ super
127
129
  @attributes = node.xpath('saml:Attribute', Namespaces::ALL).map do |attr|
128
130
  Attribute.from_xml(attr)
129
131
  end
@@ -52,6 +52,7 @@ module SAML2
52
52
  end
53
53
 
54
54
  def from_xml(node)
55
+ super
55
56
  @name = node['ServiceName']
56
57
  @requested_attributes = load_object_array(node, "md:RequestedAttribute", RequestedAttribute)
57
58
  end
@@ -2,49 +2,29 @@ require 'base64'
2
2
  require 'zlib'
3
3
 
4
4
  require 'saml2/attribute_consuming_service'
5
+ require 'saml2/bindings/http_redirect'
5
6
  require 'saml2/endpoint'
6
7
  require 'saml2/name_id'
7
8
  require 'saml2/namespaces'
9
+ require 'saml2/request'
8
10
  require 'saml2/schemas'
9
11
  require 'saml2/subject'
10
12
 
11
13
  module SAML2
12
- class MessageTooLarge < RuntimeError
13
- end
14
-
15
- class AuthnRequest
14
+ class AuthnRequest < Request
15
+ # deprecated; takes _just_ the SAMLRequest parameter's value
16
16
  def self.decode(authnrequest)
17
- begin
18
- raise MessageTooLarge if authnrequest.bytesize > SAML2.config[:max_message_size]
19
- authnrequest = Base64.decode64(authnrequest)
20
- zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
21
- xml = ''
22
- # do it in 1K slices, so we can protect against bombs
23
- (0..authnrequest.bytesize / 1024).each do |i|
24
- xml.concat(zstream.inflate(authnrequest.byteslice(i * 1024, 1024)))
25
- raise MessageTooLarge if xml.bytesize > SAML2.config[:max_message_size]
26
- end
27
- xml.concat(zstream.finish)
28
- raise MessageTooLarge if xml.bytesize > SAML2.config[:max_message_size]
29
-
30
- zstream.close
31
- rescue Zlib::DataError, Zlib::BufError
32
- end
33
- parse(xml)
34
- end
35
-
36
- def self.parse(authnrequest)
37
- new(Nokogiri::XML(authnrequest))
38
- end
39
-
40
- def initialize(document)
41
- @document = document
17
+ result, _relay_state = Bindings::HTTPRedirect.decode("http://host/?SAMLRequest=#{authnrequest}")
18
+ return nil unless result.is_a?(AuthnRequest)
19
+ result
20
+ rescue CorruptMessage
21
+ AuthnRequest.from_xml(Nokogiri::XML('<xml></xml>').root)
42
22
  end
43
23
 
44
24
  def valid_schema?
45
- return false unless Schemas.protocol.valid?(@document)
25
+ return false unless super
46
26
  # Check for the correct root element
47
- return false unless @document.at_xpath('/samlp:AuthnRequest', Namespaces::ALL)
27
+ return false unless xml.at_xpath('/samlp:AuthnRequest', Namespaces::ALL)
48
28
 
49
29
  true
50
30
  end
@@ -83,46 +63,38 @@ module SAML2
83
63
  true
84
64
  end
85
65
 
86
- def issuer
87
- @issuer ||= NameID.from_xml(@document.root.at_xpath('saml:Issuer', Namespaces::ALL))
88
- end
89
-
90
66
  def name_id_policy
91
- @name_id_policy ||= NameID::Policy.from_xml(@document.root.at_xpath('samlp:NameIDPolicy', Namespaces::ALL))
92
- end
93
-
94
- def id
95
- @document.root['ID']
67
+ @name_id_policy ||= NameID::Policy.from_xml(xml.at_xpath('samlp:NameIDPolicy', Namespaces::ALL))
96
68
  end
97
69
 
98
70
  attr_reader :assertion_consumer_service, :attribute_consuming_service
99
71
 
100
72
  def assertion_consumer_service_url
101
- @document.root['AssertionConsumerServiceURL']
73
+ xml['AssertionConsumerServiceURL']
102
74
  end
103
75
 
104
76
  def assertion_consumer_service_index
105
- @document.root['AssertionConsumerServiceIndex'] && @document.root['AssertionConsumerServiceIndex'].to_i
77
+ xml['AssertionConsumerServiceIndex'] && xml['AssertionConsumerServiceIndex'].to_i
106
78
  end
107
79
 
108
80
  def attribute_consuming_service_index
109
- @document.root['AttributeConsumerServiceIndex'] && @document.root['AttributeConsumerServiceIndex'].to_i
81
+ xml['AttributeConsumerServiceIndex'] && xml['AttributeConsumerServiceIndex'].to_i
110
82
  end
111
83
 
112
84
  def force_authn?
113
- @document.root['ForceAuthn']
85
+ xml['ForceAuthn']
114
86
  end
115
87
 
116
88
  def passive?
117
- @document.root['IsPassive']
89
+ xml['IsPassive']
118
90
  end
119
91
 
120
92
  def protocol_binding
121
- @document.root['ProtocolBinding']
93
+ xml['ProtocolBinding']
122
94
  end
123
95
 
124
96
  def subject
125
- @subject ||= Subject.from_xml(@document.at_xpath('saml:Subject', Namespaces::ALL))
97
+ @subject ||= Subject.from_xml(xml.at_xpath('saml:Subject', Namespaces::ALL))
126
98
  end
127
99
  end
128
100
  end
@@ -9,7 +9,10 @@ module SAML2
9
9
  result
10
10
  end
11
11
 
12
- def from_xml(_node)
12
+ attr_reader :xml
13
+
14
+ def from_xml(node)
15
+ @xml = node
13
16
  end
14
17
 
15
18
  def to_s
@@ -28,7 +31,7 @@ module SAML2
28
31
 
29
32
  def self.load_string_array(node, element)
30
33
  node.xpath(element, Namespaces::ALL).map do |element_node|
31
- element_node.content && element_node.content.strip
34
+ element_node.content&.strip
32
35
  end
33
36
  end
34
37
 
@@ -0,0 +1,7 @@
1
+ module SAML2
2
+ module Bindings
3
+ module Encodings
4
+ DEFLATE = 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE'.freeze
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,141 @@
1
+ require 'base64'
2
+ require 'uri'
3
+ require 'zlib'
4
+
5
+ require 'saml2/bindings'
6
+ require 'saml2/message'
7
+
8
+ module SAML2
9
+ module Bindings
10
+ module HTTPRedirect
11
+ URN ="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze
12
+
13
+ module SigAlgs
14
+ DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1".freeze
15
+ RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1".freeze
16
+
17
+ RECOGNIZED = [DSA_SHA1, RSA_SHA1].freeze
18
+ end
19
+
20
+ class << self
21
+ def decode(url, public_key: nil, public_key_used: nil)
22
+ uri = begin
23
+ URI.parse(url)
24
+ rescue URI::InvalidURIError
25
+ raise CorruptMessage
26
+ end
27
+
28
+ raise MissingMessage unless uri.query
29
+ query = URI.decode_www_form(uri.query)
30
+ base64 = query.assoc('SAMLRequest')&.last
31
+ if base64
32
+ message_param = 'SAMLRequest'
33
+ else
34
+ base64 = query.assoc('SAMLResponse')&.last
35
+ message_param = 'SAMLResponse'
36
+ end
37
+ encoding = query.assoc('SAMLEncoding')&.last
38
+ relay_state = query.assoc('RelayState')&.last
39
+ signature = query.assoc('Signature')&.last
40
+ sig_alg = query.assoc('SigAlg')&.last
41
+ raise MissingMessage unless base64
42
+
43
+ raise UnsupportedEncoding if encoding && encoding != Encodings::DEFLATE
44
+
45
+ raise MessageTooLarge if base64.bytesize > SAML2.config[:max_message_size]
46
+
47
+ deflated = begin
48
+ Base64.strict_decode64(base64)
49
+ rescue ArgumentError
50
+ raise CorruptMessage
51
+ end
52
+
53
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
54
+ xml = ''
55
+ begin
56
+ # do it in 1K slices, so we can protect against bombs
57
+ (0..deflated.bytesize / 1024).each do |i|
58
+ xml.concat(zstream.inflate(deflated.byteslice(i * 1024, 1024)))
59
+ raise MessageTooLarge if xml.bytesize > SAML2.config[:max_message_size]
60
+ end
61
+ xml.concat(zstream.finish)
62
+ raise MessageTooLarge if xml.bytesize > SAML2.config[:max_message_size]
63
+ rescue Zlib::DataError, Zlib::BufError
64
+ raise CorruptMessage
65
+ end
66
+
67
+ zstream.close
68
+ message = Message.parse(xml)
69
+ # if a block is provided, it's to fetch the proper certificate
70
+ # based on the contents of the message
71
+ public_key ||= yield(message, sig_alg) if block_given?
72
+ if public_key
73
+ raise UnsignedMessage unless signature
74
+ raise UnsupportedSignatureAlgorithm unless SigAlgs::RECOGNIZED.include?(sig_alg)
75
+
76
+ begin
77
+ signature = Base64.strict_decode64(signature)
78
+ rescue ArgumentError
79
+ raise CorruptMessage
80
+ end
81
+
82
+ base_string = find_raw_query_param(uri.query, message_param)
83
+ base_string << '&' << find_raw_query_param(uri.query, 'RelayState') if relay_state
84
+ base_string << '&' << find_raw_query_param(uri.query, 'SigAlg')
85
+
86
+ valid_signature = false
87
+ # there could be multiple certificates to try
88
+ Array(public_key).each do |key|
89
+ if key.verify(OpenSSL::Digest::SHA1.new, signature, base_string)
90
+ # notify the caller which certificate was used
91
+ public_key_used&.call(key)
92
+ valid_signature = true
93
+ break
94
+ end
95
+ end
96
+ raise InvalidSignature unless valid_signature
97
+ end
98
+ [message, relay_state]
99
+ end
100
+
101
+ def encode(message, relay_state: nil, private_key: nil)
102
+ result = URI.parse(message.destination)
103
+ original_query = URI.decode_www_form(result.query) if result.query
104
+ original_query ||= []
105
+ # remove any SAML protocol parameters
106
+ %w{SAMLEncoding SAMLRequest SAMLResponse RelayState SigAlg Signature}.each do |param|
107
+ original_query.delete_if { |(k, v)| k == param }
108
+ end
109
+
110
+ xml = message.to_s
111
+ zstream = Zlib::Deflate.new(Zlib::BEST_COMPRESSION, -Zlib::MAX_WBITS)
112
+ deflated = zstream.deflate(xml, Zlib::FINISH)
113
+ zstream.close
114
+ base64 = Base64.strict_encode64(deflated)
115
+
116
+ query = []
117
+ query << [message.is_a?(Request) ? 'SAMLRequest' : 'SAMLResponse', base64]
118
+ query << ['RelayState', relay_state] if relay_state
119
+ if private_key
120
+ query << ['SigAlg', SigAlgs::RSA_SHA1]
121
+ base_string = URI.encode_www_form(query)
122
+ signature = private_key.sign(OpenSSL::Digest::SHA1.new, base_string)
123
+ query << ['Signature', Base64.strict_encode64(signature)]
124
+ end
125
+
126
+ result.query = URI.encode_www_form(original_query + query)
127
+ result.to_s
128
+ end
129
+
130
+ private
131
+
132
+ # we need to find the param, and return it still encoded from the URL
133
+ def find_raw_query_param(query, param)
134
+ start = query.index(param)
135
+ finish = (query.index('&', start + param.length + 1) || 0) - 1
136
+ query[start..finish]
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -1,7 +1,7 @@
1
1
  require 'saml2/base'
2
2
 
3
3
  module SAML2
4
- class Contact
4
+ class Contact < Base
5
5
  module Type
6
6
  ADMINISTRATIVE = 'administrative'.freeze
7
7
  BILLING = 'billing'.freeze
@@ -12,27 +12,25 @@ module SAML2
12
12
 
13
13
  attr_accessor :type, :company, :given_name, :surname, :email_addresses, :telephone_numbers
14
14
 
15
- def self.from_xml(node)
16
- return nil unless node
17
-
18
- result = new(node['contactType'])
19
- company = node.at_xpath('md:Company', Namespaces::ALL)
20
- result.company = company && company.content && company.content.strip
21
- given_name = node.at_xpath('md:GivenName', Namespaces::ALL)
22
- result.given_name = given_name && given_name.content && given_name.content.strip
23
- surname = node.at_xpath('md:SurName', Namespaces::ALL)
24
- result.surname = surname && surname.content && surname.content.strip
25
- result.email_addresses = Base.load_string_array(node, 'md:EmailAddress')
26
- result.telephone_numbers = Base.load_string_array(node, 'md:TelephoneNumber')
27
- result
28
- end
29
-
30
15
  def initialize(type = Type::OTHER)
31
16
  @type = type
32
17
  @email_addresses = []
33
18
  @telephone_numbers = []
34
19
  end
35
20
 
21
+ def from_xml(node)
22
+ self.type = node['contactType']
23
+ company = node.at_xpath('md:Company', Namespaces::ALL)
24
+ self.company = company && company.content && company.content.strip
25
+ given_name = node.at_xpath('md:GivenName', Namespaces::ALL)
26
+ self.given_name = given_name && given_name.content && given_name.content.strip
27
+ surname = node.at_xpath('md:SurName', Namespaces::ALL)
28
+ self.surname = surname && surname.content && surname.content.strip
29
+ self.email_addresses = load_string_array(node, 'md:EmailAddress')
30
+ self.telephone_numbers = load_string_array(node, 'md:TelephoneNumber')
31
+ self
32
+ end
33
+
36
34
  def build(builder)
37
35
  builder['md'].ContactPerson('contactType' => type) do |contact_person|
38
36
  contact_person['md'].Company(company) if company
@@ -1,12 +1,10 @@
1
+ require 'saml2/bindings/http_redirect'
2
+
1
3
  module SAML2
2
4
  class Endpoint < Base
3
5
  module Bindings
4
6
  HTTP_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze
5
- HTTP_REDIRECT = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze
6
- end
7
-
8
- module Encodings
9
- DEFLATE= "urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE".freeze
7
+ HTTP_REDIRECT = ::SAML2::Bindings::HTTPRedirect::URN
10
8
  end
11
9
 
12
10
  attr_reader :location, :binding
@@ -20,6 +18,7 @@ module SAML2
20
18
  end
21
19
 
22
20
  def from_xml(node)
21
+ super
23
22
  @location = node['Location']
24
23
  @binding = node['Binding']
25
24
  end
@@ -31,7 +30,7 @@ module SAML2
31
30
  class Indexed < Endpoint
32
31
  include IndexedObject
33
32
 
34
- def initialize(location = nil, index = nil, is_default = false, binding = Bindings::HTTP_POST)
33
+ def initialize(location = nil, index = nil, is_default = nil, binding = Bindings::HTTP_POST)
35
34
  super(location, binding)
36
35
  @index, @is_default = index, is_default
37
36
  end
@@ -26,34 +26,40 @@ module SAML2
26
26
  end
27
27
  end
28
28
 
29
- class Group < Array
30
- def self.from_xml(node)
31
- node && new.from_xml(node)
29
+ class Group < Base
30
+ include Enumerable
31
+ [:each, :[]].each do |method|
32
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
33
+ def #{method}(*args, &block)
34
+ @entities.#{method}(*args, &block)
35
+ end
36
+ RUBY
32
37
  end
33
38
 
34
39
  def initialize
40
+ @entities = []
35
41
  @valid_until = nil
36
42
  end
37
43
 
38
44
  def from_xml(node)
39
- @root = node
45
+ super
40
46
  remove_instance_variable(:@valid_until)
41
- replace(Base.load_object_array(@root, "md:EntityDescriptor|md:EntitiesDescriptor",
47
+ @entities = Base.load_object_array(xml, "md:EntityDescriptor|md:EntitiesDescriptor",
42
48
  'EntityDescriptor' => Entity,
43
- 'EntitiesDescriptor' => Group))
49
+ 'EntitiesDescriptor' => Group)
44
50
  end
45
51
 
46
52
  def valid_schema?
47
- Schemas.federation.valid?(@root.document)
53
+ Schemas.federation.valid?(xml.document)
48
54
  end
49
55
 
50
56
  def signature
51
57
  unless instance_variable_defined?(:@signature)
52
- @signature = @root.at_xpath('dsig:Signature', Namespaces::ALL)
58
+ @signature = xml.at_xpath('dsig:Signature', Namespaces::ALL)
53
59
  signed_node = @signature.at_xpath('dsig:SignedInfo/dsig:Reference', Namespaces::ALL)['URI']
54
60
  # validating the schema will automatically add ID attributes, so check that first
55
- @root.set_id_attribute('ID') unless @root.document.get_id(@root['ID'])
56
- @signature = nil unless signed_node == "##{@root['ID']}"
61
+ xml.set_id_attribute('ID') unless xml.document.get_id(xml['ID'])
62
+ @signature = nil unless signed_node == "##{xml['ID']}"
57
63
  end
58
64
  @signature
59
65
  end
@@ -68,7 +74,7 @@ module SAML2
68
74
 
69
75
  def valid_until
70
76
  unless instance_variable_defined?(:@valid_until)
71
- @valid_until = @root['validUntil'] && Time.parse(@root['validUntil'])
77
+ @valid_until = xml['validUntil'] && Time.parse(xml['validUntil'])
72
78
  end
73
79
  @valid_until
74
80
  end
@@ -82,23 +88,22 @@ module SAML2
82
88
  end
83
89
 
84
90
  def from_xml(node)
85
- @root = node
91
+ super
86
92
  remove_instance_variable(:@valid_until)
87
93
  @roles = nil
88
- super
89
94
  end
90
95
 
91
96
  def valid_schema?
92
- Schemas.federation.valid?(@root.document)
97
+ Schemas.federation.valid?(xml.document)
93
98
  end
94
99
 
95
100
  def entity_id
96
- @entity_id || @root && @root['entityID']
101
+ @entity_id || xml && xml['entityID']
97
102
  end
98
103
 
99
104
  def valid_until
100
105
  unless instance_variable_defined?(:@valid_until)
101
- @valid_until = @root['validUntil'] && Time.parse(@root['validUntil'])
106
+ @valid_until = xml['validUntil'] && Time.parse(xml['validUntil'])
102
107
  end
103
108
  @valid_until
104
109
  end
@@ -112,8 +117,8 @@ module SAML2
112
117
  end
113
118
 
114
119
  def roles
115
- @roles ||= load_object_array(@root, 'md:IDPSSODescriptor', IdentityProvider) +
116
- load_object_array(@root, 'md:SPSSODescriptor', ServiceProvider)
120
+ @roles ||= load_object_array(xml, 'md:IDPSSODescriptor', IdentityProvider) +
121
+ load_object_array(xml, 'md:SPSSODescriptor', ServiceProvider)
117
122
  end
118
123
 
119
124
  def build(builder)