lawkeeper 0.0.1 → 0.1.0

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