saml-kit 1.0.9 → 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/Gemfile +2 -0
  4. data/Rakefile +2 -0
  5. data/bin/cibuild +2 -3
  6. data/bin/console +1 -0
  7. data/bin/lint +2 -4
  8. data/bin/setup +0 -2
  9. data/bin/test +2 -4
  10. data/exe/saml-kit-create-self-signed-certificate +2 -0
  11. data/exe/saml-kit-decode-http-post +2 -0
  12. data/exe/saml-kit-decode-http-redirect +2 -0
  13. data/lib/saml-kit.rb +2 -0
  14. data/lib/saml/kit.rb +2 -0
  15. data/lib/saml/kit/assertion.rb +21 -41
  16. data/lib/saml/kit/authentication_request.rb +9 -3
  17. data/lib/saml/kit/bindings.rb +2 -0
  18. data/lib/saml/kit/bindings/binding.rb +2 -0
  19. data/lib/saml/kit/bindings/http_post.rb +2 -0
  20. data/lib/saml/kit/bindings/http_redirect.rb +2 -0
  21. data/lib/saml/kit/bindings/url_builder.rb +2 -0
  22. data/lib/saml/kit/buildable.rb +2 -0
  23. data/lib/saml/kit/builders.rb +2 -0
  24. data/lib/saml/kit/builders/assertion.rb +2 -0
  25. data/lib/saml/kit/builders/authentication_request.rb +2 -0
  26. data/lib/saml/kit/builders/encrypted_assertion.rb +2 -0
  27. data/lib/saml/kit/builders/identity_provider_metadata.rb +2 -0
  28. data/lib/saml/kit/builders/logout_request.rb +2 -0
  29. data/lib/saml/kit/builders/logout_response.rb +2 -0
  30. data/lib/saml/kit/builders/metadata.rb +2 -0
  31. data/lib/saml/kit/builders/response.rb +2 -0
  32. data/lib/saml/kit/builders/service_provider_metadata.rb +2 -0
  33. data/lib/saml/kit/builders/templates/assertion.builder +2 -0
  34. data/lib/saml/kit/builders/templates/authentication_request.builder +2 -0
  35. data/lib/saml/kit/builders/templates/encrypted_assertion.builder +2 -0
  36. data/lib/saml/kit/builders/templates/identity_provider_metadata.builder +2 -0
  37. data/lib/saml/kit/builders/templates/logout_request.builder +2 -0
  38. data/lib/saml/kit/builders/templates/logout_response.builder +2 -0
  39. data/lib/saml/kit/builders/templates/metadata.builder +2 -0
  40. data/lib/saml/kit/builders/templates/response.builder +2 -0
  41. data/lib/saml/kit/builders/templates/service_provider_metadata.builder +2 -0
  42. data/lib/saml/kit/composite_metadata.rb +3 -1
  43. data/lib/saml/kit/configuration.rb +2 -0
  44. data/lib/saml/kit/default_registry.rb +2 -0
  45. data/lib/saml/kit/document.rb +9 -14
  46. data/lib/saml/kit/identity_provider_metadata.rb +4 -2
  47. data/lib/saml/kit/invalid_document.rb +2 -0
  48. data/lib/saml/kit/logout_request.rb +7 -1
  49. data/lib/saml/kit/logout_response.rb +2 -0
  50. data/lib/saml/kit/metadata.rb +35 -32
  51. data/lib/saml/kit/namespaces.rb +2 -0
  52. data/lib/saml/kit/null_assertion.rb +2 -0
  53. data/lib/saml/kit/requestable.rb +2 -0
  54. data/lib/saml/kit/respondable.rb +4 -2
  55. data/lib/saml/kit/response.rb +2 -0
  56. data/lib/saml/kit/rspec.rb +2 -0
  57. data/lib/saml/kit/rspec/have_query_param.rb +2 -0
  58. data/lib/saml/kit/rspec/have_xpath.rb +3 -8
  59. data/lib/saml/kit/serializable.rb +2 -0
  60. data/lib/saml/kit/service_provider_metadata.rb +3 -1
  61. data/lib/saml/kit/signature.rb +2 -0
  62. data/lib/saml/kit/translatable.rb +2 -0
  63. data/lib/saml/kit/trustable.rb +2 -0
  64. data/lib/saml/kit/version.rb +3 -1
  65. data/lib/saml/kit/xml_templatable.rb +2 -0
  66. data/lib/saml/kit/xsd_validatable.rb +3 -2
  67. data/saml-kit.gemspec +2 -0
  68. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0d58bbb3c3a82d505f9898c871e9038d22cbf0fe6b50c59615e3a528e3770a0
