From 493ee309161f8eead18a0c7a2533f5ab05db372a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 15:52:13 +0000 Subject: [PATCH] Add bulk user-to-group assignment and show access privileges in listings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _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. --- services/authelia.sh | 155 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 3 deletions(-) diff --git a/services/authelia.sh b/services/authelia.sh index 79b832d..70bba65 100644 --- a/services/authelia.sh +++ b/services/authelia.sh @@ -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\"),"