devise_masquerade 1.3.5 → 1.3.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 02e2f123857132cfdeaf49b1dbb8cef1dce29175448f862dba6387e72087ebe2
4
- data.tar.gz: 451e8c53e1e84d565fd0fcb13439c0f3b75dd27a00fb112e0c2fd8976f67b00b
3
+ metadata.gz: dd8da5271e7816c4823e6208ddb3d164065463a69d0be4d12bc6b6cdd6314102
4
+ data.tar.gz: 55e126ffbe80364b490ed85dd7235ad9de168614d0979d00e84ed0a9f7f31390
5
5
  SHA512:
6
- metadata.gz: 4f3ea1133abf1ae6126ce12e64fb118fa88e2627587f1e29833fb7805bd230cac706d7e4ab1f12848409cca824e670a131f8b4c3bb1e02e00a45337ab8071de0
7
- data.tar.gz: a281f8c5bcab7bde06ca043830ed98982e38c7e061a77be364768486caf7a5f10f85b35e5788c6334806a3b98a6010e8f4e1ee170a0baba88d05723429036287
6
+ metadata.gz: f575ef0026f95fc117daaee22797f2a8495e20a33c2e100c70f40df91eb7c05ad4eb84fe39fd01b0a9b1403c23c0a00a657f0b9475c02ff17e1c89b1c5ffc642
7
+ data.tar.gz: f42b17cc00ff950387b8a44f079a575a4875788e97743e8cb923cf09e73e41da3aa9bff7cc819250d96ed1347fed1258745f8ddeaaa8a0274af651ffcda0ae09
data/.travis.yml CHANGED
@@ -1,12 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.5.1
4
3
  - 2.6.0
5
- - 2.7.2
6
- gemfile:
7
- - Gemfile
4
+ - 2.7.3
8
5
  script: time ./script/travis.sh
9
- sudo: false
10
6
  addons:
11
7
  apt:
12
8
  packages:
data/Gemfile.lock CHANGED
@@ -52,7 +52,7 @@ GIT
52
52
  PATH
53
53
  remote: .
54
54
  specs:
55
- devise_masquerade (1.3.5)
55
+ devise_masquerade (1.3.9)
56
56
  devise (>= 4.7.0)
57
57
  globalid (>= 0.3.6)
58
58
  railties (>= 5.2.0)
@@ -93,7 +93,7 @@ GEM
93
93
  minitest (~> 5.1)
94
94
  tzinfo (~> 1.1)
95
95
  zeitwerk (~> 2.1, >= 2.1.8)
96
- addressable (2.7.0)
96
+ addressable (2.8.0)
97
97
  public_suffix (>= 2.0.2, < 5.0)
98
98
  archive-zip (0.12.0)
99
99
  io-like (~> 0.3.0)
@@ -201,12 +201,12 @@ GEM
201
201
  mime-types-data (~> 3.2015)
202
202
  mime-types-data (3.2019.1009)
203
203
  mini_mime (1.0.2)
204
- mini_portile2 (2.5.0)
204
+ mini_portile2 (2.5.1)
205
205
  minitest (5.12.2)
206
206
  multi_json (1.14.1)
207
207
  multi_test (0.1.2)
208
208
  nenv (0.3.0)
209
- nokogiri (1.11.1)
209
+ nokogiri (1.11.5)
210
210
  mini_portile2 (~> 2.5.0)
211
211
  racc (~> 1.4)
212
212
  notiffany (0.1.3)
@@ -220,7 +220,7 @@ GEM
220
220
  pry-byebug (3.7.0)
221
221
  byebug (~> 11.0)
222
222
  pry (~> 0.10)
223
- public_suffix (4.0.1)
223
+ public_suffix (4.0.6)
224
224
  racc (1.5.2)
225
225
  rack (2.2.3)
226
226
  rack-test (1.1.0)
data/README.md CHANGED
@@ -1,15 +1,17 @@
1
1
  # Devise Masquerade
