aclize 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +22 -20
- data/lib/aclize/acl/controllers_registry.rb +93 -0
- data/lib/aclize/acl/paths_registry.rb +77 -0
- data/lib/aclize/acl/role.rb +29 -0
- data/lib/aclize/acl.rb +23 -0
- data/lib/aclize/helper.rb +11 -38
- data/lib/aclize/version.rb +1 -1
- data/lib/aclize.rb +34 -64
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e883bc914bc0302e8ddd301838686b681420ed98
|
4
|
+
data.tar.gz: 0110271f53cc15fac67b6800f2bbc99de266dfc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74e415381c8b03575c405cec1d54b74b887e3a1aef3e784b6b64e566192e637bf4bdab0c5e692c8f07a7d6903034443f4caab56eb6b054b4a226fb132f874a76
|
7
|
+
data.tar.gz: 67a452e4826d6450df25ed6cd5bd9272216545a0ce9a62cbecae798676c74bc79c5eb1a5d7fe3d0d1c57d619f08782ad3c31edd8ce81ea791808ca607054611d
|
data/README.md
CHANGED
@@ -33,32 +33,34 @@ class ApplicationController < ActionController::Base
|
|
33
33
|
protected
|
34
34
|
|
35
35
|
def setup_acl
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
36
|
+
|
37
|
+
# define ACL for :admin
|
38
|
+
acl_for :admin do
|
39
|
+
controllers do
|
40
|
+
permit "*" # permit to access any action of any controller
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# define acl for :user
|
45
|
+
acl_for :user do
|
46
|
+
controllers do
|
47
|
+
permit :posts, only: [:index, :show] # users can access only :index and :show actions of :posts controller
|
48
|
+
permit :comments, except: [:edit, :update, :destroy] # can also access all the actions of :comments controller, except for :edit, :update and :destroy actions
|
49
|
+
end
|
50
|
+
|
51
|
+
paths do
|
52
|
+
permit "path/[a-c]", "path/[0-9]+" # permit :user to access "path/a", "path/b", "path/c" and "path/<a digit>"
|
53
|
+
deny "path/b" # deny the access to "path/b"
|
54
|
+
end
|
52
55
|
end
|
53
56
|
|
54
|
-
|
57
|
+
set_current_role(current_user.role) # assuming that current_user is returning an object representing the current user
|
58
|
+
filter_access! # apply the ACL for the current user
|
55
59
|
end
|
56
60
|
end
|
57
61
|
```
|
58
62
|
|
59
|
-
|
60
|
-
|
61
|
-
__N.B:__ When you define the ACL with `define_acl(...)` you're defining it only for the current user.
|
63
|
+
__IMPORTANT:__ you have to tell __Aclize__ what is the role of the current user by calling `set_current_role(<ROLE>)` method, because if you don't specify any role, the default role `:all` will be used.
|
62
64
|
|
63
65
|
Once you've defined the ACL, __Aclize__ will automatically manage the access control and will render the `403 Forbidden` page when the user doesn't have enough permissions to access it.
|
64
66
|
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# The policy adopted by Aclize is:
|
2
|
+
# 1. By default all the controllers and paths are denied
|
3
|
+
# 2. On rules conflict, the more restrictive rule will be used
|
4
|
+
# 3. When permit and deny rules have the same restriction,
|
5
|
+
# deny rule will be used
|
6
|
+
|
7
|
+
|
8
|
+
module Aclize
|
9
|
+
class Acl::ControllersRegistry
|
10
|
+
|
11
|
+
attr_reader :permitted, :denied
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@permitted = {"*" => []}.nested_under_indifferent_access
|
15
|
+
@denied = {"*" => []}.nested_under_indifferent_access
|
16
|
+
end
|
17
|
+
|
18
|
+
# add a new permit rule to controllers registry
|
19
|
+
def permit(controller, only: nil, except: nil)
|
20
|
+
@permitted[controller] ||= []
|
21
|
+
@denied[controller] ||= []
|
22
|
+
|
23
|
+
raise ArgumentError.new("#permit cannot accept both :only and :except. At most one of them can be specified!") if only && except
|
24
|
+
|
25
|
+
if except
|
26
|
+
@permitted[controller] = ["*"]
|
27
|
+
@denied[controller] = normalize(except)
|
28
|
+
elsif only
|
29
|
+
@denied[controller] = []
|
30
|
+
@permitted[controller] = normalize(only)
|
31
|
+
else
|
32
|
+
@permitted[controller] = ["*"]
|
33
|
+
@denied[controller] = []
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# check if each action in the list is allowed for the specified controller
|
38
|
+
def permitted?(controller, *args)
|
39
|
+
@permitted[controller] ||= []
|
40
|
+
@denied[controller] ||= []
|
41
|
+
actions = normalize(args)
|
42
|
+
|
43
|
+
if actions.empty?
|
44
|
+
return controller_permitted?(controller)
|
45
|
+
elsif controller_permitted?(controller)
|
46
|
+
# we know the there's at least one permitted action for this controller,
|
47
|
+
# so return false if there's at least one denied action in the list of actions to check
|
48
|
+
return false unless (actions & @denied[controller]).empty?
|
49
|
+
|
50
|
+
# we know that the actions aren't denied at controller level, so we could
|
51
|
+
# return true if all the actions are also permitted at controller level
|
52
|
+
return true if @permitted[controller].include?("*") || (actions & @permitted[controller]) == actions
|
53
|
+
|
54
|
+
# the actions aren't permitted at controller level, so if any of them is
|
55
|
+
# denied at global level, we will return false
|
56
|
+
return false unless (actions & @denied["*"]).empty?
|
57
|
+
|
58
|
+
# the actions aren't denied at global level, so we have to check if them
|
59
|
+
# are allowed at global level and return true if so
|
60
|
+
return true if @permitted["*"].include?("*") || (actions & @permitted["*"]) == actions
|
61
|
+
end
|
62
|
+
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
# check if the controller is permitted (at least one action in the controller should be permitted)
|
69
|
+
def controller_permitted?(controller)
|
70
|
+
# the simplies case is when there's a permission for at least one action for this controller
|
71
|
+
if @permitted[controller].empty?
|
72
|
+
# check if there're wildcard permissions
|
73
|
+
if @permitted["*"].empty?
|
74
|
+
# there isn't any global permission for the user
|
75
|
+
return false
|
76
|
+
else
|
77
|
+
# we have global permissions (for all the controllers), so we
|
78
|
+
# have to check if those actions weren't denied
|
79
|
+
denied_actions = @denied[controller] + @denied["*"]
|
80
|
+
return !(@permitted["*"] - denied_actions).empty?
|
81
|
+
end
|
82
|
+
else
|
83
|
+
# we are sure there's at least one permission for this controller
|
84
|
+
return true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def normalize(items)
|
89
|
+
result = items.nil? ? [] : items.is_a?(Array) ? items.flatten : [items]
|
90
|
+
return result.map { |x| x.to_s }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# The policy for paths ACLs is slightly different from the controllers policy, because
|
2
|
+
# on rules conflicts, the deny rule always wins. Here is a brief description of the policy:
|
3
|
+
#
|
4
|
+
# 1. By default all the paths are not permitted
|
5
|
+
# 2. On rule conflict, the deny rule always wins
|
6
|
+
# 3. A path is permitted only if there's an explicit permit rule
|
7
|
+
|
8
|
+
module Aclize
|
9
|
+
class Acl::PathsRegistry
|
10
|
+
|
11
|
+
attr_reader :permitted, :denied
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@permitted = []
|
15
|
+
@denied = []
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
# permit a new path
|
20
|
+
def permit(*paths)
|
21
|
+
@permitted += normalize(paths)
|
22
|
+
@permitted.uniq!
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# deny a path
|
27
|
+
def deny(*paths)
|
28
|
+
@denied += normalize(paths)
|
29
|
+
@denied.uniq!
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# Check if the paths are permitted. This method should return true
|
34
|
+
# only if each path passed as argument is permitted (isn't denied and
|
35
|
+
# have an explicit permission).
|
36
|
+
def permitted?(*args)
|
37
|
+
permitted = false
|
38
|
+
|
39
|
+
return false if denied?(args)
|
40
|
+
|
41
|
+
# each path should have an explicit permission in order to return true
|
42
|
+
args.flatten.each do |path|
|
43
|
+
# we assume that the path isn't permitted
|
44
|
+
permitted = false
|
45
|
+
|
46
|
+
# iterate over permitted paths and check if any of them matches the current one
|
47
|
+
@permitted.each do |permitted_path|
|
48
|
+
permitted ||= !!path.match(/^#{permitted_path}$/)
|
49
|
+
# stop iteration if the path is permitted
|
50
|
+
break if permitted
|
51
|
+
end
|
52
|
+
#return false if the path isn't permitted
|
53
|
+
return false unless permitted
|
54
|
+
end
|
55
|
+
|
56
|
+
return permitted
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Check if any of the paths is explicitly denied
|
61
|
+
def denied?(*args)
|
62
|
+
@denied.each do |denied_path|
|
63
|
+
args.flatten.each do |path|
|
64
|
+
return true if path.match(/^#{denied_path}$/)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
def normalize(items)
|
74
|
+
return items.nil? ? [] : items.is_a?(Array) ? items.flatten : [items]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module Aclize
|
3
|
+
class Acl::Role
|
4
|
+
require "aclize/acl/controllers_registry"
|
5
|
+
require "aclize/acl/paths_registry"
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name.to_s
|
9
|
+
@controllers = Aclize::Acl::ControllersRegistry.new
|
10
|
+
@paths = Aclize::Acl::PathsRegistry.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def controllers(&block)
|
14
|
+
if block_given?
|
15
|
+
@controllers.instance_eval(&block)
|
16
|
+
else
|
17
|
+
return @controllers
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def paths(&block)
|
22
|
+
if block_given?
|
23
|
+
@paths.instance_eval(&block)
|
24
|
+
else
|
25
|
+
return @paths
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/aclize/acl.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
module Aclize
|
3
|
+
class Acl
|
4
|
+
require "aclize/acl/role"
|
5
|
+
|
6
|
+
attr_reader :roles
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@roles = {
|
10
|
+
all: Aclize::Acl::Role.new(:all)
|
11
|
+
}.nested_under_indifferent_access
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_acl_for(role)
|
15
|
+
return @roles[role] || @roles[:all]
|
16
|
+
end
|
17
|
+
|
18
|
+
def setup(role = :all, &block)
|
19
|
+
@roles[role] ||= Aclize::Acl::Role.new(role)
|
20
|
+
@roles[role].instance_eval(&block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/aclize/helper.rb
CHANGED
@@ -7,55 +7,28 @@ module Aclize
|
|
7
7
|
|
8
8
|
# Check if the user have permission to access the action
|
9
9
|
def action_allowed?(controller, action)
|
10
|
-
actions_allowed?(controller, [action]
|
10
|
+
actions_allowed?(controller, [action])
|
11
11
|
end
|
12
12
|
|
13
13
|
|
14
|
-
# Returns a boolean that indicates if the current
|
15
|
-
# specified list of actions.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
acl = @_aclize_acl[:controllers]
|
20
|
-
# If there's an entry for this controller in @acl, use that rule for permissions check.
|
21
|
-
# Otherwise, check if there's an '*' entry if @acl and use that rules.
|
22
|
-
methods = ( acl[controller.to_s] || acl['*'] || {} )
|
23
|
-
allow = methods["allow"] || []
|
24
|
-
deny = methods["deny"] || []
|
25
|
-
|
26
|
-
# If the array of methods is empty, the controller isn't allowed
|
27
|
-
return false if allow.empty?
|
28
|
-
|
29
|
-
# Force the list of actions to be an Array of strings
|
30
|
-
normalized_actions = (actions.is_a?(Array) ? actions : [actions]).map{|action| action.to_s }
|
31
|
-
|
32
|
-
# If all the methods of the current controller are allowed or the list of actions to check is empty, return true
|
33
|
-
return true if (allow.include?("*") && (deny & normalized_actions).empty?) || normalized_actions.empty?
|
34
|
-
|
35
|
-
case policy.to_sym
|
36
|
-
when :all then return (deny & normalized_actions).empty? && (allow & normalized_actions == normalized_actions) # all the actions have to be allowed
|
37
|
-
when :any then return !((allow & normalized_actions) - deny).empty? # at least one action have to be allowed
|
38
|
-
else
|
39
|
-
logger.warn "Invalid policy: #{policy}."
|
40
|
-
return false
|
41
|
-
end
|
14
|
+
# Returns a boolean that indicates if the current user have enought permissions to access the
|
15
|
+
# specified list of actions.
|
16
|
+
def actions_allowed?(controller, actions = [])
|
17
|
+
acl = @_aclize_acl.get_acl_for(get_current_role)
|
18
|
+
return acl.controllers.permitted?(controller, actions)
|
42
19
|
end
|
43
20
|
|
44
21
|
|
45
22
|
# Verify if the path could be accessed by the user. Returns true when
|
46
23
|
# the path is accessible
|
47
24
|
def path_allowed?(path)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
return false if !path.match(Regexp(filter)).nil?
|
52
|
-
end
|
25
|
+
acl = @_aclize_acl.get_acl_for(get_current_role)
|
26
|
+
return acl.paths.permitted?(path)
|
27
|
+
end
|
53
28
|
|
54
|
-
(paths[:allow] || []).each do |filter|
|
55
|
-
return true if !path.match(Regexp(filter)).nil?
|
56
|
-
end
|
57
29
|
|
58
|
-
|
30
|
+
def get_current_role
|
31
|
+
return @_aclize_current_role || :all
|
59
32
|
end
|
60
33
|
end
|
61
34
|
end
|
data/lib/aclize/version.rb
CHANGED
data/lib/aclize.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "aclize/version"
|
2
|
+
require "aclize/acl"
|
2
3
|
require "aclize/helper"
|
3
4
|
require "i18n"
|
4
5
|
require "action_controller"
|
@@ -31,33 +32,55 @@ module Aclize
|
|
31
32
|
# The Initializer module will be used to initialize instance variables and to setup defaults.
|
32
33
|
module Initializer
|
33
34
|
def initialize
|
34
|
-
@_aclize_acl
|
35
|
+
@_aclize_acl ||= Aclize::Acl.new
|
36
|
+
@_aclize_current_role = nil
|
35
37
|
super
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
41
|
protected
|
40
42
|
|
41
|
-
# Returns the ACL definition
|
43
|
+
# Returns the ACL definition
|
42
44
|
def get_acl_definition
|
43
45
|
return @_aclize_acl
|
44
46
|
end
|
45
47
|
|
46
|
-
# Defines the structure of ACL for the current user
|
47
|
-
# TODO: implement a better or an alternative way for ACL definition
|
48
|
-
def define_acl(acl)
|
49
|
-
raise "Invalid ACL definition type: (expected: Hash, got: #{acl.class})" unless acl.is_a? Hash
|
50
48
|
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
def set_current_role(role)
|
50
|
+
@_aclize_current_role = role
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_current_role
|
54
|
+
return @_aclize_current_role || :all
|
55
|
+
end
|
56
|
+
|
54
57
|
|
55
|
-
|
56
|
-
|
58
|
+
# setup the ACL for a role
|
59
|
+
def acl_for(role = :all, &block)
|
60
|
+
@_aclize_acl.setup(role, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# apply the ACL for a specific role and unauthorize if the user is not permitted
|
65
|
+
# to access controller action or the path
|
66
|
+
def treat_as(role)
|
67
|
+
acl = @_aclize_acl.get_acl_for(role)
|
68
|
+
unauthorize! unless acl
|
69
|
+
|
70
|
+
if acl.controllers.permitted?(controller_name, action_name)
|
71
|
+
unauthorize! if acl.paths.denied?(request.path_info)
|
72
|
+
else
|
73
|
+
unauthorize! unless acl.paths.permitted?(request.path_info)
|
57
74
|
end
|
58
75
|
end
|
59
76
|
|
60
77
|
|
78
|
+
# use the current_role value to apply ACL
|
79
|
+
def filter_access!
|
80
|
+
treat_as get_current_role
|
81
|
+
end
|
82
|
+
|
83
|
+
|
61
84
|
# In no callbacks were defined for unauthorized access, Aclize will render a
|
62
85
|
# default 403 Forbidden page. Otherwise, the control will be passed to the callback.
|
63
86
|
def unauthorize!
|
@@ -74,59 +97,6 @@ module Aclize
|
|
74
97
|
end
|
75
98
|
end
|
76
99
|
|
77
|
-
|
78
|
-
# Check if the current user have enough permissions to access the current controller/path
|
79
|
-
def filter_access!
|
80
|
-
unauthorize! if acl_action_denied? || acl_path_denied? || !(acl_action_allowed? || acl_path_allowed?)
|
81
|
-
end
|
82
|
-
|
83
|
-
|
84
|
-
# check if the current action is denied
|
85
|
-
def acl_action_denied?
|
86
|
-
actions = (@_aclize_acl[:controllers][controller_name] || @_aclize_acl[:controllers]["*"] || {})[:deny] || []
|
87
|
-
actions.map!{|action| action.to_s }
|
88
|
-
|
89
|
-
return actions.include?("*") || actions.include?(action_name)
|
90
|
-
end
|
91
|
-
|
92
|
-
|
93
|
-
# check if the current action is allowed
|
94
|
-
def acl_action_allowed?
|
95
|
-
actions = (@_aclize_acl[:controllers][controller_name] || @_aclize_acl[:controllers]["*"] || {})[:allow] || []
|
96
|
-
actions.map!{|action| action.to_s }
|
97
|
-
|
98
|
-
return actions.include?("*") || actions.include?(action_name)
|
99
|
-
end
|
100
|
-
|
101
|
-
|
102
|
-
# check if the current path is denied
|
103
|
-
def acl_path_denied?
|
104
|
-
paths = @_aclize_acl[:paths][:deny] || []
|
105
|
-
denied = false
|
106
|
-
|
107
|
-
paths.each do |path|
|
108
|
-
denied ||= !request.path_info.match(Regexp.new("^#{path}$")).nil?
|
109
|
-
break if denied
|
110
|
-
end
|
111
|
-
|
112
|
-
return denied
|
113
|
-
end
|
114
|
-
|
115
|
-
|
116
|
-
# check if the current path is allowed
|
117
|
-
def acl_path_allowed?
|
118
|
-
paths = @_aclize_acl[:paths][:allow] || []
|
119
|
-
allowed = false
|
120
|
-
|
121
|
-
paths.each do |path|
|
122
|
-
allowed ||= !request.path_info.match(Regexp.new("^#{path}$")).nil?
|
123
|
-
break if allowed
|
124
|
-
end
|
125
|
-
|
126
|
-
return allowed
|
127
|
-
end
|
128
|
-
|
129
|
-
|
130
100
|
# register a callback to call when the user is not authorized to access the page
|
131
101
|
def register_callback(&block)
|
132
102
|
@_aclize_callback = block
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aclize
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Groza Sergiu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-11-
|
11
|
+
date: 2015-11-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -76,6 +76,10 @@ files:
|
|
76
76
|
- config/locales/en.yml
|
77
77
|
- config/locales/it.yml
|
78
78
|
- lib/aclize.rb
|
79
|
+
- lib/aclize/acl.rb
|
80
|
+
- lib/aclize/acl/controllers_registry.rb
|
81
|
+
- lib/aclize/acl/paths_registry.rb
|
82
|
+
- lib/aclize/acl/role.rb
|
79
83
|
- lib/aclize/helper.rb
|
80
84
|
- lib/aclize/version.rb
|
81
85
|
homepage: https://github.com/serioja90/aclize
|