ruby-saml 0.9 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-saml might be problematic. Click here for more details.

@@ -1,12 +1,13 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
2
 
3
- class SettingsTest < Test::Unit::TestCase
3
+ class SettingsTest < Minitest::Test
4
4
 
5
- context "Settings" do
6
- setup do
5
+ describe "Settings" do
6
+ before do
7
7
  @settings = OneLogin::RubySaml::Settings.new
8
8
  end
9
- should "should provide getters and settings" do
9
+
10
+ it "should provide getters and settings" do
10
11
  accessors = [
11
12
  :idp_entity_id, :idp_sso_target_url, :idp_slo_target_url, :idp_cert, :idp_cert_fingerprint,
12
13
  :issuer, :assertion_consumer_service_url, :assertion_consumer_service_binding,
@@ -28,7 +29,7 @@ class SettingsTest < Test::Unit::TestCase
28
29
 
29
30
  end
30
31
 
31
- should "create settings from hash" do
32
+ it "create settings from hash" do
32
33
 
33
34
  config = {
34
35
  :assertion_consumer_service_url => "http://app.muda.no/sso",
@@ -49,18 +50,16 @@ class SettingsTest < Test::Unit::TestCase
49
50
  end
50
51
  end
51
52
 
52
- should "configure attribute service attributes correctly" do
53
+ it "configure attribute service attributes correctly" do
53
54
  @settings = OneLogin::RubySaml::Settings.new
54
55
  @settings.attribute_consuming_service.configure do
55
56
  service_name "Test Service"
56
- add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
57
+ add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
57
58
  end
58
59
 
59
60
  assert_equal @settings.attribute_consuming_service.configured?, true
60
61
  assert_equal @settings.attribute_consuming_service.name, "Test Service"
61
62
  assert_equal @settings.attribute_consuming_service.attributes, [{:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name" }]
62
63
  end
63
-
64
64
  end
65
-
66
65
  end
@@ -1,63 +1,62 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
2
  require 'responses/logoutresponse_fixtures'
3
3
 
4
- class RubySamlTest < Test::Unit::TestCase
4
+ class RubySamlTest < Minitest::Test
5
5
 
6
- context "SloLogoutrequest" do
7
- should "raise an exception when response is initialized with nil" do
6
+ describe "SloLogoutrequest" do
7
+ it "raise an exception when response is initialized with nil" do
8
8
  assert_raises(ArgumentError) { OneLogin::RubySaml::SloLogoutrequest.new(nil) }
9
9
  end
10
10
 
11
- context "#is_valid?" do
12
- should "return false when response is initialized with blank data" do
11
+ describe "#is_valid?" do
12
+ it "return false when response is initialized with blank data" do
13
13
  request = OneLogin::RubySaml::SloLogoutrequest.new('')
14
14
  assert !request.is_valid?
15
15
  end
16
16
 
17
- should "return true when the request is initialized with valid data" do
17
+ it "return true when the request is initialized with valid data" do
18
18
  request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document)
19
19
  assert request.is_valid?
20
20
  assert_equal 'someone@example.org', request.name_id
21
21
  end
22
22
 
23
- should "should be idempotent when the response is initialized with invalid data" do
23
+ it "should be idempotent when the response is initialized with invalid data" do
24
24
  request = OneLogin::RubySaml::SloLogoutrequest.new(invalid_xml_response)
25
25
  assert !request.is_valid?
26
26
  assert !request.is_valid?
27
27
  end
28
28
 
29
- should "should be idempotent when the response is initialized with valid data" do
29
+ it "should be idempotent when the response is initialized with valid data" do
30
30
  request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document)
31
31
  assert request.is_valid?
32
32
  assert request.is_valid?
33
33
  end
34
34
 
35
- should "raise error for invalid xml" do
35
+ it "raise error for invalid xml" do
36
36
  logout_request = OneLogin::RubySaml::SloLogoutrequest.new(invalid_xml_response)
37
37
  assert_raises(OneLogin::RubySaml::ValidationError) { logout_request.validate! }
38
38
  end
39
39
  end
40
40
 
41
- context "#name_id" do
42
- should "extract the value of the name id element" do
41
+ describe "#name_id" do
42
+ it "extract the value of the name id element" do
43
43
  request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document)
44
44
  assert_equal "someone@example.org", request.name_id
45
45
  end
46
46
  end
47
47
 
48
- context "#issuer" do
49
- should "return the issuer inside the request" do
48
+ describe "#issuer" do
49
+ it "return the issuer inside the request" do
50
50
  request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document)
51
51
  assert_equal "https://app.onelogin.com/saml/metadata/SOMEACCOUNT", request.issuer
