saml2 3.1.2 → 3.1.4

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +6 -4
  3. data/exe/bulk_verify_responses +94 -0
  4. data/lib/saml2/assertion.rb +7 -7
  5. data/lib/saml2/attribute/x500.rb +31 -28
  6. data/lib/saml2/attribute.rb +53 -49
  7. data/lib/saml2/attribute_consuming_service.rb +29 -31
  8. data/lib/saml2/authn_request.rb +54 -47
  9. data/lib/saml2/authn_statement.rb +31 -20
  10. data/lib/saml2/base.rb +72 -63
  11. data/lib/saml2/bindings/http_post.rb +7 -7
  12. data/lib/saml2/bindings/http_redirect.rb +37 -33
  13. data/lib/saml2/bindings.rb +1 -1
  14. data/lib/saml2/conditions.rb +19 -16
  15. data/lib/saml2/contact.rb +19 -18
  16. data/lib/saml2/endpoint.rb +14 -11
  17. data/lib/saml2/entity.rb +27 -27
  18. data/lib/saml2/identity_provider.rb +13 -10
  19. data/lib/saml2/indexed_object.rb +15 -12
  20. data/lib/saml2/key.rb +43 -34
  21. data/lib/saml2/localized_name.rb +11 -10
  22. data/lib/saml2/logout_request.rb +8 -8
  23. data/lib/saml2/logout_response.rb +4 -4
  24. data/lib/saml2/message.rb +24 -20
  25. data/lib/saml2/name_id.rb +45 -41
  26. data/lib/saml2/namespaces.rb +8 -8
  27. data/lib/saml2/organization.rb +11 -10
  28. data/lib/saml2/organization_and_contacts.rb +5 -5
  29. data/lib/saml2/request.rb +3 -3
  30. data/lib/saml2/requested_authn_context.rb +4 -4
  31. data/lib/saml2/response.rb +45 -33
  32. data/lib/saml2/role.rb +11 -11
  33. data/lib/saml2/schemas.rb +13 -10
  34. data/lib/saml2/service_provider.rb +11 -12
  35. data/lib/saml2/signable.rb +23 -18
  36. data/lib/saml2/sso.rb +5 -5
  37. data/lib/saml2/status.rb +9 -7
  38. data/lib/saml2/status_response.rb +5 -5
  39. data/lib/saml2/subject.rb +28 -28
  40. data/lib/saml2/version.rb +1 -1
  41. data/lib/saml2.rb +7 -7
  42. metadata +78 -122
  43. data/spec/fixtures/FederationMetadata.xml +0 -670
  44. data/spec/fixtures/authnrequest.xml +0 -12
  45. data/spec/fixtures/certificate.pem +0 -24
  46. data/spec/fixtures/entities.xml +0 -13
  47. data/spec/fixtures/external-uri-reference-response.xml +0 -48
  48. data/spec/fixtures/identity_provider.xml +0 -46
  49. data/spec/fixtures/noconditions_response.xml +0 -1
  50. data/spec/fixtures/othercertificate.pem +0 -25
  51. data/spec/fixtures/privatekey.key +0 -27
  52. data/spec/fixtures/response_assertion_signed_reffed_from_response.xml +0 -6
  53. data/spec/fixtures/response_signed.xml +0 -46
  54. data/spec/fixtures/response_tampered_certificate.xml +0 -25
  55. data/spec/fixtures/response_tampered_signature.xml +0 -46
  56. data/spec/fixtures/response_with_attribute_signed.xml +0 -46
  57. data/spec/fixtures/response_with_encrypted_assertion.xml +0 -58
  58. data/spec/fixtures/response_with_rsa_key_value.xml +0 -1
  59. data/spec/fixtures/response_with_signed_assertion_and_encrypted_subject.xml +0 -116
  60. data/spec/fixtures/response_without_keyinfo.xml +0 -1
  61. data/spec/fixtures/service_provider.xml +0 -79
  62. data/spec/fixtures/test3-response.xml +0 -9
  63. data/spec/fixtures/test6-response.xml +0 -10
  64. data/spec/fixtures/test7-response.xml +0 -10
  65. data/spec/fixtures/xml_missigned_assertion.xml +0 -84
  66. data/spec/fixtures/xml_signature_wrapping_attack_duplicate_ids.xml +0 -11
  67. data/spec/fixtures/xml_signature_wrapping_attack_response_attributes.xml +0 -45
  68. data/spec/fixtures/xml_signature_wrapping_attack_response_nameid.xml +0 -44
  69. data/spec/fixtures/xslt-transform-response.xml +0 -57
  70. data/spec/lib/attribute_consuming_service_spec.rb +0 -129
  71. data/spec/lib/attribute_spec.rb +0 -149
  72. data/spec/lib/authn_request_spec.rb +0 -52
  73. data/spec/lib/bindings/http_redirect_spec.rb +0 -183
  74. data/spec/lib/conditions_spec.rb +0 -74
  75. data/spec/lib/entity_spec.rb +0 -58
  76. data/spec/lib/identity_provider_spec.rb +0 -43
  77. data/spec/lib/indexed_object_spec.rb +0 -71
  78. data/spec/lib/key_spec.rb +0 -23
  79. data/spec/lib/logout_request_spec.rb +0 -33
  80. data/spec/lib/logout_response_spec.rb +0 -33
  81. data/spec/lib/message_spec.rb +0 -23
  82. data/spec/lib/response_spec.rb +0 -293
  83. data/spec/lib/service_provider_spec.rb +0 -76
  84. data/spec/lib/signable_spec.rb +0 -15
  85. data/spec/spec_helper.rb +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c6bee2a21b427f883ac61136eba8021c72f7f90a3a41654a964f165d1ae3641
