Skip to content

Commit ae3539e

Browse files
authored
feat(role): add support for new roles ACL permissions (#391)
1 parent 32f8d5d commit ae3539e

11 files changed

+2113
-102
lines changed

app/controllers/forest_liana/resources_controller.rb

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,15 @@ class ResourcesController < ForestLiana::ApplicationController
1616
def index
1717
begin
1818
if request.format == 'csv'
19-
checker = ForestLiana::PermissionsChecker.new(@resource, 'export', @rendering_id)
20-
return head :forbidden unless checker.is_authorized?
21-
elsif params.has_key?(:searchToEdit)
22-
checker = ForestLiana::PermissionsChecker.new(@resource, 'searchToEdit', @rendering_id)
19+
checker = ForestLiana::PermissionsChecker.new(@resource, 'exportEnabled', @rendering_id, user_id: forest_user['id'])
2320
return head :forbidden unless checker.is_authorized?
2421
else
2522
checker = ForestLiana::PermissionsChecker.new(
2623
@resource,
27-
'list',
24+
'browseEnabled',
2825
@rendering_id,
29-
nil,
30-
get_collection_list_permission_info(forest_user, request)
26+
user_id: forest_user['id'],
27+
collection_list_parameters: get_collection_list_permission_info(forest_user, request)
3128
)
3229
return head :forbidden unless checker.is_authorized?
3330
end
@@ -59,10 +56,10 @@ def count
5956
begin
6057
checker = ForestLiana::PermissionsChecker.new(
6158
@resource,
62-
'list',
59+
'browseEnabled',
6360
@rendering_id,
64-
nil,
65-
get_collection_list_permission_info(forest_user, request)
61+
user_id: forest_user['id'],
62+
collection_list_parameters: get_collection_list_permission_info(forest_user, request)
6663
)
6764
return head :forbidden unless checker.is_authorized?
6865

@@ -89,7 +86,7 @@ def count
8986

9087
def show
9188
begin
92-
checker = ForestLiana::PermissionsChecker.new(@resource, 'show', @rendering_id)
89+
checker = ForestLiana::PermissionsChecker.new(@resource, 'readEnabled', @rendering_id, user_id: forest_user['id'])
9390
return head :forbidden unless checker.is_authorized?
9491

9592
getter = ForestLiana::ResourceGetter.new(@resource, params)
@@ -104,7 +101,7 @@ def show
104101

105102
def create
106103
begin
107-
checker = ForestLiana::PermissionsChecker.new(@resource, 'create', @rendering_id)
104+
checker = ForestLiana::PermissionsChecker.new(@resource, 'addEnabled', @rendering_id, user_id: forest_user['id'])
108105
return head :forbidden unless checker.is_authorized?
109106

110107
creator = ForestLiana::ResourceCreator.new(@resource, params)
@@ -127,7 +124,7 @@ def create
127124

128125
def update
129126
begin
130-
checker = ForestLiana::PermissionsChecker.new(@resource, 'update', @rendering_id)
127+
checker = ForestLiana::PermissionsChecker.new(@resource, 'editEnabled', @rendering_id, user_id: forest_user['id'])
131128
return head :forbidden unless checker.is_authorized?
132129

133130
updater = ForestLiana::ResourceUpdater.new(@resource, params)
@@ -149,7 +146,7 @@ def update
149146
end
150147

151148
def destroy
152-
checker = ForestLiana::PermissionsChecker.new(@resource, 'delete', @rendering_id)
149+
checker = ForestLiana::PermissionsChecker.new(@resource, 'deleteEnabled', @rendering_id, user_id: forest_user['id'])
153150
return head :forbidden unless checker.is_authorized?
154151

155152
@resource.destroy(params[:id]) if @resource.exists?(params[:id])
@@ -161,7 +158,7 @@ def destroy
161158
end
162159

163160
def destroy_bulk
164-
checker = ForestLiana::PermissionsChecker.new(@resource, 'delete', @rendering_id)
161+
checker = ForestLiana::PermissionsChecker.new(@resource, 'deleteEnabled', @rendering_id, user_id: forest_user['id'])
165162
return head :forbidden unless checker.is_authorized?
166163

167164
ids = ForestLiana::ResourcesGetter.get_ids_from_request(params)
@@ -245,8 +242,8 @@ def get_collection
245242
@collection ||= ForestLiana.apimap.find { |collection| collection.name.to_s == collection_name }
246243
end
247244

248-
# NOTICE: Return a formatted object containing the request condition filters and
249-
# the user id used by the scope validator class to validate if scope is
245+
# NOTICE: Return a formatted object containing the request condition filters and
246+
# the user id used by the scope validator class to validate if scope is
250247
# in request
251248
def get_collection_list_permission_info(user, collection_list_request)
252249
{

app/controllers/forest_liana/smart_actions_controller.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ def get_smart_action_request
1919

2020
def check_permission_for_smart_route
2121
begin
22-
22+
2323
smart_action_request = get_smart_action_request
2424
if !smart_action_request.nil? && smart_action_request.has_key?(:smart_action_id)
2525
checker = ForestLiana::PermissionsChecker.new(
2626
find_resource(smart_action_request[:collection_name]),
2727
'actions',
2828
@rendering_id,
29-
get_smart_action_permission_info(forest_user, smart_action_request)
29+
user_id: forest_user['id'],
30+
smart_action_request_info: get_smart_action_request_info
3031
)
3132
return head :forbidden unless checker.is_authorized?
3233
else
@@ -54,10 +55,14 @@ def find_resource(collection_name)
5455
end
5556
end
5657

57-
def get_smart_action_permission_info(user, smart_action_request)
58+
# smart action permissions are retrieved from the action's endpoint and http_method
59+
def get_smart_action_request_info
60+
endpoint = request.fullpath
61+
# Trim starting '/'
62+
endpoint[0] = '' if endpoint[0] == '/'
5863
{
59-
user_id: user['id'],
60-
action_id: smart_action_request[:smart_action_id],
64+
endpoint: endpoint,
65+
http_method: request.request_method
6166
}
6267
end
6368
end
Lines changed: 118 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,162 @@
11
module ForestLiana
22
class PermissionsChecker
3-
@@permissions_per_rendering = Hash.new
3+
@@permissions_cached = Hash.new
4+
@@scopes_cached = Hash.new
5+
@@roles_acl_activated = false
6+
# TODO: handle cache scopes per rendering
47
@@expiration_in_seconds = (ENV['FOREST_PERMISSIONS_EXPIRATION_IN_SECONDS'] || 3600).to_i
58

6-
def initialize(resource, permission_name, rendering_id, smart_action_parameters = nil, collection_list_parameters = nil)
9+
def initialize(resource, permission_name, rendering_id, user_id:, smart_action_request_info: nil, collection_list_parameters: nil)
10+
@user_id = user_id
711
@collection_name = ForestLiana.name_for(resource)
812
@permission_name = permission_name
913
@rendering_id = rendering_id
10-
@smart_action_parameters = smart_action_parameters
14+
@smart_action_request_info = smart_action_request_info
1115
@collection_list_parameters = collection_list_parameters
1216
end
1317

1418
def is_authorized?
15-
(is_permission_expired? || !is_allowed?) ? retrieve_permissions_and_check_allowed : true
16-
end
17-
18-
private
19+
# User is still authorized if he already was and the permission has not expire
20+
# if !have_permissions_expired && is_allowed
21+
return true unless have_permissions_expired? || !is_allowed
1922

20-
def get_permissions
21-
@@permissions_per_rendering &&
22-
@@permissions_per_rendering[@rendering_id] &&
23-
@@permissions_per_rendering[@rendering_id]['data']
23+
fetch_permissions
24+
is_allowed
2425
end
2526

26-
def get_last_retrieve
27-
@@permissions_per_rendering &&
28-
@@permissions_per_rendering[@rendering_id] &&
29-
@@permissions_per_rendering[@rendering_id]['last_retrieve']
30-
end
27+
private
3128

32-
def smart_action_allowed?(smart_actions_permissions)
33-
if !@smart_action_parameters||
34-
!@smart_action_parameters[:user_id] ||
35-
!@smart_action_parameters[:action_id] ||
36-
!smart_actions_permissions ||
37-
!smart_actions_permissions[@smart_action_parameters[:action_id]]
38-
return false
29+
def fetch_permissions
30+
permissions = ForestLiana::PermissionsGetter::get_permissions_for_rendering(@rendering_id)
31+
@@roles_acl_activated = permissions['meta']['rolesACLActivated']
32+
permissions['last_fetch'] = Time.now
33+
if @@roles_acl_activated
34+
@@permissions_cached = permissions
35+
else
36+
permissions['data'] = ForestLiana::PermissionsFormatter.convert_to_new_format(permissions['data'], @rendering_id)
37+
@@permissions_cached[@rendering_id] = permissions
3938
end
40-
41-
@user_id = @smart_action_parameters[:user_id]
42-
@action_id = @smart_action_parameters[:action_id]
43-
@smart_action_permissions = smart_actions_permissions[@action_id]
44-
@allowed = @smart_action_permissions['allowed']
45-
@users = @smart_action_permissions['users']
46-
47-
return @allowed && (@users.nil?|| @users.include?(@user_id.to_i));
39+
add_scopes_to_cache(permissions)
4840
end
4941

50-
def collection_list_allowed?(scope_permissions)
51-
return ForestLiana::ScopeValidator.new(
52-
scope_permissions['filter'],
53-
scope_permissions['dynamicScopesValues']['users']
54-
).is_scope_in_request?(@collection_list_parameters)
42+
def add_scopes_to_cache(permissions)
43+
permissions['data']['renderings'].keys.each { |rendering_id|
44+
@@scopes_cached[rendering_id] = permissions['data']['renderings'][rendering_id]
45+
@@scopes_cached[rendering_id]['last_fetch'] = Time.now
46+
} if permissions['data']['renderings']
5547
end
5648

57-
def is_allowed?
58-
permissions = get_permissions
49+
def is_allowed
50+
permissions = get_permissions_content
51+
5952
if permissions && permissions[@collection_name] &&
6053
permissions[@collection_name]['collection']
6154
if @permission_name === 'actions'
6255
return smart_action_allowed?(permissions[@collection_name]['actions'])
63-
# NOTICE: Permissions[@collection_name]['scope'] will either contains conditions filter and
64-
# dynamic user values definition, or null for collection that does not use scopes
65-
elsif @permission_name === 'list' and permissions[@collection_name]['scope']
66-
return collection_list_allowed?(permissions[@collection_name]['scope'])
6756
else
68-
return permissions[@collection_name]['collection'][@permission_name]
57+
if @permission_name === 'browseEnabled'
58+
refresh_scope_cache if scope_cache_expired?
59+
scope_permissions = get_scope_in_permissions
60+
if scope_permissions
61+
# NOTICE: current_scope will either contains conditions filter and
62+
# dynamic user values definition, or null for collection that does not use scopes
63+
return false unless are_scopes_valid?(scope_permissions)
64+
end
65+
end
66+
return is_user_allowed(permissions[@collection_name]['collection'][@permission_name])
6967
end
7068
else
7169
false
7270
end
7371
end
7472

75-
def retrieve_permissions
76-
@@permissions_per_rendering[@rendering_id] = Hash.new
77-
permissions = ForestLiana::PermissionsGetter.new(@rendering_id).perform()
78-
@@permissions_per_rendering[@rendering_id]['data'] = permissions
79-
@@permissions_per_rendering[@rendering_id]['last_retrieve'] = Time.now
73+
def get_scope_in_permissions
74+
@@scopes_cached[@rendering_id] &&
75+
@@scopes_cached[@rendering_id][@collection_name] &&
76+
@@scopes_cached[@rendering_id][@collection_name]['scope']
77+
end
78+
79+
def scope_cache_expired?
80+
return true unless @@scopes_cached[@rendering_id] && @@scopes_cached[@rendering_id]['last_fetch']
81+
82+
elapsed_seconds = date_difference_in_seconds(Time.now, @@scopes_cached[@rendering_id]['last_fetch'])
83+
elapsed_seconds >= @@expiration_in_seconds
84+
end
85+
86+
# This will happen only on rolesACLActivated (as scope cache will always be up to date on disabled)
87+
def refresh_scope_cache
88+
permissions = ForestLiana::PermissionsGetter::get_permissions_for_rendering(@rendering_id, rendering_specific_only: true)
89+
add_scopes_to_cache(permissions)
90+
end
91+
92+
# When acl disabled permissions are stored and retrieved by rendering
93+
def get_permissions
94+
@@roles_acl_activated ? @@permissions_cached : @@permissions_cached[@rendering_id]
95+
end
96+
97+
def get_permissions_content
98+
permissions = get_permissions
99+
permissions && permissions['data'] && permissions['data']['collections']
100+
end
101+
102+
def get_last_fetch
103+
permissions = get_permissions
104+
permissions && permissions['last_fetch']
105+
end
106+
107+
def get_smart_action_permissions(smart_actions_permissions)
108+
endpoint = @smart_action_request_info[:endpoint]
109+
http_method = @smart_action_request_info[:http_method]
110+
111+
return nil unless endpoint && http_method
112+
113+
schema_smart_action = ForestLiana::Utils::BetaSchemaUtils.find_action_from_endpoint(@collection_name, endpoint, http_method)
114+
115+
schema_smart_action &&
116+
schema_smart_action.name &&
117+
smart_actions_permissions &&
118+
smart_actions_permissions[schema_smart_action.name]
119+
end
120+
121+
def is_user_allowed(permission_value)
122+
return false if permission_value.nil?
123+
return permission_value if permission_value.in? [true, false]
124+
permission_value.include?(@user_id.to_i)
125+
end
126+
127+
def smart_action_allowed?(smart_actions_permissions)
128+
smart_action_permissions = get_smart_action_permissions(smart_actions_permissions)
129+
130+
return false unless smart_action_permissions
131+
132+
is_user_allowed(smart_action_permissions['triggerEnabled'])
133+
end
134+
135+
def are_scopes_valid?(scope_permissions)
136+
return ForestLiana::ScopeValidator.new(
137+
scope_permissions['filter'],
138+
scope_permissions['dynamicScopesValues']['users']
139+
).is_scope_in_request?(@collection_list_parameters)
80140
end
81141

82142
def date_difference_in_seconds(date1, date2)
83143
(date1 - date2).to_i
84144
end
85145

86-
def is_permission_expired?
87-
last_retrieve = get_last_retrieve
88-
89-
return true if last_retrieve.nil?
146+
def have_permissions_expired?
147+
last_fetch = get_last_fetch
148+
return true unless last_fetch
90149

91-
elapsed_seconds = date_difference_in_seconds(Time.now, last_retrieve)
150+
elapsed_seconds = date_difference_in_seconds(Time.now, last_fetch)
92151
elapsed_seconds >= @@expiration_in_seconds
93152
end
94153

95-
def retrieve_permissions_and_check_allowed
96-
retrieve_permissions
97-
is_allowed?
154+
# Used only for testing purpose
155+
def self.empty_cache
156+
@@permissions_cached = Hash.new
157+
@@scopes_cached = Hash.new
158+
@@roles_acl_activated = false
159+
@@expiration_in_seconds = (ENV['FOREST_PERMISSIONS_EXPIRATION_IN_SECONDS'] || 3600).to_i
98160
end
99161
end
100162
end

0 commit comments

Comments
 (0)