aegis 1.1.8 → 2.0.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/.gitignore +4 -0
- data/README.rdoc +58 -165
- data/Rakefile +20 -12
- data/VERSION +1 -1
- data/aegis.gemspec +85 -56
- data/lib/aegis.rb +9 -6
- data/lib/aegis/access_denied.rb +4 -0
- data/lib/aegis/action.rb +99 -0
- data/lib/aegis/compiler.rb +113 -0
- data/lib/aegis/has_role.rb +89 -110
- data/lib/aegis/parser.rb +110 -0
- data/lib/aegis/permissions.rb +164 -107
- data/lib/aegis/resource.rb +158 -0
- data/lib/aegis/role.rb +25 -55
- data/lib/aegis/sieve.rb +39 -0
- data/lib/rails/action_controller.rb +38 -0
- data/lib/rails/active_record.rb +1 -5
- data/spec/action_controller_spec.rb +100 -0
- data/spec/app_root/app/controllers/application_controller.rb +7 -0
- data/spec/app_root/app/controllers/reviews_controller.rb +36 -0
- data/spec/app_root/app/models/permissions.rb +14 -0
- data/spec/app_root/app/models/property.rb +5 -0
- data/spec/app_root/app/models/review.rb +5 -0
- data/{test → spec}/app_root/app/models/user.rb +1 -2
- data/{test → spec}/app_root/config/boot.rb +0 -0
- data/{test → spec}/app_root/config/database.yml +0 -0
- data/{test → spec}/app_root/config/environment.rb +0 -0
- data/{test → spec}/app_root/config/environments/in_memory.rb +0 -0
- data/{test → spec}/app_root/config/environments/mysql.rb +0 -0
- data/{test → spec}/app_root/config/environments/postgresql.rb +0 -0
- data/{test → spec}/app_root/config/environments/sqlite.rb +0 -0
- data/{test → spec}/app_root/config/environments/sqlite3.rb +0 -0
- data/spec/app_root/config/routes.rb +7 -0
- data/{test/app_root/db/migrate/20090408115228_create_users.rb → spec/app_root/db/migrate/001_create_users.rb} +2 -1
- data/spec/app_root/db/migrate/002_create_properties.rb +13 -0
- data/spec/app_root/db/migrate/003_create_reviews.rb +14 -0
- data/{test → spec}/app_root/lib/console_with_fixtures.rb +0 -0
- data/{test → spec}/app_root/log/.gitignore +0 -0
- data/{test → spec}/app_root/script/console +0 -0
- data/spec/controllers/reviews_controller_spec.rb +19 -0
- data/spec/has_role_spec.rb +177 -0
- data/spec/permissions_spec.rb +550 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +4 -0
- data/{test/test_helper.rb → spec/spec_helper.rb} +6 -9
- metadata +73 -57
- data/lib/aegis/constants.rb +0 -6
- data/lib/aegis/normalization.rb +0 -26
- data/lib/aegis/permission_error.rb +0 -5
- data/lib/aegis/permission_evaluator.rb +0 -34
- data/test/app_root/app/controllers/application_controller.rb +0 -2
- data/test/app_root/app/models/old_soldier.rb +0 -6
- data/test/app_root/app/models/permissions.rb +0 -49
- data/test/app_root/app/models/soldier.rb +0 -5
- data/test/app_root/app/models/trust_fund_kid.rb +0 -5
- data/test/app_root/app/models/user_subclass.rb +0 -2
- data/test/app_root/app/models/veteran_soldier.rb +0 -6
- data/test/app_root/config/routes.rb +0 -4
- data/test/app_root/db/migrate/20090429075648_create_soldiers.rb +0 -14
- data/test/app_root/db/migrate/20091110075648_create_veteran_soldiers.rb +0 -14
- data/test/app_root/db/migrate/20091110075649_create_trust_fund_kids.rb +0 -15
- data/test/has_role_options_test.rb +0 -64
- data/test/has_role_test.rb +0 -54
- data/test/permissions_test.rb +0 -109
- data/test/validation_test.rb +0 -55
data/lib/aegis.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
require 'aegis/access_denied'
|
4
|
+
require 'aegis/action'
|
5
|
+
require 'aegis/compiler'
|
3
6
|
require 'aegis/has_role'
|
4
|
-
require 'aegis/
|
5
|
-
require 'aegis/permission_error'
|
6
|
-
require 'aegis/permission_evaluator'
|
7
|
+
require 'aegis/parser'
|
7
8
|
require 'aegis/permissions'
|
8
9
|
require 'aegis/role'
|
9
|
-
require '
|
10
|
+
require 'aegis/sieve'
|
10
11
|
|
12
|
+
require 'rails/action_controller'
|
13
|
+
require 'rails/active_record'
|
data/lib/aegis/action.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
module Aegis
|
2
|
+
class Action
|
3
|
+
|
4
|
+
attr_reader :name, :takes_object, :takes_parent_object, :writing, :sieves, :pluralize_resource
|
5
|
+
|
6
|
+
def initialize(name, options)
|
7
|
+
@name = name.to_s
|
8
|
+
@sieves = []
|
9
|
+
update(options, true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def update(options, use_defaults = false)
|
13
|
+
update_attribute(options, :takes_object, use_defaults, true)
|
14
|
+
update_attribute(options, :takes_parent_object, use_defaults, false)
|
15
|
+
update_attribute(options, :writing, use_defaults, true)
|
16
|
+
update_attribute(options, :pluralize_resource, use_defaults, false)
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_attribute(options, key, use_defaults, default)
|
20
|
+
value = options[key]
|
21
|
+
value = default if value.nil? && use_defaults
|
22
|
+
instance_variable_set("@#{key}", value) unless value.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def may?(user, *args)
|
26
|
+
context = extract_context(user, args)
|
27
|
+
may = user.role.may_by_default?
|
28
|
+
for sieve in sieves
|
29
|
+
opinion = sieve.may?(context, *args)
|
30
|
+
may = opinion unless opinion.nil?
|
31
|
+
end
|
32
|
+
may
|
33
|
+
end
|
34
|
+
|
35
|
+
def may!(user, *args)
|
36
|
+
may?(user, *args) or raise Aegis::AccessDenied, "Access denied: #{args.inspect}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.index(options = {})
|
40
|
+
new('index', options.reverse_merge(:takes_object => false, :pluralize_resource => true, :writing => false))
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.show(options = {})
|
44
|
+
new('show', options.reverse_merge(:takes_object => true, :writing => false))
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.update(options = {})
|
48
|
+
new('update', options.reverse_merge(:takes_object => true, :writing => true))
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.create(options = {})
|
52
|
+
new('create', options.reverse_merge(:takes_object => false, :writing => true))
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.destroy(options = {})
|
56
|
+
new('destroy', options.reverse_merge(:takes_object => true, :writing => true))
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.undefined
|
60
|
+
new(nil, :takes_object => false, :writing => true)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.allow_to_all
|
64
|
+
action = undefined
|
65
|
+
action.sieves << Aegis::Sieve.allow_to_all
|
66
|
+
action
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.deny_to_all
|
70
|
+
action = undefined
|
71
|
+
action.sieves << Aegis::Sieve.deny_to_all
|
72
|
+
action
|
73
|
+
end
|
74
|
+
|
75
|
+
def abstract?
|
76
|
+
name.blank?
|
77
|
+
end
|
78
|
+
|
79
|
+
def inspect
|
80
|
+
"Action(#{{ :name => name, :takes_object => takes_object, :takes_parent_object => takes_parent_object, :sieves => sieves }.inspect})"
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# not *args so we can change the array reference
|
86
|
+
def extract_context(user, args)
|
87
|
+
context = {}
|
88
|
+
context[:user] = user
|
89
|
+
if takes_parent_object
|
90
|
+
context[:parent_object] = args.shift or raise ArgumentError, "No parent object given"
|
91
|
+
end
|
92
|
+
if takes_object
|
93
|
+
context[:object] = args.shift or raise ArgumentError, "No object given"
|
94
|
+
end
|
95
|
+
OpenStruct.new(context)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Aegis
|
2
|
+
class Compiler
|
3
|
+
|
4
|
+
ATOM_GROUPS = {
|
5
|
+
:namespace => :structure,
|
6
|
+
:resource => :structure,
|
7
|
+
:resources => :structure,
|
8
|
+
:action => :structure,
|
9
|
+
:allow => :sieve,
|
10
|
+
:deny => :sieve,
|
11
|
+
:reading => :sieve,
|
12
|
+
:writing => :sieve
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(resource)
|
16
|
+
@resource = resource
|
17
|
+
end
|
18
|
+
|
19
|
+
def compile(atoms)
|
20
|
+
grouped_atoms = group_atoms(atoms)
|
21
|
+
for atom in grouped_atoms[:structure] || []
|
22
|
+
compile_structure(atom)
|
23
|
+
end
|
24
|
+
for atom in grouped_atoms[:sieve] || []
|
25
|
+
compile_sieve(atom)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.compile(resource, atoms)
|
30
|
+
new(resource).compile(atoms)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def compile_structure(atom)
|
36
|
+
case atom[:type]
|
37
|
+
when :action
|
38
|
+
compile_action(atom)
|
39
|
+
when :namespace
|
40
|
+
compile_namespace(atom)
|
41
|
+
when :resource
|
42
|
+
compile_child_resource(atom, :singleton)
|
43
|
+
when :resources
|
44
|
+
compile_child_resource(atom, :collection)
|
45
|
+
else
|
46
|
+
"Unexpected atom type: #{atom[:type]}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def compile_namespace(atom)
|
51
|
+
atom[:options].merge!(:only => [])
|
52
|
+
compile_child_resource(atom, :singleton)
|
53
|
+
end
|
54
|
+
|
55
|
+
def compile_action(atom)
|
56
|
+
action = @resource.create_or_update_action(
|
57
|
+
atom[:name],
|
58
|
+
create_action_options(atom[:options]),
|
59
|
+
update_action_options(atom[:options])
|
60
|
+
)
|
61
|
+
for sieve_atom in atom[:children]
|
62
|
+
compile_sieve(sieve_atom, [action])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def compile_sieve(atom, affected_actions = @resource.actions)
|
67
|
+
case atom[:type]
|
68
|
+
when :allow
|
69
|
+
for action in affected_actions
|
70
|
+
action.sieves << Aegis::Sieve.new(atom[:role_name], true, atom[:block])
|
71
|
+
end
|
72
|
+
when :deny
|
73
|
+
for action in affected_actions
|
74
|
+
action.sieves << Aegis::Sieve.new(atom[:role_name], false, atom[:block])
|
75
|
+
end
|
76
|
+
when :reading
|
77
|
+
for child in atom[:children]
|
78
|
+
compile_sieve(child, @resource.reading_actions)
|
79
|
+
end
|
80
|
+
when :writing
|
81
|
+
for child in atom[:children]
|
82
|
+
compile_sieve(child, @resource.writing_actions)
|
83
|
+
end
|
84
|
+
else
|
85
|
+
"Unexpected atom type: #{atom[:type]}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def compile_child_resource(atom, type)
|
90
|
+
child = Aegis::Resource.new(@resource, atom[:name], type, atom[:options])
|
91
|
+
@resource.children << child
|
92
|
+
Aegis::Compiler.compile(child, atom[:children])
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_action_options(options)
|
96
|
+
{ :takes_object => @resource.new_action_takes_object?(options),
|
97
|
+
:takes_parent_object => @resource.new_action_takes_parent_object?(options)
|
98
|
+
}.merge(update_action_options(options))
|
99
|
+
end
|
100
|
+
|
101
|
+
def update_action_options(options)
|
102
|
+
{ :writing => options[:writing],
|
103
|
+
:pluralize_resource => options[:collection] }
|
104
|
+
end
|
105
|
+
|
106
|
+
def group_atoms(atoms)
|
107
|
+
atoms.group_by do |atom|
|
108
|
+
ATOM_GROUPS[atom[:type]]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
data/lib/aegis/has_role.rb
CHANGED
@@ -1,110 +1,89 @@
|
|
1
|
-
module Aegis
|
2
|
-
module HasRole
|
3
|
-
|
4
|
-
def
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
else
|
68
|
-
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
true
|
91
|
-
else
|
92
|
-
respond_to_without_aegis_permissions?(symb, include_private)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
alias_method_chain :respond_to?, :aegis_permissions
|
97
|
-
|
98
|
-
def set_default_aegis_role_name
|
99
|
-
if new_record? && self.aegis_role_name.blank?
|
100
|
-
self.aegis_role_name = self.class.aegis_default_role_name
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
end
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
end
|
109
|
-
|
110
|
-
end
|
1
|
+
module Aegis
|
2
|
+
module HasRole
|
3
|
+
|
4
|
+
def has_role(options = {})
|
5
|
+
|
6
|
+
if options[:accessor]
|
7
|
+
options[:reader] = "#{options[:accessor]}"
|
8
|
+
options[:writer] = "#{options[:accessor]}="
|
9
|
+
options.delete(:accessor)
|
10
|
+
end
|
11
|
+
|
12
|
+
get_role_name = (options[:reader] || "role_name").to_sym
|
13
|
+
set_role_name = (options[:writer] || "role_name=").to_sym
|
14
|
+
|
15
|
+
permissions = lambda { Aegis::Permissions.app_permissions(options[:permissions]) }
|
16
|
+
|
17
|
+
may_pattern = /^may_(.+?)([\!\?])$/
|
18
|
+
|
19
|
+
send :define_method, :role do
|
20
|
+
permissions.call.find_role_by_name(send(get_role_name))
|
21
|
+
end
|
22
|
+
|
23
|
+
send :define_method, :role= do |role|
|
24
|
+
send(set_role_name, role.name)
|
25
|
+
end
|
26
|
+
|
27
|
+
metaclass.send :define_method, :validates_role do |*validate_options|
|
28
|
+
validate_options = validate_options[0] || {}
|
29
|
+
|
30
|
+
send :define_method, :validate_role do
|
31
|
+
role = permissions.call.find_role_by_name(send(get_role_name))
|
32
|
+
unless role
|
33
|
+
message = validate_options[:message] || I18n.translate('activerecord.errors.messages.inclusion')
|
34
|
+
errors.add get_role_name, message
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
validate :validate_role
|
39
|
+
end
|
40
|
+
|
41
|
+
if options[:default]
|
42
|
+
|
43
|
+
unless method_defined?(:after_initialize)
|
44
|
+
send :define_method, :after_initialize do
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
send :define_method, :set_default_role_name do
|
49
|
+
if new_record? && send(get_role_name).blank?
|
50
|
+
send(set_role_name, options[:default])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
after_initialize :set_default_role_name
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
unless method_defined?(:method_missing_with_aegis_permissions)
|
59
|
+
|
60
|
+
# Delegate may_...? and may_...! methods to the permissions class.
|
61
|
+
send :define_method, :method_missing_with_aegis_permissions do |symb, *args|
|
62
|
+
method_name = symb.to_s
|
63
|
+
if method_name =~ may_pattern
|
64
|
+
action_path = $1
|
65
|
+
severity = $2
|
66
|
+
permissions.call.send("may#{severity}", self, action_path, *args)
|
67
|
+
else
|
68
|
+
method_missing_without_aegis_permissions(symb, *args)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
alias_method_chain :method_missing, :aegis_permissions
|
73
|
+
|
74
|
+
send :define_method, :respond_to_with_aegis_permissions? do |symb, *args|
|
75
|
+
if symb.to_s =~ may_pattern
|
76
|
+
true
|
77
|
+
else
|
78
|
+
include_private = args.first.nil? ? false : args.first
|
79
|
+
respond_to_without_aegis_permissions?(symb, include_private)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
alias_method_chain :respond_to?, :aegis_permissions
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
data/lib/aegis/parser.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module Aegis
|
2
|
+
class Parser
|
3
|
+
|
4
|
+
attr_reader :atoms
|
5
|
+
|
6
|
+
def self.parse(&block)
|
7
|
+
Aegis::Parser.new.parse(&block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@atoms = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse(&block)
|
15
|
+
instance_eval(&block) if block
|
16
|
+
atoms
|
17
|
+
end
|
18
|
+
|
19
|
+
def action(*args, &block)
|
20
|
+
split_definitions(*args) do |name, options|
|
21
|
+
@atoms.push({
|
22
|
+
:type => :action,
|
23
|
+
:name => name.to_s,
|
24
|
+
:options => options,
|
25
|
+
:children => Aegis::Parser.parse(&block)
|
26
|
+
})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def namespace(*args, &block)
|
31
|
+
split_definitions(*args) do |name, options|
|
32
|
+
@atoms.push({
|
33
|
+
:type => :namespace,
|
34
|
+
:name => name.to_s,
|
35
|
+
:options => options,
|
36
|
+
:children => Aegis::Parser.parse(&block)
|
37
|
+
})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def resource(*args, &block)
|
42
|
+
split_definitions(*args) do |name, options|
|
43
|
+
@atoms.push({
|
44
|
+
:type => :resource,
|
45
|
+
:name => name.to_s,
|
46
|
+
:options => options,
|
47
|
+
:children => Aegis::Parser.parse(&block)
|
48
|
+
})
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def resources(*args, &block)
|
53
|
+
split_definitions(*args) do |name, options|
|
54
|
+
@atoms.push({
|
55
|
+
:type => :resources,
|
56
|
+
:name => name.to_s,
|
57
|
+
:options => options,
|
58
|
+
:children => Aegis::Parser.parse(&block)
|
59
|
+
})
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def allow(*args, &block)
|
64
|
+
split_definitions(*args) do |role_name, options|
|
65
|
+
@atoms.push({
|
66
|
+
:type => :allow,
|
67
|
+
:role_name => role_name.to_s,
|
68
|
+
:block => block
|
69
|
+
})
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def deny(*args, &block)
|
74
|
+
split_definitions(*args) do |role_name, options|
|
75
|
+
@atoms.push({
|
76
|
+
:type => :deny,
|
77
|
+
:role_name => role_name.to_s,
|
78
|
+
:block => block
|
79
|
+
})
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def reading(&block)
|
84
|
+
block or raise "missing block"
|
85
|
+
@atoms.push({
|
86
|
+
:type => :reading,
|
87
|
+
:children => Aegis::Parser.parse(&block)
|
88
|
+
})
|
89
|
+
end
|
90
|
+
|
91
|
+
def writing(&block)
|
92
|
+
block or raise "missing block"
|
93
|
+
@atoms.push({
|
94
|
+
:type => :writing,
|
95
|
+
:children => Aegis::Parser.parse(&block)
|
96
|
+
})
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def split_definitions(*args, &definition)
|
102
|
+
options = args.extract_options!
|
103
|
+
args = [nil] if args.empty?
|
104
|
+
for name in args
|
105
|
+
definition.call(name, options)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|