heimdallr-resource 1.0.1 → 1.0.2
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/CHANGELOG.md +5 -0
- data/README.md +81 -23
- data/heimdallr-resource.gemspec +1 -1
- data/lib/heimdallr/resource.rb +101 -26
- data/spec/dummy/app/controllers/entity_controller.rb +4 -0
- data/spec/dummy/config/routes.rb +5 -1
- data/spec/resource_spec.rb +8 -0
- metadata +12 -12
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.0.2
|
4
|
+
|
5
|
+
* Additional methods resources assignation [@whitequark][]
|
6
|
+
|
3
7
|
## 1.0.1
|
4
8
|
|
5
9
|
* load_and_authorize_resource accepts just one hash parameter from now [@_inossidabile][]
|
6
10
|
|
7
11
|
[@_inossidabile]: http://twitter.com/#!/_inossidabile
|
12
|
+
[@whitequark]: http://twitter.com/#!/whitequark
|
data/README.md
CHANGED
@@ -5,43 +5,101 @@ Heimdallr Resource is a gem which provides CanCan-like interface for writing sec
|
|
5
5
|
controllers on top of [Heimdallr](http://github.com/roundlake/heimdallr)-protected
|
6
6
|
models.
|
7
7
|
|
8
|
-
|
8
|
+
Overview
|
9
|
+
--------
|
10
|
+
|
11
|
+
API of Heimdallr Resource basically consists of two methods, `load_resource` and `authorize_resource`.
|
12
|
+
Both work by adding a filter in standard Rails filter chain and obey the `:only` and `:except` options.
|
13
|
+
|
14
|
+
`load_resource` loads a record or scope and wraps it in a Heimadllr proxy. For `index` action, a scope is loaded. For `show`, `new`, `create`, `edit`, `update` and `destroy` a record is loaded. No further action is performed by Heimdallr Resource.
|
15
|
+
|
16
|
+
`authorize_resource` verifies if the current security context allows for creating or updating the records. The checks are performed for `new`, `create`, `edit` and `update` actions. `index` will simply follow the defined `:fetch` scope.
|
17
|
+
|
18
|
+
```ruby
|
9
19
|
class CricketController < ApplicationController
|
10
20
|
include Heimdallr::Resource
|
11
21
|
|
12
22
|
load_and_authorize_resource
|
13
23
|
|
14
|
-
# or set the name explicitly:
|
15
|
-
#
|
16
|
-
# load_and_authorize_resource :resource => :cricket
|
17
|
-
|
18
|
-
# if nested:
|
19
|
-
#
|
20
|
-
# routes.rb:
|
21
|
-
# resources :categories do
|
22
|
-
# resources :crickets
|
23
|
-
# end
|
24
|
-
#
|
25
|
-
# load_and_authorize_resource :through => :category
|
26
|
-
|
27
24
|
def index
|
28
25
|
# @crickets is loaded and secured here
|
29
26
|
end
|
27
|
+
|
28
|
+
def show
|
29
|
+
# @cricket is loaded by .find(params[:id]) and secured here
|
30
|
+
end
|
31
|
+
|
32
|
+
def create
|
33
|
+
# @cricket is created, filled with params[:cricket] and secured here
|
34
|
+
end
|
35
|
+
|
36
|
+
def update
|
37
|
+
# @cricket is loaded by .find(params[:id]) and secured here.
|
38
|
+
# Fields from params[:cricket] won't be applied automatically!
|
39
|
+
end
|
40
|
+
|
41
|
+
def show
|
42
|
+
# @cricket is loaded by .find(params[:id]) and secured here.
|
43
|
+
end
|
44
|
+
|
45
|
+
def destroy
|
46
|
+
# @cricket is loaded by .find(params[:id]) and secured here.
|
47
|
+
end
|
30
48
|
end
|
31
49
|
```
|
32
50
|
|
33
|
-
|
34
|
-
|
51
|
+
Custom entity
|
52
|
+
-------------
|
35
53
|
|
36
|
-
|
37
|
-
|
54
|
+
To explicitly specify which class should be used as a Heimdallr model you can use the following option:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
# This will use the Entity class
|
58
|
+
load_and_authorize :resource => :'entity'
|
59
|
+
# This will use the Namespace::OtherEntity class
|
60
|
+
load_and_authorize :resource => :'namespace/other_entity'
|
61
|
+
```
|
62
|
+
|
63
|
+
Namespaces
|
64
|
+
----------
|
65
|
+
By default Heimdallr Resource will seek for the namespace just like it does with the class. So for `Foo::Bars` controller it will try to bind to `Foo::Bar` model.
|
66
|
+
|
67
|
+
Custom methods (besides CRUD)
|
68
|
+
-----------------------------
|
69
|
+
|
70
|
+
By default Heimdallr Resource will consider non-CRUD methods a `:record` methods (like `show`). So it will try to find entity using `params[:id]`. To modify this behavior to make it work like `index` or `create`, you can explicitly define the way it should handle the methods.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
load_and_authorize :collection => [:search], :new_record => [:special_create], :record => [:attack]
|
74
|
+
```
|
75
|
+
|
76
|
+
Inlined resources
|
77
|
+
-----------------
|
78
|
+
If you have inlined resource with such routing:
|
38
79
|
|
39
|
-
|
40
|
-
|
41
|
-
|
80
|
+
```ruby
|
81
|
+
resources :foos do
|
82
|
+
resources :bars do
|
83
|
+
resources :bazs
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
Rails will provide `params[:foo_id]` and `params[:bar_id]` inside `BazsController`. To make Heimdallr search through and assign the parent entities you can use this syntax:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
load_and_authorize_resource :through => :foo
|
92
|
+
# or even
|
93
|
+
load_and_authorize_resource :through => [:foo, :bar]
|
94
|
+
```
|
95
|
+
|
96
|
+
If the whole path or some if its parts are optional, you can specify the `:shallow` option.
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
load_and_authorize_resource :through => [:foo, :bar], :shallow => true
|
100
|
+
```
|
42
101
|
|
43
|
-
|
44
|
-
The checks are performed for `new`, `create`, `edit` and `update` actions.
|
102
|
+
In the latter case it will work from any route, the direct or inlined one.
|
45
103
|
|
46
104
|
Credits
|
47
105
|
-------
|
data/heimdallr-resource.gemspec
CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "heimdallr-resource"
|
6
|
-
s.version = "1.0.
|
6
|
+
s.version = "1.0.2"
|
7
7
|
s.authors = ["Peter Zotov", "Boris Staal"]
|
8
8
|
s.email = ["whitequark@whitequark.org", "boris@roundlake.ru"]
|
9
9
|
s.homepage = "http://github.com/roundlake/heimdallr-resource"
|
data/lib/heimdallr/resource.rb
CHANGED
@@ -5,7 +5,7 @@ module Heimdallr
|
|
5
5
|
module ResourceImplementation
|
6
6
|
class << self
|
7
7
|
def prepare_options(klass, options)
|
8
|
-
options.merge
|
8
|
+
options = options.merge :resource => (options[:resource] || klass.name.sub(/Controller$/, '').underscore).to_s
|
9
9
|
|
10
10
|
filter_options = {}
|
11
11
|
filter_options[:only] = options.delete(:only) if options.has_key?(:only)
|
@@ -17,60 +17,125 @@ module Heimdallr
|
|
17
17
|
def load(controller, options)
|
18
18
|
unless controller.instance_variable_defined?(ivar_name(controller, options))
|
19
19
|
if options.has_key? :through
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
target = load_target(controller, options)
|
21
|
+
|
22
|
+
if target
|
23
|
+
if options[:singleton]
|
24
|
+
scope = target.send(:"#{options[:resource].parameterize('_')}")
|
25
|
+
else
|
26
|
+
scope = target.send(:"#{options[:resource].parameterize('_').pluralize}")
|
27
|
+
end
|
28
|
+
elsif options[:shallow]
|
29
|
+
scope = options[:resource].classify.constantize.scoped
|
23
30
|
else
|
24
|
-
|
25
|
-
send(:"#{options[:resource].pluralize}")
|
31
|
+
raise "Cannot fetch #{options[:resource]} via #{options[:through]}"
|
26
32
|
end
|
27
33
|
else
|
28
|
-
scope = options[:resource].
|
34
|
+
scope = options[:resource].classify.constantize.scoped
|
29
35
|
end
|
30
36
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
loaders = {
|
38
|
+
collection: -> {
|
39
|
+
controller.instance_variable_set(ivar_name(controller, options), scope)
|
40
|
+
},
|
41
|
+
|
42
|
+
new_record: -> {
|
43
|
+
controller.instance_variable_set(ivar_name(controller, options),
|
44
|
+
scope.new(controller.params[options[:resource].split('/').last]))
|
45
|
+
},
|
46
|
+
|
47
|
+
record: -> {
|
48
|
+
controller.instance_variable_set(ivar_name(controller, options),
|
49
|
+
scope.find(controller.params[:"#{options[:resource]}_id"] ||
|
50
|
+
controller.params[:id]))
|
51
|
+
},
|
52
|
+
|
53
|
+
related_record: -> {
|
54
|
+
if controller.params[:"#{options[:resource]}_id"]
|
55
|
+
controller.instance_variable_set(ivar_name(controller, options),
|
56
|
+
scope.find(controller.params[:"#{options[:resource]}_id"]))
|
57
|
+
end
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
loaders[action_type(controller.params[:action], options)].()
|
42
62
|
end
|
43
63
|
end
|
44
64
|
|
45
65
|
def authorize(controller, options)
|
46
|
-
controller.
|
47
|
-
|
66
|
+
value = controller.instance_variable_get(ivar_name(controller, options))
|
67
|
+
return unless value
|
68
|
+
|
69
|
+
controller.instance_variable_set(ivar_name(controller, options.merge(:insecure => true)), value)
|
48
70
|
|
49
|
-
value =
|
50
|
-
restrict(controller.security_context)
|
71
|
+
value = value.restrict(controller.security_context)
|
51
72
|
controller.instance_variable_set(ivar_name(controller, options), value)
|
52
73
|
|
53
74
|
case controller.params[:action]
|
54
75
|
when 'new', 'create'
|
76
|
+
value.assign_attributes(value.reflect_on_security[:restrictions].fixtures[:create])
|
77
|
+
|
55
78
|
unless value.reflect_on_security[:operations].include? :create
|
56
79
|
raise Heimdallr::AccessDenied, "Cannot create model"
|
57
80
|
end
|
81
|
+
|
58
82
|
when 'edit', 'update'
|
83
|
+
value.assign_attributes(value.reflect_on_security[:restrictions].fixtures[:update])
|
84
|
+
|
59
85
|
unless value.reflect_on_security[:operations].include? :update
|
60
86
|
raise Heimdallr::AccessDenied, "Cannot update model"
|
61
87
|
end
|
88
|
+
|
62
89
|
when 'destroy'
|
63
90
|
unless value.destroyable?
|
64
91
|
raise Heimdallr::AccessDenied, "Cannot delete model"
|
65
92
|
end
|
66
|
-
end
|
93
|
+
end unless options[:related]
|
94
|
+
end
|
95
|
+
|
96
|
+
def load_target(controller, options)
|
97
|
+
Array.wrap(options[:through]).map do |parent|
|
98
|
+
loaded = controller.instance_variable_get(:"@#{parent}")
|
99
|
+
unless loaded
|
100
|
+
load(controller, :resource => parent.to_s, :related => true)
|
101
|
+
loaded = controller.instance_variable_get(:"@#{parent}")
|
102
|
+
end
|
103
|
+
if loaded && options[:authorize_chain]
|
104
|
+
authorize(controller, :resource => parent.to_s, :related => true)
|
105
|
+
end
|
106
|
+
controller.instance_variable_get(:"@#{parent}")
|
107
|
+
end.reject(&:nil?).first
|
67
108
|
end
|
68
109
|
|
69
110
|
def ivar_name(controller, options)
|
70
|
-
if controller.params[:action] ==
|
71
|
-
:"@#{options[:resource].pluralize}"
|
111
|
+
if action_type(controller.params[:action], options) == :collection
|
112
|
+
:"@#{options[:resource].parameterize('_').pluralize}"
|
72
113
|
else
|
73
|
-
:"@#{options[:resource]}"
|
114
|
+
:"@#{options[:resource].parameterize('_')}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def action_type(action, options)
|
119
|
+
if options[:related]
|
120
|
+
:related_record
|
121
|
+
else
|
122
|
+
action = action.to_sym
|
123
|
+
case action
|
124
|
+
when :index
|
125
|
+
:collection
|
126
|
+
when :new, :create
|
127
|
+
:new_record
|
128
|
+
when :show, :edit, :update, :destroy
|
129
|
+
:record
|
130
|
+
else
|
131
|
+
if options[:collection] && options[:collection].include?(action)
|
132
|
+
:collection
|
133
|
+
elsif options[:new] && options[:new].include?(action)
|
134
|
+
:new_record
|
135
|
+
else
|
136
|
+
:record
|
137
|
+
end
|
138
|
+
end
|
74
139
|
end
|
75
140
|
end
|
76
141
|
end
|
@@ -82,12 +147,14 @@ module Heimdallr
|
|
82
147
|
|
83
148
|
module ClassMethods
|
84
149
|
def load_and_authorize_resource(options={})
|
150
|
+
options[:authorize_chain] = true
|
85
151
|
load_resource(options)
|
86
152
|
authorize_resource(options)
|
87
153
|
end
|
88
154
|
|
89
155
|
def load_resource(options={})
|
90
156
|
options, filter_options = Heimdallr::ResourceImplementation.prepare_options(self, options)
|
157
|
+
self.own_heimdallr_options = options
|
91
158
|
|
92
159
|
before_filter filter_options do |controller|
|
93
160
|
Heimdallr::ResourceImplementation.load(controller, options)
|
@@ -96,11 +163,19 @@ module Heimdallr
|
|
96
163
|
|
97
164
|
def authorize_resource(options={})
|
98
165
|
options, filter_options = Heimdallr::ResourceImplementation.prepare_options(self, options)
|
166
|
+
self.own_heimdallr_options = options
|
99
167
|
|
100
168
|
before_filter filter_options do |controller|
|
101
169
|
Heimdallr::ResourceImplementation.authorize(controller, options)
|
102
170
|
end
|
103
171
|
end
|
172
|
+
|
173
|
+
protected
|
174
|
+
|
175
|
+
def own_heimdallr_options=(options)
|
176
|
+
cattr_accessor :heimdallr_options
|
177
|
+
self.heimdallr_options = options
|
178
|
+
end
|
104
179
|
end
|
105
180
|
end
|
106
181
|
end
|
data/spec/dummy/config/routes.rb
CHANGED
data/spec/resource_spec.rb
CHANGED
@@ -69,5 +69,13 @@ describe EntityController, :type => :controller do
|
|
69
69
|
User.mock @maria
|
70
70
|
expect { post :destroy, {:id => @public.id} }.should raise_error
|
71
71
|
end
|
72
|
+
|
73
|
+
it "assigns the custom methods" do
|
74
|
+
User.mock @john
|
75
|
+
post :penetrate, {:id => @public.id}
|
76
|
+
|
77
|
+
assigns(:entity).should be_kind_of Heimdallr::Proxy::Record
|
78
|
+
assigns(:entity).id.should == @public.id
|
79
|
+
end
|
72
80
|
end
|
73
81
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: heimdallr-resource
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-04-
|
13
|
+
date: 2012-04-12 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rspec-rails
|
17
|
-
requirement: &
|
17
|
+
requirement: &70169121125720 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: '0'
|
23
23
|
type: :development
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70169121125720
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: activerecord
|
28
|
-
requirement: &
|
28
|
+
requirement: &70169121125180 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70169121125180
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: sqlite3
|
39
|
-
requirement: &
|
39
|
+
requirement: &70169121124440 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ! '>='
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: '0'
|
45
45
|
type: :development
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *70169121124440
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: tzinfo
|
50
|
-
requirement: &
|
50
|
+
requirement: &70169121116540 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ! '>='
|
@@ -55,10 +55,10 @@ dependencies:
|
|
55
55
|
version: '0'
|
56
56
|
type: :development
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *70169121116540
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: heimdallr
|
61
|
-
requirement: &
|
61
|
+
requirement: &70169121115860 !ruby/object:Gem::Requirement
|
62
62
|
none: false
|
63
63
|
requirements:
|
64
64
|
- - ! '>='
|
@@ -66,7 +66,7 @@ dependencies:
|
|
66
66
|
version: '0'
|
67
67
|
type: :runtime
|
68
68
|
prerelease: false
|
69
|
-
version_requirements: *
|
69
|
+
version_requirements: *70169121115860
|
70
70
|
description: Heimdallr-Resource provides CanCan-like interface for Heimdallr-secured
|
71
71
|
objects.
|
72
72
|
email:
|