saml_idp_metadata 0.3.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35868d94a462a7ce8648be65e5d558677e04903827c4debbd7b5ad775a527942
4
- data.tar.gz: badd623d95e8d6153df2507e39f6c2afddb66d1e09c2af1190a2583ab5288433
3
+ metadata.gz: bda49c64b9043ebd4435d5465fd6d1b64463f9082723d8cbb22a930870d9770e
4
+ data.tar.gz: a4a9e8ec42fcfad075be611070eaf887e1e7d679c0165eac19be8141f4683775
5
5
  SHA512:
6
- metadata.gz: 7cdfe25c024405ab02cd489863affc7a8ad1702136c6abbc59f197a4db7fc74e1354f9e088e8dd3900cc03e5d339f06720529e2dec79aa393fa42ac7c4ee3bd4
7
- data.tar.gz: c1fbb625c786c657b4e385519b9c199f620ad709e46cd0f96f54f68b4f69b08be3618094b376d6382e6b45c162af4c68c378a9831640f4bce784b4ea20007c8b
6
+ metadata.gz: ed85b896eb68fe0bf0eb37d2fa5814af5243dd7a91a84f1a85e7ec998b00f7a6eed996d6e82b442a1a4db950e18323c0cee07dca99a45caebf43e4981b000c6e
7
+ data.tar.gz: 6efd585b8266f52ad93a66578792dc6fdceb7d64d511fd196ff8642694ac1edce244199af302e79f129f8bec1ba22652de093b5378cbc2daa3c2a46c52131118
data/.circleci/config.yml CHANGED
@@ -60,7 +60,6 @@ workflows:
60
60
  matrix:
61
61
  parameters:
62
62
  ruby-version:
63
- - "3.1"
64
63
  - "3.2"
65
64
  - "3.3"
66
65
  - "3.4"
@@ -13,7 +13,6 @@ jobs:
13
13
  fail-fast: false
14
14
  matrix:
15
15
  ruby-version:
16
- - '3.1'
17
16
  - '3.2'
18
17
  - '3.3'
19
18
  - '3.4'
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-03-29 08:31:38 UTC using RuboCop version 1.75.1.
3
+ # on 2025-03-29 09:03:43 UTC using RuboCop version 1.75.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -11,9 +11,29 @@
11
11
  Metrics/AbcSize:
12
12
  Max: 21
13
13
 
14
- # Offense count: 26
14
+ # Offense count: 1
15
+ # Configuration parameters: CountComments, CountAsOne.
16
+ Metrics/ClassLength:
17
+ Max: 139
18
+
19
+ # Offense count: 1
20
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
21
+ Metrics/CyclomaticComplexity:
22
+ Max: 9
23
+
24
+ # Offense count: 1
25
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
26
+ Metrics/MethodLength:
27
+ Max: 12
28
+
29
+ # Offense count: 1
30
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
31
+ Metrics/PerceivedComplexity:
32
+ Max: 9
33
+
34
+ # Offense count: 1
15
35
  # This cop supports safe autocorrection (--autocorrect).
16
36
  # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
17
37
  # URISchemes: http, https
18
38
  Layout/LineLength:
