authentication-logic 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.
- checksums.yaml +7 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/lib/auth/logic/acts_as_authentic/base.rb +118 -0
- data/lib/auth/logic/acts_as_authentic/email.rb +32 -0
- data/lib/auth/logic/acts_as_authentic/logged_in_status.rb +87 -0
- data/lib/auth/logic/acts_as_authentic/login.rb +65 -0
- data/lib/auth/logic/acts_as_authentic/magic_columns.rb +40 -0
- data/lib/auth/logic/acts_as_authentic/password.rb +362 -0
- data/lib/auth/logic/acts_as_authentic/perishable_token.rb +125 -0
- data/lib/auth/logic/acts_as_authentic/persistence_token.rb +72 -0
- data/lib/auth/logic/acts_as_authentic/queries/case_sensitivity.rb +55 -0
- data/lib/auth/logic/acts_as_authentic/queries/find_with_case.rb +85 -0
- data/lib/auth/logic/acts_as_authentic/session_maintenance.rb +189 -0
- data/lib/auth/logic/acts_as_authentic/single_access_token.rb +85 -0
- data/lib/auth/logic/config.rb +41 -0
- data/lib/auth/logic/controller_adapters/abstract_adapter.rb +121 -0
- data/lib/auth/logic/controller_adapters/rack_adapter.rb +74 -0
- data/lib/auth/logic/controller_adapters/rails_adapter.rb +49 -0
- data/lib/auth/logic/controller_adapters/sinatra_adapter.rb +69 -0
- data/lib/auth/logic/cookie_credentials.rb +65 -0
- data/lib/auth/logic/crypto_providers/bcrypt.rb +116 -0
- data/lib/auth/logic/crypto_providers/md5/v2.rb +37 -0
- data/lib/auth/logic/crypto_providers/md5.rb +38 -0
- data/lib/auth/logic/crypto_providers/scrypt.rb +96 -0
- data/lib/auth/logic/crypto_providers/sha1/v2.rb +42 -0
- data/lib/auth/logic/crypto_providers/sha1.rb +43 -0
- data/lib/auth/logic/crypto_providers/sha256/v2.rb +60 -0
- data/lib/auth/logic/crypto_providers/sha256.rb +61 -0
- data/lib/auth/logic/crypto_providers/sha512/v2.rb +41 -0
- data/lib/auth/logic/crypto_providers/sha512.rb +40 -0
- data/lib/auth/logic/crypto_providers.rb +89 -0
- data/lib/auth/logic/errors.rb +52 -0
- data/lib/auth/logic/i18n/translator.rb +20 -0
- data/lib/auth/logic/i18n.rb +100 -0
- data/lib/auth/logic/random.rb +18 -0
- data/lib/auth/logic/session/base.rb +2205 -0
- data/lib/auth/logic/session/magic_column/assigns_last_request_at.rb +49 -0
- data/lib/auth/logic/test_case/mock_api_controller.rb +53 -0
- data/lib/auth/logic/test_case/mock_controller.rb +59 -0
- data/lib/auth/logic/test_case/mock_cookie_jar.rb +112 -0
- data/lib/auth/logic/test_case/mock_logger.rb +14 -0
- data/lib/auth/logic/test_case/mock_request.rb +36 -0
- data/lib/auth/logic/test_case/rails_request_adapter.rb +40 -0
- data/lib/auth/logic/test_case.rb +216 -0
- data/lib/auth/logic/version.rb +7 -0
- data/lib/auth/logic.rb +46 -0
- metadata +426 -0
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authentication
|
4
|
+
module Logic
|
5
|
+
module ActsAsAuthentic
|
6
|
+
# This is one of my favorite features that I think is pretty cool. It's
|
7
|
+
# things like this that make a library great and let you know you are on the
|
8
|
+
# right track.
|
9
|
+
#
|
10
|
+
# Just to clear up any confusion, Authentication::Logic stores both the record id and
|
11
|
+
# the persistence token in the session. Why? So stale sessions can not be
|
12
|
+
# persisted. It stores the id so it can quickly find the record, and the
|
13
|
+
# persistence token to ensure no sessions are stale. So if the persistence
|
14
|
+
# token changes, the user must log back in.
|
15
|
+
#
|
16
|
+
# Well, the persistence token changes with the password. What happens if the
|
17
|
+
# user changes his own password? He shouldn't have to log back in, he's the
|
18
|
+
# one that made the change.
|
19
|
+
#
|
20
|
+
# That being said, wouldn't it be nice if their session and cookie
|
21
|
+
# information was automatically updated? Instead of cluttering up your
|
22
|
+
# controller with redundant session code. The same thing goes for new
|
23
|
+
# registrations.
|
24
|
+
#
|
25
|
+
# That's what this module is all about. This will automatically maintain the
|
26
|
+
# cookie and session values as records are saved.
|
27
|
+
module SessionMaintenance
|
28
|
+
def self.included(klass)
|
29
|
+
klass.class_eval do
|
30
|
+
extend Config
|
31
|
+
add_acts_as_authentic_module(Methods)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Configuration for the session maintenance aspect of acts_as_authentic.
|
36
|
+
# These methods become class methods of ::ActiveRecord::Base.
|
37
|
+
module Config
|
38
|
+
# In order to turn off automatic maintenance of sessions
|
39
|
+
# after create, just set this to false.
|
40
|
+
#
|
41
|
+
# * <tt>Default:</tt> true
|
42
|
+
# * <tt>Accepts:</tt> Boolean
|
43
|
+
def log_in_after_create(value = nil)
|
44
|
+
rw_config(:log_in_after_create, value, true)
|
45
|
+
end
|
46
|
+
alias log_in_after_create= log_in_after_create
|
47
|
+
|
48
|
+
# In order to turn off automatic maintenance of sessions when updating
|
49
|
+
# the password, just set this to false.
|
50
|
+
#
|
51
|
+
# * <tt>Default:</tt> true
|
52
|
+
# * <tt>Accepts:</tt> Boolean
|
53
|
+
def log_in_after_password_change(value = nil)
|
54
|
+
rw_config(:log_in_after_password_change, value, true)
|
55
|
+
end
|
56
|
+
alias log_in_after_password_change= log_in_after_password_change
|
57
|
+
|
58
|
+
# As you may know, auth-logic sessions can be separate by id (See
|
59
|
+
# Authentication::Logic::Session::Base#id). You can specify here what session ids
|
60
|
+
# you want auto maintained. By default it is the main session, which has
|
61
|
+
# an id of nil.
|
62
|
+
#
|
63
|
+
# * <tt>Default:</tt> [nil]
|
64
|
+
# * <tt>Accepts:</tt> Array
|
65
|
+
def session_ids(value = nil)
|
66
|
+
rw_config(:session_ids, value, [nil])
|
67
|
+
end
|
68
|
+
alias session_ids= session_ids
|
69
|
+
|
70
|
+
# The name of the associated session class. This is inferred by the name
|
71
|
+
# of the model.
|
72
|
+
#
|
73
|
+
# * <tt>Default:</tt> "#{klass.name}Session".constantize
|
74
|
+
# * <tt>Accepts:</tt> Class
|
75
|
+
def session_class(value = nil)
|
76
|
+
const = begin
|
77
|
+
"#{base_class.name}Session".constantize
|
78
|
+
rescue NameError
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
rw_config(:session_class, value, const)
|
82
|
+
end
|
83
|
+
alias session_class= session_class
|
84
|
+
end
|
85
|
+
|
86
|
+
# This module, as one of the `acts_as_authentic_modules`, is only included
|
87
|
+
# into an ActiveRecord model if that model calls `acts_as_authentic`.
|
88
|
+
module Methods
|
89
|
+
def self.included(klass)
|
90
|
+
klass.class_eval do
|
91
|
+
before_save :get_session_information, if: :update_sessions?
|
92
|
+
before_save :maintain_sessions, if: :update_sessions?
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Save the record and skip session maintenance all together.
|
97
|
+
def save_without_session_maintenance(**options)
|
98
|
+
self.skip_session_maintenance = true
|
99
|
+
result = save(**options)
|
100
|
+
self.skip_session_maintenance = false
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def skip_session_maintenance=(value)
|
107
|
+
@skip_session_maintenance = value
|
108
|
+
end
|
109
|
+
|
110
|
+
def skip_session_maintenance
|
111
|
+
@skip_session_maintenance ||= false
|
112
|
+
end
|
113
|
+
|
114
|
+
def update_sessions?
|
115
|
+
!skip_session_maintenance &&
|
116
|
+
session_class &&
|
117
|
+
session_class.activated? &&
|
118
|
+
maintain_session? &&
|
119
|
+
!session_ids.blank? &&
|
120
|
+
will_save_change_to_persistence_token?
|
121
|
+
end
|
122
|
+
|
123
|
+
def maintain_session?
|
124
|
+
log_in_after_create? || log_in_after_password_change?
|
125
|
+
end
|
126
|
+
|
127
|
+
def get_session_information
|
128
|
+
# Need to determine if we are completely logged out, or logged in as
|
129
|
+
# another user.
|
130
|
+
@_sessions = []
|
131
|
+
|
132
|
+
session_ids.each do |session_id|
|
133
|
+
session = session_class.find(session_id, self)
|
134
|
+
@_sessions << session if session&.record
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def maintain_sessions
|
139
|
+
if @_sessions.empty?
|
140
|
+
create_session
|
141
|
+
else
|
142
|
+
update_sessions
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def create_session
|
147
|
+
# We only want to automatically login into the first session, since
|
148
|
+
# this is the main session. The other sessions are sessions that
|
149
|
+
# need to be created after logging into the main session.
|
150
|
+
session_id = session_ids.first
|
151
|
+
session_class.create(*[self, self, session_id].compact)
|
152
|
+
|
153
|
+
true
|
154
|
+
end
|
155
|
+
|
156
|
+
def update_sessions
|
157
|
+
# We found sessions above, let's update them with the new info
|
158
|
+
@_sessions.each do |stale_session|
|
159
|
+
next if stale_session.record != self
|
160
|
+
|
161
|
+
stale_session.unauthorized_record = self
|
162
|
+
stale_session.save
|
163
|
+
end
|
164
|
+
|
165
|
+
true
|
166
|
+
end
|
167
|
+
|
168
|
+
def session_ids
|
169
|
+
self.class.session_ids
|
170
|
+
end
|
171
|
+
|
172
|
+
def session_class
|
173
|
+
self.class.session_class
|
174
|
+
end
|
175
|
+
|
176
|
+
def log_in_after_create?
|
177
|
+
new_record? && self.class.log_in_after_create
|
178
|
+
end
|
179
|
+
|
180
|
+
def log_in_after_password_change?
|
181
|
+
persisted? &&
|
182
|
+
will_save_change_to_persistence_token? &&
|
183
|
+
self.class.log_in_after_password_change
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authentication
|
4
|
+
module Logic
|
5
|
+
module ActsAsAuthentic
|
6
|
+
# This module is responsible for maintaining the single_access token. For
|
7
|
+
# more information the single access token and how to use it, see "Params"
|
8
|
+
# in `Session::Base`.
|
9
|
+
module SingleAccessToken
|
10
|
+
def self.included(klass)
|
11
|
+
klass.class_eval do
|
12
|
+
extend Config
|
13
|
+
add_acts_as_authentic_module(Methods)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# All configuration for the single_access token aspect of acts_as_authentic.
|
18
|
+
#
|
19
|
+
# These methods become class methods of ::ActiveRecord::Base.
|
20
|
+
module Config
|
21
|
+
# The single access token is used for authentication via URLs, such as a private
|
22
|
+
# feed. That being said, if the user changes their password, that token probably
|
23
|
+
# shouldn't change. If it did, the user would have to update all of their URLs. So
|
24
|
+
# be default this is option is disabled, if you need it, feel free to turn it on.
|
25
|
+
#
|
26
|
+
# * <tt>Default:</tt> false
|
27
|
+
# * <tt>Accepts:</tt> Boolean
|
28
|
+
def change_single_access_token_with_password(value = nil)
|
29
|
+
rw_config(:change_single_access_token_with_password, value, false)
|
30
|
+
end
|
31
|
+
alias change_single_access_token_with_password= change_single_access_token_with_password
|
32
|
+
end
|
33
|
+
|
34
|
+
# All method, for the single_access token aspect of acts_as_authentic.
|
35
|
+
#
|
36
|
+
# This module, as one of the `acts_as_authentic_modules`, is only included
|
37
|
+
# into an ActiveRecord model if that model calls `acts_as_authentic`.
|
38
|
+
module Methods
|
39
|
+
def self.included(klass)
|
40
|
+
return unless klass.column_names.include?("single_access_token")
|
41
|
+
|
42
|
+
klass.class_eval do
|
43
|
+
include InstanceMethods
|
44
|
+
validates_uniqueness_of :single_access_token,
|
45
|
+
case_sensitive: true,
|
46
|
+
if: :will_save_change_to_single_access_token?
|
47
|
+
|
48
|
+
before_validation :reset_single_access_token, if: :reset_single_access_token?
|
49
|
+
if respond_to?(:after_password_set)
|
50
|
+
after_password_set(
|
51
|
+
:reset_single_access_token,
|
52
|
+
if: :change_single_access_token_with_password?
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# :nodoc:
|
59
|
+
module InstanceMethods
|
60
|
+
# Resets the single_access_token to a random friendly token.
|
61
|
+
def reset_single_access_token
|
62
|
+
self.single_access_token = Authentication::Logic::Random.friendly_token
|
63
|
+
end
|
64
|
+
|
65
|
+
# same as reset_single_access_token, but then saves the record.
|
66
|
+
def reset_single_access_token!
|
67
|
+
reset_single_access_token
|
68
|
+
save_without_session_maintenance
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
def reset_single_access_token?
|
74
|
+
single_access_token.blank?
|
75
|
+
end
|
76
|
+
|
77
|
+
def change_single_access_token_with_password?
|
78
|
+
self.class.change_single_access_token_with_password == true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authentication
|
4
|
+
module Logic
|
5
|
+
# Mixed into `Authentication::Logic::ActsAsAuthentic::Base` and
|
6
|
+
# `Authentication::Logic::Session::Base`.
|
7
|
+
module Config
|
8
|
+
E_USE_NORMAL_RAILS_VALIDATION = <<~EOS
|
9
|
+
This Authentication::Logic configuration option (%s) is deprecated. Use normal
|
10
|
+
ActiveRecord validation instead. Detailed instructions:
|
11
|
+
https://github.com/vinccool96/auth-logic/blob/master/doc/use_normal_rails_validation.md
|
12
|
+
EOS
|
13
|
+
|
14
|
+
def self.extended(klass)
|
15
|
+
klass.class_eval do
|
16
|
+
class_attribute :acts_as_authentic_config
|
17
|
+
self.acts_as_authentic_config ||= {}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def deprecate_auth_logic_config(method_name)
|
24
|
+
::ActiveSupport::Deprecation.warn(
|
25
|
+
format(E_USE_NORMAL_RAILS_VALIDATION, method_name)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
# This is a one-liner method to write a config setting, read the config
|
30
|
+
# setting, and also set a default value for the setting.
|
31
|
+
def rw_config(key, value, default_value = nil)
|
32
|
+
if value.nil?
|
33
|
+
acts_as_authentic_config.include?(key) ? acts_as_authentic_config[key] : default_value
|
34
|
+
else
|
35
|
+
self.acts_as_authentic_config = acts_as_authentic_config.merge(key => value)
|
36
|
+
value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authentication
|
4
|
+
module Logic
|
5
|
+
module ControllerAdapters # :nodoc:
|
6
|
+
# Allows you to use Authentication::Logic in any framework you want, not just rails. See
|
7
|
+
# the RailsAdapter for an example of how to adapt Authentication::Logic to work with
|
8
|
+
# your framework.
|
9
|
+
class AbstractAdapter
|
10
|
+
E_COOKIE_DOMAIN_ADAPTER = "The cookie_domain method has not been " \
|
11
|
+
"implemented by the controller adapter"
|
12
|
+
ENV_SESSION_OPTIONS = "rack.session.options"
|
13
|
+
|
14
|
+
attr_accessor :controller
|
15
|
+
|
16
|
+
def initialize(controller)
|
17
|
+
self.controller = controller
|
18
|
+
end
|
19
|
+
|
20
|
+
def authenticate_with_http_basic
|
21
|
+
@auth = Rack::Auth::Basic::Request.new(controller.request.env)
|
22
|
+
if @auth.provided? && @auth.basic?
|
23
|
+
yield(*@auth.credentials)
|
24
|
+
else
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def cookies
|
30
|
+
controller.cookies
|
31
|
+
end
|
32
|
+
|
33
|
+
def cookie_domain
|
34
|
+
raise NotImplementedError, E_COOKIE_DOMAIN_ADAPTER
|
35
|
+
end
|
36
|
+
|
37
|
+
def params
|
38
|
+
controller.params
|
39
|
+
end
|
40
|
+
|
41
|
+
def request
|
42
|
+
controller.request
|
43
|
+
end
|
44
|
+
|
45
|
+
def request_content_type
|
46
|
+
request.content_type
|
47
|
+
end
|
48
|
+
|
49
|
+
# Inform Rack that we would like a new session ID to be assigned. Changes
|
50
|
+
# the ID, but not the contents of the session.
|
51
|
+
#
|
52
|
+
# The `:renew` option is read by `rack/session/abstract/id.rb`.
|
53
|
+
#
|
54
|
+
# This is how Devise (via warden) implements defense against Session
|
55
|
+
# Fixation. Our implementation is copied directly from the warden gem
|
56
|
+
# (set_user in warden/proxy.rb)
|
57
|
+
def renew_session_id
|
58
|
+
env = request.env
|
59
|
+
options = env[ENV_SESSION_OPTIONS]
|
60
|
+
return unless options
|
61
|
+
|
62
|
+
if options.frozen?
|
63
|
+
env[ENV_SESSION_OPTIONS] = options.merge(renew: true).freeze
|
64
|
+
else
|
65
|
+
options[:renew] = true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def session
|
70
|
+
controller.session
|
71
|
+
end
|
72
|
+
|
73
|
+
def responds_to_single_access_allowed?
|
74
|
+
controller.respond_to?(:single_access_allowed?, true)
|
75
|
+
end
|
76
|
+
|
77
|
+
def single_access_allowed?
|
78
|
+
controller.send(:single_access_allowed?)
|
79
|
+
end
|
80
|
+
|
81
|
+
# You can disable the updating of `last_request_at`
|
82
|
+
# on a per-controller basis.
|
83
|
+
#
|
84
|
+
# # in your controller
|
85
|
+
# def last_request_update_allowed?
|
86
|
+
# false
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# For example, what if you had a javascript function that polled the
|
90
|
+
# server updating how much time is left in their session before it
|
91
|
+
# times out. Obviously you would want to ignore this request, because
|
92
|
+
# then the user would never time out. So you can do something like
|
93
|
+
# this in your controller:
|
94
|
+
#
|
95
|
+
# def last_request_update_allowed?
|
96
|
+
# action_name != "update_session_time_left"
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# See `auth/logic/session/magic_columns.rb` to learn more about the
|
100
|
+
# `last_request_at` column itself.
|
101
|
+
def last_request_update_allowed?
|
102
|
+
if controller.respond_to?(:last_request_update_allowed?, true)
|
103
|
+
controller.send(:last_request_update_allowed?)
|
104
|
+
else
|
105
|
+
true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def respond_to_missing?(*args)
|
110
|
+
super(*args) || controller.respond_to?(*args)
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def method_missing(id, *args, &block)
|
116
|
+
controller.send(id, *args, &block)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authentication
|
4
|
+
module Logic
|
5
|
+
module ControllerAdapters
|
6
|
+
# Adapter for auth-logic to make it function as a Rack middleware.
|
7
|
+
# First you'll have write your own Rack adapter where you have to set your cookie domain.
|
8
|
+
#
|
9
|
+
# class YourRackAdapter < Authentication::Logic::ControllerAdapters::RackAdapter
|
10
|
+
# def cookie_domain
|
11
|
+
# 'your_cookie_domain_here.com'
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# Next you need to set up a rack middleware like this:
|
16
|
+
#
|
17
|
+
# class Authentication::LogicMiddleware
|
18
|
+
# def initialize(app)
|
19
|
+
# @app = app
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# def call(env)
|
23
|
+
# YourRackAdapter.new(env)
|
24
|
+
# @app.call(env)
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# And that is all! Now just load this middleware into rack:
|
29
|
+
#
|
30
|
+
# use Authentication::LogicMiddleware
|
31
|
+
#
|
32
|
+
# Authentication::Logic will expect a User and a UserSession object to be present:
|
33
|
+
#
|
34
|
+
# class UserSession < Authentication::Logic::Session::Base
|
35
|
+
# # Authentication::Logic options go here
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# class User < ApplicationRecord
|
39
|
+
# acts_as_authentic
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
class RackAdapter < AbstractAdapter
|
43
|
+
def initialize(env)
|
44
|
+
# We use the Rack::Request object as the controller object.
|
45
|
+
# For this to work, we have to add some glue.
|
46
|
+
request = Rack::Request.new(env)
|
47
|
+
|
48
|
+
request.instance_eval do
|
49
|
+
def request
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def remote_ip
|
54
|
+
ip
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
super(request)
|
59
|
+
Authentication::Logic::Session::Base.controller = self
|
60
|
+
end
|
61
|
+
|
62
|
+
# Rack Requests stores cookies with not just the value, but also with
|
63
|
+
# flags and expire information in the hash. Authentication::Logic does not like this,
|
64
|
+
# so we drop everything except the cookie value.
|
65
|
+
def cookies
|
66
|
+
controller
|
67
|
+
.cookies
|
68
|
+
.map { |key, value_hash| { key => value_hash[:value] } }
|
69
|
+
.inject(:merge) || {}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authentication
|
4
|
+
module Logic
|
5
|
+
module ControllerAdapters
|
6
|
+
# Adapts auth-logic to work with rails. The point is to close the gap between
|
7
|
+
# what auth-logic expects and what the rails controller object provides.
|
8
|
+
# Similar to how ActiveRecord has an adapter for MySQL, PostgreSQL, SQLite,
|
9
|
+
# etc.
|
10
|
+
class RailsAdapter < AbstractAdapter
|
11
|
+
def authenticate_with_http_basic(&block)
|
12
|
+
controller.authenticate_with_http_basic(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns a `ActionDispatch::Cookies::CookieJar`. See the AC guide
|
16
|
+
# http://guides.rubyonrails.org/action_controller_overview.html#cookies
|
17
|
+
def cookies
|
18
|
+
controller.respond_to?(:cookies, true) ? controller.send(:cookies) : nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def cookie_domain
|
22
|
+
controller.request.session_options[:domain]
|
23
|
+
end
|
24
|
+
|
25
|
+
def request_content_type
|
26
|
+
request.format.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
# Lets Authentication::Logic know about the controller object via a before filter, AKA
|
30
|
+
# "activates" auth-logic.
|
31
|
+
module RailsImplementation
|
32
|
+
def self.included(klass) # :nodoc:
|
33
|
+
klass.prepend_before_action :activate_auth_logic
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def activate_auth_logic
|
39
|
+
Authentication::Logic::Session::Base.controller = RailsAdapter.new(self)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
ActiveSupport.on_load(:action_controller) do
|
48
|
+
include Authentication::Logic::ControllerAdapters::RailsAdapter::RailsImplementation
|
49
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Authentication::Logic bridge for Sinatra
|
4
|
+
module Authentication
|
5
|
+
module Logic
|
6
|
+
module ControllerAdapters
|
7
|
+
module SinatraAdapter
|
8
|
+
# Cookie management functions
|
9
|
+
class Cookies
|
10
|
+
attr_reader :request, :response
|
11
|
+
|
12
|
+
def initialize(request, response)
|
13
|
+
@request = request
|
14
|
+
@response = response
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(key, options = {})
|
18
|
+
@response.delete_cookie(key, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def []=(key, options)
|
22
|
+
@response.set_cookie(key, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(meth, *args, &block)
|
26
|
+
@request.cookies.send(meth, *args, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Thin wrapper around request and response.
|
31
|
+
class Controller
|
32
|
+
attr_reader :request, :response, :cookies
|
33
|
+
|
34
|
+
def initialize(request, response)
|
35
|
+
@request = request
|
36
|
+
@cookies = Cookies.new(request, response)
|
37
|
+
end
|
38
|
+
|
39
|
+
def session
|
40
|
+
env["rack.session"]
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing(meth, *args, &block)
|
44
|
+
@request.send meth, *args, &block
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Sinatra controller adapter
|
49
|
+
class Adapter < AbstractAdapter
|
50
|
+
def cookie_domain
|
51
|
+
env["SERVER_NAME"]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Mixed into `Sinatra::Base`
|
55
|
+
module Implementation
|
56
|
+
def self.included(klass)
|
57
|
+
klass.send :before do
|
58
|
+
controller = Controller.new(request, response)
|
59
|
+
Authentication::Logic::Session::Base.controller = Adapter.new(controller)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
Sinatra::Base.include Authentication::Logic::ControllerAdapters::SinatraAdapter::Adapter::Implementation
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authentication
|
4
|
+
module Logic
|
5
|
+
# Represents the credentials *in* the cookie. The value of the cookie.
|
6
|
+
# This is primarily a data object. It doesn't interact with controllers.
|
7
|
+
# It doesn't know about eg. cookie expiration.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class CookieCredentials
|
11
|
+
# @api private
|
12
|
+
class ParseError < RuntimeError
|
13
|
+
end
|
14
|
+
|
15
|
+
DELIMITER = "::"
|
16
|
+
|
17
|
+
attr_reader :persistence_token, :record_id, :remember_me_until
|
18
|
+
|
19
|
+
# @api private
|
20
|
+
# @param persistence_token [String]
|
21
|
+
# @param record_id [String, Numeric]
|
22
|
+
# @param remember_me_until [ActiveSupport::TimeWithZone]
|
23
|
+
def initialize(persistence_token, record_id, remember_me_until)
|
24
|
+
@persistence_token = persistence_token
|
25
|
+
@record_id = record_id
|
26
|
+
@remember_me_until = remember_me_until
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
# @api private
|
31
|
+
def parse(string)
|
32
|
+
parts = string.split(DELIMITER)
|
33
|
+
raise ParseError, format("Expected 1..3 parts, got %d", parts.length) unless (1..3).cover?(parts.length)
|
34
|
+
|
35
|
+
new(parts[0], parts[1], parse_time(parts[2]))
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# @api private
|
41
|
+
def parse_time(string)
|
42
|
+
return if string.nil?
|
43
|
+
|
44
|
+
::Time.parse(string)
|
45
|
+
rescue ::ArgumentError => e
|
46
|
+
raise ParseError, format("Found cookie, cannot parse remember_me_until: #{e}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
def remember_me?
|
52
|
+
!@remember_me_until.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
def to_s
|
57
|
+
[
|
58
|
+
@persistence_token,
|
59
|
+
@record_id.to_s,
|
60
|
+
@remember_me_until&.iso8601
|
61
|
+
].compact.join(DELIMITER)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|