lawkeeper 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -72,17 +72,54 @@ only requirement is that they end with '?'.
72
72
  Policy classes are instantiated with the current user and a record for checking.
73
73
 
74
74
  If you wish to use an unconventially named Policy class for a model, add the
75
- `#policy_class` instance method to your model. For example:
75
+ `.policy_class` class method to your model. For example:
76
76
 
77
77
  ```ruby
78
78
  class Post
79
- def policy_class
79
+ def self.policy_class
80
80
  OwnershipPolicy
81
81
  end
82
82
  end
83
83
  ```
84
84
 
85
- Lawkeeper helper methods will prefer the `#policy_class` specified if it exists.
85
+ Lawkeeper helper methods will prefer the `policy_class` specified if it exists.
86
+
87
+ ### Specifying Scope classes for policy use
88
+
89
+ For finding records for collection records (like an index) it is possible to
90
+ do scoped find if your relational or document mapper permits it. This is
91
+ accomplished by creating a `Scope` class inside your policy class. Take
92
+ this example for Ohm:
93
+
94
+ ```ruby
95
+ class PostPolicy < AppPolicy
96
+ class Scope < Lawkeeper::Policy::Scope
97
+ def resolve
98
+ scope.find(published: "true")
99
+ end
100
+ end
101
+ end
102
+ ```
103
+
104
+ You can proceed to use this in an action to find posts where published is the
105
+ string "true":
106
+
107
+ ```ruby
108
+ @posts = policy_scope(Post)
109
+ ```
110
+
111
+ The policy scope lookup is handled by a scope finder stored at `Lawkeeper.scope_finder`.
112
+ Currently Ohm and ActiveRecord adapters are provided. The finder only has one requirement,
113
+ it must respond to `call`. You can use a class method or Proc to facilitate this. It
114
+ should return a capitalized string representing the class, such as "Post" or "Comment".
115
+
116
+ If you wish to use `policy_scope` you should configure a finder appropriate for your storage:
117
+
118
+ ```ruby
119
+ Lawkeeper.scope_finder = Lawkeeper::ScopeFinders::Ohm
120
+ # or
121
+ Lawkeeper.scope_finder = Proc.new { |s| ... }
122
+ ```
86
123
 
87
124
  ### Authorizing in actions
88
125
 
@@ -145,30 +182,6 @@ Lawkeeper.skip_set_headers = true
145
182
  This will not prevent how Lawkeeper does its primary job of authorizing policy
146
183
  actions.
147
184
 
148
- ## Outstanding Problems
149
-
150
- Lawkeeper as yet has no mechanism for scoping finds for collection 'index' like
151
- methods. Pundit (which Lawkeeper is influenced by) has some similiar problems
152
- in this area as well (though it does provide a scope object).
153
-
154
- The primary challenges are:
155
-
156
- * A collection action is very different than an instance authorization, because
157
- you don't authorize instances but rather find them via policy
158
- * As compared to instance authorization, scoped finding is very coupled to an
159
- ORM, model, or storage pattern in a given application.
160
-
161
- My immediate thinking for Lawkeeper is to call `skip_authorization` in actions
162
- where you have performed a scoped find. This will likely keep development in check
163
- since adding the call is a mental note for "hey this should be permitted records only"
164
-
165
- To build upon this, I believe either:
166
-
167
- * The scoping should be up to you, or
168
- * The scope permission handling should delegate to third party ORM specific plugins.
169
-
170
- I am still rolling around this in my head, with the goal of keeping it simple
171
-
172
185
  ## Contributing
173
186
 
174
187
  1. Fork it
@@ -8,12 +8,25 @@ module Lawkeeper
8
8
  class NotDefined < StandardError; end
9
9
 
10
10
  class << self
11
- attr_accessor :skip_set_headers
11
+ attr_accessor :skip_set_headers, :scope_finder
12
12
  end
13
13
 
