saml2 3.1.2 → 3.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) 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 -137
  43. data/schemas/MetadataExchange.xsd +0 -112
  44. data/schemas/metadata_combined.xsd +0 -13
  45. data/schemas/oasis-200401-wss-wssecurity-secext-1.0.xsd +0 -195
  46. data/schemas/oasis-200401-wss-wssecurity-utility-1.0.xsd +0 -108
  47. data/schemas/saml-schema-assertion-2.0.xsd +0 -283
  48. data/schemas/saml-schema-metadata-2.0.xsd +0 -339
  49. data/schemas/saml-schema-protocol-2.0.xsd +0 -302
  50. data/schemas/sstc-saml-metadata-ext-query.xsd +0 -66
  51. data/schemas/ws-addr.xsd +0 -137
  52. data/schemas/ws-authorization.xsd +0 -145
  53. data/schemas/ws-federation.xsd +0 -471
  54. data/schemas/ws-securitypolicy-1.2.xsd +0 -1205
  55. data/schemas/xenc-schema.xsd +0 -136
  56. data/schemas/xml.xsd +0 -287
  57. data/schemas/xmldsig-core-schema.xsd +0 -309
  58. data/spec/fixtures/FederationMetadata.xml +0 -670
  59. data/spec/fixtures/authnrequest.xml +0 -12
  60. data/spec/fixtures/certificate.pem +0 -24
  61. data/spec/fixtures/entities.xml +0 -13
  62. data/spec/fixtures/external-uri-reference-response.xml +0 -48
  63. data/spec/fixtures/identity_provider.xml +0 -46
  64. data/spec/fixtures/noconditions_response.xml +0 -1
  65. data/spec/fixtures/othercertificate.pem +0 -25
  66. data/spec/fixtures/privatekey.key +0 -27
  67. data/spec/fixtures/response_assertion_signed_reffed_from_response.xml +0 -6
  68. data/spec/fixtures/response_signed.xml +0 -46
  69. data/spec/fixtures/response_tampered_certificate.xml +0 -25
  70. data/spec/fixtures/response_tampered_signature.xml +0 -46
  71. data/spec/fixtures/response_with_attribute_signed.xml +0 -46
  72. data/spec/fixtures/response_with_encrypted_assertion.xml +0 -58
  73. data/spec/fixtures/response_with_rsa_key_value.xml +0 -1
  74. data/spec/fixtures/response_with_signed_assertion_and_encrypted_subject.xml +0 -116
  75. data/spec/fixtures/response_without_keyinfo.xml +0 -1
  76. data/spec/fixtures/service_provider.xml +0 -79
  77. data/spec/fixtures/test3-response.xml +0 -9
  78. data/spec/fixtures/test6-response.xml +0 -10
  79. data/spec/fixtures/test7-response.xml +0 -10
  80. data/spec/fixtures/xml_missigned_assertion.xml +0 -84
  81. data/spec/fixtures/xml_signature_wrapping_attack_duplicate_ids.xml +0 -11
  82. data/spec/fixtures/xml_signature_wrapping_attack_response_attributes.xml +0 -45
  83. data/spec/fixtures/xml_signature_wrapping_attack_response_nameid.xml +0 -44
  84. data/spec/fixtures/xslt-transform-response.xml +0 -57
  85. data/spec/lib/attribute_consuming_service_spec.rb +0 -129
  86. data/spec/lib/attribute_spec.rb +0 -149
  87. data/spec/lib/authn_request_spec.rb +0 -52
  88. data/spec/lib/bindings/http_redirect_spec.rb +0 -183
  89. data/spec/lib/conditions_spec.rb +0 -74
  90. data/spec/lib/entity_spec.rb +0 -58
  91. data/spec/lib/identity_provider_spec.rb +0 -43
  92. data/spec/lib/indexed_object_spec.rb +0 -71
  93. data/spec/lib/key_spec.rb +0 -23
  94. data/spec/lib/logout_request_spec.rb +0 -33
  95. data/spec/lib/logout_response_spec.rb +0 -33
  96. data/spec/lib/message_spec.rb +0 -23
  97. data/spec/lib/response_spec.rb +0 -293
  98. data/spec/lib/service_provider_spec.rb +0 -76
  99. data/spec/lib/signable_spec.rb +0 -15
  100. 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: 8f10ad7e5f4379ecbb273d27aed0db3b58ac1db6115b54f2199662169b0ee808
4
+ data.tar.gz: 7842786be374bb809438567a58abcda27c88b5953512787b1d7a9338cf8e9901
5
5
  SHA512:
6
- metadata.gz: c427fc0852c531675c5573b1a51a3a6097414fc7ceee458a1c80d682dff696123e59d61a6e90cea0f6c221fbcef01a87f5a91f33dfebb2d0bf0f281e2b278794
7
- data.tar.gz: a2281d564327fe52d98055559663cd4c26945aafc3b535c2d4adc2851480418f387e3a80f73f8bed665afc3eaf2ab771d94576c0332a1296ef8c0e73208467eb
6
+ metadata.gz: 0ad080a07e9083b9c4734319e4344e81e0866c8d73c08da9d55312866607f6820141c161875905726f5bc8aeaa36ca50f79fb4c8bcf334c03f71b498a692d68b
7
+ data.tar.gz: a8905fd54b6137a4474857155e40288798d8fdeb033282dbfbf37bbcc0740d1c12080efc5e4a0f83bdc9a91ace3da2f1b71f9290d7019c386179e82fb3f2acb4
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|