lawkeeper 0.0.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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +178 -0
- data/Rakefile +11 -0
- data/lawkeeper.gemspec +19 -0
- data/lib/lawkeeper/version.rb +3 -0
- data/lib/lawkeeper.rb +102 -0
- data/spec/helpers_spec.rb +102 -0
- data/spec/middleware_spec.rb +14 -0
- data/spec/policy_lookup_spec.rb +25 -0
- data/spec/policy_spec.rb +9 -0
- data/spec/spec_helper.rb +2 -0
- metadata +74 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Brendon Murphy
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
# Lawkeeper
|
2
|
+
|
3
|
+
Lawkeeper - Simple authorization policies for Rack apps
|
4
|
+
|
5
|
+
Lawkeeper was heavily inspired by the Pundit authorization gem. Lawkeeper
|
6
|
+
follows a very similar pattern, but is more agnostic and geared towards use
|
7
|
+
in smaller Rack applications.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'lawkeeper'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install lawkeeper
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
Lawkeeper makes a couple basic assumptions
|
26
|
+
|
27
|
+
* You have a `current_user` helper
|
28
|
+
* You create policy files like `PostPolicy` for a `Post` model
|
29
|
+
* You have a headers method with a settable hash for response headers
|
30
|
+
|
31
|
+
After setting up your model policies, include `Lawkeeper::Helpers`
|
32
|
+
into your app. Let's assume Sinatra as our example:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
helpers do
|
36
|
+
include Lawkeeper::Helpers
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
This provides a few useful helpers:
|
41
|
+
|
42
|
+
* `can?` - for checking if the current_user is permitted an action on the
|
43
|
+
record
|
44
|
+
* `authorize` - checks if the user can perform the action, otherwise raise
|
45
|
+
`Lawkeeper::NotAuthorized`
|
46
|
+
* `skip_authorization` - used to flag an action as not needing authorization
|
47
|
+
|
48
|
+
### Declaring policy classes
|
49
|
+
|
50
|
+
By default, Lawkeeper follows a convention of mapping policy classes like
|
51
|
+
`PostPolicy` for a `Post` class, `CommentPolicy` for `Comment`, etc.
|
52
|
+
|
53
|
+
The simplest way to declare a post policy is inherit `Lawkeeper::Policy`
|
54
|
+
and declare predicates for policy checks:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class PostPolicy < Lawkeeper::Policy
|
58
|
+
def read?
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
def update?
|
63
|
+
record.owned_by?(user)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
Lawkeeper makes no assumptions about the name of your policy queries. You can
|
69
|
+
call them `show?` or `read?`, `delete?` or `destroy?`, whichever you prefer. The
|
70
|
+
only requirement is that they end with '?'.
|
71
|
+
|
72
|
+
Policy classes are instantiated with the current user and a record for checking.
|
73
|
+
|
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:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
class Post
|
79
|
+
def policy_class
|
80
|
+
OwnershipPolicy
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
Lawkeeper helper methods will prefer the `#policy_class` specified if it exists.
|
86
|
+
|
87
|
+
### Authorizing in actions
|
88
|
+
|
89
|
+
To authorize in a controller action is simple:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
get "/post/:id" do
|
93
|
+
@post = Post.find(id)
|
94
|
+
authorize @post, :read
|
95
|
+
erb :post_show
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
If authorize is permitted (which it usually should be) the action will continue
|
100
|
+
as normal. If it fails, Lawkeeper::NotAuthorized will be raised.
|
101
|
+
|
102
|
+
### Checking in views
|
103
|
+
|
104
|
+
Lawkeeper provides a `can?` helper to use in your views:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
<% if can? :edit, @post %>
|
108
|
+
<a href="/posts/<%= @post.id %>/edit">Edit Post</a>
|
109
|
+
<% end %>
|
110
|
+
```
|
111
|
+
|
112
|
+
The `can?` method is a check, it will not raise authorization exceptions.
|
113
|
+
|
114
|
+
### Specifying policy classes
|
115
|
+
|
116
|
+
If you wish to specify a policy class at runtime for a call to `can?` or `authorize`,
|
117
|
+
you can pass a policy class as an option third argument.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
authorize @post, :read, OwnershipPolicy
|
121
|
+
```
|
122
|
+
|
123
|
+
## Ensuring authorization with middlewares
|
124
|
+
|
125
|
+
Lawkeeper provides `EnsureWare` for checking that authorization was performed
|
126
|
+
for all actions. When the `authorize` or `skip_authorization` methods are
|
127
|
+
employed in actions, response headers are set. The middleware then checks
|
128
|
+
and deletes the headers. If the header was not present, a 403 forbidden status
|
129
|
+
will be returned.
|
130
|
+
|
131
|
+
This is useful to ensure you do not forget to authorize the resource in any
|
132
|
+
given action.
|
133
|
+
|
134
|
+
If you do not wish to enforce such a check, you should employ the `ScrubWare`
|
135
|
+
middleware instead. This is simply responsible for stripping Lawkeeper headers
|
136
|
+
before sending the response on its way.
|
137
|
+
|
138
|
+
If you'd prefer to not use middleware at all, it's advised you set Lawkeeper to
|
139
|
+
simply skip the setting of headers:
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
Lawkeeper.skip_set_headers = true
|
143
|
+
```
|
144
|
+
|
145
|
+
This will not prevent how Lawkeeper does its primary job of authorizing policy
|
146
|
+
actions.
|
147
|
+
|
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
|
+
## Contributing
|
173
|
+
|
174
|
+
1. Fork it
|
175
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
176
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
177
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
178
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lawkeeper.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/lawkeeper/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Brendon Murphy"]
|
6
|
+
gem.email = ["xternal1+github@gmail.com"]
|
7
|
+
gem.summary = %q{Lawkeeper - Simple authorization policies for Rack apps}
|
8
|
+
gem.description = gem.summary
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "lawkeeper"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Lawkeeper::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency "minitest"
|
19
|
+
end
|
data/lib/lawkeeper.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require "lawkeeper/version"
|
2
|
+
|
3
|
+
module Lawkeeper
|
4
|
+
AUTHORIZED_HEADER = 'Lawkeeper-Authorized'.freeze
|
5
|
+
SKIPPED_HEADER = 'Lawkeeper-Skipped'.freeze
|
6
|
+
|
7
|
+
class NotAuthorized < StandardError; end
|
8
|
+
class NotDefined < StandardError; end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :skip_set_headers
|
12
|
+
end
|
13
|
+
|
14
|
+
class Policy
|
15
|
+
attr_reader :user, :record
|
16
|
+
|
17
|
+
def initialize(user, record)
|
18
|
+
@user = user
|
19
|
+
@record = record
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class PolicyLookup
|
24
|
+
def self.[](model)
|
25
|
+
if model.respond_to?(:policy_class)
|
26
|
+
model.policy_class
|
27
|
+
else
|
28
|
+
begin
|
29
|
+
Object.const_get("#{model.class}Policy")
|
30
|
+
rescue NameError
|
31
|
+
raise NotDefined
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Helpers
|
38
|
+
def can?(action, model, policy_class = nil)
|
39
|
+
policy_class ||= Lawkeeper::PolicyLookup[model]
|
40
|
+
policy_method = "#{action}?"
|
41
|
+
policy_class.new(current_user, model).public_send(policy_method)
|
42
|
+
end
|
43
|
+
|
44
|
+
def authorize(model, action, policy_class = nil)
|
45
|
+
if can?(action, model, policy_class)
|
46
|
+
set_lawkeeper_header(AUTHORIZED_HEADER)
|
47
|
+
else
|
48
|
+
raise NotAuthorized
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def skip_authorization
|
53
|
+
set_lawkeeper_header(SKIPPED_HEADER)
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_lawkeeper_header(header)
|
57
|
+
headers[header] = 'true' unless Lawkeeper.skip_set_headers
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class EnsureWare
|
62
|
+
def initialize(app, options = {})
|
63
|
+
@app = app
|
64
|
+
@options = options
|
65
|
+
end
|
66
|
+
|
67
|
+
def call(env)
|
68
|
+
dup._call(env)
|
69
|
+
end
|
70
|
+
|
71
|
+
def _call(env)
|
72
|
+
status, headers, body = @app.call(env)
|
73
|
+
|
74
|
+
if headers.delete(AUTHORIZED_HEADER) || headers.delete(SKIPPED_HEADER)
|
75
|
+
[status, headers, body]
|
76
|
+
else
|
77
|
+
[status_code, {"Content-Type" => "text/plain"}, ['forbidden, authorization required']]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def status_code
|
82
|
+
@options.fetch(:status_code, 403)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class ScrubWare
|
87
|
+
def initialize(app)
|
88
|
+
@app = app
|
89
|
+
end
|
90
|
+
|
91
|
+
def call(env)
|
92
|
+
dup._call(env)
|
93
|
+
end
|
94
|
+
|
95
|
+
def _call(env)
|
96
|
+
status, headers, body = @app.call(env)
|
97
|
+
headers.delete(AUTHORIZED_HEADER)
|
98
|
+
headers.delete(SKIPPED_HEADER)
|
99
|
+
[status, headers, body]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class User
|
4
|
+
attr_accessor :permit
|
5
|
+
end
|
6
|
+
|
7
|
+
class Comment
|
8
|
+
attr_accessor :permit
|
9
|
+
end
|
10
|
+
|
11
|
+
class CommentPolicy < Lawkeeper::Policy
|
12
|
+
def read?
|
13
|
+
user.permit && record.permit
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe Lawkeeper::Helpers do
|
18
|
+
let(:comment) { Comment.new }
|
19
|
+
let(:controller) {
|
20
|
+
c = Object.new.tap { |c| c.extend(Lawkeeper::Helpers) }
|
21
|
+
def c.current_user
|
22
|
+
@current_user ||= User.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def c.headers
|
26
|
+
@headers ||= {}
|
27
|
+
end
|
28
|
+
|
29
|
+
c
|
30
|
+
}
|
31
|
+
|
32
|
+
def permit_action
|
33
|
+
controller.current_user.permit = true
|
34
|
+
comment.permit = true
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#can?' do
|
38
|
+
it "returns false for a user denied action" do
|
39
|
+
controller.current_user.permit = true
|
40
|
+
comment.permit = false
|
41
|
+
controller.can?(:read, comment).must_equal false
|
42
|
+
|
43
|
+
controller.current_user.permit = false
|
44
|
+
comment.permit = true
|
45
|
+
controller.can?(:read, comment).must_equal false
|
46
|
+
end
|
47
|
+
|
48
|
+
it "returns true for a user permitted action" do
|
49
|
+
permit_action
|
50
|
+
controller.can?(:read, comment).must_equal true
|
51
|
+
end
|
52
|
+
|
53
|
+
it "can receive a specified policy class" do
|
54
|
+
klass = Class.new(Lawkeeper::Policy) do
|
55
|
+
def read?
|
56
|
+
true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
controller.can?(:read, comment, klass).must_equal true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#authorize' do
|
64
|
+
it "sets the authorized header to 'true' if the user can run the action" do
|
65
|
+
permit_action
|
66
|
+
controller.authorize(comment, :read)
|
67
|
+
controller.headers['Lawkeeper-Authorized'].must_equal 'true'
|
68
|
+
end
|
69
|
+
|
70
|
+
it "skips setting the header if Lawkeeper.skip_set_headers is true" do
|
71
|
+
Lawkeeper.skip_set_headers = true
|
72
|
+
|
73
|
+
permit_action
|
74
|
+
controller.authorize(comment, :read)
|
75
|
+
controller.headers['Lawkeeper-Authorized'].must_be_nil 'true'
|
76
|
+
|
77
|
+
Lawkeeper.skip_set_headers = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
it "raises NotAuthorized if the user is not permitted the action" do
|
81
|
+
lambda {
|
82
|
+
controller.authorize(comment, :read)
|
83
|
+
}.must_raise(Lawkeeper::NotAuthorized)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "can receive a specified policy class" do
|
87
|
+
klass = Class.new(Lawkeeper::Policy) do
|
88
|
+
def read?
|
89
|
+
true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
controller.authorize(comment, :read, klass)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '#skip_authorization' do
|
97
|
+
it "sets the authorized header to 'true'" do
|
98
|
+
controller.skip_authorization
|
99
|
+
controller.headers['Lawkeeper-Skipped'].must_equal 'true'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lawkeeper, 'middlewares' do
|
4
|
+
describe Lawkeeper::EnsureWare do
|
5
|
+
it "returns the result of calling the app if Lawkeeper-Authorized is 'true'"
|
6
|
+
it "returns the result of calling the app if Lawkeeper-Skipped is 'true'"
|
7
|
+
it "returns the result of calling the app if no Lawkeeper header is set"
|
8
|
+
it "deletes the lawkeeper headers"
|
9
|
+
end
|
10
|
+
|
11
|
+
describe Lawkeeper::ScrubWare do
|
12
|
+
it "deletes the lawkeeper headers"
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Post; end
|
4
|
+
class PostPolicy; end
|
5
|
+
class SpecifiedPolicy; end
|
6
|
+
|
7
|
+
describe Lawkeeper::PolicyLookup do
|
8
|
+
it "returns PostPolicy for a Post instance" do
|
9
|
+
Lawkeeper::PolicyLookup[Post.new].must_equal PostPolicy
|
10
|
+
end
|
11
|
+
|
12
|
+
it "prefers the instance#policy_class if available" do
|
13
|
+
post = Post.new
|
14
|
+
def post.policy_class
|
15
|
+
SpecifiedPolicy
|
16
|
+
end
|
17
|
+
Lawkeeper::PolicyLookup[post].must_equal SpecifiedPolicy
|
18
|
+
end
|
19
|
+
|
20
|
+
it "raises NotDefined if the policy can not be found" do
|
21
|
+
lambda {
|
22
|
+
Lawkeeper::PolicyLookup[Object.new]
|
23
|
+
}.must_raise(Lawkeeper::NotDefined)
|
24
|
+
end
|
25
|
+
end
|
data/spec/policy_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lawkeeper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brendon Murphy
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
requirement: &2152801400 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2152801400
|
25
|
+
description: Lawkeeper - Simple authorization policies for Rack apps
|
26
|
+
email:
|
27
|
+
- xternal1+github@gmail.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- LICENSE
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- lawkeeper.gemspec
|
38
|
+
- lib/lawkeeper.rb
|
39
|
+
- lib/lawkeeper/version.rb
|
40
|
+
- spec/helpers_spec.rb
|
41
|
+
- spec/middleware_spec.rb
|
42
|
+
- spec/policy_lookup_spec.rb
|
43
|
+
- spec/policy_spec.rb
|
44
|
+
- spec/spec_helper.rb
|
45
|
+
homepage: ''
|
46
|
+
licenses: []
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.8.15
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: Lawkeeper - Simple authorization policies for Rack apps
|
69
|
+
test_files:
|
70
|
+
- spec/helpers_spec.rb
|
71
|
+
- spec/middleware_spec.rb
|
72
|
+
- spec/policy_lookup_spec.rb
|
73
|
+
- spec/policy_spec.rb
|
74
|
+
- spec/spec_helper.rb
|