saml2 1.0.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 +7 -0
- data/Rakefile +13 -0
- data/app/views/saml2/http_post.html.erb +14 -0
- data/lib/saml2.rb +9 -0
- data/lib/saml2/assertion.rb +37 -0
- data/lib/saml2/attribute.rb +127 -0
- data/lib/saml2/attribute/x500.rb +79 -0
- data/lib/saml2/attribute_consuming_service.rb +76 -0
- data/lib/saml2/authn_request.rb +116 -0
- data/lib/saml2/authn_statement.rb +26 -0
- data/lib/saml2/base.rb +53 -0
- data/lib/saml2/contact.rb +50 -0
- data/lib/saml2/endpoint.rb +46 -0
- data/lib/saml2/engine.rb +4 -0
- data/lib/saml2/entity.rb +84 -0
- data/lib/saml2/identity_provider.rb +57 -0
- data/lib/saml2/indexed_object.rb +59 -0
- data/lib/saml2/key.rb +46 -0
- data/lib/saml2/name_id.rb +60 -0
- data/lib/saml2/namespaces.rb +21 -0
- data/lib/saml2/organization.rb +74 -0
- data/lib/saml2/organization_and_contacts.rb +35 -0
- data/lib/saml2/profiles.rb +7 -0
- data/lib/saml2/response.rb +92 -0
- data/lib/saml2/role.rb +53 -0
- data/lib/saml2/schemas.rb +18 -0
- data/lib/saml2/service_provider.rb +30 -0
- data/lib/saml2/sso.rb +36 -0
- data/lib/saml2/subject.rb +49 -0
- data/lib/saml2/version.rb +3 -0
- data/schemas/saml-schema-assertion-2.0.xsd +283 -0
- data/schemas/saml-schema-metadata-2.0.xsd +339 -0
- data/schemas/saml-schema-protocol-2.0.xsd +302 -0
- data/schemas/xenc-schema.xsd +136 -0
- data/schemas/xml.xsd +287 -0
- data/schemas/xmldsig-core-schema.xsd +309 -0
- data/spec/fixtures/authnrequest.xml +12 -0
- data/spec/fixtures/calculated.txt +1 -0
- data/spec/fixtures/certificate.pem +25 -0
- data/spec/fixtures/entities.xml +13 -0
- data/spec/fixtures/privatekey.key +27 -0
- data/spec/fixtures/response_signed.xml +47 -0
- data/spec/fixtures/response_with_attribute_signed.xml +47 -0
- data/spec/fixtures/service_provider.xml +79 -0
- data/spec/fixtures/xmlsec.txt +1 -0
- data/spec/lib/attribute_consuming_service_spec.rb +74 -0
- data/spec/lib/attribute_spec.rb +39 -0
- data/spec/lib/authn_request_spec.rb +52 -0
- data/spec/lib/entity_spec.rb +45 -0
- data/spec/lib/identity_provider_spec.rb +23 -0
- data/spec/lib/indexed_object_spec.rb +38 -0
- data/spec/lib/response_spec.rb +60 -0
- data/spec/lib/service_provider_spec.rb +30 -0
- data/spec/spec_helper.rb +6 -0
- metadata +191 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e6cca9457d5ec3880dd5e3972e96c9275973e3b1
|
4
|
+
data.tar.gz: 2c2276cd951f78f8d6fb34eb0322e019a1de152b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cdb33be918597518b11aa3ebfb3e8ddcbb788f517dd53282e24305d71070398bcc9ff0d4045c1ec0e61dafbe42d7866a89ef2ed16cfd97219a13d139beac640d
|
7
|
+
data.tar.gz: 74e237a2935f8b8cef4bf8f32a768d10947f8eee1bcd5f6c84df0fdf61318f1d5d7a6e73a185c66ce6d8c48992d1ba83091d04f4f545ce3350192d0d26907f36
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
6
|
+
</head>
|
7
|
+
<body onload="document.forms[0].submit();" style="visibility:hidden;">
|
8
|
+
<%= form_tag(@saml_acs_url) do %>
|
9
|
+
<%= hidden_field_tag("SAMLResponse", @saml_response) %>
|
10
|
+
<%= hidden_field_tag("RelayState", @relay_state) if @relay_state %>
|
11
|
+
<%= submit_tag "Submit" %>
|
12
|
+
<% end %>
|
13
|
+
</body>
|
14
|
+
</html>
|
data/lib/saml2.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module SAML2
|
2
|
+
class Assertion
|
3
|
+
attr_reader :id, :issue_instant, :statements
|
4
|
+
attr_accessor :issuer, :subject
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@id = "_#{SecureRandom.uuid}"
|
8
|
+
@issue_instant = Time.now.utc
|
9
|
+
@statements = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def sign(x509_certificate, private_key, algorithm_name = :sha256)
|
13
|
+
to_xml
|
14
|
+
|
15
|
+
@xml.set_id_attribute('ID')
|
16
|
+
@xml.sign!(cert: x509_certificate, key: private_key, digest_alg: algorithm_name.to_s, signature_alg: "rsa-#{algorithm_name}", uri: "##{id}")
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_xml
|
21
|
+
@xml ||= Nokogiri::XML::Builder.new do |builder|
|
22
|
+
builder['saml'].Assertion(
|
23
|
+
'xmlns:saml' => Namespaces::SAML,
|
24
|
+
ID: id,
|
25
|
+
Version: '2.0',
|
26
|
+
IssueInstant: issue_instant.iso8601
|
27
|
+
) do |builder|
|
28
|
+
issuer.build(builder, element: 'Issuer')
|
29
|
+
|
30
|
+
subject.build(builder)
|
31
|
+
|
32
|
+
statements.each { |stmt| stmt.build(builder) }
|
33
|
+
end
|
34
|
+
end.doc.root
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'saml2/base'
|
2
|
+
require 'saml2/namespaces'
|
3
|
+
|
4
|
+
module SAML2
|
5
|
+
class AttributeType < Base
|
6
|
+
attr_accessor :name, :friendly_name, :name_format
|
7
|
+
|
8
|
+
def initialize(name = nil, friendly_name = nil, name_format = nil)
|
9
|
+
@name, @friendly_name, @name_format = name, friendly_name, name_format
|
10
|
+
end
|
11
|
+
|
12
|
+
def from_xml(node)
|
13
|
+
@name = node['Name']
|
14
|
+
@friendly_name = node['FriendlyName']
|
15
|
+
@name_format = node['NameFormat']
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Attribute < AttributeType
|
21
|
+
module NameFormats
|
22
|
+
BASIC = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic".freeze
|
23
|
+
UNSPECIFIED = "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified".freeze
|
24
|
+
URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri".freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def subclasses
|
29
|
+
@subclasses ||= []
|
30
|
+
end
|
31
|
+
|
32
|
+
def inherited(klass)
|
33
|
+
subclasses << klass
|
34
|
+
end
|
35
|
+
|
36
|
+
def from_xml(node)
|
37
|
+
# pass through for subclasses
|
38
|
+
super unless self == Attribute
|
39
|
+
|
40
|
+
# look for an appropriate subclass
|
41
|
+
klass = subclasses.find { |klass| klass.recognizes?(node) }
|
42
|
+
if klass
|
43
|
+
klass.from_xml(node)
|
44
|
+
else
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def create(name, value = nil)
|
50
|
+
klass = subclasses.find { |klass| klass.recognizes?(name) } || self
|
51
|
+
klass.new(name, value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_accessor :value
|
56
|
+
|
57
|
+
def initialize(name = nil, value = nil, friendly_name = nil, name_format = nil)
|
58
|
+
super(name, friendly_name, name_format)
|
59
|
+
@value = value
|
60
|
+
end
|
61
|
+
|
62
|
+
def build(builder)
|
63
|
+
builder['saml'].Attribute('Name' => name) do |builder|
|
64
|
+
builder.parent['FriendlyName'] = friendly_name if friendly_name
|
65
|
+
builder.parent['NameFormat'] = name_format if name_format
|
66
|
+
Array(value).each do |val|
|
67
|
+
xsi_type, val = convert_to_xsi(value)
|
68
|
+
builder['saml'].AttributeValue(val) do |builder|
|
69
|
+
builder.parent['xsi:type'] = xsi_type if xsi_type
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def from_xml(node)
|
76
|
+
@value = node.xpath('saml:AttributeValue', Namespaces::ALL).map do |node|
|
77
|
+
convert_from_xsi(node['xsi:type'], node.content && node.content.strip)
|
78
|
+
end
|
79
|
+
@value = @value.first if @value.length == 1
|
80
|
+
super
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
XSI_TYPES = {
|
85
|
+
'xsd:string' => [String, nil, nil],
|
86
|
+
nil => [DateTime, ->(v) { v.iso8601 }, ->(v) { DateTime.parse(v) if v }]
|
87
|
+
}.freeze
|
88
|
+
|
89
|
+
def convert_to_xsi(value)
|
90
|
+
xsi_type = nil
|
91
|
+
converter = nil
|
92
|
+
XSI_TYPES.each do |type, (klass, to_xsi, from_xsi)|
|
93
|
+
if klass === value
|
94
|
+
xsi_type = type
|
95
|
+
converter = to_xsi
|
96
|
+
break
|
97
|
+
end
|
98
|
+
end
|
99
|
+
value = converter.call(value) if converter
|
100
|
+
[xsi_type, value]
|
101
|
+
end
|
102
|
+
|
103
|
+
def convert_from_xsi(type, value)
|
104
|
+
info = XSI_TYPES[type]
|
105
|
+
if info && info.last
|
106
|
+
value = info.last.call(value)
|
107
|
+
end
|
108
|
+
value
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class AttributeStatement
|
113
|
+
attr_reader :attributes
|
114
|
+
|
115
|
+
def initialize(attributes)
|
116
|
+
@attributes = attributes
|
117
|
+
end
|
118
|
+
|
119
|
+
def build(builder)
|
120
|
+
builder['saml'].AttributeStatement('xmlns:xsi' => Namespaces::XSI) do |builder|
|
121
|
+
@attributes.each { |attr| attr.build(builder) }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
require 'saml2/attribute/x500'
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module SAML2
|
2
|
+
class Attribute
|
3
|
+
class X500 < Attribute
|
4
|
+
GIVEN_NAME = 'urn:oid:2.5.4.42'.freeze
|
5
|
+
SN = SURNAME = 'urn:oid:2.5.4.4'.freeze
|
6
|
+
# https://www.ietf.org/rfc/rfc2798.txt
|
7
|
+
module InetOrgPerson
|
8
|
+
DISPLAY_NAME = 'urn:oid:2.16.840.1.113730.3.1.241'.freeze
|
9
|
+
EMPLOYEE_NUMBER = 'urn:oid:2.16.840.1.113730.3.1.3'.freeze
|
10
|
+
EMPLOYEE_TYPE = 'urn:oid:2.16.840.1.113730.3.1.4'.freeze
|
11
|
+
PREFERRED_LANGUAGE = 'urn:oid:2.16.840.1.113730.3.1.39'.freeze
|
12
|
+
end
|
13
|
+
# https://www.internet2.edu/media/medialibrary/2013/09/04/internet2-mace-dir-eduperson-201203.html
|
14
|
+
module EduPerson
|
15
|
+
AFFILIATION = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.1'.freeze
|
16
|
+
ASSURANCE = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.11'.freeze
|
17
|
+
ENTITLEMENT = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.7'.freeze
|
18
|
+
NICKNAME = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.2'.freeze
|
19
|
+
ORG_D_N = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.3'.freeze
|
20
|
+
PRIMARY_AFFILIATION = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.5'.freeze
|
21
|
+
PRIMARY_ORG_UNIT_D_N = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.8'.freeze
|
22
|
+
PRINCIPAL_NAME = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6'.freeze
|
23
|
+
SCOPED_AFFILIATION = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.9'.freeze
|
24
|
+
TARGETED_I_D = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.10'.freeze
|
25
|
+
UNIT_D_N = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.4'.freeze
|
26
|
+
end
|
27
|
+
# http://www.ietf.org/rfc/rfc4524.txt
|
28
|
+
MAIL = 'urn:oid:0.9.2342.19200300.100.1.3'.freeze
|
29
|
+
|
30
|
+
def self.recognizes?(name_or_node)
|
31
|
+
if name_or_node.is_a?(Nokogiri::XML::Element)
|
32
|
+
!!name_or_node.at_xpath("@x500:Encoding", Namespaces::ALL)
|
33
|
+
else
|
34
|
+
FRIENDLY_NAMES.include?(name_or_node) || OIDS.include?(name_or_node)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(name = nil, value = nil)
|
39
|
+
# if they pass an OID, infer the friendly name
|
40
|
+
friendly_name = OIDS[name]
|
41
|
+
unless friendly_name
|
42
|
+
# if they pass a friendly name, infer the OID
|
43
|
+
proper_name = FRIENDLY_NAMES[name]
|
44
|
+
if proper_name
|
45
|
+
name, friendly_name = proper_name, name
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
super(name, value, friendly_name, NameFormats::URI)
|
50
|
+
end
|
51
|
+
|
52
|
+
def build(builder)
|
53
|
+
super
|
54
|
+
attr = builder.parent.last_element_child
|
55
|
+
attr.add_namespace_definition('x500', Namespaces::X500)
|
56
|
+
attr['x500:Encoding'] = 'LDAP'
|
57
|
+
end
|
58
|
+
|
59
|
+
# build hashes out of our known attribute names for quick lookup
|
60
|
+
FRIENDLY_NAMES = ([self] + constants).inject({}) do |hash, mod|
|
61
|
+
mod = const_get(mod) unless mod.is_a?(Module)
|
62
|
+
next hash unless mod.is_a?(Module)
|
63
|
+
# Don't look in modules inherited from parent classes
|
64
|
+
next hash unless mod.name.start_with?(self.name)
|
65
|
+
mod.constants.each do |key|
|
66
|
+
value = mod.const_get(key)
|
67
|
+
next unless value.is_a?(String)
|
68
|
+
key = key.to_s.downcase.gsub(/_\w/) { |c| c[1].upcase }
|
69
|
+
# eduPerson prefixes all of their names
|
70
|
+
key = "eduPerson#{key.sub(/^\w/) { |c| c.upcase }}" if mod == EduPerson
|
71
|
+
hash[key] = value
|
72
|
+
end
|
73
|
+
hash
|
74
|
+
end.freeze
|
75
|
+
OIDS = FRIENDLY_NAMES.invert.freeze
|
76
|
+
private_constant :FRIENDLY_NAMES, :OIDS
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'saml2/attribute'
|
2
|
+
require 'saml2/indexed_object'
|
3
|
+
require 'saml2/namespaces'
|
4
|
+
|
5
|
+
module SAML2
|
6
|
+
class RequestedAttribute < AttributeType
|
7
|
+
def initialize(name = nil, is_required = nil, name_format = nil)
|
8
|
+
super(name, name_format)
|
9
|
+
@is_required = is_required
|
10
|
+
end
|
11
|
+
|
12
|
+
def from_xml(node)
|
13
|
+
@is_required = node['isRequired'] && node['isRequired'] == 'true'
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def required?
|
18
|
+
@is_required
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class RequiredAttributeMissing < RuntimeError
|
23
|
+
attr_reader :requested_attribute
|
24
|
+
|
25
|
+
def initialize(requested_attribute)
|
26
|
+
super("Required attribute #{requested_attribute.name} not provided")
|
27
|
+
@requested_attribute = requested_attribute
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class AttributeConsumingService < Base
|
32
|
+
include IndexedObject
|
33
|
+
|
34
|
+
attr_reader :name, :requested_attributes
|
35
|
+
|
36
|
+
def initialize(name = nil, requested_attributes = [])
|
37
|
+
@name, @requested_attributes = name, requested_attributes
|
38
|
+
end
|
39
|
+
|
40
|
+
def from_xml(node)
|
41
|
+
@name = node['ServiceName']
|
42
|
+
@requested_attributes = load_object_array(node, "md:RequestedAttribute", RequestedAttribute)
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_statement(attributes)
|
47
|
+
if attributes.is_a?(Hash)
|
48
|
+
attributes = attributes.map { |k, v| Attribute.create(k, v) }
|
49
|
+
end
|
50
|
+
|
51
|
+
attributes_hash = {}
|
52
|
+
attributes.each do |attr|
|
53
|
+
attr.value = attr.value.call if attr.value.respond_to?(:call)
|
54
|
+
attributes_hash[[attr.name, attr.name_format]] = attr
|
55
|
+
if attr.name_format
|
56
|
+
attributes_hash[[attr.name, nil]] = attr
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
attributes = []
|
61
|
+
requested_attributes.each do |requested_attr|
|
62
|
+
attr = attributes_hash[[requested_attr.name, requested_attr.name_format]]
|
63
|
+
if requested_attr.name_format
|
64
|
+
attr ||= attributes_hash[[requested_attr.name, nil]]
|
65
|
+
end
|
66
|
+
if attr
|
67
|
+
attributes << attr
|
68
|
+
elsif requested_attr.required?
|
69
|
+
raise RequiredAttributeMissing.new(requested_attr)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
return nil if attributes.empty?
|
73
|
+
AttributeStatement.new(attributes)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'zlib'
|
3
|
+
|
4
|
+
require 'saml2/attribute_consuming_service'
|
5
|
+
require 'saml2/endpoint'
|
6
|
+
require 'saml2/name_id'
|
7
|
+
require 'saml2/namespaces'
|
8
|
+
require 'saml2/schemas'
|
9
|
+
require 'saml2/subject'
|
10
|
+
|
11
|
+
module SAML2
|
12
|
+
class AuthnRequest
|
13
|
+
def self.decode(authnrequest)
|
14
|
+
begin
|
15
|
+
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
16
|
+
authnrequest = zstream.inflate(Base64.decode64(authnrequest))
|
17
|
+
zstream.finish
|
18
|
+
zstream.close
|
19
|
+
rescue Zlib::BufError
|
20
|
+
end
|
21
|
+
parse(authnrequest)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.parse(authnrequest)
|
25
|
+
new(Nokogiri::XML(authnrequest))
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(document)
|
29
|
+
@document = document
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid_schema?
|
33
|
+
return false unless Schemas.protocol.valid?(@document)
|
34
|
+
# Check for the correct root element
|
35
|
+
return false unless @document.at_xpath('/samlp:AuthnRequest', Namespaces::ALL)
|
36
|
+
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid_web_browser_sso_profile?
|
41
|
+
return false unless issuer
|
42
|
+
return false if issuer.format && issuer.format != NameID::Format::ENTITY
|
43
|
+
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def valid_interoperable_profile?
|
48
|
+
# It's a subset of Web Browser SSO profile
|
49
|
+
return false unless valid_web_browser_sso_profile?
|
50
|
+
|
51
|
+
return false unless assertion_consumer_service_url
|
52
|
+
return false if protocol_binding && protocol_binding != Endpoint::Bindings::HTTP_POST
|
53
|
+
return false if subject
|
54
|
+
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
def resolve(service_provider)
|
59
|
+
# TODO: check signature if present
|
60
|
+
|
61
|
+
if assertion_consumer_service_url
|
62
|
+
@assertion_consumer_service = service_provider.assertion_consumer_services.find { |acs| acs.location == assertion_consumer_service_url }
|
63
|
+
else
|
64
|
+
@assertion_consumer_service = service_provider.assertion_consumer_services.resolve(assertion_consumer_service_index)
|
65
|
+
end
|
66
|
+
@attribute_consuming_service = service_provider.attribute_consuming_services.resolve(attribute_consuming_service_index)
|
67
|
+
|
68
|
+
return false unless @assertion_consumer_service
|
69
|
+
return false if attribute_consuming_service_index && !@attribute_consuming_service
|
70
|
+
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
def issuer
|
75
|
+
@issuer ||= NameID.from_xml(@document.root.at_xpath('saml:Issuer', Namespaces::ALL))
|
76
|
+
end
|
77
|
+
|
78
|
+
def name_id_policy
|
79
|
+
@name_id_policy ||= NameID::Policy.from_xml(@document.root.at_xpath('samlp:NameIDPolicy', Namespaces::ALL))
|
80
|
+
end
|
81
|
+
|
82
|
+
def id
|
83
|
+
@document.root['ID']
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_reader :assertion_consumer_service, :attribute_consuming_service
|
87
|
+
|
88
|
+
def assertion_consumer_service_url
|
89
|
+
@document.root['AssertionConsumerServiceURL']
|
90
|
+
end
|
91
|
+
|
92
|
+
def assertion_consumer_service_index
|
93
|
+
@document.root['AssertionConsumerServiceIndex'] && @document.root['AssertionConsumerServiceIndex'].to_i
|
94
|
+
end
|
95
|
+
|
96
|
+
def attribute_consuming_service_index
|
97
|
+
@document.root['AttributeConsumerServiceIndex'] && @document.root['AttributeConsumerServiceIndex'].to_i
|
98
|
+
end
|
99
|
+
|
100
|
+
def force_authn?
|
101
|
+
@document.root['ForceAuthn']
|
102
|
+
end
|
103
|
+
|
104
|
+
def passive?
|
105
|
+
@document.root['IsPassive']
|
106
|
+
end
|
107
|
+
|
108
|
+
def protocol_binding
|
109
|
+
@document.root['ProtocolBinding']
|
110
|
+
end
|
111
|
+
|
112
|
+
def subject
|
113
|
+
@subject ||= Subject.from_xml(@document.at_xpath('saml:Subject', Namespaces::ALL))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|