user_impersonate2 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +231 -0
  4. data/Rakefile +41 -0
  5. data/app/assets/javascripts/user_impersonate/application.js +15 -0
  6. data/app/assets/javascripts/user_impersonate/impersonate.js +2 -0
  7. data/app/assets/stylesheets/user_impersonate/application.css +13 -0
  8. data/app/assets/stylesheets/user_impersonate/impersonate.css +4 -0
  9. data/app/controllers/user_impersonate/application_controller.rb +4 -0
  10. data/app/controllers/user_impersonate/impersonate_controller.rb +146 -0
  11. data/app/helpers/user_impersonate/application_helper.rb +11 -0
  12. data/app/views/layouts/user_impersonate/application.html.erb +14 -0
  13. data/app/views/user_impersonate/_header.html.erb +60 -0
  14. data/app/views/user_impersonate/impersonate/index.html.erb +30 -0
  15. data/config/cucumber.yml +8 -0
  16. data/config/routes.rb +6 -0
  17. data/lib/generators/user_impersonate/USAGE +6 -0
  18. data/lib/generators/user_impersonate/templates/app/views/user_impersonate/_header.html.erb +60 -0
  19. data/lib/generators/user_impersonate/templates/config/initializers/user_impersonate.rb +15 -0
  20. data/lib/generators/user_impersonate/user_impersonate_generator.rb +15 -0
  21. data/lib/tasks/user_impersonate_tasks.rake +4 -0
  22. data/lib/user_impersonate.rb +5 -0
  23. data/lib/user_impersonate/devise_helpers.rb +31 -0
  24. data/lib/user_impersonate/engine.rb +17 -0
  25. data/lib/user_impersonate/version.rb +4 -0
  26. data/test/dummy/README.rdoc +261 -0
  27. data/test/dummy/Rakefile +7 -0
  28. data/test/dummy/app/assets/javascripts/application.js +15 -0
  29. data/test/dummy/app/assets/javascripts/home.js +2 -0
  30. data/test/dummy/app/assets/stylesheets/application.css +16 -0
  31. data/test/dummy/app/assets/stylesheets/home.css +7 -0
  32. data/test/dummy/app/controllers/application_controller.rb +3 -0
  33. data/test/dummy/app/controllers/home_controller.rb +4 -0
  34. data/test/dummy/app/helpers/application_helper.rb +2 -0
  35. data/test/dummy/app/helpers/home_helper.rb +2 -0
  36. data/test/dummy/app/models/user.rb +12 -0
  37. data/test/dummy/app/views/home/index.html.erb +11 -0
  38. data/test/dummy/app/views/layouts/application.html.erb +22 -0
  39. data/test/dummy/app/views/user_impersonate/_header.html.erb +65 -0
  40. data/test/dummy/config.ru +4 -0
  41. data/test/dummy/config/application.rb +60 -0
  42. data/test/dummy/config/boot.rb +10 -0
  43. data/test/dummy/config/database.yml +25 -0
  44. data/test/dummy/config/environment.rb +5 -0
  45. data/test/dummy/config/environments/development.rb +40 -0
  46. data/test/dummy/config/environments/production.rb +67 -0
  47. data/test/dummy/config/environments/test.rb +37 -0
  48. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  49. data/test/dummy/config/initializers/devise.rb +232 -0
  50. data/test/dummy/config/initializers/inflections.rb +15 -0
  51. data/test/dummy/config/initializers/mime_types.rb +5 -0
  52. data/test/dummy/config/initializers/secret_token.rb +7 -0
  53. data/test/dummy/config/initializers/session_store.rb +8 -0
  54. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  55. data/test/dummy/config/locales/devise.en.yml +58 -0
  56. data/test/dummy/config/locales/en.yml +5 -0
  57. data/test/dummy/config/routes.rb +8 -0
  58. data/test/dummy/db/migrate/20120914174453_devise_create_users.rb +48 -0
  59. data/test/dummy/db/migrate/20120914184123_add_staff_flag_to_users.rb +5 -0
  60. data/test/dummy/db/schema.rb +36 -0
  61. data/test/dummy/db/test.sqlite3 +0 -0
  62. data/test/dummy/lib/tasks/cucumber.rake +65 -0
  63. data/test/dummy/log/test.log +15 -0
  64. data/test/dummy/public/404.html +26 -0
  65. data/test/dummy/public/422.html +26 -0
  66. data/test/dummy/public/500.html +25 -0
  67. data/test/dummy/public/favicon.ico +0 -0
  68. data/test/dummy/script/rails +6 -0
  69. data/test/dummy/test/fixtures/users.yml +14 -0
  70. data/test/dummy/test/unit/helpers/home_helper_test.rb +4 -0
  71. data/test/dummy/test/unit/user_test.rb +7 -0
  72. data/test/integration/navigation_test.rb +10 -0
  73. data/test/test_helper.rb +15 -0
  74. data/test/unit/helpers/user_impersonate/impersonate_helper_test.rb +6 -0
  75. data/test/user_impersonate_test.rb +7 -0
  76. metadata +268 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 682a1471167a96c1fbf29afe05c351f5672c323d