4
- data.tar.gz: 527f1c7a86aafa31d7bb5629325c177dfaeb3517c4c2d570832298c33622c647
3
+ metadata.gz: 48040329dac3aa9d3171bf1098ec49937b7667067dbd7c8f8a0fa7adc0837edb
4
+ data.tar.gz: fc968fad4d56cb939ce34cd7da486e19844f55e11f976cd8454228571cfbc2a7
5
5
  SHA512:
6
- metadata.gz: 315cb5a0c5f725577fdae560a924d27cf421299af013bdc10a69a8842add199723088165b266f68dec8fa6af47fbc3b26d8c1f72fde4da5b857e627ae6618696
7
- data.tar.gz: 05b34c3e338867fc0ebd8fe22bcddd41b41ccbf0629deb2813a2f63081cf0e05def3aaf48e9a6caa7474f754bbdffe6a224b90697a8ba7a6f9549176059d4d78
6
+ metadata.gz: 93afbfeeabbfc19f6f8d772adc63b96206ce27ad6cc3b1620d4cd934e14230123400d1b94e03f2c8b10b5a52ca341d116106dd7f3d82b272ffda1f8bc510d746
7
+ data.tar.gz: 7148ddf6fb5511bfe17f4659c1b79b2122bf7646e9b06a4f7445335fd7925b1e9117bb9c43a0efb1aca80e3f310b492e81c39522d0a3e66afed3ceb4538ba280
data/.rubocop.yml CHANGED
@@ -70,6 +70,9 @@ Naming/FileName:
70
70
  Style/Documentation:
71
71
  Enabled: false
72
72
 
73
+ Style/EachWithObject:
74
+ Enabled: false
75
+
73
76
  Style/StringLiterals:
74
77
  EnforcedStyle: 'single_quotes'
75
78
 
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
3
5
 
data/bin/cibuild CHANGED
@@ -7,8 +7,7 @@ set -e
7
7
 
8
8
  cd "$(dirname "$0")/.."
9
9
 
10
- echo "Started at…"
11
- date "+%H:%M:%S"
10
+ echo [$(date "+%H:%M:%S")] "==> Started at…"
12
11
 
13
12
  # GC customizations
14
13
  export RUBY_GC_MALLOC_LIMIT=79000000
@@ -19,4 +18,4 @@ export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1
19
18
 
20
19
  ruby -v
21
20
  gem install bundler --no-ri --no-rdoc --conservative
22
- time bin/test
21
+ bin/test
data/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'saml/kit'
data/bin/lint CHANGED
@@ -4,10 +4,8 @@ set -e
4
4
 
5
5
  [ -z "$DEBUG" ] || set -x
6
6
 
7
- echo "==> Running setup…"
8
- date "+%H:%M:%S"
7
+ echo [$(date "+%H:%M:%S")] "==> Running setup…"
9
8
  bin/setup
10
9
 
11
- echo "==> Running linters…"
12
- date "+%H:%M:%S"
10
+ echo [$(date "+%H:%M:%S")] "==> Running linters…"
13
11
  bundle exec rake rubocop
data/bin/setup CHANGED
@@ -4,5 +4,3 @@ IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
6
  bundle check || bundle install --jobs $(nproc)
7
-
8
- # Do any other automated setup that you need to do here
data/bin/test CHANGED
@@ -10,10 +10,8 @@ cd "$(dirname "$0")/.."
10
10
 
11
11
  [ -z "$DEBUG" ] || set -x
12
12
 
13
- echo "==> Running setup…"
14
- date "+%H:%M:%S"
13
+ echo [$(date "+%H:%M:%S")] "==> Running setup…"
15
14
  bin/setup
16
15
 
17
- echo "==> Running tests…"
18
- date "+%H:%M:%S"
16
+ echo [$(date "+%H:%M:%S")] "==> Running tests…"
19
17
  bundle exec rake spec
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  require 'saml/kit'
3
5
 
4
6
  Saml::Kit.deprecate("Use the 'saml-kit-cli' gem instead. saml-kit-create-self-signed-certificate")
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  require 'saml/kit'
3
5
 
