spid 0.3.1 → 0.4.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: 9f94ff48217764f22a3055a10c96d6f95a551ad8c6bd3feb70c7cd13642dc438
4
- data.tar.gz: 2e189a0bee7b42c4518a955c856ce641dc5ba0108f9725a907a8ed8505363b40
3
+ metadata.gz: 11409a6db93f8bc29e6a8b0d6678e9c893441ad99fc67302667ad2493a0b0dc8
4
+ data.tar.gz: 9e6e5c2c079bf2bc232e92fc7585b7f461614dc521de1eff92427a782d60e8ea
5
5
  SHA512:
6
- metadata.gz: 893a1e5c15958af449b3d7d2d900bf472c79d44f7b19d4083b0ab80c4b15fea7a46c865b27a2cd7bf7d0bc89d283ce192f9847732418005e687addbfaae6587b
7
- data.tar.gz: dc68d4cd16a9b2d088b5e5da333a19b805d3de613102ded4c6d8543748cad7a33692e3373c549d7449d96a68c8ee953456159b5f04bb73ce6fe2a8530c2a33de
6
+ metadata.gz: 015c7a6b37a8640123c3bb15df3aadfdc9ea21a76acc9ee5886191cdec2b3c95a013b1e8b5039efd0250351180c2f97c6e928161539f435353e985a555895ec3
7
+ data.tar.gz: 52445d55b3bf64d28ee9caf601b7a1e3146df9721f87beafed2ecc63a6f46f41c45d7ae09deeef9d2bb0c00f857a972d4890f35fd0af889080d7055a957b5742
data/.gitignore CHANGED
@@ -25,3 +25,4 @@ build-iPhoneOS/
25
25
  build-iPhoneSimulator/
26
26
  build/
27
27
  /Gemfile.lock
