dude_policy 0.1.0 → 0.1.1

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: c1b4e1e0aa981141b42efcca848389438987fb99a890d4dfcb2375b9567cf2de
4
- data.tar.gz: e65b4dfdce1fbfb939b5e73caaae218c72c1b6e895daecdbd9bb5351ce8fe4ce
3
+ metadata.gz: ea59aff3b035ce29f5f37fae1b57ff7fa4a7a64626dfde17ff03265877612aad
4
+ data.tar.gz: 9060298e426595b6b91564a39041101937b50212b9389f5aee1d1a9f931af223
5
5
  SHA512:
6
- metadata.gz: 4a1d56704ffafee188c45bf81937b2663cf5d638ff4441bd11bd8aa780f1ffbc1a0f451f611e728fda2aaf7d40963c9505309e5371e8717a27881a48d8907116
7
- data.tar.gz: 281ae48292156284ee965d9554969b4476923d53921f5fcdcd91214756386676bef6c5aceeff392849b8f34a867e634a1b7e01cfca863238fba257c54b5d4145
6
+ metadata.gz: d302e8aa34fc76f754692d10177c22a66c73d0605cf329877bbe16cec1df6c316225bbfc001c011b93df80d1eef3faf5605acf96464063b8c978b5f662e095ad
7
+ data.tar.gz: 2d082b2f92376f1230c2b6c637a28a8733bf3c89cbc03e916f68a8128522d087e4b9432e650dad89271e27fd620e9f4adcb20a9101b75e1896decc1fe3dc0769
data/README.md CHANGED
@@ -5,11 +5,11 @@ from point of view of current_user/current_account (the **dude**)
5
5
 