4
6
  Saml::Kit.deprecate("Use the 'saml-kit-cli' gem instead. saml-kit-decode-http-post")
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  require 'saml/kit'
3
5
 
4
6
  Saml::Kit.deprecate("Use the 'saml-kit-cli' gem instead. saml-kit-decode-http-redirect*")
data/lib/saml-kit.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'saml/kit'
data/lib/saml/kit.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'saml/kit/version'
2
4
 
3
5
  require 'active_model'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  class Assertion
@@ -18,22 +20,18 @@ module Saml
18
20
  def initialize(node, configuration: Saml::Kit.configuration, private_keys: [])
19
21
  @name = 'Assertion'
20
22
  @node = node
21
- @xml_hash = hash_from(node)['Response'] || {}
22
23
  @configuration = configuration
23
24
  @occurred_at = Time.current
24
- decrypt!(::Xml::Kit::Decryption.new(
25
- private_keys: (
26
- configuration.private_keys(use: :encryption) + private_keys
27
- ).uniq
28
- ))
25
+ private_keys = (configuration.private_keys(use: :encryption) + private_keys).uniq
26
+ decrypt!(::Xml::Kit::Decryption.new(private_keys: private_keys))
29
27
  end
30
28
 
31
29
  def issuer
32
- assertion.fetch('Issuer')
30
+ at_xpath('./saml:Issuer').try(:text)
33
31
  end
34
32
 
35
33
  def name_id
36
- assertion.fetch('Subject', {}).fetch('NameID', nil)
34
+ at_xpath('./saml:Subject/saml:NameID').try(:text)
37
35
  end
38
36
 
39
37
  def signed?
@@ -54,35 +52,26 @@ module Saml
54
52
  end
55
53
 
56
54
  def attributes
57
- @attributes ||=
58
- begin
59
- attrs = assertion.fetch('AttributeStatement', {}).fetch('Attribute', [])
60
- items = if attrs.is_a? Hash
61
- [[attrs['Name'], attrs['AttributeValue']]]
62
- else
63
- attrs.map { |item| [item['Name'], item['AttributeValue']] }
64
- end
65
- Hash[items].with_indifferent_access
66
- end
55
+ @attributes ||= search('./saml:AttributeStatement/saml:Attribute').inject({}) do |memo, item|
56
+ memo[item.attribute('Name').value] = item.at_xpath('./saml:AttributeValue', Saml::Kit::Document::NAMESPACES).try(:text)
57
+ memo
58
+ end.with_indifferent_access
67
59
  end
68
60
 
69
61
  def started_at
70
- parse_date(assertion.fetch('Conditions', {}).fetch('NotBefore', nil))
62
+ parse_date(at_xpath('./saml:Conditions/@NotBefore').try(:value))
71
63
  end
72
64
 
73
65
  def expired_at
74
- parse_date(assertion.fetch('Conditions', {}).fetch('NotOnOrAfter', nil))
66
+ parse_date(at_xpath('./saml:Conditions/@NotOnOrAfter').try(:value))
75
67
  end
76
68
 
77
69
  def audiences
78
- Array(assertion['Conditions']['AudienceRestriction']['Audience'])
79
- rescue StandardError => error
80
- Saml::Kit.logger.error(error)
81
- []
70
+ search('./saml:Conditions/saml:AudienceRestriction/saml:Audience').map(&:text)
82
71
  end
83
72
 
84
73
  def encrypted?
85
- @xml_hash.fetch('EncryptedAssertion', nil).present?
74
+ @encrypted
86
75
  end
87
76
 
88
77
  def decryptable?
@@ -91,7 +80,7 @@ module Saml
91
80
  end
92
81
 
93
82
  def present?
94
- assertion.present?
83
+ @node.present?
95
84
  end
96
85
 
97
86
  def to_xml(pretty: false)
@@ -102,19 +91,10 @@ module Saml
102
91
 
103
92
  attr_reader :configuration
104
93
 
105
- def assertion
106
- @assertion ||=
107
- begin
108
- result = (hash_from(@node)['Response'] || {})['Assertion']
109
- return result if result.is_a?(Hash)
110
- {}
111
- end
112
- end
113
-
114
94
  def decrypt!(decryptor)
