pretendest 0.6.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/CHANGELOG.md +58 -0
- data/LICENSE.txt +22 -0
- data/README.md +214 -0
- data/lib/pretendest/version.rb +3 -0
- data/lib/pretendest.rb +92 -0
- metadata +57 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fee5567af38a736c8cae555f4e67af84010934fe0b802a7f1477df325db12cd6
|
|
4
|
+
data.tar.gz: e49b7269760c28557c031e8725a8776be0072c8052eb0013c00d98db486dcc67
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8f6c3d405a29ecc8fc625f3a6e37ca6e527d4013d7bd48fc65a95c3ed92b1c7c81f3b6741eb28b921bac0ef9994beb3e02292c9dd7824c527f356c14483b32bf
|
|
7
|
+
data.tar.gz: 0c5852cba0b01ba96796ec1c1458e21571a6adb6000cd0ea78ddd6f97b89ec93f616a9d780ba2a5fbbe0a7e710837b738254dee64d0d341eaf800a2c3013bc41
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
## 0.6.0 (2025-04-03)
|
|
2
|
+
|
|
3
|
+
- Dropped support for Ruby < 3.2 and Rails < 7.1
|
|
4
|
+
|
|
5
|
+
## 0.5.0 (2023-07-02)
|
|
6
|
+
|
|
7
|
+
- Dropped support for Ruby < 3 and Rails < 6.1
|
|
8
|
+
|
|
9
|
+
## 0.4.0 (2022-01-10)
|
|
10
|
+
|
|
11
|
+
- Dropped support for Ruby < 2.6 and Rails < 5.2
|
|
12
|
+
|
|
13
|
+
## 0.3.4 (2019-01-31)
|
|
14
|
+
|
|
15
|
+
- Fixed error with Action Cable and eager loading
|
|
16
|
+
|
|
17
|
+
## 0.3.3 (2018-09-21)
|
|
18
|
+
|
|
19
|
+
- Added support for Action Cable
|
|
20
|
+
|
|
21
|
+
## 0.3.2 (2018-01-21)
|
|
22
|
+
|
|
23
|
+
- Support for Mongoid `BSON::ObjectId` out of the box
|
|
24
|
+
- Fixed issue with impersonated resource caching
|
|
25
|
+
|
|
26
|
+
## 0.3.1 (2017-06-18)
|
|
27
|
+
|
|
28
|
+
- Fixed `stack level too deep` error
|
|
29
|
+
|
|
30
|
+
## 0.3.0 (2017-06-11)
|
|
31
|
+
|
|
32
|
+
- Fixed compatibility with Clearance
|
|
33
|
+
- Added support for Rails API
|
|
34
|
+
- Added support for custom primary key
|
|
35
|
+
|
|
36
|
+
## 0.2.1 (2016-07-07)
|
|
37
|
+
|
|
38
|
+
- Better error message
|
|
39
|
+
|
|
40
|
+
## 0.2.0 (2016-07-07)
|
|
41
|
+
|
|
42
|
+
- Fixed error when `current_user` is overridden in `ApplicationController`
|
|
43
|
+
|
|
44
|
+
## 0.1.0 (2014-06-23)
|
|
45
|
+
|
|
46
|
+
- Removed need to call `stop_impersonating_user` before signing out
|
|
47
|
+
|
|
48
|
+
## 0.0.3 (2013-03-09)
|
|
49
|
+
|
|
50
|
+
- Added security warning
|
|
51
|
+
|
|
52
|
+
## 0.0.2 (2013-03-09)
|
|
53
|
+
|
|
54
|
+
- Ensure user is logged in
|
|
55
|
+
|
|
56
|
+
## 0.0.1 (2013-03-05)
|
|
57
|
+
|
|
58
|
+
- First release
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013-2025 Andrew Kane
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Pretender
|
|
2
|
+
|
|
3
|
+
As an admin, there are times you want to see exactly what another user sees. Meet Pretender.
|
|
4
|
+
|
|
5
|
+
- Easily switch between users
|
|
6
|
+
- Minimal code changes
|
|
7
|
+
- Plays nicely with Action Cable and auditing tools
|
|
8
|
+
|
|
9
|
+
:boom: [Rock on](https://www.youtube.com/watch?v=SBjQ9tuuTJQ)
|
|
10
|
+
|
|
11
|
+
Pretender is flexible and lightweight - less than 100 lines of code :-)
|
|
12
|
+
|
|
13
|
+
Works with any authentication system - [Devise](https://github.com/plataformatec/devise), [Authlogic](https://github.com/binarylogic/authlogic), and [Sorcery](https://github.com/Sorcery/sorcery) to name a few.
|
|
14
|
+
|
|
15
|
+
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
|
|
16
|
+
|
|
17
|
+
[](https://github.com/enercoop/pretendest/actions)
|
|
18
|
+
|
|
19
|
+
## Pretendest
|
|
20
|
+
|
|
21
|
+
Pretendest is a friendly fork of [Pretender](https://github.com/ankane/pretender), with a handful of extra features:
|
|
22
|
+
- An `#impersonating_user?` helper,
|
|
23
|
+
- An option to impersonate a resource by another resource (e.g. a Client impersonated by an Employee).
|
|
24
|
+
|
|
25
|
+
If you don't need those features, the original Pretender gem is perfectly fine.
|
|
26
|
+
Otherwise, `pretendest` is a drop-in replacement for `pretender`.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
Add this line to your application’s Gemfile:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
gem "pretendest"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
And add this to your `ApplicationController`:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
class ApplicationController < ActionController::Base
|
|
40
|
+
impersonates :user
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## How It Works
|
|
45
|
+
|
|
46
|
+
Sign in as another user with:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
impersonate_user(user)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The `current_user` method now returns the impersonated user.
|
|
53
|
+
|
|
54
|
+
You can access the true user with:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
true_user
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
And stop impersonating with:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
stop_impersonating_user
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Sample Implementation
|
|
67
|
+
|
|
68
|
+
Create a controller
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
class UsersController < ApplicationController
|
|
72
|
+
before_action :require_admin! # your authorization method
|
|
73
|
+
|
|
74
|
+
def index
|
|
75
|
+
@users = User.order(:id)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def impersonate
|
|
79
|
+
user = User.find(params[:id])
|
|
80
|
+
impersonate_user(user)
|
|
81
|
+
redirect_to root_path
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def stop_impersonating
|
|
85
|
+
stop_impersonating_user
|
|
86
|
+
redirect_to root_path
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Add routes
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
resources :users, only: [:index] do
|
|
95
|
+
post :impersonate, on: :member
|
|
96
|
+
post :stop_impersonating, on: :collection
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Create an index view
|
|
101
|
+
|
|
102
|
+
```erb
|
|
103
|
+
<ul>
|
|
104
|
+
<% @users.each do |user| %>
|
|
105
|
+
<li>Sign in as <%= button_to user.name, impersonate_user_path(user), data: {turbo: false} %></li>
|
|
106
|
+
<% end %>
|
|
107
|
+
</ul>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
And show when someone is signed in as another user in your application layout
|
|
111
|
+
|
|
112
|
+
```erb
|
|
113
|
+
<% if impersonating_user? %>
|
|
114
|
+
You (<%= true_user.name %>) are signed in as <%= current_user.name %>
|
|
115
|
+
<%= button_to "Back to admin", stop_impersonating_users_path, data: {turbo: false} %>
|
|
116
|
+
<% end %>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Audits
|
|
120
|
+
|
|
121
|
+
If you keep audit logs with a library like [Audited](https://github.com/collectiveidea/audited), make sure it uses the **true user**.
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
Audited.current_user_method = :true_user
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Action Cable
|
|
128
|
+
|
|
129
|
+
And add this to your `ApplicationCable::Connection`:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
module ApplicationCable
|
|
133
|
+
class Connection < ActionCable::Connection::Base
|
|
134
|
+
identified_by :current_user, :true_user
|
|
135
|
+
impersonates :user
|
|
136
|
+
|
|
137
|
+
def connect
|
|
138
|
+
self.current_user = find_verified_user
|
|
139
|
+
reject_unauthorized_connection unless current_user
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def find_verified_user
|
|
145
|
+
env["warden"].user # for Devise
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The `current_user` method now returns the impersonated user in channels.
|
|
152
|
+
|
|
153
|
+
## Configuration
|
|
154
|
+
|
|
155
|
+
Pretendest is super flexible. You can change the names of methods and even impersonate multiple roles at the same time. Here’s the default configuration.
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
impersonates :user,
|
|
159
|
+
method: :current_user,
|
|
160
|
+
with: ->(id) { User.find_by(id: id) }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Mold it to fit your application.
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
impersonates :account,
|
|
167
|
+
method: :authenticated_account,
|
|
168
|
+
with: ->(id) { EnterpriseAccount.find_by(id: id) }
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
This creates five methods:
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
true_account
|
|
175
|
+
impersonate_account
|
|
176
|
+
stop_impersonating_account
|
|
177
|
+
impersonating_account? # Pretender Plus addition
|
|
178
|
+
account_impersonator # Pretender Plus addition
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Impersonating from another role (Pretender Plus addition)
|
|
182
|
+
|
|
183
|
+
You can impersonate a role from another role. Consider for instance when Employees are allowed to impersonate Clients:
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
impersonates :client,
|
|
187
|
+
impersonator: :employee
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
In that case, when impersonating:
|
|
191
|
+
- `true_client` returns `nil` (because there is no "true" client),
|
|
192
|
+
- `client_impersonator` returns the `current_employee`.
|
|
193
|
+
|
|
194
|
+
## History
|
|
195
|
+
|
|
196
|
+
View the [changelog](https://github.com/enercoop/pretendest/blob/master/CHANGELOG.md)
|
|
197
|
+
|
|
198
|
+
## Contributing
|
|
199
|
+
|
|
200
|
+
Everyone is encouraged to help improve this project. Here are a few ways you can help:
|
|
201
|
+
|
|
202
|
+
- [Report bugs](https://github.com/enercoop/pretendest/issues)
|
|
203
|
+
- Fix bugs and [submit pull requests](https://github.com/enercoop/pretendest/pulls)
|
|
204
|
+
- Write, clarify, or fix documentation
|
|
205
|
+
- Suggest or add new features
|
|
206
|
+
|
|
207
|
+
To get started with development:
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
git clone https://github.com/enercoop/pretendest.git
|
|
211
|
+
cd pretendest
|
|
212
|
+
bundle install
|
|
213
|
+
bundle exec rake test
|
|
214
|
+
```
|
data/lib/pretendest.rb
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# dependencies
|
|
2
|
+
require "active_support"
|
|
3
|
+
|
|
4
|
+
# modules
|
|
5
|
+
require_relative "pretendest/version"
|
|
6
|
+
|
|
7
|
+
module Pretendest
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
|
|
10
|
+
module Methods
|
|
11
|
+
def impersonates(scope = :user, opts = {})
|
|
12
|
+
impersonated_method = opts[:method] || :"current_#{scope}"
|
|
13
|
+
impersonate_with = opts[:with] || proc { |id|
|
|
14
|
+
klass = scope.to_s.classify.constantize
|
|
15
|
+
primary_key = klass.respond_to?(:primary_key) ? klass.primary_key : :id
|
|
16
|
+
klass.find_by(primary_key => id)
|
|
17
|
+
}
|
|
18
|
+
impersonator = opts[:impersonator] || scope
|
|
19
|
+
impersonator_method = opts[:impersonator_method] || :"current_#{impersonator}"
|
|
20
|
+
true_method = :"true_#{scope}"
|
|
21
|
+
session_key = :"impersonated_#{scope}_id"
|
|
22
|
+
impersonated_var = :"@impersonated_#{scope}"
|
|
23
|
+
stop_impersonating_method = :"stop_impersonating_#{scope}"
|
|
24
|
+
|
|
25
|
+
# define methods
|
|
26
|
+
if method_defined?(impersonated_method) || private_method_defined?(impersonated_method)
|
|
27
|
+
alias_method true_method, impersonated_method
|
|
28
|
+
else
|
|
29
|
+
sc = superclass
|
|
30
|
+
define_method true_method do
|
|
31
|
+
# TODO handle private methods
|
|
32
|
+
raise Pretendest::Error, "#{impersonated_method} must be defined before the impersonates method" unless sc.method_defined?(impersonated_method)
|
|
33
|
+
sc.instance_method(impersonated_method).bind(self).call
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
helper_method(true_method) if respond_to?(:helper_method)
|
|
37
|
+
|
|
38
|
+
define_method impersonated_method do
|
|
39
|
+
impersonated_resource = instance_variable_get(impersonated_var) if instance_variable_defined?(impersonated_var)
|
|
40
|
+
|
|
41
|
+
if !impersonated_resource && request.session[session_key]
|
|
42
|
+
# only fetch impersonation if user is logged in
|
|
43
|
+
# this is a safety check (once per request) so
|
|
44
|
+
# if a user logs out without session being destroyed
|
|
45
|
+
# or stop_impersonating_user being called,
|
|
46
|
+
# we can stop the impersonation
|
|
47
|
+
if send(impersonator_method)
|
|
48
|
+
impersonated_resource = impersonate_with.call(request.session[session_key])
|
|
49
|
+
instance_variable_set(impersonated_var, impersonated_resource) if impersonated_resource
|
|
50
|
+
else
|
|
51
|
+
# TODO better message
|
|
52
|
+
warn "[pretendest] Stopping impersonation due to safety check"
|
|
53
|
+
send(stop_impersonating_method)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
impersonated_resource || send(true_method)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
define_method :"#{scope}_impersonator" do
|
|
61
|
+
send(impersonator_method)
|
|
62
|
+
end
|
|
63
|
+
helper_method("#{scope}_impersonator") if respond_to?(:helper_method)
|
|
64
|
+
|
|
65
|
+
define_method :"impersonate_#{scope}" do |resource|
|
|
66
|
+
raise ArgumentError, "No resource to impersonate" unless resource
|
|
67
|
+
raise Pretendest::Error, "Must be logged in to impersonate" unless send(impersonator_method)
|
|
68
|
+
|
|
69
|
+
instance_variable_set(impersonated_var, resource)
|
|
70
|
+
# use to_s for Mongoid for BSON::ObjectId
|
|
71
|
+
request.session[session_key] = resource.id.is_a?(Numeric) ? resource.id : resource.id.to_s
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
define_method stop_impersonating_method do
|
|
75
|
+
remove_instance_variable(impersonated_var) if instance_variable_defined?(impersonated_var)
|
|
76
|
+
request.session.delete(session_key)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
define_method "impersonating_#{scope}?" do
|
|
80
|
+
send(impersonated_method) != send(true_method)
|
|
81
|
+
end
|
|
82
|
+
helper_method("impersonating_#{scope}?") if respond_to?(:helper_method)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
ActiveSupport.on_load(:action_controller) do
|
|
88
|
+
extend Pretendest::Methods
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# ActiveSupport.on_load(:action_cable) runs too late with Unicorn
|
|
92
|
+
ActionCable::Connection::Base.extend(Pretendest::Methods) if defined?(ActionCable)
|
metadata
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: pretendest
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.6.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Pierre de La Morinerie
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: actionpack
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.1'
|
|
26
|
+
email: kemenaran@gmail.com
|
|
27
|
+
executables: []
|
|
28
|
+
extensions: []
|
|
29
|
+
extra_rdoc_files: []
|
|
30
|
+
files:
|
|
31
|
+
- CHANGELOG.md
|
|
32
|
+
- LICENSE.txt
|
|
33
|
+
- README.md
|
|
34
|
+
- lib/pretendest.rb
|
|
35
|
+
- lib/pretendest/version.rb
|
|
36
|
+
homepage: https://github.com/enercoop/pretendest
|
|
37
|
+
licenses:
|
|
38
|
+
- MIT
|
|
39
|
+
metadata: {}
|
|
40
|
+
rdoc_options: []
|
|
41
|
+
require_paths:
|
|
42
|
+
- lib
|
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.2'
|
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '0'
|
|
53
|
+
requirements: []
|
|
54
|
+
rubygems_version: 3.7.2
|
|
55
|
+
specification_version: 4
|
|
56
|
+
summary: Log in as another user in Rails - with extra features
|
|
57
|
+
test_files: []
|