spid 0.10.0 → 0.11.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.
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Spid
6
+ module Saml2
7
+ class ServiceProvider # :nodoc:
8
+ attr_reader :host
9
+ attr_reader :acs_path
10
+ attr_reader :acs_binding
11
+ attr_reader :slo_path
12
+ attr_reader :slo_binding
13
+ attr_reader :metadata_path
14
+ attr_reader :private_key
15
+ attr_reader :certificate
16
+ attr_reader :digest_method
17
+ attr_reader :signature_method
18
+ attr_reader :attribute_service_name
19
+
20
+ # rubocop:disable Metrics/ParameterLists
21
+ # rubocop:disable Metrics/MethodLength
22
+ def initialize(
23
+ host:,
24
+ acs_path:,
25
+ acs_binding:,
26
+ slo_path:,
27
+ slo_binding:,
28
+ metadata_path:,
29
+ private_key:,
30
+ certificate:,
31
+ digest_method:,
32
+ signature_method:,
33
+ attribute_service_name:
34
+ )
35
+ @host = host
36
+ @acs_path = acs_path
37
+ @acs_binding = acs_binding
38
+ @slo_path = slo_path
39
+ @slo_binding = slo_binding
40
+ @metadata_path = metadata_path
41
+ @private_key = private_key
42
+ @certificate = certificate
43
+ @digest_method = digest_method
44
+ @signature_method = signature_method
45
+ @attribute_service_name = attribute_service_name
46
+ validate_attributes
47
+ end
48
+ # rubocop:enable Metrics/MethodLength
49
+ # rubocop:enable Metrics/ParameterLists
50
+
51
+ def acs_url
52
+ @acs_url ||= URI.join(host, acs_path).to_s
53
+ end
54
+
55
+ def slo_url
56
+ @slo_url ||= URI.join(host, slo_path).to_s
57
+ end
58
+
59
+ def metadata_url
60
+ @metadata_url ||= URI.join(host, metadata_path).to_s
61
+ end
62
+
63
+ private
64
+
65
+ def validate_attributes
66
+ if !DIGEST_METHODS.include?(digest_method)
67
+ raise UnknownDigestMethodError,
68
+ "Provided digest method is not valid:" \
69
+ " use one of #{DIGEST_METHODS.join(', ')}"
70
+ elsif !SIGNATURE_METHODS.include?(signature_method)
71
+ raise UnknownSignatureMethodError,
72
+ "Provided digest method is not valid:" \
73
+ " use one of #{SIGNATURE_METHODS.join(', ')}"
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module Spid
6
+ module Saml2
7
+ class Settings # :nodoc:
8
+ attr_reader :identity_provider
9
+ attr_reader :service_provider
10
+ attr_reader :authn_context
11
+
12
+ def initialize(identity_provider:, service_provider:, authn_context: nil)
13
+ @authn_context = authn_context || Spid::L1
14
+ unless AUTHN_CONTEXTS.include?(@authn_context)
15
+ raise Spid::UnknownAuthnContextError,
16
+ "Provided authn_context '#{@authn_context}' is not valid:" \
17
+ " use one of #{AUTHN_CONTEXTS.join(', ')}"
18
+ end
19
+
20
+ @identity_provider = identity_provider
21
+ @service_provider = service_provider
22
+ end
23
+
24
+ def idp_entity_id
25
+ identity_provider.entity_id
26
+ end
27
+
28
+ def idp_sso_target_url
29
+ identity_provider.sso_target_url
30
+ end
31
+
32
+ def idp_slo_target_url
33
+ identity_provider.slo_target_url
34
+ end
35
+
36
+ def sp_entity_id
37
+ service_provider.host
38
+ end
39
+
40
+ def sp_acs_url
41
+ service_provider.acs_url
42
+ end
43
+
44
+ def sp_acs_binding
45
+ service_provider.acs_binding
46
+ end
47
+
48
+ def sp_slo_service_url
49
+ service_provider.slo_url
50
+ end
51
+
52
+ def sp_slo_service_binding
53
+ service_provider.slo_binding
54
+ end
55
+
56
+ def private_key
57
+ service_provider.private_key
58
+ end
59
+
60
+ def certificate
61
+ service_provider.certificate
62
+ end
63
+
64
+ def signature_method
65
+ service_provider.signature_method
66
+ end
67
+
68
+ def acs_index
69
+ "0"
70
+ end
71
+
72
+ def force_authn?
73
+ authn_context > Spid::L1
74
+ end
75
+
76
+ def x509_certificate_der
77
+ @x509_certificate_der ||=
78
+ begin
79
+ cert = OpenSSL::X509::Certificate.new(certificate)
80
+ Base64.encode64(cert.to_der).delete("\n")
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spid
4
+ module Saml2
5
+ class SPMetadata # :nodoc:
6
+ attr_reader :document
7
+ attr_reader :settings
8
+ attr_reader :uuid
9
+
10
+ def initialize(settings:, uuid: nil)
11
+ @document = REXML::Document.new
12
+ @settings = settings
13
+ @uuid = uuid || SecureRandom.uuid
14
+ end
15
+
16
+ def to_saml
17
+ document.add_element(entity_descriptor)
18
+ document.to_s
19
+ end
20
+
21
+ def entity_descriptor
22
+ @entity_descriptor ||=
23
+ begin
24
+ element = REXML::Element.new("md:EntityDescriptor")
25
+ element.add_attributes(entity_descriptor_attributes)
26
+ element.add_element sp_sso_descriptor
27
+ element
28
+ end
29
+ end
30
+
31
+ def entity_descriptor_attributes
32
+ @entity_descriptor_attributes ||= {
33
+ "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
34
+ "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata",
35
+ "entityID" => settings.sp_entity_id,
36
+ "ID" => "_#{uuid}"
37
+ }
38
+ end
39
+
40
+ def sp_sso_descriptor
41
+ @sp_sso_descriptor ||=
42
+ begin
43
+ element = REXML::Element.new("md:SPSSODescriptor")
44
+ element.add_attributes(sp_sso_descriptor_attributes)
45
+ element.add_element key_descriptor
46
+ element.add_element ac_service
47
+ element.add_element slo_service
48
+ element
49
+ end
50
+ end
51
+
52
+ def sp_sso_descriptor_attributes
53
+ @sp_sso_descriptor_attributes ||= {
54
+ "protocolSupportEnumeration" =>
55
+ "urn:oasis:names:tc:SAML:2.0:protocol",
56
+ "AuthnRequestsSigned" => true
57
+ }
58
+ end
59
+
60
+ def ac_service
61
+ @ac_service ||=
62
+ begin
63
+ element = REXML::Element.new("md:AssertionConsumerService")
64
+ element.add_attributes(ac_service_attributes)
65
+ element
66
+ end
67
+ end
68
+
69
+ def ac_service_attributes
70
+ @ac_service_attributes ||= {
71
+ "Binding" => settings.sp_acs_binding,
72
+ "Location" => settings.sp_acs_url,
73
+ "index" => 0,
74
+ "isDefault" => true
75
+ }
76
+ end
77
+
78
+ def slo_service
79
+ @slo_service ||=
80
+ begin
81
+ element = REXML::Element.new("md:SingleLogoutService")
82
+ element.add_attributes(
83
+ "Binding" => settings.sp_slo_service_binding,
84
+ "Location" => settings.sp_slo_service_url
85
+ )
86
+ element
87
+ end
88
+ end
89
+
90
+ def key_descriptor
91
+ @key_descriptor ||=
92
+ begin
93
+ kd = REXML::Element.new("md:KeyDescriptor")
94
+ kd.add_attributes("use" => "signing")
95
+ ki = kd.add_element "ds:KeyInfo"
96
+ data = ki.add_element "ds:X509Data"
97
+ certificate = data.add_element "ds:X509Certificate"
98
+ certificate.text = settings.x509_certificate_der
99
+ kd
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "zlib"
5
+ require "cgi"
6
+ require "spid/saml2/utils/query_params_signer"
7
+
8
+ module Spid
9
+ module Saml2
10
+ module Utils # :nodoc:
11
+ def decode(message)
12
+ Base64.decode64(message)
13
+ end
14
+
15
+ def encode(message)
16
+ Base64.encode64(message)
17
+ end
18
+
19
+ def deflate(message)
20
+ Zlib::Deflate.deflate(message, 9)[2..-5]
21
+ end
22
+
23
+ def inflate(message)
24
+ Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(message)
25
+ rescue Zlib::DataError => _e
26
+ message
27
+ end
28
+
29
+ def deflate_and_encode(message)
30
+ encode(deflate(message)).delete("\n")
31
+ end
32
+
33
+ def decode_and_inflate(message)
34
+ inflate(decode(message))
35
+ end
36
+
37
+ def escaped_params(params)
38
+ params.each_with_object({}) do |(key, value), acc|
39
+ acc[key] = CGI.escape(value)
40
+ end
41
+ end
42
+
43
+ def query_param(key, value)
44
+ "#{key}=#{value}"
45
+ end
46
+
47
+ def query_params(params)
48
+ params.map do |key, value|
49
+ query_param(key, value)
50
+ end
51
+ end
52
+
53
+ def query_string(params)
54
+ query_params(params).join("&")
55
+ end
56
+
57
+ def escaped_query_string(params)
58
+ query_string(escaped_params(params))
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module Spid
6
+ module Saml2
7
+ module Utils
8
+ class QueryParamsSigner # :nodoc:
9
+ include Spid::Saml2::Utils
10
+
11
+ attr_reader :saml_message
12
+ attr_reader :private_key
13
+ attr_reader :signature_method
14
+ attr_reader :relay_state
15
+
16
+ def initialize(
17
+ saml_message:,
18
+ private_key:,
19
+ signature_method:,
20
+ relay_state: nil
21
+ )
22
+ @saml_message = saml_message.delete("\n")
23
+ @private_key = OpenSSL::PKey::RSA.new(private_key)
24
+ @signature_method = signature_method
25
+ @relay_state = relay_state
26
+ end
27
+
28
+ def signature_algorithm
29
+ @signature_algorithm ||= Spid::SIGNATURE_ALGORITHMS[signature_method]
30
+ end
31
+
32
+ def signature
33
+ @signature ||=
34
+ begin
35
+ encode(raw_signature)
36
+ end
37
+ end
38
+
39
+ def signed_query_params
40
+ params_for_signature.merge(
41
+ "Signature" => signature
42
+ )
43
+ end
44
+
45
+ def escaped_signed_query_string
46
+ @escaped_signed_query_string ||=
47
+ escaped_query_string(signed_query_params)
48
+ end
49
+
50
+ def raw_signature
51
+ @raw_signature ||=
52
+ begin
53
+ private_key.sign(
54
+ signature_algorithm,
55
+ escaped_query_string(params_for_signature)
56
+ )
57
+ end
58
+ end
59
+
60
+ def params_for_signature
61
+ @params_for_signature ||=
62
+ begin
63
+ params = {
64
+ "SAMLRequest" => deflate_and_encode(saml_message),
65
+ "RelayState" => relay_state,
66
+ "SigAlg" => signature_method
67
+ }
68
+ params.delete("RelayState") if params["RelayState"].nil?
69
+ params
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "spid/slo/request"
4
4
  require "spid/slo/response"
