spid 0.16.1 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -2
- data/lib/spid.rb +2 -0
- data/lib/spid/saml2.rb +1 -0
- data/lib/spid/saml2/idp_logout_request.rb +9 -23
- data/lib/spid/saml2/logout_response.rb +4 -22
- data/lib/spid/saml2/response.rb +48 -41
- data/lib/spid/saml2/response_validator.rb +33 -9
- data/lib/spid/saml2/saml_parser.rb +23 -0
- data/lib/spid/saml2/service_provider.rb +15 -0
- data/lib/spid/saml2/settings.rb +1 -2
- data/lib/spid/saml2/sp_metadata.rb +2 -4
- data/lib/spid/slo/response.rb +5 -1
- data/lib/spid/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c918415822f2873b617da91c797c8987825252578e359c620a334ad9ba439d3
|
4
|
+
data.tar.gz: 2e699735bcefc0e37ed9efced5ed53ad845a29405b4c1616ec4985bcd497a346
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e80c2322252a27a0362c738a3eace0fdba76735026fe0a7dd344bbf25b4543d2a014724301915d87a97fb517399d3bf0695e792141d90b95d1e5df3d82ca40c
|
7
|
+
data.tar.gz: 9fd3aecb75d5a2dad2e6464532c774fd40f2737eecc471f693aabdabf688ca9c5644a6fa3f8c29ea8576076570616d0a402a389f5922f403f8e0c67087330709
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
4
|
|
5
|
+
## [0.17.0] - 2018-09-10
|
6
|
+
### Added
|
7
|
+
- Ensure private key lenght and certificate validation
|
8
|
+
- Previous SPID specification use sp_entity_id for /samlp:Response/@Destination
|
9
|
+
- Validate Subject attributes
|
10
|
+
- Validate Issuer and Assertion/Issuer
|
11
|
+
|
5
12
|
## [0.16.1] - 2018-09-05
|
6
13
|
### Changed
|
7
14
|
- Use login_path and logout_path instead of start_sso_path and start_slo_path
|
@@ -127,8 +134,9 @@
|
|
127
134
|
- Coveralls Integration
|
128
135
|
- Rubygems version badge in README
|
129
136
|
|
130
|
-
[Unreleased]: https://github.com/italia/spid-ruby/compare/v0.
|
131
|
-
[0.
|
137
|
+
[Unreleased]: https://github.com/italia/spid-ruby/compare/v0.17.0...HEAD
|
138
|
+
[0.17.0]: https://github.com/italia/spid-ruby/compare/v0.16.1...v0.17.0
|
139
|
+
[0.16.1]: https://github.com/italia/spid-ruby/compare/v0.16.0...v0.16.1
|
132
140
|
[0.16.0]: https://github.com/italia/spid-ruby/compare/v0.15.2...v0.16.0
|
133
141
|
[0.15.2]: https://github.com/italia/spid-ruby/compare/v0.15.1...v0.15.2
|
134
142
|
[0.15.1]: https://github.com/italia/spid-ruby/compare/v0.15.0...v0.15.1
|
data/lib/spid.rb
CHANGED
@@ -18,6 +18,8 @@ module Spid # :nodoc:
|
|
18
18
|
class UnknownSignatureMethodError < StandardError; end
|
19
19
|
class UnknownAttributeFieldError < StandardError; end
|
20
20
|
class MissingAttributeServicesError < StandardError; end
|
21
|
+
class PrivateKeyTooShortError < StandardError; end
|
22
|
+
class CertificateNotBelongsToPKeyError < StandardError; end
|
21
23
|
|
22
24
|
EXACT_COMPARISON = :exact
|
23
25
|
MINIMUM_COMPARISON = :minimum
|
data/lib/spid/saml2.rb
CHANGED
@@ -4,6 +4,7 @@ require "spid/saml2/service_provider"
|
|
4
4
|
require "spid/saml2/identity_provider"
|
5
5
|
require "spid/saml2/settings"
|
6
6
|
require "spid/saml2/authn_request"
|
7
|
+
require "spid/saml2/saml_parser"
|
7
8
|
require "spid/saml2/response"
|
8
9
|
require "spid/saml2/logout_request"
|
9
10
|
require "spid/saml2/idp_logout_request"
|
@@ -2,51 +2,37 @@
|
|
2
2
|
|
3
3
|
module Spid
|
4
4
|
module Saml2
|
5
|
-
class IdpLogoutRequest # :nodoc:
|
6
|
-
attr_reader :saml_message
|
7
|
-
attr_reader :document
|
8
|
-
|
9
|
-
def initialize(saml_message:)
|
10
|
-
@saml_message = saml_message
|
11
|
-
@document = REXML::Document.new(@saml_message)
|
12
|
-
end
|
13
|
-
|
5
|
+
class IdpLogoutRequest < SamlParser # :nodoc:
|
14
6
|
def id
|
15
|
-
|
7
|
+
element_from_xpath("/samlp:LogoutRequest/@ID")
|
16
8
|
end
|
17
9
|
|
18
10
|
def destination
|
19
|
-
|
11
|
+
element_from_xpath("/samlp:LogoutRequest/@Destination")
|
20
12
|
end
|
21
13
|
|
22
14
|
def issue_instant
|
23
|
-
|
15
|
+
element_from_xpath("/samlp:LogoutRequest/@IssueInstant")
|
24
16
|
end
|
25
17
|
|
26
18
|
def issuer_name_qualifier
|
27
|
-
|
28
|
-
"/samlp:LogoutRequest/saml:Issuer/@NameQualifier"
|
29
|
-
]&.value
|
19
|
+
element_from_xpath("/samlp:LogoutRequest/saml:Issuer/@NameQualifier")
|
30
20
|
end
|
31
21
|
|
32
22
|
def name_id
|
33
|
-
|
23
|
+
element_from_xpath("/samlp:LogoutRequest/saml:NameID/text()")
|
34
24
|
end
|
35
25
|
|
36
26
|
def name_id_name_qualifier
|
37
|
-
|
38
|
-
"/samlp:LogoutRequest/saml:NameID/@NameQualifier"
|
39
|
-
]&.value
|
27
|
+
element_from_xpath("/samlp:LogoutRequest/saml:NameID/@NameQualifier")
|
40
28
|
end
|
41
29
|
|
42
30
|
def issuer
|
43
|
-
|
31
|
+
element_from_xpath("/samlp:LogoutRequest/saml:Issuer/text()")
|
44
32
|
end
|
45
33
|
|
46
34
|
def session_index
|
47
|
-
|
48
|
-
"/samlp:LogoutRequest/saml:SessionIndex/text()"
|
49
|
-
]&.value
|
35
|
+
element_from_xpath("/samlp:LogoutRequest/saml:SessionIndex/text()")
|
50
36
|
end
|
51
37
|
end
|
52
38
|
end
|
@@ -4,35 +4,17 @@ require "spid/saml2/utils"
|
|
4
4
|
|
5
5
|
module Spid
|
6
6
|
module Saml2
|
7
|
-
class LogoutResponse # :nodoc:
|
8
|
-
include Spid::Saml2::Utils
|
9
|
-
|
10
|
-
attr_reader :body
|
11
|
-
attr_reader :saml_message
|
12
|
-
attr_reader :document
|
13
|
-
|
14
|
-
def initialize(body:)
|
15
|
-
@body = body
|
16
|
-
@saml_message = decode_and_inflate(body)
|
17
|
-
@document = REXML::Document.new(@saml_message)
|
18
|
-
end
|
19
|
-
|
7
|
+
class LogoutResponse < SamlParser # :nodoc:
|
20
8
|
def issuer
|
21
|
-
|
22
|
-
"/samlp:LogoutResponse/saml:Issuer/text()"
|
23
|
-
]&.value&.strip
|
9
|
+
element_from_xpath("/samlp:LogoutResponse/saml:Issuer/text()")
|
24
10
|
end
|
25
11
|
|
26
12
|
def in_response_to
|
27
|
-
|
28
|
-
"/samlp:LogoutResponse/@InResponseTo"
|
29
|
-
]&.value&.strip
|
13
|
+
element_from_xpath("/samlp:LogoutResponse/@InResponseTo")
|
30
14
|
end
|
31
15
|
|
32
16
|
def destination
|
33
|
-
|
34
|
-
"/samlp:LogoutResponse/@Destination"
|
35
|
-
]&.value
|
17
|
+
element_from_xpath("/samlp:LogoutResponse/@Destination")
|
36
18
|
end
|
37
19
|
end
|
38
20
|
end
|
data/lib/spid/saml2/response.rb
CHANGED
@@ -1,20 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "spid/saml2/utils"
|
4
|
-
|
5
3
|
module Spid
|
6
4
|
module Saml2
|
7
|
-
class Response # :nodoc:
|
8
|
-
include Spid::Saml2::Utils
|
9
|
-
|
10
|
-
attr_reader :saml_message
|
11
|
-
attr_reader :document
|
12
|
-
|
13
|
-
def initialize(saml_message:)
|
14
|
-
@saml_message = saml_message
|
15
|
-
@document = REXML::Document.new(@saml_message)
|
16
|
-
end
|
17
|
-
|
5
|
+
class Response < SamlParser # :nodoc:
|
18
6
|
def issuer
|
19
7
|
document.elements["/samlp:Response/saml:Issuer/text()"]&.value
|
20
8
|
end
|
@@ -24,15 +12,16 @@ module Spid
|
|
24
12
|
end
|
25
13
|
|
26
14
|
def name_id
|
27
|
-
|
15
|
+
element_from_xpath(
|
28
16
|
"/samlp:Response/saml:Assertion/saml:Subject/saml:NameID/text()"
|
29
|
-
|
17
|
+
)
|
30
18
|
end
|
31
19
|
|
32
20
|
def raw_certificate
|
33
|
-
|
34
|
-
|
35
|
-
|
21
|
+
element_from_xpath(
|
22
|
+
"/samlp:Response/saml:Assertion/ds:Signature/ds:KeyInfo" \
|
23
|
+
"/ds:X509Data/ds:X509Certificate/text()"
|
24
|
+
)
|
36
25
|
end
|
37
26
|
|
38
27
|
def certificate
|
@@ -40,59 +29,77 @@ module Spid
|
|
40
29
|
end
|
41
30
|
|
42
31
|
def assertion_issuer
|
43
|
-
|
44
|
-
"/samlp:Response/saml:Assertion/saml:Issuer/text()"
|
45
|
-
]&.value
|
32
|
+
element_from_xpath("/samlp:Response/saml:Assertion/saml:Issuer/text()")
|
46
33
|
end
|
47
34
|
|
48
35
|
def session_index
|
49
|
-
|
36
|
+
element_from_xpath(
|
50
37
|
"/samlp:Response/saml:Assertion/saml:AuthnStatement/@SessionIndex"
|
51
|
-
|
38
|
+
)
|
52
39
|
end
|
53
40
|
|
54
41
|
def destination
|
55
|
-
|
56
|
-
"/samlp:Response/@Destination"
|
57
|
-
]&.value
|
42
|
+
element_from_xpath("/samlp:Response/@Destination")
|
58
43
|
end
|
59
44
|
|
60
45
|
def conditions_not_before
|
61
|
-
|
46
|
+
element_from_xpath(
|
62
47
|
"/samlp:Response/saml:Assertion/saml:Conditions/@NotBefore"
|
63
|
-
|
48
|
+
)
|
64
49
|
end
|
65
50
|
|
66
51
|
def conditions_not_on_or_after
|
67
|
-
|
52
|
+
element_from_xpath(
|
68
53
|
"/samlp:Response/saml:Assertion/saml:Conditions/@NotOnOrAfter"
|
69
|
-
|
54
|
+
)
|
70
55
|
end
|
71
56
|
|
72
57
|
def audience
|
73
|
-
|
74
|
-
|
75
|
-
|
58
|
+
element_from_xpath(
|
59
|
+
"/samlp:Response/saml:Assertion/saml:Conditions" \
|
60
|
+
"/saml:AudienceRestriction/saml:Audience/text()"
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def subject_confirmation_data_node_xpath
|
65
|
+
xpath = "/samlp:Response/saml:Assertion/saml:Subject/"
|
66
|
+
"#{xpath}/saml:SubjectConfirmation/saml:SubjectConfirmationData"
|
67
|
+
end
|
68
|
+
|
69
|
+
def subject_recipient
|
70
|
+
element_from_xpath("#{subject_confirmation_data_node_xpath}/@Recipient")
|
71
|
+
end
|
72
|
+
|
73
|
+
def subject_in_response_to
|
74
|
+
element_from_xpath(
|
75
|
+
"#{subject_confirmation_data_node_xpath}/@InResponseTo"
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def subject_not_on_or_after
|
80
|
+
element_from_xpath(
|
81
|
+
"#{subject_confirmation_data_node_xpath}/@NotOnOrAfter"
|
82
|
+
)
|
76
83
|
end
|
77
84
|
|
78
85
|
def status_code
|
79
|
-
|
86
|
+
element_from_xpath(
|
80
87
|
"/samlp:Response/samlp:Status/samlp:StatusCode/@Value"
|
81
|
-
|
88
|
+
)
|
82
89
|
end
|
83
90
|
|
84
91
|
def status_message
|
85
|
-
|
86
|
-
"
|
87
|
-
"samlp:
|
88
|
-
|
92
|
+
element_from_xpath(
|
93
|
+
"samlp:StatusMessage/@Value" \
|
94
|
+
"/samlp:Response/samlp:Status/samlp:StatusCode/"
|
95
|
+
)
|
89
96
|
end
|
90
97
|
|
91
98
|
def status_detail
|
92
|
-
|
99
|
+
element_from_xpath(
|
93
100
|
"/samlp:Response/samlp:Status/samlp:StatusCode/" \
|
94
101
|
"samlp:StatusDetail/@Value"
|
95
|
-
|
102
|
+
)
|
96
103
|
end
|
97
104
|
|
98
105
|
def attributes
|
@@ -4,6 +4,7 @@ require "xmldsig"
|
|
4
4
|
|
5
5
|
module Spid
|
6
6
|
module Saml2
|
7
|
+
# rubocop:disable Metrics/ClassLength
|
7
8
|
class ResponseValidator # :nodoc:
|
8
9
|
attr_reader :response
|
9
10
|
attr_reader :settings
|
@@ -20,13 +21,8 @@ module Spid
|
|
20
21
|
def call
|
21
22
|
return false unless success?
|
22
23
|
[
|
23
|
-
matches_request_uuid,
|
24
|
-
|
25
|
-
certificate,
|
26
|
-
destination,
|
27
|
-
conditions,
|
28
|
-
audience,
|
29
|
-
signature
|
24
|
+
matches_request_uuid, issuer, assertion_issuer, certificate,
|
25
|
+
destination, conditions, audience, signature
|
30
26
|
].all?
|
31
27
|
end
|
32
28
|
|
@@ -50,11 +46,22 @@ module Spid
|
|
50
46
|
end
|
51
47
|
|
52
48
|
def issuer
|
53
|
-
return true if response.
|
49
|
+
return true if response.issuer == settings.idp_entity_id
|
54
50
|
|
55
51
|
@errors["issuer"] =
|
56
52
|
begin
|
57
|
-
"Response Issuer is '#{response.
|
53
|
+
"Response Issuer is '#{response.issuer}'" \
|
54
|
+
" but was expected '#{settings.idp_entity_id}'"
|
55
|
+
end
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def assertion_issuer
|
60
|
+
return true if response.assertion_issuer == settings.idp_entity_id
|
61
|
+
|
62
|
+
@errors["assertion_issuer"] =
|
63
|
+
begin
|
64
|
+
"Response Assertion Issuer is '#{response.assertion_issuer}'" \
|
58
65
|
" but was expected '#{settings.idp_entity_id}'"
|
59
66
|
end
|
60
67
|
false
|
@@ -71,6 +78,7 @@ module Spid
|
|
71
78
|
|
72
79
|
def destination
|
73
80
|
return true if response.destination == settings.sp_acs_url
|
81
|
+
return true if response.destination == settings.sp_entity_id
|
74
82
|
|
75
83
|
@errors["destination"] =
|
76
84
|
begin
|
@@ -85,6 +93,7 @@ module Spid
|
|
85
93
|
|
86
94
|
if response.conditions_not_before <= time &&
|
87
95
|
response.conditions_not_on_or_after > time
|
96
|
+
|
88
97
|
return true
|
89
98
|
end
|
90
99
|
|
@@ -109,6 +118,21 @@ module Spid
|
|
109
118
|
@errors["signature"] = "Signature mismatch"
|
110
119
|
false
|
111
120
|
end
|
121
|
+
|
122
|
+
def subject_recipient
|
123
|
+
return true if response.subject_recipient == settings.sp_acs_url
|
124
|
+
end
|
125
|
+
|
126
|
+
def subject_in_response_to
|
127
|
+
return true if response.subject_in_response_to == request_uuid
|
128
|
+
end
|
129
|
+
|
130
|
+
def subject_not_on_or_after
|
131
|
+
time = Time.now.utc.iso8601
|
132
|
+
|
133
|
+
return true if response.subject_not_on_or_after > time
|
134
|
+
end
|
112
135
|
end
|
136
|
+
# rubocop:enable Metrics/ClassLength
|
113
137
|
end
|
114
138
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spid/saml2/utils"
|
4
|
+
|
5
|
+
module Spid
|
6
|
+
module Saml2
|
7
|
+
class SamlParser # :nodoc:
|
8
|
+
include Spid::Saml2::Utils
|
9
|
+
|
10
|
+
attr_reader :saml_message
|
11
|
+
attr_reader :document
|
12
|
+
|
13
|
+
def initialize(saml_message:)
|
14
|
+
@saml_message = saml_message
|
15
|
+
@document = REXML::Document.new(@saml_message)
|
16
|
+
end
|
17
|
+
|
18
|
+
def element_from_xpath(xpath)
|
19
|
+
document.elements[xpath]&.value&.strip
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -45,6 +45,8 @@ module Spid
|
|
45
45
|
@attribute_services = attribute_services
|
46
46
|
validate_digest_methods
|
47
47
|
validate_attributes
|
48
|
+
validate_private_key
|
49
|
+
validate_certificate
|
48
50
|
end
|
49
51
|
# rubocop:enable Metrics/MethodLength
|
50
52
|
# rubocop:enable Metrics/ParameterLists
|
@@ -92,6 +94,19 @@ module Spid
|
|
92
94
|
" use one of #{SIGNATURE_METHODS.join(', ')}"
|
93
95
|
end
|
94
96
|
end
|
97
|
+
|
98
|
+
def validate_private_key
|
99
|
+
return true if private_key.n.num_bits >= 1024
|
100
|
+
raise PrivateKeyTooShortError,
|
101
|
+
"Private key is too short: provide at least a " \
|
102
|
+
" private key with 1024 bits"
|
103
|
+
end
|
104
|
+
|
105
|
+
def validate_certificate
|
106
|
+
return true if certificate.verify(private_key)
|
107
|
+
raise CertificateNotBelongsToPKeyError,
|
108
|
+
"Provided a certificate signed with current private key"
|
109
|
+
end
|
95
110
|
end
|
96
111
|
end
|
97
112
|
end
|
data/lib/spid/saml2/settings.rb
CHANGED
@@ -6,12 +6,10 @@ module Spid
|
|
6
6
|
class SPMetadata # :nodoc:
|
7
7
|
attr_reader :document
|
8
8
|
attr_reader :settings
|
9
|
-
attr_reader :uuid
|
10
9
|
|
11
|
-
def initialize(settings
|
10
|
+
def initialize(settings:)
|
12
11
|
@document = REXML::Document.new
|
13
12
|
@settings = settings
|
14
|
-
@uuid = uuid || SecureRandom.uuid
|
15
13
|
end
|
16
14
|
|
17
15
|
def to_saml
|
@@ -34,7 +32,7 @@ module Spid
|
|
34
32
|
"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
|
35
33
|
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata",
|
36
34
|
"entityID" => settings.sp_entity_id,
|
37
|
-
"ID" =>
|
35
|
+
"ID" => settings.sp_entity_id
|
38
36
|
}
|
39
37
|
end
|
40
38
|
|
data/lib/spid/slo/response.rb
CHANGED
@@ -3,14 +3,18 @@
|
|
3
3
|
module Spid
|
4
4
|
module Slo
|
5
5
|
class Response # :nodoc:
|
6
|
+
include Spid::Saml2::Utils
|
7
|
+
|
6
8
|
attr_reader :body
|
7
9
|
attr_reader :session_index
|
8
10
|
attr_reader :request_uuid
|
11
|
+
attr_reader :saml_message
|
9
12
|
|
10
13
|
def initialize(body:, session_index:, request_uuid:)
|
11
14
|
@body = body
|
12
15
|
@session_index = session_index
|
13
16
|
@request_uuid = request_uuid
|
17
|
+
@saml_message = decode_and_inflate(body)
|
14
18
|
end
|
15
19
|
|
16
20
|
def valid?
|
@@ -59,7 +63,7 @@ module Spid
|
|
59
63
|
|
60
64
|
def saml_response
|
61
65
|
@saml_response ||= Spid::Saml2::LogoutResponse.new(
|
62
|
-
|
66
|
+
saml_message: saml_message
|
63
67
|
)
|
64
68
|
end
|
65
69
|
end
|
data/lib/spid/version.rb
CHANGED
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.
|
4
|
+
version: 0.17.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-09-
|
11
|
+
date: 2018-09-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -343,6 +343,7 @@ files:
|
|
343
343
|
- lib/spid/saml2/logout_response_validator.rb
|
344
344
|
- lib/spid/saml2/response.rb
|
345
345
|
- lib/spid/saml2/response_validator.rb
|
346
|
+
- lib/spid/saml2/saml_parser.rb
|
346
347
|
- lib/spid/saml2/service_provider.rb
|
347
348
|
- lib/spid/saml2/settings.rb
|
348
349
|
- lib/spid/saml2/sp_metadata.rb
|