115
- return unless encrypted?
116
-
117
- encrypted_assertion = @node.at_xpath('./xmlenc:EncryptedData', Saml::Kit::Document::NAMESPACES)
95
+ encrypted_assertion = at_xpath('./xmlenc:EncryptedData')
96
+ @encrypted = encrypted_assertion.present?
97
+ return unless @encrypted
118
98
  @node = decryptor.decrypt_node(encrypted_assertion)
119
99
  rescue Xml::Kit::DecryptionError => error
120
100
  @cannot_decrypt = true
@@ -151,12 +131,12 @@ module Saml
151
131
  end
152
132
 
153
133
  def at_xpath(xpath)
134
+ return unless @node
154
135
  @node.at_xpath(xpath, Saml::Kit::Document::NAMESPACES)
155
136
  end
156
137
 
157
- def hash_from(node)
158
- return {} if node.nil?
159
- Hash.from_xml(node.document.root.to_s) || {}
138
+ def search(xpath)
139
+ @node.search(xpath, Saml::Kit::Document::NAMESPACES)
160
140
  end
161
141
  end
162
142
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  # This class can be used to parse a SAML AuthnRequest or generate one.
@@ -31,15 +33,19 @@ module Saml
31
33
  # Extract the AssertionConsumerServiceURL from the AuthnRequest
32
34
  # <samlp:AuthnRequest AssertionConsumerServiceURL="https://carroll.com/acs"></samlp:AuthnRequest>
33
35
  def assertion_consumer_service_url
34
- to_h[name]['AssertionConsumerServiceURL']
36
+ at_xpath('./*/@AssertionConsumerServiceURL').try(:value)
37
+ end
38
+
39
+ def name_id_format
40
+ name_id_policy
35
41
  end
36
42
 
37
43
  # Extract the NameIDPolicy from the AuthnRequest
38
44
  # <samlp:AuthnRequest>
39
45
  # <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
40
46
  # </samlp:AuthnRequest>
41
- def name_id_format
42
- to_h[name]['NameIDPolicy']['Format']
47
+ def name_id_policy
48
+ at_xpath('./*/samlp:NameIDPolicy/@Format').try(:value)
43
49
  end
44
50
 
45
51
  # Generate a Response for a specific user.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'saml/kit/bindings/binding'
2
4
  require 'saml/kit/bindings/http_post'
3
5
  require 'saml/kit/bindings/http_redirect'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Bindings
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Bindings
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Bindings
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Bindings
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Buildable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'saml/kit/xml_templatable'
2
4
  require 'saml/kit/builders/assertion'
3
5
  require 'saml/kit/builders/authentication_request'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Builders
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Builders
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Builders
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Builders
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Builders
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Builders
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Builders
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Builders
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Builders
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  xml.Assertion(assertion_options) do
2
4
  xml.Issuer issuer
3
5
  signature_for(reference_id: reference_id, xml: xml)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  xml.instruct!
2
4
  xml.tag!('samlp:AuthnRequest', request_options) do
3
5
  xml.tag!('saml:Issuer', issuer)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  xml.EncryptedAssertion xmlns: Saml::Kit::Namespaces::ASSERTION do
2
4
  encryption_for(xml: xml) do |xml|
3
5
  render assertion, xml: xml
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  xml.IDPSSODescriptor descriptor_options do
2
4
  configuration.certificates(use: :signing).each do |certificate|
3
5
  render certificate, xml: xml
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  xml.instruct!
2
4
  xml.LogoutRequest logout_request_options do
3
5
  xml.Issuer({ xmlns: Saml::Kit::Namespaces::ASSERTION }, issuer)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  xml.instruct!
2
4
  xml.LogoutResponse logout_response_options do
3
5
  xml.Issuer(issuer, xmlns: Saml::Kit::Namespaces::ASSERTION)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  xml.instruct!
2
4
  xml.EntityDescriptor entity_descriptor_options do
3
5
  signature_for(reference_id: id, xml: xml)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  xml.instruct!
2
4
  xml.Response response_options do
3
5
  xml.Issuer(issuer, xmlns: Saml::Kit::Namespaces::ASSERTION)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  xml.SPSSODescriptor descriptor_options do
2
4
  configuration.certificates(use: :signing).each do |certificate|
3
5
  render certificate, xml: xml
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  class CompositeMetadata < Metadata # :nodoc:
@@ -14,7 +16,7 @@ module Saml
14
16
 