6
6
  ![](https://triblive.com/wp-content/uploads/2019/01/673808_web1_gtr-liv-goldenglobes-121718.jpg)
7
7
 
8
- Here are some examples what we mean:
8
+ Here are some examples what I mean:
9
9
 
10
10
 
11
11
  ```ruby
12
- # irb
12
+ # rails console
13
13
  article = Article.find(123)
14
14
  review = Review.find(123)
15
15
  current_user = User.find(432) # e.g. Devise on any authentication solution
@@ -69,7 +69,7 @@ class ArticlePolicy < DudePolicy::BasePolicy
69
69
  end
70
70
  ```
71
71
 
72
- Full example in example app
72
+ > For more examples pls check the [example app](https://github.com/equivalent/dude_policy_example1)
73
73
 
74
74
  ## Installation
75
75
 
@@ -89,13 +89,253 @@ Or install it yourself as:
89
89
 
90
90
  ## Usage
91
91
 
92
- ### Gem is responsible for Authorization
92
+ Gem is responsible for **Authorization** (what a logged in user can/cannot do)
93
+ For **Authentication** (is User logged in ?) you will need a different solution / gem (e.g. [Devise](https://github.com/heartcombo/devise), custom login solution, ...)
93
94
 
94
- Gem will provide a way how to do `Authorization` (whan logged in user can/cannot do)
95
+ Once you have your Authentication solution implemeted and `dude_policy`
96
+ gem installed create `app/policy` a directory in your Ruby on Rails app
95
97
 
96
- For `Authentication` (is User logged in ?) you will need a different solution / gem (e.g. [Devise](https://github.com/heartcombo/devise), custom login solution, ...)
98
+ And create your policy file:
97
99
 
98
100
 
101
+ ```ruby
102
+ # app/policy/article_policy
103
+ class ArticlePolicy < DudePolicy::BasePolicy
104
+ def able_to_update_article?(dude:)
105
+ return true if dude.admin?
106
+ return true if dude == resource.author
107
+ false
108
+ end
109
+ end
110
+ ```
111
+
112
+ > Note: Since Rails version 4 files in `app/anything` directories are autoloaded. So no additional magic is needed
113
+
114
+ > Note: Policy should be name as the model suffixed with word "Policy". So if
115
+ > you have `ConstructiveComment` model your policy should be named `ConstructiveCommentPolicy` located in `app/policy/constructive_comment_policy.rb`
116
+
117
+
118
+ You also need to tell your models what role they play
119
+
120
+ ```ruby
121
+ class Article < ApplicationRecord
122
+ include DudePolicy::HasPolicy
123
+
124
+ # ...
125
+ end
126
+ ```
127
+
128
+
129
+ ```ruby
130
+ class User < ApplicationRecord
131
+ include DudePolicy::IsADude
132
+ include DudePolicy::HasPolicy
133
+
134
+ # ...
135
+ end
136
+ ```
137
+
138
+ This way you will be able to call:
139
+
140
+ ```ruby
141
+ user = User.find(123)
142
+ user.dude.able_to_update_article?(@article)
143
+ ```
144
+
145
+ > Note: same model can include both `DudePolicy::IsADude` and `DudePolicy::IsADude` but don't have to.
146
+
147
+ > Note: please be sure to check the "Philosophy" section of this README to fully understand the flow
148
+
149
+ This way you will be implement it in your application:
150
+
151
+ #### protect views
152
+
153
+ ```erb
154
+ <td><%= link_to 'Edit', edit_article_path(article) if current_user.dude.able_to_update_article?(article) %></td>
155
+ ```
156
+
157
+ #### protect controllers
158
+
159
+ ```ruby
160
+ # app/controllers/articles_controller.rb
161
+ class ArticlesController < ApplicationController
162
+ # ...
163
+
164
+ def update
165
+ @article = Article.find_by(params[:id])
166
+ raise(DudePolicy::NotAuthorized) unless current_user.dude.able_to_update_article?(@article)
167
+
168
+ if @article.update(article_params)
169
+ redirect_to @article, notice: 'Article was successfully updated.'
170
+ else
171
+ render :edit
172
+ end
173
+ end
174
+ end
175
+ ```
176
+
177
+ #### protect business logic
178
+
179
+ There are cases when you want to protect your business logic that is
180
+ beyond MVC level. For example:
181
+
182
+ ```ruby
183
+ # app/polcy/user_policy.rb
184
+ class UserPolicy < DudePolicy::BasePolicy
185
+ def able_to_see_user_full_name?(dude:)
186
+ return true if dude.admin?
187
+ return true if dude == resource
188
+ false
189
+ end
190
+ end
191
+
192
+
193
+ # app/helpers/application_helper.rb
194
+ def author_name(user)
195
+ return unless user
196
+ if current_user.dude.able_to_see_user_full_name?(user)
197
+ user.name
198
+ else
199
+ first_letter = user.name[0]
200
+ "#{first_letter}."
201
+ end
202
+ end
203
+ ```
204
+
205
+ #### RSpec testing
206
+
207
+
208
+ ##### policy test
209
+
210
+ You should be writing tests from perspective of current_user / current_account (the dude) and what roles they play.
211
+
212
+ If you have 2 roles (admin/regular user) test all policiese for every
213
+ role. If you have 8 roles (admin/moderator/client-manager/external-employee/noob/...) test every method from their perspective.
214
+
215
+ ```ruby
216
+ # spec/policy/article_policy_spec.rb
217
+ require 'rails_helper'
218
+ RSpec.describe ArticlePolicy do
219
+ let(:article) { create :article, author: author }
220
+ let(:author) { create :user }
221
+
222
+ context 'when nil user' do
223
+ let(:current_user) { nil }
224
+
225
+ it { expect(current_user.dude).not_to be_able_to_update_article(article) }
226
+ it { expect(current_user.dude).not_to be_able_to_delete_article(article) }
227
+ end
228
+
229
+ context 'when regular_user' do
230
+ let(:current_user) { create :user }
231
+
232
+ it { expect(current_user.dude).not_to be_able_to_update_article(article) }
233
+ it { expect(current_user.dude).not_to be_able_to_delete_article(article) }
234
+
235
+ context ' is author of article' do
236
+ let(:author) { current_user }
237
+ it { expect(current_user.dude).to be_able_to_update_article(article) }
238
+ it { expect(current_user.dude).to be_able_to_delete_article(article) }
239
+ end
240
+ end
241
+
242
+ context 'when admin' do
243
+ let(:current_user) { create :user, admin: true }
244
+ it { expect(current_user.dude).to be_able_to_update_article(article) }
245
+ it { expect(current_user.dude).not_to be_able_to_delete_article(article) }
246
+
247
+ context ' is author of article' do
248
+ let(:author) { current_user }
249
+ it { expect(current_user.dude).to be_able_to_update_article(article) }
250
+ it { expect(current_user.dude).to be_able_to_update_article(article) }
251
+ end
252
+ end
253
+ end
254
+ ```
255
+
256
+
257
+ Do yourself a favor and **don't write low level Unit Tests** like `expect(ArticlePolicy.new(article)).to be_able_to_update_article(dude: current_user)` !
258
+ When it comes to policy tests this can lead to huge security disasters ([full explanation](https://blog.eq8.eu/assets/2019/unit-test.jpg))
259
+
260
+ ##### request test
261
+
262
+ Now that we tested policy for every possible role of a user we can stub
263
+ the policy. We want to do in on same interface as we tested our policies
264
+ that means `allow(current_user.dude).to receive(:able_to_update_article?).and_return(true)`
265
+
266
+ > note: simmilar approach we would apply if you write controller RSpec test
267
+
268
+
269
+ ```ruby
270
+ require 'rails_helper'
271
+ RSpec.describe "Articles", type: :request do
272
+ let(:article) { create :article }
273
+
274
+ describe "put /articles/xxxx" do
275
+ def trigger_update
276
+ if current_user
277
+ # devise login
278
+ sign_in current_user
279
+
280
+ # policies are tested with `spec/policy/article_spec.rb so we can stub
281
+ allow(current_user.dude)
282
+ .to receive(:able_to_update_article?)
283
+ .with(article)
284
+ .and_return(authorized)
285
+ end
286
+
287
+ put article_path(article), params: {format: :html, article: { title: 'cat' }}
288
+ article.reload
289
+ end
290
+
291
+ context 'when not authenticated' do
292
+ let(:current_user) { nil }
293
+
294
+ it { expect { trigger_update }.not_to change { article.title } }
295
+ it do
296
+ trigger_update
297
+ expect(response.status).to eq 302 # redirect by update
298
+ follow_redirect!
299
+ expect(response.body).to include('You need to sign in or sign up before continuing.')
300
+ end
301
+ end
302
+
303
+ context 'when authenticated' do
304
+ let(:current_user) { create :user }
305
+
306
+ context 'when not authorized' do
307
+ let(:authorized) { false }
308
+
309
+ it { expect { trigger_update }.not_to change { article.title } }
310
+ it do
311
+ trigger_update
312
+ expect(response.status).to eq 302 # redirect to root_path by `authenticate!` method is ApplicationController
313
+ follow_redirect!
314
+ expect(response.body).to include('Sorry current user is not authorized to perform this action')
315
+ end
316
+ end
317
+
318
+ context 'when authorized' do
319
+ let(:authorized) { true }
320
+
321
+ it { expect { trigger_update }.to change { article.title }.from('interesting article').to('cat') }
322
+ it do
323
+ trigger_update
324
+ expect(response.status).to eq 302
325
+ follow_redirect!
326
+ expect(response.body).to include('Article was successfully updated.')
327
+ end
328
+ end
329
+ end
330
+ end
331
+ end
332
+ ```
333
+
334
+
335
+ #### More examples
336
+
337
+ > For more examples pls check the [example app](https://github.com/equivalent/dude_policy_example1)
338
+
99
339
 
100
340
  ## Contributing
101
341
 
@@ -6,6 +6,12 @@ module DudePolicy
6
6
  false
7
7
  end
8
8
 
9
+ # test frameworks like RSpec test first if method responds to before calling it
10
+ # as this is a NullObject delegator it responds to any method call
11
+ def respond_to?(*)
12
+ true
13
+ end
14
+
9
15
  def inspect
10
16
  "<#DudePolicy##{object_id} on nil>"
11
17
  end
@@ -1,3 +1,3 @@
1
1
  module DudePolicy
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dude_policy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomas Valent
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-12 00:00:00.000000000 Z
11
+ date: 2020-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport