saml2 2.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/saml2.rb +2 -0
  3. data/lib/saml2/assertion.rb +6 -0
  4. data/lib/saml2/attribute.rb +45 -13
  5. data/lib/saml2/attribute/x500.rb +32 -19
  6. data/lib/saml2/attribute_consuming_service.rb +52 -4
  7. data/lib/saml2/authn_request.rb +39 -3
  8. data/lib/saml2/authn_statement.rb +23 -11
  9. data/lib/saml2/base.rb +36 -0
  10. data/lib/saml2/bindings.rb +3 -1
  11. data/lib/saml2/bindings/http_post.rb +17 -1
  12. data/lib/saml2/bindings/http_redirect.rb +54 -9
  13. data/lib/saml2/conditions.rb +43 -16
  14. data/lib/saml2/contact.rb +17 -6
  15. data/lib/saml2/endpoint.rb +13 -0
  16. data/lib/saml2/engine.rb +2 -0
  17. data/lib/saml2/entity.rb +20 -0
  18. data/lib/saml2/identity_provider.rb +11 -1
  19. data/lib/saml2/indexed_object.rb +13 -3
  20. data/lib/saml2/key.rb +89 -32
  21. data/lib/saml2/localized_name.rb +8 -0
  22. data/lib/saml2/logout_request.rb +12 -3
  23. data/lib/saml2/logout_response.rb +9 -0
  24. data/lib/saml2/message.rb +38 -7
  25. data/lib/saml2/name_id.rb +42 -16
  26. data/lib/saml2/namespaces.rb +10 -8
  27. data/lib/saml2/organization.rb +5 -0
  28. data/lib/saml2/organization_and_contacts.rb +5 -0
  29. data/lib/saml2/request.rb +3 -0
  30. data/lib/saml2/requested_authn_context.rb +7 -1
  31. data/lib/saml2/response.rb +20 -2
  32. data/lib/saml2/role.rb +12 -2
  33. data/lib/saml2/schemas.rb +2 -0
  34. data/lib/saml2/service_provider.rb +6 -0
  35. data/lib/saml2/signable.rb +32 -2
  36. data/lib/saml2/sso.rb +7 -0
  37. data/lib/saml2/status.rb +8 -1
  38. data/lib/saml2/status_response.rb +7 -1
  39. data/lib/saml2/subject.rb +22 -5
  40. data/lib/saml2/version.rb +3 -1
  41. data/spec/lib/bindings/http_redirect_spec.rb +23 -2
  42. data/spec/lib/conditions_spec.rb +10 -11
  43. data/spec/lib/identity_provider_spec.rb +1 -1
  44. data/spec/lib/service_provider_spec.rb +7 -2
  45. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c776c54f1dc7f9b26b39544abd6bdde6c2f68a1f28339627ad9d8745cdfc4ee7
4
- data.tar.gz: 703170dd49abeb43b09070b6000f748737d1ccbe0374b154c274f84065935daa
3
+ metadata.gz: 7882f9e6593924c18faed398409e973ca227162a990557156d7992a51c4f7cdb
4
+ data.tar.gz: c862c3fd1c560068e59340ba4f19a3d810807b6c22a4c36c07fb0ef9a6711132
5
5
  SHA512:
6
- metadata.gz: 74fe75be6913966fd33a9e72535398a2ecc6916f483aeb47b3202afba09fb175b3c2234c8dfb63a0d48b1aa9f3536204635471e0ab73c125b8276d063838e7cb
7
- data.tar.gz: dd4f61cc065ebfc647c4306a084b56e5eb5fd57877898f05a67a09c5df87837f34d8c503ca933aa2f50cc770f44e6712bc0ee9192a0f9d07a48d8fb4a974154f
6
+ metadata.gz: 89aa07641f0625f8f9fd781cb41a169349567531984c1fe2adc278ae0568687c4e4822be1a9c00e0c561116ffbc39d140abde34a1be439bd36b1a3869d34dcf5
7
+ data.tar.gz: f07d6fbf9dc816d3223ae9cd1728b3232b469b61057ae7e6e14518efd939e3e8e4aff5c1e3b4a3c56584d7e5dbc9abc33dab180452a8b5d9b5ff7e34012c15e3
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'saml2/authn_request'
2
4
  require 'saml2/entity'
