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.
- checksums.yaml +4 -4
- data/Rakefile +2 -7
- data/lib/saml2.rb +2 -0
- data/lib/saml2/attribute.rb +2 -0
- data/lib/saml2/attribute_consuming_service.rb +1 -0
- data/lib/saml2/authn_request.rb +19 -47
- data/lib/saml2/base.rb +5 -2
- data/lib/saml2/bindings.rb +7 -0
- data/lib/saml2/bindings/http_redirect.rb +141 -0
- data/lib/saml2/contact.rb +14 -16
- data/lib/saml2/endpoint.rb +5 -6
- data/lib/saml2/entity.rb +23 -18
- data/lib/saml2/identity_provider.rb +4 -4
- data/lib/saml2/indexed_object.rb +7 -3
- data/lib/saml2/key.rb +19 -1
- data/lib/saml2/logout_request.rb +43 -0
- data/lib/saml2/logout_response.rb +23 -0
- data/lib/saml2/message.rb +109 -0
- data/lib/saml2/name_id.rb +16 -8
- data/lib/saml2/organization_and_contacts.rb +2 -2
- data/lib/saml2/request.rb +8 -0
- data/lib/saml2/response.rb +7 -23
- data/lib/saml2/role.rb +2 -3
- data/lib/saml2/service_provider.rb +24 -2
- data/lib/saml2/sso.rb +2 -2
- data/lib/saml2/status.rb +28 -0
- data/lib/saml2/status_response.rb +33 -0
- data/lib/saml2/version.rb +1 -1
- data/spec/fixtures/identity_provider.xml +1 -0
- data/spec/fixtures/response_signed.xml +1 -1
- data/spec/fixtures/response_with_attribute_signed.xml +1 -1
- data/spec/lib/attribute_consuming_service_spec.rb +37 -37
- data/spec/lib/attribute_spec.rb +17 -17
- data/spec/lib/authn_request_spec.rb +15 -71
- data/spec/lib/bindings/http_redirect_spec.rb +151 -0
- data/spec/lib/conditions_spec.rb +10 -10
- data/spec/lib/entity_spec.rb +12 -12
- data/spec/lib/identity_provider_spec.rb +4 -4
- data/spec/lib/indexed_object_spec.rb +38 -7
- data/spec/lib/logout_request_spec.rb +31 -0
- data/spec/lib/logout_response_spec.rb +31 -0
- data/spec/lib/message_spec.rb +21 -0
- data/spec/lib/response_spec.rb +8 -9
- data/spec/lib/service_provider_spec.rb +29 -8
- data/spec/spec_helper.rb +0 -1
- metadata +41 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 382418cbc200bcf81a58ff09ebadd551d0c16101
|
4
|
+
data.tar.gz: 0f7318a0d65eb9b80afbe0405d6dc768cc4e3f0d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 '
|
6
|
-
|
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
|
data/lib/saml2.rb
CHANGED
data/lib/saml2/attribute.rb
CHANGED
@@ -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
|
data/lib/saml2/authn_request.rb
CHANGED
@@ -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
|
13
|
-
|
14
|
-
|
15
|
-
class AuthnRequest
|
14
|
+
class AuthnRequest < Request
|
15
|
+
# deprecated; takes _just_ the SAMLRequest parameter's value
|
16
16
|
def self.decode(authnrequest)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
25
|
+
return false unless super
|
46
26
|
# Check for the correct root element
|
47
|
-
return false unless
|
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(
|
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
|
-
|
73
|
+
xml['AssertionConsumerServiceURL']
|
102
74
|
end
|
103
75
|
|
104
76
|
def assertion_consumer_service_index
|
105
|
-
|
77
|
+
xml['AssertionConsumerServiceIndex'] && xml['AssertionConsumerServiceIndex'].to_i
|
106
78
|
end
|
107
79
|
|
108
80
|
def attribute_consuming_service_index
|
109
|
-
|
81
|
+
xml['AttributeConsumerServiceIndex'] && xml['AttributeConsumerServiceIndex'].to_i
|
110
82
|
end
|
111
83
|
|
112
84
|
def force_authn?
|
113
|
-
|
85
|
+
xml['ForceAuthn']
|
114
86
|
end
|
115
87
|
|
116
88
|
def passive?
|
117
|
-
|
89
|
+
xml['IsPassive']
|
118
90
|
end
|
119
91
|
|
120
92
|
def protocol_binding
|
121
|
-
|
93
|
+
xml['ProtocolBinding']
|
122
94
|
end
|
123
95
|
|
124
96
|
def subject
|
125
|
-
@subject ||= Subject.from_xml(
|
97
|
+
@subject ||= Subject.from_xml(xml.at_xpath('saml:Subject', Namespaces::ALL))
|
126
98
|
end
|
127
99
|
end
|
128
100
|
end
|
data/lib/saml2/base.rb
CHANGED
@@ -9,7 +9,10 @@ module SAML2
|
|
9
9
|
result
|
10
10
|
end
|
11
11
|
|
12
|
-
|
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
|
34
|
+
element_node.content&.strip
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
@@ -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
|
data/lib/saml2/contact.rb
CHANGED
@@ -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
|
data/lib/saml2/endpoint.rb
CHANGED
@@ -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 =
|
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 =
|
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
|
data/lib/saml2/entity.rb
CHANGED
@@ -26,34 +26,40 @@ module SAML2
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
class Group <
|
30
|
-
|
31
|
-
|
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
|
-
|
45
|
+
super
|
40
46
|
remove_instance_variable(:@valid_until)
|
41
|
-
|
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?(
|
53
|
+
Schemas.federation.valid?(xml.document)
|
48
54
|
end
|
49
55
|
|
50
56
|
def signature
|
51
57
|
unless instance_variable_defined?(:@signature)
|
52
|
-
@signature =
|
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
|
-
|
56
|
-
@signature = nil unless signed_node == "##{
|
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 =
|
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
|
-
|
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?(
|
97
|
+
Schemas.federation.valid?(xml.document)
|
93
98
|
end
|
94
99
|
|
95
100
|
def entity_id
|
96
|
-
@entity_id ||
|
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 =
|
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(
|
116
|
-
load_object_array(
|
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)
|