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