heimdallr 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 +4 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/heimdallr.gemspec +28 -0
- data/lib/heimdallr.rb +9 -0
- data/lib/heimdallr/evaluator.rb +94 -0
- data/lib/heimdallr/model.rb +35 -0
- data/lib/heimdallr/proxy.rb +61 -0
- data/lib/heimdallr/resource.rb +111 -0
- data/lib/heimdallr/version.rb +3 -0
- data/spec/proxy_spec.rb +5 -0
- data/spec/spec_helper.rb +5 -0
- metadata +105 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/heimdallr.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "heimdallr/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "heimdallr"
|
7
|
+
s.version = Heimdallr::VERSION
|
8
|
+
s.authors = ["Peter Zotov"]
|
9
|
+
s.email = ["whitequark@whitequark.org"]
|
10
|
+
s.homepage = "http://github.com/roundlake/heimdallr"
|
11
|
+
s.summary = %q{Heimdallr is an ActiveModel extension which provides object- and field-level access control.}
|
12
|
+
s.description = %q{Heimdallr aims to provide an easy to configure and efficient object- and field-level access
|
13
|
+
control solution, reusing proven patterns from gems like CanCan and allowing one to control permissions in a very
|
14
|
+
fine-grained manner.}
|
15
|
+
|
16
|
+
s.rubyforge_project = "heimdallr"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
s.add_runtime_dependency "activesupport", '>= 3.0.0'
|
24
|
+
s.add_runtime_dependency "activemodel", '>= 3.0.0'
|
25
|
+
|
26
|
+
s.add_development_dependency "rspec"
|
27
|
+
s.add_development_dependency "activerecord"
|
28
|
+
end
|
data/lib/heimdallr.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module Heimdallr
|
2
|
+
class Evaluator
|
3
|
+
attr_reader :whitelist, :validations
|
4
|
+
|
5
|
+
def initialize(model_class, &block)
|
6
|
+
@model_class, @block = model_class, block
|
7
|
+
|
8
|
+
@whitelist = @validations = nil
|
9
|
+
@last_context = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def evaluate(context)
|
13
|
+
if context != @last_context
|
14
|
+
@whitelist = Hash.new { [] }
|
15
|
+
@validations = Hash.new { [] }
|
16
|
+
|
17
|
+
instance_exec context, &block
|
18
|
+
|
19
|
+
@whitelist.freeze
|
20
|
+
@validations.freeze
|
21
|
+
|
22
|
+
@last_context = context
|
23
|
+
end
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate(action, record)
|
29
|
+
@validations[action].each do |validator|
|
30
|
+
validator.validate(record)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def can(actions, fields=@model_class.attribute_names)
|
35
|
+
actions = Array(actions)
|
36
|
+
|
37
|
+
case fields
|
38
|
+
when Hash # a list of validations
|
39
|
+
actions.each do |action|
|
40
|
+
@whitelist[action] += fields.keys
|
41
|
+
@validations[action] += make_validators(fields)
|
42
|
+
end
|
43
|
+
|
44
|
+
else # an array or a field name
|
45
|
+
actions.each do |action|
|
46
|
+
@whitelist[action] += Array(fields)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def cannot(actions, fields)
|
52
|
+
actions = Array(actions)
|
53
|
+
|
54
|
+
actions.each do |action|
|
55
|
+
@whitelist[action] -= fields
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def make_validators(fields)
|
62
|
+
validators = []
|
63
|
+
|
64
|
+
fields.each do |attribute, validations|
|
65
|
+
validations.each do |key, options|
|
66
|
+
key = "#{key.to_s.camelize}Validator"
|
67
|
+
|
68
|
+
begin
|
69
|
+
validator = key.include?('::') ? key.constantize : ActiveModel::Validations.const_get(key)
|
70
|
+
rescue NameError
|
71
|
+
raise ArgumentError, "Unknown validator: '#{key}'"
|
72
|
+
end
|
73
|
+
|
74
|
+
validators << validator.new(_parse_validates_options(options).merge(:attributes => [ attribute ]))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
validators
|
79
|
+
end
|
80
|
+
|
81
|
+
def _parse_validates_options(options) #:nodoc:
|
82
|
+
case options
|
83
|
+
when TrueClass
|
84
|
+
{}
|
85
|
+
when Hash
|
86
|
+
options
|
87
|
+
when Range, Array
|
88
|
+
{ :in => options }
|
89
|
+
else
|
90
|
+
{ :with => options }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Heimdallr
|
2
|
+
module Model
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def restrict(&block)
|
7
|
+
@restrictions = Evaluator.new(self, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def restricted?
|
11
|
+
!@restrictions.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def restrictions(context)
|
15
|
+
@restrictions.evaluate(context)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods
|
20
|
+
def to_proxy(context, action)
|
21
|
+
if self.class.restricted?
|
22
|
+
Proxy.new(context, action, self)
|
23
|
+
else
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate_action(context, action)
|
29
|
+
if self.class.restricted?
|
30
|
+
self.class.restrictions(context).validate(action, self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Heimdallr
|
2
|
+
class Proxy
|
3
|
+
def initialize(context, action, object)
|
4
|
+
@context, @action, @object = context, action, object
|
5
|
+
|
6
|
+
@whitelist = @object.class.restrictions(context).whitelist[@action]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.filter_attributes(attributes, whitelist)
|
10
|
+
attributes.delete_if do |key, value|
|
11
|
+
!whitelist.include?(key)
|
12
|
+
end
|
13
|
+
|
14
|
+
attributes
|
15
|
+
end
|
16
|
+
|
17
|
+
def attributes
|
18
|
+
self.class.filter_attributes(@object.attributes, @whitelist)
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_attributes(attributes)
|
22
|
+
@object.update_attributes(self.class.filter_attributes(attributes, @whitelist))
|
23
|
+
end
|
24
|
+
|
25
|
+
def update_attributes!(attributes)
|
26
|
+
@object.update_attributes!(self.class.filter_attributes(attributes, @whitelist))
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(method, *args)
|
30
|
+
if method.to_s.ends_with?("?") || method.to_s.ends_with?("=")
|
31
|
+
normalized_method = method[0..-2].to_sym
|
32
|
+
else
|
33
|
+
normalized_method = method
|
34
|
+
end
|
35
|
+
|
36
|
+
if defined?(ActiveRecord) && @object.is_a?(ActiveRecord::Base) &&
|
37
|
+
association = @object.class.reflect_on_association(method)
|
38
|
+
if association.collection?
|
39
|
+
raise "not implemented"
|
40
|
+
else
|
41
|
+
referenced = @object.send(method, *args)
|
42
|
+
if referenced.respond_to? :to_proxy
|
43
|
+
referenced.to_proxy(@context, @action)
|
44
|
+
else
|
45
|
+
referenced
|
46
|
+
end
|
47
|
+
end
|
48
|
+
elsif @whitelist.include? normalized_method
|
49
|
+
@object.send method, *args
|
50
|
+
elsif @object.respond_to? method
|
51
|
+
nil
|
52
|
+
else
|
53
|
+
super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
"#<Heimdallr::Proxy(#{@whitelist.join ", "}): #{@object.inspect}>"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Heimdallr
|
2
|
+
module Resource
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
attr_reader :model
|
7
|
+
|
8
|
+
def resource_for(model, options={})
|
9
|
+
@model = model.to_s.capitalize.constantize
|
10
|
+
|
11
|
+
before_filter :load_all_resources, only: [ :index ].concat(options[:all] || [])
|
12
|
+
before_filter :load_one_resource, only: [ :show ].concat(options[:member] || [])
|
13
|
+
before_filter :load_referenced_resources, only: [ :update, :destroy ].concat(options[:collection] || [])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
def index
|
19
|
+
end
|
20
|
+
|
21
|
+
def show
|
22
|
+
end
|
23
|
+
|
24
|
+
def new
|
25
|
+
render :json => {
|
26
|
+
:fields => model.restrictions(security_context).whitelist[:create]
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def create
|
31
|
+
model.transaction do
|
32
|
+
if params.has_key? model.name.underscore
|
33
|
+
scoped_model.new.to_proxy(security_context, :create).
|
34
|
+
update_attributes!(params[model.name.underscore])
|
35
|
+
else
|
36
|
+
@resources.each_with_index do |resource, index|
|
37
|
+
scoped_model.new.to_proxy(security_context, :create).
|
38
|
+
update_attributes!(params[model.name.underscore.pluralize][index])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
if block_given?
|
44
|
+
yield
|
45
|
+
else
|
46
|
+
render_modified_resources
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def edit
|
51
|
+
render :json => {
|
52
|
+
:fields => model.restrictions(security_context).whitelist[:update]
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def update
|
57
|
+
model.transaction do
|
58
|
+
if params.has_key? model.name.underscore
|
59
|
+
@resources.first.to_proxy(security_context, :update).
|
60
|
+
update_attributes!(params[model.name.underscore])
|
61
|
+
else
|
62
|
+
@resources.each_with_index do |resource, index|
|
63
|
+
resource.to_proxy(security_context, :update).
|
64
|
+
update_attributes!(params[model.name.underscore.pluralize][index])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if block_given?
|
70
|
+
yield
|
71
|
+
else
|
72
|
+
render_modified_resources
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def destroy
|
77
|
+
model.transaction do
|
78
|
+
@resources.each &:destroy
|
79
|
+
end
|
80
|
+
|
81
|
+
render :json => {}, :status => :ok
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
def model
|
87
|
+
self.class.model
|
88
|
+
end
|
89
|
+
|
90
|
+
def scoped_model
|
91
|
+
self.model.scoped
|
92
|
+
end
|
93
|
+
|
94
|
+
def load_one_resource
|
95
|
+
@resource = scoped_model.find(params[:id])
|
96
|
+
end
|
97
|
+
|
98
|
+
def load_all_resources
|
99
|
+
@resources = scoped_model.scoped
|
100
|
+
end
|
101
|
+
|
102
|
+
def load_referenced_resources
|
103
|
+
@resources = scoped_model.find(params[:id].split(','))
|
104
|
+
end
|
105
|
+
|
106
|
+
def render_modified_resources
|
107
|
+
render :action => :index
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/spec/proxy_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: heimdallr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Peter Zotov
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: &80513130 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *80513130
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activemodel
|
27
|
+
requirement: &80522090 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.0.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *80522090
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &80518720 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *80518720
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: activerecord
|
49
|
+
requirement: &79906240 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *79906240
|
58
|
+
description: ! "Heimdallr aims to provide an easy to configure and efficient object-
|
59
|
+
and field-level access\n control solution, reusing proven patterns from gems like
|
60
|
+
CanCan and allowing one to control permissions in a very\n fine-grained manner."
|
61
|
+
email:
|
62
|
+
- whitequark@whitequark.org
|
63
|
+
executables: []
|
64
|
+
extensions: []
|
65
|
+
extra_rdoc_files: []
|
66
|
+
files:
|
67
|
+
- .gitignore
|
68
|
+
- .rspec
|
69
|
+
- Gemfile
|
70
|
+
- Rakefile
|
71
|
+
- heimdallr.gemspec
|
72
|
+
- lib/heimdallr.rb
|
73
|
+
- lib/heimdallr/evaluator.rb
|
74
|
+
- lib/heimdallr/model.rb
|
75
|
+
- lib/heimdallr/proxy.rb
|
76
|
+
- lib/heimdallr/resource.rb
|
77
|
+
- lib/heimdallr/version.rb
|
78
|
+
- spec/proxy_spec.rb
|
79
|
+
- spec/spec_helper.rb
|
80
|
+
homepage: http://github.com/roundlake/heimdallr
|
81
|
+
licenses: []
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project: heimdallr
|
100
|
+
rubygems_version: 1.8.10
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: Heimdallr is an ActiveModel extension which provides object- and field-level
|
104
|
+
access control.
|
105
|
+
test_files: []
|