maestrano 0.1.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.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +43 -0
  5. data/LICENSE +21 -0
  6. data/README.md +4 -0
  7. data/Rakefile +32 -0
  8. data/bin/maestrano-console +9 -0
  9. data/lib/maestrano.rb +114 -0
  10. data/lib/maestrano/account/bill.rb +14 -0
  11. data/lib/maestrano/api/error/authentication_error.rb +8 -0
  12. data/lib/maestrano/api/error/base_error.rb +24 -0
  13. data/lib/maestrano/api/error/connection_error.rb +8 -0
  14. data/lib/maestrano/api/error/invalid_request_error.rb +14 -0
  15. data/lib/maestrano/api/list_object.rb +37 -0
  16. data/lib/maestrano/api/object.rb +187 -0
  17. data/lib/maestrano/api/operation/base.rb +216 -0
  18. data/lib/maestrano/api/operation/create.rb +18 -0
  19. data/lib/maestrano/api/operation/delete.rb +13 -0
  20. data/lib/maestrano/api/operation/list.rb +18 -0
  21. data/lib/maestrano/api/operation/update.rb +59 -0
  22. data/lib/maestrano/api/resource.rb +39 -0
  23. data/lib/maestrano/api/util.rb +121 -0
  24. data/lib/maestrano/saml/attribute_value.rb +15 -0
  25. data/lib/maestrano/saml/metadata.rb +64 -0
  26. data/lib/maestrano/saml/request.rb +93 -0
  27. data/lib/maestrano/saml/response.rb +201 -0
  28. data/lib/maestrano/saml/schemas/saml20assertion_schema.xsd +283 -0
  29. data/lib/maestrano/saml/schemas/saml20protocol_schema.xsd +302 -0
  30. data/lib/maestrano/saml/schemas/xenc_schema.xsd +146 -0
  31. data/lib/maestrano/saml/schemas/xmldsig_schema.xsd +318 -0
  32. data/lib/maestrano/saml/settings.rb +37 -0
  33. data/lib/maestrano/saml/validation_error.rb +7 -0
  34. data/lib/maestrano/sso.rb +81 -0
  35. data/lib/maestrano/sso/base_group.rb +31 -0
  36. data/lib/maestrano/sso/base_user.rb +75 -0
  37. data/lib/maestrano/sso/group.rb +24 -0
  38. data/lib/maestrano/sso/session.rb +63 -0
  39. data/lib/maestrano/sso/user.rb +34 -0
  40. data/lib/maestrano/version.rb +3 -0
  41. data/lib/maestrano/xml_security/signed_document.rb +170 -0
  42. data/maestrano.gemspec +32 -0
  43. data/test/helpers/api_helpers.rb +82 -0
  44. data/test/helpers/saml_helpers.rb +62 -0
  45. data/test/maestrano/account/bill_test.rb +48 -0
  46. data/test/maestrano/api/list_object_test.rb +20 -0
  47. data/test/maestrano/api/object_test.rb +28 -0
  48. data/test/maestrano/api/resource_test.rb +343 -0
  49. data/test/maestrano/api/util_test.rb +31 -0
  50. data/test/maestrano/maestrano_test.rb +49 -0
  51. data/test/maestrano/saml/request_test.rb +168 -0
  52. data/test/maestrano/saml/response_test.rb +290 -0
  53. data/test/maestrano/saml/settings_test.rb +51 -0
  54. data/test/maestrano/sso/base_group_test.rb +54 -0
  55. data/test/maestrano/sso/base_user_test.rb +114 -0
  56. data/test/maestrano/sso/group_test.rb +47 -0
  57. data/test/maestrano/sso/session_test.rb +108 -0
  58. data/test/maestrano/sso/user_test.rb +65 -0
  59. data/test/maestrano/sso_test.rb +81 -0
  60. data/test/maestrano/xml_security/signed_document.rb +163 -0
  61. data/test/support/saml/certificates/certificate1 +12 -0
  62. data/test/support/saml/certificates/r1_certificate2_base64 +1 -0
  63. data/test/support/saml/responses/adfs_response_sha1.xml +46 -0
  64. data/test/support/saml/responses/adfs_response_sha256.xml +46 -0
  65. data/test/support/saml/responses/adfs_response_sha384.xml +46 -0
  66. data/test/support/saml/responses/adfs_response_sha512.xml +46 -0
  67. data/test/support/saml/responses/no_signature_ns.xml +48 -0
  68. data/test/support/saml/responses/open_saml_response.xml +56 -0
  69. data/test/support/saml/responses/r1_response6.xml.base64 +1 -0
  70. data/test/support/saml/responses/response1.xml.base64 +1 -0
  71. data/test/support/saml/responses/response2.xml.base64 +79 -0
  72. data/test/support/saml/responses/response3.xml.base64 +66 -0
  73. data/test/support/saml/responses/response4.xml.base64 +93 -0
  74. data/test/support/saml/responses/response5.xml.base64 +102 -0
  75. data/test/support/saml/responses/response_with_ampersands.xml +139 -0
  76. data/test/support/saml/responses/response_with_ampersands.xml.base64 +93 -0
  77. data/test/support/saml/responses/response_with_multiple_attribute_values.xml +57 -0
  78. data/test/support/saml/responses/simple_saml_php.xml +71 -0
  79. data/test/support/saml/responses/starfield_response.xml.base64 +1 -0
  80. data/test/support/saml/responses/wrapped_response_2.xml.base64 +150 -0
  81. data/test/test_helper.rb +46 -0
  82. metadata +305 -0