52
52
  end
53
53
  end
54
54
 
55
- context "#id" do
56
- should "extract the value of the ID attribute" do
55
+ describe "#id" do
56
+ it "extract the value of the ID attribute" do
57
57
  request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document)
58
58
  assert_equal "_c0348950-935b-0131-1060-782bcb56fcaa", request.id
59
59
  end
60
60
  end
61
-
62
61
  end
63
62
  end
@@ -1,11 +1,11 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
2
 
3
- class SloLogoutresponseTest < Test::Unit::TestCase
3
+ class SloLogoutresponseTest < Minitest::Test
4
4
 
5
- context "SloLogoutresponse" do
6
- settings = OneLogin::RubySaml::Settings.new
5
+ describe "SloLogoutresponse" do
6
+ let(:settings) { OneLogin::RubySaml::Settings.new }
7
7
 
8
- should "create the deflated SAMLResponse URL parameter" do
8
+ it "create the deflated SAMLResponse URL parameter" do
9
9
  settings.idp_slo_target_url = "http://unauth.com/logout"
10
10
  settings.name_identifier_value = "f00f00"
11
11
  settings.compress_request = true
@@ -22,7 +22,7 @@ class SloLogoutresponseTest < Test::Unit::TestCase
22
22
  assert_match /^<samlp:LogoutResponse/, inflated
23
23
  end
24
24
 
25
- should "support additional params" do
25
+ it "support additional params" do
26
26
  settings.idp_slo_target_url = "http://unauth.com/logout"
27
27
  settings.name_identifier_value = "f00f00"
28
28
  settings.compress_request = true
@@ -39,7 +39,7 @@ class SloLogoutresponseTest < Test::Unit::TestCase
39
39
  assert unauth_url =~ /&RelayState=http%3A%2F%2Fidp.example.com$/
40
40
  end
41
41
 
42
- should "set InResponseTo to the ID from the logout request" do
42
+ it "set InResponseTo to the ID from the logout request" do
43
43
  settings.idp_slo_target_url = "http://unauth.com/logout"
44
44
  settings.name_identifier_value = "f00f00"
45
45
  settings.compress_request = true
@@ -52,7 +52,7 @@ class SloLogoutresponseTest < Test::Unit::TestCase
52
52
  assert_match /InResponseTo='_c0348950-935b-0131-1060-782bcb56fcaa'/, inflated
53
53
  end
54
54
 
55
- should "set a custom successful logout message on the response" do
55
+ it "set a custom successful logout message on the response" do
56
56
  settings.idp_slo_target_url = "http://unauth.com/logout"
57
57
  settings.name_identifier_value = "f00f00"
58
58
  settings.compress_request = true
@@ -65,8 +65,8 @@ class SloLogoutresponseTest < Test::Unit::TestCase
65
65
  assert_match /<samlp:StatusMessage>Custom Logout Message<\/samlp:StatusMessage>/, inflated
66
66
  end
67
67
 
68
- context "when the settings indicate to sign (embebed) the logout response" do
69
- should "create a signed logout response" do
68
+ describe "when the settings indicate to sign (embebed) the logout response" do
69
+ it "create a signed logout response" do
70
70
  settings = OneLogin::RubySaml::Settings.new
71
71
  settings.compress_response = false
72
72
  settings.idp_slo_target_url = "http://example.com?field=value"
@@ -84,7 +84,7 @@ class SloLogoutresponseTest < Test::Unit::TestCase
84
84
  response_xml =~ /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2000\/09\/xmldsig#rsa-sha1'\/>/
85
85
  end
86
86
 
87
- should "create a signed logout response with 256 digest and signature methods" do
87
+ it "create a signed logout response with 256 digest and signature methods" do
88
88
  settings = OneLogin::RubySaml::Settings.new
89
89
  settings.compress_response = false
90
90
  settings.idp_slo_target_url = "http://example.com?field=value"
@@ -105,8 +105,8 @@ class SloLogoutresponseTest < Test::Unit::TestCase
105
105
  end
106
106
  end
107
107
 
108
- context "when the settings indicate to sign the logout response" do
109
- should "create a signature parameter" do
108
+ describe "when the settings indicate to sign the logout response" do
109
+ it "create a signature parameter" do
110
110
  settings = OneLogin::RubySaml::Settings.new
111
111
  settings.compress_response = false
112
112
  settings.idp_slo_target_url = "http://example.com?field=value"
@@ -129,18 +129,5 @@ class SloLogoutresponseTest < Test::Unit::TestCase
129
129
  assert params['SigAlg'] == XMLSecurity::Document::SHA1
130
130
  end