28
+ /idp_metadata/*.xml
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.4.0] - 2018-07-13
6
+ ### Added
7
+ - ServiceProviderConfiguration class handles configuration for a specific host
8
+ - SsoResponse class
9
+
5
10
  ## [0.3.1] - 2018-07-09
6
11
  ### Added
7
12
  - Signature in authn_request
@@ -39,7 +44,8 @@
39
44
  - Coveralls Integration
40
45
  - Rubygems version badge in README
41
46
 
42
- [Unreleased]: https://github.com/italia/spid-ruby/compare/v0.3.1...HEAD
47
+ [Unreleased]: https://github.com/italia/spid-ruby/compare/v0.4.0...HEAD
48
+ [0.4.0]: https://github.com/italia/spid-ruby/compare/v0.3.1...v0.4.0
43
49
  [0.3.1]: https://github.com/italia/spid-ruby/compare/v0.3.0...v0.3.1
44
50
  [0.3.0]: https://github.com/italia/spid-ruby/compare/v0.2.2...v0.3.0
45
51
  [0.2.2]: https://github.com/italia/spid-ruby/compare/v0.2.1...v0.2.2
File without changes
data/lib/spid.rb CHANGED
@@ -1,15 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "spid/authn_request"
4
- require "spid/generate_authn_request"
4
+ require "spid/sso_request"
5
+ require "spid/sso_response"
5
6
  require "spid/identity_providers"
6
7
  require "spid/metadata"
7
8
  require "spid/idp_metadata"
8
9
  require "spid/version"
10
+ require "spid/identity_provider_configuration"
11
+ require "spid/service_provider_configuration"
9
12
 
10
13
  module Spid # :nodoc:
11
14
  class UnknownAuthnComparisonMethodError < StandardError; end
12
15
  class UnknownAuthnContextError < StandardError; end
16
+ class UnknownDigestMethodError < StandardError; end
17
+ class UnknownSignatureMethodError < StandardError; end
13
18
 
14
19
  EXACT_COMPARISON = :exact
15
20
  MININUM_COMPARISON = :minumum
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spid
4
+ class IdentityProviderConfiguration # :nodoc:
5
+ attr_reader :idp_metadata, :idp_metadata_hash
6
+
7
+ def initialize(idp_metadata:)
8
+ @idp_metadata = idp_metadata
9
+ @idp_metadata_hash = idp_metadata_parser.parse_to_hash(idp_metadata)
10
+ end
11
+
12
+ def entity_id
13
+ @entity_id ||= idp_metadata_hash[:idp_entity_id]
14
+ end
15
+
16
+ def sso_target_url
17
+ @sso_target_url ||= idp_metadata_hash[:idp_sso_target_url]
18
+ end
19
+
20
+ def cert_fingerprint
21
+ @cert_fingerprint ||= idp_metadata_hash[:idp_cert_fingerprint]
22
+ end
23
+
24
+ private
25
+
26
+ def idp_metadata_parser
27
+ @idp_metadata_parser ||= ::OneLogin::RubySaml::IdpMetadataParser.new
28
+ end
29
+ end
30
+ end
@@ -5,15 +5,18 @@ require "faraday_middleware"
5
5
 
6
6
  module Spid
7
7
  class IdentityProviders # :nodoc:
8
+ class MetadataFetchError < StandardError; end
9
+
8
10
  def self.fetch_all
9
11
  new.fetch_all
10
12
  end
11
13
 
12
14
  def fetch_all
13
15
  spid_idp_entities.map do |idp|
16
+ metadata_url = check_for_final_metadata_url(idp["metadata_url"])
14
17
  {
15
18
  name: idp["entity_name"].gsub(/ ID$/, "").downcase,
16
- metadata_url: idp["metadata_url"],
19
+ metadata_url: metadata_url,
17
20
  entity_id: idp["entity_id"]
18
21
  }
19
22
  end
@@ -21,8 +24,18 @@ module Spid
21
24
 
22
25
  private
23
26
 
27
+ def check_for_final_metadata_url(metadata_url)
28
+ response = Faraday.get(metadata_url)
29
+ case response.status
30
+ when 200
31
+ metadata_url
32
+ when 301, 302
33
+ response["Location"]
34
+ end
35
+ end
36
+
24
37
  def spid_idp_entities
25
- return [] if response.body["spidFederationRegistry"].blank?
38
+ return [] if response.body["spidFederationRegistry"].nil?
26
39
  response.body["spidFederationRegistry"]["entities"]
27
40
  end
28
41
 
@@ -13,7 +13,7 @@ module Spid
13
13
  end
14
14
 
15
15
  def [](idp_name)
16
- return @metadata[idp_name] if @metadata[idp_name].present?
16
+ return @metadata[idp_name] unless @metadata[idp_name].nil?
17
17
  idp_hash = identity_provider_hash(idp_name)
18
18
 
19
19
  @metadata[idp_name] = parser.parse_remote_to_hash(
data/lib/spid/metadata.rb CHANGED
@@ -6,25 +6,22 @@ require "onelogin/ruby-saml/settings"
6
6
  module Spid
7
7
  class Metadata # :nodoc:
8
8
  attr_reader :metadata_attributes,
9
+ :service_provider_configuration,
9
10
  :attribute_service_name
10
11
 
11
12
  # rubocop:disable Metrics/MethodLength
12
- # rubocop:disable Metrics/ParameterLists
13
13
  def initialize(
14
- issuer:,
15
- private_key_filepath:,
16
- certificate_filepath:,
17
- assertion_consumer_service_url:,
18
- single_logout_service_url:,
14
+ service_provider_configuration:,
19
15
  attribute_service_name:,
20
16
  digest_method: Spid::SHA256,
21
17
  signature_method: Spid::RSA_SHA256
22
18
  )
19
+ @service_provider_configuration = service_provider_configuration
23
20
  @attribute_service_name = attribute_service_name
24
21
  @metadata_attributes = {
25
22
  issuer: issuer,
26
- private_key: File.read(private_key_filepath),
27
- certificate: File.read(certificate_filepath),
23
+ private_key: private_key_content,
24
+ certificate: certificate_content,
28
25
  assertion_consumer_service_url: assertion_consumer_service_url,
29
26
  single_logout_service_url: single_logout_service_url,
30
27
  security: {
@@ -41,13 +38,32 @@ module Spid
41
38
  }
42
39
  }
43
40
  end
44
- # rubocop:enable Metrics/ParameterLists
45
41
  # rubocop:enable Metrics/MethodLength
46
42
 
47
43
  def to_xml
48
44
  metadata.generate(saml_settings)
49
45
  end
50
46
 
47
+ def issuer
48
+ service_provider_configuration.host
49
+ end
50
+
51
+ def private_key_content
52
+ service_provider_configuration.private_key
53
+ end
54
+
55
+ def certificate_content
56
+ service_provider_configuration.certificate
57
+ end
58
+
59
+ def assertion_consumer_service_url
60
+ service_provider_configuration.sso_url
61
+ end
62
+
63
+ def single_logout_service_url
64
+ service_provider_configuration.slo_url
65
+ end
66
+
51
67
  private
52
68
 
53
69
  def metadata
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Spid
6
+ class ServiceProviderConfiguration # :nodoc:
7
+ attr_reader :host,
8
+ :sso_path,
9
+ :slo_path,
10
+ :metadata_path,
11
+ :private_key_file_path,
12
+ :certificate_file_path,
13
+ :digest_method,
14
+ :signature_method
15
+
16
+ # rubocop:disable Metrics/ParameterLists
17
+ def initialize(
18
+ host:,
19
+ sso_path:,
20
+ slo_path:,
21
+ metadata_path:,
22
+ private_key_file_path:,
23
+ certificate_file_path:,
24
+ digest_method:,
25
+ signature_method:
26
+ )
27
+ @host = host
28
+ @sso_path = sso_path
29
+ @slo_path = slo_path
30
+ @metadata_path = metadata_path
31
+ @private_key_file_path = private_key_file_path
32
+ @certificate_file_path = certificate_file_path
33
+ @digest_method = digest_method
34
+ @signature_method = signature_method
35
+ validate_attributes
36
+ end
37
+ # rubocop:enable Metrics/ParameterLists
38
+
39
+ def sso_url
40
+ @sso_url ||= URI.join(host, sso_path).to_s
41
+ end
42
+
43
+ def slo_url
44
+ @slo_url ||= URI.join(host, slo_path).to_s
45
+ end
46
+
47
+ def metadata_url
48
+ @metadata_url ||= URI.join(host, metadata_path).to_s
49
+ end
50
+
51
+ def private_key
52
+ @private_key ||= File.read(private_key_file_path)
53
+ end
54
+
55
+ def certificate
56
+ @certificate ||= File.read(certificate_file_path)
57
+ end
58
+
59
+ private
60
+
61
+ def validate_attributes
62
+ if !DIGEST_METHODS.include?(digest_method)
63
+ raise UnknownDigestMethodError,
64
+ "Provided digest method is not valid:" \
65
+ " use one of #{DIGEST_METHODS.join(', ')}"
66
+ elsif !SIGNATURE_METHODS.include?(signature_method)
67
+ raise UnknownSignatureMethodError,
68
+ "Provided digest method is not valid:" \
69
+ " use one of #{SIGNATURE_METHODS.join(', ')}"
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spid/authn_request"
4
+ require "onelogin/ruby-saml/settings"
5
+
6
+ module Spid
7
+ class SsoRequest # :nodoc:
8
+ attr_reader :service_provider_configuration,
9
+ :identity_provider_configuration,
10
+ :authn_context,
11
+ :authn_context_comparison
12
+
13
+ # rubocop:disable Metrics/MethodLength
14
+ def initialize(
15
+ identity_provider_configuration:,
16
+ service_provider_configuration:,
17
+ authn_context: Spid::L1,
18
+ authn_context_comparison: Spid::EXACT_COMPARISON
19
+ )
20
+
21
+ unless AUTHN_CONTEXTS.include?(authn_context)
22
+ raise Spid::UnknownAuthnContextError,
23
+ "Provided authn_context is not valid:" \
24
+ " use one of #{AUTHN_CONTEXTS.join(', ')}"
25
+ end
26
+
27
+ unless COMPARISON_METHODS.include?(authn_context_comparison)
28
+ raise Spid::UnknownAuthnComparisonMethodError,
29
+ "Provided authn_context_comparison_method is not valid:" \
30
+ " use one of #{COMPARISON_METHODS.join(', ')}"
31
+ end
32
+
33
+ @service_provider_configuration = service_provider_configuration
34
+ @identity_provider_configuration = identity_provider_configuration
35
+ @authn_context = authn_context
36
+ @authn_context_comparison = authn_context_comparison
37
+ end
38
+ # rubocop:enable Metrics/MethodLength
39
+
40
+ def to_saml
41
+ authn_request.create(saml_settings)
42
+ end
43
+
44
+ # rubocop:disable Metrics/MethodLength
45
+ # rubocop:disable Metrics/AbcSize
46
+ def authn_request_attributes
47
+ return @authn_request_attributes if @authn_request_attributes.present?
48
+ @authn_request_attributes = {
49
+ idp_sso_target_url: identity_provider_configuration.sso_target_url,
50
+ assertion_consumer_service_url: service_provider_configuration.sso_url,
51
+ protocol_binding: protocol_binding,
52
+ issuer: service_provider_configuration.host,
53
+ private_key: service_provider_configuration.private_key,
54
+ certificate: service_provider_configuration.certificate,
55
+ name_identifier_format: name_identifier_format,
56
+ authn_context: authn_context,
57
+ authn_context_comparison: authn_context_comparison,
58
+ idp_cert_fingerprint: identity_provider_configuration.cert_fingerprint,
59
+ security: {
60
+ authn_requests_signed: true,
61
+ embed_sign: true,
62
+ digest_method: service_provider_configuration.digest_method,
63
+ signature_method: service_provider_configuration.signature_method
64
+ }
65
+ }
66
+ @authn_request_attributes[:force_authn] = true if authn_context > Spid::L1
67
+ @authn_request_attributes
68
+ end
69
+ # rubocop:enable Metrics/AbcSize
70
+ # rubocop:enable Metrics/MethodLength
71
+
72
+ def authn_request
73
+ AuthnRequest.new
74
+ end
75
+
76
+ def saml_settings
77
+ ::OneLogin::RubySaml::Settings.new authn_request_attributes
78
+ end
79
+
80
+ private
81
+
82
+ def protocol_binding
83
+ "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
84
+ end
85
+
86
+ def name_identifier_format
87
+ "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "onelogin/ruby-saml/response"
4
+ require "active_support/inflector/methods"
5
+
6
+ module Spid
7
+ class SsoResponse # :nodoc:
8
+ attr_reader :body, :sso_settings
9
+
10
+ def initialize(body:, sso_settings:)
11
+ @body = body
12
+ @sso_settings = sso_settings
13
+ end
14
+
15
+ def valid?
16
+ saml_response.is_valid?
17
+ end
18
+
19
+ def attributes
20
+ raw_attributes.each_with_object({}) do |(key, value), acc|
21
+ acc[normalize_key(key)] = value
22
+ end
23
+ end
24
+
25
+ def raw_attributes
26
+ saml_response.attributes.attributes
27
+ end
28
+
29
+ private
30
+
31
+ def normalize_key(key)
32
+ ActiveSupport::Inflector.underscore(
33
+ key.to_s
34
+ ).to_sym
35
+ end
36
+
37
+ def saml_response
38
+ @saml_response ||= ::OneLogin::RubySaml::Response.new(
39
+ body,
40
+ settings: sso_settings
41
+ )
42
+ end
43
+ end
44
+ end
data/lib/spid/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spid
4
- VERSION = "0.3.1"
4
+ VERSION = "0.4.0"
5
5
  end
data/spid.gemspec CHANGED
@@ -38,6 +38,7 @@ Gem::Specification.new do |spec|
38
38
  spec.add_development_dependency "rspec", "~> 3.0"
39
39
  spec.add_development_dependency "rubocop", "0.57.2"
40
40
  spec.add_development_dependency "rubocop-rspec", "1.27.0"
41
+ spec.add_development_dependency "timecop", "~> 0"
41
42
  spec.add_development_dependency "vcr", "~> 4.0", ">= 4.0.0"
42
43
  spec.add_development_dependency "webmock", "~> 3.4", ">= 3.4.2"
43
44
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Librera
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-09 00:00:00.000000000 Z
11
+ date: 2018-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-saml
@@ -204,6 +204,20 @@ dependencies:
204
204
  - - '='
205
205
  - !ruby/object:Gem::Version
206
206
  version: 1.27.0
207
+ - !ruby/object:Gem::Dependency
208
+ name: timecop
209
+ requirement: !ruby/object:Gem::Requirement
210
+ requirements:
211
+ - - "~>"
212
+ - !ruby/object:Gem::Version
213
+ version: '0'
214
+ type: :development
215
+ prerelease: false
216
+ version_requirements: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - "~>"
219
+ - !ruby/object:Gem::Version
220
+ version: '0'
207
221
  - !ruby/object:Gem::Dependency
208
222
  name: vcr
209
223
  requirement: !ruby/object:Gem::Requirement
@@ -261,12 +275,16 @@ files:
261
275
  - LICENSE
262
276
  - README.md
263
277
  - Rakefile
278
+ - idp_metadata/.gitkeep
264
279
  - lib/spid.rb
265
280
  - lib/spid/authn_request.rb
266
- - lib/spid/generate_authn_request.rb
281
+ - lib/spid/identity_provider_configuration.rb
267
282
  - lib/spid/identity_providers.rb
268
283
  - lib/spid/idp_metadata.rb
269
284
  - lib/spid/metadata.rb
285
+ - lib/spid/service_provider_configuration.rb
286
+ - lib/spid/sso_request.rb
287
+ - lib/spid/sso_response.rb
270
288
  - lib/spid/version.rb
271
289
  - spid.gemspec
272
290
  homepage: https://github.com/italia/spid-ruby
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spid/authn_request"
4
- require "onelogin/ruby-saml/settings"
5
-
6
- module Spid
7
- class GenerateAuthnRequest # :nodoc:
8
- attr_reader :authn_request_attributes
9
-
10
- # rubocop:disable Metrics/MethodLength
11
- # rubocop:disable Metrics/ParameterLists
12
- def initialize(
13
- idp_sso_target_url:,
14
- assertion_consumer_service_url:,
15
- private_key_filepath:,
16
- certificate_filepath:,
17
- issuer:,
18
- authn_context: Spid::L1,
19
- authn_context_comparison: Spid::EXACT_COMPARISON,
20
- digest_method: Spid::SHA256,
21
- signature_method: Spid::RSA_SHA256
22
- )
23
-
24
- unless AUTHN_CONTEXTS.include?(authn_context)
25
- raise Spid::UnknownAuthnContextError,
26
- "Provided authn_context is not valid:" \
27
- " use one of #{AUTHN_CONTEXTS.join(', ')}"
28
- end
29
-
30
- unless COMPARISON_METHODS.include?(authn_context_comparison)
31
- raise Spid::UnknownAuthnComparisonMethodError,
32
- "Provided authn_context_comparison_method is not valid:" \
33
- " use one of #{COMPARISON_METHODS.join(', ')}"
34
- end
35
-
36
- @authn_request_attributes = {
37
- idp_sso_target_url: idp_sso_target_url,
38
- assertion_consumer_service_url: assertion_consumer_service_url,
39
- protocol_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
40
- issuer: issuer,
41
- private_key: File.read(private_key_filepath),
42
- certificate: File.read(certificate_filepath),
43
- name_identifier_format: name_identifier_format,
44
- authn_context: authn_context,
45
- authn_context_comparison: authn_context_comparison,
46
- security: {
47
- authn_requests_signed: true,
48
- embed_sign: true,
49
- digest_method: digest_method,
50
- signature_method: signature_method
51
- }
52
- }
53
-
54
- return if authn_context <= Spid::L1
55
- @authn_request_attributes[:force_authn] = true
56
- end
57
- # rubocop:enable Metrics/ParameterLists
58
- # rubocop:enable Metrics/MethodLength
59
-
60
- def to_saml
61
- authn_request.create(saml_settings)
62
- end
63
-
64
- private
65
-
66
- def name_identifier_format
67
- "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
68
- end
69
-
70
- def authn_request
71
- AuthnRequest.new
72
- end
73
-
74
- def saml_settings
75
- ::OneLogin::RubySaml::Settings.new authn_request_attributes
76
- end
77
- end
78
- end