Skip to content

Commit fd6287a

Browse files
EdwardAbboudEdward
andauthored
CANS-116: Allow moderators to log in as users (#754)
* CANS-116: Allow moderators to log in as users * CANS-116: Add support for config values regarding user_switch * CANS-116: Add usergroup comaprison for switch_user --------- Co-authored-by: Edward <edward@driebit.nl>
1 parent 64ce9cd commit fd6287a

File tree

3 files changed

+133
-2
lines changed

3 files changed

+133
-2
lines changed

modules/mod_ginger_base/mod_ginger_base.erl

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
-mod_title("Ginger Base").
88
-mod_description("Ginger Base").
99
-mod_prio(250).
10-
-mod_depends([mod_content_groups, mod_acl_user_groups]).
10+
-mod_depends([mod_content_groups, mod_acl_user_groups, mod_admin_identity]).
1111

1212
-mod_schema(13).
1313

@@ -198,6 +198,32 @@ manage_schema(_Version, Context) ->
198198
%%observe_acl_is_allowed(#acl_is_allowed{}, _Context) ->
199199
%% undefined.
200200

201+
202+
%% @doc Allow mod_admin_identity managers to impersonate other users
203+
event(#postback{message={switch_user, [{id, Id}]}}, Context) ->
204+
IsAdmin = z_acl:is_admin(Context),
205+
CanSwitch = IsAdmin
206+
orelse z_acl:is_allowed(use, mod_admin_identity, Context),
207+
ImpersonationEnabled = is_impersonation_enabled(Context),
208+
AdminAllowed = IsAdmin
209+
andalso Id =/= 1,
210+
RegularAllowed = ImpersonationEnabled
211+
andalso CanSwitch
212+
andalso Id =/= 1
213+
andalso can_impersonate_user(Id, Context),
214+
case AdminAllowed orelse RegularAllowed of
215+
true ->
216+
{ok, NewContext} = z_auth:switch_user(Id, Context),
217+
Url = case z_acl:is_allowed(use, mod_admin, NewContext) of
218+
true ->
219+
z_dispatcher:url_for(admin, NewContext);
220+
false ->
221+
<<"/">>
222+
end,
223+
z_render:wire({redirect, [{location, Url}]}, NewContext);
224+
false ->
225+
z_render:growl_error(?__("You are not allowed to switch users.", Context), Context)
226+
end;
201227
%% @doc Handle the submit event of a new comment
202228
event(#submit{message={newcomment, Args}, form=FormId}, Context) ->
203229
ExtraActions = proplists:get_all_values(action, Args),
@@ -267,6 +293,80 @@ event(#postback{message={map_infobox, _Args}}, Context) ->
267293
),
268294
z_render:wire({script, [{script, JS}]}, Context).
269295

296+
can_impersonate_user(TargetId, Context) ->
297+
case z_acl:user(Context) of
298+
undefined ->
299+
false;
300+
TargetId ->
301+
true;
302+
SwitcherId when is_integer(SwitcherId) ->
303+
AllowHorizontal = is_horizontal_impersonation_allowed(Context),
304+
SudoContext = z_acl:sudo(Context),
305+
SwitcherGroups = direct_user_groups(SwitcherId, SudoContext),
306+
TargetGroups = direct_user_groups(TargetId, SudoContext),
307+
case {SwitcherGroups, TargetGroups} of
308+
{[], _} ->
309+
false;
310+
{_, []} ->
311+
false;
312+
_ ->
313+
lists:all(
314+
fun(TargetGroup) ->
315+
group_impersonation_allowed(TargetGroup, SwitcherGroups, SudoContext, AllowHorizontal)
316+
end,
317+
TargetGroups)
318+
end;
319+
_ ->
320+
false
321+
end.
322+
323+
direct_user_groups(UserId, Context) ->
324+
lists:usort(acl_user_groups_checks:has_user_groups(UserId, Context)).
325+
326+
group_impersonation_allowed(TargetGroup, SwitcherGroups, Context, AllowHorizontal) ->
327+
TargetPath = user_group_path(TargetGroup, Context),
328+
TargetPath =/= [] andalso
329+
lists:any(
330+
fun(SwitcherGroup) ->
331+
SwitcherPath = user_group_path(SwitcherGroup, Context),
332+
path_allows(TargetPath, SwitcherPath, AllowHorizontal)
333+
end,
334+
SwitcherGroups).
335+
336+
user_group_path(GroupId, Context) ->
337+
case mod_acl_user_groups:lookup(GroupId, Context) of
338+
undefined ->
339+
[GroupId];
340+
Path when is_list(Path) ->
341+
Path
342+
end.
343+
344+
path_allows(TargetPath, SwitcherPath, AllowHorizontal) when is_list(TargetPath), is_list(SwitcherPath) ->
345+
case lists:suffix(TargetPath, SwitcherPath) of
346+
true when AllowHorizontal ->
347+
true;
348+
true ->
349+
length(SwitcherPath) > length(TargetPath);
350+
false ->
351+
false
352+
end.
353+
354+
is_impersonation_enabled(Context) ->
355+
case m_config:get_value(mod_ginger_base, activate_impersonation, Context) of
356+
undefined ->
357+
false;
358+
Value ->
359+
z_utils:is_true(Value)
360+
end.
361+
362+
is_horizontal_impersonation_allowed(Context) ->
363+
case m_config:get_value(mod_ginger_base, allow_horizontal_impersonation, Context) of
364+
undefined ->
365+
false;
366+
Value ->
367+
z_utils:is_true(Value)
368+
end.
369+
270370
%% @doc When a resource is persisted in the admin, update granularity for
271371
%% granular date fields.
272372
observe_admin_rscform(#admin_rscform{}, Post, _Context) ->

modules/mod_ginger_base/support/ginger_config.erl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,7 @@ get_config() ->
3636
{mod_l10n, timezone, <<"Europe/Berlin">>},
3737
%% Allow ginger-embed elements
3838
{site, html_elt_extra, <<"embed,iframe,object,script,ginger-embed">>},
39-
{site, html_attr_extra, <<"data,allowfullscreen,flashvars,frameborder,scrolling,async,defer,data-rdf">>}
39+
{site, html_attr_extra, <<"data,allowfullscreen,flashvars,frameborder,scrolling,async,defer,data-rdf">>},
40+
{mod_ginger_base, activate_impersonation, false},
41+
{mod_ginger_base, allow_horizontal_impersonation, false}
4042
].
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<fieldset class="form">
2+
3+
4+
{% if m.acl.is_allowed.use.mod_admin_identity or id == m.acl.user %}
5+
<div class="col-md-6">
6+
<div class="well">
7+
<h4 style="margin-top: 0">{_ User actions _}</h4>
8+
<div class="form-group">
9+
{% button class="btn btn-default" action={dialog_set_username_password id=id} text=_"Set username / password" %}
10+
</div>
11+
12+
{% if (m.acl.is_admin or m.acl.is_allowed.use.mod_admin_identity) and m.identity[id].is_user and id != m.acl.user and id != 1 %}
13+
<div class="form-group">
14+
{% button class="btn btn-default" action={confirm text=_"Click OK to log on as this user. You will be redirected to the home page if this user has no rights to access the admin system." postback={switch_user id=id} delegate=`mod_ginger_base`} text=_"Log on as this user" %}
15+
</div>
16+
{% endif %}
17+
18+
{% if id /= 1 %}
19+
<div>
20+
{% button class="btn btn-default" text=_"Delete Username" action={dialog_delete_username id=id on_success={slide_fade_out target=#tr.id}} %}
21+
</div>
22+
{% endif %}
23+
</div>
24+
</div>
25+
{% endif %}
26+
27+
{% all include "_admin_edit_basics_user_extra.tpl" %}
28+
29+
</fieldset>

0 commit comments

Comments
 (0)