131
131
  end
132
-
133
132
  end
134
-
135
- def decode_saml_response_payload(unauth_url)
136
- payload = CGI.unescape(unauth_url.split("SAMLResponse=").last)
137
- decoded = Base64.decode64(payload)
138
-
139
- zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
140
- inflated = zstream.inflate(decoded)
141
- zstream.finish
142
- zstream.close
143
- inflated
144
- end
145
-
146
133
  end
@@ -1,6 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'bundler'
3
- require 'test/unit'
3
+ require 'minitest/autorun'
4
4
  require 'mocha/setup'
5
5
 
6
6
  Bundler.require :default, :test
@@ -11,7 +11,7 @@ require 'ruby-saml'
11
11
 
12
12
  ENV["ruby-saml/testing"] = "1"
13
13
 
14
- class Test::Unit::TestCase
14
+ class Minitest::Test
15
15
  def fixture(document, base64 = true)
16
16
  response = Dir.glob(File.join(File.dirname(__FILE__), "responses", "#{document}*")).first
17
17
  if base64 && response =~ /\.xml$/
@@ -109,4 +109,38 @@ class Test::Unit::TestCase
109
109
  File.read(File.join(File.dirname(__FILE__), 'certificates', 'ruby-saml.key'))
110
110
  end
111
111
 
112
+ #
113
+ # logoutresponse fixtures
114
+ #
115
+ def random_id
116
+ "_#{UUID.new.generate}"
117
+ end
118
+
119
+ #
120
+ # decodes a base64 encoded SAML response for use in SloLogoutresponse tests
121
+ #
122
+ def decode_saml_response_payload(unauth_url)
123
+ payload = CGI.unescape(unauth_url.split("SAMLResponse=").last)
124
+ decoded = Base64.decode64(payload)
125
+
126
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
127
+ inflated = zstream.inflate(decoded)
128
+ zstream.finish
129
+ zstream.close
130
+ inflated
131
+ end
132
+
133
+ #
134
+ # decodes a base64 encoded SAML request for use in Logoutrequest tests
135
+ #
136
+ def decode_saml_request_payload(unauth_url)
137
+ payload = CGI.unescape(unauth_url.split("SAMLRequest=").last)
138
+ decoded = Base64.decode64(payload)
139
+
140
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
141
+ inflated = zstream.inflate(decoded)
142
+ zstream.finish
143
+ zstream.close
144
+ inflated
145
+ end
112
146
  end
@@ -1,112 +1,109 @@
1
- require 'test_helper'
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
2
  require 'xml_security'
3
+ require 'timecop'
3
4
 
4
- class XmlSecurityTest < Test::Unit::TestCase
5
+ class XmlSecurityTest < Minitest::Test
5
6
  include XMLSecurity
6
7
 
7
- context "XmlSecurity" do
8
- setup do
8
+ describe "XmlSecurity" do
9
+ before do
9
10
  @document = XMLSecurity::SignedDocument.new(Base64.decode64(response_document))
10
11
  @base64cert = @document.elements["//ds:X509Certificate"].text
11
12
  end
12
13
 
13
- should "should run validate without throwing NS related exceptions" do
14
+ it "should run validate without throwing NS related exceptions" do
14
15
  assert !@document.validate_signature(@base64cert, true)
15
16
  end
16
17
 
17
- should "should run validate with throwing NS related exceptions" do
18
- assert_raise(OneLogin::RubySaml::ValidationError) do
18
+ it "should run validate with throwing NS related exceptions" do
19
+ assert_raises(OneLogin::RubySaml::ValidationError) do
19
20
  @document.validate_signature(@base64cert, false)
20
21
  end
21
22
  end
22
23
 
23
- should "not raise an error when softly validating the document multiple times" do
24
- assert_nothing_raised do
25
- 2.times { @document.validate_signature(@base64cert, true) }
26
- end
24
+ it "not raise an error when softly validating the document multiple times" do
25
+ 2.times { assert_equal @document.validate_signature(@base64cert, true), false }
27
26
  end
28
27
 
29
- should "not raise an error when softly validating the document and the X509Certificate is missing" do
28
+ it "not raise an error when softly validating the document and the X509Certificate is missing" do
30
29
  response = Base64.decode64(response_document)
31
30
  response.sub!(/<ds:X509Certificate>.*<\/ds:X509Certificate>/, "")
32
31
  document = XMLSecurity::SignedDocument.new(response)
33
- assert_nothing_raised do
34
- assert !document.validate_document("a fingerprint", true) # The fingerprint isn't relevant to this test
35
- end
32
+ assert !document.validate_document("a fingerprint", true) # The fingerprint isn't relevant to this test
36
33
  end
