mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 16:06:32 +00:00
fix: explain sharing when Stalwart 1.0 keeps its directory closed
Stalwart 1.0 refuses Principal/query with `forbidden` unless the
administrator allows directory queries, which is off by default. The
app took the missing Principal/get answer for an empty directory: the
calendar share sheet said "Sharing is not available on this server.",
the file share sheet said "No other users found", and existing shares
showed bare ids.
- `listPrincipals` says whether the listing is complete, `restricted`
or failed. On `forbidden` it falls back to Principal/get without ids,
which 1.0 still answers with the user's own account, their groups and
the accounts that shared something with them.
- Principal/get asks for `id`, `type`, `name`, `description` and `email`
only; 1.0's default list adds `capabilities` and `accounts`.
- Both share sheets explain a restricted directory under the search
box. Only 1.0's own refusal ("The administrator has disabled directory
queries.") gets the wording that points at Stalwart's sharing
settings; any other refusal gets a generic one, and a failed listing
says so.
- A sharee the app can't name shows as "Unknown user" with its id, and
can still be changed or removed.
On 0.16 with the default roles the listing is complete and the sheets
behave as before.
This commit is contained in:
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "تعذّر تحميل قائمة المستخدمين والمجموعات.",
|
||||
"directory_not_browsable": "لا يسمح لك خادمك بتصفح دليل المستخدمين.",
|
||||
"directory_restricted": "لا يسمح خادمك بتصفح دليل المستخدمين، لذا لا تظهر إلا مجموعاتك والأشخاص الذين شاركوا شيئًا معك. يمكن للمسؤول تفعيل استعلامات الدليل من إعدادات المشاركة في Stalwart.",
|
||||
"unknown_user": "مستخدم غير معروف"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "يحتاج إلى انتباه",
|
||||
"remove_account_hint": "اضغط مطولًا لإزالة هذا الحساب",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "No s'ha pogut carregar la llista d'usuaris i grups.",
|
||||
"directory_not_browsable": "El vostre servidor no us permet explorar el directori d'usuaris.",
|
||||
"directory_restricted": "El vostre servidor no permet explorar el directori d'usuaris, així que només es mostren els vostres grups i les persones que han compartit alguna cosa amb vós. Un administrador pot activar les consultes al directori a la configuració de compartició de Stalwart.",
|
||||
"unknown_user": "Usuari desconegut"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Cal atenció",
|
||||
"remove_account_hint": "Manteniu premut per eliminar aquest compte",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Seznam uživatelů a skupin se nepodařilo načíst.",
|
||||
"directory_not_browsable": "Váš server vám neumožňuje procházet adresář uživatelů.",
|
||||
"directory_restricted": "Váš server neumožňuje procházet adresář uživatelů, proto se zobrazují jen vaše skupiny a lidé, kteří s vámi něco sdíleli. Správce může dotazy na adresář zapnout v nastavení sdílení serveru Stalwart.",
|
||||
"unknown_user": "Neznámý uživatel"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Vyžaduje pozornost",
|
||||
"remove_account_hint": "Dlouhým stisknutím tento účet odeberete",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Kunne ikke indlæse listen over brugere og grupper.",
|
||||
"directory_not_browsable": "Din server lader dig ikke gennemse brugerkataloget.",
|
||||
"directory_restricted": "Din server tillader ikke, at brugerkataloget gennemses, så kun dine grupper og personer, der har delt noget med dig, vises. En administrator kan slå katalogforespørgsler til i Stalwarts delingsindstillinger.",
|
||||
"unknown_user": "Ukendt bruger"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Kræver handling",
|
||||
"remove_account_hint": "Tryk længe for at fjerne denne konto",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Die Liste der Benutzer und Gruppen konnte nicht geladen werden.",
|
||||
"directory_not_browsable": "Ihr Server erlaubt Ihnen nicht, sein Benutzerverzeichnis zu durchsuchen.",
|
||||
"directory_restricted": "Ihr Server erlaubt kein Durchsuchen seines Benutzerverzeichnisses, daher werden nur Ihre Gruppen und Personen aufgeführt, die etwas mit Ihnen geteilt haben. Ein Administrator kann Verzeichnisabfragen in den Freigabeeinstellungen von Stalwart aktivieren.",
|
||||
"unknown_user": "Unbekannter Benutzer"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Aktion erforderlich",
|
||||
"remove_account_hint": "Lange drücken, um dieses Konto zu entfernen",
|
||||
|
||||
@@ -984,6 +984,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Couldn't load the list of users and groups.",
|
||||
"directory_not_browsable": "Your server doesn't let you browse its user directory.",
|
||||
"directory_restricted": "Your server doesn't allow browsing its user directory, so only your groups and people who have shared with you are listed. An administrator can turn on directory queries in Stalwart's sharing settings.",
|
||||
"unknown_user": "Unknown user"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Needs attention",
|
||||
"remove_account_hint": "Long-press to remove this account",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "No se pudo cargar la lista de usuarios y grupos.",
|
||||
"directory_not_browsable": "Tu servidor no te permite explorar su directorio de usuarios.",
|
||||
"directory_restricted": "Tu servidor no permite explorar su directorio de usuarios, así que solo se muestran tus grupos y las personas que han compartido algo contigo. Un administrador puede activar las consultas al directorio en los ajustes de uso compartido de Stalwart.",
|
||||
"unknown_user": "Usuario desconocido"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Requiere atención",
|
||||
"remove_account_hint": "Mantén pulsado para quitar esta cuenta",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "بارگذاری فهرست کاربران و گروهها ممکن نشد.",
|
||||
"directory_not_browsable": "سرور شما به شما اجازهٔ مرور فهرست کاربرانش را نمیدهد.",
|
||||
"directory_restricted": "سرور شما اجازهٔ مرور فهرست کاربرانش را نمیدهد، بنابراین فقط گروههای شما و افرادی که چیزی را با شما به اشتراک گذاشتهاند نمایش داده میشوند. مدیر میتواند پرسوجوهای فهرست را در تنظیمات اشتراکگذاری Stalwart فعال کند.",
|
||||
"unknown_user": "کاربر ناشناس"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "نیاز به توجه دارد",
|
||||
"remove_account_hint": "برای حذف این حساب، انگشت خود را نگه دارید",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Impossible de charger la liste des utilisateurs et des groupes.",
|
||||
"directory_not_browsable": "Votre serveur ne vous permet pas de parcourir son annuaire d'utilisateurs.",
|
||||
"directory_restricted": "Votre serveur ne permet pas de parcourir son annuaire d'utilisateurs, seuls vos groupes et les personnes ayant partagé quelque chose avec vous sont donc listés. Un administrateur peut activer les requêtes d'annuaire dans les paramètres de partage de Stalwart.",
|
||||
"unknown_user": "Utilisateur inconnu"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Action requise",
|
||||
"remove_account_hint": "Appuyez longuement pour supprimer ce compte",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "לא ניתן היה לטעון את רשימת המשתמשים והקבוצות.",
|
||||
"directory_not_browsable": "השרת שלך אינו מאפשר לך לעיין בספריית המשתמשים שלו.",
|
||||
"directory_restricted": "השרת שלך אינו מאפשר לעיין בספריית המשתמשים שלו, ולכן מוצגות רק הקבוצות שלך ואנשים ששיתפו איתך משהו. מנהל מערכת יכול להפעיל שאילתות ספרייה בהגדרות השיתוף של Stalwart.",
|
||||
"unknown_user": "משתמש לא ידוע"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "דורש תשומת לב",
|
||||
"remove_account_hint": "לחיצה ארוכה להסרת החשבון הזה",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Nem sikerült betölteni a felhasználók és csoportok listáját.",
|
||||
"directory_not_browsable": "A szervered nem engedi, hogy böngészd a felhasználói címtárát.",
|
||||
"directory_restricted": "A szervered nem engedi a felhasználói címtár böngészését, ezért csak a csoportjaid és azok jelennek meg, akik megosztottak veled valamit. Egy rendszergazda a Stalwart megosztási beállításaiban engedélyezheti a címtár-lekérdezéseket.",
|
||||
"unknown_user": "Ismeretlen felhasználó"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Figyelmet igényel",
|
||||
"remove_account_hint": "Nyomd hosszan a fiók eltávolításához",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Impossibile caricare l'elenco di utenti e gruppi.",
|
||||
"directory_not_browsable": "Il tuo server non ti consente di sfogliare la directory degli utenti.",
|
||||
"directory_restricted": "Il tuo server non consente di sfogliare la directory degli utenti, quindi vengono elencati solo i tuoi gruppi e le persone che hanno condiviso qualcosa con te. Un amministratore può attivare le query sulla directory nelle impostazioni di condivisione di Stalwart.",
|
||||
"unknown_user": "Utente sconosciuto"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Richiede attenzione",
|
||||
"remove_account_hint": "Tieni premuto per rimuovere questo account",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "ユーザーとグループの一覧を読み込めませんでした。",
|
||||
"directory_not_browsable": "お使いのサーバーでは、ユーザーディレクトリを参照できません。",
|
||||
"directory_restricted": "お使いのサーバーではユーザーディレクトリの参照が許可されていないため、所属グループと、あなたと共有しているユーザーのみが表示されます。管理者は Stalwart の共有設定でディレクトリクエリを有効にできます。",
|
||||
"unknown_user": "不明なユーザー"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "要確認",
|
||||
"remove_account_hint": "長押しするとこのアカウントを削除できます",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "사용자 및 그룹 목록을 불러오지 못했습니다.",
|
||||
"directory_not_browsable": "서버에서 사용자 디렉터리를 탐색할 수 없습니다.",
|
||||
"directory_restricted": "서버에서 사용자 디렉터리 탐색을 허용하지 않아 내 그룹과 나와 공유한 사람만 표시됩니다. 관리자는 Stalwart의 공유 설정에서 디렉터리 쿼리를 켤 수 있습니다.",
|
||||
"unknown_user": "알 수 없는 사용자"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "확인 필요",
|
||||
"remove_account_hint": "길게 눌러 이 계정 삭제",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Neizdevās ielādēt lietotāju un grupu sarakstu.",
|
||||
"directory_not_browsable": "Jūsu serveris neļauj jums pārlūkot tā lietotāju direktoriju.",
|
||||
"directory_restricted": "Jūsu serveris neļauj pārlūkot tā lietotāju direktoriju, tāpēc tiek parādītas tikai jūsu grupas un personas, kas ar jums kaut ko kopīgojušas. Administrators var ieslēgt direktorija vaicājumus Stalwart kopīgošanas iestatījumos.",
|
||||
"unknown_user": "Nezināms lietotājs"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Nepieciešama uzmanība",
|
||||
"remove_account_hint": "Turiet nospiestu, lai noņemtu šo kontu",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Хэрэглэгч болон бүлгийн жагсаалтыг ачаалж чадсангүй.",
|
||||
"directory_not_browsable": "Таны сервер танд хэрэглэгчийн лавлахаа үзэхийг зөвшөөрдөггүй.",
|
||||
"directory_restricted": "Таны сервер хэрэглэгчийн лавлахаа үзэхийг зөвшөөрдөггүй тул зөвхөн таны бүлгүүд болон тантай ямар нэг зүйл хуваалцсан хүмүүс харагдана. Админ Stalwart-ын хуваалцах тохиргоонд лавлахын хайлтыг идэвхжүүлж болно.",
|
||||
"unknown_user": "Үл мэдэгдэх хэрэглэгч"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Анхаарал шаардлагатай",
|
||||
"remove_account_hint": "Энэ бүртгэлийг хасахын тулд удаан дарна уу",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Kunne ikke laste inn listen over brukere og grupper.",
|
||||
"directory_not_browsable": "Serveren din lar deg ikke bla gjennom brukerkatalogen.",
|
||||
"directory_restricted": "Serveren din tillater ikke å bla gjennom brukerkatalogen, så bare gruppene dine og personer som har delt noe med deg, vises. En administrator kan slå på katalogspørringer i delingsinnstillingene i Stalwart.",
|
||||
"unknown_user": "Ukjent bruker"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Krever handling",
|
||||
"remove_account_hint": "Trykk lenge for å fjerne denne kontoen",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Kan de lijst met gebruikers en groepen niet laden.",
|
||||
"directory_not_browsable": "Uw server staat u niet toe de gebruikersdirectory te doorbladeren.",
|
||||
"directory_restricted": "Uw server staat het doorbladeren van de gebruikersdirectory niet toe, daarom worden alleen uw groepen en personen die iets met u hebben gedeeld weergegeven. Een beheerder kan directoryquery's inschakelen in de instellingen voor delen van Stalwart.",
|
||||
"unknown_user": "Onbekende gebruiker"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Actie vereist",
|
||||
"remove_account_hint": "Lang drukken om dit account te verwijderen",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Nie udało się wczytać listy użytkowników i grup.",
|
||||
"directory_not_browsable": "Twój serwer nie pozwala Ci przeglądać katalogu użytkowników.",
|
||||
"directory_restricted": "Twój serwer nie pozwala przeglądać katalogu użytkowników, dlatego widoczne są tylko Twoje grupy i osoby, które coś Ci udostępniły. Administrator może włączyć zapytania do katalogu w ustawieniach udostępniania Stalwart.",
|
||||
"unknown_user": "Nieznany użytkownik"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Wymaga uwagi",
|
||||
"remove_account_hint": "Przytrzymaj, aby usunąć to konto",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Não foi possível carregar a lista de usuários e grupos.",
|
||||
"directory_not_browsable": "Seu servidor não permite que você navegue no diretório de usuários.",
|
||||
"directory_restricted": "Seu servidor não permite navegar no diretório de usuários, então apenas seus grupos e as pessoas que compartilharam algo com você são listados. Um administrador pode ativar as consultas ao diretório nas configurações de compartilhamento do Stalwart.",
|
||||
"unknown_user": "Usuário desconhecido"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Requer atenção",
|
||||
"remove_account_hint": "Toque e segure para remover esta conta",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Lista de utilizatori și grupuri nu a putut fi încărcată.",
|
||||
"directory_not_browsable": "Serverul dvs. nu vă permite să răsfoiți directorul de utilizatori.",
|
||||
"directory_restricted": "Serverul dvs. nu permite răsfoirea directorului de utilizatori, așa că sunt afișate doar grupurile dvs. și persoanele care au partajat ceva cu dvs. Un administrator poate activa interogările directorului în setările de partajare ale Stalwart.",
|
||||
"unknown_user": "Utilizator necunoscut"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Necesită atenție",
|
||||
"remove_account_hint": "Apăsați lung pentru a elimina acest cont",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Не удалось загрузить список пользователей и групп.",
|
||||
"directory_not_browsable": "Ваш сервер не позволяет вам просматривать каталог пользователей.",
|
||||
"directory_restricted": "Ваш сервер не разрешает просматривать каталог пользователей, поэтому показаны только ваши группы и люди, которые чем-то поделились с вами. Администратор может включить запросы к каталогу в настройках общего доступа Stalwart.",
|
||||
"unknown_user": "Неизвестный пользователь"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Требует внимания",
|
||||
"remove_account_hint": "Удерживайте, чтобы удалить этот аккаунт",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Zoznam používateľov a skupín sa nepodarilo načítať.",
|
||||
"directory_not_browsable": "Váš server vám nepovoľuje prehliadať adresár používateľov.",
|
||||
"directory_restricted": "Váš server nepovoľuje prehliadať adresár používateľov, preto sa zobrazujú iba vaše skupiny a ľudia, ktorí s vami niečo zdieľali. Správca môže zapnúť dopyty na adresár v nastaveniach zdieľania servera Stalwart.",
|
||||
"unknown_user": "Neznámy používateľ"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Vyžaduje pozornosť",
|
||||
"remove_account_hint": "Dlhým stlačením odstránite tento účet",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Kullanıcı ve grup listesi yüklenemedi.",
|
||||
"directory_not_browsable": "Sunucunuz kullanıcı dizinine göz atmanıza izin vermiyor.",
|
||||
"directory_restricted": "Sunucunuz kullanıcı dizinine göz atılmasına izin vermiyor, bu nedenle yalnızca gruplarınız ve sizinle bir şey paylaşmış kişiler listeleniyor. Bir yönetici, Stalwart'ın paylaşım ayarlarından dizin sorgularını açabilir.",
|
||||
"unknown_user": "Bilinmeyen kullanıcı"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "İlgilenmeniz gerekiyor",
|
||||
"remove_account_hint": "Bu hesabı kaldırmak için uzun basın",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "Не вдалося завантажити список користувачів і груп.",
|
||||
"directory_not_browsable": "Ваш сервер не дозволяє вам переглядати каталог користувачів.",
|
||||
"directory_restricted": "Ваш сервер не дозволяє переглядати каталог користувачів, тому показано лише ваші групи та людей, які чимось поділилися з вами. Адміністратор може ввімкнути запити до каталогу в налаштуваннях спільного доступу Stalwart.",
|
||||
"unknown_user": "Невідомий користувач"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "Потребує уваги",
|
||||
"remove_account_hint": "Утримуйте, щоб видалити цей обліковий запис",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "無法載入使用者和群組清單。",
|
||||
"directory_not_browsable": "您的伺服器不允許您瀏覽使用者目錄。",
|
||||
"directory_restricted": "您的伺服器不允許瀏覽使用者目錄,因此只會列出您所屬的群組,以及曾與您分享內容的使用者。管理員可以在 Stalwart 的分享設定中啟用目錄查詢。",
|
||||
"unknown_user": "未知的使用者"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "需要處理",
|
||||
"remove_account_hint": "長按即可移除此帳號",
|
||||
|
||||
@@ -971,6 +971,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"directory_failed": "无法加载用户和群组列表。",
|
||||
"directory_not_browsable": "您的服务器不允许您浏览用户目录。",
|
||||
"directory_restricted": "您的服务器不允许浏览用户目录,因此仅列出您所在的群组以及与您共享过内容的用户。管理员可以在 Stalwart 的共享设置中启用目录查询。",
|
||||
"unknown_user": "未知用户"
|
||||
},
|
||||
"sidebar": {
|
||||
"account_error": "需要处理",
|
||||
"remove_account_hint": "长按可移除此账户",
|
||||
|
||||
@@ -36,11 +36,13 @@ import {
|
||||
getPrincipals,
|
||||
isCrossAccountId,
|
||||
isFolder,
|
||||
listPrincipals,
|
||||
moveFileNode,
|
||||
renameFileNode,
|
||||
setFileNodeShare,
|
||||
supportsSharing,
|
||||
} from '../files';
|
||||
import { directoryNotice } from '../../lib/principal-directory';
|
||||
|
||||
const mockRequest = jmapClient.request as ReturnType<typeof vi.fn>;
|
||||
const mockHasCapability = jmapClient.hasCapability as ReturnType<typeof vi.fn>;
|
||||
@@ -488,6 +490,88 @@ describe('getPrincipals', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('listPrincipals', () => {
|
||||
// userc's view on the test servers (live-checked).
|
||||
const DIRECTORY = [
|
||||
{ id: 'b', type: 'individual', name: 'admin@example.org', description: 'System administrator', email: 'admin@example.org' },
|
||||
{ id: 'c', type: 'group', name: 'team@example.org', description: 'Team (test group)', email: 'team@example.org' },
|
||||
{ id: 'd', type: 'individual', name: 'usera@example.org', description: 'User A', email: 'usera@example.org' },
|
||||
{ id: 'e', type: 'individual', name: 'userb@example.org', description: 'User B', email: 'userb@example.org' },
|
||||
{ id: 'f', type: 'individual', name: 'userc@example.org', description: 'User C', email: 'userc@example.org' },
|
||||
];
|
||||
const OWN = [DIRECTORY[4]];
|
||||
const PROPERTIES = ['id', 'type', 'name', 'description', 'email'];
|
||||
// Stalwart 1.0 with allowDirectoryQueries off (the default).
|
||||
const QUERIES_OFF = [
|
||||
['error', { type: 'forbidden', description: 'The administrator has disabled directory queries.' }, '0'],
|
||||
['error', { type: 'invalidResultReference', description: 'Result reference to 0#Principal/query not found.' }, '1'],
|
||||
];
|
||||
|
||||
it('lists the whole directory on Stalwart 0.16, asking only for the properties it shows', async () => {
|
||||
mockRequest.mockResolvedValue({
|
||||
methodResponses: [
|
||||
['Principal/query', { accountId: 'acc-1', queryState: 'n', canCalculateChanges: true, position: 0, ids: ['b', 'c', 'd', 'e', 'f'] }, '0'],
|
||||
['Principal/get', { accountId: 'acc-1', state: 'n', list: DIRECTORY, notFound: [] }, '1'],
|
||||
],
|
||||
});
|
||||
|
||||
expect(await listPrincipals()).toEqual({ principals: DIRECTORY, status: 'ok' });
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequest.mock.calls[0][0][1][1].properties).toEqual(PROPERTIES);
|
||||
});
|
||||
|
||||
it('falls back to the principals the user knows when Stalwart 1.0 keeps its directory closed', async () => {
|
||||
mockRequest
|
||||
.mockResolvedValueOnce({ methodResponses: QUERIES_OFF })
|
||||
.mockResolvedValueOnce({
|
||||
methodResponses: [['Principal/get', { accountId: 'acc-1', state: 'n', list: OWN, notFound: [] }, '0']],
|
||||
});
|
||||
|
||||
expect(await listPrincipals()).toEqual({ principals: OWN, status: 'restricted', directoryQueriesOff: true });
|
||||
const [calls, using] = mockRequest.mock.calls[1];
|
||||
expect(calls).toEqual([['Principal/get', { accountId: 'acc-1', ids: null, properties: PROPERTIES }, '0']]);
|
||||
expect(using).toEqual([CAPABILITIES.CORE, CAPABILITIES.PRINCIPALS]);
|
||||
});
|
||||
|
||||
it('calls another forbidden answer restricted without claiming directory queries are off', async () => {
|
||||
mockRequest
|
||||
.mockResolvedValueOnce({
|
||||
methodResponses: [
|
||||
['error', { type: 'forbidden', description: 'You do not have enough permissions to access this resource.' }, '0'],
|
||||
['error', { type: 'invalidResultReference' }, '1'],
|
||||
],
|
||||
})
|
||||
.mockResolvedValueOnce({ methodResponses: [['error', { type: 'forbidden' }, '0']] });
|
||||
|
||||
expect(await listPrincipals()).toEqual({ principals: [], status: 'restricted', directoryQueriesOff: false });
|
||||
});
|
||||
|
||||
it('reports a failed listing with what arrived', async () => {
|
||||
mockMaxObjectsInGet.mockReturnValue(2);
|
||||
mockRequest
|
||||
.mockResolvedValueOnce({
|
||||
methodResponses: [
|
||||
['Principal/query', { ids: ['b', 'c'] }, '0'],
|
||||
['Principal/get', { list: DIRECTORY.slice(0, 2) }, '1'],
|
||||
],
|
||||
})
|
||||
.mockRejectedValueOnce(new Error('JMAP request failed: 503'));
|
||||
expect(await listPrincipals()).toEqual({ principals: DIRECTORY.slice(0, 2), status: 'error' });
|
||||
|
||||
mockRequest.mockResolvedValueOnce({ methodResponses: [['error', { type: 'serverFail' }, '0']] });
|
||||
expect(await listPrincipals()).toEqual({ principals: [], status: 'error' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('directoryNotice', () => {
|
||||
it('uses the 1.0 wording only for Stalwart 1.0\'s own refusal', () => {
|
||||
expect(directoryNotice({ status: 'ok' })).toBeNull();
|
||||
expect(directoryNotice({ status: 'restricted', directoryQueriesOff: true })).toBe('restricted');
|
||||
expect(directoryNotice({ status: 'restricted', directoryQueriesOff: false })).toBe('not_browsable');
|
||||
expect(directoryNotice({ status: 'error' })).toBe('failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decodeFileNodeName at the API boundary (#869)', () => {
|
||||
it('decodes percent-encoded names from getAllFileNodes and the cross-account list', async () => {
|
||||
mockRequest.mockResolvedValue({
|
||||
|
||||
+109
-25
@@ -422,35 +422,119 @@ export async function setFileNodeShare(
|
||||
/** Safety bound on how many principals the share picker pages through. */
|
||||
const MAX_PRINCIPALS = 20_000;
|
||||
|
||||
// List all principals visible to the user. Stalwart returns the full
|
||||
// directory regardless of `filter`, so callers filter client-side. Paged by
|
||||
// maxObjectsInGet: an unbounded query hands every id to the chained
|
||||
// Principal/get, which the server refuses with `requestTooLarge` once the
|
||||
// directory holds more than that (500), leaving the picker empty.
|
||||
export async function getPrincipals(): Promise<Principal[]> {
|
||||
if (!supportsSharing()) return [];
|
||||
// The Principal properties the share sheets read. 0.16's default list is the
|
||||
// same; 1.0's adds `capabilities` for every principal and `accounts`, a whole
|
||||
// account object with every account capability.
|
||||
const PRINCIPAL_PROPERTIES = ['id', 'type', 'name', 'description', 'email'];
|
||||
|
||||
// Stalwart 1.0's answer to Principal/query while the administrator keeps
|
||||
// directory queries off (the default).
|
||||
const DIRECTORY_QUERIES_OFF = 'The administrator has disabled directory queries.';
|
||||
|
||||
/**
|
||||
* How complete a principal listing is:
|
||||
* - `ok`: the whole directory;
|
||||
* - `restricted`: the server refused to list its directory (`forbidden`:
|
||||
* Stalwart 1.0 with directory queries off, or a role without the
|
||||
* permission). The principals are what Principal/get without ids still
|
||||
* returns, on 1.0 the user's own account, their groups and the accounts
|
||||
* that shared something with them;
|
||||
* - `error`: the listing failed part-way; the principals are what arrived.
|
||||
*/
|
||||
export type PrincipalDirectoryStatus = 'ok' | 'restricted' | 'error';
|
||||
|
||||
export interface PrincipalListing {
|
||||
principals: Principal[];
|
||||
status: PrincipalDirectoryStatus;
|
||||
/**
|
||||
* The refusal was Stalwart 1.0's own: an administrator can allow directory
|
||||
* queries in its sharing settings.
|
||||
*/
|
||||
directoryQueriesOff?: boolean;
|
||||
}
|
||||
|
||||
function principalsUsing(): string[] {
|
||||
return [CAPABILITIES.CORE, CAPABILITIES.PRINCIPALS];
|
||||
}
|
||||
|
||||
// List all principals visible to the user, and how complete the list is.
|
||||
// Stalwart returns the full directory regardless of `filter`, so callers
|
||||
// filter client-side. Paged by maxObjectsInGet: an unbounded query hands every
|
||||
// id to the chained Principal/get, which the server refuses with
|
||||
// `requestTooLarge` once the directory holds more than that (500), leaving the
|
||||
// picker empty.
|
||||
export async function listPrincipals(): Promise<PrincipalListing> {
|
||||
if (!supportsSharing()) return { principals: [], status: 'ok' };
|
||||
const accountId = jmapClient.accountId;
|
||||
const pageSize = jmapClient.getMaxObjectsInGet();
|
||||
const all: Principal[] = [];
|
||||
for (let position = 0; position < MAX_PRINCIPALS;) {
|
||||
const res = await jmapClient.request(
|
||||
[
|
||||
['Principal/query', { accountId, position, limit: pageSize }, '0'],
|
||||
['Principal/get', {
|
||||
accountId,
|
||||
'#ids': { resultOf: '0', name: 'Principal/query', path: '/ids' },
|
||||
}, '1'],
|
||||
],
|
||||
[CAPABILITIES.CORE, CAPABILITIES.PRINCIPALS],
|
||||
);
|
||||
const ids = (res.methodResponses.find((r) => r[0] === 'Principal/query')?.[1].ids ?? []) as string[];
|
||||
const getResp = res.methodResponses.find((r) => r[0] === 'Principal/get');
|
||||
if (!getResp) break;
|
||||
all.push(...((getResp[1].list ?? []) as Principal[]));
|
||||
if (ids.length < pageSize) break;
|
||||
position += ids.length;
|
||||
try {
|
||||
for (let position = 0; position < MAX_PRINCIPALS;) {
|
||||
const res = await jmapClient.request(
|
||||
[
|
||||
['Principal/query', { accountId, position, limit: pageSize }, '0'],
|
||||
['Principal/get', {
|
||||
accountId,
|
||||
'#ids': { resultOf: '0', name: 'Principal/query', path: '/ids' },
|
||||
properties: PRINCIPAL_PROPERTIES,
|
||||
}, '1'],
|
||||
],
|
||||
principalsUsing(),
|
||||
);
|
||||
const responses = res.methodResponses ?? [];
|
||||
const queryResp = responses.find((r) => r[0] === 'Principal/query');
|
||||
const getResp = responses.find((r) => r[0] === 'Principal/get');
|
||||
if (!queryResp || !getResp) {
|
||||
// A refused query makes the chained get fail with
|
||||
// invalidResultReference; only the query's own error says why.
|
||||
const error = (responses.find((r) => r[0] === 'error' && r[2] === '0')
|
||||
?? responses.find((r) => r[0] === 'error'))?.[1] as { type?: string; description?: string } | undefined;
|
||||
if (error?.type === 'forbidden') {
|
||||
return {
|
||||
principals: await getKnownPrincipals(accountId),
|
||||
status: 'restricted',
|
||||
directoryQueriesOff: error.description === DIRECTORY_QUERIES_OFF,
|
||||
};
|
||||
}
|
||||
console.warn('[files] listing principals failed', error);
|
||||
return { principals: all, status: 'error' };
|
||||
}
|
||||
const ids = (queryResp[1].ids ?? []) as string[];
|
||||
all.push(...((getResp[1].list ?? []) as Principal[]));
|
||||
if (ids.length < pageSize) break;
|
||||
position += ids.length;
|
||||
}
|
||||
return { principals: all, status: 'ok' };
|
||||
} catch (err) {
|
||||
console.warn('[files] listing principals failed', err);
|
||||
return { principals: all, status: 'error' };
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Principal/get without ids. When the directory can't be listed, Stalwart 1.0
|
||||
* still returns the principals the user already knows: their own account,
|
||||
* their groups and the accounts that shared something with them. Empty when
|
||||
* that is refused too.
|
||||
*/
|
||||
async function getKnownPrincipals(accountId: string): Promise<Principal[]> {
|
||||
try {
|
||||
const res = await jmapClient.request(
|
||||
[['Principal/get', { accountId, ids: null, properties: PRINCIPAL_PROPERTIES }, '0']],
|
||||
principalsUsing(),
|
||||
);
|
||||
const [method, result] = res.methodResponses?.[0] ?? [];
|
||||
if (method !== 'Principal/get') return [];
|
||||
return (result.list ?? []) as Principal[];
|
||||
} catch (err) {
|
||||
console.warn('[files] reading known principals failed', err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** The principals of {@link listPrincipals}, without the status. */
|
||||
export async function getPrincipals(): Promise<Principal[]> {
|
||||
return (await listPrincipals()).principals;
|
||||
}
|
||||
|
||||
// The principal id that represents the logged-in user (excluded from the
|
||||
|
||||
@@ -12,8 +12,9 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { Trash2, UserPlus, Users } from 'lucide-react-native';
|
||||
import { getPrincipals, ownPrincipalId } from '../../api/files';
|
||||
import { listPrincipals, ownPrincipalId, type PrincipalListing } from '../../api/files';
|
||||
import type { Calendar, CalendarRights, Principal } from '../../api/types';
|
||||
import { directoryNotice } from '../../lib/principal-directory';
|
||||
import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens';
|
||||
import { useColors } from '../../theme/colors';
|
||||
import { useLocaleStore } from '../../stores/locale-store';
|
||||
@@ -74,20 +75,27 @@ export function CalendarShareSheet({ calendar, onShare, onClose }: CalendarShare
|
||||
const t = useLocaleStore((s) => s.t);
|
||||
|
||||
const [principals, setPrincipals] = React.useState<Principal[]>([]);
|
||||
const [directory, setDirectory] = React.useState<Omit<PrincipalListing, 'principals'>>({ status: 'ok' });
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [shares, setShares] = React.useState<Record<string, CalendarRights>>({});
|
||||
const [search, setSearch] = React.useState('');
|
||||
const [savingId, setSavingId] = React.useState<string | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!calendar) return;
|
||||
if (!calendar) return undefined;
|
||||
let cancelled = false;
|
||||
setShares(calendar.shareWith ?? {});
|
||||
setSearch('');
|
||||
setLoading(true);
|
||||
getPrincipals()
|
||||
.then(setPrincipals)
|
||||
.catch(() => setPrincipals([]))
|
||||
.finally(() => setLoading(false));
|
||||
void listPrincipals().then(({ principals: list, ...listing }) => {
|
||||
if (cancelled) return;
|
||||
setPrincipals(list);
|
||||
setDirectory(listing);
|
||||
setLoading(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [calendar]);
|
||||
|
||||
const principalsById = React.useMemo(() => {
|
||||
@@ -137,15 +145,32 @@ export function CalendarShareSheet({ calendar, onShare, onClose }: CalendarShare
|
||||
|
||||
if (!calendar) return null;
|
||||
|
||||
// The list below is partial or empty for a reason the user can't see
|
||||
// otherwise; "Sharing is not available on this server." would be wrong.
|
||||
const notice = loading ? null : directoryNotice(directory);
|
||||
const noticeText =
|
||||
notice === 'failed' ? t('sharing.directory_failed', "Couldn't load the list of users and groups.")
|
||||
: notice === 'restricted'
|
||||
? t(
|
||||
'sharing.directory_restricted',
|
||||
"Your server doesn't allow browsing its user directory, so only your groups and people who have shared with you are listed. An administrator can turn on directory queries in Stalwart's sharing settings.",
|
||||
)
|
||||
: notice === 'not_browsable'
|
||||
? t('sharing.directory_not_browsable', "Your server doesn't let you browse its user directory.")
|
||||
: null;
|
||||
|
||||
const renderPrincipalLabel = (principalId: string) => {
|
||||
const p = principalsById.get(principalId);
|
||||
// Stalwart 1.0 with directory queries off hides every principal the user
|
||||
// has no relationship with, so a sharee may have no name.
|
||||
const unresolved = !p && !loading;
|
||||
return (
|
||||
<View style={styles.principalInfo}>
|
||||
<Text style={styles.principalName} numberOfLines={1}>
|
||||
{p?.description || p?.name || principalId}
|
||||
{p?.description || p?.name || (unresolved ? t('sharing.unknown_user', 'Unknown user') : principalId)}
|
||||
</Text>
|
||||
{p?.email ? (
|
||||
<Text style={styles.principalEmail} numberOfLines={1}>{p.email}</Text>
|
||||
{p?.email || unresolved ? (
|
||||
<Text style={styles.principalEmail} numberOfLines={1}>{p?.email || principalId}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
@@ -224,14 +249,19 @@ export function CalendarShareSheet({ calendar, onShare, onClose }: CalendarShare
|
||||
autoCorrect={false}
|
||||
style={styles.input}
|
||||
/>
|
||||
{noticeText ? (
|
||||
<Text style={styles.empty} accessibilityLiveRegion="polite">{noticeText}</Text>
|
||||
) : null}
|
||||
{loading ? (
|
||||
<ActivityIndicator size="small" color={c.textMuted} style={{ marginTop: spacing.md }} />
|
||||
) : candidates.length === 0 ? (
|
||||
<Text style={styles.empty}>
|
||||
{principals.length === 0
|
||||
? t('calendar.share.no_principals', 'Sharing is not available on this server.')
|
||||
: t('calendar.share.no_matches', 'No matches')}
|
||||
</Text>
|
||||
notice && !search.trim() ? null : (
|
||||
<Text style={styles.empty}>
|
||||
{principals.length === 0 && !notice
|
||||
? t('calendar.share.no_principals', 'Sharing is not available on this server.')
|
||||
: t('calendar.share.no_matches', 'No matches')}
|
||||
</Text>
|
||||
)
|
||||
) : (
|
||||
candidates.map((p) => (
|
||||
<Pressable
|
||||
|
||||
@@ -13,8 +13,9 @@ import {
|
||||
} from 'react-native';
|
||||
import { Folder, FileText, Trash2, UserPlus, Users } from 'lucide-react-native';
|
||||
|
||||
import { getPrincipals, isFolder, ownPrincipalId, setFileNodeShare } from '../../api/files';
|
||||
import { isFolder, listPrincipals, ownPrincipalId, setFileNodeShare, type PrincipalListing } from '../../api/files';
|
||||
import type { FileNode, FileNodeRights, Principal } from '../../api/types';
|
||||
import { directoryNotice } from '../../lib/principal-directory';
|
||||
import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens';
|
||||
import { useColors } from '../../theme/colors';
|
||||
import { useLocaleStore } from '../../stores/locale-store';
|
||||
@@ -70,20 +71,27 @@ export default function ShareSheet({ node, onClose, onChanged }: ShareSheetProps
|
||||
const t = useLocaleStore((s) => s.t);
|
||||
|
||||
const [principals, setPrincipals] = useState<Principal[]>([]);
|
||||
const [directory, setDirectory] = useState<Omit<PrincipalListing, 'principals'>>({ status: 'ok' });
|
||||
const [loadingPrincipals, setLoadingPrincipals] = useState(false);
|
||||
const [shares, setShares] = useState<Record<string, FileNodeRights>>({});
|
||||
const [search, setSearch] = useState('');
|
||||
const [savingId, setSavingId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!node) return;
|
||||
if (!node) return undefined;
|
||||
let cancelled = false;
|
||||
setShares(node.shareWith ?? {});
|
||||
setSearch('');
|
||||
setLoadingPrincipals(true);
|
||||
getPrincipals()
|
||||
.then(setPrincipals)
|
||||
.catch(() => setPrincipals([]))
|
||||
.finally(() => setLoadingPrincipals(false));
|
||||
void listPrincipals().then(({ principals: list, ...listing }) => {
|
||||
if (cancelled) return;
|
||||
setPrincipals(list);
|
||||
setDirectory(listing);
|
||||
setLoadingPrincipals(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [node]);
|
||||
|
||||
const principalsById = useMemo(() => {
|
||||
@@ -135,16 +143,33 @@ export default function ShareSheet({ node, onClose, onChanged }: ShareSheetProps
|
||||
if (!node) return null;
|
||||
const Icon = isFolder(node) ? Folder : FileText;
|
||||
|
||||
// The list below is partial or empty for a reason the user can't see
|
||||
// otherwise; say so instead of "No other users found".
|
||||
const notice = loadingPrincipals ? null : directoryNotice(directory);
|
||||
const noticeText =
|
||||
notice === 'failed' ? t('sharing.directory_failed', "Couldn't load the list of users and groups.")
|
||||
: notice === 'restricted'
|
||||
? t(
|
||||
'sharing.directory_restricted',
|
||||
"Your server doesn't allow browsing its user directory, so only your groups and people who have shared with you are listed. An administrator can turn on directory queries in Stalwart's sharing settings.",
|
||||
)
|
||||
: notice === 'not_browsable'
|
||||
? t('sharing.directory_not_browsable', "Your server doesn't let you browse its user directory.")
|
||||
: null;
|
||||
|
||||
const renderPrincipalLabel = (principalId: string) => {
|
||||
const p = principalsById.get(principalId);
|
||||
// Stalwart 1.0 with directory queries off hides every principal the user
|
||||
// has no relationship with, so a sharee may have no name.
|
||||
const unresolved = !p && !loadingPrincipals;
|
||||
return (
|
||||
<View style={styles.principalInfo}>
|
||||
<Text style={styles.principalName} numberOfLines={1}>
|
||||
{p?.description || p?.name || principalId}
|
||||
{p?.description || p?.name || (unresolved ? t('sharing.unknown_user', 'Unknown user') : principalId)}
|
||||
</Text>
|
||||
{p?.email ? (
|
||||
{p?.email || unresolved ? (
|
||||
<Text style={styles.principalEmail} numberOfLines={1}>
|
||||
{p.email}
|
||||
{p?.email || principalId}
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
@@ -236,6 +261,9 @@ export default function ShareSheet({ node, onClose, onChanged }: ShareSheetProps
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
/>
|
||||
{noticeText ? (
|
||||
<Text style={styles.noMatches} accessibilityLiveRegion="polite">{noticeText}</Text>
|
||||
) : null}
|
||||
{loadingPrincipals ? (
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
@@ -243,11 +271,13 @@ export default function ShareSheet({ node, onClose, onChanged }: ShareSheetProps
|
||||
style={{ marginTop: spacing.sm }}
|
||||
/>
|
||||
) : candidates.length === 0 ? (
|
||||
<Text style={styles.noMatches}>
|
||||
{principals.length === 0
|
||||
? t('files.share_no_users', 'No other users found')
|
||||
: t('files.share_no_matches', 'No matches')}
|
||||
</Text>
|
||||
notice && !search.trim() ? null : (
|
||||
<Text style={styles.noMatches}>
|
||||
{principals.length === 0 && !notice
|
||||
? t('files.share_no_users', 'No other users found')
|
||||
: t('files.share_no_matches', 'No matches')}
|
||||
</Text>
|
||||
)
|
||||
) : (
|
||||
candidates.map((p) => (
|
||||
<Pressable
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { PrincipalListing } from '../api/files';
|
||||
|
||||
/** What a share sheet says under its search box about the people it lists. */
|
||||
export type DirectoryNotice = 'failed' | 'restricted' | 'not_browsable';
|
||||
|
||||
/**
|
||||
* The notice for a principal listing that isn't the whole directory, or null
|
||||
* when it is. Only Stalwart 1.0's own refusal says who is still listed and
|
||||
* what an administrator can do about it; any other refusal (a 0.16 role
|
||||
* without the permission) gets the generic wording.
|
||||
*/
|
||||
export function directoryNotice(
|
||||
listing: Pick<PrincipalListing, 'status' | 'directoryQueriesOff'>,
|
||||
): DirectoryNotice | null {
|
||||
if (listing.status === 'ok') return null;
|
||||
if (listing.status === 'error') return 'failed';
|
||||
return listing.directoryQueriesOff ? 'restricted' : 'not_browsable';
|
||||
}
|
||||
Reference in New Issue
Block a user