authlogic 6.2.0 → 6.4.2
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/lib/authlogic/controller_adapters/abstract_adapter.rb +21 -0
- data/lib/authlogic/session/base.rb +94 -40
- data/lib/authlogic/test_case/mock_cookie_jar.rb +23 -6
- data/lib/authlogic/test_case/mock_request.rb +6 -0
- data/lib/authlogic/test_case/rails_request_adapter.rb +7 -1
- data/lib/authlogic/version.rb +1 -1
- metadata +24 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9fad0a99beb89fdac894ff1979b59459073fbf597d375b6749b263e86be930d
|
4
|
+
data.tar.gz: 4ea061519a7a881f78cfaddf1a86e312d082dba28ce22c51e52c38ac51c62581
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db0557f858087739e3a292ab6ba63becbca9a0204874f2794e934e442c58fc6d7cf814450d9df4adc76c8d1e41d18f625fc7fa942df2810248aa7b9176aa60ee
|
7
|
+
data.tar.gz: 8c349c4e3a8579384c2e1b7f3fc2f85b2974e7871d02a9e9a93117f83777110e21ad57fa4f04493f8116aaba113ebd9f735cea7ff9ae8b02e73897a51b044593
|
@@ -8,6 +8,7 @@ module Authlogic
|
|
8
8
|
class AbstractAdapter
|
9
9
|
E_COOKIE_DOMAIN_ADAPTER = "The cookie_domain method has not been " \
|
10
10
|
"implemented by the controller adapter"
|
11
|
+
ENV_SESSION_OPTIONS = "rack.session.options"
|
11
12
|
|
12
13
|
attr_accessor :controller
|
13
14
|
|
@@ -44,6 +45,26 @@ module Authlogic
|
|
44
45
|
request.content_type
|
45
46
|
end
|
46
47
|
|
48
|
+
# Inform Rack that we would like a new session ID to be assigned. Changes
|
49
|
+
# the ID, but not the contents of the session.
|
50
|
+
#
|
51
|
+
# The `:renew` option is read by `rack/session/abstract/id.rb`.
|
52
|
+
#
|
53
|
+
# This is how Devise (via warden) implements defense against Session
|
54
|
+
# Fixation. Our implementation is copied directly from the warden gem
|
55
|
+
# (set_user in warden/proxy.rb)
|
56
|
+
def renew_session_id
|
57
|
+
env = request.env
|
58
|
+
options = env[ENV_SESSION_OPTIONS]
|
59
|
+
if options
|
60
|
+
if options.frozen?
|
61
|
+
env[ENV_SESSION_OPTIONS] = options.merge(renew: true).freeze
|
62
|
+
else
|
63
|
+
options[:renew] = true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
47
68
|
def session
|
48
69
|
controller.session
|
49
70
|
end
|
@@ -351,6 +351,13 @@ module Authlogic
|
|
351
351
|
- https://github.com/binarylogic/authlogic/pull/558
|
352
352
|
- https://github.com/binarylogic/authlogic/pull/577
|
353
353
|
EOS
|
354
|
+
E_DPR_FIND_BY_LOGIN_METHOD = <<~EOS.squish.freeze
|
355
|
+
find_by_login_method is deprecated in favor of record_selection_method,
|
356
|
+
to avoid confusion with ActiveRecord's "Dynamic Finders".
|
357
|
+
(https://guides.rubyonrails.org/v6.0/active_record_querying.html#dynamic-finders)
|
358
|
+
For example, rubocop-rails is confused by the deprecated method.
|
359
|
+
(https://github.com/rubocop-hq/rubocop-rails/blob/master/lib/rubocop/cop/rails/dynamic_find_by.rb)
|
360
|
+
EOS
|
354
361
|
VALID_SAME_SITE_VALUES = [nil, "Lax", "Strict", "None"].freeze
|
355
362
|
|
356
363
|
# Callbacks
|
@@ -417,6 +424,7 @@ module Authlogic
|
|
417
424
|
after_save :reset_perishable_token!
|
418
425
|
after_save :save_cookie, if: :cookie_enabled?
|
419
426
|
after_save :update_session
|
427
|
+
after_create :renew_session_id
|
420
428
|
|
421
429
|
after_destroy :destroy_cookie, if: :cookie_enabled?
|
422
430
|
after_destroy :update_session
|
@@ -663,35 +671,10 @@ module Authlogic
|
|
663
671
|
end
|
664
672
|
end
|
665
673
|
|
666
|
-
#
|
667
|
-
# validation is actually finding the user and making sure it exists.
|
668
|
-
# What method it uses the do this is up to you.
|
669
|
-
#
|
670
|
-
# Let's say you have a UserSession that is authenticating a User. By
|
671
|
-
# default UserSession will call User.find_by_login(login). You can
|
672
|
-
# change what method UserSession calls by specifying it here. Then in
|
673
|
-
# your User model you can make that method do anything you want, giving
|
674
|
-
# you complete control of how users are found by the UserSession.
|
675
|
-
#
|
676
|
-
# Let's take an example: You want to allow users to login by username or
|
677
|
-
# email. Set this to the name of the class method that does this in the
|
678
|
-
# User model. Let's call it "find_by_username_or_email"
|
679
|
-
#
|
680
|
-
# class User < ActiveRecord::Base
|
681
|
-
# def self.find_by_username_or_email(login)
|
682
|
-
# find_by_username(login) || find_by_email(login)
|
683
|
-
# end
|
684
|
-
# end
|
685
|
-
#
|
686
|
-
# Now just specify the name of this method for this configuration option
|
687
|
-
# and you are all set. You can do anything you want here. Maybe you
|
688
|
-
# allow users to have multiple logins and you want to search a has_many
|
689
|
-
# relationship, etc. The sky is the limit.
|
690
|
-
#
|
691
|
-
# * <tt>Default:</tt> "find_by_smart_case_login_field"
|
692
|
-
# * <tt>Accepts:</tt> Symbol or String
|
674
|
+
# @deprecated in favor of record_selection_method
|
693
675
|
def find_by_login_method(value = nil)
|
694
|
-
|
676
|
+
::ActiveSupport::Deprecation.warn(E_DPR_FIND_BY_LOGIN_METHOD)
|
677
|
+
record_selection_method(value)
|
695
678
|
end
|
696
679
|
alias find_by_login_method= find_by_login_method
|
697
680
|
|
@@ -776,15 +759,23 @@ module Authlogic
|
|
776
759
|
# example, the UserSession class will authenticate with the User class
|
777
760
|
# unless you specify otherwise in your configuration. See
|
778
761
|
# authenticate_with for information on how to change this value.
|
762
|
+
#
|
763
|
+
# @api public
|
779
764
|
def klass
|
780
765
|
@klass ||= klass_name ? klass_name.constantize : nil
|
781
766
|
end
|
782
767
|
|
783
|
-
# The
|
768
|
+
# The model name, guessed from the session class name, e.g. "User",
|
769
|
+
# from "UserSession".
|
770
|
+
#
|
771
|
+
# TODO: This method can return nil. We should explore this. It seems
|
772
|
+
# likely to cause a NoMethodError later, so perhaps we should raise an
|
773
|
+
# error instead.
|
774
|
+
#
|
775
|
+
# @api private
|
784
776
|
def klass_name
|
785
|
-
return @klass_name if
|
786
|
-
@klass_name = name.scan(/(.*)Session/)[0]
|
787
|
-
@klass_name = klass_name ? klass_name[0] : nil
|
777
|
+
return @klass_name if instance_variable_defined?(:@klass_name)
|
778
|
+
@klass_name = name.scan(/(.*)Session/)[0]&.first
|
788
779
|
end
|
789
780
|
|
790
781
|
# The name of the method you want Authlogic to create for storing the
|
@@ -792,8 +783,8 @@ module Authlogic
|
|
792
783
|
# Authlogic::Session, if you want it can be something completely
|
793
784
|
# different than the field in your model. So if you wanted people to
|
794
785
|
# login with a field called "login" and then find users by email this is
|
795
|
-
# completely doable. See the
|
796
|
-
# for
|
786
|
+
# completely doable. See the `record_selection_method` configuration
|
787
|
+
# option for details.
|
797
788
|
#
|
798
789
|
# * <tt>Default:</tt> klass.login_field || klass.email_field
|
799
790
|
# * <tt>Accepts:</tt> Symbol or String
|
@@ -876,6 +867,47 @@ module Authlogic
|
|
876
867
|
end
|
877
868
|
alias password_field= password_field
|
878
869
|
|
870
|
+
# Authlogic tries to validate the credentials passed to it. One part of
|
871
|
+
# validation is actually finding the user and making sure it exists.
|
872
|
+
# What method it uses the do this is up to you.
|
873
|
+
#
|
874
|
+
# ```
|
875
|
+
# # user_session.rb
|
876
|
+
# record_selection_method :find_by_email
|
877
|
+
# ```
|
878
|
+
#
|
879
|
+
# This is the recommended way to find the user by email address.
|
880
|
+
# The resulting query will be `User.find_by_email(send(login_field))`.
|
881
|
+
# (`login_field` will fall back to `email_field` if there's no `login`
|
882
|
+
# or `username` column).
|
883
|
+
#
|
884
|
+
# In your User model you can make that method do anything you want,
|
885
|
+
# giving you complete control of how users are found by the UserSession.
|
886
|
+
#
|
887
|
+
# Let's take an example: You want to allow users to login by username or
|
888
|
+
# email. Set this to the name of the class method that does this in the
|
889
|
+
# User model. Let's call it "find_by_username_or_email"
|
890
|
+
#
|
891
|
+
# ```
|
892
|
+
# class User < ActiveRecord::Base
|
893
|
+
# def self.find_by_username_or_email(login)
|
894
|
+
# find_by_username(login) || find_by_email(login)
|
895
|
+
# end
|
896
|
+
# end
|
897
|
+
# ```
|
898
|
+
#
|
899
|
+
# Now just specify the name of this method for this configuration option
|
900
|
+
# and you are all set. You can do anything you want here. Maybe you
|
901
|
+
# allow users to have multiple logins and you want to search a has_many
|
902
|
+
# relationship, etc. The sky is the limit.
|
903
|
+
#
|
904
|
+
# * <tt>Default:</tt> "find_by_smart_case_login_field"
|
905
|
+
# * <tt>Accepts:</tt> Symbol or String
|
906
|
+
def record_selection_method(value = nil)
|
907
|
+
rw_config(:record_selection_method, value, "find_by_smart_case_login_field")
|
908
|
+
end
|
909
|
+
alias record_selection_method= record_selection_method
|
910
|
+
|
879
911
|
# Whether or not to request HTTP authentication
|
880
912
|
#
|
881
913
|
# If set to true and no HTTP authentication credentials are sent with
|
@@ -945,6 +977,16 @@ module Authlogic
|
|
945
977
|
end
|
946
978
|
alias secure= secure
|
947
979
|
|
980
|
+
# Should the Rack session ID be reset after authentication, to protect
|
981
|
+
# against Session Fixation attacks?
|
982
|
+
#
|
983
|
+
# * <tt>Default:</tt> true
|
984
|
+
# * <tt>Accepts:</tt> Boolean
|
985
|
+
def session_fixation_defense(value = nil)
|
986
|
+
rw_config(:session_fixation_defense, value, true)
|
987
|
+
end
|
988
|
+
alias session_fixation_defense= session_fixation_defense
|
989
|
+
|
948
990
|
# Should the cookie be signed? If the controller adapter supports it, this is a
|
949
991
|
# measure against cookie tampering.
|
950
992
|
def sign_cookie(value = nil)
|
@@ -1650,6 +1692,13 @@ module Authlogic
|
|
1650
1692
|
define_password_field_methods
|
1651
1693
|
end
|
1652
1694
|
|
1695
|
+
# Assign a new controller-session ID, to defend against Session Fixation.
|
1696
|
+
# https://guides.rubyonrails.org/v6.0/security.html#session-fixation
|
1697
|
+
def renew_session_id
|
1698
|
+
return unless self.class.session_fixation_defense
|
1699
|
+
controller.renew_session_id
|
1700
|
+
end
|
1701
|
+
|
1653
1702
|
def define_login_field_methods
|
1654
1703
|
return unless login_field
|
1655
1704
|
self.class.send(:attr_writer, login_field) unless respond_to?("#{login_field}=")
|
@@ -1740,8 +1789,10 @@ module Authlogic
|
|
1740
1789
|
attempted_record.failed_login_count >= consecutive_failed_logins_limit
|
1741
1790
|
end
|
1742
1791
|
|
1792
|
+
# @deprecated in favor of `self.class.record_selection_method`
|
1743
1793
|
def find_by_login_method
|
1744
|
-
|
1794
|
+
::ActiveSupport::Deprecation.warn(E_DPR_FIND_BY_LOGIN_METHOD)
|
1795
|
+
self.class.record_selection_method
|
1745
1796
|
end
|
1746
1797
|
|
1747
1798
|
def generalize_credentials_error_messages?
|
@@ -1795,7 +1846,7 @@ module Authlogic
|
|
1795
1846
|
end
|
1796
1847
|
end
|
1797
1848
|
|
1798
|
-
def
|
1849
|
+
def increment_login_count
|
1799
1850
|
if record.respond_to?(:login_count)
|
1800
1851
|
record.login_count = (record.login_count.blank? ? 1 : record.login_count + 1)
|
1801
1852
|
end
|
@@ -1980,7 +2031,7 @@ module Authlogic
|
|
1980
2031
|
|
1981
2032
|
# @api private
|
1982
2033
|
def set_last_request_at
|
1983
|
-
current_time =
|
2034
|
+
current_time = Time.current
|
1984
2035
|
MagicColumn::AssignsLastRequestAt
|
1985
2036
|
.new(current_time, record, controller, last_request_at_threshold)
|
1986
2037
|
.assign
|
@@ -2025,7 +2076,7 @@ module Authlogic
|
|
2025
2076
|
end
|
2026
2077
|
|
2027
2078
|
def update_info
|
2028
|
-
|
2079
|
+
increment_login_count
|
2029
2080
|
clear_failed_login_count
|
2030
2081
|
update_login_timestamps
|
2031
2082
|
update_login_ip_addresses
|
@@ -2041,7 +2092,7 @@ module Authlogic
|
|
2041
2092
|
def update_login_timestamps
|
2042
2093
|
if record.respond_to?(:current_login_at)
|
2043
2094
|
record.last_login_at = record.current_login_at if record.respond_to?(:last_login_at)
|
2044
|
-
record.current_login_at =
|
2095
|
+
record.current_login_at = Time.current
|
2045
2096
|
end
|
2046
2097
|
end
|
2047
2098
|
|
@@ -2072,7 +2123,10 @@ module Authlogic
|
|
2072
2123
|
self.invalid_password = false
|
2073
2124
|
validate_by_password__blank_fields
|
2074
2125
|
return if errors.count > 0
|
2075
|
-
self.attempted_record = search_for_record(
|
2126
|
+
self.attempted_record = search_for_record(
|
2127
|
+
self.class.record_selection_method,
|
2128
|
+
send(login_field)
|
2129
|
+
)
|
2076
2130
|
if attempted_record.blank?
|
2077
2131
|
add_login_not_found_error
|
2078
2132
|
return
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module Authlogic
|
4
4
|
module TestCase
|
5
5
|
# A mock of `ActionDispatch::Cookies::CookieJar`.
|
6
|
+
# See action_dispatch/middleware/cookies.rb
|
6
7
|
class MockCookieJar < Hash # :nodoc:
|
7
8
|
attr_accessor :set_cookies
|
8
9
|
|
@@ -11,9 +12,12 @@ module Authlogic
|
|
11
12
|
hash && hash[:value]
|
12
13
|
end
|
13
14
|
|
15
|
+
# @param options - "the cookie's value [usually a string] or a hash of
|
16
|
+
# options as documented above [in action_dispatch/middleware/cookies.rb]"
|
14
17
|
def []=(key, options)
|
15
|
-
|
16
|
-
|
18
|
+
opt = cookie_options_to_hash(options)
|
19
|
+
(@set_cookies ||= {})[key.to_s] = opt
|
20
|
+
super(key, opt)
|
17
21
|
end
|
18
22
|
|
19
23
|
def delete(key, _options = {})
|
@@ -27,6 +31,17 @@ module Authlogic
|
|
27
31
|
def encrypted
|
28
32
|
@encrypted ||= MockEncryptedCookieJar.new(self)
|
29
33
|
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def cookie_options_to_hash(options)
|
39
|
+
if options.is_a?(Hash)
|
40
|
+
options
|
41
|
+
else
|
42
|
+
{ value: options }
|
43
|
+
end
|
44
|
+
end
|
30
45
|
end
|
31
46
|
|
32
47
|
# A mock of `ActionDispatch::Cookies::SignedKeyRotatingCookieJar`
|
@@ -52,8 +67,9 @@ module Authlogic
|
|
52
67
|
end
|
53
68
|
|
54
69
|
def []=(key, options)
|
55
|
-
|
56
|
-
|
70
|
+
opt = cookie_options_to_hash(options)
|
71
|
+
opt[:value] = "#{opt[:value]}--#{Digest::SHA1.hexdigest opt[:value]}"
|
72
|
+
@parent_jar[key] = opt
|
57
73
|
end
|
58
74
|
end
|
59
75
|
|
@@ -75,8 +91,9 @@ module Authlogic
|
|
75
91
|
end
|
76
92
|
|
77
93
|
def []=(key, options)
|
78
|
-
|
79
|
-
|
94
|
+
opt = cookie_options_to_hash(options)
|
95
|
+
opt[:value] = self.class.encrypt(opt[:value])
|
96
|
+
@parent_jar[key] = opt
|
80
97
|
end
|
81
98
|
|
82
99
|
# simple caesar cipher for testing
|
@@ -9,6 +9,12 @@ module Authlogic
|
|
9
9
|
self.controller = controller
|
10
10
|
end
|
11
11
|
|
12
|
+
def env
|
13
|
+
@env ||= {
|
14
|
+
ControllerAdapters::AbstractAdapter::ENV_SESSION_OPTIONS => {}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
12
18
|
def format
|
13
19
|
controller.request_content_type if controller.respond_to? :request_content_type
|
14
20
|
end
|
@@ -12,7 +12,7 @@ module Authlogic
|
|
12
12
|
def cookies
|
13
13
|
new_cookies = MockCookieJar.new
|
14
14
|
super.each do |key, value|
|
15
|
-
new_cookies[key] = value
|
15
|
+
new_cookies[key] = cookie_value(value)
|
16
16
|
end
|
17
17
|
new_cookies
|
18
18
|
end
|
@@ -28,6 +28,12 @@ module Authlogic
|
|
28
28
|
def request_content_type
|
29
29
|
request.format.to_s
|
30
30
|
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def cookie_value(value)
|
35
|
+
value.is_a?(Hash) ? value[:value] : value
|
36
|
+
end
|
31
37
|
end
|
32
38
|
end
|
33
39
|
end
|
data/lib/authlogic/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: authlogic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.2
|
4
|
+
version: 6.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Johnson
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2021-12-21 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activemodel
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '5.2'
|
22
22
|
- - "<"
|
23
23
|
- !ruby/object:Gem::Version
|
24
|
-
version: '
|
24
|
+
version: '7.1'
|
25
25
|
type: :runtime
|
26
26
|
prerelease: false
|
27
27
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -31,7 +31,7 @@ dependencies:
|
|
31
31
|
version: '5.2'
|
32
32
|
- - "<"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '
|
34
|
+
version: '7.1'
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: activerecord
|
37
37
|
requirement: !ruby/object:Gem::Requirement
|
@@ -41,7 +41,7 @@ dependencies:
|
|
41
41
|
version: '5.2'
|
42
42
|
- - "<"
|
43
43
|
- !ruby/object:Gem::Version
|
44
|
-
version: '
|
44
|
+
version: '7.1'
|
45
45
|
type: :runtime
|
46
46
|
prerelease: false
|
47
47
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -51,7 +51,7 @@ dependencies:
|
|
51
51
|
version: '5.2'
|
52
52
|
- - "<"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '7.1'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: activesupport
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -61,7 +61,7 @@ dependencies:
|
|
61
61
|
version: '5.2'
|
62
62
|
- - "<"
|
63
63
|
- !ruby/object:Gem::Version
|
64
|
-
version: '
|
64
|
+
version: '7.1'
|
65
65
|
type: :runtime
|
66
66
|
prerelease: false
|
67
67
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -71,7 +71,7 @@ dependencies:
|
|
71
71
|
version: '5.2'
|
72
72
|
- - "<"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: '
|
74
|
+
version: '7.1'
|
75
75
|
- !ruby/object:Gem::Dependency
|
76
76
|
name: request_store
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -170,6 +170,20 @@ dependencies:
|
|
170
170
|
- - "~>"
|
171
171
|
- !ruby/object:Gem::Version
|
172
172
|
version: 1.1.4
|
173
|
+
- !ruby/object:Gem::Dependency
|
174
|
+
name: rake
|
175
|
+
requirement: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - "~>"
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '13.0'
|
180
|
+
type: :development
|
181
|
+
prerelease: false
|
182
|
+
version_requirements: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - "~>"
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '13.0'
|
173
187
|
- !ruby/object:Gem::Dependency
|
174
188
|
name: rubocop
|
175
189
|
requirement: !ruby/object:Gem::Requirement
|
@@ -327,7 +341,7 @@ files:
|
|
327
341
|
- lib/authlogic/test_case/mock_request.rb
|
328
342
|
- lib/authlogic/test_case/rails_request_adapter.rb
|
329
343
|
- lib/authlogic/version.rb
|
330
|
-
homepage:
|
344
|
+
homepage: https://github.com/binarylogic/authlogic
|
331
345
|
licenses:
|
332
346
|
- MIT
|
333
347
|
metadata: {}
|
@@ -339,7 +353,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
339
353
|
requirements:
|
340
354
|
- - ">="
|
341
355
|
- !ruby/object:Gem::Version
|
342
|
-
version: 2.
|
356
|
+
version: 2.6.0
|
343
357
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
344
358
|
requirements:
|
345
359
|
- - ">="
|