feat(debyltech-cloud): lock customers to read-only, no discovery

Customers are admin-created accounts in per-customer groups. They should
read what staff share with them and nothing else. Checked against a test
customer in the UI, and by running the sharee and contacts-menu search
services as that user.

- shareapi_exclude_groups=allow, list [admin]: only staff can share. Exclude
  mode ("yes") only disables sharing for users whose groups are ALL
  excluded, so it never catches a customer in their own group.
- User/group autocomplete off. By default a customer typing "bas" found the
  owner's account. Staff share by exact group name; LibreSign signers are
  found by email.
- New shares default to View only (shareapi_default_permissions=1).
- files default_quota 0 B, so customers get no personal storage. Staff in
  cloud_debyltech_staff_users are exempted (skipped if not yet created).
- No "Leon Green" sample contact in new address books.
- The verify script fails the deploy if any isolation setting drifts.
- README: staff and customer onboarding checklist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Bastian de Byl
2026-09-28 19:10:47 -04:00
co-authored by Claude Opus 5.5
parent be50798096
commit c4fe506860
4 changed files with 118 additions and 0 deletions
@@ -468,6 +468,68 @@
insertbefore: '^\);'
create: false
# ---------------------------------------------------------------------------
# Customer isolation. Customers are admin-created accounts in a per-customer
# group, and must only READ what staff share with them -- not upload, not
# share onward, and not discover that other customers exist. Verified
# 2026-09-28 against a test customer, both in the UI and with the sharee and
# contacts-menu search services run as that user.
#
# shareapi_exclude_groups=allow + list=[staff]
# Only staff may share. NOT "yes" (exclude mode): that only disables
# sharing for users whose groups are ALL excluded, so a customer in
# their own per-customer group would never be caught by it.
# shareapi_allow_share_dialog_user_enumeration=no
# No partial-match browsing of accounts or groups, for anyone. By
# default a customer typing "bas" found the owner's account. Staff
# share to a customer group by typing its exact name; LibreSign signers
# are found by email and are unaffected.
# shareapi_default_permissions=1
# New shares default to View only; tick "Allow editing" per share to
# let a customer upload.
# files default_quota=0 B
# No personal storage, so no "+ New" in a customer's own home. Uploads
# into a share granted editing count against the OWNER's quota and still
# work. Staff are exempted by the next task.
# dav enableDefaultContact=false
# No "Leon Green" sample contact in new address books.
- name: set debyltech-cloud customer isolation policy
become: true
become_user: "{{ podman_user }}"
ansible.builtin.command: >
podman exec -u www-data debyltech-cloud
php occ config:app:set {{ item.app }} {{ item.k }} --value={{ item.v | quote }}
{{ ('--type=' ~ item.t) if item.t is defined else '' }}
register: debyltech_isolation
changed_when: "'is now set to' in debyltech_isolation.stdout"
loop:
- {app: core, k: shareapi_exclude_groups, v: allow}
- {app: core, k: shareapi_exclude_groups_list, v: "{{ [cloud_debyltech_staff_group] | to_json }}"}
- {app: core, k: shareapi_allow_share_dialog_user_enumeration, v: "no"}
- {app: core, k: shareapi_default_permissions, v: "1"}
- {app: files, k: default_quota, v: "0 B"}
- {app: dav, k: enableDefaultContact, v: "0", t: boolean}
loop_control:
label: "{{ item.app }}.{{ item.k }}"
- name: exempt debyltech-cloud staff from the zero default quota
become: true
become_user: "{{ podman_user }}"
ansible.builtin.shell: |
set -o pipefail
occ() { podman exec -u www-data debyltech-cloud php occ "$@"; }
occ user:info {{ item | quote }} >/dev/null 2>&1 || exit 0
cur=$(occ user:setting {{ item | quote }} files quota) || cur='<unset>'
if [ "$cur" != none ]; then
occ user:setting {{ item | quote }} files quota none
echo CHANGED
fi
args:
executable: /bin/bash
register: debyltech_staff_quota
changed_when: "'CHANGED' in debyltech_staff_quota.stdout"
loop: "{{ cloud_debyltech_staff_users }}"
# ---------------------------------------------------------------------------
# Branding: debyltechmail, cloned from skudakmail. custom_apps is a persisted
# bind mount, so only enabling and config need reasserting.