authlogic 4.0.1 → 4.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +43 -1
- data/.rubocop_todo.yml +23 -132
- data/CHANGELOG.md +12 -0
- data/CONTRIBUTING.md +10 -3
- data/Gemfile +2 -2
- data/Rakefile +6 -6
- data/authlogic.gemspec +13 -12
- data/lib/authlogic/acts_as_authentic/base.rb +12 -7
- data/lib/authlogic/acts_as_authentic/email.rb +16 -6
- data/lib/authlogic/acts_as_authentic/logged_in_status.rb +10 -5
- data/lib/authlogic/acts_as_authentic/login.rb +11 -5
- data/lib/authlogic/acts_as_authentic/password.rb +111 -57
- data/lib/authlogic/acts_as_authentic/perishable_token.rb +6 -2
- data/lib/authlogic/acts_as_authentic/persistence_token.rb +1 -1
- data/lib/authlogic/acts_as_authentic/queries/find_with_case.rb +2 -2
- data/lib/authlogic/acts_as_authentic/restful_authentication.rb +31 -3
- data/lib/authlogic/acts_as_authentic/session_maintenance.rb +11 -3
- data/lib/authlogic/acts_as_authentic/single_access_token.rb +14 -2
- data/lib/authlogic/acts_as_authentic/validations_scope.rb +6 -6
- data/lib/authlogic/authenticates_many/association.rb +2 -2
- data/lib/authlogic/authenticates_many/base.rb +27 -19
- data/lib/authlogic/controller_adapters/rack_adapter.rb +1 -1
- data/lib/authlogic/controller_adapters/rails_adapter.rb +6 -3
- data/lib/authlogic/controller_adapters/sinatra_adapter.rb +2 -2
- data/lib/authlogic/crypto_providers.rb +2 -0
- data/lib/authlogic/crypto_providers/bcrypt.rb +15 -9
- data/lib/authlogic/crypto_providers/md5.rb +2 -1
- data/lib/authlogic/crypto_providers/scrypt.rb +12 -7
- data/lib/authlogic/crypto_providers/sha256.rb +2 -1
- data/lib/authlogic/crypto_providers/wordpress.rb +31 -2
- data/lib/authlogic/i18n.rb +22 -17
- data/lib/authlogic/regex.rb +57 -29
- data/lib/authlogic/session/activation.rb +1 -1
- data/lib/authlogic/session/brute_force_protection.rb +2 -2
- data/lib/authlogic/session/callbacks.rb +43 -36
- data/lib/authlogic/session/cookies.rb +4 -2
- data/lib/authlogic/session/existence.rb +1 -1
- data/lib/authlogic/session/foundation.rb +5 -1
- data/lib/authlogic/session/http_auth.rb +2 -2
- data/lib/authlogic/session/klass.rb +2 -1
- data/lib/authlogic/session/magic_columns.rb +4 -2
- data/lib/authlogic/session/magic_states.rb +9 -10
- data/lib/authlogic/session/params.rb +11 -4
- data/lib/authlogic/session/password.rb +72 -38
- data/lib/authlogic/session/perishable_token.rb +2 -1
- data/lib/authlogic/session/persistence.rb +2 -1
- data/lib/authlogic/session/scopes.rb +26 -16
- data/lib/authlogic/session/unauthorized_record.rb +12 -7
- data/lib/authlogic/session/validation.rb +1 -1
- data/lib/authlogic/test_case/mock_controller.rb +1 -1
- data/lib/authlogic/test_case/mock_cookie_jar.rb +1 -1
- data/lib/authlogic/test_case/mock_request.rb +1 -1
- data/lib/authlogic/version.rb +1 -1
- data/test/acts_as_authentic_test/base_test.rb +1 -1
- data/test/acts_as_authentic_test/email_test.rb +11 -11
- data/test/acts_as_authentic_test/logged_in_status_test.rb +4 -4
- data/test/acts_as_authentic_test/login_test.rb +2 -2
- data/test/acts_as_authentic_test/magic_columns_test.rb +1 -1
- data/test/acts_as_authentic_test/password_test.rb +1 -1
- data/test/acts_as_authentic_test/perishable_token_test.rb +2 -2
- data/test/acts_as_authentic_test/persistence_token_test.rb +1 -1
- data/test/acts_as_authentic_test/restful_authentication_test.rb +12 -3
- data/test/acts_as_authentic_test/session_maintenance_test.rb +1 -1
- data/test/acts_as_authentic_test/single_access_test.rb +1 -1
- data/test/adapter_test.rb +3 -3
- data/test/authenticates_many_test.rb +1 -1
- data/test/config_test.rb +9 -9
- data/test/crypto_provider_test/aes256_test.rb +1 -1
- data/test/crypto_provider_test/bcrypt_test.rb +1 -1
- data/test/crypto_provider_test/scrypt_test.rb +1 -1
- data/test/crypto_provider_test/sha1_test.rb +1 -1
- data/test/crypto_provider_test/sha256_test.rb +1 -1
- data/test/crypto_provider_test/sha512_test.rb +1 -1
- data/test/crypto_provider_test/wordpress_test.rb +24 -0
- data/test/i18n_test.rb +3 -3
- data/test/libs/user_session.rb +2 -2
- data/test/random_test.rb +1 -1
- data/test/session_test/activation_test.rb +1 -1
- data/test/session_test/active_record_trickery_test.rb +3 -3
- data/test/session_test/brute_force_protection_test.rb +1 -1
- data/test/session_test/callbacks_test.rb +9 -3
- data/test/session_test/cookies_test.rb +11 -11
- data/test/session_test/existence_test.rb +1 -1
- data/test/session_test/foundation_test.rb +1 -1
- data/test/session_test/http_auth_test.rb +6 -6
- data/test/session_test/id_test.rb +1 -1
- data/test/session_test/klass_test.rb +1 -1
- data/test/session_test/magic_columns_test.rb +1 -1
- data/test/session_test/magic_states_test.rb +1 -1
- data/test/session_test/params_test.rb +7 -4
- data/test/session_test/password_test.rb +1 -1
- data/test/session_test/perishability_test.rb +1 -1
- data/test/session_test/persistence_test.rb +1 -1
- data/test/session_test/scopes_test.rb +9 -3
- data/test/session_test/session_test.rb +2 -2
- data/test/session_test/timeout_test.rb +1 -1
- data/test/session_test/unauthorized_record_test.rb +1 -1
- data/test/session_test/validation_test.rb +1 -1
- data/test/test_helper.rb +34 -14
- metadata +6 -4
@@ -14,7 +14,8 @@ module Authlogic
|
|
14
14
|
private
|
15
15
|
|
16
16
|
def reset_perishable_token!
|
17
|
-
if record.respond_to?(:reset_perishable_token) &&
|
17
|
+
if record.respond_to?(:reset_perishable_token) &&
|
18
|
+
!record.disable_perishable_token_maintenance?
|
18
19
|
record.reset_perishable_token
|
19
20
|
end
|
20
21
|
end
|
@@ -28,7 +28,8 @@ module Authlogic
|
|
28
28
|
# @current_user = current_user_session && current_user_session.user
|
29
29
|
# end
|
30
30
|
#
|
31
|
-
# Also, this method accepts a single parameter as the id, to find
|
31
|
+
# Also, this method accepts a single parameter as the id, to find
|
32
|
+
# session that you marked with an id:
|
32
33
|
#
|
33
34
|
# UserSession.find(:secure)
|
34
35
|
#
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "request_store"
|
2
2
|
|
3
3
|
module Authlogic
|
4
4
|
module Session
|
@@ -25,10 +25,10 @@ module Authlogic
|
|
25
25
|
RequestStore.store[:authlogic_scope]
|
26
26
|
end
|
27
27
|
|
28
|
-
# What with_scopes focuses on is scoping the query when finding the
|
29
|
-
# name of the cookie / session. It works very similar to
|
30
|
-
# ActiveRecord::Base#with_scopes. It accepts a hash with any of the
|
31
|
-
# options:
|
28
|
+
# What with_scopes focuses on is scoping the query when finding the
|
29
|
+
# object and the name of the cookie / session. It works very similar to
|
30
|
+
# ActiveRecord::Base#with_scopes. It accepts a hash with any of the
|
31
|
+
# following options:
|
32
32
|
#
|
33
33
|
# * <tt>find_options:</tt> any options you can pass into ActiveRecord::Base.find.
|
34
34
|
# This is used when trying to find the record.
|
@@ -37,21 +37,27 @@ module Authlogic
|
|
37
37
|
#
|
38
38
|
# Here is how you use it:
|
39
39
|
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
40
|
+
# ```
|
41
|
+
# UserSession.with_scope(find_options: {conditions: "account_id = 2"}, id: "account_2") do
|
42
|
+
# UserSession.find
|
43
|
+
# end
|
44
|
+
# ```
|
43
45
|
#
|
44
|
-
# Essentially what the above does is scope the searching of the object
|
45
|
-
# sql you provided. So instead of:
|
46
|
+
# Essentially what the above does is scope the searching of the object
|
47
|
+
# with the sql you provided. So instead of:
|
46
48
|
#
|
47
|
-
#
|
49
|
+
# ```
|
50
|
+
# User.where("login = 'ben'").first
|
51
|
+
# ```
|
48
52
|
#
|
49
53
|
# it would be:
|
50
54
|
#
|
51
|
-
#
|
55
|
+
# ```
|
56
|
+
# User.where("login = 'ben' and account_id = 2").first
|
57
|
+
# ```
|
52
58
|
#
|
53
|
-
# You will also notice the :id option. This works just like the id
|
54
|
-
# scopes your cookies. So the name of your cookie will be:
|
59
|
+
# You will also notice the :id option. This works just like the id
|
60
|
+
# method. It scopes your cookies. So the name of your cookie will be:
|
55
61
|
#
|
56
62
|
# account_2_user_credentials
|
57
63
|
#
|
@@ -59,9 +65,13 @@ module Authlogic
|
|
59
65
|
#
|
60
66
|
# user_credentials
|
61
67
|
#
|
62
|
-
# What is also nifty about scoping with an :id is that it merges your
|
68
|
+
# What is also nifty about scoping with an :id is that it merges your
|
69
|
+
# id's. So if you do:
|
63
70
|
#
|
64
|
-
# UserSession.with_scope(
|
71
|
+
# UserSession.with_scope(
|
72
|
+
# find_options: { conditions: "account_id = 2"},
|
73
|
+
# id: "account_2"
|
74
|
+
# ) do
|
65
75
|
# session = UserSession.new
|
66
76
|
# session.id = :secure
|
67
77
|
# end
|
@@ -4,18 +4,23 @@ module Authlogic
|
|
4
4
|
#
|
5
5
|
# UserSession.create(my_user_object)
|
6
6
|
#
|
7
|
-
# Be careful with this, because Authlogic is assuming that you have already
|
8
|
-
# user is who he says he is.
|
7
|
+
# Be careful with this, because Authlogic is assuming that you have already
|
8
|
+
# confirmed that the user is who he says he is.
|
9
9
|
#
|
10
|
-
# For example, this is the method used to persist the session internally.
|
11
|
-
# the persistence token. At this point we know
|
12
|
-
#
|
13
|
-
#
|
10
|
+
# For example, this is the method used to persist the session internally.
|
11
|
+
# Authlogic finds the user with the persistence token. At this point we know
|
12
|
+
# the user is who he says he is, so Authlogic just creates a session with
|
13
|
+
# the record. This is particularly useful for 3rd party authentication
|
14
|
+
# methods, such as OpenID. Let that method verify the identity, once it's
|
15
|
+
# verified, pass the object and create a session.
|
14
16
|
module UnauthorizedRecord
|
15
17
|
def self.included(klass)
|
16
18
|
klass.class_eval do
|
17
19
|
attr_accessor :unauthorized_record
|
18
|
-
validate
|
20
|
+
validate(
|
21
|
+
:validate_by_unauthorized_record,
|
22
|
+
if: :authenticating_with_unauthorized_record?
|
23
|
+
)
|
19
24
|
end
|
20
25
|
end
|
21
26
|
|
@@ -14,7 +14,7 @@ module Authlogic
|
|
14
14
|
yield http_user, http_password
|
15
15
|
end
|
16
16
|
|
17
|
-
def authenticate_or_request_with_http_basic(realm =
|
17
|
+
def authenticate_or_request_with_http_basic(realm = "DefaultRealm")
|
18
18
|
self.realm = realm
|
19
19
|
@http_auth_requested = true
|
20
20
|
yield http_user, http_password
|
@@ -33,7 +33,7 @@ module Authlogic
|
|
33
33
|
def [](val)
|
34
34
|
signed_message = @parent_jar[val]
|
35
35
|
if signed_message
|
36
|
-
payload, signature = signed_message.split(
|
36
|
+
payload, signature = signed_message.split("--")
|
37
37
|
raise "Invalid signature" unless Digest::SHA1.hexdigest(payload) == signature
|
38
38
|
payload
|
39
39
|
end
|
data/lib/authlogic/version.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
module ActsAsAuthenticTest
|
4
4
|
class EmailTest < ActiveSupport::TestCase
|
@@ -46,7 +46,7 @@ module ActsAsAuthenticTest
|
|
46
46
|
BAD_UTF8_EMAILS = [
|
47
47
|
"",
|
48
48
|
".みんな", # https://github.com/binarylogic/authlogic/issues/176#issuecomment-55829320
|
49
|
-
|
49
|
+
"δκιμή@παράδεγμα.δ", # short TLD
|
50
50
|
"öm(@ava.fi", # L paren
|
51
51
|
"é)@domain.com", # R paren
|
52
52
|
"é[@example.com", # L bracket
|
@@ -94,10 +94,10 @@ module ActsAsAuthenticTest
|
|
94
94
|
|
95
95
|
def test_validates_format_of_email_field_options_config
|
96
96
|
default = {
|
97
|
-
with: Authlogic::Regex
|
97
|
+
with: Authlogic::Regex::EMAIL,
|
98
98
|
message: proc do
|
99
99
|
I18n.t(
|
100
|
-
|
100
|
+
"error_messages.email_invalid",
|
101
101
|
default: "should look like an email address."
|
102
102
|
)
|
103
103
|
end
|
@@ -122,10 +122,10 @@ module ActsAsAuthenticTest
|
|
122
122
|
assert_equal default, User.validates_format_of_email_field_options
|
123
123
|
|
124
124
|
with_email_nonascii = {
|
125
|
-
with: Authlogic::Regex
|
126
|
-
message:
|
125
|
+
with: Authlogic::Regex::EMAIL_NONASCII,
|
126
|
+
message: proc do
|
127
127
|
I18n.t(
|
128
|
-
|
128
|
+
"error_messages.email_invalid_international",
|
129
129
|
default: "should look like an international email address."
|
130
130
|
)
|
131
131
|
end
|
@@ -140,11 +140,11 @@ module ActsAsAuthenticTest
|
|
140
140
|
# ensure we successfully loaded the test locale
|
141
141
|
assert I18n.available_locales.include?(:lol), "Test locale failed to load"
|
142
142
|
|
143
|
-
I18n.with_locale(
|
143
|
+
I18n.with_locale("lol") do
|
144
144
|
message = I18n.t("authlogic.error_messages.email_invalid")
|
145
145
|
|
146
146
|
cat = User.new
|
147
|
-
cat.email =
|
147
|
+
cat.email = "meow"
|
148
148
|
cat.valid?
|
149
149
|
|
150
150
|
# filter duplicate error messages
|
@@ -213,11 +213,11 @@ module ActsAsAuthenticTest
|
|
213
213
|
|
214
214
|
def test_validates_format_of_nonascii_email_field
|
215
215
|
(GOOD_ASCII_EMAILS + GOOD_ISO88591_EMAILS + GOOD_UTF8_EMAILS).each do |e|
|
216
|
-
assert e =~
|
216
|
+
assert e =~ Authlogic::Regex::EMAIL_NONASCII, "Good email should validate: #{e}"
|
217
217
|
end
|
218
218
|
|
219
219
|
(BAD_ASCII_EMAILS + BAD_ISO88591_EMAILS + BAD_UTF8_EMAILS).each do |e|
|
220
|
-
assert e !~
|
220
|
+
assert e !~ Authlogic::Regex::EMAIL_NONASCII, "Bad email should not validate: #{e}"
|
221
221
|
end
|
222
222
|
end
|
223
223
|
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
module ActsAsAuthenticTest
|
4
4
|
class LoggedInStatusTest < ActiveSupport::TestCase
|
5
|
-
ERROR_MSG =
|
5
|
+
ERROR_MSG = "Multiple calls to %s should result in different relations".freeze
|
6
6
|
|
7
7
|
def test_logged_in_timeout_config
|
8
8
|
assert_equal 10.minutes.to_i, User.logged_in_timeout
|
@@ -25,7 +25,7 @@ module ActsAsAuthenticTest
|
|
25
25
|
query1 = User.logged_in.to_sql
|
26
26
|
sleep 0.1
|
27
27
|
query2 = User.logged_in.to_sql
|
28
|
-
assert query1 != query2, ERROR_MSG %
|
28
|
+
assert query1 != query2, ERROR_MSG % "#logged_in"
|
29
29
|
|
30
30
|
assert_equal 0, User.logged_in.count
|
31
31
|
user = User.first
|
@@ -43,7 +43,7 @@ module ActsAsAuthenticTest
|
|
43
43
|
|
44
44
|
# for rails 5 I've changed the where_values to to_sql to compare
|
45
45
|
|
46
|
-
assert User.logged_in.to_sql != User.logged_out.to_sql, ERROR_MSG %
|
46
|
+
assert User.logged_in.to_sql != User.logged_out.to_sql, ERROR_MSG % "#logged_out"
|
47
47
|
|
48
48
|
assert_equal 3, User.logged_out.count
|
49
49
|
User.first.update_attribute(:last_request_at, Time.now)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
module ActsAsAuthenticTest
|
4
4
|
# Tests for configuration option: `validates_format_of_login_field_options`
|
@@ -36,7 +36,7 @@ module ActsAsAuthenticTest
|
|
36
36
|
with: /\A[a-zA-Z0-9_][a-zA-Z0-9\.+\-_@ ]+\z/,
|
37
37
|
message: proc do
|
38
38
|
I18n.t(
|
39
|
-
|
39
|
+
"error_messages.login_invalid",
|
40
40
|
default: "should use only letters, numbers, spaces, and .-_@+ please."
|
41
41
|
)
|
42
42
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
module ActsAsAuthenticTest
|
4
4
|
class PerishableTokenTest < ActiveSupport::TestCase
|
@@ -89,7 +89,7 @@ module ActsAsAuthenticTest
|
|
89
89
|
|
90
90
|
def test_find_perishable_token_with_bang
|
91
91
|
assert_raises ActiveRecord::RecordNotFound do
|
92
|
-
User.find_using_perishable_token!(
|
92
|
+
User.find_using_perishable_token!("some_bad_value")
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
@@ -1,7 +1,16 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
module ActsAsAuthenticTest
|
4
4
|
class RestfulAuthenticationTest < ActiveSupport::TestCase
|
5
|
+
def setup
|
6
|
+
@old_deprecation_behavior = ::ActiveSupport::Deprecation.behavior
|
7
|
+
::ActiveSupport::Deprecation.behavior = :silence
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
::ActiveSupport::Deprecation.behavior = @old_deprecation_behavior
|
12
|
+
end
|
13
|
+
|
5
14
|
def test_act_like_restful_authentication_config
|
6
15
|
refute User.act_like_restful_authentication
|
7
16
|
refute Employee.act_like_restful_authentication
|
@@ -10,7 +19,7 @@ module ActsAsAuthenticTest
|
|
10
19
|
assert User.act_like_restful_authentication
|
11
20
|
assert_equal Authlogic::CryptoProviders::Sha1, User.crypto_provider
|
12
21
|
assert defined?(::REST_AUTH_SITE_KEY)
|
13
|
-
assert_equal
|
22
|
+
assert_equal "", ::REST_AUTH_SITE_KEY
|
14
23
|
assert_equal 1, Authlogic::CryptoProviders::Sha1.stretches
|
15
24
|
|
16
25
|
User.act_like_restful_authentication false
|
@@ -27,7 +36,7 @@ module ActsAsAuthenticTest
|
|
27
36
|
User.transition_from_restful_authentication = true
|
28
37
|
assert User.transition_from_restful_authentication
|
29
38
|
assert defined?(::REST_AUTH_SITE_KEY)
|
30
|
-
assert_equal
|
39
|
+
assert_equal "", ::REST_AUTH_SITE_KEY
|
31
40
|
assert_equal 1, Authlogic::CryptoProviders::Sha1.stretches
|
32
41
|
|
33
42
|
User.transition_from_restful_authentication false
|
data/test/adapter_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
module Authlogic
|
4
4
|
module ControllerAdapters
|
@@ -6,7 +6,7 @@ module Authlogic
|
|
6
6
|
def test_controller
|
7
7
|
controller = Class.new(MockController) do
|
8
8
|
def controller.an_arbitrary_method
|
9
|
-
|
9
|
+
"bar"
|
10
10
|
end
|
11
11
|
end.new
|
12
12
|
adapter = Authlogic::ControllerAdapters::AbstractAdapter.new(controller)
|
@@ -14,7 +14,7 @@ module Authlogic
|
|
14
14
|
assert_equal controller, adapter.controller
|
15
15
|
assert controller.params.equal?(adapter.params)
|
16
16
|
assert adapter.respond_to?(:an_arbitrary_method)
|
17
|
-
assert_equal
|
17
|
+
assert_equal "bar", adapter.an_arbitrary_method
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
data/test/config_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
class ConfigTest < ActiveSupport::TestCase
|
4
4
|
def setup
|
@@ -6,7 +6,7 @@ class ConfigTest < ActiveSupport::TestCase
|
|
6
6
|
extend Authlogic::Config
|
7
7
|
|
8
8
|
def self.foobar(value = nil)
|
9
|
-
rw_config(:foobar_field, value,
|
9
|
+
rw_config(:foobar_field, value, "default_foobar")
|
10
10
|
end
|
11
11
|
}
|
12
12
|
|
@@ -18,19 +18,19 @@ class ConfigTest < ActiveSupport::TestCase
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def test_rw_config_read_with_default
|
21
|
-
assert
|
21
|
+
assert "default_foobar", @klass.foobar
|
22
22
|
end
|
23
23
|
|
24
24
|
def test_rw_config_write
|
25
|
-
assert_equal
|
26
|
-
assert_equal
|
25
|
+
assert_equal "my_foobar", @klass.foobar("my_foobar")
|
26
|
+
assert_equal "my_foobar", @klass.foobar
|
27
27
|
|
28
|
-
assert_equal
|
29
|
-
assert_equal
|
28
|
+
assert_equal "my_new_foobar", @klass.foobar("my_new_foobar")
|
29
|
+
assert_equal "my_new_foobar", @klass.foobar
|
30
30
|
end
|
31
31
|
|
32
32
|
def test_subclass_rw_config_write
|
33
|
-
assert_equal
|
34
|
-
assert_equal
|
33
|
+
assert_equal "subklass_foobar", @subklass.foobar("subklass_foobar")
|
34
|
+
assert_equal "default_foobar", @klass.foobar
|
35
35
|
end
|
36
36
|
end
|