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 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
+ [![Build Status](https://github.com/enercoop/pretendest/actions/workflows/build.yml/badge.svg)](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
+ ```
@@ -0,0 +1,3 @@
1
+ module Pretendest
2
+ VERSION = "0.6.0"
3
+ end
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: []