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/permissions.rb
CHANGED
@@ -1,107 +1,164 @@
|
|
1
|
-
module Aegis
|
2
|
-
class Permissions
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@
|
18
|
-
end
|
19
|
-
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
1
|
+
module Aegis
|
2
|
+
class Permissions
|
3
|
+
class << self
|
4
|
+
|
5
|
+
MISSING_ACTION_STRATEGIES = [
|
6
|
+
:allow, :deny, :default_permission, :error
|
7
|
+
]
|
8
|
+
|
9
|
+
def missing_action_means(strategy)
|
10
|
+
prepare
|
11
|
+
MISSING_ACTION_STRATEGIES.include?(strategy) or raise ArgumentError, "missing_action_means must be one of #{MISSING_ACTION_STRATEGIES.inspect}"
|
12
|
+
@missing_action_strategy = strategy
|
13
|
+
end
|
14
|
+
|
15
|
+
def missing_user_means(&strategy)
|
16
|
+
prepare
|
17
|
+
@missing_user_strategy = strategy
|
18
|
+
end
|
19
|
+
|
20
|
+
def alias_action(aliases)
|
21
|
+
prepare
|
22
|
+
aliases.each do |key, value|
|
23
|
+
@action_aliases[key.to_s] = value.to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def permission(*args)
|
28
|
+
raise "The Aegis API has changed. See http://wiki.github.com/makandra/aegis/upgrading-to-aegis-2 for migration instructions."
|
29
|
+
end
|
30
|
+
|
31
|
+
def action(*args, &block)
|
32
|
+
prepare
|
33
|
+
@parser.action(*args, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def resource(*args, &block)
|
37
|
+
prepare
|
38
|
+
@parser.resource(*args, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def namespace(*args, &block)
|
42
|
+
prepare
|
43
|
+
@parser.namespace(*args, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def resources(*args, &block)
|
47
|
+
prepare
|
48
|
+
@parser.resources(*args, &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def may?(user, path, *args)
|
52
|
+
query_action(:may?, user, path, *args)
|
53
|
+
end
|
54
|
+
|
55
|
+
def may!(user, path, *args)
|
56
|
+
query_action(:may!, user, path, *args)
|
57
|
+
end
|
58
|
+
|
59
|
+
def role(role_name, options = {})
|
60
|
+
role_name = role_name.to_s
|
61
|
+
role_name != 'everyone' or raise "Cannot define a role named: #{role_name}"
|
62
|
+
@roles_by_name ||= {}
|
63
|
+
@roles_by_name[role_name] = Aegis::Role.new(role_name, options)
|
64
|
+
end
|
65
|
+
|
66
|
+
def roles
|
67
|
+
@roles_by_name.values.sort
|
68
|
+
end
|
69
|
+
|
70
|
+
def find_role_by_name(name)
|
71
|
+
@roles_by_name[name.to_s]
|
72
|
+
end
|
73
|
+
|
74
|
+
def guess_action(resource_name, action_name, map = {})
|
75
|
+
compile
|
76
|
+
action = nil
|
77
|
+
action_name = action_name.to_s
|
78
|
+
guess_action_paths(resource_name, action_name, map).detect do |path|
|
79
|
+
action = find_action_by_path(path, false)
|
80
|
+
end
|
81
|
+
handle_missing_action(action)
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_action_by_path(path, handle_missing = true)
|
85
|
+
compile
|
86
|
+
action = @actions_by_path[path.to_s]
|
87
|
+
action = handle_missing_action(action) if handle_missing
|
88
|
+
action
|
89
|
+
end
|
90
|
+
|
91
|
+
def app_permissions(option)
|
92
|
+
if option.is_a?(Class)
|
93
|
+
option
|
94
|
+
else
|
95
|
+
(option || '::Permissions').constantize
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def inspect
|
100
|
+
compile
|
101
|
+
"Permissions(#{@root_resource.inspect})"
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def query_action(verb, user, path, *args)
|
107
|
+
user = handle_missing_user(user)
|
108
|
+
action = find_action_by_path(path)
|
109
|
+
action.send(verb, user, *args)
|
110
|
+
end
|
111
|
+
|
112
|
+
def handle_missing_user(possibly_missing_user)
|
113
|
+
possibly_missing_user ||= case @missing_user_strategy
|
114
|
+
when :error then raise "Cannot check permission without a user"
|
115
|
+
when Proc then @missing_user_strategy.call
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def handle_missing_action(possibly_missing_action)
|
120
|
+
possibly_missing_action ||= case @missing_action_strategy
|
121
|
+
when :default_permission then Aegis::Action.undefined
|
122
|
+
when :allow then Aegis::Action.allow_to_all
|
123
|
+
when :deny then Aegis::Action.deny_to_all
|
124
|
+
when :error then raise "Undefined Aegis action: #{action}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def guess_action_paths(resource_name, action_name, map)
|
129
|
+
if mapped = map[action_name]
|
130
|
+
[ mapped.to_s ]
|
131
|
+
else
|
132
|
+
[ "#{action_name}_#{resource_name.to_s.singularize}",
|
133
|
+
"#{action_name}_#{resource_name.to_s.pluralize}" ]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def prepare
|
138
|
+
unless @parser
|
139
|
+
@parser = Aegis::Parser.new
|
140
|
+
@missing_user_strategy = :error
|
141
|
+
@missing_action_strategy = :default_permission
|
142
|
+
@action_aliases = {
|
143
|
+
'new' => 'create',
|
144
|
+
'edit' => 'update'
|
145
|
+
}
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def compile
|
150
|
+
unless @root_resource
|
151
|
+
prepare
|
152
|
+
@root_resource = Aegis::Resource.new(nil, nil, :root, {})
|
153
|
+
Aegis::Compiler.compile(@root_resource, @parser.atoms)
|
154
|
+
index_actions
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def index_actions
|
159
|
+
@actions_by_path = @root_resource.index_actions_by_path(@action_aliases)
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module Aegis
|
2
|
+
class Resource
|
3
|
+
|
4
|
+
attr_reader :parent, :children, :name, :type, :never_takes_object, :actions
|
5
|
+
|
6
|
+
def initialize(parent, name, type, options)
|
7
|
+
@parent = parent
|
8
|
+
@children = []
|
9
|
+
@name = name
|
10
|
+
@type = type
|
11
|
+
@actions = initial_actions(options)
|
12
|
+
# @never_takes_object = options[:object] == false
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"Resource(#{{:name => name || 'root', :actions => actions, :children => children, :parent => parent}.inspect})"
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_action_by_name(name)
|
20
|
+
name = name.to_s
|
21
|
+
@actions.detect { |action| action.name == name }
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_or_update_action(name, create_options, update_options)
|
25
|
+
action = nil
|
26
|
+
if action = find_action_by_name(name)
|
27
|
+
action.update(update_options)
|
28
|
+
else
|
29
|
+
action = Action.new(name, create_options)
|
30
|
+
@actions << action
|
31
|
+
end
|
32
|
+
action
|
33
|
+
end
|
34
|
+
|
35
|
+
def root?
|
36
|
+
type == :root
|
37
|
+
end
|
38
|
+
|
39
|
+
def singleton?
|
40
|
+
type == :singleton
|
41
|
+
end
|
42
|
+
|
43
|
+
def collection?
|
44
|
+
type == :collection
|
45
|
+
end
|
46
|
+
|
47
|
+
def reading_actions
|
48
|
+
actions.reject(&:writing)
|
49
|
+
end
|
50
|
+
|
51
|
+
def writing_actions
|
52
|
+
actions.select(&:writing)
|
53
|
+
end
|
54
|
+
|
55
|
+
def action_paths(action, aliases)
|
56
|
+
if root?
|
57
|
+
action_names_with_aliases(action.name, aliases)
|
58
|
+
else
|
59
|
+
action_names_with_aliases(action.name, aliases).collect do |action_name|
|
60
|
+
build_path(
|
61
|
+
action_name,
|
62
|
+
parent && parent.path(false),
|
63
|
+
action.pluralize_resource ? name.pluralize : name.singularize
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def action_names_with_aliases(native_name, aliases)
|
70
|
+
names = [native_name]
|
71
|
+
aliases.each do |key, value|
|
72
|
+
names << key if value == native_name
|
73
|
+
end
|
74
|
+
names
|
75
|
+
end
|
76
|
+
|
77
|
+
def index_actions_by_path(aliases)
|
78
|
+
index = {}
|
79
|
+
actions.each do |action|
|
80
|
+
action_paths(action, aliases).each do |path|
|
81
|
+
index[path] = action
|
82
|
+
end
|
83
|
+
end
|
84
|
+
children.each do |child|
|
85
|
+
index.merge! child.index_actions_by_path(aliases)
|
86
|
+
end
|
87
|
+
index
|
88
|
+
end
|
89
|
+
|
90
|
+
def new_action_takes_object?(action_options = {})
|
91
|
+
collection? && action_options[:collection] != true # && !never_takes_object
|
92
|
+
end
|
93
|
+
|
94
|
+
def new_action_takes_parent_object?(action_options = {})
|
95
|
+
parent && parent.collection? # && !parent.never_takes_object
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
def path(pluralize = true)
|
101
|
+
parent_path = parent && parent.path(false)
|
102
|
+
pluralized_name = name ? (pluralize ? name.pluralize : name.singularize) : nil
|
103
|
+
build_path(parent_path, pluralized_name)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def build_path(*args)
|
109
|
+
args.select(&:present?).join("_")
|
110
|
+
end
|
111
|
+
|
112
|
+
def filter_actions(actions, options)
|
113
|
+
if options[:only]
|
114
|
+
actions = actions.select(&action_name_filter(options[:only]))
|
115
|
+
elsif options[:except]
|
116
|
+
actions = actions.reject(&action_name_filter(options[:except]))
|
117
|
+
end
|
118
|
+
actions
|
119
|
+
end
|
120
|
+
|
121
|
+
def action_name_filter(whitelist)
|
122
|
+
whitelist = whitelist.collect(&:to_s)
|
123
|
+
lambda { |action| whitelist.include?(action.name) }
|
124
|
+
end
|
125
|
+
|
126
|
+
def initial_actions(options)
|
127
|
+
send("initial_actions_for_#{type}", options)
|
128
|
+
end
|
129
|
+
|
130
|
+
def initial_actions_for_collection(options = {})
|
131
|
+
filter_actions([
|
132
|
+
Aegis::Action.index(initial_action_options),
|
133
|
+
Aegis::Action.show(initial_action_options),
|
134
|
+
Aegis::Action.update(initial_action_options),
|
135
|
+
Aegis::Action.create(initial_action_options),
|
136
|
+
Aegis::Action.destroy(initial_action_options),
|
137
|
+
], options)
|
138
|
+
end
|
139
|
+
|
140
|
+
def initial_actions_for_singleton(options = {})
|
141
|
+
filter_actions([
|
142
|
+
Aegis::Action.show(initial_action_options(:takes_object => false)),
|
143
|
+
Aegis::Action.update(initial_action_options(:takes_object => false)),
|
144
|
+
Aegis::Action.create(initial_action_options(:takes_object => false)),
|
145
|
+
Aegis::Action.destroy(initial_action_options(:takes_object => false))
|
146
|
+
], options)
|
147
|
+
end
|
148
|
+
|
149
|
+
def initial_action_options(options = {})
|
150
|
+
{ :takes_parent_object => new_action_takes_parent_object? }.merge(options)
|
151
|
+
end
|
152
|
+
|
153
|
+
def initial_actions_for_root(options = {})
|
154
|
+
[]
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
data/lib/aegis/role.rb
CHANGED
@@ -1,55 +1,25 @@
|
|
1
|
-
module Aegis
|
2
|
-
class Role
|
3
|
-
|
4
|
-
attr_reader :name, :default_permission
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
@
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def to_s
|
28
|
-
name.to_s.humanize
|
29
|
-
end
|
30
|
-
|
31
|
-
def id
|
32
|
-
name.to_s
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def method_missing(symb, *args)
|
38
|
-
method_name = symb.to_s
|
39
|
-
if method_name =~ /^may_(.+)(\?|\!)$/
|
40
|
-
permission, severity = $1, $2
|
41
|
-
permission = Aegis::Normalization.normalize_permission(permission)
|
42
|
-
may = may?(permission, *args)
|
43
|
-
if severity == '!' && !may
|
44
|
-
raise PermissionError, "Access denied: #{permission}"
|
45
|
-
else
|
46
|
-
may
|
47
|
-
end
|
48
|
-
else
|
49
|
-
super
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
end
|
1
|
+
module Aegis
|
2
|
+
class Role
|
3
|
+
|
4
|
+
attr_reader :name, :default_permission
|
5
|
+
|
6
|
+
def initialize(name, options)
|
7
|
+
@name = name
|
8
|
+
@default_permission = options[:default_permission] == :allow ? :allow : :deny
|
9
|
+
freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
def may_by_default?
|
13
|
+
@default_permission == :allow
|
14
|
+
end
|
15
|
+
|
16
|
+
def <=>(other)
|
17
|
+
name <=> other.name
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
name.to_s.humanize
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|