3
5
  require 'saml2/logout_request'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'saml2/conditions'
2
4
 
3
5
  module SAML2
@@ -16,6 +18,7 @@ module SAML2
16
18
  @statements = nil
17
19
  end
18
20
 
21
+ # @return [Subject, nil]
19
22
  def subject
20
23
  if xml && !instance_variable_defined?(:@subject)
21
24
  @subject = Subject.from_xml(xml.at_xpath('saml:Subject', Namespaces::ALL))
@@ -23,14 +26,17 @@ module SAML2
23
26
  @subject
24
27
  end
25
28
 
29
+ # @return [Conditions]
26
30
  def conditions
27
31
  @conditions ||= Conditions.from_xml(xml.at_xpath('saml:Conditions', Namespaces::ALL))
28
32
  end
29
33
 
34
+ # @return [Array<AuthnStatement, AttributeStatement>]
30
35
  def statements
31
36
  @statements ||= load_object_array(xml, 'saml:AuthnStatement|saml:AttributeStatement')
32
37
  end
33
38
 
39
+ # (see Base#build)
34
40
  def build(builder)
35
41
  builder['saml'].Assertion(
36
42
  'xmlns:saml' => Namespaces::SAML
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'date'
2
4
 
3
5
  require 'active_support/core_ext/array/wrap'
@@ -8,20 +10,13 @@ require 'saml2/namespaces'
8
10
  module SAML2
9
11
  class Attribute < Base
10
12
  module NameFormats
11
- BASIC = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic".freeze
12
- UNSPECIFIED = "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified".freeze
13
- URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri".freeze
13
+ BASIC = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
14
+ UNSPECIFIED = "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"
15
+ URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
14
16
  end
15
17
 
16
18
  class << self
17
- def subclasses
18
- @subclasses ||= []
19
- end
20
-
21
- def inherited(klass)
22
- subclasses << klass
23
- end
24
-
19
+ # (see Base.from_xml)
25
20
  def from_xml(node)
26
21
  # pass through for subclasses
27
22
  return super unless self == Attribute
@@ -31,20 +26,43 @@ module SAML2
31
26
  klass ? klass.from_xml(node) : super
32
27
  end
33
28
 
29
+ # Create an appropriate object to represent an attribute.
30
+ #
31
+ # This will create the most appropriate object (i.e. an
32
+ # {Attribute::X500} if possible) to represent this attribute,
33
+ # based on its name.
34
+ # @param name [String]
35
+ # The attribute name. This can be a friendly name, or a URI.
36
+ # @param value optional
37
+ # The attribute value.
38
+ # @return [Attribute]
34
39
  def create(name, value = nil)
35
40
  (class_for(name) || self).new(name, value)
36
41
  end
37
42
 
43
+ # The XML namespace that this attribute class serializes as.
44
+ # @return ['saml']
38
45
  def namespace
39
46
  'saml'
40
47
  end
41
48
 
49
+ # The XML element that this attribute class serializes as.
50
+ # @return ['Attribute']
42
51
  def element
43
52
  'Attribute'
44
53
  end
45
54
 
46
55
  protected
47
56
 
57
+ def subclasses
58
+ @subclasses ||= []
59
+ end
60
+
61
+ def inherited(klass)
62
+ subclasses << klass
63
+ end
64
+
65
+
48
66
  def class_for(name_or_node)
49
67
  subclasses.find do |klass|
50
68
  klass.respond_to?(:recognizes?) && klass.recognizes?(name_or_node)
@@ -52,12 +70,24 @@ module SAML2
52
70
  end
53
71
  end
54
72
 
55
- attr_accessor :name, :friendly_name, :name_format, :value
56
-
73
+ # @return [String]
74
+ attr_accessor :name
75
+ # @return [String, nil]
76
+ attr_accessor :friendly_name, :name_format
77
+ # @return [Object, nil]
78
+ attr_accessor :value
79
+
80
+ # Create a new generic Attribute
81
+ #
82
+ # @param name [String]
83
+ # @param value optional [Object, nil]
84
+ # @param friendly_name optional [String, nil]
85
+ # @param name_format optional [String, nil]
57
86
  def initialize(name = nil, value = nil, friendly_name = nil, name_format = nil)
58
87
  @name, @value, @friendly_name, @name_format = name, value, friendly_name, name_format
59
88
  end
60
89
 
90
+ # (see Base#build)
61
91
  def build(builder)
62
92
  builder[self.class.namespace].__send__(self.class.element, 'Name' => name) do |attribute|
63
93
  attribute.parent['FriendlyName'] = friendly_name if friendly_name
@@ -71,6 +101,7 @@ module SAML2
71
101
  end
72
102
  end
73
103
 
104
+ # (see Base#from_xml)
74
105
  def from_xml(node)
75
106
  super
76
107
  @name = node['Name']
@@ -87,6 +118,7 @@ module SAML2
87
118
  end
88
119
 
89
120
  private
121
+
90
122
  XS_TYPES = {
91
123
  lookup_qname('xs:boolean', Namespaces::ALL) =>
92
124
  [[TrueClass, FalseClass], nil, ->(v) { %w{true 1}.include?(v) ? true : false }],
@@ -1,34 +1,38 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SAML2
2
4
  class Attribute
3
5
  class X500 < Attribute
4
- GIVEN_NAME = 'urn:oid:2.5.4.42'.freeze
5
- SN = SURNAME = 'urn:oid:2.5.4.4'.freeze
6
+ GIVEN_NAME = 'urn:oid:2.5.4.42'
7
+ SN = SURNAME = 'urn:oid:2.5.4.4'
6
8
  # https://www.ietf.org/rfc/rfc2798.txt
7
9
  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
10
+ DISPLAY_NAME = 'urn:oid:2.16.840.1.113730.3.1.241'
11
+ EMPLOYEE_NUMBER = 'urn:oid:2.16.840.1.113730.3.1.3'
12
+ EMPLOYEE_TYPE = 'urn:oid:2.16.840.1.113730.3.1.4'
13
+ PREFERRED_LANGUAGE = 'urn:oid:2.16.840.1.113730.3.1.39'
12
14
  end
13
15
  # https://www.internet2.edu/media/medialibrary/2013/09/04/internet2-mace-dir-eduperson-201203.html
14
16
  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
17
+ AFFILIATION = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.1'
18
+ ASSURANCE = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.11'
19
+ ENTITLEMENT = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.7'
20
+ NICKNAME = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.2'
21
+ ORG_D_N = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.3'
22
+ PRIMARY_AFFILIATION = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.5'
23
+ PRIMARY_ORG_UNIT_D_N = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.8'
24
+ PRINCIPAL_NAME = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6'
25
+ SCOPED_AFFILIATION = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.9'
26
+ TARGETED_I_D = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.10'
27
+ UNIT_D_N = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.4'
26
28
  end
27
29
  # http://www.ietf.org/rfc/rfc4519.txt
28
- UID = USERID = 'urn:oid:0.9.2342.19200300.100.1.1'.freeze
30
+ UID = USERID = 'urn:oid:0.9.2342.19200300.100.1.1'
29
31
  # http://www.ietf.org/rfc/rfc4524.txt
30
- MAIL = 'urn:oid:0.9.2342.19200300.100.1.3'.freeze
32
+ MAIL = 'urn:oid:0.9.2342.19200300.100.1.3'
31
33
 
34
+ # Returns true if the param should be an {X500} Attribute.
35
+ # @param name_or_node [String, Nokogiri::XML::Element]
32
36
  def self.recognizes?(name_or_node)
33
37
  if name_or_node.is_a?(Nokogiri::XML::Element)
34
38
  !!name_or_node.at_xpath("@x500:Encoding", Namespaces::ALL)
@@ -37,6 +41,14 @@ module SAML2
37
41
  end
38
42
  end
39
43
 
44
+ # Create a new X.500 attribute.
45
+ #
46
+ # The name format will always be set to URI.
47
+ #
48
+ # @param name [String]
49
+ # Either an OID or a known friendly name. The opposite value will be
50
+ # inferred automatically.
51
+ # @param value optional [Object, nil]
40
52
  def initialize(name = nil, value = nil)
41
53
  # if they pass an OID, infer the friendly name
42
54
  friendly_name = OIDS[name]
@@ -51,6 +63,7 @@ module SAML2
51
63
  super(name, value, friendly_name, NameFormats::URI)
52
64
  end
53
65
 
66
+ # (see Base#build)
54
67
  def build(builder)
55
68
  super
56
69
  attr = builder.parent.last_element_child
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/core_ext/array/wrap'
2
4
 
3
5
  require 'saml2/attribute'
@@ -8,40 +10,61 @@ require 'saml2/namespaces'
8
10
  module SAML2
9
11
  class RequestedAttribute < Attribute
10
12
  class << self
13
+ # The XML namespace that this attribute class serializes as.
14
+ # @return ['md']
11
15
  def namespace
12
16
  'md'
13
17
  end
14
18
 
19
+ # The XML element that this attribute class serializes as.
20
+ # @return ['RequestedAttribute']
15
21
  def element
16
22
  'RequestedAttribute'
17
23
  end
18
24
 
25
+ # Create a RequestAttribute object to represent an attribute.
26
+ #
27
+ # {Attribute.create} will be used to create a temporary object, so that
28
+ # attribute-class specific inferences (i.e. {Attribute::X500} friendly
29
+ # names) will be done, but always returns a {RequestedAttribute}.
30
+ # @param name [String]
31
+ # The attribute name. This can be a friendly name, or a URI.
32
+ # @param is_required optional [true, false, nil]
33
+ # @return [RequestedAttribute]
19
34
  def create(name, is_required = nil)
20
- # use Attribute.create to get other subclasses to automatically fill out friendly_name
21
- # and name_format, but still return a RequestedAttribute object
22
35
  attribute = Attribute.create(name)
23
36
  new(attribute.name, is_required, attribute.friendly_name, attribute.name_format)
24
37
  end
25
38
  end
26
39
 
40
+ # Create a new {RequestedAttribute}.
41
+ #
42
+ # @param name [String]
43
+ # @param is_required optional [true, false, nil]
44
+ # @param friendly_name optional [String, nil]
45
+ # @param name_format optional [String, nil]
27
46
  def initialize(name = nil, is_required = nil, friendly_name = nil, name_format = nil)
28
47
  super(name, nil, friendly_name, name_format)
29
48
  @is_required = is_required
30
49
  end
31
50
 
51
+ # (see Base#from_xml)
32
52
  def from_xml(node)
33
53
  super
34
54
  @is_required = node['isRequired'] && node['isRequired'] == 'true'
35
55
  end
36
56
 
57
+ # @return [true, false, nil]
37
58
  def required?
38
59
  @is_required
39
60
  end
40
61
  end
41
62
 
42
63
  class RequiredAttributeMissing < RuntimeError
64
+ # @return [RequestedAttribute]
43
65
  attr_reader :requested_attribute
44
66
 
67
+ # @param requested_attribute [RequestedAttribute]
45
68
  def initialize(requested_attribute)
46
69
  super("Required attribute #{requested_attribute.name} not provided")
47
70
  @requested_attribute = requested_attribute
@@ -49,8 +72,11 @@ module SAML2
49
72
  end
50
73
 
51
74
  class InvalidAttributeValue < RuntimeError
52
- attr_reader :requested_attribute, :provided_value
75
+ # @return [RequestedAttribute]
76
+ attr_reader :requested_attribute
77
+ attr_reader :provided_value
53
78
 
79
+ # @param requested_attribute [RequestedAttribute]
54
80
  def initialize(requested_attribute, provided_value)
55
81
  super("Attribute #{requested_attribute.name} is provided value " \
56
82
  "#{provided_value.inspect}, but only allows " \
@@ -62,8 +88,13 @@ module SAML2
62
88
  class AttributeConsumingService < Base
63
89
  include IndexedObject
64
90
 
65
- attr_reader :name, :description, :requested_attributes
91
+ # @return [LocalizedName]
92
+ attr_reader :name, :description
93
+ # @return [Array<RequestedAttribute>]
94
+ attr_reader :requested_attributes
66
95
 
96
+ # @param name [String]
97
+ # @param requested_attributes [::Array<RequestedAttributes>]
67
98
  def initialize(name = nil, requested_attributes = [])
68
99
  super()
69
100
  @name = LocalizedName.new('ServiceName', name)
@@ -71,6 +102,7 @@ module SAML2
71
102
  @requested_attributes = requested_attributes
72
103
  end
73
104
 
105
+ # (see Base#from_xml)
74
106
  def from_xml(node)
75
107
  super
76
108
  name.from_xml(node.xpath('md:ServiceName', Namespaces::ALL))
@@ -78,6 +110,21 @@ module SAML2
78
110
  @requested_attributes = load_object_array(node, "md:RequestedAttribute", RequestedAttribute)
79
111
  end
80
112
 
113
+ # Create an {AttributeStatement} from the given attributes hash.
114
+ #
115
+ # Given a set of attributes, create and return an {AttributeStatement}
116
+ # with only the attributes that this {AttributeConsumingService} requests.
117
+ #
118
+ # @param attributes [Hash<String => Object>, Array<Attribute>]
119
+ # If it's a hash, the elements are run through {Attribute.create} first
120
+ # in order to create proper {Attribute} objects.
121
+ # @return [AttributeStatement]
122
+ # @raise [InvalidAttributeValue]
123
+ # If a {RequestedAttribute} specifies that only specific values are
124
+ # permissible, and the provided attribute does not match that value.
125
+ # @raise [RequiredAttributeMissing]
126
+ # If a {RequestedAttribute} is tagged as required, but it has not been
127
+ # supplied.
81
128
  def create_statement(attributes)
82
129
  if attributes.is_a?(Hash)
83
130
  attributes = attributes.map { |k, v| Attribute.create(k, v) }
@@ -119,6 +166,7 @@ module SAML2
119
166
  AttributeStatement.new(attributes)
120
167
  end
121
168
 
169
+ # (see Base#build)
122
170
  def build(builder)
123
171
  builder['md'].AttributeConsumingService do |attribute_consuming_service|
124
172
  name.build(attribute_consuming_service)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
  require 'zlib'
3
5
 
@@ -16,12 +18,22 @@ module SAML2
16
18
  attr_writer :assertion_consumer_service_index,
17
19
  :assertion_consumer_service_url,
18
20
  :attribute_consuming_service_index,
19
- :force_authn,
20
21
  :name_id_policy,
21
- :passive,
22
22
  :protocol_binding
23
+ # @return [Boolean, nil]
24
+ attr_writer :force_authn, :passive
25
+ # @return [RequestedAuthnContext, nil]
23
26
  attr_accessor :requested_authn_context
24
27
 
28
+ # Initiate a SAML SSO flow, from a service provider to an identity
29
+ # provider.
30
+ # @todo go over these params, and use kwargs. Maybe pass Entity instead
31
+ # of ServiceProvider.
32
+ # @param issuer [NameID]
33
+ # @param identity_provider [IdentityProvider]
34
+ # @param assertion_consumer_service [Endpoint::Indexed]
35
+ # @param service_provider [ServiceProvider]
36
+ # @return [AuthnRequest]
25
37
  def self.initiate(issuer, identity_provider = nil,
26
38
  assertion_consumer_service: nil,
27
39
  service_provider: nil)
@@ -37,6 +49,7 @@ module SAML2
37
49
  authn_request
38
50
  end
39
51
 
52
+ # @see https://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf section 4.1
40
53
  def valid_web_browser_sso_profile?
41
54
  return false unless issuer
42
55
  return false if issuer.format && issuer.format != NameID::Format::ENTITY
@@ -44,6 +57,7 @@ module SAML2
44
57
  true
45
58
  end
46
59
 
60
+ # @see https://saml2int.org/profile/current/#section82
47
61
  def valid_interoperable_profile?
48
62
  # It's a subset of Web Browser SSO profile
49
63
  return false unless valid_web_browser_sso_profile?
@@ -55,6 +69,14 @@ module SAML2
55
69
  true
56
70
  end
57
71
 
72
+ # Populate {#assertion_consumer_service} and {#attribute_consuming_service}
73
+ # attributes.
74
+ #
75
+ # Given {ServiceProvider} metadata, resolve the index/urls in this object to actual
76
+ # objects.
77
+ #
78
+ # @param service_provider [ServiceProvider]
79
+ # @return [Boolean]
58
80
  def resolve(service_provider)
59
81
  # TODO: check signature if present
60
82
 
@@ -71,6 +93,7 @@ module SAML2
71
93
  true
72
94
  end
73
95
 
96
+ # @return [NameID::Policy, nil]
74
97
  def name_id_policy
75
98
  if xml && !instance_variable_defined?(:@name_id_policy)
76
99
  @name_id_policy = NameID::Policy.from_xml(xml.at_xpath('samlp:NameIDPolicy', Namespaces::ALL))
@@ -78,8 +101,14 @@ module SAML2
78
101
  @name_id_policy
79
102
  end
80
103
 
81
- attr_reader :assertion_consumer_service, :attribute_consuming_service
104
+ # Must call {#resolve} before accessing.
105
+ # @return [AssertionConsumerService, nil]
106
+ attr_reader :assertion_consumer_service
107
+ # Must call {#resolve} before accessing.
108
+ # @return [AttributeConsumingService, nil]
109
+ attr_reader :attribute_consuming_service
82
110
 
111
+ # @return [Integer, nil]
83
112
  def assertion_consumer_service_index
84
113
  if xml && !instance_variable_defined?(:@assertion_consumer_service_index)
85
114
  @assertion_consumer_service_index = xml['AssertionConsumerServiceIndex']&.to_i
@@ -87,6 +116,7 @@ module SAML2
87
116
  @assertion_consumer_service_index
88
117
  end
89
118
 
119
+ # @return [String, nil]
90
120
  def assertion_consumer_service_url
91
121
  if xml && !instance_variable_defined?(:@assertion_consumer_service_url)
92
122
  @assertion_consumer_service_url = xml['AssertionConsumerServiceURL']
@@ -94,6 +124,7 @@ module SAML2
94
124
  @assertion_consumer_service_url
95
125
  end
96
126
 
127
+ # @return [Integer, nil]
97
128
  def attribute_consuming_service_index
98
129
  if xml && !instance_variable_defined?(:@attribute_consuming_service_index)
99
130
  @attribute_consuming_service_index = xml['AttributeConsumingServiceIndex']&.to_i
@@ -101,6 +132,7 @@ module SAML2
101
132
  @attribute_consuming_service_index
102
133
  end
103
134
 
135
+ # @return [true, false, nil]
104
136
  def force_authn?
105
137
  if xml && !instance_variable_defined?(:@force_authn)
106
138
  @force_authn = xml['ForceAuthn']&.== 'true'
@@ -108,6 +140,7 @@ module SAML2
108
140
  @force_authn
109
141
  end
110
142
 
143
+ # @return [true, false, nil]
111
144
  def passive?
112
145
  if xml && !instance_variable_defined?(:@passive)
113
146
  @passive = xml['IsPassive']&.== 'true'
@@ -115,6 +148,7 @@ module SAML2
115
148
  @passive
116
149
  end
117
150
 
151
+ # @return [String, nil]
118
152
  def protocol_binding
119
153
  if xml && !instance_variable_defined?(:@protocol_binding)
120
154
  @protocol_binding = xml['ProtocolBinding']
@@ -122,6 +156,7 @@ module SAML2
122
156
  @protocol_binding
123
157
  end
124
158
 
159
+ # @return [Subject, nil]
125
160
  def subject
126
161
  if xml && !instance_variable_defined?(:@subject)
127
162
  @subject = Subject.from_xml(xml.at_xpath('saml:Subject', Namespaces::ALL))
@@ -129,6 +164,7 @@ module SAML2
129
164
  @subject
130
165
  end
131
166
 
167
+ # (see Base#build)
132
168
  def build(builder)
133
169
  builder['samlp'].AuthnRequest(
134
170
  'xmlns:samlp' => Namespaces::SAMLP,