14
14
  class Policy
15
15
  attr_reader :user, :record
16
16
 
17
+ class Scope
18
+ attr_reader :user, :scope
19
+
20
+ def initialize(user, scope)
21
+ @user = user
22
+ @scope = scope
23
+ end
24
+
25
+ def resolve
26
+ scope
27
+ end
28
+ end
29
+
17
30
  def initialize(user, record)
18
31
  @user = user
19
32
  @record = record
@@ -22,16 +35,23 @@ module Lawkeeper
22
35
 
23
36
  class PolicyLookup
24
37
  def self.[](model)
25
- if model.respond_to?(:policy_class)
26
- model.policy_class
38
+ klass = model.is_a?(Class) ? model : model.class
39
+
40
+ if klass.respond_to?(:policy_class)
41
+ klass.policy_class
27
42
  else
28
43
  begin
29
- Object.const_get("#{model.class}Policy")
44
+ Object.const_get("#{klass}Policy")
30
45
  rescue NameError
31
46
  raise NotDefined
32
47
  end
33
48
  end
34
49
  end
50
+
51
+ def self.for_scope(scope)
52
+ model_name = Lawkeeper.scope_finder.call(scope)
53
+ self[Object.const_get(model_name)]
54
+ end
35
55
  end
36
56
 
37
57
  module Helpers
@@ -49,6 +69,12 @@ module Lawkeeper
49
69
  end
50
70
  end
51
71
 
72
+ def policy_scope(scope)
73
+ set_lawkeeper_header(AUTHORIZED_HEADER)
74
+ klass = Lawkeeper::PolicyLookup.for_scope(scope)::Scope
75
+ klass.new(current_user, scope).resolve
76
+ end
77
+
52
78
  def skip_authorization
53
79
  set_lawkeeper_header(SKIPPED_HEADER)
54
80
  end
@@ -0,0 +1,9 @@
1
+ module Lawkeeper
2
+ module ScopeFinders
3
+ class ActiveRecord
4
+ def self.call(scope)
5
+ scope.model_name
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Lawkeeper
2
+ module ScopeFinders
3
+ class Ohm
4
+ def self.call(scope)
5
+ scope.key.split(':')[0]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Lawkeeper
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -72,7 +72,7 @@ describe Lawkeeper::Helpers do
72
72
 
73
73
  permit_action
74
74
  controller.authorize(comment, :read)
75
- controller.headers['Lawkeeper-Authorized'].must_be_nil 'true'
75
+ controller.headers['Lawkeeper-Authorized'].must_be_nil
76
76
 
77
77
  Lawkeeper.skip_set_headers = nil
78
78
  end
@@ -93,6 +93,40 @@ describe Lawkeeper::Helpers do
93
93
  end
94
94
  end
95
95
 
96
+ describe '#policy_scope' do
97
+ before do
98
+ Foo ||= Class.new
99
+
100
+ class FooPolicy
101
+ class Scope
102
+ def initialize(*)
103
+ end
104
+
105
+ def resolve
106
+ [:foo, :bar]
107
+ end
108
+ end
109
+ end
110
+
111
+ @klass = Class.new do
112
+ def self.policy_class
113
+ FooPolicy
114
+ end
115
+ end
116
+
117
+ Lawkeeper.scope_finder = Proc.new { "Foo" }
118
+ end
119
+
120
+ it "sets the authorized header to 'true'" do
121
+ controller.policy_scope(@klass)
122
+ controller.headers['Lawkeeper-Authorized'].must_equal 'true'
123
+ end
124
+
125
+ it "resolves using the found scope class" do
126
+ controller.policy_scope(@klass).must_equal [:foo, :bar]
127
+ end
128
+ end
129
+
96
130
  describe '#skip_authorization' do
97
131
  it "sets the authorized header to 'true'" do
98
132
  controller.skip_authorization