4
- data.tar.gz: 0e53c2b7e6d460f61f331c0d2cc4031ba3cea9895cc3b3b9d6fca153f1ca7653
3
+ metadata.gz: 6af3303cbbfd4c78c055015c30df1772bddbee535f27144696c57464398f439e
4
+ data.tar.gz: fd0fb87f88e987cf4fc081e7c4e833e12d3a7489662f314a5e5383e5f4278952
5
5
  SHA512:
6
- metadata.gz: c427fc0852c531675c5573b1a51a3a6097414fc7ceee458a1c80d682dff696123e59d61a6e90cea0f6c221fbcef01a87f5a91f33dfebb2d0bf0f281e2b278794
7
- data.tar.gz: a2281d564327fe52d98055559663cd4c26945aafc3b535c2d4adc2851480418f387e3a80f73f8bed665afc3eaf2ab771d94576c0332a1296ef8c0e73208467eb
6
+ metadata.gz: 5af19de37ce48ef31e22c18fa7eeaeb44c371d8751af579c2d2a06b6b1cd76579c64eba9b40a304a8da549937d0d7e0b9a8ef38439149be52567e0f424b85a9e
7
+ data.tar.gz: 6a224a75ff2d4f6408adf1beb3ca4fa65c15be36d65173b5882b6a0223b5b71fd3499fa4364e53f47ccba5ebd6c746fa2c2433012a0bdcd698cdb864fafd6dd8
data/Rakefile CHANGED
@@ -1,8 +1,10 @@
1
- require 'rubygems'
2
- require 'bundler'
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require "bundler"
3
5
  Bundler::GemHelper.install_tasks
4
6
 
5
- require 'rspec/core/rake_task'
7
+ require "rspec/core/rake_task"
6
8
  RSpec::Core::RakeTask.new
7
9
 
