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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +6 -1
- data/Gemfile +0 -6
- data/README.md +11 -14
- data/lib/spid.rb +16 -10
- data/lib/spid/configuration.rb +27 -19
- data/lib/spid/identity_provider_manager.rb +14 -4
- data/lib/spid/metadata.rb +10 -77
- data/lib/spid/rack/login.rb +1 -1
- data/lib/spid/rack/logout.rb +1 -1
- data/lib/spid/saml2.rb +17 -0
- data/lib/spid/saml2/authn_request.rb +104 -0
- data/lib/spid/saml2/identity_provider.rb +27 -0
- data/lib/spid/saml2/idp_metadata_parser.rb +283 -0
- data/lib/spid/saml2/logout_request.rb +88 -0
- data/lib/spid/saml2/logout_response.rb +33 -0
- data/lib/spid/saml2/response.rb +58 -0
- data/lib/spid/saml2/service_provider.rb +78 -0
- data/lib/spid/saml2/settings.rb +85 -0
- data/lib/spid/saml2/sp_metadata.rb +104 -0
- data/lib/spid/saml2/utils.rb +62 -0
- data/lib/spid/saml2/utils/query_params_signer.rb +75 -0
- data/lib/spid/slo.rb +0 -1
- data/lib/spid/slo/request.rb +29 -20
- data/lib/spid/slo/response.rb +5 -32
- data/lib/spid/sso.rb +0 -1
- data/lib/spid/sso/request.rb +26 -19
- data/lib/spid/sso/response.rb +9 -30
- data/lib/spid/version.rb +1 -1
- data/spid.gemspec +1 -1
- metadata +28 -28
- data/lib/spid/authn_request.rb +0 -28
- data/lib/spid/identity_provider.rb +0 -60
- data/lib/spid/logout_request.rb +0 -21
- data/lib/spid/service_provider.rb +0 -107
- data/lib/spid/slo/settings.rb +0 -53
- data/lib/spid/sso/settings.rb +0 -62
@@ -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
|
data/lib/spid/slo.rb
CHANGED
data/lib/spid/slo/request.rb
CHANGED
@@ -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
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
30
|
-
|
38
|
+
def saml_message
|
39
|
+
@saml_message ||= logout_request.to_saml
|
31
40
|
end
|
32
41
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
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
|