37
34
 
38
- should "should raise Fingerprint mismatch" do
39
- exception = assert_raise(OneLogin::RubySaml::ValidationError) do
35
+ it "should raise Fingerprint mismatch" do
36
+ exception = assert_raises(OneLogin::RubySaml::ValidationError) do
40
37
  @document.validate_document("no:fi:ng:er:pr:in:t", false)
41
38
  end
42
39
  assert_equal("Fingerprint mismatch", exception.message)
43
40
  assert @document.errors.include? "Fingerprint mismatch"
44
41
  end
45
42
 
46
- should "should raise Digest mismatch" do
47
- exception = assert_raise(OneLogin::RubySaml::ValidationError) do
43
+ it "should raise Digest mismatch" do
44
+ exception = assert_raises(OneLogin::RubySaml::ValidationError) do
48
45
  @document.validate_signature(@base64cert, false)
49
46
  end
50
47
  assert_equal("Digest mismatch", exception.message)
51
48
  assert @document.errors.include? "Digest mismatch"
52
49
  end
53
50
 
54
- should "should raise Key validation error" do
51
+ it "should raise Key validation error" do
55
52
  response = Base64.decode64(response_document)
56
53
  response.sub!("<ds:DigestValue>pJQ7MS/ek4KRRWGmv/H43ReHYMs=</ds:DigestValue>",
57
54
  "<ds:DigestValue>b9xsAXLsynugg3Wc1CI3kpWku+0=</ds:DigestValue>")
58
55
  document = XMLSecurity::SignedDocument.new(response)
59
56
  base64cert = document.elements["//ds:X509Certificate"].text
60
- exception = assert_raise(OneLogin::RubySaml::ValidationError) do
57
+ exception = assert_raises(OneLogin::RubySaml::ValidationError) do
61
58
  document.validate_signature(base64cert, false)
62
59
  end
63
60
  assert_equal("Key validation error", exception.message)
64
61
  assert document.errors.include? "Key validation error"
65
62
  end
66
63
 
67
- should "correctly obtain the digest method with alternate namespace declaration" do
64
+ it "correctly obtain the digest method with alternate namespace declaration" do
68
65
  document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_xmlns, false))
69
66
  base64cert = document.elements["//X509Certificate"].text
70
67
  assert document.validate_signature(base64cert, false)
71
68
  end
72
69
 
73
- should "raise validation error when the X509Certificate is missing" do
70
+ it "raise validation error when the X509Certificate is missing" do
74
71
  response = Base64.decode64(response_document)
75
72
  response.sub!(/<ds:X509Certificate>.*<\/ds:X509Certificate>/, "")
76
73
  document = XMLSecurity::SignedDocument.new(response)
77
- exception = assert_raise(OneLogin::RubySaml::ValidationError) do
74
+ exception = assert_raises(OneLogin::RubySaml::ValidationError) do
78
75
  document.validate_document("a fingerprint", false) # The fingerprint isn't relevant to this test
79
76
  end
80
77
  assert_equal("Certificate element missing in response (ds:X509Certificate)", exception.message)
81
78
  end
82
79
  end
83
80
 
84
- context "Algorithms" do
85
- should "validate using SHA1" do
81
+ describe "Algorithms" do
82
+ it "validate using SHA1" do
86
83
  @document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha1, false))
87
84
  assert @document.validate_document("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")
88
85
  end
89
86
 
90
- should "validate using SHA256" do
87
+ it "validate using SHA256" do
91
88
  @document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha256, false))
92
89
  assert @document.validate_document("28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA")
93
90
  end
94
91
 
95
- should "validate using SHA384" do
92
+ it "validate using SHA384" do
96
93
  @document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha384, false))
97
94
  assert @document.validate_document("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")
98
95
  end
99
96
 
100
- should "validate using SHA512" do
97
+ it "validate using SHA512" do
101
98
  @document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha512, false))
102
99
  assert @document.validate_document("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")
103
100
  end
104
101
  end
105
102
 
106
- context "XmlSecurity::SignedDocument" do
103
+ describe "XmlSecurity::SignedDocument" do
107
104
 
108
- context "#extract_inclusive_namespaces" do
109
- should "support explicit namespace resolution for exclusive canonicalization" do
105
+ describe "#extract_inclusive_namespaces" do
106
+ it "support explicit namespace resolution for exclusive canonicalization" do
110
107
  response = fixture(:open_saml_response, false)
111
108
  document = XMLSecurity::SignedDocument.new(response)
112
109
  inclusive_namespaces = document.send(:extract_inclusive_namespaces)