8
- task :default => :spec
10
+ task default: :spec
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "openssl"
6
+
7
+ require "saml2"
8
+
9
+ debug = ARGV.delete("--debug")
10
+
11
+ service_provider_entity = SAML2::Entity.new
12
+ service_provider = SAML2::ServiceProvider.new
13
+ service_provider_entity.roles << service_provider
14
+
15
+ def getarg(key)
16
+ return unless (index = ARGV.index(key))
17
+
18
+ ARGV.delete_at(index)
19
+ ARGV.delete_at(index)
20
+ end
21
+
22
+ if (verification_time = getarg("--at"))
23
+ verification_time = Time.parse(verification_time)
24
+ end
25
+ verification_time ||= Time.now.utc
26
+
27
+ while (key_file = getarg("--key"))
28
+ service_provider.private_keys << OpenSSL::PKey.read(File.read(key_file))
29
+ end
30
+
31
+ while (cert_file = getarg("--certificate"))
32
+ service_provider.keys << SAML2::KeyDescriptor.new(File.read(cert_file))
33
+ end
34
+
35
+ ignored_idps = Set.new
36
+ while (idp = getarg("--ignore"))
37
+ if idp[0] == "@"
38
+ ignored_idps.merge(File.read(idp[1..]).strip.split("\n"))
39
+ else
40
+ ignored_idps << idp
41
+ end
42
+ end
43
+
44
+ idps = {}
45
+ if (trusted_certificates_file = getarg("--trusted-certificates"))
46
+ trusted_certificates = JSON.parse(File.read(trusted_certificates_file))
47
+ trusted_certificates.each do |(issuer, fingerprints)|
48
+ idp_entity = SAML2::Entity.new
49
+ idp_entity.entity_id = issuer
50
+ idp = SAML2::IdentityProvider.new
51
+ idp.fingerprints = fingerprints
52
+ idp_entity.roles << idp
53
+ idps[issuer] = idp_entity
54
+ end
55
+ end
56
+
57
+ responses = JSON.parse(File.read(ARGV.first))
58
+ index = ARGV.pop.to_i if ARGV.last.to_i.to_s == ARGV.last
59
+
60
+ bad_counts = Hash.new(0)
61
+ non_ignored_count = 0
62
+
63
+ responses = [responses[index]] if index
64
+ responses.each_with_index do |response_raw, i|
65
+ next if response_raw["SAMLResponse"].empty?
66
+ next if response_raw["error"] # we're not expected to be able to validate this
67
+
68
+ begin
69
+ puts response_raw.to_json if debug
70
+ response, _relay_state = SAML2::Bindings::HTTP_POST.decode(response_raw)
71
+ rescue => e
72
+ warn "Unable to decode '#{response_raw}' (index #{i}) due to #{e}"
73
+ next
74
+ end
75
+
76
+ next if ignored_idps.include?(response.issuer&.id)
77
+
78
+ non_ignored_count += 1
79
+
80
+ puts response.xml if debug
81
+
82
+ # TODO: ignore audience restrictions
83
+ errors = response.validate(service_provider: service_provider_entity,
84
+ identity_provider: idps[response.issuer&.id],
85
+ verification_time: verification_time)
86
+ unless errors.empty?
87
+ bad_counts[response.issuer&.id] += 1
88
+ warn "#{errors.inspect} for response #{response.id} from #{response.issuer&.id} (index #{i})"
89
+ end
90
+ end
91
+
92
+ puts ""
93
+ puts bad_counts.sort_by(&:last).reverse.to_h.inspect
94
+ puts "#{bad_counts.values.sum}/#{non_ignored_count} failed"
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'saml2/conditions'
3
+ require "saml2/conditions"
4
4
 
5
5
  module SAML2
6
6
  class Assertion < Message
@@ -21,7 +21,7 @@ module SAML2
21
21
  # @return [Subject, nil]
22
22
  def subject
23
23
  if xml && !instance_variable_defined?(:@subject)
24
- @subject = Subject.from_xml(xml.at_xpath('saml:Subject', Namespaces::ALL))
24
+ @subject = Subject.from_xml(xml.at_xpath("saml:Subject", Namespaces::ALL))
25
25
  end
26
26
  @subject
27
27
  end
@@ -29,7 +29,7 @@ module SAML2
29
29
  # @return [Conditions]
30
30
  def conditions
31
31
  if !instance_variable_defined?(:@conditions) && xml
32
- @conditions = Conditions.from_xml(xml.at_xpath('saml:Conditions', Namespaces::ALL))
32
+ @conditions = Conditions.from_xml(xml.at_xpath("saml:Conditions", Namespaces::ALL))
33
33
  end
34
34
  @conditions
35
35
  end
@@ -46,19 +46,19 @@ module SAML2
46
46
 
47
47
  # @return [Array<AuthnStatement, AttributeStatement>]
48
48
  def statements
49
- @statements ||= load_object_array(xml, 'saml:AuthnStatement|saml:AttributeStatement')
49
+ @statements ||= load_object_array(xml, "saml:AuthnStatement|saml:AttributeStatement")
50
50
  end