@@ -0,0 +1,31 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+
3
+ module Maestrano
4
+ module API
5
+ class UtilTest < Test::Unit::TestCase
6
+ should "symbolize_names should convert names to symbols" do
7
+ start = {
8
+ 'foo' => 'bar',
9
+ 'array' => [{ 'foo' => 'bar' }],
10
+ 'nested' => {
11
+ 1 => 2,
12
+ :symbol => 9,
13
+ 'string' => nil
14
+ }
15
+ }
16
+ finish = {
17
+ :foo => 'bar',
18
+ :array => [{ :foo => 'bar' }],
19
+ :nested => {
20
+ 1 => 2,
21
+ :symbol => 9,
22
+ :string => nil
23
+ }
24
+ }
25
+
26
+ symbolized = Maestrano::API::Util.symbolize_names(start)
27
+ assert_equal(finish, symbolized)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,49 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class MaestranoTest < Test::Unit::TestCase
4
+ setup do
5
+ @config = {
6
+ environment: 'production',
7
+ api_key: 'someapikey',
8
+ sso_enabled: false,
9
+ app_host: 'http://mysuperapp.com',
10
+ sso_app_init_path: '/mno/sso/init',
11
+ sso_app_consume_path: '/mno/sso/consume',
12
+ user_creation_mode: 'real',
13
+ }
14
+
15
+ Maestrano.configure do |config|
16
+ config.environment = @config[:environment]
17
+ config.api_key = @config[:api_key]
18
+ config.sso_enabled = @config[:sso_enabled]
19
+ config.app_host = @config[:app_host]
20
+ config.sso_app_init_path = @config[:sso_app_init_path]
21
+ config.sso_app_consume_path = @config[:sso_app_consume_path]
22
+ config.user_creation_mode = @config[:user_creation_mode]
23
+ end
24
+ end
25
+
26
+ should "return the specified parameters" do
27
+ @config.keys.each do |key|
28
+ assert Maestrano.param(key) == @config[key]
29
+ end
30
+ end
31
+
32
+ context "configuration" do
33
+ should "return the right test parameters" do
34
+ Maestrano.configure { |config| config.environment = 'test' }
35
+
36
+ ['api_host','api_base','sso_name_id_format', 'sso_x509_certificate'].each do |parameter|
37
+ assert Maestrano.param(parameter) == Maestrano::Configuration::EVT_CONFIG[:test][parameter.to_sym]
38
+ end
39
+ end
40
+
41
+ should "return the right production parameters" do
42
+ Maestrano.configure { |config| config.environment = 'production' }
43
+
44
+ ['api_host','api_base','sso_name_id_format', 'sso_x509_certificate'].each do |parameter|
45
+ assert Maestrano.param(parameter) == Maestrano::Configuration::EVT_CONFIG[:production][parameter.to_sym]
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,168 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+
3
+ module Maestrano
4
+ module Saml
5
+ class RequestTest < Test::Unit::TestCase
6
+
7
+ context "Request" do
8
+ should "create the deflated SAMLRequest URL parameter" do
9
+ settings = Maestrano::Saml::Settings.new
10
+ settings.idp_sso_target_url = "http://example.com"
11
+ request = Maestrano::Saml::Request.new
12
+ request.settings = settings
13
+ auth_url = request.redirect_url
14
+ assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
15
+
16
+ payload = CGI.unescape(auth_url.split("=").last)
17
+ decoded = Base64.decode64(payload)
18
+
19
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
20
+ inflated = zstream.inflate(decoded)
21
+ zstream.finish
22
+ zstream.close
23
+ assert_match /^<samlp:AuthnRequest/, inflated
24
+ end
25
+
26
+ should "create the deflated SAMLRequest URL parameter including the Destination" do
27
+ settings = Maestrano::Saml::Settings.new
28
+ settings.idp_sso_target_url = "http://example.com"
29
+ request = Maestrano::Saml::Request.new
30
+ request.settings = settings
31
+ auth_url = request.redirect_url
32
+ payload = CGI.unescape(auth_url.split("=").last)
33
+ decoded = Base64.decode64(payload)
34
+
35
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
36
+ inflated = zstream.inflate(decoded)
37
+ zstream.finish
38
+ zstream.close
39
+
40
+ assert_match /<samlp:AuthnRequest[^<]* Destination='http:\/\/example.com'/, inflated
41
+ end
42
+
43
+ should "create the SAMLRequest URL parameter without deflating" do
44
+ settings = Maestrano::Saml::Settings.new
45
+ settings.compress_request = false
46
+ settings.idp_sso_target_url = "http://example.com"
47
+ request = Maestrano::Saml::Request.new
48
+ request.settings = settings
49
+ auth_url = request.redirect_url
50
+ assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
51
+
52
+ payload = CGI.unescape(auth_url.split("=").last)
53
+ decoded = Base64.decode64(payload)
54
+ assert_match /^<samlp:AuthnRequest/, decoded
55
+ end
56
+
57
+ should "create the SAMLRequest URL parameter with IsPassive" do
58
+ settings = Maestrano::Saml::Settings.new
59
+ settings.idp_sso_target_url = "http://example.com"
60
+ settings.passive = true
61
+ request = Maestrano::Saml::Request.new
62
+ request.settings = settings
63
+ auth_url = request.redirect_url
64
+ assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
65
+ payload = CGI.unescape(auth_url.split("=").last)
66
+ decoded = Base64.decode64(payload)
67
+
68
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
69
+ inflated = zstream.inflate(decoded)
70
+ zstream.finish
71
+ zstream.close
72
+
73
+ assert_match /<samlp:AuthnRequest[^<]* IsPassive='true'/, inflated
74
+ end
75
+
76
+ should "create the SAMLRequest URL parameter with ProtocolBinding" do
77
+ settings = Maestrano::Saml::Settings.new
78
+ settings.idp_sso_target_url = "http://example.com"
79
+ settings.protocol_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
80
+ request = Maestrano::Saml::Request.new
81
+ request.settings = settings
82
+ auth_url = request.redirect_url
83
+ assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/
84
+ payload = CGI.unescape(auth_url.split("=").last)
85
+ decoded = Base64.decode64(payload)
86
+
87
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
88
+ inflated = zstream.inflate(decoded)
89
+ zstream.finish
90
+ zstream.close
91
+
92
+ assert_match /<samlp:AuthnRequest[^<]* ProtocolBinding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'/, inflated
93
+ end
94
+
95
+ should "accept extra parameters" do
96
+ settings = Maestrano::Saml::Settings.new
97
+ settings.idp_sso_target_url = "http://example.com"
98
+
99
+ request = Maestrano::Saml::Request.new
100
+ request.settings = settings
101
+ request.params = { :hello => "there" }
102
+ auth_url = request.redirect_url
103
+ assert auth_url =~ /&hello=there$/
104
+
105
+ request = Maestrano::Saml::Request.new
106
+ request.settings = settings
107
+ request.params = { :hello => nil }
108
+ auth_url = request.redirect_url
109
+ assert auth_url =~ /&hello=$/
110
+ end
111
+
112
+ context "when the target url doesn't contain a query string" do
113
+ should "create the SAMLRequest parameter correctly" do
114
+ settings = Maestrano::Saml::Settings.new
115
+ settings.idp_sso_target_url = "http://example.com"
116
+
117
+ request = Maestrano::Saml::Request.new
118
+ request.settings = settings
119
+ auth_url = request.redirect_url
120
+ assert auth_url =~ /^http:\/\/example.com\?SAMLRequest/
121
+ end
122
+ end
123
+
124
+ context "when the target url contains a query string" do
125
+ should "create the SAMLRequest parameter correctly" do
126
+ settings = Maestrano::Saml::Settings.new
127
+ settings.idp_sso_target_url = "http://example.com?field=value"
128
+
129
+ request = Maestrano::Saml::Request.new
130
+ request.settings = settings
131
+ auth_url = request.redirect_url
132
+ assert auth_url =~ /^http:\/\/example.com\?field=value&SAMLRequest/
133
+ end
134
+ end
135
+
136
+ context "with session" do
137
+ should "pass the group_id from session to the url" do
138
+ settings = Maestrano::Saml::Settings.new
139
+ settings.idp_sso_target_url = "http://example.com"
140
+ session = {mno_group_uid: 'cld-1'}
141
+
142
+ request = Maestrano::Saml::Request.new
143
+ request.settings = settings
144
+ request.session = session
145
+ auth_url = request.redirect_url
146
+ assert auth_url =~ /&group_id=cld-1/
147
+ end
148
+
149
+ should "pass ignore the group_id from session if already present in the params" do
150
+ settings = Maestrano::Saml::Settings.new
151
+ settings.idp_sso_target_url = "http://example.com"
152
+ session = {mno_group_uid: 'cld-1'}
153
+
154
+ request = Maestrano::Saml::Request.new
155
+ request.settings = settings
156
+ request.params = {group_id: 'cld-2'}
157
+ request.session = session
158
+ auth_url = request.redirect_url
159
+
160
+ assert auth_url =~ /&group_id=cld-2/
161
+ assert auth_url !~ /&group_id=cld-1/
162
+ end
163
+ end
164
+
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,290 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+
3
+ module Maestrano
4
+ module Saml
5
+ class SamlTest < Test::Unit::TestCase
6
+ include SamlTestHelper
7
+
8
+ context "Response" do
9
+ should "raise an exception when response is initialized with nil" do
10
+ assert_raises(ArgumentError) { Maestrano::Saml::Response.new(nil) }
11
+ end
12
+
13
+ should "be able to parse a document which contains ampersands" do
14
+ Maestrano::XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true)
15
+ Maestrano::Saml::Response.any_instance.stubs(:validate_conditions).returns(true)
16
+
17
+ response = Maestrano::Saml::Response.new(ampersands_response)
18
+ settings = Maestrano::Saml::Settings.new
19
+ settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783'
20
+ response.settings = settings
21
+ response.validate!
22
+ end
23
+
24
+ should "adapt namespace" do
25
+ response = Maestrano::Saml::Response.new(response_document)
26
+ assert !response.name_id.nil?
27
+ response = Maestrano::Saml::Response.new(response_document_2)
28
+ assert !response.name_id.nil?
29
+ response = Maestrano::Saml::Response.new(response_document_3)
30
+ assert !response.name_id.nil?
31
+ end
32
+
33
+ should "default to raw input when a response is not Base64 encoded" do
34
+ decoded = Base64.decode64(response_document_2)
35
+ response = Maestrano::Saml::Response.new(decoded)
36
+ assert response.document
37
+ end
38
+
39
+ context "Assertion" do
40
+ should "only retreive an assertion with an ID that matches the signature's reference URI" do
41
+ response = Maestrano::Saml::Response.new(wrapped_response_2)
42
+ response.stubs(:conditions).returns(nil)
43
+ settings = Maestrano::Saml::Settings.new
44
+ settings.idp_cert_fingerprint = signature_fingerprint_1
45
+ response.settings = settings
46
+ assert response.name_id.nil?
47
+ end
48
+ end
49
+
50
+ context "#validate!" do
51
+ should "raise when encountering a condition that prevents the document from being valid" do
52
+ response = Maestrano::Saml::Response.new(response_document)
53
+ assert_raise(Maestrano::Saml::ValidationError) do
54
+ response.validate!
55
+ end
56
+ end
57
+ end
58
+
59
+ context "#is_valid?" do
60
+ should "return false when response is initialized with blank data" do
61
+ response = Maestrano::Saml::Response.new('')
62
+ assert !response.is_valid?
63
+ end
64
+
65
+ should "return false if settings have not been set" do
66
+ response = Maestrano::Saml::Response.new(response_document)
67
+ assert !response.is_valid?
68
+ end
69
+
70
+ should "return true when the response is initialized with valid data" do
71
+ response = Maestrano::Saml::Response.new(response_document_4)
72
+ response.stubs(:conditions).returns(nil)
73
+ assert !response.is_valid?
74
+ settings = Maestrano::Saml::Settings.new
75
+ assert !response.is_valid?
76
+ response.settings = settings
77
+ assert !response.is_valid?
78
+ settings.idp_cert_fingerprint = signature_fingerprint_1
79
+ assert response.is_valid?
80
+ end
81
+
82
+ should "should be idempotent when the response is initialized with invalid data" do
83
+ response = Maestrano::Saml::Response.new(response_document_4)
84
+ response.stubs(:conditions).returns(nil)
85
+ settings = Maestrano::Saml::Settings.new
86
+ response.settings = settings
87
+ assert !response.is_valid?
88
+ assert !response.is_valid?
89
+ end
90
+
91
+ should "should be idempotent when the response is initialized with valid data" do
92
+ response = Maestrano::Saml::Response.new(response_document_4)
93
+ response.stubs(:conditions).returns(nil)
94
+ settings = Maestrano::Saml::Settings.new
95
+ response.settings = settings
96
+ settings.idp_cert_fingerprint = signature_fingerprint_1
97
+ assert response.is_valid?
98
+ assert response.is_valid?
99
+ end
100
+
101
+ should "return true when using certificate instead of fingerprint" do
102
+ response = Maestrano::Saml::Response.new(response_document_4)
103
+ response.stubs(:conditions).returns(nil)
104
+ settings = Maestrano::Saml::Settings.new
105
+ response.settings = settings
106
+ settings.idp_cert = signature_1
107
+ assert response.is_valid?
108
+ end
109
+
110
+ should "not allow signature wrapping attack" do
111
+ response = Maestrano::Saml::Response.new(response_document_4)
112
+ response.stubs(:conditions).returns(nil)
113
+ settings = Maestrano::Saml::Settings.new
114
+ settings.idp_cert_fingerprint = signature_fingerprint_1
115
+ response.settings = settings
116
+ assert response.is_valid?
117
+ assert response.name_id == "test@onelogin.com"
118
+ end
119
+
120
+ should "support dynamic namespace resolution on signature elements" do
121
+ response = Maestrano::Saml::Response.new(fixture("no_signature_ns.xml"))
122
+ response.stubs(:conditions).returns(nil)
123
+ settings = Maestrano::Saml::Settings.new
124
+ response.settings = settings
125
+ settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
126
+ Maestrano::XMLSecurity::SignedDocument.any_instance.expects(:validate_signature).returns(true)
127
+ assert response.validate!
128
+ end
129
+
130
+ should "validate ADFS assertions" do
131
+ response = Maestrano::Saml::Response.new(fixture(:adfs_response_sha256))
132
+ response.stubs(:conditions).returns(nil)
133
+ settings = Maestrano::Saml::Settings.new
134
+ settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA"
135
+ response.settings = settings
136
+ assert response.validate!
137
+ end
138
+
139
+ should "validate the digest" do
140
+ response = Maestrano::Saml::Response.new(r1_response_document_6)
141
+ response.stubs(:conditions).returns(nil)
142
+ settings = Maestrano::Saml::Settings.new
143
+ settings.idp_cert = Base64.decode64(r1_signature_2)
144
+ response.settings = settings
145
+ assert response.validate!
146
+ end
147
+
148
+ should "validate SAML 2.0 XML structure" do
149
+ resp_xml = Base64.decode64(response_document_4).gsub(/emailAddress/,'test')
150
+ response = Maestrano::Saml::Response.new(Base64.encode64(resp_xml))
151
+ response.stubs(:conditions).returns(nil)
152
+ settings = Maestrano::Saml::Settings.new
153
+ settings.idp_cert_fingerprint = signature_fingerprint_1
154
+ response.settings = settings
155
+ assert_raises(Maestrano::Saml::ValidationError, 'Digest mismatch'){ response.validate! }
156
+ end
157
+ end
158
+
159
+ context "#name_id" do
160
+ should "extract the value of the name id element" do
161
+ response = Maestrano::Saml::Response.new(response_document)
162
+ assert_equal "support@onelogin.com", response.name_id
163
+
164
+ response = Maestrano::Saml::Response.new(response_document_3)
165
+ assert_equal "someone@example.com", response.name_id
166
+ end
167
+
168
+ should "be extractable from an OpenSAML response" do
169
+ response = Maestrano::Saml::Response.new(fixture(:open_saml))
170
+ assert_equal "someone@example.org", response.name_id
171
+ end
172
+
173
+ should "be extractable from a Simple SAML PHP response" do
174
+ response = Maestrano::Saml::Response.new(fixture(:simple_saml_php))
175
+ assert_equal "someone@example.com", response.name_id
176
+ end
177
+ end
178
+
179
+ context "#check_conditions" do
180
+ should "check time conditions" do
181
+ response = Maestrano::Saml::Response.new(response_document)
182
+ assert !response.send(:validate_conditions, true)
183
+ response = Maestrano::Saml::Response.new(response_document_6)
184
+ assert response.send(:validate_conditions, true)
185
+ time = Time.parse("2011-06-14T18:25:01.516Z")
186
+ Time.stubs(:now).returns(time)
187
+ response = Maestrano::Saml::Response.new(response_document_5)
188
+ assert response.send(:validate_conditions, true)
189
+ end
190
+
191
+ should "optionally allow for clock drift" do
192
+ # The NotBefore condition in the document is 2011-06-14T18:21:01.516Z
193
+ Time.stubs(:now).returns(Time.parse("2011-06-14T18:21:01Z"))
194
+ response = Maestrano::Saml::Response.new(response_document_5, :allowed_clock_drift => 0.515)
195
+ assert !response.send(:validate_conditions, true)
196
+
197
+ Time.stubs(:now).returns(Time.parse("2011-06-14T18:21:01Z"))
198
+ response = Maestrano::Saml::Response.new(response_document_5, :allowed_clock_drift => 0.516)
199
+ assert response.send(:validate_conditions, true)
200
+ end
201
+ end
202
+
203
+ context "#attributes" do
204
+ should "extract the first attribute in a hash accessed via its symbol" do
205
+ response = Maestrano::Saml::Response.new(response_document)
206
+ assert_equal "demo", response.attributes[:uid]
207
+ end
208
+
209
+ should "extract the first attribute in a hash accessed via its name" do
210
+ response = Maestrano::Saml::Response.new(response_document)
211
+ assert_equal "demo", response.attributes["uid"]
212
+ end
213
+
214
+ should "extract all attributes" do
215
+ response = Maestrano::Saml::Response.new(response_document)
216
+ assert_equal "demo", response.attributes[:uid]
217
+ assert_equal "value", response.attributes[:another_value]
218
+ end
219
+
220
+ should "work for implicit namespaces" do
221
+ response = Maestrano::Saml::Response.new(response_document_3)
222
+ assert_equal "someone@example.com", response.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
223
+ end
224
+
225
+ should "not raise on responses without attributes" do
226
+ response = Maestrano::Saml::Response.new(response_document_4)
227
+ assert_equal Hash.new, response.attributes
228
+ end
229
+
230
+ context "#multiple values" do
231
+ should "extract single value as string" do
232
+ response = Maestrano::Saml::Response.new(fixture(:response_with_multiple_attribute_values))
233
+ assert_equal "demo", response.attributes[:uid]
234
+ end
235
+
236
+ should "extract first of multiple values as string for b/w compatibility" do
237
+ response = Maestrano::Saml::Response.new(fixture(:response_with_multiple_attribute_values))
238
+ assert_equal 'value1', response.attributes[:another_value]
239
+ end
240
+
241
+ should "return array with all attributes when asked" do
242
+ response = Maestrano::Saml::Response.new(fixture(:response_with_multiple_attribute_values))
243
+ assert_equal ['value2', 'value1'], response.attributes[:another_value].values
244
+ end
245
+
246
+ should "return last of multiple values when multiple Attribute tags in XML" do
247
+ response = Maestrano::Saml::Response.new(fixture(:response_with_multiple_attribute_values))
248
+ assert_equal 'role2', response.attributes[:role]
249
+ end
250
+
251
+ should "return all of multiple values in reverse order when multiple Attribute tags in XML" do
252
+ response = Maestrano::Saml::Response.new(fixture(:response_with_multiple_attribute_values))
253
+ assert_equal ['role2', 'role1'], response.attributes[:role].values
254
+ end
255
+ end
256
+ end
257
+
258
+ context "#session_expires_at" do
259
+ should "extract the value of the SessionNotOnOrAfter attribute" do
260
+ response = Maestrano::Saml::Response.new(response_document)
261
+ assert response.session_expires_at.is_a?(Time)
262
+
263
+ response = Maestrano::Saml::Response.new(response_document_2)
264
+ assert response.session_expires_at.nil?
265
+ end
266
+ end
267
+
268
+ context "#issuer" do
269
+ should "return the issuer inside the response assertion" do
270
+ response = Maestrano::Saml::Response.new(response_document)
271
+ assert_equal "https://app.onelogin.com/saml/metadata/13590", response.issuer
272
+ end
273
+
274
+ should "return the issuer inside the response" do
275
+ response = Maestrano::Saml::Response.new(response_document_2)
276
+ assert_equal "wibble", response.issuer
277
+ end
278
+ end
279
+
280
+ context "#success" do
281
+ should "find a status code that says success" do
282
+ response = Maestrano::Saml::Response.new(response_document)
283
+ response.success?
284
+ end
285
+ end
286
+
287
+ end
288
+ end
289
+ end
290
+ end