diff options
Diffstat (limited to 'apps')
46 files changed, 565 insertions, 210 deletions
diff --git a/apps/accessibility/l10n/fr.js b/apps/accessibility/l10n/fr.js index c5cec2c28d3..897430a5b3c 100644 --- a/apps/accessibility/l10n/fr.js +++ b/apps/accessibility/l10n/fr.js @@ -3,7 +3,7 @@ OC.L10N.register( { "Dark theme" : "Thème sombre", "Enable dark theme" : "Activer le thème sombre", - "A dark theme to ease your eyes by reducing the overall luminosity and brightness. It is still under development, so please report any issues you may find." : "Un thème sombre pour soulager vos yeux en réduisant la luminosité et l’éclat général. Il est encore en cours de développement, veuillez donc signaler tout problème que vous pourriez rencontrer.", + "A dark theme to ease your eyes by reducing the overall luminosity and brightness. It is still under development, so please report any issues you may find." : "Un thème sombre pour soulager vos yeux en réduisant la luminosité et l’éclat général. Il est encore en cours de développement, veuillez donc nous signaler les problèmes que vous pourriez rencontrer.", "High contrast mode" : "Thème à contraste élevé", "Enable high contrast mode" : "Activer le thème à contraste élevé", "A high contrast mode to ease your navigation. Visual quality will be reduced but clarity will be increased." : "Un thème au contraste élevé pour faciliter votre navigation. La qualité visuelle sera réduite, mais la clarté sera améliorée.", @@ -13,14 +13,14 @@ OC.L10N.register( "Accessibility" : "Accessibilité", "Accessibility options for nextcloud" : "Options d'accessibilité pour Nextcloud", "Provides multiple accessibilities options to ease your use of Nextcloud" : "Offre de multiples options d'accessibilité pour faciliter votre utilisation de Nextcloud", - "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines}Web Content Accessibility Guidelines{linkend} 2.1 on AA level, with the high contrast theme even on AAA level." : "L'accès universel est très important pour nous. Nous suivons les standards du web et vérifions que tout soit utilisable également sans souris et avec des logiciels d'assistance technique tels que les lecteurs d'écran. Nous visons à respecter les {guidelines}Règles pour l'accessibilité des contenus Web{linkend} 2.1 de niveau AA et même de niveau AAA avec le thème à fort contraste.", - "If you find any issues, don’t hesitate to report them on {issuetracker}our issue tracker{linkend}. And if you want to get involved, come join {designteam}our design team{linkend}!" : "Si vous rencontrez un problème, n'hésitez pas à nous le signaler sur {issuetracker}notre traqueur de problème{linkend}. Et si vous souhaitez vous impliquer, rejoignez {designteam}notre équipe de conception{linkend} !", + "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines}Web Content Accessibility Guidelines{linkend} 2.1 on AA level, with the high contrast theme even on AAA level." : "L'accès universel est très important pour nous. Nous suivons les standards du web et nous assurons que tout soit également utilisable sans souris et avec des logiciels d'assistance technique tels que les lecteurs d'écran. Nous visons à respecter les {guidelines}Règles pour l'accessibilité des contenus Web{linkend} 2.1 de niveau AA et même de niveau AAA avec le thème à fort contraste.", + "If you find any issues, don’t hesitate to report them on {issuetracker}our issue tracker{linkend}. And if you want to get involved, come join {designteam}our design team{linkend}!" : "Si vous rencontrez un problème, n'hésitez pas à nous le signaler sur {issuetracker}notre traqueur de problèmes{linkend}. Et si vous souhaitez vous impliquer, rejoignez {designteam}notre équipe de conception{linkend} !", "High contrast theme" : "Thème à contraste élevé", - "A high contrast theme to ease your navigation. Visual quality will be reduced but clarity will be increased." : "Un thème au contraste élevé pour faciliter votre navigation. La qualité visuelle sera réduite, mais la clarté sera améliorée.", + "A high contrast theme to ease your navigation. Visual quality will be reduced but clarity will be increased." : "Un thème au contraste élevé pour faciliter votre navigation. La qualité visuelle en sera réduite, mais la clarté améliorée.", "Web Content Accessibility Guidelines" : "Règles pour l'accessibilité des contenus Web", "our issue tracker" : "notre outil de suivi des problèmes", "our design team" : "notre équipe de conception", - "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines} 2.1 on AA level, with the high contrast theme even on AAA level." : "L'accès universel est très important pour nous. Nous suivons les standards du web et vérifions de tout rendre utilisable également sans souris, et avec des logiciels d'assistance technique tels que les lecteurs d'écran. Nous visons à respecter les {guidelines} 2.1 de niveau AA, et même de niveau AAA avec le thème à fort contraste.", + "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines} 2.1 on AA level, with the high contrast theme even on AAA level." : "L'accès universel est très important pour nous. Nous suivons les standards du web et nous assurons que tout soit également utilisable sans souris et avec des logiciels d'assistance technique tels que les lecteurs d'écran. Nous visons à respecter les {guidelines} 2.1 de niveau AA, et même de niveau AAA avec le thème à fort contraste.", "If you find any issues, don’t hesitate to report them on {issuetracker}. And if you want to get involved, come join {designteam}!" : "Si vous rencontrez un problème, n'hésitez pas à nous le signaler sur {issuetracker}. Et si vous souhaitez vous impliquer, rejoignez {designteam} !", "Enable" : "Activer" }, diff --git a/apps/accessibility/l10n/fr.json b/apps/accessibility/l10n/fr.json index a998ba958fd..6c47ea61519 100644 --- a/apps/accessibility/l10n/fr.json +++ b/apps/accessibility/l10n/fr.json @@ -1,7 +1,7 @@ { "translations": { "Dark theme" : "Thème sombre", "Enable dark theme" : "Activer le thème sombre", - "A dark theme to ease your eyes by reducing the overall luminosity and brightness. It is still under development, so please report any issues you may find." : "Un thème sombre pour soulager vos yeux en réduisant la luminosité et l’éclat général. Il est encore en cours de développement, veuillez donc signaler tout problème que vous pourriez rencontrer.", + "A dark theme to ease your eyes by reducing the overall luminosity and brightness. It is still under development, so please report any issues you may find." : "Un thème sombre pour soulager vos yeux en réduisant la luminosité et l’éclat général. Il est encore en cours de développement, veuillez donc nous signaler les problèmes que vous pourriez rencontrer.", "High contrast mode" : "Thème à contraste élevé", "Enable high contrast mode" : "Activer le thème à contraste élevé", "A high contrast mode to ease your navigation. Visual quality will be reduced but clarity will be increased." : "Un thème au contraste élevé pour faciliter votre navigation. La qualité visuelle sera réduite, mais la clarté sera améliorée.", @@ -11,14 +11,14 @@ "Accessibility" : "Accessibilité", "Accessibility options for nextcloud" : "Options d'accessibilité pour Nextcloud", "Provides multiple accessibilities options to ease your use of Nextcloud" : "Offre de multiples options d'accessibilité pour faciliter votre utilisation de Nextcloud", - "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines}Web Content Accessibility Guidelines{linkend} 2.1 on AA level, with the high contrast theme even on AAA level." : "L'accès universel est très important pour nous. Nous suivons les standards du web et vérifions que tout soit utilisable également sans souris et avec des logiciels d'assistance technique tels que les lecteurs d'écran. Nous visons à respecter les {guidelines}Règles pour l'accessibilité des contenus Web{linkend} 2.1 de niveau AA et même de niveau AAA avec le thème à fort contraste.", - "If you find any issues, don’t hesitate to report them on {issuetracker}our issue tracker{linkend}. And if you want to get involved, come join {designteam}our design team{linkend}!" : "Si vous rencontrez un problème, n'hésitez pas à nous le signaler sur {issuetracker}notre traqueur de problème{linkend}. Et si vous souhaitez vous impliquer, rejoignez {designteam}notre équipe de conception{linkend} !", + "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines}Web Content Accessibility Guidelines{linkend} 2.1 on AA level, with the high contrast theme even on AAA level." : "L'accès universel est très important pour nous. Nous suivons les standards du web et nous assurons que tout soit également utilisable sans souris et avec des logiciels d'assistance technique tels que les lecteurs d'écran. Nous visons à respecter les {guidelines}Règles pour l'accessibilité des contenus Web{linkend} 2.1 de niveau AA et même de niveau AAA avec le thème à fort contraste.", + "If you find any issues, don’t hesitate to report them on {issuetracker}our issue tracker{linkend}. And if you want to get involved, come join {designteam}our design team{linkend}!" : "Si vous rencontrez un problème, n'hésitez pas à nous le signaler sur {issuetracker}notre traqueur de problèmes{linkend}. Et si vous souhaitez vous impliquer, rejoignez {designteam}notre équipe de conception{linkend} !", "High contrast theme" : "Thème à contraste élevé", - "A high contrast theme to ease your navigation. Visual quality will be reduced but clarity will be increased." : "Un thème au contraste élevé pour faciliter votre navigation. La qualité visuelle sera réduite, mais la clarté sera améliorée.", + "A high contrast theme to ease your navigation. Visual quality will be reduced but clarity will be increased." : "Un thème au contraste élevé pour faciliter votre navigation. La qualité visuelle en sera réduite, mais la clarté améliorée.", "Web Content Accessibility Guidelines" : "Règles pour l'accessibilité des contenus Web", "our issue tracker" : "notre outil de suivi des problèmes", "our design team" : "notre équipe de conception", - "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines} 2.1 on AA level, with the high contrast theme even on AAA level." : "L'accès universel est très important pour nous. Nous suivons les standards du web et vérifions de tout rendre utilisable également sans souris, et avec des logiciels d'assistance technique tels que les lecteurs d'écran. Nous visons à respecter les {guidelines} 2.1 de niveau AA, et même de niveau AAA avec le thème à fort contraste.", + "Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines} 2.1 on AA level, with the high contrast theme even on AAA level." : "L'accès universel est très important pour nous. Nous suivons les standards du web et nous assurons que tout soit également utilisable sans souris et avec des logiciels d'assistance technique tels que les lecteurs d'écran. Nous visons à respecter les {guidelines} 2.1 de niveau AA, et même de niveau AAA avec le thème à fort contraste.", "If you find any issues, don’t hesitate to report them on {issuetracker}. And if you want to get involved, come join {designteam}!" : "Si vous rencontrez un problème, n'hésitez pas à nous le signaler sur {issuetracker}. Et si vous souhaitez vous impliquer, rejoignez {designteam} !", "Enable" : "Activer" },"pluralForm" :"nplurals=2; plural=(n > 1);" diff --git a/apps/dav/l10n/fr.js b/apps/dav/l10n/fr.js index e811182be9c..609fa1c5409 100644 --- a/apps/dav/l10n/fr.js +++ b/apps/dav/l10n/fr.js @@ -84,6 +84,8 @@ OC.L10N.register( "Tasks" : "Tâches", "Untitled task" : "Tâche sans titre", "Completed on %s" : "Terminé le %s", + "Due on %s by %s" : "Echéance le %s pour %s", + "Due on %s" : "Echéance le %s", "WebDAV" : "WebDAV", "WebDAV endpoint" : "Point d'accès WebDAV", "There was an error updating your attendance status." : "Une erreur s'est produite lors de la mise à jour de votre statut de présence.", diff --git a/apps/dav/l10n/fr.json b/apps/dav/l10n/fr.json index 37553234e27..782678c9cfe 100644 --- a/apps/dav/l10n/fr.json +++ b/apps/dav/l10n/fr.json @@ -82,6 +82,8 @@ "Tasks" : "Tâches", "Untitled task" : "Tâche sans titre", "Completed on %s" : "Terminé le %s", + "Due on %s by %s" : "Echéance le %s pour %s", + "Due on %s" : "Echéance le %s", "WebDAV" : "WebDAV", "WebDAV endpoint" : "Point d'accès WebDAV", "There was an error updating your attendance status." : "Une erreur s'est produite lors de la mise à jour de votre statut de présence.", diff --git a/apps/dav/l10n/he.js b/apps/dav/l10n/he.js index a1dc18fa696..98b7367600a 100644 --- a/apps/dav/l10n/he.js +++ b/apps/dav/l10n/he.js @@ -81,6 +81,7 @@ OC.L10N.register( "Configures a CalDAV account" : "מגדיר חשבון CalDAV", "Configures a CardDAV account" : "מגדיר חשבון CardDAV", "Tasks" : "משימות", + "Untitled task" : "משימה ללא כותרת", "WebDAV" : "WebDAV", "WebDAV endpoint" : "נקודת קצה WebDAV", "There was an error updating your attendance status." : "אירעה שגיאה בעת עדכון מצב ההשתתפות שלך.", diff --git a/apps/dav/l10n/he.json b/apps/dav/l10n/he.json index 90975f35aa3..c33edb4216a 100644 --- a/apps/dav/l10n/he.json +++ b/apps/dav/l10n/he.json @@ -79,6 +79,7 @@ "Configures a CalDAV account" : "מגדיר חשבון CalDAV", "Configures a CardDAV account" : "מגדיר חשבון CardDAV", "Tasks" : "משימות", + "Untitled task" : "משימה ללא כותרת", "WebDAV" : "WebDAV", "WebDAV endpoint" : "נקודת קצה WebDAV", "There was an error updating your attendance status." : "אירעה שגיאה בעת עדכון מצב ההשתתפות שלך.", diff --git a/apps/dav/l10n/tr.js b/apps/dav/l10n/tr.js index 865077bd54b..1f60de92845 100644 --- a/apps/dav/l10n/tr.js +++ b/apps/dav/l10n/tr.js @@ -80,8 +80,12 @@ OC.L10N.register( "Your %s needs to be configured to use HTTPS in order to use CalDAV and CardDAV with iOS/macOS." : "iOS/macOS üzerinde CalDAV ve CardDAV kullanabilmek için %s HTTPS kullanacak şekilde yapılandırılmalıdır.", "Configures a CalDAV account" : "Bir CalDAV hesabı yapılandırır", "Configures a CardDAV account" : "Bir CardDAV hesabı yapılandırır", + "Events" : "Etkinlikler", "Tasks" : "Görevler", "Untitled task" : "Başlıksız görev", + "Completed on %s" : "%s tarihinde tamamlandı", + "Due on %s by %s" : "%s tarihine kadar %s tarafından", + "Due on %s" : "%s tarihine kadar", "WebDAV" : "WebDAV", "WebDAV endpoint" : "WebDAV Bağlantı Noktası", "There was an error updating your attendance status." : "Katılım durumunuz güncellenirken bir sorun çıktı.", diff --git a/apps/dav/l10n/tr.json b/apps/dav/l10n/tr.json index 08bffc13b85..cde3db797f7 100644 --- a/apps/dav/l10n/tr.json +++ b/apps/dav/l10n/tr.json @@ -78,8 +78,12 @@ "Your %s needs to be configured to use HTTPS in order to use CalDAV and CardDAV with iOS/macOS." : "iOS/macOS üzerinde CalDAV ve CardDAV kullanabilmek için %s HTTPS kullanacak şekilde yapılandırılmalıdır.", "Configures a CalDAV account" : "Bir CalDAV hesabı yapılandırır", "Configures a CardDAV account" : "Bir CardDAV hesabı yapılandırır", + "Events" : "Etkinlikler", "Tasks" : "Görevler", "Untitled task" : "Başlıksız görev", + "Completed on %s" : "%s tarihinde tamamlandı", + "Due on %s by %s" : "%s tarihine kadar %s tarafından", + "Due on %s" : "%s tarihine kadar", "WebDAV" : "WebDAV", "WebDAV endpoint" : "WebDAV Bağlantı Noktası", "There was an error updating your attendance status." : "Katılım durumunuz güncellenirken bir sorun çıktı.", diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php index ecb91fee9c9..2f5bb719166 100644 --- a/apps/files/composer/composer/autoload_classmap.php +++ b/apps/files/composer/composer/autoload_classmap.php @@ -12,6 +12,7 @@ return array( 'OCA\\Files\\Activity\\Helper' => $baseDir . '/../lib/Activity/Helper.php', 'OCA\\Files\\Activity\\Provider' => $baseDir . '/../lib/Activity/Provider.php', 'OCA\\Files\\Activity\\Settings\\FavoriteAction' => $baseDir . '/../lib/Activity/Settings/FavoriteAction.php', + 'OCA\\Files\\Activity\\Settings\\FileActivitySettings' => $baseDir . '/../lib/Activity/Settings/FileActivitySettings.php', 'OCA\\Files\\Activity\\Settings\\FileChanged' => $baseDir . '/../lib/Activity/Settings/FileChanged.php', 'OCA\\Files\\Activity\\Settings\\FileCreated' => $baseDir . '/../lib/Activity/Settings/FileCreated.php', 'OCA\\Files\\Activity\\Settings\\FileDeleted' => $baseDir . '/../lib/Activity/Settings/FileDeleted.php', diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php index a8c80a4201a..fe9809f9e29 100644 --- a/apps/files/composer/composer/autoload_static.php +++ b/apps/files/composer/composer/autoload_static.php @@ -27,6 +27,7 @@ class ComposerStaticInitFiles 'OCA\\Files\\Activity\\Helper' => __DIR__ . '/..' . '/../lib/Activity/Helper.php', 'OCA\\Files\\Activity\\Provider' => __DIR__ . '/..' . '/../lib/Activity/Provider.php', 'OCA\\Files\\Activity\\Settings\\FavoriteAction' => __DIR__ . '/..' . '/../lib/Activity/Settings/FavoriteAction.php', + 'OCA\\Files\\Activity\\Settings\\FileActivitySettings' => __DIR__ . '/..' . '/../lib/Activity/Settings/FileActivitySettings.php', 'OCA\\Files\\Activity\\Settings\\FileChanged' => __DIR__ . '/..' . '/../lib/Activity/Settings/FileChanged.php', 'OCA\\Files\\Activity\\Settings\\FileCreated' => __DIR__ . '/..' . '/../lib/Activity/Settings/FileCreated.php', 'OCA\\Files\\Activity\\Settings\\FileDeleted' => __DIR__ . '/..' . '/../lib/Activity/Settings/FileDeleted.php', diff --git a/apps/files/l10n/fr.js b/apps/files/l10n/fr.js index 1ff8aa11366..490e8bc68dc 100644 --- a/apps/files/l10n/fr.js +++ b/apps/files/l10n/fr.js @@ -8,12 +8,12 @@ OC.L10N.register( "Move or copy" : "Déplacer ou copier", "Download" : "Télécharger", "Delete" : "Supprimer", - "Home" : "Mes fichiers", + "Home" : "Accueil", "Close" : "Fermer", "Favorites" : "Favoris", "Could not create folder \"{dir}\"" : "Impossible de créer le dossier \"{dir}\"", "This will stop your current uploads." : "Cela va arrêter vos envois en cours.", - "Upload cancelled." : "Envoi annulé.", + "Upload cancelled." : "Téléversement annulé.", "Processing files …" : "Fichiers en cours de traitement …", "…" : "…", "Unable to upload {filename} as it is a directory or has 0 bytes" : "Impossible d'envoyer {filename} car il s'agit d'un dossier ou d'un fichier vide", @@ -42,7 +42,7 @@ OC.L10N.register( "Pending" : "En attente", "Unable to determine date" : "Impossible de déterminer la date", "This operation is forbidden" : "Cette opération est interdite", - "This directory is unavailable, please check the logs or contact the administrator" : "Ce dossier n'est pas disponible, consultez les logs ou contactez votre administrateur", + "This directory is unavailable, please check the logs or contact the administrator" : "Ce répertoire est indisponnible, merci de consulter les journaux ou de contacter votre administrateur", "Could not move \"{file}\", target exists" : "Impossible de déplacer \"{file}\", la cible existe", "Could not move \"{file}\"" : "Impossible de déplacer \"{file}\"", "copy" : "copie", @@ -51,7 +51,7 @@ OC.L10N.register( "Copied {origin} inside {destination}" : "{origin} copié dans {destination}", "Copied {origin} and {nbfiles} other files inside {destination}" : "{origin} et {nbfiles} autres fichiers copiés dans {destination}", "{newName} already exists" : "{newName} existe déjà", - "Could not rename \"{fileName}\", it does not exist any more" : "Impossible de renommer \"{fileName}\", il n'existe plus", + "Could not rename \"{fileName}\", it does not exist any more" : "Impossible de renommer \"{fileName}\" car il n'existe plus", "The name \"{targetName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Le nom \"{targetName}\" est déjà utilisé dans le dossier \"{dir}\". Merci de choisir un nom différent.", "Could not rename \"{fileName}\"" : "Impossible de renommer \"{fileName}\"", "Could not create file \"{file}\"" : "Impossible de créer le fichier \"{file}\"", diff --git a/apps/files/l10n/fr.json b/apps/files/l10n/fr.json index 071934bb0df..f2ea3eb98e9 100644 --- a/apps/files/l10n/fr.json +++ b/apps/files/l10n/fr.json @@ -6,12 +6,12 @@ "Move or copy" : "Déplacer ou copier", "Download" : "Télécharger", "Delete" : "Supprimer", - "Home" : "Mes fichiers", + "Home" : "Accueil", "Close" : "Fermer", "Favorites" : "Favoris", "Could not create folder \"{dir}\"" : "Impossible de créer le dossier \"{dir}\"", "This will stop your current uploads." : "Cela va arrêter vos envois en cours.", - "Upload cancelled." : "Envoi annulé.", + "Upload cancelled." : "Téléversement annulé.", "Processing files …" : "Fichiers en cours de traitement …", "…" : "…", "Unable to upload {filename} as it is a directory or has 0 bytes" : "Impossible d'envoyer {filename} car il s'agit d'un dossier ou d'un fichier vide", @@ -40,7 +40,7 @@ "Pending" : "En attente", "Unable to determine date" : "Impossible de déterminer la date", "This operation is forbidden" : "Cette opération est interdite", - "This directory is unavailable, please check the logs or contact the administrator" : "Ce dossier n'est pas disponible, consultez les logs ou contactez votre administrateur", + "This directory is unavailable, please check the logs or contact the administrator" : "Ce répertoire est indisponnible, merci de consulter les journaux ou de contacter votre administrateur", "Could not move \"{file}\", target exists" : "Impossible de déplacer \"{file}\", la cible existe", "Could not move \"{file}\"" : "Impossible de déplacer \"{file}\"", "copy" : "copie", @@ -49,7 +49,7 @@ "Copied {origin} inside {destination}" : "{origin} copié dans {destination}", "Copied {origin} and {nbfiles} other files inside {destination}" : "{origin} et {nbfiles} autres fichiers copiés dans {destination}", "{newName} already exists" : "{newName} existe déjà", - "Could not rename \"{fileName}\", it does not exist any more" : "Impossible de renommer \"{fileName}\", il n'existe plus", + "Could not rename \"{fileName}\", it does not exist any more" : "Impossible de renommer \"{fileName}\" car il n'existe plus", "The name \"{targetName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Le nom \"{targetName}\" est déjà utilisé dans le dossier \"{dir}\". Merci de choisir un nom différent.", "Could not rename \"{fileName}\"" : "Impossible de renommer \"{fileName}\"", "Could not create file \"{file}\"" : "Impossible de créer le fichier \"{file}\"", diff --git a/apps/files/l10n/nb.js b/apps/files/l10n/nb.js index 4dbdf17b3a4..2b840ca1bfc 100644 --- a/apps/files/l10n/nb.js +++ b/apps/files/l10n/nb.js @@ -57,8 +57,10 @@ OC.L10N.register( "Could not create file \"{file}\"" : "Klarte ikke å opprette fil \"{file}\"", "Could not create file \"{file}\" because it already exists" : "Klarte ikke å opprette fil \"{file}\" fordi den finnes allerede", "Could not create folder \"{dir}\" because it already exists" : "Klarete ikke å opprette mappe \"{dir}\" fordi den finnes allerede", + "Could not fetch file details \"{file}\"" : "Kunne ikke hente fildetaljene \"{file}\"", "Error deleting file \"{fileName}\"." : "Feil ved sletting av fil \"{fileName}\".", "No search results in other folders for {tag}{filter}{endtag}" : "Ingen søkeresultater i andre mapper etter {tag}{filter}{endtag}", + "Enter more than two characters to search in other folders" : "Tast inn mer enn to tegn for å lete i andre mapper", "Name" : "Navn", "Size" : "Størrelse", "Modified" : "Endret", @@ -69,6 +71,7 @@ OC.L10N.register( "You don’t have permission to upload or create files here" : "Du har ikke tillatelse til å laste opp eller opprette filer her", "_Uploading %n file_::_Uploading %n files_" : ["Laster opp %n fil","Laster opp %n filer"], "New" : "Ny", + "Select file range" : "Velg filutvalg", "{used} of {quota} used" : "{used} av {quota} brukt", "{used} used" : "{used} brukt", "\"{name}\" is an invalid file name." : "\"{name}\" er et uglydig filnavn.", @@ -87,6 +90,7 @@ OC.L10N.register( "_%n byte_::_%n bytes_" : ["%n byte","%n byte"], "Favorited" : "Favorisert", "Favorite" : "Gjør til favoritt", + "You can only favorite a single file or folder at a time" : "Du kan bare legge til en fil av gangen til favoritter", "New folder" : "Ny mappe", "Upload file" : "Last opp fil", "Recent" : "Nylig", @@ -135,15 +139,38 @@ OC.L10N.register( "Unlimited" : "Ubegrenset", "Upload (max. %s)" : "Opplasting (maks %s)", "Accept" : "Aksepter", + "Reject" : "Avvis", + "Incoming ownership transfer from {user}" : "Ny eierskapsoverføring fra {user}", + "Do you want to accept {path}?\n\nNote: The transfer process after accepting may take up to 1 hour." : "Ønsker du å godta {path}?\n\nObs: Overføringsprosessen kan ta opp til 1 time.", + "Ownership transfer failed" : "Overføring av eierskap mislyktes", + "Your ownership transfer of {path} to {user} failed." : "Eierskapsoverføring din av {path} til {user} mislyktes.", + "The ownership transfer of {path} from {user} failed." : "Eierskapsoverføring av {path} fra {user} mislyktes.", + "Ownership transfer done" : "Overføring av eierskap ferdig", + "Your ownership transfer of {path} to {user} has completed." : "Eierskapsoverføringen din av {path} til {user} er ferdig.", + "The ownership transfer of {path} from {user} has completed." : "Eierskapsoverføring av {path} fra {user} er ferdig.", "in %s" : "om %s", "File Management" : "Filbehandling", + "Transfer ownership of a file or folder" : "Eierskapsoverføring av en fil eller mappe", + "Choose file or folder to transfer" : "Velg en fil eller mappe som skal overføres", "Change" : "Endre", + "New owner" : "Ny eier", + "Search users" : "Søk etter brukere", + "Choose a file or folder to transfer" : "Velg en fil eller mappe som skal overføres", + "Transfer" : "Overfør", + "Transfer {path} to {userid}" : "Overfør {path} til {userid}", + "Invalid path selected" : "Ugyldig angitt sti", + "Ownership transfer request sent" : "Forespørsel om overføring av eierskap er sendt", + "Cannot transfer ownership of a file or folder you don't own" : "Kan ikke overføre eierskap til en fil eller mappe du ikke eier", "Tags" : "Merkelapper", + "Error while loading the file data" : "Feil ved lasting av fildata", + "Unable to change the favourite state of the file" : "Kan ikke endre favorittstatus til filen", "%s used" : "%s brukt", + "%s%% of %s used" : "%s%% av %s brukt", "%1$s of %2$s used" : "%1$s av %2$s brukt", "Settings" : "Innstillinger", "Show hidden files" : "Vis skjulte filer", "WebDAV" : "WebDAV", + "Use this address to access your Files via WebDAV" : "Bruk denne adressen for tilgang til filene dine via WebDAV", "Toggle grid view" : "Veksle rutenett-visning", "No files in here" : "Ingen filer", "Upload some content or sync with your devices!" : "Last opp innhold eller synkroniser med enhetene dine!", @@ -159,8 +186,10 @@ OC.L10N.register( "Shared with you" : "Delt med deg", "Shared by link" : "Delt med lenke", "Deleted shares" : "Slettede delinger", + "Pending shares" : "Ventende delinger", "Text file" : "Tekstfil", "New text file.txt" : "Ny tekstfil.txt", - "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Bruk adressen <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">for å få tilgang til dine filer via WebDAV</a>" + "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Bruk adressen <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">for å få tilgang til dine filer via WebDAV</a>", + "Cannot transfter ownership of a file or folder you don't own" : "Kan ikke overføre eierskap til en fil eller mappe du ikke eier" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files/l10n/nb.json b/apps/files/l10n/nb.json index 2bb79489355..565e3dbffed 100644 --- a/apps/files/l10n/nb.json +++ b/apps/files/l10n/nb.json @@ -55,8 +55,10 @@ "Could not create file \"{file}\"" : "Klarte ikke å opprette fil \"{file}\"", "Could not create file \"{file}\" because it already exists" : "Klarte ikke å opprette fil \"{file}\" fordi den finnes allerede", "Could not create folder \"{dir}\" because it already exists" : "Klarete ikke å opprette mappe \"{dir}\" fordi den finnes allerede", + "Could not fetch file details \"{file}\"" : "Kunne ikke hente fildetaljene \"{file}\"", "Error deleting file \"{fileName}\"." : "Feil ved sletting av fil \"{fileName}\".", "No search results in other folders for {tag}{filter}{endtag}" : "Ingen søkeresultater i andre mapper etter {tag}{filter}{endtag}", + "Enter more than two characters to search in other folders" : "Tast inn mer enn to tegn for å lete i andre mapper", "Name" : "Navn", "Size" : "Størrelse", "Modified" : "Endret", @@ -67,6 +69,7 @@ "You don’t have permission to upload or create files here" : "Du har ikke tillatelse til å laste opp eller opprette filer her", "_Uploading %n file_::_Uploading %n files_" : ["Laster opp %n fil","Laster opp %n filer"], "New" : "Ny", + "Select file range" : "Velg filutvalg", "{used} of {quota} used" : "{used} av {quota} brukt", "{used} used" : "{used} brukt", "\"{name}\" is an invalid file name." : "\"{name}\" er et uglydig filnavn.", @@ -85,6 +88,7 @@ "_%n byte_::_%n bytes_" : ["%n byte","%n byte"], "Favorited" : "Favorisert", "Favorite" : "Gjør til favoritt", + "You can only favorite a single file or folder at a time" : "Du kan bare legge til en fil av gangen til favoritter", "New folder" : "Ny mappe", "Upload file" : "Last opp fil", "Recent" : "Nylig", @@ -133,15 +137,38 @@ "Unlimited" : "Ubegrenset", "Upload (max. %s)" : "Opplasting (maks %s)", "Accept" : "Aksepter", + "Reject" : "Avvis", + "Incoming ownership transfer from {user}" : "Ny eierskapsoverføring fra {user}", + "Do you want to accept {path}?\n\nNote: The transfer process after accepting may take up to 1 hour." : "Ønsker du å godta {path}?\n\nObs: Overføringsprosessen kan ta opp til 1 time.", + "Ownership transfer failed" : "Overføring av eierskap mislyktes", + "Your ownership transfer of {path} to {user} failed." : "Eierskapsoverføring din av {path} til {user} mislyktes.", + "The ownership transfer of {path} from {user} failed." : "Eierskapsoverføring av {path} fra {user} mislyktes.", + "Ownership transfer done" : "Overføring av eierskap ferdig", + "Your ownership transfer of {path} to {user} has completed." : "Eierskapsoverføringen din av {path} til {user} er ferdig.", + "The ownership transfer of {path} from {user} has completed." : "Eierskapsoverføring av {path} fra {user} er ferdig.", "in %s" : "om %s", "File Management" : "Filbehandling", + "Transfer ownership of a file or folder" : "Eierskapsoverføring av en fil eller mappe", + "Choose file or folder to transfer" : "Velg en fil eller mappe som skal overføres", "Change" : "Endre", + "New owner" : "Ny eier", + "Search users" : "Søk etter brukere", + "Choose a file or folder to transfer" : "Velg en fil eller mappe som skal overføres", + "Transfer" : "Overfør", + "Transfer {path} to {userid}" : "Overfør {path} til {userid}", + "Invalid path selected" : "Ugyldig angitt sti", + "Ownership transfer request sent" : "Forespørsel om overføring av eierskap er sendt", + "Cannot transfer ownership of a file or folder you don't own" : "Kan ikke overføre eierskap til en fil eller mappe du ikke eier", "Tags" : "Merkelapper", + "Error while loading the file data" : "Feil ved lasting av fildata", + "Unable to change the favourite state of the file" : "Kan ikke endre favorittstatus til filen", "%s used" : "%s brukt", + "%s%% of %s used" : "%s%% av %s brukt", "%1$s of %2$s used" : "%1$s av %2$s brukt", "Settings" : "Innstillinger", "Show hidden files" : "Vis skjulte filer", "WebDAV" : "WebDAV", + "Use this address to access your Files via WebDAV" : "Bruk denne adressen for tilgang til filene dine via WebDAV", "Toggle grid view" : "Veksle rutenett-visning", "No files in here" : "Ingen filer", "Upload some content or sync with your devices!" : "Last opp innhold eller synkroniser med enhetene dine!", @@ -157,8 +184,10 @@ "Shared with you" : "Delt med deg", "Shared by link" : "Delt med lenke", "Deleted shares" : "Slettede delinger", + "Pending shares" : "Ventende delinger", "Text file" : "Tekstfil", "New text file.txt" : "Ny tekstfil.txt", - "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Bruk adressen <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">for å få tilgang til dine filer via WebDAV</a>" + "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Bruk adressen <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">for å få tilgang til dine filer via WebDAV</a>", + "Cannot transfter ownership of a file or folder you don't own" : "Kan ikke overføre eierskap til en fil eller mappe du ikke eier" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files/lib/Activity/Settings/FavoriteAction.php b/apps/files/lib/Activity/Settings/FavoriteAction.php index 9db53eada8e..017669a137c 100644 --- a/apps/files/lib/Activity/Settings/FavoriteAction.php +++ b/apps/files/lib/Activity/Settings/FavoriteAction.php @@ -23,21 +23,7 @@ namespace OCA\Files\Activity\Settings; -use OCP\Activity\ISetting; -use OCP\IL10N; - -class FavoriteAction implements ISetting { - - /** @var IL10N */ - protected $l; - - /** - * @param IL10N $l - */ - public function __construct(IL10N $l) { - $this->l = $l; - } - +class FavoriteAction extends FileActivitySettings { /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 diff --git a/apps/files/lib/Activity/Settings/FileActivitySettings.php b/apps/files/lib/Activity/Settings/FileActivitySettings.php new file mode 100644 index 00000000000..0ec7cb27be5 --- /dev/null +++ b/apps/files/lib/Activity/Settings/FileActivitySettings.php @@ -0,0 +1,48 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files\Activity\Settings; + +use OCP\Activity\ActivitySettings; +use OCP\IL10N; + +abstract class FileActivitySettings extends ActivitySettings { + /** @var IL10N */ + protected $l; + + /** + * @param IL10N $l + */ + public function __construct(IL10N $l) { + $this->l = $l; + } + + public function getGroupIdentifier() { + return 'files'; + } + + public function getGroupName() { + return $this->l->t('Files'); + } +} diff --git a/apps/files/lib/Activity/Settings/FileChanged.php b/apps/files/lib/Activity/Settings/FileChanged.php index 503201bd129..bf3780a575f 100644 --- a/apps/files/lib/Activity/Settings/FileChanged.php +++ b/apps/files/lib/Activity/Settings/FileChanged.php @@ -23,21 +23,7 @@ namespace OCA\Files\Activity\Settings; -use OCP\Activity\ISetting; -use OCP\IL10N; - -class FileChanged implements ISetting { - - /** @var IL10N */ - protected $l; - - /** - * @param IL10N $l - */ - public function __construct(IL10N $l) { - $this->l = $l; - } - +class FileChanged extends FileActivitySettings { /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 diff --git a/apps/files/lib/Activity/Settings/FileCreated.php b/apps/files/lib/Activity/Settings/FileCreated.php index 481c719d024..589f905286a 100644 --- a/apps/files/lib/Activity/Settings/FileCreated.php +++ b/apps/files/lib/Activity/Settings/FileCreated.php @@ -23,21 +23,7 @@ namespace OCA\Files\Activity\Settings; -use OCP\Activity\ISetting; -use OCP\IL10N; - -class FileCreated implements ISetting { - - /** @var IL10N */ - protected $l; - - /** - * @param IL10N $l - */ - public function __construct(IL10N $l) { - $this->l = $l; - } - +class FileCreated extends FileActivitySettings { /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 diff --git a/apps/files/lib/Activity/Settings/FileDeleted.php b/apps/files/lib/Activity/Settings/FileDeleted.php index d4e56b6c717..05754463bc2 100644 --- a/apps/files/lib/Activity/Settings/FileDeleted.php +++ b/apps/files/lib/Activity/Settings/FileDeleted.php @@ -23,21 +23,7 @@ namespace OCA\Files\Activity\Settings; -use OCP\Activity\ISetting; -use OCP\IL10N; - -class FileDeleted implements ISetting { - - /** @var IL10N */ - protected $l; - - /** - * @param IL10N $l - */ - public function __construct(IL10N $l) { - $this->l = $l; - } - +class FileDeleted extends FileActivitySettings { /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 diff --git a/apps/files/lib/Activity/Settings/FileFavorite.php b/apps/files/lib/Activity/Settings/FileFavorite.php index ac62242f703..72ba01e537a 100644 --- a/apps/files/lib/Activity/Settings/FileFavorite.php +++ b/apps/files/lib/Activity/Settings/FileFavorite.php @@ -23,21 +23,7 @@ namespace OCA\Files\Activity\Settings; -use OCP\Activity\ISetting; -use OCP\IL10N; - -class FileFavorite implements ISetting { - - /** @var IL10N */ - protected $l; - - /** - * @param IL10N $l - */ - public function __construct(IL10N $l) { - $this->l = $l; - } - +class FileFavorite extends FileActivitySettings { /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 diff --git a/apps/files/lib/Activity/Settings/FileRestored.php b/apps/files/lib/Activity/Settings/FileRestored.php index 59b722ddf85..f149554bc88 100644 --- a/apps/files/lib/Activity/Settings/FileRestored.php +++ b/apps/files/lib/Activity/Settings/FileRestored.php @@ -23,21 +23,7 @@ namespace OCA\Files\Activity\Settings; -use OCP\Activity\ISetting; -use OCP\IL10N; - -class FileRestored implements ISetting { - - /** @var IL10N */ - protected $l; - - /** - * @param IL10N $l - */ - public function __construct(IL10N $l) { - $this->l = $l; - } - +class FileRestored extends FileActivitySettings { /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php index 73ee589fdbc..1568e0c9f54 100644 --- a/apps/files/lib/Controller/ViewController.php +++ b/apps/files/lib/Controller/ViewController.php @@ -278,9 +278,8 @@ class ViewController extends Controller { } $event = new LoadAdditionalScriptsEvent(); - $this->eventDispatcher->dispatch(LoadAdditionalScriptsEvent::class, $event); - - $this->eventDispatcher->dispatch(LoadSidebar::class, new LoadSidebar()); + $this->eventDispatcher->dispatchTyped($event); + $this->eventDispatcher->dispatchTyped(new LoadSidebar()); // Load Viewer scripts if (class_exists(LoadViewer::class)) { $this->eventDispatcher->dispatchTyped(new LoadViewer()); diff --git a/apps/files/lib/Event/LoadAdditionalScriptsEvent.php b/apps/files/lib/Event/LoadAdditionalScriptsEvent.php index d9c80a23207..458ec916f8f 100644 --- a/apps/files/lib/Event/LoadAdditionalScriptsEvent.php +++ b/apps/files/lib/Event/LoadAdditionalScriptsEvent.php @@ -28,6 +28,11 @@ namespace OCA\Files\Event; use OCP\EventDispatcher\Event; +/** + * This event is triggered when the files app is rendered. It canb e used to add additional scripts to the files app. + * + * @since 17.0.0 + */ class LoadAdditionalScriptsEvent extends Event { private $hiddenFields = []; diff --git a/apps/files/lib/Service/OwnershipTransferService.php b/apps/files/lib/Service/OwnershipTransferService.php index 535822b999c..b04a53919ac 100644 --- a/apps/files/lib/Service/OwnershipTransferService.php +++ b/apps/files/lib/Service/OwnershipTransferService.php @@ -194,7 +194,7 @@ class OwnershipTransferService { $output->writeln('Validating quota'); $size = $view->getFileInfo($sourcePath, false)->getSize(false); $freeSpace = $view->free_space($destinationUid . '/files/'); - if ($size > $freeSpace) { + if ($size > $freeSpace && $freeSpace !== FileInfo::SPACE_UNKNOWN) { $output->writeln('<error>Target user does not have enough free space available.</error>'); throw new \Exception('Execution terminated.'); } diff --git a/apps/files_sharing/l10n/nb.js b/apps/files_sharing/l10n/nb.js index 9d3db514976..a3193b9f050 100644 --- a/apps/files_sharing/l10n/nb.js +++ b/apps/files_sharing/l10n/nb.js @@ -13,6 +13,7 @@ OC.L10N.register( "Deleted shares" : "Slettede delinger", "No deleted shares" : "Ingen slettede delinger", "Shares you deleted will show up here" : "Delinger du har slettet vil vises her", + "Pending shares" : "Ventende delinger", "Shares" : "Delinger", "No shares" : "Ingen delinger", "Shares will show up here" : "Delinger vil vises her", @@ -109,6 +110,7 @@ OC.L10N.register( "Share API is disabled" : "Deling API er deaktivert", "File sharing" : "Fildeling", "Accept" : "Aksepter", + "Reject" : "Avvis", "Sharing" : "Deling", "Allow editing" : "Tillat redigering", "Allow resharing" : "TIllat videre deling", diff --git a/apps/files_sharing/l10n/nb.json b/apps/files_sharing/l10n/nb.json index 9e14185b020..29972f89eae 100644 --- a/apps/files_sharing/l10n/nb.json +++ b/apps/files_sharing/l10n/nb.json @@ -11,6 +11,7 @@ "Deleted shares" : "Slettede delinger", "No deleted shares" : "Ingen slettede delinger", "Shares you deleted will show up here" : "Delinger du har slettet vil vises her", + "Pending shares" : "Ventende delinger", "Shares" : "Delinger", "No shares" : "Ingen delinger", "Shares will show up here" : "Delinger vil vises her", @@ -107,6 +108,7 @@ "Share API is disabled" : "Deling API er deaktivert", "File sharing" : "Fildeling", "Accept" : "Aksepter", + "Reject" : "Avvis", "Sharing" : "Deling", "Allow editing" : "Tillat redigering", "Allow resharing" : "TIllat videre deling", diff --git a/apps/provisioning_api/lib/Controller/UsersController.php b/apps/provisioning_api/lib/Controller/UsersController.php index 6e8613085d0..570a02f780d 100644 --- a/apps/provisioning_api/lib/Controller/UsersController.php +++ b/apps/provisioning_api/lib/Controller/UsersController.php @@ -59,6 +59,8 @@ use OCP\IUserManager; use OCP\IUserSession; use OCP\L10N\IFactory; use OCP\Security\ISecureRandom; +use OCP\Security\Events\GenerateSecurePasswordEvent; +use OCP\EventDispatcher\IEventDispatcher; class UsersController extends AUserData { @@ -76,6 +78,8 @@ class UsersController extends AUserData { private $secureRandom; /** @var RemoteWipe */ private $remoteWipe; + /** @var IEventDispatcher */ + private $eventDispatcher; public function __construct(string $appName, IRequest $request, @@ -90,7 +94,8 @@ class UsersController extends AUserData { NewUserMailHelper $newUserMailHelper, FederatedShareProviderFactory $federatedShareProviderFactory, ISecureRandom $secureRandom, - RemoteWipe $remoteWipe) { + RemoteWipe $remoteWipe, + IEventDispatcher $eventDispatcher) { parent::__construct($appName, $request, $userManager, @@ -107,6 +112,7 @@ class UsersController extends AUserData { $this->federatedShareProviderFactory = $federatedShareProviderFactory; $this->secureRandom = $secureRandom; $this->remoteWipe = $remoteWipe; + $this->eventDispatcher = $eventDispatcher; } /** @@ -286,9 +292,18 @@ class UsersController extends AUserData { throw new OCSException('To send a password link to the user an email address is required.', 108); } - $password = $this->secureRandom->generate(10); - // Make sure we pass the password_policy - $password .= $this->secureRandom->generate(2, '$!.,;:-~+*[]{}()'); + $passwordEvent = new GenerateSecurePasswordEvent(); + $this->eventDispatcher->dispatchTyped($passwordEvent); + + $password = $passwordEvent->getPassword(); + if ($password === null) { + // Fallback: ensure to pass password_policy in any case + $password = $this->secureRandom->generate(10) + . $this->secureRandom->generate(1, ISecureRandom::CHAR_UPPER) + . $this->secureRandom->generate(1, ISecureRandom::CHAR_LOWER) + . $this->secureRandom->generate(1, ISecureRandom::CHAR_DIGITS) + . $this->secureRandom->generate(1, ISecureRandom::CHAR_SYMBOLS); + } $generatePasswordResetToken = true; } diff --git a/apps/provisioning_api/tests/Controller/UsersControllerTest.php b/apps/provisioning_api/tests/Controller/UsersControllerTest.php index 152a6526653..ceb84cbed4e 100644 --- a/apps/provisioning_api/tests/Controller/UsersControllerTest.php +++ b/apps/provisioning_api/tests/Controller/UsersControllerTest.php @@ -48,6 +48,7 @@ use OCA\Provisioning_API\FederatedShareProviderFactory; use OCA\Settings\Mailer\NewUserMailHelper; use OCP\App\IAppManager; use OCP\AppFramework\Http\DataResponse; +use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\IGroup; use OCP\IL10N; @@ -58,6 +59,7 @@ use OCP\IUserManager; use OCP\IUserSession; use OCP\L10N\IFactory; use OCP\Mail\IEMailTemplate; +use OCP\Security\Events\GenerateSecurePasswordEvent; use OCP\Security\ISecureRandom; use OCP\UserInterface; use PHPUnit\Framework\MockObject\MockObject; @@ -94,6 +96,8 @@ class UsersControllerTest extends TestCase { private $secureRandom; /** @var RemoteWipe|MockObject */ private $remoteWipe; + /** @var IEventDispatcher */ + private $eventDispatcher; protected function setUp(): void { parent::setUp(); @@ -111,6 +115,7 @@ class UsersControllerTest extends TestCase { $this->federatedShareProviderFactory = $this->createMock(FederatedShareProviderFactory::class); $this->secureRandom = $this->createMock(ISecureRandom::class); $this->remoteWipe = $this->createMock(RemoteWipe::class); + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); $this->api = $this->getMockBuilder(UsersController::class) ->setConstructorArgs([ @@ -128,6 +133,7 @@ class UsersControllerTest extends TestCase { $this->federatedShareProviderFactory, $this->secureRandom, $this->remoteWipe, + $this->eventDispatcher, ]) ->setMethods(['fillStorageInfo']) ->getMock(); @@ -389,7 +395,8 @@ class UsersControllerTest extends TestCase { $this->newUserMailHelper, $this->federatedShareProviderFactory, $this->secureRandom, - $this->remoteWipe + $this->remoteWipe, + $this->eventDispatcher, ]) ->setMethods(['editUser']) ->getMock(); @@ -486,6 +493,46 @@ class UsersControllerTest extends TestCase { )); } + public function testAddUserSuccessfulGeneratePassword() { + $this->userManager + ->expects($this->once()) + ->method('userExists') + ->with('NewUser') + ->willReturn(false); + $this->userManager + ->expects($this->once()) + ->method('createUser'); + $this->logger + ->expects($this->once()) + ->method('info') + ->with('Successful addUser call with userid: NewUser', ['app' => 'ocs_api']); + $loggedInUser = $this->getMockBuilder(IUser::class) + ->disableOriginalConstructor() + ->getMock(); + $loggedInUser + ->expects($this->once()) + ->method('getUID') + ->willReturn('adminUser'); + $this->userSession + ->expects($this->once()) + ->method('getUser') + ->willReturn($loggedInUser); + $this->groupManager + ->expects($this->once()) + ->method('isAdmin') + ->with('adminUser') + ->willReturn(true); + $this->eventDispatcher + ->expects($this->once()) + ->method('dispatchTyped') + ->with(new GenerateSecurePasswordEvent()); + + $this->assertTrue(key_exists( + 'id', + $this->api->addUser('NewUser', '', '', 'foo@bar')->getData() + )); + } + public function testAddUserFailedToGenerateUserID() { $this->expectException(\OCP\AppFramework\OCS\OCSException::class); @@ -3126,6 +3173,7 @@ class UsersControllerTest extends TestCase { $this->federatedShareProviderFactory, $this->secureRandom, $this->remoteWipe, + $this->eventDispatcher, ]) ->setMethods(['getUserData']) ->getMock(); @@ -3190,6 +3238,7 @@ class UsersControllerTest extends TestCase { $this->federatedShareProviderFactory, $this->secureRandom, $this->remoteWipe, + $this->eventDispatcher, ]) ->setMethods(['getUserData']) ->getMock(); diff --git a/apps/settings/composer/composer/autoload_classmap.php b/apps/settings/composer/composer/autoload_classmap.php index 480d84e3639..e59b2e7fdbb 100644 --- a/apps/settings/composer/composer/autoload_classmap.php +++ b/apps/settings/composer/composer/autoload_classmap.php @@ -54,4 +54,6 @@ return array( 'OCA\\Settings\\Settings\\Personal\\Security\\TwoFactor' => $baseDir . '/../lib/Settings/Personal/Security/TwoFactor.php', 'OCA\\Settings\\Settings\\Personal\\Security\\WebAuthn' => $baseDir . '/../lib/Settings/Personal/Security/WebAuthn.php', 'OCA\\Settings\\Settings\\Personal\\ServerDevNotice' => $baseDir . '/../lib/Settings/Personal/ServerDevNotice.php', + 'OCA\\Settings\\SetupChecks\\PhpDefaultCharset' => $baseDir . '/../lib/SetupChecks/PhpDefaultCharset.php', + 'OCA\\Settings\\SetupChecks\\PhpOutputBuffering' => $baseDir . '/../lib/SetupChecks/PhpOutputBuffering.php', ); diff --git a/apps/settings/composer/composer/autoload_static.php b/apps/settings/composer/composer/autoload_static.php index d98e58c1c67..4d69a0193a7 100644 --- a/apps/settings/composer/composer/autoload_static.php +++ b/apps/settings/composer/composer/autoload_static.php @@ -69,6 +69,8 @@ class ComposerStaticInitSettings 'OCA\\Settings\\Settings\\Personal\\Security\\TwoFactor' => __DIR__ . '/..' . '/../lib/Settings/Personal/Security/TwoFactor.php', 'OCA\\Settings\\Settings\\Personal\\Security\\WebAuthn' => __DIR__ . '/..' . '/../lib/Settings/Personal/Security/WebAuthn.php', 'OCA\\Settings\\Settings\\Personal\\ServerDevNotice' => __DIR__ . '/..' . '/../lib/Settings/Personal/ServerDevNotice.php', + 'OCA\\Settings\\SetupChecks\\PhpDefaultCharset' => __DIR__ . '/..' . '/../lib/SetupChecks/PhpDefaultCharset.php', + 'OCA\\Settings\\SetupChecks\\PhpOutputBuffering' => __DIR__ . '/..' . '/../lib/SetupChecks/PhpOutputBuffering.php', ); public static function getInitializer(ClassLoader $loader) diff --git a/apps/settings/l10n/pt_BR.js b/apps/settings/l10n/pt_BR.js index 5b702a41f26..dcd1c5775a8 100644 --- a/apps/settings/l10n/pt_BR.js +++ b/apps/settings/l10n/pt_BR.js @@ -168,7 +168,7 @@ OC.L10N.register( "Rename" : "Renomear", "Revoke" : "Revogar", "Wipe device" : "Limpar o dispositivo", - "Revoking this token might prevent the wiping of your device if it hasn't started the wipe yet." : "A revogação desse token pode impedir a limpeza do seu dispositivo, caso ainda não a tenha iniciado.", + "Revoking this token might prevent the wiping of your device if it hasn't started the wipe yet." : "A revogação deste token pode impedir a limpeza do seu dispositivo, caso ainda não tenha sido iniciada.", "Internet Explorer" : "Internet Explorer", "Edge" : "Edge", "Firefox" : "Firefox", diff --git a/apps/settings/l10n/pt_BR.json b/apps/settings/l10n/pt_BR.json index 34ac7df5761..ccfbc46757f 100644 --- a/apps/settings/l10n/pt_BR.json +++ b/apps/settings/l10n/pt_BR.json @@ -166,7 +166,7 @@ "Rename" : "Renomear", "Revoke" : "Revogar", "Wipe device" : "Limpar o dispositivo", - "Revoking this token might prevent the wiping of your device if it hasn't started the wipe yet." : "A revogação desse token pode impedir a limpeza do seu dispositivo, caso ainda não a tenha iniciado.", + "Revoking this token might prevent the wiping of your device if it hasn't started the wipe yet." : "A revogação deste token pode impedir a limpeza do seu dispositivo, caso ainda não tenha sido iniciada.", "Internet Explorer" : "Internet Explorer", "Edge" : "Edge", "Firefox" : "Firefox", diff --git a/apps/settings/lib/Controller/CheckSetupController.php b/apps/settings/lib/Controller/CheckSetupController.php index 3fd4407f7c9..6e45edee18f 100644 --- a/apps/settings/lib/Controller/CheckSetupController.php +++ b/apps/settings/lib/Controller/CheckSetupController.php @@ -53,6 +53,8 @@ use OC\DB\SchemaWrapper; use OC\IntegrityCheck\Checker; use OC\Lock\NoopLockingProvider; use OC\MemoryInfo; +use OCA\Settings\SetupChecks\PhpDefaultCharset; +use OCA\Settings\SetupChecks\PhpOutputBuffering; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\DataDisplayResponse; use OCP\AppFramework\Http\DataResponse; @@ -683,6 +685,8 @@ Raw output * @return DataResponse */ public function check() { + $phpDefaultCharset = new PhpDefaultCharset(); + $phpOutputBuffering = new PhpOutputBuffering(); return new DataResponse( [ 'isGetenvServerWorking' => !empty(getenv('PATH')), @@ -723,6 +727,8 @@ Raw output 'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(), 'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(), 'reverseProxyGeneratedURL' => $this->urlGenerator->getAbsoluteURL('index.php'), + PhpDefaultCharset::class => ['pass' => $phpDefaultCharset->run(), 'description' => $phpDefaultCharset->description(), 'severity' => $phpDefaultCharset->severity()], + PhpOutputBuffering::class => ['pass' => $phpOutputBuffering->run(), 'description' => $phpOutputBuffering->description(), 'severity' => $phpOutputBuffering->severity()], ] ); } diff --git a/apps/settings/lib/SetupChecks/PhpDefaultCharset.php b/apps/settings/lib/SetupChecks/PhpDefaultCharset.php new file mode 100644 index 00000000000..0e50a8db1fc --- /dev/null +++ b/apps/settings/lib/SetupChecks/PhpDefaultCharset.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020 Daniel Kesselberg <mail@danielkesselberg.de> + * + * @author Daniel Kesselberg <mail@danielkesselberg.de> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Settings\SetupChecks; + +class PhpDefaultCharset { + public function description(): string { + return 'PHP configuration option default_charset should be UTF-8'; + } + + public function severity(): string { + return 'warning'; + } + + public function run(): bool { + return strtoupper(trim(ini_get('default_charset'))) === 'UTF-8'; + } +} diff --git a/apps/settings/lib/SetupChecks/PhpOutputBuffering.php b/apps/settings/lib/SetupChecks/PhpOutputBuffering.php new file mode 100644 index 00000000000..c89ec273e71 --- /dev/null +++ b/apps/settings/lib/SetupChecks/PhpOutputBuffering.php @@ -0,0 +1,43 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020 Daniel Kesselberg <mail@danielkesselberg.de> + * + * @author Daniel Kesselberg <mail@danielkesselberg.de> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Settings\SetupChecks; + +class PhpOutputBuffering { + public function description(): string { + return 'PHP configuration option output_buffering must be disabled'; + } + + public function severity(): string { + return 'error'; + } + + public function run(): bool { + $value = trim(ini_get('output_buffering')); + return $value === '' || $value === '0'; + } +} diff --git a/apps/settings/tests/Controller/CheckSetupControllerTest.php b/apps/settings/tests/Controller/CheckSetupControllerTest.php index ebbe9fd95b7..271427d455a 100644 --- a/apps/settings/tests/Controller/CheckSetupControllerTest.php +++ b/apps/settings/tests/Controller/CheckSetupControllerTest.php @@ -593,6 +593,8 @@ class CheckSetupControllerTest extends TestCase { 'isMysqlUsedWithoutUTF8MB4' => false, 'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => true, 'reverseProxyGeneratedURL' => 'https://server/index.php', + 'OCA\Settings\SetupChecks\PhpDefaultCharset' => ['pass' => true, 'description' => 'PHP configuration option default_charset should be UTF-8', 'severity' => 'warning'], + 'OCA\Settings\SetupChecks\PhpOutputBuffering' => ['pass' => true, 'description' => 'PHP configuration option output_buffering must be disabled', 'severity' => 'error'], ] ); $this->assertEquals($expected, $this->checkSetupController->check()); diff --git a/apps/settings/tests/SetupChecks/PhpDefaultCharsetTest.php b/apps/settings/tests/SetupChecks/PhpDefaultCharsetTest.php new file mode 100644 index 00000000000..18a72dc2ff8 --- /dev/null +++ b/apps/settings/tests/SetupChecks/PhpDefaultCharsetTest.php @@ -0,0 +1,47 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020 Daniel Kesselberg <mail@danielkesselberg.de> + * + * @author Daniel Kesselberg <mail@danielkesselberg.de> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Settings\Tests; + +use OCA\Settings\SetupChecks\PhpDefaultCharset; +use Test\TestCase; + +class PhpDefaultCharsetTest extends TestCase { + public function testPass(): void { + $check = new PhpDefaultCharset(); + $this->assertTrue($check->run()); + } + + public function testFail(): void { + ini_set('default_charset', 'ISO-8859-15'); + + $check = new PhpDefaultCharset(); + $this->assertFalse($check->run()); + + ini_restore('default_charset'); + } +} diff --git a/apps/settings/tests/SetupChecks/PhpOutputBufferingTest.php b/apps/settings/tests/SetupChecks/PhpOutputBufferingTest.php new file mode 100644 index 00000000000..83d560d281d --- /dev/null +++ b/apps/settings/tests/SetupChecks/PhpOutputBufferingTest.php @@ -0,0 +1,43 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020 Daniel Kesselberg <mail@danielkesselberg.de> + * + * @author Daniel Kesselberg <mail@danielkesselberg.de> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Settings\Tests; + +use OCA\Settings\SetupChecks\PhpOutputBuffering; +use Test\TestCase; + +class PhpOutputBufferingTest extends TestCase { + /* + * output_buffer is PHP_INI_PERDIR and cannot changed at runtime. + * Run this test with -d output_buffering=1 to validate the fail case. + */ + + public function testPass(): void { + $check = new PhpOutputBuffering(); + $this->assertTrue($check->run()); + } +} diff --git a/apps/user_ldap/lib/Command/ShowConfig.php b/apps/user_ldap/lib/Command/ShowConfig.php index 80af5718dcb..65d6f5165a7 100644 --- a/apps/user_ldap/lib/Command/ShowConfig.php +++ b/apps/user_ldap/lib/Command/ShowConfig.php @@ -108,7 +108,7 @@ class ShowConfig extends Command { $rows[] = [$key, $value]; } $table->setRows($rows); - $table->render($output); + $table->render(); } } } diff --git a/apps/user_ldap/lib/Command/ShowRemnants.php b/apps/user_ldap/lib/Command/ShowRemnants.php index 4722bf76ce1..c5e7c5321f3 100644 --- a/apps/user_ldap/lib/Command/ShowRemnants.php +++ b/apps/user_ldap/lib/Command/ShowRemnants.php @@ -101,7 +101,7 @@ class ShowRemnants extends Command { $output->writeln(json_encode($rows)); } else { $table->setRows($rows); - $table->render($output); + $table->render(); } return 0; } diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php index 6f6e7362e97..6bf1bd31ba0 100644 --- a/apps/user_ldap/lib/Group_LDAP.php +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -20,6 +20,7 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Roland Tapken <roland@bitarbeiter.net> * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Tobias Perschon <tobias@perschon.at> * @author Victor Dubiniuk <dubiniuk@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> * @author Vinicius Cubas Brand <vinicius@eita.org.br> @@ -66,6 +67,11 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I /** @var ILogger */ protected $logger; + /** + * @var string $ldapGroupMemberAssocAttr contains the LDAP setting (in lower case) with the same name + */ + protected $ldapGroupMemberAssocAttr; + public function __construct(Access $access, GroupPluginManager $groupPluginManager) { parent::__construct($access); $filter = $this->access->connection->ldapGroupFilter; @@ -79,6 +85,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I $this->cachedNestedGroups = new CappedMemoryCache(); $this->groupPluginManager = $groupPluginManager; $this->logger = OC::$server->getLogger(); + $this->ldapGroupMemberAssocAttr = strtolower($gAssoc); } /** @@ -136,31 +143,38 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } //extra work if we don't get back user DNs - if (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') { - $requestAttributes = $this->access->userManager->getAttributes(true); - $dns = []; - $filterParts = []; - $bytes = 0; - foreach ($members as $mid) { - $filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter); - $filterParts[] = $filter; - $bytes += strlen($filter); - if ($bytes >= 9000000) { - // AD has a default input buffer of 10 MB, we do not want - // to take even the chance to exceed it + switch ($this->ldapGroupMemberAssocAttr) { + case 'memberuid': + case 'zimbramailforwardingaddress': + $requestAttributes = $this->access->userManager->getAttributes(true); + $dns = []; + $filterParts = []; + $bytes = 0; + foreach ($members as $mid) { + if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') { + $parts = explode('@', $mid); //making sure we get only the uid + $mid = $parts[0]; + } + $filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter); + $filterParts[] = $filter; + $bytes += strlen($filter); + if ($bytes >= 9000000) { + // AD has a default input buffer of 10 MB, we do not want + // to take even the chance to exceed it + $filter = $this->access->combineFilterWithOr($filterParts); + $users = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts)); + $bytes = 0; + $filterParts = []; + $dns = array_merge($dns, $users); + } + } + if (count($filterParts) > 0) { $filter = $this->access->combineFilterWithOr($filterParts); - $bytes = 0; - $filterParts = []; $users = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts)); $dns = array_merge($dns, $users); } - } - if (count($filterParts) > 0) { - $filter = $this->access->combineFilterWithOr($filterParts); - $users = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts)); - $dns = array_merge($dns, $users); - } - $members = $dns; + $members = $dns; + break; } $isInGroup = in_array($userDN, $members); @@ -673,8 +687,8 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I // memberof doesn't support memberuid, so skip it here. if ((int)$this->access->connection->hasMemberOfFilterSupport === 1 && (int)$this->access->connection->useMemberOfToDetectMembership === 1 - && strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid' - ) { + && $this->ldapGroupMemberAssocAttr !== 'memberuid' + && $this->ldapGroupMemberAssocAttr !== 'zimbramailforwardingaddress') { $groupDNs = $this->_getGroupDNsFromMemberOf($userDN); if (is_array($groupDNs)) { foreach ($groupDNs as $dn) { @@ -698,27 +712,33 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } //uniqueMember takes DN, memberuid the uid, so we need to distinguish - if ((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember') - || (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member') - ) { - $uid = $userDN; - } elseif (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') { - $result = $this->access->readAttribute($userDN, 'uid'); - if ($result === false) { - $this->logger->debug('No uid attribute found for DN {dn} on {host}', - [ - 'app' => 'user_ldap', - 'dn' => $userDN, - 'host' => $this->access->connection->ldapHost, - ] - ); - $uid = false; - } else { - $uid = $result[0]; - } - } else { - // just in case - $uid = $userDN; + switch ($this->ldapGroupMemberAssocAttr) { + case 'uniquemember': + case 'member': + $uid = $userDN; + break; + + case 'memberuid': + case 'zimbramailforwardingaddress': + $result = $this->access->readAttribute($userDN, 'uid'); + if ($result === false) { + $this->logger->debug('No uid attribute found for DN {dn} on {host}', + [ + 'app' => 'user_ldap', + 'dn' => $userDN, + 'host' => $this->access->connection->ldapHost, + ] + ); + $uid = false; + } else { + $uid = $result[0]; + } + break; + + default: + // just in case + $uid = $userDN; + break; } if ($uid !== false) { @@ -759,6 +779,12 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I $allGroups = []; $seen[$dn] = true; $filter = $this->access->connection->ldapGroupMemberAssocAttr . '=' . $dn; + + if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') { + //in this case the member entries are email addresses + $filter .= '@*'; + } + $groups = $this->access->fetchListOfGroups($filter, [strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']); if (is_array($groups)) { @@ -768,6 +794,11 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } return $this->getGroupsByMember($dn, $seen); }; + + if (empty($dn)) { + $dn = ""; + } + $allGroups = $this->walkNestedGroups($dn, $fetcher, $groups); } $visibleGroups = $this->filterValidGroups($allGroups); @@ -828,50 +859,57 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } $groupUsers = []; - $isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid'); $attrs = $this->access->userManager->getAttributes(true); foreach ($members as $member) { - if ($isMemberUid) { - //we got uids, need to get their DNs to 'translate' them to user names - $filter = $this->access->combineFilterWithAnd([ - str_replace('%uid', trim($member), $this->access->connection->ldapLoginFilter), - $this->access->combineFilterWithAnd([ - $this->access->getFilterPartForUserSearch($search), - $this->access->connection->ldapUserFilter - ]) - ]); - $ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1); - if (count($ldap_users) < 1) { - continue; - } - $groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]); - } else { - //we got DNs, check if we need to filter by search or we can give back all of them - $uid = $this->access->dn2username($member); - if (!$uid) { - continue; - } - - $cacheKey = 'userExistsOnLDAP' . $uid; - $userExists = $this->access->connection->getFromCache($cacheKey); - if ($userExists === false) { - continue; - } - if ($userExists === null || $search !== '') { - if (!$this->access->readAttribute($member, - $this->access->connection->ldapUserDisplayName, + switch ($this->ldapGroupMemberAssocAttr) { + case 'zimbramailforwardingaddress': + //we get email addresses and need to convert them to uids + $parts = explode('@', $member); + $member = $parts[0]; + //no break needed because we just needed to remove the email part and now we have uids + case 'memberuid': + //we got uids, need to get their DNs to 'translate' them to user names + $filter = $this->access->combineFilterWithAnd([ + str_replace('%uid', trim($member), $this->access->connection->ldapLoginFilter), $this->access->combineFilterWithAnd([ $this->access->getFilterPartForUserSearch($search), $this->access->connection->ldapUserFilter - ]))) { - if ($search === '') { - $this->access->connection->writeToCache($cacheKey, false); + ]) + ]); + $ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1); + if (empty($ldap_users)) { + break; + } + $groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]); + break; + default: + //we got DNs, check if we need to filter by search or we can give back all of them + $uid = $this->access->dn2username($member); + if (!$uid) { + break; + } + + $cacheKey = 'userExistsOnLDAP' . $uid; + $userExists = $this->access->connection->getFromCache($cacheKey); + if ($userExists === false) { + break; + } + if ($userExists === null || $search !== '') { + if (!$this->access->readAttribute($member, + $this->access->connection->ldapUserDisplayName, + $this->access->combineFilterWithAnd([ + $this->access->getFilterPartForUserSearch($search), + $this->access->connection->ldapUserFilter + ]))) { + if ($search === '') { + $this->access->connection->writeToCache($cacheKey, false); + } + break; } - continue; + $this->access->connection->writeToCache($cacheKey, true); } - $this->access->connection->writeToCache($cacheKey, true); - } - $groupUsers[] = $uid; + $groupUsers[] = $uid; + break; } } @@ -930,8 +968,8 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } $search = $this->access->escapeFilterPart($search, true); $isMemberUid = - (strtolower($this->access->connection->ldapGroupMemberAssocAttr) - === 'memberuid'); + ($this->ldapGroupMemberAssocAttr === 'memberuid' || + $this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress'); //we need to apply the search filter //alternatives that need to be checked: @@ -944,6 +982,11 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I $groupUsers = []; foreach ($members as $member) { if ($isMemberUid) { + if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') { + //we get email addresses and need to convert them to uids + $parts = explode('@', $member); + $member = $parts[0]; + } //we got uids, need to get their DNs to 'translate' them to user names $filter = $this->access->combineFilterWithAnd([ str_replace('%uid', $member, $this->access->connection->ldapLoginFilter), diff --git a/apps/user_ldap/lib/Jobs/UpdateGroups.php b/apps/user_ldap/lib/Jobs/UpdateGroups.php index 58254bf41e9..035caf0c33c 100644 --- a/apps/user_ldap/lib/Jobs/UpdateGroups.php +++ b/apps/user_ldap/lib/Jobs/UpdateGroups.php @@ -44,6 +44,8 @@ use OCA\User_LDAP\LogWrapper; use OCA\User_LDAP\Mapping\GroupMapping; use OCA\User_LDAP\Mapping\UserMapping; use OCA\User_LDAP\User\Manager; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Group\Events\UserRemovedEvent; use OCP\ILogger; class UpdateGroups extends \OC\BackgroundJob\TimedJob { @@ -94,6 +96,10 @@ class UpdateGroups extends \OC\BackgroundJob\TimedJob { * @param string[] $groups */ private static function handleKnownGroups($groups) { + $dispatcher = \OC::$server->query(IEventDispatcher::class); + $groupManager = \OC::$server->getGroupManager(); + $userManager = \OC::$server->getUserManager(); + \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Dealing with known Groups.', ILogger::DEBUG); $query = \OC_DB::prepare(' UPDATE `*PREFIX*ldap_group_members` @@ -105,8 +111,11 @@ class UpdateGroups extends \OC\BackgroundJob\TimedJob { $knownUsers = unserialize(self::$groupsFromDB[$group]['owncloudusers']); $actualUsers = self::getGroupBE()->usersInGroup($group); $hasChanged = false; + + $groupObject = $groupManager->get($group); foreach (array_diff($knownUsers, $actualUsers) as $removedUser) { - \OCP\Util::emitHook('OC_User', 'post_removeFromGroup', ['uid' => $removedUser, 'gid' => $group]); + $userObject = $userManager->get($removedUser); + $dispatcher->dispatchTyped(new UserRemovedEvent($groupObject, $userObject)); \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – "'.$removedUser.'" removed from "'.$group.'".', ILogger::INFO); diff --git a/apps/user_ldap/lib/Wizard.php b/apps/user_ldap/lib/Wizard.php index 73032bfd7f2..32ad19efa5b 100644 --- a/apps/user_ldap/lib/Wizard.php +++ b/apps/user_ldap/lib/Wizard.php @@ -794,7 +794,7 @@ class Wizard extends LDAPUtility { * @throws \Exception */ private function detectGroupMemberAssoc() { - $possibleAttrs = ['uniqueMember', 'memberUid', 'member', 'gidNumber']; + $possibleAttrs = ['uniqueMember', 'memberUid', 'member', 'gidNumber', 'zimbraMailForwardingAddress']; $filter = $this->configuration->ldapGroupFilter; if (empty($filter)) { return false; diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index 6b0221f0935..32dfdd12abe 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -103,7 +103,10 @@ style('user_ldap', 'settings'); p(' selected'); } ?>>member (AD)</option><option value="gidNumber"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'gidNumber')) { p(' selected'); - } ?>>gidNumber</option></select></p> <p><label for="ldap_dynamic_group_member_url"><?php p($l->t('Dynamic Group Member URL'));?></label><input type="text" id="ldap_dynamic_group_member_url" name="ldap_dynamic_group_member_url" title="<?php p($l->t('The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)'));?>" data-default="<?php p($_['ldap_dynamic_group_member_url_default']); ?>" /></p> + } ?>>gidNumber</option><option value="zimbraMailForwardingAddress"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'zimbraMailForwardingAddress')) { + p(' selected'); + } ?>>zimbraMailForwardingAddress</option></select></p> + <p><label for="ldap_dynamic_group_member_url"><?php p($l->t('Dynamic Group Member URL'));?></label><input type="text" id="ldap_dynamic_group_member_url" name="ldap_dynamic_group_member_url" title="<?php p($l->t('The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)'));?>" data-default="<?php p($_['ldap_dynamic_group_member_url_default']); ?>" /></p> <p><label for="ldap_nested_groups"><?php p($l->t('Nested Groups'));?></label><input type="checkbox" id="ldap_nested_groups" name="ldap_nested_groups" value="1" data-default="<?php p($_['ldap_nested_groups_default']); ?>" title="<?php p($l->t('When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)'));?>" /></p> <p><label for="ldap_paging_size"><?php p($l->t('Paging chunksize'));?></label><input type="number" id="ldap_paging_size" name="ldap_paging_size" title="<?php p($l->t('Chunksize used for paged LDAP searches that may return bulky results like user or group enumeration. (Setting it 0 disables paged LDAP searches in those situations.)'));?>" data-default="<?php p($_['ldap_paging_size_default']); ?>" /></p> <p><label for="ldap_turn_on_pwd_change"><?php p($l->t('Enable LDAP password changes per user'));?></label><span class="inlinetable"><span class="tablerow left"><input type="checkbox" id="ldap_turn_on_pwd_change" name="ldap_turn_on_pwd_change" value="1" data-default="<?php p($_['ldap_turn_on_pwd_change_default']); ?>" title="<?php p($l->t('Allow LDAP users to change their password and allow Super Administrators and Group Administrators to change the password of their LDAP users. Only works when access control policies are configured accordingly on the LDAP server. As passwords are sent in plaintext to the LDAP server, transport encryption must be used and password hashing should be configured on the LDAP server.'));?>" /><span class="tablecell"><?php p($l->t('(New password is sent as plain text to LDAP)'));?></span></span> diff --git a/apps/workflowengine/lib/AppInfo/Application.php b/apps/workflowengine/lib/AppInfo/Application.php index ddac4a74e28..8f762f0d707 100644 --- a/apps/workflowengine/lib/AppInfo/Application.php +++ b/apps/workflowengine/lib/AppInfo/Application.php @@ -36,6 +36,7 @@ use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; use OCP\ILogger; use OCP\IServerContainer; +use OCP\WorkflowEngine\Events\LoadSettingsScriptsEvent; use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IEntityCompat; use OCP\WorkflowEngine\IOperation; @@ -51,7 +52,7 @@ class Application extends App implements IBootstrap { public function register(IRegistrationContext $context): void { $context->registerServiceAlias('RequestTimeController', RequestTime::class); $context->registerEventListener( - 'OCP\WorkflowEngine::loadAdditionalSettingScripts', + LoadSettingsScriptsEvent::class, LoadAdditionalSettingsScriptsListener::class, -100 ); diff --git a/apps/workflowengine/lib/Settings/ASettings.php b/apps/workflowengine/lib/Settings/ASettings.php index 3d704e662ee..4d0d4312f16 100644 --- a/apps/workflowengine/lib/Settings/ASettings.php +++ b/apps/workflowengine/lib/Settings/ASettings.php @@ -82,10 +82,12 @@ abstract class ASettings implements ISettings { * @return TemplateResponse */ public function getForm() { + // @deprecated in 20.0.0: retire this one in favor of the typed event $this->eventDispatcher->dispatch( 'OCP\WorkflowEngine::loadAdditionalSettingScripts', new LoadSettingsScriptsEvent() ); + $this->eventDispatcher->dispatchTyped(new LoadSettingsScriptsEvent()); $entities = $this->manager->getEntitiesList(); $this->initialStateService->provideInitialState( |