51
51
 
52
52
  # (see Base#build)
53
53
  def build(builder)
54
- builder['saml'].Assertion(
55
- 'xmlns:saml' => Namespaces::SAML
54
+ builder["saml"].Assertion(
55
+ "xmlns:saml" => Namespaces::SAML
56
56
  ) do |assertion|
57
57
  super(assertion)
58
58
 
59
59
  subject.build(assertion)
60
60
 
61
- conditions.build(assertion) if conditions
61
+ conditions&.build(assertion)
62
62
 
63
63
  statements.each { |stmt| stmt.build(assertion) }
64
64
  end
@@ -3,41 +3,42 @@
3
3
  module SAML2
4
4
  class Attribute
5
5
  class X500 < Attribute
6
- GIVEN_NAME = 'urn:oid:2.5.4.42'
7
- SN = SURNAME = 'urn:oid:2.5.4.4'
6
+ GIVEN_NAME = "urn:oid:2.5.4.42"
7
+ SN = SURNAME = "urn:oid:2.5.4.4"
8
8
  # https://www.ietf.org/rfc/rfc2798.txt
9
9
  module InetOrgPerson
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'
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"
14
14
  end
15
+
15
16
  # https://www.internet2.edu/media/medialibrary/2013/09/04/internet2-mace-dir-eduperson-201203.html
16
17
  module EduPerson
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
- ORG_UNIT_D_N = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.4'
23
- PRIMARY_AFFILIATION = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.5'
24
- PRIMARY_ORG_UNIT_D_N = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.8'
25
- PRINCIPAL_NAME = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6'
26
- SCOPED_AFFILIATION = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.9'
27
- TARGETED_I_D = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.10'
18
+ AFFILIATION = "urn:oid:1.3.6.1.4.1.5923.1.1.1.1"
19
+ ASSURANCE = "urn:oid:1.3.6.1.4.1.5923.1.1.1.11"
20
+ ENTITLEMENT = "urn:oid:1.3.6.1.4.1.5923.1.1.1.7"
21
+ NICKNAME = "urn:oid:1.3.6.1.4.1.5923.1.1.1.2"
22
+ ORG_D_N = "urn:oid:1.3.6.1.4.1.5923.1.1.1.3"
23
+ ORG_UNIT_D_N = "urn:oid:1.3.6.1.4.1.5923.1.1.1.4"
24
+ PRIMARY_AFFILIATION = "urn:oid:1.3.6.1.4.1.5923.1.1.1.5"
25
+ PRIMARY_ORG_UNIT_D_N = "urn:oid:1.3.6.1.4.1.5923.1.1.1.8"
26
+ PRINCIPAL_NAME = "urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
27
+ SCOPED_AFFILIATION = "urn:oid:1.3.6.1.4.1.5923.1.1.1.9"
28
+ TARGETED_I_D = "urn:oid:1.3.6.1.4.1.5923.1.1.1.10"
28
29
  end
29
30
  # http://www.ietf.org/rfc/rfc4519.txt
30
- UID = USERID = 'urn:oid:0.9.2342.19200300.100.1.1'
31
+ UID = USERID = "urn:oid:0.9.2342.19200300.100.1.1"
31
32
  # http://www.ietf.org/rfc/rfc4524.txt
32
- MAIL = 'urn:oid:0.9.2342.19200300.100.1.3'
33
+ MAIL = "urn:oid:0.9.2342.19200300.100.1.3"
33
34
 
34
35
  # Returns true if the param should be an {X500} Attribute.
35
36
  # @param name_or_node [String, Nokogiri::XML::Element]
36
37
  def self.recognizes?(name_or_node)
37
38
  if name_or_node.is_a?(Nokogiri::XML::Element)
38
39
  !!name_or_node.at_xpath("@x500:Encoding", Namespaces::ALL) ||
39
- (name_or_node['NameFormat'] == NameFormats::URI || name_or_node['NameFormat'].nil?) &&
40
- OIDS.include?(name_or_node['Name'])
40
+ ((name_or_node["NameFormat"] == NameFormats::URI || name_or_node["NameFormat"].nil?) &&
41
+ OIDS.include?(name_or_node["Name"]))
41
42
  else
42
43
  FRIENDLY_NAMES.include?(name_or_node) || OIDS.include?(name_or_node)
43
44
  end
@@ -58,7 +59,8 @@ module SAML2
58
59
  # if they pass a friendly name, infer the OID
59
60
  proper_name = FRIENDLY_NAMES[name]
60
61
  if proper_name
61
- name, friendly_name = proper_name, name
62
+ friendly_name = name
63
+ name = proper_name
62
64
  end
63
65
  end
64
66
 
@@ -77,25 +79,26 @@ module SAML2
77
79
  def build(builder)
78
80
  super
79
81
  attr = builder.parent.last_element_child
80
- attr.add_namespace_definition('x500', Namespaces::X500)
81
- attr['x500:Encoding'] = 'LDAP'
82
+ attr.add_namespace_definition("x500", Namespaces::X500)
83
+ attr["x500:Encoding"] = "LDAP"
82
84
  end
83
85
 
84
86
  # build hashes out of our known attribute names for quick lookup
85
- FRIENDLY_NAMES = ([self] + constants).inject({}) do |hash, mod|
87
+ FRIENDLY_NAMES = ([self] + constants).each_with_object({}) do |mod, hash|
86
88
  mod = const_get(mod) unless mod.is_a?(Module)
87
89
  next hash unless mod.is_a?(Module)
88
90
  # Don't look in modules inherited from parent classes
89
- next hash unless mod.name.start_with?(self.name)
91
+ next hash unless mod.name.start_with?(name)
92
+
90
93
  mod.constants.each do |key|
91
94
  value = mod.const_get(key)
92
95
  next unless value.is_a?(String)
96
+
93
97
  key = key.to_s.downcase.gsub(/_\w/) { |c| c[1].upcase }
94
98
  # eduPerson prefixes all of their names
95
- key = "eduPerson#{key.sub(/^\w/) { |c| c.upcase }}" if mod == EduPerson
99
+ key = "eduPerson#{key.sub(/^\w/, &:upcase)}" if mod == EduPerson
96
100
  hash[key] = value
97
101
  end
98
- hash
99
102
  end.freeze
100
103
  OIDS = FRIENDLY_NAMES.invert.freeze
101
104
  private_constant :FRIENDLY_NAMES, :OIDS
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'date'
3
+ require "date"
4
4
 
5
- require 'active_support/core_ext/array/wrap'
5
+ require "active_support/core_ext/array/wrap"
6
6
 
7
- require 'saml2/base'
8
- require 'saml2/namespaces'
7
+ require "saml2/base"
8
+ require "saml2/namespaces"
9
9
 
10
10
  module SAML2
11
11
  class Attribute < Base
@@ -43,13 +43,13 @@ module SAML2
43
43
  # The XML namespace that this attribute class serializes as.
44
44
  # @return ['saml']
45
45
  def namespace
46
- 'saml'
46
+ "saml"
47
47
  end
48
48
 
49
49
  # The XML element that this attribute class serializes as.
50
50
  # @return ['Attribute']
51
51
  def element
52
- 'Attribute'
52
+ "Attribute"
53
53
  end
54
54
 
55
55
  protected
@@ -59,10 +59,10 @@ module SAML2
59
59
  end
60
60
 
61
61
  def inherited(klass)
62
+ super
62
63
  subclasses << klass
63
64
  end
64
65
 
65
-
66
66
  def class_for(name_or_node)
67
67
  subclasses.find do |klass|
68
68
  klass.respond_to?(:recognizes?) && klass.recognizes?(name_or_node)
@@ -84,18 +84,22 @@ module SAML2
84
84
  # @param friendly_name optional [String, nil]
85
85
  # @param name_format optional [String, nil]
86
86
  def initialize(name = nil, value = nil, friendly_name = nil, name_format = nil)
87
- @name, @value, @friendly_name, @name_format = name, value, friendly_name, name_format
87
+ super()
88
+ @name = name
89
+ @value = value
90
+ @friendly_name = friendly_name
91
+ @name_format = name_format
88
92
  end
89
93
 
90
94
  # (see Base#build)
91
95
  def build(builder)
92
- builder[self.class.namespace].__send__(self.class.element, 'Name' => name) do |attribute|
93
- attribute.parent['FriendlyName'] = friendly_name if friendly_name
94
- attribute.parent['NameFormat'] = name_format if name_format
96
+ builder[self.class.namespace].__send__(self.class.element, "Name" => name) do |attribute|
97
+ attribute.parent["FriendlyName"] = friendly_name if friendly_name
98
+ attribute.parent["NameFormat"] = name_format if name_format
95
99
  Array.wrap(value).each do |value|
96
100
  xsi_type, val = convert_to_xsi(value)
97
- attribute['saml'].AttributeValue(val) do |attribute_value|
98
- attribute_value.parent['xsi:type'] = xsi_type if xsi_type
101
+ attribute["saml"].AttributeValue(val) do |attribute_value|
102
+ attribute_value.parent["xsi:type"] = xsi_type if xsi_type
99
103
  end
100
104
  end
101
105
  end
@@ -104,15 +108,15 @@ module SAML2
104
108
  # (see Base#from_xml)
105
109
  def from_xml(node)
106
110
  super
107
- @name = node['Name']
108
- @friendly_name = node['FriendlyName']
109
- @name_format = node['NameFormat']
110
- values = node.xpath('saml:AttributeValue', Namespaces::ALL).map do |value|
111
- convert_from_xsi(value.attribute_with_ns('type', Namespaces::XSI), value.content && value.content.strip)
111
+ @name = node["Name"]
112
+ @friendly_name = node["FriendlyName"]
113
+ @name_format = node["NameFormat"]
114
+ values = node.xpath("saml:AttributeValue", Namespaces::ALL).map do |value|
115
+ convert_from_xsi(value.attribute_with_ns("type", Namespaces::XSI), value.content && value.content.strip)
112
116
  end
113
117
  @value = case values.length
114
- when 0; nil
115
- when 1; values.first
118
+ when 0 then nil
119
+ when 1 then values.first
116
120
  else; values
117
121
  end
118
122
  end
@@ -120,13 +124,13 @@ module SAML2
120
124
  private
121
125
 
122
126
  XS_TYPES = {
123
- lookup_qname('xs:boolean', Namespaces::ALL) =>
124
- [[TrueClass, FalseClass], nil, ->(v) { %w{true 1}.include?(v) ? true : false }],
125
- lookup_qname('xs:string', Namespaces::ALL) =>
127
+ lookup_qname("xs:boolean", Namespaces::ALL) =>
128
+ [[TrueClass, FalseClass], nil, ->(v) { %w[true 1].include?(v) }],
129
+ lookup_qname("xs:string", Namespaces::ALL) =>
126
130
  [String, nil, nil],
127
- lookup_qname('xs:date', Namespaces::ALL) =>
131
+ lookup_qname("xs:date", Namespaces::ALL) =>
128
132
  [Date, nil, ->(v) { Date.parse(v) if v }],
129
- lookup_qname('xs:dateTime', Namespaces::ALL) =>
133
+ lookup_qname("xs:dateTime", Namespaces::ALL) =>
130
134
  [Time, ->(v) { v.iso8601 }, ->(v) { Time.parse(v) if v }]
131
135
  }.freeze
132
136
 
@@ -134,11 +138,11 @@ module SAML2
134
138
  xs_type = nil
135
139
  converter = nil
136
140
  XS_TYPES.each do |type, (klasses, to_xsi, _from_xsi)|
137
- if Array.wrap(klasses).any? { |klass| klass === value }
138
- xs_type = "xs:#{type.last}"
139
- converter = to_xsi
140
- break
141
- end
141
+ next unless Array.wrap(klasses).any? { |klass| value.is_a?(klass) }
142
+
143
+ xs_type = "xs:#{type.last}"
144
+ converter = to_xsi
145
+ break
142
146
  end
143
147
  value = converter.call(value) if converter
144
148
  [xs_type, value]
@@ -146,12 +150,11 @@ module SAML2
146
150
 
147
151
  def convert_from_xsi(type, value)
148
152
  return value unless type
153
+
149
154
  qname = self.class.lookup_qname(type.value, type.namespaces)
150
155
 
151
156
  info = XS_TYPES[qname]
152
- if info && info.last
153
- value = info.last.call(value)
154
- end
157
+ value = info.last.call(value) if info&.last
155
158
  value
156
159
  end
157
160
  end
@@ -160,12 +163,13 @@ module SAML2
160
163
  attr_reader :attributes
161
164
 
162
165
  def initialize(attributes = [])
166
+ super()
163
167
  @attributes = attributes
164
168
  end
165
169
 
166
170
  def from_xml(node)
167
171
  super
168
- @attributes = node.xpath('saml:Attribute', Namespaces::ALL).map do |attr|
172
+ @attributes = node.xpath("saml:Attribute", Namespaces::ALL).map do |attr|
169
173
  Attribute.from_xml(attr)
170
174
  end
171
175
  end
@@ -190,29 +194,29 @@ module SAML2
190
194
 
191
195
  prior_value = result[key]
192
196
  result[key] = if prior_value
193
- value = Array.wrap(prior_value)
194
- # repeated key; convert to array
195
- if attribute.value.is_a?(Array)
196
- # both values are arrays; concatenate them
197
- value.concat(attribute.value)
198
- else
199
- value << attribute.value
200
- end
201
- value
202
- else
203
- attribute.value
204
- end
197
+ value = Array.wrap(prior_value)
198
+ # repeated key; convert to array
199
+ if attribute.value.is_a?(Array)
200
+ # both values are arrays; concatenate them
201
+ value.concat(attribute.value)
202
+ else
203
+ value << attribute.value
204
+ end
205
+ value
206
+ else
207
+ attribute.value
208
+ end
205
209
  end
206
210
  result
207
211
  end
208
212
 
209
213
  def build(builder)
210
- builder['saml'].AttributeStatement('xmlns:xs' => Namespaces::XS,
211
- 'xmlns:xsi' => Namespaces::XSI) do |statement|
214
+ builder["saml"].AttributeStatement("xmlns:xs" => Namespaces::XS,
215
+ "xmlns:xsi" => Namespaces::XSI) do |statement|
212
216
  @attributes.each { |attr| attr.build(statement) }
213
217
  end
214
218
  end
215
219
  end
216
220
  end
217
221
 
218
- require 'saml2/attribute/x500'
222
+ require "saml2/attribute/x500"
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/core_ext/array/wrap'
3
+ require "active_support/core_ext/array/wrap"
4
4
 
5
- require 'saml2/attribute'
6
- require 'saml2/indexed_object'
7
- require 'saml2/localized_name'
8
- require 'saml2/namespaces'
5
+ require "saml2/attribute"
6
+ require "saml2/indexed_object"
7
+ require "saml2/localized_name"
8
+ require "saml2/namespaces"
9
9
 
10
10
  module SAML2
11
11
  class RequestedAttribute < Attribute
@@ -13,13 +13,13 @@ module SAML2
13
13
  # The XML namespace that this attribute class serializes as.
14
14
  # @return ['md']
15
15
  def namespace
16
- 'md'
16
+ "md"
17
17
  end
18
18
 
19
19
  # The XML element that this attribute class serializes as.
20
20
  # @return ['RequestedAttribute']
21
21
  def element
22
- 'RequestedAttribute'
22
+ "RequestedAttribute"
23
23
  end
24
24
 
25
25
  # Create a RequestAttribute object to represent an attribute.
@@ -51,7 +51,7 @@ module SAML2
51
51
  # (see Base#from_xml)
52
52
  def from_xml(node)
53
53
  super
54
- @is_required = node['isRequired'] && node['isRequired'] == 'true'
54
+ @is_required = node["isRequired"] && node["isRequired"] == "true"
55
55
  end
56
56
 
57
57
  # @return [true, false, nil]
@@ -79,9 +79,10 @@ module SAML2
79
79
  # @param requested_attribute [RequestedAttribute]
80
80
  def initialize(requested_attribute, provided_value)
81
81
  super("Attribute #{requested_attribute.name} is provided value " \
82
- "#{provided_value.inspect}, but only allows " \
83
- "#{Array.wrap(requested_attribute.value).inspect}")
84
- @requested_attribute, @provided_value = requested_attribute, provided_value
82
+ "#{provided_value.inspect}, but only allows " \
83
+ "#{Array.wrap(requested_attribute.value).inspect}")
84
+ @requested_attribute = requested_attribute
85
+ @provided_value = provided_value
85
86
  end
