Add bulk user-to-group assignment and show access privileges in listings
_authelia_bulk_assign_group() (menu option 17) picks several users and one target group in a single step, repeatable for multiple batches in one visit (e.g. "1 4 5 6" -> internal, then "2 3 7 8" -> external1) — the missing third combination alongside the existing per-user (option 6) and per-group (option 16) toggles, which only handle one user or one group at a time respectively. "Internal" clears every outside-access group instead of assigning one, since internal access is the absence of a group. _authelia_describe_user_access() is a new shared one-line summary (admin / internal / group names) used both here and in edit_authelia_user()'s own listing, so current access is visible right where you're about to change it instead of requiring a separate trip to option 15's report. Verified end-to-end against a mock users.yml: batch 1 correctly cleared an existing group from 4 users, batch 2 correctly added a brand-new group to a different 4, with the listing reflecting each change before the next batch starts.
This commit is contained in:
+152
-3
@@ -250,10 +250,13 @@ install_authelia() {
|
||||
echo " 15) Show every group's sites and users (site groups + user groups overview)"
|
||||
echo " 16) Add/remove users from a group (pick the group, then toggle members —"
|
||||
echo " the reverse of option 4's per-user group toggle)"
|
||||
echo " 17) Bulk-assign several users to one group at once (e.g. \"1 4 5\" ->"
|
||||
echo " internal, then \"2 3 7\" -> a named group), with each user's"
|
||||
echo " current access shown alongside their name"
|
||||
echo " 0) Leave as-is / exit"
|
||||
echo ""
|
||||
local EXISTING_CHOICE=""
|
||||
prompt_text " Choice [1-16, 0 to exit]:" "0" EXISTING_CHOICE
|
||||
prompt_text " Choice [1-17, 0 to exit]:" "0" EXISTING_CHOICE
|
||||
case "$EXISTING_CHOICE" in
|
||||
1)
|
||||
add_authelia_domain
|
||||
@@ -318,6 +321,10 @@ install_authelia() {
|
||||
_authelia_manage_group_membership
|
||||
return 0
|
||||
;;
|
||||
17)
|
||||
_authelia_bulk_assign_group
|
||||
return 0
|
||||
;;
|
||||
0|*)
|
||||
echo " Keeping existing Authelia. (Edit config/users.yml then: cd $AUTHELIA_DIR && docker compose restart authelia)"
|
||||
return 0
|
||||
@@ -2075,6 +2082,129 @@ _authelia_manage_group_membership() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Bulk version spanning BOTH axes at once — several users, one target group,
|
||||
# in a single step (e.g. "1 4 5 6 -> internal", then "2 3 7 8 -> external1"),
|
||||
# repeatable for as many user/group batches as needed in one menu visit.
|
||||
# Complements the two single-axis tools above: option 6 (per-user menu) is
|
||||
# one user, many groups to toggle; option 16 is one group, many users to
|
||||
# toggle; this is many users, one group, picked together. "Internal" isn't
|
||||
# a real group — picking it clears every outside-access group membership
|
||||
# for the selected users, since internal access is the absence of a
|
||||
# restricting group, not a group of its own.
|
||||
_authelia_bulk_assign_group() {
|
||||
local users_file="$DOCKER_DIR/authelia/config/users.yml"
|
||||
[ -f "$users_file" ] || { log_warning "No users.yml found — install Authelia first."; return 1; }
|
||||
|
||||
local KEEP_GOING="y"
|
||||
while [[ "$KEEP_GOING" =~ ^[Yy]$ ]]; do
|
||||
local -a all_users
|
||||
mapfile -t all_users < <(_authelia_list_usernames "$users_file")
|
||||
if [ "${#all_users[@]}" -eq 0 ]; then
|
||||
log_info "No users exist yet — add one first (this menu's \"Add a new user\")."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " Existing users:"
|
||||
local i start_end start end
|
||||
for i in "${!all_users[@]}"; do
|
||||
start_end="$(_authelia_user_line_range "$users_file" "${all_users[$i]}")"
|
||||
start="${start_end% *}"; end="${start_end#* }"
|
||||
echo " $((i + 1))) ${all_users[$i]} [$(_authelia_describe_user_access "$users_file" "$start" "$end")]"
|
||||
done
|
||||
echo ""
|
||||
echo " Select one or more users by number (space-separated), or 0 to cancel."
|
||||
local USEL=""
|
||||
prompt_text " User number(s) [0]:" "0" USEL
|
||||
if [ -z "$USEL" ] || [ "$USEL" = "0" ]; then
|
||||
log_info "Cancelled."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local -a usel_tokens targets=()
|
||||
read -ra usel_tokens <<< "$USEL"
|
||||
local tok
|
||||
for tok in "${usel_tokens[@]}"; do
|
||||
if [[ "$tok" =~ ^[0-9]+$ ]] && [ "$tok" -ge 1 ] && [ "$tok" -le "${#all_users[@]}" ]; then
|
||||
targets+=("${all_users[$((tok - 1))]}")
|
||||
else
|
||||
log_warning "Skipping invalid selection: $tok"
|
||||
fi
|
||||
done
|
||||
if [ "${#targets[@]}" -eq 0 ]; then
|
||||
log_warning "No valid users selected."
|
||||
prompt_yn " Try again? (y/n):" "n" KEEP_GOING
|
||||
continue
|
||||
fi
|
||||
|
||||
local -a existing_groups
|
||||
mapfile -t existing_groups < <(_authelia_list_scoped_groups "$users_file")
|
||||
echo ""
|
||||
echo " Assign ${#targets[@]} user(s) to:"
|
||||
echo " 0) Internal — remove from every outside-access group"
|
||||
local gi
|
||||
for gi in "${!existing_groups[@]}"; do
|
||||
echo " $((gi + 1))) ${existing_groups[$gi]%-only}"
|
||||
done
|
||||
echo " Or type a new group name to create one."
|
||||
local GSEL=""
|
||||
prompt_text " Group [0 for internal]:" "0" GSEL
|
||||
|
||||
if [ -z "$GSEL" ] || [ "$GSEL" = "0" ]; then
|
||||
local t t_start_end t_start t_end g
|
||||
for t in "${targets[@]}"; do
|
||||
t_start_end="$(_authelia_user_line_range "$users_file" "$t")"
|
||||
t_start="${t_start_end% *}"; t_end="${t_start_end#* }"
|
||||
for g in "${existing_groups[@]}"; do
|
||||
sed -n "${t_start},${t_end}p" "$users_file" | grep -qF " - ${g}" \
|
||||
&& _authelia_toggle_group "$users_file" "$t_start" "$t_end" "$g" "false"
|
||||
done
|
||||
log_success "${t} set to internal (removed from every outside-access group)"
|
||||
done
|
||||
else
|
||||
local group=""
|
||||
if [[ "$GSEL" =~ ^[0-9]+$ ]] && [ "$GSEL" -ge 1 ] && [ "$GSEL" -le "${#existing_groups[@]}" ]; then
|
||||
group="${existing_groups[$((GSEL - 1))]}"
|
||||
else
|
||||
local clean_name
|
||||
clean_name="$(echo "$GSEL" | tr -cs 'a-zA-Z0-9_-' '-' | sed 's/^-*//;s/-*$//')"
|
||||
if [ -z "$clean_name" ]; then
|
||||
log_warning "Invalid group name — nothing changed."
|
||||
prompt_yn " Try again? (y/n):" "n" KEEP_GOING
|
||||
continue
|
||||
fi
|
||||
group="${clean_name}-only"
|
||||
local is_new="true" eg
|
||||
for eg in "${existing_groups[@]}"; do [ "$eg" = "$group" ] && is_new="false"; done
|
||||
if [ "$is_new" = "true" ]; then
|
||||
log_warning "'$clean_name' isn't attached to any site yet — membership alone won't grant"
|
||||
log_warning "access to anything until a site is scoped to it (site protection's"
|
||||
log_warning "\"Outside access\" choice, or re-running a service's own SSO offer)."
|
||||
fi
|
||||
fi
|
||||
local t t_start_end2 t_start2 t_end2
|
||||
for t in "${targets[@]}"; do
|
||||
t_start_end2="$(_authelia_user_line_range "$users_file" "$t")"
|
||||
t_start2="${t_start_end2% *}"; t_end2="${t_start_end2#* }"
|
||||
_authelia_toggle_group "$users_file" "$t_start2" "$t_end2" "$group" "true"
|
||||
log_success "Added ${t} to '${group%-only}'"
|
||||
done
|
||||
fi
|
||||
|
||||
chown 1000:1000 "$users_file" 2>/dev/null || true
|
||||
local RESTART_AUTH=""
|
||||
prompt_yn " Restart Authelia to apply? (y/n):" "y" RESTART_AUTH
|
||||
if [[ "$RESTART_AUTH" =~ ^[Yy]$ ]]; then
|
||||
(cd "$DOCKER_DIR/authelia" && docker compose restart authelia 2>/dev/null) \
|
||||
&& log_success "Authelia restarted" \
|
||||
|| log_warning "Restart failed — check: docker compose logs authelia"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
prompt_yn " Assign another batch (different users and/or a different group)? (y/n):" "n" KEEP_GOING
|
||||
done
|
||||
}
|
||||
|
||||
# Menu-driven, idempotent bulk version of _authelia_ensure_admin_bypass —
|
||||
# backfills the admin-bypass rule for every apex domain currently on this
|
||||
# instance in one pass. install_authelia and add_authelia_domain bake the
|
||||
@@ -2452,6 +2582,23 @@ _authelia_set_2fa_exempt() {
|
||||
chown 1000:1000 "$config_file" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# One-line access summary for a user, e.g. "admin" / "internal" /
|
||||
# "customer1, customer2" / "admin, customer1" — used by both
|
||||
# edit_authelia_user()'s listing and _authelia_bulk_assign_group() so a
|
||||
# user's current privileges are visible right where you're about to change
|
||||
# them, not something you have to cross-check against option 15 first.
|
||||
_authelia_describe_user_access() {
|
||||
local users_file="$1" start="$2" end="$3"
|
||||
local -a tags=()
|
||||
sed -n "${start},${end}p" "$users_file" | grep -q '^ - admins$' && tags+=("admin")
|
||||
local -a groups
|
||||
mapfile -t groups < <(sed -n "${start},${end}p" "$users_file" | grep -oE '^ - [a-zA-Z0-9_-]+-only$' | sed 's/^ - //; s/-only$//')
|
||||
tags+=("${groups[@]}")
|
||||
[ "${#tags[@]}" -eq 0 ] && tags=("internal")
|
||||
local IFS=", "
|
||||
echo "${tags[*]}"
|
||||
}
|
||||
|
||||
# Interactive: pick an existing user from users.yml, then act on them —
|
||||
# edit email/display name, force a password reset, reset their 2FA device,
|
||||
# toggle whether they need 2FA at all, or toggle admin group membership.
|
||||
@@ -2481,9 +2628,11 @@ edit_authelia_user() {
|
||||
|
||||
echo ""
|
||||
echo " Existing users:"
|
||||
local i
|
||||
local i u_start_end u_start u_end
|
||||
for i in "${!USERNAMES[@]}"; do
|
||||
echo " $((i + 1))) ${USERNAMES[$i]}"
|
||||
u_start_end="$(_authelia_user_line_range "$USERS_FILE" "${USERNAMES[$i]}")"
|
||||
u_start="${u_start_end% *}"; u_end="${u_start_end#* }"
|
||||
echo " $((i + 1))) ${USERNAMES[$i]} [$(_authelia_describe_user_access "$USERS_FILE" "$u_start" "$u_end")]"
|
||||
done
|
||||
echo ""
|
||||
echo " Select one or more by number (space-separated, e.g. \"2 4\"),"
|
||||
|
||||
Reference in New Issue
Block a user