4
+ data.tar.gz: 8b3324d882edf3c2bdbb8f1c8952469126ee6589
5
+ SHA512:
6
+ metadata.gz: e968520eb6e202e4423a9636d007568f58e486f8ddd079497ce451c10d21020deda7dc780c1a168686a46eb1b3d99ad12fa406041caea1091763939dd3e504e0
7
+ data.tar.gz: 0e43043f7da986d10a310fe7f3b7c0991dfd284184e81a40232eaca6b558ba3d67d5992a5bdfe2f76de4b699d22d0ca8b6acced1ddacb66f38522c864917f9ae
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,231 @@
1
+ # `user_impersonate2`
2
+
3
+ [![Install gem](https://badge.fury.io/rb/user_impersonate2.png)](https://rubygems.org/gems/user_impersonate2)
4
+ [![Build status](https://travis-ci.org/rcook/user_impersonate2.png)](https://travis-ci.org/rcook/user_impersonate2)
5
+ [![Coverage status](https://coveralls.io/repos/rcook/user_impersonate2/badge.png?branch=master)](https://coveralls.io/r/rcook/user_impersonate2)
6
+
7
+ # Note
8
+
9
+ This is a fork of Engine Yard's no-longer-maintained
10
+ [User Impersonate](https://github.com/engineyard/user_impersonate) gem. It supports Rails 3.2.x and
11
+ later as well as Rails 4.
12
+
13
+ # Overview
14
+
15
+ Allow staff users to impersonate your normal users: see what they see, only do what they can do.
16
+
17
+ This concept and code was extracted from [Engine Yard Cloud](http://www.engineyard.com/products/cloud), which we use when we want to help support a customer remotely.
18
+
19
+ This Rails engine currently supports the following Rails authentication systems:
20
+
21
+ * [Devise](https://github.com/plataformatec/devise)
22
+
23
+ # Example usage
24
+
25
+ When you are impersonating a user you see what they see, with a header section above:
26
+
27
+ <img src="https://img.skitch.com/20120919-c8382rgdcub7gsh2p82k8reng3.png" alt="Impersonating a user" />
28
+
29
+ # Installation
30
+
31
+ Add the gem to your Rails application's Gemfile and run `bundle`:
32
+
33
+ ``` ruby
34
+ gem 'user_impersonate2', :require => 'user_impersonate'
35
+ ```
36
+
37
+ Note that `:require => 'user_impersonate'` is required as this gem currently
38
+ maintains the same internal directory structure as the original
39
+ [User Impersonate](https://github.com/engineyard/user_impersonate) gem. This may
40
+ change in future versions.
41
+
42
+ Run the (sort of optional) generator:
43
+
44
+ ```
45
+ bundle
46
+ rails g user_impersonate
47
+ ```
48
+
49
+ This adds the following line within your `config/routes.rb`:
50
+
51
+ ``` ruby
52
+ mount UserImpersonate::Engine => "/impersonate", as: "impersonate_engine"
53
+ ```
54
+
55
+ Include in your layout files support for `flash[:error]` and `flash[:notice]`, such as:
56
+
57
+ ``` erb
58
+ <p class="notice"><%= flash[:notice] %></p>
59
+ <p class="alert"><%= flash[:error] %></p>
60
+ ```
61
+
62
+ Next, add the impersonation header to your layouts:
63
+
64
+ ``` erb
65
+ <% if current_staff_user %>
66
+ <%= render 'user_impersonate/header' %>
67
+ <% end %>
68
+ ```
69
+
70
+ Next, add staff concept to your User model.
71
+
72
+ To test the engine out, make all users staff!
73
+
74
+ ``` ruby
75
+ # app/models/user.rb
76
+
77
+ def staff?
78
+ true
79
+ end
80
+
81
+ # String to represent a user (email, name, etc)
82
+ def to_s
83
+ email
84
+ end
85
+ ```
86
+
87
+ You can now go to [http://localhost:3000/impersonate](http://localhost:3000/impersonate) to see the list of users, except your own user account. If you impersonate one you will see the magic!
88
+
89
+ # Integration
90
+
91
+ To support this Rails engine, you need to add some things.
92
+
93
+ * `current_user` helper within controllers & helpers
94
+ * `current_user.staff?` - your `User` model needs a `staff?` to identify if the current user is allowed to impersonate other users; if missing, no user can access impersonation system
95
+
96
+ ## User#staff?
97
+
98
+ One way to add this helper is to add a column to your User model:
99
+
100
+ ```
101
+ rails g migration add_staff_flag_to_users staff:boolean
102
+ rake db:migrate db:test:prepare
103
+ ```
104
+
105
+ # Customization
106
+
107
+ ## Header
108
+
109
+
110
+ You can override the bright red header by creating a `app/views/user_impersonate/_header.html.erb` file (or whatever template system you like).
111
+
112
+ For example, the Engine Yard Cloud uses a header that looks like:
113
+
114
+ ![](https://img.skitch.com/20120915-mk8mnpdsu5nuym3bxs678qf1a8.png)
115
+
116
+ The `app/views/user_impersonate/_header.html.haml` HAML partial for this header would be:
117
+
118
+ ``` haml
119
+ %div#impersonating
120
+ .impersonate-controls.page
121
+ .impersonate-info.grid_12
122
+ You (
123
+ %span.admin_name= current_staff_user
124
+ ) are impersonating
125
+ %span.user_name= link_to current_user, url_for([:admin, current_user])
126
+ ( User id:
127
+ %span.user_id= current_user.id
128
+ )
129
+ - if current_user.no_accounts?
130
+ ( No accounts )
131
+ - else
132
+ ( Account name:
133
+ %span.account_id= link_to current_user.accounts.first, url_for([:admin, current_user.accounts.first])
134
+ , id:
135
+ %strong= current_user.accounts.first.id
136
+ )
137
+ .impersonate-buttons.grid_12
138
+ = form_tag url_for([:ssh_key, :admin, current_user]), :method => "put" do
139
+ %span Support SSH Key
140
+ = select_tag 'public_key', options_for_select(current_staff_user.keys.map {|k| k})
141
+ %button{:type => "submit"} Install SSH Key
142
+ or
143
+ = form_tag [:admin, :revert], :method => :delete, :class => 'revert-form' do
144
+ %button{:type => "submit"} Revert to admin
145
+ ```
146
+
147
+ ## Redirects
148
+
149
+ By default, when you impersonate and when you stop impersonating a user you are redirected to the root url.
150
+
151
+ Configure alternate paths in `config/initializers/user_impersonate.rb`, which is created by the generator above.
152
+
153
+ ``` ruby
154
+ # config/initializers/user_impersonate.rb
155
+ module UserImpersonate
156
+ class Engine < Rails::Engine
157
+ config.redirect_on_impersonate = "/"
158
+ config.redirect_on_revert = "/impersonate"
159
+ end
160
+ end
161
+ ```
162
+
163
+ ## User model & lookup
164
+
165
+ By default, it assumes the User model is `User`, that you use `User.find(id)` to find a user, and `aUser.id` to get the related id value.
166
+
167
+ You can fix this default behavior in `config/initializers/user_impersonate.rb`, which is created by the generator above.
168
+
169
+ ``` ruby
170
+ # config/initializers/user_impersonate.rb
171
+ module UserImpersonate
172
+ class Engine < Rails::Engine
173
+ config.user_class = "User"
174
+ config.user_finder = "find" # User.find
175
+ config.user_id_column = "id" # Such that User.find(aUser.id) works
176
+ config.user_is_staff_method = "staff?" # current_user.staff?
177
+ end
178
+ end
179
+ ```
180
+
181
+ ## Spree specific stuff
182
+
183
+ Modify User and add current_user helper
184
+ ``` ruby
185
+ Spree::User.class_eval do
186
+ def staff?
187
+ has_spree_role?('admin')
188
+ end
189
+
190
+ def to_s
191
+ email
192
+ end
193
+ end
194
+
195
+ ApplicationController.class_eval do
196
+ helper_method :current_user
197
+ def current_user
198
+ spree_current_user
199
+ end
200
+ end
201
+ ```
202
+
203
+ Initializer
204
+ ``` ruby
205
+ # config/initializers/user_impersonate.rb
206
+ module UserImpersonate
207
+ class Engine < Rails::Engine
208
+ config.user_class = "Spree::User"
209
+ config.user_finder = "find" # User.find
210
+ config.user_id_column = "id" # Such that User.find(aUser.id) works
211
+ config.user_is_staff_method = "staff?" # current_user.staff?
212
+ config.authenticate_user_method = "authenticate_spree_user!"
213
+ config.redirect_on_impersonate = "/"
214
+ config.redirect_on_revert = "/"
215
+ config.user_name_column = "users"
216
+ end
217
+ end
218
+ ```
219
+
220
+ Deface to add header
221
+ ``` ruby
222
+ Deface::Override.new(:virtual_path => "spree/layouts/spree_application",
223
+ :name => "impersonate_header",
224
+ :insert_before => "div.container",
225
+ :text => "<% if current_staff_user %><%= render 'user_impersonate/header' %><% end %>")
226
+ ```
227
+
228
+ # Licence
229
+
230
+ `user_impersonate2` is released under the MIT licence.
231
+
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'UserImpersonate'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+ task :cucumber => 'app:cucumber'
40
+ # task :default => [:test, :spec, :cucumber]
41
+ task :default => [:test, :cucumber]
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require_tree .
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,4 @@
1
+ module UserImpersonate
2
+ class ApplicationController < ::ApplicationController
3
+ end
4
+ end
@@ -0,0 +1,146 @@
1
+ require_dependency "user_impersonate/application_controller"
2
+
3
+ module UserImpersonate
4
+ class ImpersonateController < ApplicationController
5
+ before_filter :authenticate_the_user
6
+ before_filter :current_user_must_be_staff!, except: ["destroy"]
7
+
8
+ # Display list of all users, except current (staff) user
9
+ # Is this exclusion unnecessary complexity?
10
+ # Normal apps wouldn't bother with this action; rather they would
11
+ # go straight to GET /impersonate/user/123 (create action)
12
+ def index
13
+ users_table = Arel::Table.new(user_table.to_sym) # e.g. :users
14
+ id_column = users_table[user_id_column.to_sym] # e.g. users_table[:id]
15
+ @users = user_class.order("updated_at DESC").
16
+ where(
17
+ id_column.not_in [
18
+ current_user.send(user_id_column.to_sym) # e.g. current_user.id
19
+ ])
20
+ if params[:search]
21
+ @users = @users.where("#{user_name_column} like ?", "%#{params[:search]}%")
22
+ end
23
+ end
24
+
25
+ # Perform the user impersonate action
26
+ # GET /impersonate/user/123
27
+ def create
28
+ @user = find_user(params[:user_id])
29
+ impersonate(@user)
30
+ redirect_on_impersonate(@user)
31
+ end
32
+
33
+ # Revert the user impersonation
34
+ # DELETE /impersonation/revert
35
+ def destroy
36
+ unless current_staff_user
37
+ flash[:notice] = "You weren't impersonating anyone"
38
+ redirect_on_revert and return
39
+ end
40
+ user = current_user
41
+ revert_impersonate
42
+ if user
43
+ flash[:notice] = "No longer impersonating #{user}"
44
+ redirect_on_revert(user)
45
+ else
46
+ flash[:notice] = "No longer impersonating a user"
47
+ redirect_on_revert
48
+ end
49
+ end
50
+
51
+ private
52
+ def current_user_must_be_staff!
53
+ unless user_is_staff?(current_user)
54
+ flash[:error] = "You don't have access to this section."
55
+ redirect_to :back
56
+ end
57
+ rescue ActionController::RedirectBackError
58
+ redirect_to '/'
59
+ end
60
+
61
+ # current_user changes from a staff user to
62
+ # +new_user+; current user stored in +session[:staff_user_id]+
63
+ def impersonate(new_user)
64
+ session[:staff_user_id] = current_user.id #
65
+ sign_in_user new_user
66
+ end
67
+
68
+ # revert the +current_user+ back to the staff user
69
+ # stored in +session[:staff_user_id]+
70
+ def revert_impersonate
71
+ return unless current_staff_user
72
+ sign_in_user current_staff_user
73
+ session[:staff_user_id] = nil
74
+ end
75
+
76
+ def sign_in_user(user)
77
+ method = config_or_default :sign_in_user_method, "sign_in"
78
+ self.send(method.to_sym, user)
79
+ end
80
+
81
+ def authenticate_the_user
82
+ method = config_or_default :authenticate_user_method, "authenticate_user!"
83
+ self.send(method.to_sym)
84
+ end
85
+
86
+ # Helper to load a User, using all the UserImpersonate config options
87
+ def find_user(id)
88
+ user_class.send(user_finder_method, id)
89
+ end
90
+
91
+ # Similar to user.staff?
92
+ # Using all the UserImpersonate config options
93
+ def user_is_staff?(user)
94
+ current_user.respond_to?(user_is_staff_method.to_sym) &&
95
+ current_user.send(user_is_staff_method.to_sym)
96
+ end
97
+
98
+ def user_finder_method
99
+ (config_or_default :user_finder, "find").to_sym
100
+ end
101
+
102
+ def user_class_name
103
+ config_or_default :user_class, "User"
104
+ end
105
+
106
+ def user_class
107
+ user_class_name.constantize
108
+ end
109
+
110
+ def user_table
111
+ user_class_name.tableize.tr('/', '_')
112
+ end
113
+
114
+ def user_id_column
115
+ config_or_default :user_id_column, "id"
116
+ end
117
+
118
+ def user_name_column
119
+ config_or_default :user_name_column, "name"
120
+ end
121
+
122
+ def user_is_staff_method
123
+ config_or_default :user_is_staff_method, "staff?"
124
+ end
125
+
126
+ def redirect_on_impersonate(impersonated_user)
127
+ url = config_or_default :redirect_on_impersonate, root_url
128
+ redirect_to url
129
+ end
130
+
131
+ def redirect_on_revert(impersonated_user = nil)
132
+ url = config_or_default :redirect_on_revert, root_url
133
+ redirect_to url
134
+ end
135
+
136
+ # gets overridden config value for engine, else returns default
137
+ def config_or_default(attribute, default)
138
+ attribute = attribute.to_sym
139
+ if UserImpersonate::Engine.config.respond_to?(attribute)
140
+ UserImpersonate::Engine.config.send(attribute)
141
+ else
142
+ default
143
+ end
144
+ end
145
+ end
146
+ end