86
87
  end
87
88
 
@@ -97,16 +98,16 @@ module SAML2
97
98
  # @param requested_attributes [::Array<RequestedAttributes>]
98
99
  def initialize(name = nil, requested_attributes = [])
99
100
  super()
100
- @name = LocalizedName.new('ServiceName', name)
101
- @description = LocalizedName.new('ServiceDescription')
101
+ @name = LocalizedName.new("ServiceName", name)
102
+ @description = LocalizedName.new("ServiceDescription")
102
103
  @requested_attributes = requested_attributes
103
104
  end
104
105
 
105
106
  # (see Base#from_xml)
106
107
  def from_xml(node)
107
108
  super
108
- name.from_xml(node.xpath('md:ServiceName', Namespaces::ALL))
109
- description.from_xml(node.xpath('md:ServiceDescription', Namespaces::ALL))
109
+ name.from_xml(node.xpath("md:ServiceName", Namespaces::ALL))
110
+ description.from_xml(node.xpath("md:ServiceDescription", Namespaces::ALL))
110
111
  @requested_attributes = load_object_array(node, "md:RequestedAttribute", RequestedAttribute)
111
112
  end
112
113
 
@@ -126,49 +127,46 @@ module SAML2
126
127
  # If a {RequestedAttribute} is tagged as required, but it has not been