19
- Max: 126
39
+ Max: 121
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.3.8](https://github.com/tknzk/saml_idp_metadata/tree/v0.3.8) (2025-03-29)
4
+
5
+ [Full Changelog](https://github.com/tknzk/saml_idp_metadata/compare/v0.3.7...v0.3.8)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - bumpup [\#235](https://github.com/tknzk/saml_idp_metadata/pull/235) ([tknzk](https://github.com/tknzk))
10
+ - Obey rubocop [\#234](https://github.com/tknzk/saml_idp_metadata/pull/234) ([tknzk](https://github.com/tknzk))
11
+ - bundle update [\#233](https://github.com/tknzk/saml_idp_metadata/pull/233) ([tknzk](https://github.com/tknzk))
12
+ - refactor github actions [\#232](https://github.com/tknzk/saml_idp_metadata/pull/232) ([tknzk](https://github.com/tknzk))
13
+ - refactor circleci config [\#231](https://github.com/tknzk/saml_idp_metadata/pull/231) ([tknzk](https://github.com/tknzk))
14
+ - require ruby \>= 3.1 [\#230](https://github.com/tknzk/saml_idp_metadata/pull/230) ([tknzk](https://github.com/tknzk))
15
+ - bundle update [\#229](https://github.com/tknzk/saml_idp_metadata/pull/229) ([tknzk](https://github.com/tknzk))
16
+
3
17
  ## [v0.3.7](https://github.com/tknzk/saml_idp_metadata/tree/v0.3.7) (2025-03-21)
4
18
 
5
19
  [Full Changelog](https://github.com/tknzk/saml_idp_metadata/compare/v0.3.6...v0.3.7)
data/Gemfile.lock CHANGED
@@ -1,45 +1,22 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- saml_idp_metadata (0.3.8)
5
- activesupport (< 8)
4
+ saml_idp_metadata (1.0.0)
6
5
  rexml
7
6
 
8
7
  GEM
9
8
  remote: https://rubygems.org/
10
9
  specs:
11
- activesupport (7.2.2.1)
12
- base64
13
- benchmark (>= 0.3)
14
- bigdecimal
15
- concurrent-ruby (~> 1.0, >= 1.3.1)
16
- connection_pool (>= 2.2.5)
17
- drb
18
- i18n (>= 1.6, < 2)
19
- logger (>= 1.4.2)
20
- minitest (>= 5.1)
21
- securerandom (>= 0.3)
22
- tzinfo (~> 2.0, >= 2.0.5)
23
10
  ast (2.4.3)
24
- base64 (0.2.0)
25
- benchmark (0.4.0)
26
- bigdecimal (3.1.9)
27
11
  coderay (1.1.3)
28
- concurrent-ruby (1.3.5)
29
- connection_pool (2.5.0)
30
- diff-lcs (1.6.1)
12
+ diff-lcs (1.6.2)
31
13
  docile (1.4.1)
32
- drb (2.2.1)
33
- i18n (1.14.7)
34
- concurrent-ruby (~> 1.0)
35
- json (2.10.2)
36
- language_server-protocol (3.17.0.4)
14
+ json (2.12.2)
15
+ language_server-protocol (3.17.0.5)
37
16
  lint_roller (1.1.0)
38
- logger (1.7.0)
39
17
  method_source (1.1.0)
40
- minitest (5.25.5)
41
- parallel (1.26.3)
42
- parser (3.3.7.3)
18
+ parallel (1.27.0)
19
+ parser (3.3.8.0)
43
20
  ast (~> 2.4.1)
44
21
  racc
45
22
  prism (1.4.0)
@@ -57,14 +34,14 @@ GEM
57
34
  rspec-mocks (~> 3.13.0)
58
35
  rspec-core (3.13.3)
59
36
  rspec-support (~> 3.13.0)
60
- rspec-expectations (3.13.3)
37
+ rspec-expectations (3.13.4)
61
38
  diff-lcs (>= 1.2.0, < 2.0)
62
39
  rspec-support (~> 3.13.0)
63
- rspec-mocks (3.13.2)
40
+ rspec-mocks (3.13.4)
64
41
  diff-lcs (>= 1.2.0, < 2.0)
65
42
  rspec-support (~> 3.13.0)
66
- rspec-support (3.13.2)
67
- rubocop (1.75.1)
43
+ rspec-support (3.13.3)
44
+ rubocop (1.75.7)
68
45
  json (~> 2.3)
69
46
  language_server-protocol (~> 3.17.0.2)
70
47
  lint_roller (~> 1.1.0)
@@ -72,28 +49,25 @@ GEM
72
49
  parser (>= 3.3.0.2)
73
50
  rainbow (>= 2.2.2, < 4.0)
74
51
  regexp_parser (>= 2.9.3, < 3.0)
75
- rubocop-ast (>= 1.43.0, < 2.0)
52
+ rubocop-ast (>= 1.44.0, < 2.0)
76
53
  ruby-progressbar (~> 1.7)
77
54
  unicode-display_width (>= 2.4.0, < 4.0)
78
- rubocop-ast (1.43.0)
55
+ rubocop-ast (1.44.1)
79
56
  parser (>= 3.3.7.2)
80
57
  prism (~> 1.4)
81
58
  rubocop-rake (0.7.1)
82
59
  lint_roller (~> 1.1)
83
60
  rubocop (>= 1.72.1)
84
- rubocop-rspec (3.5.0)
61
+ rubocop-rspec (3.6.0)
85
62
  lint_roller (~> 1.1)
86
63
  rubocop (~> 1.72, >= 1.72.1)
87
64
  ruby-progressbar (1.13.0)
88
- securerandom (0.4.1)
89
65
  simplecov (0.22.0)
90
66
  docile (~> 1.1)
91
67
  simplecov-html (~> 0.11)
92
68
  simplecov_json_formatter (~> 0.1)
93
69
  simplecov-html (0.13.1)
94
70
  simplecov_json_formatter (0.1.4)
95
- tzinfo (2.0.6)
96
- concurrent-ruby (~> 1.0)
97
71
  unicode-display_width (3.1.4)
98
72
  unicode-emoji (~> 4.0, >= 4.0.4)
99
73
  unicode-emoji (4.0.4)
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support'
4
- require 'active_support/core_ext'
3
+ require 'rexml/document'
5
4
 
6
5
  module SamlIdpMetadata
7
6
  #
8
- # SAML IdP metadata parser
7
+ # SAML IdP metadata parser with REXML
9
8
  #
10
9
  class Parser
11
10
  attr_reader :xml, :xmlns, :entity_id, :sso_http_redirect_url, :sso_http_post_url, :slo_url, :nameid_format,
@@ -13,7 +12,7 @@ module SamlIdpMetadata
13
12
 
14
13
  def initialize(xml:)
15
14
  @xml = xml
16
- @hash = Hash.from_xml(xml)
15
+ @doc = REXML::Document.new(xml)
17
16
 
18
17
  @xmlns = nil
19
18
  @entity_id = nil
@@ -30,10 +29,9 @@ module SamlIdpMetadata
30
29
 
31
30
  def call
32
31
  @xmlns = parse_xmlns
33
-
34
32
  @entity_id = parse_entity_id
35
33
  @sso_http_redirect_url = parse_sso_http_redirect_url
36
- @sso_http_post_url = parse_sso_http_post_url.presence || parse_sso_http_redirect_url
34
+ @sso_http_post_url = parse_sso_http_post_url || parse_sso_http_redirect_url
37
35
  @slo_url = parse_slo_url
38
36
  @nameid_format = parse_nameid_format
39
37
  @x509_certificate = parse_x509_certificate
@@ -46,7 +44,7 @@ module SamlIdpMetadata
46
44
  end
47
45
 
48
46
  def ensure_params?
49
- entity_id.present? && sso_http_redirect_url.present? && sso_http_post_url.present? && x509_certificate.present?
47
+ present?(entity_id) && present?(sso_http_redirect_url) && present?(sso_http_post_url) && present?(x509_certificate)
50
48
  end
51
49
 
52
50
  def build_params
@@ -64,78 +62,148 @@ module SamlIdpMetadata
64
62
  private
65
63
 
66
64
  def entity_descriptor
67
- if @hash['EntitiesDescriptor'].present?
68
- @hash['EntitiesDescriptor']['EntityDescriptor']
65
+ # Handle EntitiesDescriptor case
66
+ if @doc.root.name == 'EntitiesDescriptor'
67
+ find_with_namespace(@doc.root.elements, 'EntityDescriptor')
69
68
  else
70
- @hash['EntityDescriptor']
69
+ @doc.root
71
70
  end
72
71
  end
73
72
 
74
73
  def parse_entity_id
75
- entity_descriptor['entityID']
74
+ entity_descriptor&.attributes&.[]('entityID')
76
75
  end
77
76
 
78
77
  def parse_xmlns
79
- entity_descriptor.key?('xmlns:md') ? entity_descriptor['xmlns:md'] : entity_descriptor['xmlns']
78
+ if entity_descriptor&.attributes&.[]('xmlns:md')
79
+ entity_descriptor.attributes['xmlns:md']
80
+ else
81
+ entity_descriptor&.attributes&.[]('xmlns')
82
+ end
80
83
  end
81
84
 
82
- def parse_sso_http_redirect_url
83
- return nil if entity_descriptor.dig('IDPSSODescriptor', 'SingleSignOnService').nil?
85
+ def idp_descriptor
86
+ find_with_namespace(entity_descriptor&.elements, 'IDPSSODescriptor')
87
+ end
84
88
 
85
- single_signon_services = entity_descriptor['IDPSSODescriptor']['SingleSignOnService']
89
+ def parse_sso_http_redirect_url
90
+ services = find_all_with_namespace(idp_descriptor&.elements, 'SingleSignOnService')
91
+ return nil if services.empty?
86
92
 
87
- return single_signon_services['Location'] if single_signon_services.is_a?(Hash)
93
+ # If there's only one service
94
+ return services.first.attributes['Location'] if services.size == 1
88
95
 
89
- single_signon_services.each do |service|
90
- return service['Location'] if service['Binding'] == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
96
+ # Find service with HTTP-Redirect binding
97
+ services.each do |service|
98
+ binding = service.attributes['Binding']
99
+ return service.attributes['Location'] if binding == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
91
100
  end
101
+
92
102
  nil
93
103
  end
94
104
 
95
105
  def parse_sso_http_post_url
96
- return nil if entity_descriptor.dig('IDPSSODescriptor', 'SingleSignOnService').nil?
97
-
98
- single_signon_services = entity_descriptor['IDPSSODescriptor']['SingleSignOnService']
106
+ services = find_all_with_namespace(idp_descriptor&.elements, 'SingleSignOnService')
107
+ return nil if services.empty?
99
108
 
100
- return single_signon_services['Location'] if single_signon_services.is_a?(Hash)
109
+ # If there's only one service
110
+ return services.first.attributes['Location'] if services.size == 1
101
111
 
102
- single_signon_services.each do |service|
103
- return service['Location'] if service['Binding'] == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
112
+ # Find service with HTTP-POST binding
113
+ services.each do |service|
114
+ binding = service.attributes['Binding']
115
+ return service.attributes['Location'] if binding == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
104
116
  end
117
+
105
118
  nil
106
119
  end
107
120
 
108
121
  def parse_slo_url
109
- return nil if entity_descriptor.dig('IDPSSODescriptor', 'SingleLogoutService').nil?
110
-
111
- single_logout_services = entity_descriptor['IDPSSODescriptor']['SingleLogoutService']
122
+ services = find_all_with_namespace(idp_descriptor&.elements, 'SingleLogoutService')
123
+ return nil if services.empty?
112
124
 
113
- return single_logout_services['Location'] if single_logout_services.is_a?(Hash)
125
+ # If there's only one service
126
+ return services.first.attributes['Location'] if services.size == 1
114
127
 
115
- single_logout_services.each do |service|
116
- return service['Location'] if service['Binding'] == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
128
+ # Find service with HTTP-Redirect binding
129
+ services.each do |service|
130
+ binding = service.attributes['Binding']
131
+ return service.attributes['Location'] if binding == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
117
132
  end
133
+
118
134
  nil
119
135
  end
120
136
 
121
137
  def parse_nameid_format
122
- return nil if entity_descriptor.dig('IDPSSODescriptor', 'NameIDFormat').nil?
138
+ formats = find_all_with_namespace(idp_descriptor&.elements, 'NameIDFormat')
139
+ return nil if formats.empty?
123
140
 
124
- if entity_descriptor['IDPSSODescriptor']['NameIDFormat'].instance_of?(Array)
125
- entity_descriptor['IDPSSODescriptor']['NameIDFormat'].last
126
- else
127
- entity_descriptor['IDPSSODescriptor']['NameIDFormat']
128
- end
141
+ # Return the last format if there are multiple
142
+ formats.last.text
129
143
  end
130
144
 
131
145
  def parse_x509_certificate
132
- return nil if entity_descriptor.dig('IDPSSODescriptor', 'KeyDescriptor').nil?
146
+ key_descriptors = find_all_with_namespace(idp_descriptor&.elements, 'KeyDescriptor')
147
+ return nil if key_descriptors.empty?
133
148
 
134
- if entity_descriptor['IDPSSODescriptor']['KeyDescriptor'].instance_of?(Array)
135
- entity_descriptor['IDPSSODescriptor']['KeyDescriptor'].last['KeyInfo']['X509Data']['X509Certificate']
136
- else
137
- entity_descriptor['IDPSSODescriptor']['KeyDescriptor']['KeyInfo']['X509Data']['X509Certificate']
138
- end
149
+ # Use the last key descriptor
150
+ key_descriptor = key_descriptors.last
151
+
152
+ # Navigate the XML structure to find the X509Certificate element
153
+ key_info = find_with_namespace(key_descriptor.elements, 'KeyInfo') ||
154
+ find_element(key_descriptor.elements, 'ds:KeyInfo')
155
+
156
+ return nil unless key_info
157
+
158
+ x509_data = find_with_namespace(key_info.elements, 'X509Data') ||
159
+ find_element(key_info.elements, 'ds:X509Data')
160
+
161
+ return nil unless x509_data
162
+
163
+ cert_element = find_with_namespace(x509_data.elements, 'X509Certificate') ||
164
+ find_element(x509_data.elements, 'ds:X509Certificate')
165
+
166
+ cert_element&.text
167
+ end
168
+
169
+ # Helper methods to handle namespace variations
170
+ def find_with_namespace(elements, name)
171
+ return nil if elements.nil?
172
+
173
+ # Try with different namespace prefixes
174
+ element = find_element(elements, name) ||
175
+ find_element(elements, "md:#{name}") ||
176
+ find_element(elements, "saml:#{name}")
177
+
178
+ element
179
+ end
180
+
181
+ def find_all_with_namespace(elements, name)
182
+ return [] if elements.nil?
183
+
184
+ # Collect elements with different namespace prefixes
185
+ result = []
186
+ result.concat(find_all_elements(elements, name))
187
+ result.concat(find_all_elements(elements, "md:#{name}"))
188
+ result.concat(find_all_elements(elements, "saml:#{name}"))
189
+
190
+ result
191
+ end
192
+
193
+ def find_element(elements, name)
194
+ return nil if elements.nil?
195
+
196
+ elements.to_a.find { |e| e.name == name }
197
+ end
198
+
199
+ def find_all_elements(elements, name)
200
+ return [] if elements.nil?
201
+
202
+ elements.to_a.select { |e| e.name == name }
203
+ end
204
+
205
+ def present?(value)
206
+ value && !value.to_s.strip.empty?
139
207
  end
140
208
  end
141
209
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SamlIdpMetadata
4
- VERSION = '0.3.8'
4
+ VERSION = '1.0.0'
5
5
  end
@@ -6,7 +6,7 @@ require 'saml_idp_metadata/version'
6
6
 
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = 'saml_idp_metadata'
9
- spec.required_ruby_version = '>= 3.1', '< 4'
9
+ spec.required_ruby_version = '>= 3.2', '< 4'
10
10
  spec.version = SamlIdpMetadata::VERSION
11
11
  spec.authors = ['tknzk']
12
12
  spec.email = ['info@tknzk.dev']
@@ -25,7 +25,6 @@ Gem::Specification.new do |spec|
25
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
26
  spec.require_paths = ['lib']
27
27
 
28
- spec.add_dependency 'activesupport', '< 8'
29
28
  spec.add_dependency 'rexml'
30
29
 
31
30
  spec.add_development_dependency 'bundler', '~> 2'
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saml_idp_metadata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.8
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tknzk
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-03-29 00:00:00.000000000 Z
11
+ date: 2025-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: activesupport
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "<"
18
- - !ruby/object:Gem::Version
19
- version: '8'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "<"
25
- - !ruby/object:Gem::Version
26
- version: '8'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: rexml
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -193,7 +179,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
193
179
  requirements:
194
180
  - - ">="
195
181
  - !ruby/object:Gem::Version
196
- version: '3.1'
182
+ version: '3.2'
197
183
  - - "<"
198
184
  - !ruby/object:Gem::Version
199
185
  version: '4'