spid 0.16.1 → 0.17.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/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
|