@@ -4,17 +4,19 @@ class Post; end
4
4
  class PostPolicy; end
5
5
  class SpecifiedPolicy; end
6
6
 
7
- describe Lawkeeper::PolicyLookup do
7
+ describe Lawkeeper::PolicyLookup, '.[]' do
8
8
  it "returns PostPolicy for a Post instance" do
9
9
  Lawkeeper::PolicyLookup[Post.new].must_equal PostPolicy
10
10
  end
11
11
 
12
- it "prefers the instance#policy_class if available" do
12
+ it "prefers the classes policy_class if available" do
13
13
  post = Post.new
14
- def post.policy_class
15
- SpecifiedPolicy
14
+ klass = Class.new do
15
+ def self.policy_class
16
+ SpecifiedPolicy
17
+ end
16
18
  end
17
- Lawkeeper::PolicyLookup[post].must_equal SpecifiedPolicy
19
+ Lawkeeper::PolicyLookup[klass].must_equal SpecifiedPolicy
18
20
  end
19
21
 
20
22
  it "raises NotDefined if the policy can not be found" do
@@ -23,3 +25,13 @@ describe Lawkeeper::PolicyLookup do
23
25
  }.must_raise(Lawkeeper::NotDefined)
24
26
  end
25
27
  end
28
+
29
+ describe Lawkeeper::PolicyLookup, '.for_scope' do
30
+ it "calls to the Lawkeeper.scope_finder" do
31
+ class ForScope; end
32
+ class ForScopePolicy; end
33
+ Lawkeeper.scope_finder = Proc.new { |s| s.class.name }
34
+
35
+ Lawkeeper::PolicyLookup.for_scope(ForScope.new).must_equal ForScopePolicy
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require "lawkeeper/scope_finders/active_record"
3
+ require "lawkeeper/scope_finders/ohm"
4
+
5
+ describe Lawkeeper::ScopeFinders::ActiveRecord do
6
+ it "returns the .model_name of the passed scope" do
7
+ o = Object.new
8
+ def o.model_name
9
+ "Comment"
10
+ end
11
+ Lawkeeper::ScopeFinders::ActiveRecord.call(o).must_equal "Comment"
12
+ end
13
+ end
14
+
15
+ describe Lawkeeper::ScopeFinders::Ohm do
16
+ it "returns the first portion of the .key for the passed scope" do
17
+ o = Object.new
18
+ def o.key
19
+ "Comment:1"
20
+ end
21
+ Lawkeeper::ScopeFinders::Ohm.call(o).must_equal "Comment"
22
+ end
23
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lawkeeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2013-06-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
16
- requirement: &2152801400 !ruby/object:Gem::Requirement
16
+ requirement: &2152801280 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *2152801400
24
+ version_requirements: *2152801280
25
25
  description: Lawkeeper - Simple authorization policies for Rack apps
26
26
  email:
27
27
  - xternal1+github@gmail.com
@@ -36,11 +36,14 @@ files:
36
36
  - Rakefile
37
37
  - lawkeeper.gemspec
38
38
  - lib/lawkeeper.rb
39
+ - lib/lawkeeper/scope_finders/active_record.rb
40
+ - lib/lawkeeper/scope_finders/ohm.rb
39
41
  - lib/lawkeeper/version.rb
40
42
  - spec/helpers_spec.rb
41
43
  - spec/middleware_spec.rb
42
44
  - spec/policy_lookup_spec.rb
43
45
  - spec/policy_spec.rb
46
+ - spec/scope_finders_spec.rb
44
47
  - spec/spec_helper.rb
45
48
  homepage: ''
46
49
  licenses: []
@@ -71,4 +74,5 @@ test_files:
71
74
  - spec/middleware_spec.rb
72
75
  - spec/policy_lookup_spec.rb
73
76
  - spec/policy_spec.rb
77
+ - spec/scope_finders_spec.rb
74
78
  - spec/spec_helper.rb