ruby-saml 0.4.7 → 0.5.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.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- data/README.rdoc +25 -2
- data/Rakefile +1 -22
- data/lib/onelogin/ruby-saml/authrequest.rb +74 -0
- data/lib/onelogin/ruby-saml/logging.rb +22 -0
- data/lib/onelogin/ruby-saml/metadata.rb +47 -0
- data/lib/onelogin/ruby-saml/response.rb +146 -0
- data/lib/onelogin/ruby-saml/settings.rb +9 -0
- data/lib/onelogin/{saml → ruby-saml}/validation_error.rb +0 -0
- data/lib/onelogin/ruby-saml/version.rb +5 -0
- data/lib/ruby-saml.rb +7 -5
- data/lib/xml_security.rb +7 -3
- data/ruby-saml.gemspec +7 -43
- data/test/request_test.rb +20 -0
- data/test/response_test.rb +29 -1
- data/test/responses/response_with_ampersands.xml +139 -0
- data/test/responses/response_with_ampersands.xml.base64 +93 -0
- data/test/responses/wrapped_response_2.xml.base64 +150 -0
- data/test/test_helper.rb +8 -0
- metadata +28 -26
- data/lib/onelogin/saml.rb +0 -5
- data/lib/onelogin/saml/authrequest.rb +0 -33
- data/lib/onelogin/saml/response.rb +0 -137
- data/lib/onelogin/saml/settings.rb +0 -6
data/test/test_helper.rb
CHANGED
@@ -38,6 +38,10 @@ class Test::Unit::TestCase
|
|
38
38
|
@response_document5 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response5.xml.base64'))
|
39
39
|
end
|
40
40
|
|
41
|
+
def ampersands_response
|
42
|
+
@ampersands_resposne ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_with_ampersands.xml.base64'))
|
43
|
+
end
|
44
|
+
|
41
45
|
def response_document_6
|
42
46
|
doc = Base64.decode64(response_document)
|
43
47
|
doc.gsub!(/NotBefore=\"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z\"/, "NotBefore=\"#{(Time.now-300).getutc.strftime("%Y-%m-%dT%XZ")}\"")
|
@@ -45,6 +49,10 @@ class Test::Unit::TestCase
|
|
45
49
|
Base64.encode64(doc)
|
46
50
|
end
|
47
51
|
|
52
|
+
def wrapped_response_2
|
53
|
+
@wrapped_response_2 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'wrapped_response_2.xml.base64'))
|
54
|
+
end
|
55
|
+
|
48
56
|
def signature_fingerprint_1
|
49
57
|
@signature_fingerprint1 ||= "C5:19:85:D9:47:F1:BE:57:08:20:25:05:08:46:EB:27:F6:CA:B7:83"
|
50
58
|
end
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-saml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 1
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
7
|
+
- 5
|
8
|
+
- 0
|
9
|
+
version: 0.5.0
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- OneLogin LLC
|
@@ -15,18 +14,16 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date: 2011-06-30 00:00:00
|
17
|
+
date: 2011-06-30 00:00:00 -07:00
|
19
18
|
default_executable:
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
22
21
|
name: canonix
|
23
22
|
prerelease: false
|
24
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
24
|
requirements:
|
27
25
|
- - ~>
|
28
26
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 9
|
30
27
|
segments:
|
31
28
|
- 0
|
32
29
|
- 1
|
@@ -37,11 +34,9 @@ dependencies:
|
|
37
34
|
name: uuid
|
38
35
|
prerelease: false
|
39
36
|
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
-
none: false
|
41
37
|
requirements:
|
42
38
|
- - ~>
|
43
39
|
- !ruby/object:Gem::Version
|
44
|
-
hash: 5
|
45
40
|
segments:
|
46
41
|
- 2
|
47
42
|
- 3
|
@@ -52,11 +47,9 @@ dependencies:
|
|
52
47
|
name: shoulda
|
53
48
|
prerelease: false
|
54
49
|
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
-
none: false
|
56
50
|
requirements:
|
57
51
|
- - ">="
|
58
52
|
- !ruby/object:Gem::Version
|
59
|
-
hash: 3
|
60
53
|
segments:
|
61
54
|
- 0
|
62
55
|
version: "0"
|
@@ -66,11 +59,9 @@ dependencies:
|
|
66
59
|
name: ruby-debug
|
67
60
|
prerelease: false
|
68
61
|
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
-
none: false
|
70
62
|
requirements:
|
71
63
|
- - ">="
|
72
64
|
- !ruby/object:Gem::Version
|
73
|
-
hash: 3
|
74
65
|
segments:
|
75
66
|
- 0
|
76
67
|
version: "0"
|
@@ -80,11 +71,9 @@ dependencies:
|
|
80
71
|
name: mocha
|
81
72
|
prerelease: false
|
82
73
|
requirement: &id005 !ruby/object:Gem::Requirement
|
83
|
-
none: false
|
84
74
|
requirements:
|
85
75
|
- - ">="
|
86
76
|
- !ruby/object:Gem::Version
|
87
|
-
hash: 3
|
88
77
|
segments:
|
89
78
|
- 0
|
90
79
|
version: "0"
|
@@ -106,11 +95,13 @@ files:
|
|
106
95
|
- README.rdoc
|
107
96
|
- Rakefile
|
108
97
|
- VERSION
|
109
|
-
- lib/onelogin/saml.rb
|
110
|
-
- lib/onelogin/saml/
|
111
|
-
- lib/onelogin/saml/
|
112
|
-
- lib/onelogin/saml/
|
113
|
-
- lib/onelogin/saml/
|
98
|
+
- lib/onelogin/ruby-saml/authrequest.rb
|
99
|
+
- lib/onelogin/ruby-saml/logging.rb
|
100
|
+
- lib/onelogin/ruby-saml/metadata.rb
|
101
|
+
- lib/onelogin/ruby-saml/response.rb
|
102
|
+
- lib/onelogin/ruby-saml/settings.rb
|
103
|
+
- lib/onelogin/ruby-saml/validation_error.rb
|
104
|
+
- lib/onelogin/ruby-saml/version.rb
|
114
105
|
- lib/ruby-saml.rb
|
115
106
|
- lib/xml_security.rb
|
116
107
|
- ruby-saml.gemspec
|
@@ -124,7 +115,10 @@ files:
|
|
124
115
|
- test/responses/response3.xml.base64
|
125
116
|
- test/responses/response4.xml.base64
|
126
117
|
- test/responses/response5.xml.base64
|
118
|
+
- test/responses/response_with_ampersands.xml
|
119
|
+
- test/responses/response_with_ampersands.xml.base64
|
127
120
|
- test/responses/simple_saml_php.xml
|
121
|
+
- test/responses/wrapped_response_2.xml.base64
|
128
122
|
- test/settings_test.rb
|
129
123
|
- test/test_helper.rb
|
130
124
|
- test/xml_security_test.rb
|
@@ -138,33 +132,41 @@ rdoc_options:
|
|
138
132
|
require_paths:
|
139
133
|
- lib
|
140
134
|
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
-
none: false
|
142
135
|
requirements:
|
143
136
|
- - ">="
|
144
137
|
- !ruby/object:Gem::Version
|
145
|
-
hash: 3
|
146
138
|
segments:
|
147
139
|
- 0
|
148
140
|
version: "0"
|
149
141
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
-
none: false
|
151
142
|
requirements:
|
152
143
|
- - ">="
|
153
144
|
- !ruby/object:Gem::Version
|
154
|
-
hash: 3
|
155
145
|
segments:
|
156
146
|
- 0
|
157
147
|
version: "0"
|
158
148
|
requirements: []
|
159
149
|
|
160
|
-
rubyforge_project:
|
161
|
-
rubygems_version: 1.3.
|
150
|
+
rubyforge_project: http://www.rubygems.org/gems/ruby-saml
|
151
|
+
rubygems_version: 1.3.6
|
162
152
|
signing_key:
|
163
153
|
specification_version: 3
|
164
154
|
summary: SAML Ruby Tookit
|
165
155
|
test_files:
|
156
|
+
- test/certificates/certificate1
|
166
157
|
- test/request_test.rb
|
167
158
|
- test/response_test.rb
|
159
|
+
- test/responses/adfs_response.xml.base64
|
160
|
+
- test/responses/open_saml_response.xml
|
161
|
+
- test/responses/response1.xml.base64
|
162
|
+
- test/responses/response2.xml.base64
|
163
|
+
- test/responses/response3.xml.base64
|
164
|
+
- test/responses/response4.xml.base64
|
165
|
+
- test/responses/response5.xml.base64
|
166
|
+
- test/responses/response_with_ampersands.xml
|
167
|
+
- test/responses/response_with_ampersands.xml.base64
|
168
|
+
- test/responses/simple_saml_php.xml
|
169
|
+
- test/responses/wrapped_response_2.xml.base64
|
168
170
|
- test/settings_test.rb
|
169
171
|
- test/test_helper.rb
|
170
172
|
- test/xml_security_test.rb
|
data/lib/onelogin/saml.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
require "base64"
|
2
|
-
require "uuid"
|
3
|
-
require "zlib"
|
4
|
-
require "cgi"
|
5
|
-
|
6
|
-
module Onelogin::Saml
|
7
|
-
class Authrequest
|
8
|
-
def create(settings, params = {})
|
9
|
-
uuid = "_" + UUID.new.generate
|
10
|
-
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
11
|
-
|
12
|
-
request =
|
13
|
-
"<samlp:AuthnRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\"#{uuid}\" Version=\"2.0\" IssueInstant=\"#{time}\" ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" AssertionConsumerServiceURL=\"#{settings.assertion_consumer_service_url}\">" +
|
14
|
-
"<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{settings.issuer}</saml:Issuer>\n" +
|
15
|
-
"<samlp:NameIDPolicy xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Format=\"#{settings.name_identifier_format}\" AllowCreate=\"true\"></samlp:NameIDPolicy>\n" +
|
16
|
-
"<samlp:RequestedAuthnContext xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" Comparison=\"exact\">" +
|
17
|
-
"<saml:AuthnContextClassRef xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></samlp:RequestedAuthnContext>\n" +
|
18
|
-
"</samlp:AuthnRequest>"
|
19
|
-
|
20
|
-
deflated_request = Zlib::Deflate.deflate(request, 9)[2..-5]
|
21
|
-
base64_request = Base64.encode64(deflated_request)
|
22
|
-
encoded_request = CGI.escape(base64_request)
|
23
|
-
request_params = "?SAMLRequest=" + encoded_request
|
24
|
-
|
25
|
-
params.each_pair do |key, value|
|
26
|
-
request_params << "&#{key}=#{CGI.escape(value.to_s)}"
|
27
|
-
end
|
28
|
-
|
29
|
-
settings.idp_sso_target_url + request_params
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
33
|
-
end
|
@@ -1,137 +0,0 @@
|
|
1
|
-
require "xml_security"
|
2
|
-
require "time"
|
3
|
-
|
4
|
-
module Onelogin::Saml
|
5
|
-
|
6
|
-
class Response
|
7
|
-
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
8
|
-
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
9
|
-
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
10
|
-
|
11
|
-
attr_accessor :options, :response, :document, :settings
|
12
|
-
|
13
|
-
def initialize(response, options = {})
|
14
|
-
raise ArgumentError.new("Response cannot be nil") if response.nil?
|
15
|
-
self.options = options
|
16
|
-
self.response = response
|
17
|
-
self.document = XMLSecurity::SignedDocument.new(Base64.decode64(response))
|
18
|
-
end
|
19
|
-
|
20
|
-
def is_valid?
|
21
|
-
validate(soft = true)
|
22
|
-
end
|
23
|
-
|
24
|
-
def validate!
|
25
|
-
validate(soft = false)
|
26
|
-
end
|
27
|
-
|
28
|
-
# The value of the user identifier as designated by the initialization request response
|
29
|
-
def name_id
|
30
|
-
@name_id ||= begin
|
31
|
-
node = REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
32
|
-
node ||= REXML::XPath.first(document, "/p:Response[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Assertion/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
33
|
-
node.nil? ? nil : node.text
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# A hash of alle the attributes with the response. Assuming there is only one value for each key
|
38
|
-
def attributes
|
39
|
-
@attr_statements ||= begin
|
40
|
-
result = {}
|
41
|
-
|
42
|
-
stmt_element = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AttributeStatement", { "p" => PROTOCOL, "a" => ASSERTION })
|
43
|
-
return {} if stmt_element.nil?
|
44
|
-
|
45
|
-
stmt_element.elements.each do |attr_element|
|
46
|
-
name = attr_element.attributes["Name"]
|
47
|
-
value = attr_element.elements.first.text
|
48
|
-
|
49
|
-
result[name] = value
|
50
|
-
end
|
51
|
-
|
52
|
-
result.keys.each do |key|
|
53
|
-
result[key.intern] = result[key]
|
54
|
-
end
|
55
|
-
|
56
|
-
result
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
# When this user session should expire at latest
|
61
|
-
def session_expires_at
|
62
|
-
@expires_at ||= begin
|
63
|
-
node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AuthnStatement", { "p" => PROTOCOL, "a" => ASSERTION })
|
64
|
-
parse_time(node, "SessionNotOnOrAfter")
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
# Conditions (if any) for the assertion to run
|
69
|
-
def conditions
|
70
|
-
@conditions ||= begin
|
71
|
-
REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Conditions", { "p" => PROTOCOL, "a" => ASSERTION })
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def validation_error(message)
|
78
|
-
raise ValidationError.new(message)
|
79
|
-
end
|
80
|
-
|
81
|
-
def validate(soft = true)
|
82
|
-
validate_response_state(soft) &&
|
83
|
-
validate_conditions(soft) &&
|
84
|
-
document.validate(get_fingerprint, soft)
|
85
|
-
end
|
86
|
-
|
87
|
-
def validate_response_state(soft = true)
|
88
|
-
if response.empty?
|
89
|
-
return soft ? false : validation_error("Blank response")
|
90
|
-
end
|
91
|
-
|
92
|
-
if settings.nil?
|
93
|
-
return soft ? false : validation_error("No settings on response")
|
94
|
-
end
|
95
|
-
|
96
|
-
if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
|
97
|
-
return soft ? false : validation_error("No fingerprint or certificate on settings")
|
98
|
-
end
|
99
|
-
|
100
|
-
true
|
101
|
-
end
|
102
|
-
|
103
|
-
def get_fingerprint
|
104
|
-
if settings.idp_cert
|
105
|
-
cert = OpenSSL::X509::Certificate.new(settings.idp_cert)
|
106
|
-
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
|
107
|
-
else
|
108
|
-
settings.idp_cert_fingerprint
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def validate_conditions(soft = true)
|
113
|
-
return true if conditions.nil?
|
114
|
-
return true if options[:skip_conditions]
|
115
|
-
|
116
|
-
if not_before = parse_time(conditions, "NotBefore")
|
117
|
-
if Time.now.utc < not_before
|
118
|
-
return soft ? false : validation_error("Current time is earlier than NotBefore condition")
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
if not_on_or_after = parse_time(conditions, "NotOnOrAfter")
|
123
|
-
if Time.now.utc >= not_on_or_after
|
124
|
-
return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
true
|
129
|
-
end
|
130
|
-
|
131
|
-
def parse_time(node, attribute)
|
132
|
-
if node && node.attributes[attribute]
|
133
|
-
Time.parse(node.attributes[attribute])
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|