15
17
  def services(type)
16
18
  xpath = map { |x| "//md:EntityDescriptor/md:#{x.name}/md:#{type}" }.join('|')
17
- document.find_all(xpath).map do |item|
19
+ search(xpath).map do |item|
18
20
  binding = item.attribute('Binding').value
19
21
  location = item.attribute('Location').value
20
22
  Saml::Kit::Bindings.create_for(binding, location)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  # This class represents the main configuration that is use for generating SAML documents.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  # The default metadata registry is used to fetch the metadata associated with an issuer or entity id.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  class Document
@@ -29,27 +31,27 @@ module Saml
29
31
 
30
32
  # Returns the ID for the SAML document.
31
33
  def id
32
- root.fetch('ID', nil)
34
+ at_xpath('./*/@ID').try(:value)
33
35
  end
34
36
 
35
37
  # Returns the Issuer for the SAML document.
36
38
  def issuer
37
- root.fetch('Issuer', nil)
39
+ at_xpath('./*/saml:Issuer').try(:text)
38
40
  end
39
41
 
40
42
  # Returns the Version of the SAML document.
41
43
  def version
42
- root.fetch('Version', {})
44
+ at_xpath('./*/@Version').try(:value)
43
45
  end
44
46
 
45
47
  # Returns the Destination of the SAML document.
46
48
  def destination
47
- root.fetch('Destination', nil)
49
+ at_xpath('./*/@Destination').try(:value)
48
50
  end
49
51
 
50
52
  # Returns the Destination of the SAML document.
51
53
  def issue_instant
52
- Time.parse(root['IssueInstant'])
54
+ Time.parse(at_xpath('./*/@IssueInstant').try(:value))
53
55
  end
54
56
 
55
57
  # Returns the SAML document returned as a Hash.
@@ -102,15 +104,12 @@ module Saml
102
104
  # @param xml [String] the raw xml string.
103
105
  # @param configuration [Saml::Kit::Configuration] the configuration to use for unpacking the document.
104
106
  def to_saml_document(xml, configuration: Saml::Kit.configuration)
105
- xml_document = ::Xml::Kit::Document.new(xml, namespaces: {
106
- "samlp": ::Saml::Kit::Namespaces::PROTOCOL
107
- })
108
107
  constructor = {
109
108
  'AuthnRequest' => Saml::Kit::AuthenticationRequest,
110
109
  'LogoutRequest' => Saml::Kit::LogoutRequest,
111
110
  'LogoutResponse' => Saml::Kit::LogoutResponse,
112
111
  'Response' => Saml::Kit::Response,
113
- }[xml_document.find_by(XPATH).name] || InvalidDocument
112
+ }[Nokogiri::XML(xml).at_xpath(XPATH, "samlp": ::Saml::Kit::Namespaces::PROTOCOL).name] || InvalidDocument
114
113
  constructor.new(xml, configuration: configuration)
115
114
  rescue StandardError => error
116
115
  Saml::Kit.logger.error(error)
@@ -138,10 +137,6 @@ module Saml
138
137
 
139
138
  attr_reader :content, :name, :configuration
140
139
 
141
- def root
142
- to_h.fetch(name, {})
143
- end
144
-
145
140
  def must_match_xsd
146
141
  matches_xsd?(PROTOCOL_XSD)
147
142
  end
@@ -151,7 +146,7 @@ module Saml
151
146
  end
152
147
 
153
148
  def expected_type?
154
- to_h[name].present?
149
+ at_xpath("./samlp:#{name}").present?
155
150
  end
156
151
 
157
152
  def must_be_valid_version
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  # This class is used to parse the IDPSSODescriptor from a SAML metadata document.
@@ -38,7 +40,7 @@ module Saml
38
40
  # Returns the IDPSSODescriptor/@WantAuthnRequestsSigned attribute.
39
41
  def want_authn_requests_signed
40
42
  xpath = "/md:EntityDescriptor/md:#{name}"
41
- attribute = document.find_by(xpath).attribute('WantAuthnRequestsSigned')
43
+ attribute = at_xpath(xpath).attribute('WantAuthnRequestsSigned')
42
44
  return true if attribute.nil?
43
45
  attribute.text.casecmp('true').zero?
44
46
  end
@@ -57,7 +59,7 @@ module Saml
57
59
 
58
60
  # Returns each of the Attributes in the metadata.