5
- require "spid/slo/settings"
6
5
 
7
6
  module Spid
8
7
  module Slo # :nodoc:
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "spid/logout_request"
4
- require "onelogin/ruby-saml/settings"
5
-
6
3
  module Spid
7
4
  module Slo
8
5
  class Request # :nodoc:
@@ -19,25 +16,43 @@ module Spid
19
16
  end
20
17
  end
21
18
 
22
- def to_saml
23
- logout_request.create(
24
- saml_settings,
25
- "RelayState" => relay_state
26
- )
19
+ def url
20
+ [
21
+ settings.idp_slo_target_url,
22
+ query_params_signer.escaped_signed_query_string
23
+ ].join("?")
24
+ end
25
+
26
+ def query_params_signer
27
+ @query_params_signer ||=
28
+ begin
29
+ Spid::Saml2::Utils::QueryParamsSigner.new(
30
+ saml_message: saml_message,
31
+ relay_state: relay_state,
32
+ private_key: settings.private_key,
33
+ signature_method: settings.signature_method
34
+ )
35
+ end
27
36
  end
28
37
 
29
- def saml_settings
30
- slo_settings.saml_settings
38
+ def saml_message
39
+ @saml_message ||= logout_request.to_saml
31
40
  end
32
41
 
33
- def slo_settings
34
- Settings.new(
35
- service_provider: service_provider,
36
- identity_provider: identity_provider,
42
+ def logout_request
43
+ @logout_request ||= Spid::Saml2::LogoutRequest.new(
44
+ settings: settings,
37
45
  session_index: session_index
38
46
  )
39
47
  end
40
48
 
49
+ def settings
50
+ @settings ||= Spid::Saml2::Settings.new(
51
+ service_provider: service_provider,
52
+ identity_provider: identity_provider
53
+ )
54
+ end
55
+
41
56
  def identity_provider
42
57
  @identity_provider ||=
43
58
  IdentityProviderManager.find_by_name(idp_name)
@@ -47,12 +62,6 @@ module Spid
47
62
  @service_provider ||=
48
63
  Spid.configuration.service_provider
49
64
  end
50
-
51
- private
52
-
53
- def logout_request
54
- LogoutRequest.new
55
- end
56
65
  end
57
66
  end
58
67
  end