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 +40 -27
- data/lib/lawkeeper.rb +30 -4
- data/lib/lawkeeper/scope_finders/active_record.rb +9 -0
- data/lib/lawkeeper/scope_finders/ohm.rb +9 -0
- data/lib/lawkeeper/version.rb +1 -1
- data/spec/helpers_spec.rb +35 -1
- data/spec/policy_lookup_spec.rb +17 -5
- data/spec/scope_finders_spec.rb +23 -0
- metadata +7 -3
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
|
-
|
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
|
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
|
data/lib/lawkeeper.rb
CHANGED
@@ -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
|
-
|
26
|
-
|
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("#{
|
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
|
data/lib/lawkeeper/version.rb
CHANGED
data/spec/helpers_spec.rb
CHANGED
@@ -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
|
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
|
data/spec/policy_lookup_spec.rb
CHANGED
@@ -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
|
12
|
+
it "prefers the classes policy_class if available" do
|
13
13
|
post = Post.new
|
14
|
-
|
15
|
-
|
14
|
+
klass = Class.new do
|
15
|
+
def self.policy_class
|
16
|
+
SpecifiedPolicy
|
17
|
+
end
|
16
18
|
end
|
17
|
-
Lawkeeper::PolicyLookup[
|
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
|
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: &
|
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: *
|
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
|