@@ -114,7 +111,7 @@ class XmlSecurityTest < Test::Unit::TestCase
114
111
  assert_equal %w[ xs ], inclusive_namespaces
115
112
  end
116
113
 
117
- should "support implicit namespace resolution for exclusive canonicalization" do
114
+ it "support implicit namespace resolution for exclusive canonicalization" do
118
115
  response = fixture(:no_signature_ns, false)
119
116
  document = XMLSecurity::SignedDocument.new(response)
120
117
  inclusive_namespaces = document.send(:extract_inclusive_namespaces)
@@ -122,8 +119,8 @@ class XmlSecurityTest < Test::Unit::TestCase
122
119
  assert_equal %w[ #default saml ds xs xsi ], inclusive_namespaces
123
120
  end
124
121
 
125
- should_eventually 'support inclusive canonicalization' do
126
-
122
+ it 'support inclusive canonicalization' do
123
+ skip('test not yet implemented')
127
124
  response = OneLogin::RubySaml::Response.new(fixture("tdnf_response.xml"))
128
125
  response.stubs(:conditions).returns(nil)
129
126
  assert !response.is_valid?
@@ -135,7 +132,7 @@ class XmlSecurityTest < Test::Unit::TestCase
135
132
  assert response.validate!
136
133
  end
137
134
 
138
- should "return an empty list when inclusive namespace element is missing" do
135
+ it "return an empty list when inclusive namespace element is missing" do
139
136
  response = fixture(:no_signature_ns, false)
140
137
  response.slice! %r{<InclusiveNamespaces xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="#default saml ds xs xsi"/>}
141
138
 
@@ -146,8 +143,8 @@ class XmlSecurityTest < Test::Unit::TestCase
146
143
  end
147
144
  end
148
145
 
149
- context "XMLSecurity::DSIG" do
150
- should "sign a AuthNRequest" do
146
+ describe "XMLSecurity::DSIG" do
147
+ it "sign a AuthNRequest" do
151
148
  settings = OneLogin::RubySaml::Settings.new({
152
149
  :idp_sso_target_url => "https://idp.example.com/sso",
153
150
  :protocol_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
@@ -163,7 +160,7 @@ class XmlSecurityTest < Test::Unit::TestCase
163
160
  signed_doc.validate_document(ruby_saml_cert_fingerprint, false)
164
161
  end
165
162
 
166
- should "sign a LogoutRequest" do
163
+ it "sign a LogoutRequest" do
167
164
  settings = OneLogin::RubySaml::Settings.new({
168
165
  :idp_slo_target_url => "https://idp.example.com/slo",
169
166
  :protocol_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
@@ -179,7 +176,7 @@ class XmlSecurityTest < Test::Unit::TestCase
179
176
  signed_doc.validate_document(ruby_saml_cert_fingerprint, false)
180
177
  end
181
178
 
182
- should "sign a LogoutResponse" do
179
+ it "sign a LogoutResponse" do
183
180
  settings = OneLogin::RubySaml::Settings.new({
184
181
  :idp_slo_target_url => "https://idp.example.com/slo",
185
182
  :protocol_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
@@ -196,33 +193,31 @@ class XmlSecurityTest < Test::Unit::TestCase
196
193
  end
197
194
  end
198
195
 
199
- context "StarfieldTMS" do
200
- setup do
196
+ describe "StarfieldTMS" do
197
+ before do
201
198
  @response = OneLogin::RubySaml::Response.new(fixture(:starfield_response))
202
199
  @response.settings = OneLogin::RubySaml::Settings.new(
203
200
  :idp_cert_fingerprint => "8D:BA:53:8E:A3:B6:F9:F1:69:6C:BB:D9:D8:BD:41:B3:AC:4F:9D:4D"
204
201
  )
205
202
  end
206
203
 
207
- should "be able to validate a good response" do
204
+ it "be able to validate a good response" do
208
205
  Timecop.freeze Time.parse('2012-11-28 17:55:00 UTC') do
209
206
  assert @response.validate!
210
207
  end
211
208
  end
212
209
 
213
- should "fail before response is valid" do
210
+ it "fail before response is valid" do
214
211
  Timecop.freeze Time.parse('2012-11-20 17:55:00 UTC') do
215
212
  assert ! @response.is_valid?
216
213
  end
217
214
  end
218
215
 
219
- should "fail after response expires" do
216
+ it "fail after response expires" do
220
217
  Timecop.freeze Time.parse('2012-11-30 17:55:00 UTC') do
221
218
  assert ! @response.is_valid?
222
219
  end
223
220
  end
224
221
  end
225
-
226
222
  end
227
-
228
223
  end