59
61
  def attributes
60
- document.find_all("/md:EntityDescriptor/md:#{name}/saml:Attribute").map do |item|
62
+ search("/md:EntityDescriptor/md:#{name}/saml:Attribute").map do |item|
61
63
  {
62
64
  format: item.attribute('NameFormat').try(:value),
63
65
  name: item.attribute('Name').value,
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  # {include:file:spec/saml/invalid_document_spec.rb}
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  # This class can be used to parse a LogoutRequest SAML document.
@@ -36,7 +38,11 @@ module Saml
36
38
 
37
39
  # Returns the NameID value.
38
40
  def name_id
39
- to_h[name]['NameID']
41
+ at_xpath('./*/saml:NameID').try(:text)
42
+ end
43
+
44
+ def name_id_format
45
+ at_xpath('./*/saml:NameID/@Format').try(:value)
40
46
  end
41
47
 
42
48
  # Generates a Serialized LogoutResponse using the encoding rules for the specified binding.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  # This class is used to parse a LogoutResponse SAML document.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  # The Metadata object can be used to parse an XML string of metadata.
@@ -29,11 +31,11 @@ module Saml
29
31
  include Buildable
30
32
  METADATA_XSD = File.expand_path('./xsd/saml-schema-metadata-2.0.xsd', File.dirname(__FILE__)).freeze
31
33
  NAMESPACES = {
32
- "NameFormat": Namespaces::ATTR_SPLAT,
33
- "ds": ::Xml::Kit::Namespaces::XMLDSIG,
34
- "md": Namespaces::METADATA,
35
- "saml": Namespaces::ASSERTION,
36
- "samlp": Namespaces::PROTOCOL,
34
+ NameFormat: Namespaces::ATTR_SPLAT,
35
+ ds: ::Xml::Kit::Namespaces::XMLDSIG,
36
+ md: Namespaces::METADATA,
37
+ saml: Namespaces::ASSERTION,
38
+ samlp: Namespaces::PROTOCOL,
37
39
  }.freeze
38
40
 
39
41
  validates_presence_of :metadata
@@ -50,36 +52,34 @@ module Saml
50
52
 
51
53
  # Returns the /EntityDescriptor/@entityID
52
54
  def entity_id
53
- document.find_by('/md:EntityDescriptor/@entityID').value
55
+ at_xpath('/md:EntityDescriptor/@entityID').try(:value)
54
56
  end
55
57
 
56
58
  # Returns the supported NameIDFormats.
57
59
  def name_id_formats
58
- document.find_all("/md:EntityDescriptor/md:#{name}/md:NameIDFormat").map(&:text)
60
+ search("/md:EntityDescriptor/md:#{name}/md:NameIDFormat").map(&:text)
59
61
  end
60
62
 
61
63
  # Returns the Organization Name
62
64
  def organization_name
63
- document.find_by('/md:EntityDescriptor/md:Organization/md:OrganizationName').try(:text)
65
+ at_xpath('/md:EntityDescriptor/md:Organization/md:OrganizationName').try(:text)
64
66
  end
65
67
 
66
68
  # Returns the Organization URL
67
69
  def organization_url
68
- document.find_by('/md:EntityDescriptor/md:Organization/md:OrganizationURL').try(:text)
70
+ at_xpath('/md:EntityDescriptor/md:Organization/md:OrganizationURL').try(:text)
69
71
  end
70
72
 
71
73
  # Returns the Company
72
74
  def contact_person_company
73
- document.find_by('/md:EntityDescriptor/md:ContactPerson/md:Company').try(:text)
75
+ at_xpath('/md:EntityDescriptor/md:ContactPerson/md:Company').try(:text)
74
76
  end
75
77
 
76
78
  # Returns each of the X509 certificates.
77
79
  def certificates
78
- @certificates ||= document.find_all("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item|
79
- cert = item.at_xpath('./ds:KeyInfo/ds:X509Data/ds:X509Certificate', NAMESPACES).text
80
- attribute = item.attribute('use')
81
- use = attribute.nil? ? nil : item.attribute('use').value
82
- ::Xml::Kit::Certificate.new(cert, use: use)
80
+ @certificates ||= search("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item|
81
+ cert = item.at_xpath('./ds:KeyInfo/ds:X509Data/ds:X509Certificate', 'ds' => ::Xml::Kit::Namespaces::XMLDSIG).try(:text)
82
+ ::Xml::Kit::Certificate.new(cert, use: item.attribute('use').try(:value))
83
83
  end
84
84
  end
85
85
 
@@ -97,7 +97,7 @@ module Saml
97
97
  #
98
98
  # @param type [String] the type of service. .E.g. `AssertionConsumerServiceURL`
99
99
  def services(type)
100
- document.find_all("/md:EntityDescriptor/md:#{name}/md:#{type}").map do |item|
100
+ search("/md:EntityDescriptor/md:#{name}/md:#{type}").map do |item|
101
101
  binding = item.attribute('Binding').value
102
102
  location = item.attribute('Location').value
103
103
  Saml::Kit::Bindings.create_for(binding, location)
@@ -132,9 +132,7 @@ module Saml
132
132
  # @param relay_state [String] the relay state to have echo'd back.
133
133
  # @return [Array] Returns an array with a url and Hash of parameters to send to the other party.
134
134
  def logout_request_for(user, binding: :http_post, relay_state: nil)
135
- builder = Saml::Kit::LogoutRequest.builder(user) do |x|
136
- yield x if block_given?
137
- end
135
+ builder = Saml::Kit::LogoutRequest.builder(user) { |x| yield x if block_given? }
138
136
  request_binding = single_logout_service_for(binding: binding)
139
137
  request_binding.serialize(builder, relay_state: relay_state)
140
138
  end
@@ -145,9 +143,7 @@ module Saml
145
143
  # @param use [Symbol] the type of certificates to look at. Can be `:signing` or `:encryption`.
146
144
  # @return [Xml::Kit::Certificate] returns the matching `{Xml::Kit::Certificate}`
147
145
  def matches?(fingerprint, use: :signing)
148
- certificates.find do |certificate|
149
- certificate.for?(use) && certificate.fingerprint == fingerprint
150
- end
146
+ certificates.find { |x| x.for?(use) && x.fingerprint == fingerprint }
151
147
  end
152
148
 
153
149
  # Returns the XML document converted to a Hash.
@@ -159,7 +155,7 @@ module Saml
159
155
  #
160
156
  # @param pretty [Symbol] true to return a human friendly version of the XML.
161
157
  def to_xml(pretty: false)
162
- document.to_xml(pretty: pretty)
158
+ pretty ? to_nokogiri.to_xml(indent: 2) : @xml
163
159
  end
164
160
 
165
161
  # Returns the XML document as a [String].
@@ -189,13 +185,15 @@ module Saml
189
185
  # @param content [String] the raw metadata XML.
190
186
  # @return [Saml::Kit::Metadata] the metadata document or subclass.
191
187
  def from(content)
192
- hash = Hash.from_xml(content)
193
- entity_descriptor = hash['EntityDescriptor']
194
- if entity_descriptor.key?('SPSSODescriptor') && entity_descriptor.key?('IDPSSODescriptor')
188
+ document = Nokogiri::XML(content)
189
+ return unless document.at_xpath('/md:EntityDescriptor', NAMESPACES)
190
+ sp = document.at_xpath('/md:EntityDescriptor/md:SPSSODescriptor', NAMESPACES)
191
+ idp = document.at_xpath('/md:EntityDescriptor/md:IDPSSODescriptor', NAMESPACES)
192
+ if sp && idp
195
193
  Saml::Kit::CompositeMetadata.new(content)
196
- elsif entity_descriptor.keys.include?('SPSSODescriptor')
194
+ elsif sp
197
195
  Saml::Kit::ServiceProviderMetadata.new(content)
198
- elsif entity_descriptor.keys.include?('IDPSSODescriptor')
196
+ elsif idp
199
197
  Saml::Kit::IdentityProviderMetadata.new(content)
200
198
  end
201
199
  end
@@ -210,16 +208,21 @@ module Saml
210
208
 
211
209
  attr_reader :xml
212
210
 
213
- def document
214
- @document ||= ::Xml::Kit::Document.new(xml, namespaces: NAMESPACES)
211
+ # @!visibility private
212
+ def to_nokogiri
213
+ @nokogiri ||= Nokogiri::XML(xml)
215
214
  end
216
215
 
217
216
  def at_xpath(xpath)
218
- document.find_by(xpath)
217
+ to_nokogiri.at_xpath(xpath, NAMESPACES)
218
+ end
219
+
220
+ def search(xpath)
221
+ to_nokogiri.search(xpath, NAMESPACES)
219
222
  end
220
223
 
221
224
  def metadata
222
- document.find_by("/md:EntityDescriptor/md:#{name}").present?
225
+ at_xpath("/md:EntityDescriptor/md:#{name}").present?
223
226
  end
224
227
 
225
228
  def must_contain_descriptor
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Namespaces
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  class NullAssertion
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Requestable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Respondable
@@ -16,12 +18,12 @@ module Saml
16
18
 
17
19
  # Returns the /Status/StatusCode@Value
18
20
  def status_code
19
- to_h.fetch(name, {}).fetch('Status', {}).fetch('StatusCode', {}).fetch('Value', nil)
21
+ at_xpath('./*/samlp:Status/samlp:StatusCode/@Value').try(:value)
20
22
  end
21
23
 
22
24
  # Returns the /InResponseTo attribute.
23
25
  def in_response_to
24
- to_h.fetch(name, {}).fetch('InResponseTo', nil)
26
+ at_xpath('./*/@InResponseTo').try(:value)
25
27
  end
26
28
 
27
29
  # Returns true if the Status code is #{Saml::Kit::Namespaces::SUCCESS}
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  # {include:file:spec/examples/response_spec.rb}
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'saml/kit/rspec/have_query_param'
2
4
  require 'saml/kit/rspec/have_xpath'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
 
3
5
  RSpec::Matchers.define :have_query_param do |key|
@@ -1,13 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec::Matchers.define :have_xpath do |xpath|
2
4
  match do |actual|
3
- namespaces = {
4
- "NameFormat": Saml::Kit::Namespaces::ATTR_SPLAT,
5
- "ds": ::Xml::Kit::Namespaces::XMLDSIG,
6
- "md": Saml::Kit::Namespaces::METADATA,
7
- "saml": Saml::Kit::Namespaces::ASSERTION,
8
- "samlp": Saml::Kit::Namespaces::PROTOCOL,
9
- }
10
- xml_document(actual).xpath(xpath, namespaces).any?
5
+ xml_document(actual).xpath(xpath, Saml::Kit::Document::NAMESPACES).any?
11
6
  end
12
7
 
13
8
  failure_message do |actual|
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Serializable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  # {include:file:spec/examples/service_provider_metadata_spec.rb}
@@ -20,7 +22,7 @@ module Saml
20
22
 
21
23
  # Returns true when the metadata demands that Assertions must be signed.
22
24
  def want_assertions_signed
23
- attribute = document.find_by("/md:EntityDescriptor/md:#{name}").attribute('WantAssertionsSigned')
25
+ attribute = at_xpath("/md:EntityDescriptor/md:#{name}").attribute('WantAssertionsSigned')
24
26
  return true if attribute.nil?
25
27
  attribute.text.casecmp('true').zero?
26
28
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  class Signature
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Translatable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module Trustable
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
- VERSION = '1.0.9'.freeze
5
+ VERSION = '1.0.10'.freeze
4
6
  end
5
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module XmlTemplatable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Saml
2
4
  module Kit
3
5
  module XsdValidatable
@@ -5,8 +7,7 @@ module Saml
5
7
  def matches_xsd?(xsd)
6
8
  Dir.chdir(File.dirname(xsd)) do
7
9
  xsd = Nokogiri::XML::Schema(IO.read(xsd))
8
- document = Nokogiri::XML(to_xml)
9
- xsd.validate(document).each do |error|
10
+ xsd.validate(to_nokogiri).each do |error|
10
11
  errors[:base] << error.message
11
12
  end
12
13
  end
data/saml-kit.gemspec CHANGED
@@ -1,4 +1,6 @@
1
1
 
2
+ # frozen_string_literal: true
3
+
2
4
  lib = File.expand_path('../lib', __FILE__)
3
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
6
  require 'saml/kit/version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saml-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.9
4
+ version: 1.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - mo khan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-18 00:00:00.000000000 Z
11
+ date: 2018-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -294,7 +294,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
294
294
  version: '0'
295
295
  requirements: []
296
296
  rubyforge_project:
297
- rubygems_version: 2.7.5
297
+ rubygems_version: 2.7.6
298
298
  signing_key:
299
299
  specification_version: 4
300
300
  summary: A simple toolkit for working with SAML.