2
- [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/oivoodoo/devise_masquerade?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
3
- [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Foivoodoo%2Fdevise_masquerade.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Foivoodoo%2Fdevise_masquerade?ref=badge_shield)
4
2
 
5
- [![Build Status](https://secure.travis-ci.org/oivoodoo/devise_masquerade.png?branch=master)](https://travis-ci.org/oivoodoo/devise_masquerade)
3
+ [![Gitter chat](https://badges.gitter.im/oivoodoo/devise_masquerade.svg)](https://gitter.im/oivoodoo/devise_masquerade?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
+
5
+ [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Foivoodoo%2Fdevise_masquerade.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Foivoodoo%2Fdevise_masquerade?ref=badge_shield)
6
6
 
7
- [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/oivoodoo/devise_masquerade)
7
+ [![Build Status](https://secure.travis-ci.org/oivoodoo/devise_masquerade.svg?branch=master)](https://travis-ci.org/oivoodoo/devise_masquerade)
8
8
 
9
- [![endorse](https://api.coderwall.com/oivoodoo/endorsecount.png)](https://coderwall.com/oivoodoo)
9
+ [![Maintainability](https://api.codeclimate.com/v1/badges/cf63d775dc014a7ebc03/maintainability)](https://codeclimate.com/github/oivoodoo/devise_masquerade/maintainability)
10
10
 
11
11
  [![Analytics](https://ga-beacon.appspot.com/UA-46818771-1/devise_masquerade/README.md)](https://github.com/oivoodoo/devise_masquerade)
12
12
 
13
+ [Consulting](https://bitscorp.co)
14
+
13
15
  It's a utility library for enabling functionallity like login as button for
14
16
  admin.
15
17
 
@@ -31,7 +33,9 @@ And then execute:
31
33
 
32
34
  In the view you can use url helper for defining link:
33
35
 
36
+ ```ruby
34
37
  = link_to "Login As", masquerade_path(user)
38
+ ```
35
39
 
36
40
  `masquerade_path` would create specific `/masquerade` path with query params `masquerade`(key) and `masqueraded_resource_class` to know
37
41
  which model to choose to search and sign in by masquerade key.
@@ -62,9 +66,11 @@ Instead of user you can use your resource name admin, student or another names.
62
66
  If you want to back to the owner of masquerade action user you could use
63
67
  helpers:
64
68
 
69
+ ```ruby
65
70
  user_masquerade? # current user was masqueraded by owner?
66
71
 
67
72
  = link_to "Reverse masquerade", back_masquerade_path(current_user)
73
+ ```
68
74
 
69
75
  ## Custom controller for adding cancan for authorization
70
76
 
@@ -162,12 +168,12 @@ in `routes.rb`:
162
168
  Devise.masquerade_key_size = 16 # size of the generate by SecureRandom.urlsafe_base64
163
169
  Devise.masquerade_bypass_warden_callback = false
164
170
  Devise.masquerade_routes_back = false # if true, route back to the page the user was on via redirect_back
165
- Devise.masquerading_resource_class = User
171
+ Devise.masquerading_resource_class = AdminUser
166
172
  # optional, default: masquerading_resource_class.model_name.param_key
167
- Devise.masquerading_resource_name = :user
168
- Devise.masqueraded_resource_class = AdminUser
173
+ Devise.masquerading_resource_name = :admin_user
174
+ Devise.masqueraded_resource_class = User
169
175
  # optional, default: masqueraded_resource_class.model_name.param_key
170
- Devise.masqueraded_resource_name = :admin_user
176
+ Devise.masqueraded_resource_name = :user
171
177
  ```
172
178
 
173
179
  ## Demo project
@@ -181,7 +187,7 @@ And check http://localhost:3000/, use for login user1@example.com and
181
187
 
182
188
  ## Troubleshooting
183
189
 
184
- Are you working in development mode and wondering why masquerade attempts result in a [Receiving "You are already signed in" flash[:error]](https://github.com/oivoodoo/devise_masquerade/issues/58) message? `Filter chain halted as :require_no_authentication rendered or redirected` showing up in your logfile? Chances are that you need to enable caching:
190
+ Are you working in development mode and wondering why masquerade attempts result in a [Receiving "You are already signed in" flash[:error]](https://github.com/oivoodoo/devise_masquerade/issues/58) message? `Filter chain halted as :require_no_authentication rendered or redirected` showing up in your logfile? Do you find that your `user_masquerade?` method is always returning false? Chances are that you need to enable caching:
185
191
 
186
192
  rails dev:cache
187
193
 
@@ -1,3 +1,5 @@
1
+ require 'securerandom'
2
+
1
3
  class Devise::MasqueradesController < DeviseController
2
4
  Devise.mappings.each do |name, _|
3
5
  class_eval <<-METHODS, __FILE__, __LINE__ + 1
@@ -10,41 +12,52 @@ class Devise::MasqueradesController < DeviseController
10
12
  prepend_before_action :masquerade_authorize!
11
13
 
12
14
  def show
13
- masqueradable_resource = find_masqueradable_resource
15
+ if send("#{masqueraded_resource_name}_masquerade?")
16
+ resource = masquerading_current_user
14
17
 
15
- save_masquerade_owner_session(masqueradable_resource)
18
+ go_back(resource, path: after_masquerade_full_path_for(resource))
19
+ else
20
+ masqueradable_resource = find_masqueradable_resource
16
21
 
17
- self.resource = masqueradable_resource
18
- sign_out(send("current_#{masquerading_resource_name}"))
22
+ save_masquerade_owner_session(masqueradable_resource)
19
23
 
20
- unless resource
21
- flash[:error] = "#{masqueraded_resource_class} not found."
22
- redirect_to(new_user_session_path) and return
23
- end
24
+ resource = masqueradable_resource
25
+ sign_out(masquerading_current_user)
26
+
27
+ unless resource
28
+ flash[:error] = "#{masqueraded_resource_class} not found."
29
+ redirect_to(new_user_session_path) and return
30
+ end
24
31
 
25
- request.env['devise.skip_trackable'] = '1'
32
+ request.env['devise.skip_trackable'] = '1'
26
33
 
27
- masquerade_sign_in(resource)
34
+ masquerade_sign_in(resource)
28
35
 
29
- go_back(resource, path: after_masquerade_full_path_for(resource))
36
+ go_back(resource, path: after_masquerade_full_path_for(resource))
37
+ end
30
38
  end
31
39
 
32
40
  def back
33
- masqueradable_resource = send("current_#{masqueraded_resource_name}")
41
+ unless send("#{masqueraded_resource_name}_masquerade?")
42
+ resource = send("current_#{masqueraded_resource_name}")
43
+ go_back(resource, path: after_back_masquerade_path_for(resource))
44
+ else
45
+ masqueradable_resource = send("current_#{masqueraded_resource_name}")
34
46
 
35
- unless send("#{masqueraded_resource_name}_signed_in?")
36
- head(401) and return
37
- end
47
+ unless send("#{masqueraded_resource_name}_signed_in?")
48
+ head(401) and return
49
+ end
38
50
 
39
- self.resource = find_owner_resource(masqueradable_resource)
40
- sign_out(send("current_#{masqueraded_resource_name}"))
51
+ resource = find_owner_resource(masqueradable_resource)
52
+ sign_out(send("current_#{masqueraded_resource_name}"))
41
53
 
42
- masquerade_sign_in(resource)
43
- request.env['devise.skip_trackable'] = nil
54
+ sign_in(resource)
55
+ request.env['devise.skip_trackable'] = nil
44
56
 
45
- go_back(resource, path: after_back_masquerade_path_for(resource))
57
+ go_back(resource, path: after_back_masquerade_path_for(resource))
46
58
 
47
- cleanup_masquerade_owner_session(masqueradable_resource)
59
+ cleanup_masquerade_owner_session(masqueradable_resource)
60
+ end
48
61
  end
49
62
 
50
63
  protected
@@ -62,7 +75,7 @@ class Devise::MasqueradesController < DeviseController
62
75
  end
63
76
 
64
77
  def find_owner_resource(masqueradable_resource)
65
- skey = session_key(masqueradable_resource)
78
+ skey = session_key(masqueradable_resource, masquerading_guid)
66
79
 
67
80
  GlobalID::Locator.locate_signed(Rails.cache.read(skey), for: 'masquerade')
68
81
  end
@@ -130,29 +143,32 @@ class Devise::MasqueradesController < DeviseController
130
143
  end
131
144
 
132
145
  def save_masquerade_owner_session(masqueradable_resource)
133
- skey = session_key(masqueradable_resource)
146
+ guid = SecureRandom.uuid
147
+
148
+ skey = session_key(masqueradable_resource, guid)
134
149
 
135
- resource_gid = send("current_#{masquerading_resource_name}").to_sgid(
136
- expires_in: Devise.masquerade_expires_in, for: 'masquerade')
150
+ resource_gid = send("current_#{masquerading_resource_name}").to_sgid(for: 'masquerade')
137
151
 
138
152
  # skip sharing owner id via session
139
- Rails.cache.write(skey, resource_gid, expires_in: Devise.masquerade_expires_in)
153
+ Rails.cache.write(skey, resource_gid)
140
154
  session[skey] = true
141
155
  session[session_key_masquerading_resource_class] = masquerading_resource_class.name
142
156
  session[session_key_masqueraded_resource_class] = masqueraded_resource_class.name
157
+ session[session_key_masquerading_resource_guid] = guid
143
158
  end
144
159
 
145
160
  def cleanup_masquerade_owner_session(masqueradable_resource)
146
- skey = session_key(masqueradable_resource)
161
+ skey = session_key(masqueradable_resource, masquerading_guid)
147
162
 
148
163
  Rails.cache.delete(skey)
149
164
  session.delete(skey)
150
165
  session.delete(session_key_masqueraded_resource_class)
151
166
  session.delete(session_key_masquerading_resource_class)
167
+ session.delete(session_key_masquerading_resource_guid)
152
168
  end
153
169
 
154
- def session_key(masqueradable_resource)
155
- "devise_masquerade_#{masqueraded_resource_name}_#{masqueradable_resource.to_param}".to_sym
170
+ def session_key(masqueradable_resource, guid)
171
+ "devise_masquerade_#{masqueraded_resource_name}_#{masqueradable_resource.to_param}_#{guid}".to_sym
156
172
  end
157
173
 
158
174
  def session_key_masqueraded_resource_class
@@ -160,6 +176,19 @@ class Devise::MasqueradesController < DeviseController
160
176
  end
161
177
 
162
178
  def session_key_masquerading_resource_class
163
- "devise_masquerade_masquerading_resource_class"
179
+ "devise_masquerade_masquerading_resource_class"
180
+ end
181
+
182
+ def session_key_masquerading_resource_guid
183
+ "devise_masquerade_masquerading_resource_guid"
184
+ end
185
+
186
+ def masquerading_current_user
187
+ send("current_#{masquerading_resource_name}")
188
+ end
189
+
190
+ def masquerading_guid
191
+ session[session_key_masquerading_resource_guid]
164
192
  end
165
193
  end
194
+
@@ -0,0 +1,18 @@
1
+ Feature: Use back button for returning to the owner despite on expiration time.
2
+ In order to back to the owner user
3
+ As an masquerade user
4
+ I want to be able to press a simple button on the page
5
+
6
+ Scenario: Use back button
7
+ Given I logged in
8
+ And I have a user for masquerade
9
+
10
+ When I have devise masquerade expiration time in 1 second
11
+
12
+ When I am on the users page
13
+ And I login as one user
14
+ Then I should be login as this user
15
+ And I waited for 2 seconds
16
+
17
+ When I press back masquerade button
18
+ Then I should be login as owner user
@@ -0,0 +1,9 @@
1
+ When("I have devise masquerade expiration time in {int} second") do |seconds|
2
+ Devise.masquerade_expires_in = seconds.second
3
+ end
4
+
5
+ Then("I waited for {int} seconds") do |seconds|
6
+ sleep(seconds)
7
+
8
+ Devise.masquerade_expires_in = 5.minutes
9
+ end
@@ -39,22 +39,28 @@ module DeviseMasquerade
39
39
 
40
40
  def #{name}_masquerade?
41
41
  return false if current_#{name}.blank?
42
+ return false if session[#{name}_helper_session_key].blank?
42
43
 
43
- key = "devise_masquerade_#{name}_" + current_#{name}.to_param
44
- return false if session[key].blank?
45
-
46
- ::Rails.cache.exist?(key.to_sym).present?
44
+ ::Rails.cache.exist?(#{name}_helper_session_key).present?
47
45
  end
48
46
 
49
47
  def #{name}_masquerade_owner
50
48
  return unless send(:#{name}_masquerade?)
51
49
 
52
- key = "devise_masquerade_#{name}_" + current_#{name}.to_param
53
- GlobalID::Locator.locate_signed(::Rails.cache.read(key.to_sym, for: 'masquerade'))
50
+ sgid = ::Rails.cache.read(#{name}_helper_session_key)
51
+ GlobalID::Locator.locate_signed(sgid, for: 'masquerade')
54
52
  end
55
53
 
56
54
  private
57
55
 
56
+ def #{name}_helper_session_key
57
+ ["devise_masquerade_#{name}", current_#{name}.to_param, #{name}_helper_masquerading_resource_guid].join("_")
58
+ end
59
+
60
+ def #{name}_helper_masquerading_resource_guid
61
+ session["devise_masquerade_masquerading_resource_guid"].to_s
62
+ end
63
+
58
64
  def masquerade_sign_in(resource)
59
65
  if Devise.masquerade_bypass_warden_callback
60
66
  if respond_to?(:bypass_sign_in)
@@ -8,9 +8,9 @@ module DeviseMasquerade
8
8
  scope = Devise::Mapping.find_scope!(resource)
9
9
 
10
10
  opts = args.shift || {}
11
- opts.merge!(masqueraded_resource_class: resource.class.name)
11
+ opts[:masqueraded_resource_class] = resource.class.name
12
12
 
13
- opts.merge!(Devise.masquerade_param => resource.masquerade_key)
13
+ opts[Devise.masquerade_param] = resource.masquerade_key
14
14
 
15
15
  send("#{scope}_masquerade_index_path", opts, *args)
16
16
  end
@@ -19,7 +19,7 @@ module DeviseMasquerade
19
19
  scope = Devise::Mapping.find_scope!(resource)
20
20
 
21
21
  opts = args.first || {}
22
- opts.merge!(masqueraded_resource_class: resource.class.name)
22
+ opts[:masqueraded_resource_class] = resource.class.name
23
23
 
24
24
  send("back_#{scope}_masquerade_index_path", opts, *args)
25
25
  end
@@ -1,3 +1,3 @@
1
1
  module DeviseMasquerade
2
- VERSION = '1.3.5'.freeze
2
+ VERSION = '1.3.9'.freeze
3
3
  end
@@ -14,7 +14,7 @@ describe Devise::MasqueradesController, type: :controller do
14
14
  get :show, params: { id: mask.to_param, masqueraded_resource_class: mask.class.name, masquerade: mask.masquerade_key }
15
15
  end
16
16
 
17
- it { expect(Rails.cache.read("devise_masquerade_student_#{mask.to_param}")).to be }
17
+ it { expect(cache_read(mask)).to be }
18
18
 
19
19
  it 'should have warden keys defined' do
20
20
  expect(session["warden.user.student.key"].first.first).to eq(mask.id)
@@ -22,6 +22,9 @@ describe Devise::MasqueradesController, type: :controller do
22
22
 
23
23
  it { should redirect_to('/') }
24
24
  end
25
+ end
26
+ context 'when logged in' do
27
+ before { logged_in }
25
28
 
26
29
  describe '#masquerade user' do
27
30
  let(:mask) { create(:user) }
@@ -30,7 +33,7 @@ describe Devise::MasqueradesController, type: :controller do
30
33
  get :show, params: { id: mask.to_param, masquerade: mask.masquerade_key }
31
34
  end
32
35
 
33
- it { expect(Rails.cache.read("devise_masquerade_user_#{mask.to_param}")).to be }
36
+ it { expect(cache_read(mask)).to be }
34
37
  it { expect(session["warden.user.user.key"].first.first).to eq(mask.id) }
35
38
  it { should redirect_to('/') }
36
39
 
@@ -39,7 +42,7 @@ describe Devise::MasqueradesController, type: :controller do
39
42
 
40
43
  it { should redirect_to(masquerade_page) }
41
44
  it { expect(current_user.reload).to eq(@user) }
42
- it { expect(Rails.cache.read("devise_masquerade_user_#{mask.to_param}")).not_to be }
45
+ it { expect(cache_read(mask)).not_to be }
43
46
  end
44
47
  end
45
48
 
@@ -107,4 +110,16 @@ describe Devise::MasqueradesController, type: :controller do
107
110
  def masquerade_page
108
111
  "/"
109
112
  end
113
+
114
+ def guid
115
+ session[:devise_masquerade_masquerading_resource_guid]
116
+ end
117
+
118
+ def cache_read(user)
119
+ Rails.cache.read(cache_key(user))
120
+ end
121
+
122
+ def cache_key(user)
123
+ "devise_masquerade_#{mask.class.name.downcase}_#{mask.to_param}_#{guid}"
124
+ end
110
125
  end
@@ -16,7 +16,7 @@ describe MasqueradesTestsController, type: :controller do
16
16
  before { get :show, params: { id: mask.to_param, masquerade: mask.masquerade_key } }
17
17
 
18
18
  it { expect(response.status).to eq(403) }
19
- it { expect(Rails.cache.read("devise_masquerade_user_#{mask.to_param}")).not_to be }
19
+ it { expect(cache_read(mask)).not_to be }
20
20
  it { expect(session['warden.user.user.key'].first.first).not_to eq(mask.id) }
21
21
  end
22
22
 
@@ -35,7 +35,20 @@ describe MasqueradesTestsController, type: :controller do
35
35
  end
36
36
 
37
37
  it { expect(response.status).to eq(302) }
38
- it { expect(Rails.cache.read("devise_masquerade_user_#{mask.to_param}")).to be }
38
+ it { expect(cache_read(mask)).to be }
39
39
  it { expect(session['warden.user.user.key'].first.first).to eq(mask.id) }
40
40
  end
41
+
42
+
43
+ def guid
44
+ session[:devise_masquerade_masquerading_resource_guid]
45
+ end
46
+
47
+ def cache_read(user)
48
+ Rails.cache.read(cache_key(user))
49
+ end
50
+
51
+ def cache_key(user)
52
+ "devise_masquerade_#{mask.class.name.downcase}_#{mask.to_param}_#{guid}"
53
+ end
41
54
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise_masquerade
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.5
4
+ version: 1.3.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandr Korsak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-23 00:00:00.000000000 Z
11
+ date: 2021-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -92,9 +92,11 @@ files:
92
92
  - config/environment.rb
93
93
  - devise_masquerade.gemspec
94
94
  - features/back.feature
95
+ - features/expires_masquerade.feature
95
96
  - features/multiple_masquerading_models.feature
96
97
  - features/step_definitions/auth_steps.rb
97
98
  - features/step_definitions/back_steps.rb
99
+ - features/step_definitions/expires_steps.rb
98
100
  - features/step_definitions/url_helpers_steps.rb
99
101
  - features/support/env.rb
100
102
  - features/url_helpers.feature
@@ -185,9 +187,11 @@ specification_version: 4
185
187
  summary: use for login as functionallity on your admin users pages
186
188
  test_files:
187
189
  - features/back.feature
190
+ - features/expires_masquerade.feature
188
191
  - features/multiple_masquerading_models.feature
189
192
  - features/step_definitions/auth_steps.rb
190
193
  - features/step_definitions/back_steps.rb
194
+ - features/step_definitions/expires_steps.rb
191
195
  - features/step_definitions/url_helpers_steps.rb
192
196
  - features/support/env.rb
193
197
  - features/url_helpers.feature