eaco 0.5.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.
- 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
|