eaco 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/.yardopts +1 -0
- data/Appraisals +22 -0
- data/Gemfile +4 -0
- data/Guardfile +34 -0
- data/LICENSE.txt +23 -0
- data/README.md +225 -0
- data/Rakefile +26 -0
- data/eaco.gemspec +27 -0
- data/features/active_record.example.yml +8 -0
- data/features/active_record.travis.yml +7 -0
- data/features/rails_integration.feature +10 -0
- data/features/step_definitions/database.rb +7 -0
- data/features/step_definitions/resource_authorization.rb +15 -0
- data/features/support/env.rb +9 -0
- data/gemfiles/rails_3.2.gemfile +9 -0
- data/gemfiles/rails_4.0.gemfile +8 -0
- data/gemfiles/rails_4.1.gemfile +8 -0
- data/gemfiles/rails_4.2.gemfile +8 -0
- data/lib/eaco.rb +93 -0
- data/lib/eaco/acl.rb +206 -0
- data/lib/eaco/actor.rb +86 -0
- data/lib/eaco/adapters.rb +14 -0
- data/lib/eaco/adapters/active_record.rb +70 -0
- data/lib/eaco/adapters/active_record/compatibility.rb +83 -0
- data/lib/eaco/adapters/active_record/compatibility/v32.rb +27 -0
- data/lib/eaco/adapters/active_record/compatibility/v40.rb +59 -0
- data/lib/eaco/adapters/active_record/compatibility/v41.rb +16 -0
- data/lib/eaco/adapters/active_record/compatibility/v42.rb +17 -0
- data/lib/eaco/adapters/active_record/postgres_jsonb.rb +36 -0
- data/lib/eaco/adapters/couchrest_model.rb +37 -0
- data/lib/eaco/adapters/couchrest_model/couchdb_lucene.rb +71 -0
- data/lib/eaco/controller.rb +158 -0
- data/lib/eaco/cucumber.rb +11 -0
- data/lib/eaco/cucumber/active_record.rb +163 -0
- data/lib/eaco/cucumber/active_record/department.rb +19 -0
- data/lib/eaco/cucumber/active_record/document.rb +18 -0
- data/lib/eaco/cucumber/active_record/position.rb +21 -0
- data/lib/eaco/cucumber/active_record/schema.rb +36 -0
- data/lib/eaco/cucumber/active_record/user.rb +24 -0
- data/lib/eaco/cucumber/world.rb +136 -0
- data/lib/eaco/designator.rb +264 -0
- data/lib/eaco/dsl.rb +40 -0
- data/lib/eaco/dsl/acl.rb +163 -0
- data/lib/eaco/dsl/actor.rb +139 -0
- data/lib/eaco/dsl/actor/designators.rb +110 -0
- data/lib/eaco/dsl/base.rb +52 -0
- data/lib/eaco/dsl/resource.rb +129 -0
- data/lib/eaco/dsl/resource/permissions.rb +131 -0
- data/lib/eaco/error.rb +36 -0
- data/lib/eaco/railtie.rb +46 -0
- data/lib/eaco/rake.rb +10 -0
- data/lib/eaco/rake/default_task.rb +164 -0
- data/lib/eaco/resource.rb +234 -0
- data/lib/eaco/version.rb +7 -0
- data/spec/eaco/acl_spec.rb +147 -0
- data/spec/eaco/actor_spec.rb +13 -0
- data/spec/eaco/adapters/active_record/postgres_jsonb_spec.rb +9 -0
- data/spec/eaco/adapters/active_record_spec.rb +13 -0
- data/spec/eaco/adapters/couchrest_model/couchdb_lucene_spec.rb +9 -0
- data/spec/eaco/adapters/couchrest_model_spec.rb +9 -0
- data/spec/eaco/controller_spec.rb +12 -0
- data/spec/eaco/designator_spec.rb +25 -0
- data/spec/eaco/dsl/acl_spec.rb +9 -0
- data/spec/eaco/dsl/actor/designators_spec.rb +7 -0
- data/spec/eaco/dsl/actor_spec.rb +15 -0
- data/spec/eaco/dsl/resource/permissions_spec.rb +7 -0
- data/spec/eaco/dsl/resource_spec.rb +17 -0
- data/spec/eaco/error_spec.rb +9 -0
- data/spec/eaco/resource_spec.rb +31 -0
- data/spec/eaco_spec.rb +49 -0
- data/spec/spec_helper.rb +71 -0
- metadata +296 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
module Eaco
|
2
|
+
module DSL
|
3
|
+
|
4
|
+
##
|
5
|
+
# Parses the Actor DSL, that describes how to harvest {Designator}s from
|
6
|
+
# an {Actor} and how to identify it as an +admin+, or superuser.
|
7
|
+
#
|
8
|
+
# actor User do
|
9
|
+
# admin do |user|
|
10
|
+
# user.admin?
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# designators do
|
14
|
+
# authenticated from: :class
|
15
|
+
# user from: :id
|
16
|
+
# group from: :group_ids
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
class Actor < Base
|
21
|
+
autoload :Designators, 'eaco/dsl/actor/designators'
|
22
|
+
|
23
|
+
##
|
24
|
+
# Initializes an Actor class.
|
25
|
+
#
|
26
|
+
# @see Eaco::Actor
|
27
|
+
#
|
28
|
+
def initialize(*)
|
29
|
+
super
|
30
|
+
|
31
|
+
target_eval do
|
32
|
+
include Eaco::Actor
|
33
|
+
|
34
|
+
def designators
|
35
|
+
@_designators
|
36
|
+
end
|
37
|
+
|
38
|
+
def admin_logic
|
39
|
+
@_admin_logic
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Defines the designators that apply to this {Actor}.
|
46
|
+
#
|
47
|
+
# Example:
|
48
|
+
#
|
49
|
+
# actor User do
|
50
|
+
# designators do
|
51
|
+
# authenticated from: :class
|
52
|
+
# user from: :id
|
53
|
+
# group from: :group_ids
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# {Designator} names are collected using +method_missing+, and are
|
58
|
+
# named after the method name. Implementations are looked up in
|
59
|
+
# a +Designators+ module in the {Actor}'s class.
|
60
|
+
#
|
61
|
+
# Each designator implementation is expected to be named after the
|
62
|
+
# designator's name, camelized, and inherit from {Eaco::Designator}.
|
63
|
+
#
|
64
|
+
# TODO all designators share the same namespace. This is due to the
|
65
|
+
# fact that designator string representations aren't scoped by the
|
66
|
+
# Actor model they belong to. As such when instantiating a designator
|
67
|
+
# from +Eaco::Designator.make+ the registry is consulted to find the
|
68
|
+
# designator implementation.
|
69
|
+
#
|
70
|
+
# @see DSL::Actor::Designators
|
71
|
+
#
|
72
|
+
def designators(&block)
|
73
|
+
new_designators = target_eval do
|
74
|
+
@_designators = Designators.eval(self, &block).result.freeze
|
75
|
+
end
|
76
|
+
|
77
|
+
Actor.register_designators(new_designators)
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Defines the boolean logic that determines whether an {Actor} is an
|
82
|
+
# admin. Usually you'll have an +admin+ method on your model, that you
|
83
|
+
# can call from here. Or, feel free to just return +false+ to disable
|
84
|
+
# this functionality.
|
85
|
+
#
|
86
|
+
# Example:
|
87
|
+
#
|
88
|
+
# actor User do
|
89
|
+
# admin do |user|
|
90
|
+
# user.admin?
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
def admin(&block)
|
95
|
+
target_eval do
|
96
|
+
@admin_logic = block
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class << self
|
101
|
+
##
|
102
|
+
# Looks up the given designator implementation by its +name+.
|
103
|
+
#
|
104
|
+
# @param name [Symbol] the designator name.
|
105
|
+
#
|
106
|
+
# @raise [Eaco::Malformed] if the designator is not found.
|
107
|
+
#
|
108
|
+
# @return [Class]
|
109
|
+
#
|
110
|
+
def find_designator(name)
|
111
|
+
all_designators.fetch(name.intern)
|
112
|
+
|
113
|
+
rescue KeyError
|
114
|
+
raise Malformed, "Designator not found: #{name.inspect}"
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Saves the given designators in the global designators registry.
|
119
|
+
#
|
120
|
+
# @param new_designators [Hash]
|
121
|
+
#
|
122
|
+
# @return [Hash] the designators registry.
|
123
|
+
#
|
124
|
+
def register_designators(new_designators)
|
125
|
+
all_designators.update(new_designators)
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
##
|
130
|
+
# @return [Hash] a registry of all the defined designators.
|
131
|
+
#
|
132
|
+
def all_designators
|
133
|
+
@_all_designators ||= {}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Eaco
|
2
|
+
module DSL
|
3
|
+
class Actor < Base
|
4
|
+
|
5
|
+
##
|
6
|
+
# Designators collector using +method_missing+.
|
7
|
+
#
|
8
|
+
# Parses the following DSL:
|
9
|
+
#
|
10
|
+
# actor User do
|
11
|
+
# designators do
|
12
|
+
# authenticated from: :class
|
13
|
+
# user from: :id
|
14
|
+
# group from: :group_ids
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# and looks up within the Designators namespace of the Actor model the
|
19
|
+
# concrete implementations of the described designators.
|
20
|
+
#
|
21
|
+
# Here the User model is expected to define an User::Designators module
|
22
|
+
# and to implement within it a +class Authenticated < Eaco::Designator+
|
23
|
+
#
|
24
|
+
# @see Designator
|
25
|
+
#
|
26
|
+
class Designators < Base
|
27
|
+
##
|
28
|
+
# Sets up the designators registry.
|
29
|
+
#
|
30
|
+
def initialize(*)
|
31
|
+
super
|
32
|
+
|
33
|
+
@designators = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# The parsed designators, keyed by type symbol and with concrete
|
38
|
+
# implementations as values.
|
39
|
+
#
|
40
|
+
# @return [Hash]
|
41
|
+
#
|
42
|
+
attr_reader :designators
|
43
|
+
alias result designators
|
44
|
+
|
45
|
+
private
|
46
|
+
##
|
47
|
+
# Looks up the implementation for the designator of the given
|
48
|
+
# +name+, configures it with the given +options+ and saves it in
|
49
|
+
# the designators registry.
|
50
|
+
#
|
51
|
+
# @param name [Symbol]
|
52
|
+
# @param options [Hash]
|
53
|
+
#
|
54
|
+
# @return [Class]
|
55
|
+
#
|
56
|
+
# @see #implementation_for
|
57
|
+
#
|
58
|
+
def define_designator(name, options)
|
59
|
+
designators[name] = implementation_for(name).configure!(options)
|
60
|
+
end
|
61
|
+
alias method_missing define_designator
|
62
|
+
|
63
|
+
##
|
64
|
+
# Looks up the +name+ designator implementation in the {Actor}'s
|
65
|
+
# +Designators+ namespace.
|
66
|
+
#
|
67
|
+
# @param name [Symbol]
|
68
|
+
#
|
69
|
+
# @return [Class]
|
70
|
+
#
|
71
|
+
# @raise [Malformed] if the implementation class is not found.
|
72
|
+
#
|
73
|
+
# @see #container
|
74
|
+
# @see Designator.type
|
75
|
+
#
|
76
|
+
def implementation_for(name)
|
77
|
+
impl = name.to_s.camelize.intern
|
78
|
+
|
79
|
+
unless container.const_defined?(impl)
|
80
|
+
raise Malformed, <<-EOF
|
81
|
+
Implementation #{container}::#{impl} for Designator #{name} not found.
|
82
|
+
EOF
|
83
|
+
end
|
84
|
+
|
85
|
+
container.const_get(impl)
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Looks up the +Designators+ namespace within the {Actor}'s class.
|
90
|
+
#
|
91
|
+
# @return [Class]
|
92
|
+
#
|
93
|
+
# @raise Malformed if the +Designators+ module cannot be found.
|
94
|
+
#
|
95
|
+
# @see #implementation_for
|
96
|
+
#
|
97
|
+
def container
|
98
|
+
@_container ||= begin
|
99
|
+
unless target.const_defined?(:Designators)
|
100
|
+
raise Malformed, "Please put designators implementations in #{target}::Designators"
|
101
|
+
end
|
102
|
+
|
103
|
+
target.const_get(:Designators)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Eaco
|
2
|
+
module DSL
|
3
|
+
|
4
|
+
##
|
5
|
+
# Base DSL class. Provides handy access to the +target+ class being
|
6
|
+
# manipulated, DSL-specific options, and a {#target_eval} helper to do
|
7
|
+
# +instance_eval+ on the +target+.
|
8
|
+
#
|
9
|
+
# Nothing too fancy.
|
10
|
+
#
|
11
|
+
class Base
|
12
|
+
|
13
|
+
##
|
14
|
+
# Executes a DSL block in the context of a DSL manipulator.
|
15
|
+
#
|
16
|
+
# @see DSL::ACL
|
17
|
+
# @see DSL::Actor
|
18
|
+
# @see DSL::Resource
|
19
|
+
#
|
20
|
+
# @return [Base]
|
21
|
+
#
|
22
|
+
def self.eval(klass, options = {}, &block)
|
23
|
+
new(klass, options).tap do |dsl|
|
24
|
+
dsl.instance_eval(&block) if block
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# The target class of the manipulation
|
29
|
+
attr_reader :target
|
30
|
+
|
31
|
+
# DSL-specific options
|
32
|
+
attr_reader :options
|
33
|
+
|
34
|
+
##
|
35
|
+
# @param target [Class]
|
36
|
+
# @param options [Hash]
|
37
|
+
#
|
38
|
+
def initialize(target, options)
|
39
|
+
@target, @options = target, options
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
##
|
44
|
+
# Evaluates the given block in the context of the target class
|
45
|
+
#
|
46
|
+
def target_eval(&block)
|
47
|
+
target.instance_eval(&block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Eaco
|
2
|
+
module DSL
|
3
|
+
|
4
|
+
##
|
5
|
+
# Parses the Resource definition DSL.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# authorize Document do
|
10
|
+
# roles :owner, :editor, :reader
|
11
|
+
#
|
12
|
+
# role :owner, 'Author'
|
13
|
+
#
|
14
|
+
# permissions do
|
15
|
+
# reader :read
|
16
|
+
# editor reader, :edit
|
17
|
+
# owner editor, :destroy
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# The DSL installs authorization in your +Document+ model,
|
22
|
+
# defining three access roles.
|
23
|
+
#
|
24
|
+
# The +owner+ role is given a label of "Author".
|
25
|
+
#
|
26
|
+
# Each role has then different abilities, defined in the
|
27
|
+
# permissions block.
|
28
|
+
#
|
29
|
+
# @see DSL::Resource::Permissions
|
30
|
+
#
|
31
|
+
class Resource < Base
|
32
|
+
autoload :Permissions, 'eaco/dsl/resource/permissions'
|
33
|
+
|
34
|
+
##
|
35
|
+
# Sets up an authorized resource. The only required API
|
36
|
+
# is +accessible_by+. For available implementations, see
|
37
|
+
# the {Adapters} module.
|
38
|
+
#
|
39
|
+
# @see Resource
|
40
|
+
#
|
41
|
+
def initialize(*)
|
42
|
+
super
|
43
|
+
|
44
|
+
target_eval do
|
45
|
+
include Eaco::Resource
|
46
|
+
|
47
|
+
def permissions
|
48
|
+
@_permissions
|
49
|
+
end
|
50
|
+
|
51
|
+
def roles
|
52
|
+
@_roles || []
|
53
|
+
end
|
54
|
+
|
55
|
+
def roles_priority
|
56
|
+
@_roles_priority ||= {}.tap do |priorities|
|
57
|
+
roles.each_with_index {|role, idx| priorities[role] = idx }
|
58
|
+
end.freeze
|
59
|
+
end
|
60
|
+
|
61
|
+
def roles_with_labels
|
62
|
+
@_roles_with_labels ||= roles.inject({}) do |labels, role|
|
63
|
+
labels.update(role => role.to_s.humanize)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Reset memoizations when this method is called on the target class,
|
68
|
+
# so that reloading the authorizations configuration file will
|
69
|
+
# refresh the models' configuration.
|
70
|
+
@_roles_priority = nil
|
71
|
+
@_roles_with_labels = nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Defines the permissions on this resource.
|
77
|
+
# The evaluated registries are memoized in the target class.
|
78
|
+
#
|
79
|
+
# @return [void]
|
80
|
+
#
|
81
|
+
def permissions(&block)
|
82
|
+
target_eval do
|
83
|
+
@_permissions = Permissions.eval(self, &block).result.freeze
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Defines the roles valid for this resource. e.g.
|
89
|
+
#
|
90
|
+
# authorize Foobar do
|
91
|
+
# roles :owner, :editor, :reader
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# Roles defined first have higher priority.
|
95
|
+
#
|
96
|
+
# If the same user is at the same time +reader+ and +editor+, the
|
97
|
+
# resulting role is +editor+.
|
98
|
+
#
|
99
|
+
# @param keys [Variadic]
|
100
|
+
#
|
101
|
+
# @return [void]
|
102
|
+
#
|
103
|
+
def roles(*keys)
|
104
|
+
target_eval do
|
105
|
+
@_roles = keys.flatten.freeze
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Sets the given label on the given role.
|
111
|
+
#
|
112
|
+
# TODO rename this method, or use it to pass options
|
113
|
+
# to improve readability of the DSL and to store more
|
114
|
+
# metadata with each role for future extensibility.
|
115
|
+
#
|
116
|
+
# @param role [Symbol]
|
117
|
+
# @param label [String]
|
118
|
+
#
|
119
|
+
# @return [void]
|
120
|
+
#
|
121
|
+
def role(role, label)
|
122
|
+
target_eval do
|
123
|
+
roles_with_labels[role] = label
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Eaco
|
2
|
+
module DSL
|
3
|
+
class Resource < Base
|
4
|
+
|
5
|
+
##
|
6
|
+
# Permission collector, based on +method_missing+.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# 1 authorize Foobar do
|
11
|
+
# 2 permissions do
|
12
|
+
# 3 reader :read_foo, :read_bar
|
13
|
+
# 4 editor reader, :edit_foo, :edit_bar
|
14
|
+
# 5 owner editor, :destroy
|
15
|
+
# 6 end
|
16
|
+
# 7 end
|
17
|
+
#
|
18
|
+
# Within the block, each undefined method call defines a new
|
19
|
+
# method that returns the given arguments.
|
20
|
+
#
|
21
|
+
# After evaluating line 3 above:
|
22
|
+
#
|
23
|
+
# >> reader
|
24
|
+
# => #<Set{ :read_foo, :read_bar }>
|
25
|
+
#
|
26
|
+
# The method is used then on line 4, giving the +editor+ role the
|
27
|
+
# same set of permissions granted to the +reader+, plus its own
|
28
|
+
# set of permissions:
|
29
|
+
#
|
30
|
+
# >> editor
|
31
|
+
# => #<Set{ :read_foo, :read_bar, :edit_foo, :edit_bar }>
|
32
|
+
#
|
33
|
+
class Permissions < Base
|
34
|
+
|
35
|
+
##
|
36
|
+
# Evaluates the given block in the context of a new collector
|
37
|
+
#
|
38
|
+
# Returns an Hash of permissions, keyed by role.
|
39
|
+
#
|
40
|
+
# >> Permissions.eval do
|
41
|
+
# | permissions do
|
42
|
+
# | reader :read
|
43
|
+
# | editor reader, :edit
|
44
|
+
# | end
|
45
|
+
# | end
|
46
|
+
#
|
47
|
+
# => {
|
48
|
+
# | reader: #<Set{ :read },
|
49
|
+
# | editor: #<Set{ :read, :edit }
|
50
|
+
# | }
|
51
|
+
#
|
52
|
+
# @return [Permissions]
|
53
|
+
#
|
54
|
+
def self.eval(*, &block)
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Sets up an hash with a default value of a new Set.
|
60
|
+
#
|
61
|
+
def initialize(*)
|
62
|
+
super
|
63
|
+
|
64
|
+
@permissions = Hash.new {|hsh, key| hsh[key] = Set.new}
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Returns the collected permissions in a plain Hash, lacking the
|
69
|
+
# default block used by the collector's internals - to give to
|
70
|
+
# the outside an Hash with a predictable behaviour :-).
|
71
|
+
#
|
72
|
+
# @return [Hash]
|
73
|
+
#
|
74
|
+
def result
|
75
|
+
Hash.new.merge(@permissions)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
##
|
80
|
+
# Here the method name is the role code. If we already have defined
|
81
|
+
# permissions for the given role, those are returned.
|
82
|
+
#
|
83
|
+
# Else, {#save_permission} is called to memoize the given permissions
|
84
|
+
# for the +role+.
|
85
|
+
#
|
86
|
+
# @param role [Symbol]
|
87
|
+
# @param permissions [Array] permissions to grant to the given +role+.
|
88
|
+
#
|
89
|
+
# @return [Set]
|
90
|
+
#
|
91
|
+
def method_missing(role, *permissions)
|
92
|
+
if @permissions.key?(role)
|
93
|
+
@permissions[role]
|
94
|
+
else
|
95
|
+
save_permission(role, permissions)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Memoizes the given set of permissions for the given role.
|
101
|
+
#
|
102
|
+
# @param role [Symbol]
|
103
|
+
# @param permissions [Array]
|
104
|
+
#
|
105
|
+
# @return [Set]
|
106
|
+
#
|
107
|
+
# @raise [Malformed] if the syntax is not valid.
|
108
|
+
#
|
109
|
+
def save_permission(role, permissions)
|
110
|
+
permissions = permissions.inject(Set.new) do |set, perm|
|
111
|
+
if perm.is_a?(Symbol)
|
112
|
+
set.add perm
|
113
|
+
|
114
|
+
elsif perm.is_a?(Set)
|
115
|
+
set.merge perm
|
116
|
+
|
117
|
+
else
|
118
|
+
raise Malformed, <<-EOF
|
119
|
+
Invalid #{role} permission definition: #{perm.inspect}.
|
120
|
+
Permissions can be defined only by plain symbols.
|
121
|
+
EOF
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
@permissions[role].merge(permissions)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|