dude_policy 0.1.0 → 0.1.1

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 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