spid 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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