From 49b965bd1dcead6874837d25067f94491a1ffeba Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 16:32:34 +0000 Subject: [PATCH] Add group-first membership management (menu option 16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Group membership was previously only editable per-user (option 4's user menu, option 6 toggles that one user's groups) — no way to pick a group and see/toggle its members directly. Adds the reverse entry point: pick a group, then toggle which users are in it. Same _authelia_toggle_group() underneath, just entered from the other direction. --- services/authelia.sh | 110 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/services/authelia.sh b/services/authelia.sh index 869db6e..566f058 100644 --- a/services/authelia.sh +++ b/services/authelia.sh @@ -248,10 +248,12 @@ install_authelia() { echo " safe to re-run any time)" echo " 14) Rename an outside-access group (e.g. \"customer1\" -> \"acme-corp\")" 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 " 0) Leave as-is / exit" echo "" local EXISTING_CHOICE="" - prompt_text " Choice [1-15, 0 to exit]:" "0" EXISTING_CHOICE + prompt_text " Choice [1-16, 0 to exit]:" "0" EXISTING_CHOICE case "$EXISTING_CHOICE" in 1) add_authelia_domain @@ -312,6 +314,10 @@ install_authelia() { _authelia_report_groups return 0 ;; + 16) + _authelia_manage_group_membership + return 0 + ;; 0|*) echo " Keeping existing Authelia. (Edit config/users.yml then: cd $AUTHELIA_DIR && docker compose restart authelia)" return 0 @@ -1967,6 +1973,108 @@ _authelia_report_groups() { done } +# Group-first complement to _authelia_manage_one_user()'s option 6 (which is +# user-first: pick a user, then toggle which groups they're in). This is the +# other direction — pick a group, then toggle which users are in it — for +# when you know which group you want to populate and don't want to visit +# each user one at a time. Same _authelia_toggle_group() underneath either +# way; this is purely a different entry point onto the same membership data. +# +# A group only exists here once it's been attached to at least one site +# (via site protection's "Outside access" choice) — Authelia has no notion +# of a group that isn't referenced by an access rule or a user's +# membership, so there's no separate "create an empty group" step; naming +# a new group during site protection is what creates it. +_authelia_manage_group_membership() { + local users_file="$DOCKER_DIR/authelia/config/users.yml" + local config_file="$DOCKER_DIR/authelia/config/configuration.yml" + [ -f "$users_file" ] || { log_warning "No users.yml found — install Authelia first."; return 1; } + + local -a groups + mapfile -t groups < <(_authelia_list_scoped_groups "$users_file") + if [ "${#groups[@]}" -eq 0 ]; then + log_info "No outside-access groups exist yet. A group is created the first time you" + log_info "protect a site (option 10, or a service's own \"Add Sign in with Authelia\"" + log_info "offer) and choose \"Outside access\" instead of \"Native\" — name it there" + log_info "(e.g. \"customer1\"), and it'll show up here afterward to manage its members." + return 0 + fi + + echo "" + echo " Outside-access groups:" + local gi + for gi in "${!groups[@]}"; do + echo " $((gi + 1))) ${groups[$gi]%-only}" + done + echo " 0) Cancel" + local GROUP_CHOICE="" + prompt_text " Which group? [0]:" "0" GROUP_CHOICE + if ! [[ "$GROUP_CHOICE" =~ ^[0-9]+$ ]] || [ "$GROUP_CHOICE" -lt 1 ] || [ "$GROUP_CHOICE" -gt "${#groups[@]}" ]; then + log_info "Cancelled — nothing changed." + return 0 + fi + local GROUP="${groups[$((GROUP_CHOICE - 1))]}" + + 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 " Members of '${GROUP%-only}' (* = currently a member):" + local ui u start_end start end member + for ui in "${!all_users[@]}"; do + u="${all_users[$ui]}" + start_end="$(_authelia_user_line_range "$users_file" "$u")" + start="${start_end% *}"; end="${start_end#* }" + member=" " + sed -n "${start},${end}p" "$users_file" | grep -qF " - ${GROUP}" && member="*" + echo " $((ui + 1))) [${member}] ${u}" + done + echo "" + echo " Pick by number (space-separated) to toggle — a member gets removed, a" + echo " non-member gets added. 0 (or blank) to leave unchanged." + local TOGGLE_SEL="" + prompt_text " Numbers [0]:" "0" TOGGLE_SEL + local -a TOGGLE_TOKENS + read -ra TOGGLE_TOKENS <<< "$TOGGLE_SEL" + local tk tidx tu t_range t_start t_end is_member CHANGED=0 + for tk in "${TOGGLE_TOKENS[@]}"; do + [[ "$tk" =~ ^[0-9]+$ ]] || continue + [ "$tk" -ge 1 ] && [ "$tk" -le "${#all_users[@]}" ] || continue + tidx=$((tk - 1)) + tu="${all_users[$tidx]}" + # Re-resolve line range before every toggle — a prior toggle in this + # same loop shifts every line after it (see the equivalent comment + # in _authelia_manage_one_user's own group-toggle option). + t_range="$(_authelia_user_line_range "$users_file" "$tu")" + t_start="${t_range% *}"; t_end="${t_range#* }" + is_member="false" + sed -n "${t_start},${t_end}p" "$users_file" | grep -qF " - ${GROUP}" && is_member="true" + if [ "$is_member" = "true" ]; then + _authelia_toggle_group "$users_file" "$t_start" "$t_end" "$GROUP" "false" + log_success "Removed ${tu} from '${GROUP%-only}'" + else + _authelia_toggle_group "$users_file" "$t_start" "$t_end" "$GROUP" "true" + log_success "Added ${tu} to '${GROUP%-only}'" + fi + CHANGED=1 + done + + if [ "$CHANGED" = "1" ]; then + 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 + fi +} + # 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