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