127
128
  # supplied.
128
129
  def create_statement(attributes)
129
- if attributes.is_a?(Hash)
130
- attributes = attributes.map { |k, v| Attribute.create(k, v) }
131
- end
130
+ attributes = attributes.map { |k, v| Attribute.create(k, v) } if attributes.is_a?(Hash)
132
131
 
133
132
  attributes_hash = {}
134
133
  attributes.each do |attr|
135
134
  attr.value = attr.value.call if attr.value.respond_to?(:call)
136
135
  attributes_hash[[attr.name, attr.name_format]] = attr
137
- if attr.name_format
138
- attributes_hash[[attr.name, nil]] = attr
139
- end
136
+ attributes_hash[[attr.name, nil]] = attr if attr.name_format
140
137
  end
141
138
 
142
139
  attributes = []
143
140
  requested_attributes.each do |requested_attr|
144
141
  attr = attributes_hash[[requested_attr.name, requested_attr.name_format]]
145
- if requested_attr.name_format
146
- attr ||= attributes_hash[[requested_attr.name, nil]]
147
- end
142
+ attr ||= attributes_hash[[requested_attr.name, nil]] if requested_attr.name_format
148
143
  if attr
149
144
  if requested_attr.value &&
150
- !Array.wrap(requested_attr.value).include?(attr.value)
145
+ !Array.wrap(requested_attr.value).include?(attr.value)
151
146
  raise InvalidAttributeValue.new(requested_attr, attr.value)
152
147
  end
148
+
153
149
  attributes << attr
154
150
  elsif requested_attr.required?
155
151
  # if the metadata includes only one possible value, helpfully set
156
152
  # that value
157
- if requested_attr.value && !requested_attr.value.is_a?(::Array)
158
- attributes << Attribute.create(requested_attr.name,
159
- requested_attr.value)
160
- else
161
- raise RequiredAttributeMissing.new(requested_attr)
153
+ unless requested_attr.value && !requested_attr.value.is_a?(::Array)
154
+ raise RequiredAttributeMissing, requested_attr
162
155
  end
156
+
157
+ attributes << Attribute.create(requested_attr.name,
158
+ requested_attr.value)
159
+
163
160
  end
164
161
  end
165
162
  return nil if attributes.empty?
163
+
166
164
  AttributeStatement.new(attributes)
167
165
  end
168
166
 
169
167
  # (see Base#build)
170
168
  def build(builder)
171
- builder['md'].AttributeConsumingService do |attribute_consuming_service|
169
+ builder["md"].AttributeConsumingService do |attribute_consuming_service|
172
170
  name.build(attribute_consuming_service)
173
171
  description.build(attribute_consuming_service)
174
172
  requested_attributes.each do |requested_attribute|