diff options
author | Côme Chilliet <91878298+come-nc@users.noreply.github.com> | 2022-11-22 16:47:42 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-22 16:47:42 +0100 |
commit | 911967867fe4fb29641afd85cee3f43c41b36adb (patch) | |
tree | e9a42c9ba61f8573f06bc92e6e52fcb87d9fc7d6 /apps/user_ldap | |
parent | c323022d06a2fb76745e626452ea035121f35ca9 (diff) | |
parent | 603feb98f24c92992da025255b0e125ad9d169ba (diff) | |
download | nextcloud-server-911967867fe4fb29641afd85cee3f43c41b36adb.tar.gz nextcloud-server-911967867fe4fb29641afd85cee3f43c41b36adb.zip |
Merge branch 'master' into ldapi-unix-socket-support
Signed-off-by: Côme Chilliet <91878298+come-nc@users.noreply.github.com>
Diffstat (limited to 'apps/user_ldap')
57 files changed, 867 insertions, 900 deletions
diff --git a/apps/user_ldap/ajax/clearMappings.php b/apps/user_ldap/ajax/clearMappings.php index 39462f334c9..f8469cc85b1 100644 --- a/apps/user_ldap/ajax/clearMappings.php +++ b/apps/user_ldap/ajax/clearMappings.php @@ -35,7 +35,7 @@ $subject = (string)$_POST['ldap_clear_mapping']; $mapping = null; try { if ($subject === 'user') { - $mapping = new UserMapping(\OC::$server->getDatabaseConnection()); + $mapping = \OCP\Server::get(UserMapping::class); $result = $mapping->clearCb( function ($uid) { \OC::$server->getUserManager()->emit('\OC\User', 'preUnassignedUserId', [$uid]); diff --git a/apps/user_ldap/ajax/testConfiguration.php b/apps/user_ldap/ajax/testConfiguration.php index a5f41e93a92..9d89b641d66 100644 --- a/apps/user_ldap/ajax/testConfiguration.php +++ b/apps/user_ldap/ajax/testConfiguration.php @@ -47,7 +47,7 @@ try { if ($configurationOk) { //Configuration is okay /* - * Clossing the session since it won't be used from this point on. There might be a potential + * Closing the session since it won't be used from this point on. There might be a potential * race condition if a second request is made: either this request or the other might not * contact the LDAP backup server the first time when it should, but there shouldn't be any * problem with that other than the extra connection. diff --git a/apps/user_ldap/appinfo/info.xml b/apps/user_ldap/appinfo/info.xml index 803b2645705..2f4cb7f2eed 100644 --- a/apps/user_ldap/appinfo/info.xml +++ b/apps/user_ldap/appinfo/info.xml @@ -9,7 +9,7 @@ A user logs into Nextcloud with their LDAP or AD credentials, and is granted access based on an authentication request handled by the LDAP or AD server. Nextcloud does not store LDAP or AD passwords, rather these credentials are used to authenticate a user and then Nextcloud uses a session for the user ID. More information is available in the LDAP User and Group Backend documentation. </description> - <version>1.15.0</version> + <version>1.16.0</version> <licence>agpl</licence> <author>Dominik Schmidt</author> <author>Arthur Schiwon</author> @@ -24,7 +24,7 @@ A user logs into Nextcloud with their LDAP or AD credentials, and is granted acc <bugs>https://github.com/nextcloud/server/issues</bugs> <dependencies> <lib>ldap</lib> - <nextcloud min-version="25" max-version="25"/> + <nextcloud min-version="26" max-version="26"/> </dependencies> <background-jobs> diff --git a/apps/user_ldap/composer/composer/autoload_classmap.php b/apps/user_ldap/composer/composer/autoload_classmap.php index ae112b2b604..c9001d3d59a 100644 --- a/apps/user_ldap/composer/composer/autoload_classmap.php +++ b/apps/user_ldap/composer/composer/autoload_classmap.php @@ -71,8 +71,6 @@ return array( 'OCA\\User_LDAP\\Migration\\Version1130Date20220110154719' => $baseDir . '/../lib/Migration/Version1130Date20220110154719.php', 'OCA\\User_LDAP\\Migration\\Version1141Date20220323143801' => $baseDir . '/../lib/Migration/Version1141Date20220323143801.php', 'OCA\\User_LDAP\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php', - 'OCA\\User_LDAP\\PagedResults\\IAdapter' => $baseDir . '/../lib/PagedResults/IAdapter.php', - 'OCA\\User_LDAP\\PagedResults\\Php73' => $baseDir . '/../lib/PagedResults/Php73.php', 'OCA\\User_LDAP\\PagedResults\\TLinkId' => $baseDir . '/../lib/PagedResults/TLinkId.php', 'OCA\\User_LDAP\\Proxy' => $baseDir . '/../lib/Proxy.php', 'OCA\\User_LDAP\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php', diff --git a/apps/user_ldap/composer/composer/autoload_static.php b/apps/user_ldap/composer/composer/autoload_static.php index 3ff92c350f5..53d7bcb6481 100644 --- a/apps/user_ldap/composer/composer/autoload_static.php +++ b/apps/user_ldap/composer/composer/autoload_static.php @@ -86,8 +86,6 @@ class ComposerStaticInitUser_LDAP 'OCA\\User_LDAP\\Migration\\Version1130Date20220110154719' => __DIR__ . '/..' . '/../lib/Migration/Version1130Date20220110154719.php', 'OCA\\User_LDAP\\Migration\\Version1141Date20220323143801' => __DIR__ . '/..' . '/../lib/Migration/Version1141Date20220323143801.php', 'OCA\\User_LDAP\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php', - 'OCA\\User_LDAP\\PagedResults\\IAdapter' => __DIR__ . '/..' . '/../lib/PagedResults/IAdapter.php', - 'OCA\\User_LDAP\\PagedResults\\Php73' => __DIR__ . '/..' . '/../lib/PagedResults/Php73.php', 'OCA\\User_LDAP\\PagedResults\\TLinkId' => __DIR__ . '/..' . '/../lib/PagedResults/TLinkId.php', 'OCA\\User_LDAP\\Proxy' => __DIR__ . '/..' . '/../lib/Proxy.php', 'OCA\\User_LDAP\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php', diff --git a/apps/user_ldap/composer/composer/installed.php b/apps/user_ldap/composer/composer/installed.php index c54b7ff5437..5f83b3f2bff 100644 --- a/apps/user_ldap/composer/composer/installed.php +++ b/apps/user_ldap/composer/composer/installed.php @@ -3,7 +3,7 @@ 'name' => '__root__', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '7669331be09dd3cb848182ae8edc2408802a4fb4', + 'reference' => '144514e49e25b7b123fd535902fee97fa39fb446', 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), @@ -13,7 +13,7 @@ '__root__' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '7669331be09dd3cb848182ae8edc2408802a4fb4', + 'reference' => '144514e49e25b7b123fd535902fee97fa39fb446', 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), diff --git a/apps/user_ldap/img/copy.svg b/apps/user_ldap/img/copy.svg index ac68211f7c2..23905faf7e2 100644 --- a/apps/user_ldap/img/copy.svg +++ b/apps/user_ldap/img/copy.svg @@ -1 +1,11 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" version="1"><path d="M8.172 4.685A2.4 2.4 0 0 0 5.767 7.09v30.675a2.4 2.4 0 0 0 2.405 2.404h28.944a2.4 2.4 0 0 0 2.404-2.405V7.09a2.4 2.4 0 0 0-2.404-2.405H8.172z" stroke-linejoin="round" stroke="#fff" stroke-width="8.12547063" fill="none"/><path d="M8.172 4.685A2.4 2.4 0 0 0 5.767 7.09v30.675a2.4 2.4 0 0 0 2.405 2.404h28.944a2.4 2.4 0 0 0 2.404-2.405V7.09a2.4 2.4 0 0 0-2.404-2.405H8.172z" stroke-linejoin="round" fill-rule="evenodd" stroke="#000" stroke-width="3.1254052800000003" fill="#fff"/><path d="M22.884 19.83a2.4 2.4 0 0 0-2.404 2.406V52.91a2.4 2.4 0 0 0 2.404 2.405h28.944a2.4 2.4 0 0 0 2.406-2.404V22.237a2.4 2.4 0 0 0-2.406-2.406H22.884z" stroke-linejoin="round" stroke="#fff" stroke-width="8.12547063" fill="none"/><path d="M22.884 19.83a2.4 2.4 0 0 0-2.404 2.406V52.91a2.4 2.4 0 0 0 2.404 2.405h28.944a2.4 2.4 0 0 0 2.406-2.404V22.237a2.4 2.4 0 0 0-2.406-2.406H22.884z" stroke-linejoin="round" fill-rule="evenodd" stroke="#000" stroke-width="3.1254052800000003" fill="#fff"/><path d="M12.457 21.6c2.325 20.528 20.15 19.15 21.296 19.042v6.14l8.98-8.89-8.98-8.87v6.066c-1.348.16-13.94 1.422-21.296-13.49z" stroke-linejoin="round" stroke="#fff" stroke-linecap="round" stroke-width="8.125" fill="none"/><path d="M12.457 21.6c2.325 20.528 20.15 19.15 21.296 19.042v6.14l8.98-8.89-8.98-8.87v6.066c-1.348.16-13.94 1.422-21.296-13.49z" stroke-linejoin="round" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-width="3.125"/></svg>
\ No newline at end of file +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1"> +<g id="surface1"> +<path style="fill:none;stroke-width:8.125471;stroke-linecap:butt;stroke-linejoin:round;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:4;" d="M 8.173828 4.6875 C 7.529297 4.6875 6.914062 4.936523 6.474609 5.390625 C 6.020508 5.844727 5.771484 6.445312 5.771484 7.089844 L 5.771484 37.763672 C 5.771484 38.408203 6.020508 39.008789 6.474609 39.462891 C 6.928711 39.916992 7.529297 40.166016 8.173828 40.166016 L 37.119141 40.166016 C 37.749023 40.166016 38.364258 39.916992 38.818359 39.462891 C 39.272461 39.008789 39.521484 38.408203 39.521484 37.763672 L 39.521484 7.089844 C 39.521484 6.445312 39.272461 5.844727 38.818359 5.390625 C 38.364258 4.936523 37.749023 4.6875 37.119141 4.6875 Z M 8.173828 4.6875 " transform="matrix(0.266667,0,0,0.266667,0,0)"/> +<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:3.125405;stroke-linecap:butt;stroke-linejoin:round;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 8.173828 4.6875 C 7.529297 4.6875 6.914062 4.936523 6.474609 5.390625 C 6.020508 5.844727 5.771484 6.445312 5.771484 7.089844 L 5.771484 37.763672 C 5.771484 38.408203 6.020508 39.008789 6.474609 39.462891 C 6.928711 39.916992 7.529297 40.166016 8.173828 40.166016 L 37.119141 40.166016 C 37.749023 40.166016 38.364258 39.916992 38.818359 39.462891 C 39.272461 39.008789 39.521484 38.408203 39.521484 37.763672 L 39.521484 7.089844 C 39.521484 6.445312 39.272461 5.844727 38.818359 5.390625 C 38.364258 4.936523 37.749023 4.6875 37.119141 4.6875 Z M 8.173828 4.6875 " transform="matrix(0.266667,0,0,0.266667,0,0)"/> +<path style="fill:none;stroke-width:8.125471;stroke-linecap:butt;stroke-linejoin:round;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:4;" d="M 22.880859 19.833984 C 22.250977 19.833984 21.635742 20.083008 21.181641 20.537109 C 20.727539 20.991211 20.478516 21.591797 20.478516 22.236328 L 20.478516 52.910156 C 20.478516 53.554688 20.727539 54.155273 21.181641 54.609375 C 21.635742 55.063477 22.250977 55.3125 22.880859 55.3125 L 51.826172 55.3125 C 52.470703 55.3125 53.085938 55.063477 53.525391 54.609375 C 53.979492 54.155273 54.228516 53.554688 54.228516 52.910156 L 54.228516 22.236328 C 54.228516 21.591797 53.979492 20.991211 53.525391 20.537109 C 53.085938 20.083008 52.470703 19.833984 51.826172 19.833984 Z M 22.880859 19.833984 " transform="matrix(0.266667,0,0,0.266667,0,0)"/> +<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:3.125405;stroke-linecap:butt;stroke-linejoin:round;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 22.880859 19.833984 C 22.250977 19.833984 21.635742 20.083008 21.181641 20.537109 C 20.727539 20.991211 20.478516 21.591797 20.478516 22.236328 L 20.478516 52.910156 C 20.478516 53.554688 20.727539 54.155273 21.181641 54.609375 C 21.635742 55.063477 22.250977 55.3125 22.880859 55.3125 L 51.826172 55.3125 C 52.470703 55.3125 53.085938 55.063477 53.525391 54.609375 C 53.979492 54.155273 54.228516 53.554688 54.228516 52.910156 L 54.228516 22.236328 C 54.228516 21.591797 53.979492 20.991211 53.525391 20.537109 C 53.085938 20.083008 52.470703 19.833984 51.826172 19.833984 Z M 22.880859 19.833984 " transform="matrix(0.266667,0,0,0.266667,0,0)"/> +<path style="fill:none;stroke-width:8.125;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:4;" d="M 12.451172 21.606445 C 14.780273 42.128906 32.607422 40.751953 33.75 40.634766 L 33.75 46.787109 L 42.729492 37.895508 L 33.75 29.018555 L 33.75 35.083008 C 32.402344 35.244141 19.819336 36.503906 12.451172 21.591797 Z M 12.451172 21.606445 " transform="matrix(0.266667,0,0,0.266667,0,0)"/> +<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:3.125;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 12.451172 21.606445 C 14.780273 42.128906 32.607422 40.751953 33.75 40.634766 L 33.75 46.787109 L 42.729492 37.895508 L 33.75 29.018555 L 33.75 35.083008 C 32.402344 35.244141 19.819336 36.503906 12.451172 21.591797 Z M 12.451172 21.606445 " transform="matrix(0.266667,0,0,0.266667,0,0)"/> +</g> +</svg> diff --git a/apps/user_ldap/js/wizard/view.js b/apps/user_ldap/js/wizard/view.js index 30a00d614cc..8a530c17e4f 100644 --- a/apps/user_ldap/js/wizard/view.js +++ b/apps/user_ldap/js/wizard/view.js @@ -40,7 +40,7 @@ OCA = OCA || {}; }, /** - * applies click events to the forward and backword buttons + * applies click events to the forward and backward buttons */ initControls: function() { var view = this; diff --git a/apps/user_ldap/l10n/bg.js b/apps/user_ldap/l10n/bg.js index 89edb9481a5..92a957be430 100644 --- a/apps/user_ldap/l10n/bg.js +++ b/apps/user_ldap/l10n/bg.js @@ -60,6 +60,10 @@ OC.L10N.register( "Your password will expire today." : "Вашата парола ще изтече днес.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Вашата парола ще изтече в рамките на %n дни.","Вашата парола ще изтече в рамките на %n дни."], "LDAP/AD integration" : "LDAP/AD интеграция", + "_%n group found_::_%n groups found_" : ["%n открити групи","%n открити групи"], + "> 1000 groups found" : "> 1000 открити групи", + "> 1000 users found" : "> 1000 намерени потребители", + "_%n user found_::_%n users found_" : ["%n намерени потребители","%n намерени потребители"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Не можа да се открие атрибут за показвано име на потребителя. Моля, посочете го сами в разширените настройки на LDAP.", "Could not find the desired feature" : "Не е открита желанта функция", "Invalid Host" : "Невалиден хост", diff --git a/apps/user_ldap/l10n/bg.json b/apps/user_ldap/l10n/bg.json index 9028213f374..c5481157ee2 100644 --- a/apps/user_ldap/l10n/bg.json +++ b/apps/user_ldap/l10n/bg.json @@ -58,6 +58,10 @@ "Your password will expire today." : "Вашата парола ще изтече днес.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Вашата парола ще изтече в рамките на %n дни.","Вашата парола ще изтече в рамките на %n дни."], "LDAP/AD integration" : "LDAP/AD интеграция", + "_%n group found_::_%n groups found_" : ["%n открити групи","%n открити групи"], + "> 1000 groups found" : "> 1000 открити групи", + "> 1000 users found" : "> 1000 намерени потребители", + "_%n user found_::_%n users found_" : ["%n намерени потребители","%n намерени потребители"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Не можа да се открие атрибут за показвано име на потребителя. Моля, посочете го сами в разширените настройки на LDAP.", "Could not find the desired feature" : "Не е открита желанта функция", "Invalid Host" : "Невалиден хост", diff --git a/apps/user_ldap/l10n/de_DE.js b/apps/user_ldap/l10n/de_DE.js index 85cfb957044..8434df1bb38 100644 --- a/apps/user_ldap/l10n/de_DE.js +++ b/apps/user_ldap/l10n/de_DE.js @@ -63,7 +63,7 @@ OC.L10N.register( "_%n group found_::_%n groups found_" : ["%n Gruppe gefunden","%n Gruppen gefunden"], "> 1000 groups found" : "Mehr als 1000 Gruppen gefunden", "> 1000 users found" : "Mehr als 1000 Benutzer gefunden", - "_%n user found_::_%n users found_" : ["%n Benutzer gefunden","%n Benugtzer gefunden"], + "_%n user found_::_%n users found_" : ["%n Benutzer gefunden","%n Benutzer gefunden"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Das Anzeigename-Attribut des Benutzers konnte nicht gefunden werden. Bitte geben Sie es selbst in den erweiterten LDAP-Einstellungen an.", "Could not find the desired feature" : "Die gewünschte Funktion konnte nicht gefunden werden", "Invalid Host" : "Ungültiger Host", @@ -119,7 +119,7 @@ OC.L10N.register( "Continue" : "Fortsetzen", "Please renew your password." : "Bitte erneuern Sie Ihr Passwort.", "An internal error occurred." : "Es ist ein interner Fehler aufgetreten.", - "Please try again or contact your administrator." : "Bitte versuchen Sie es noch einmal oder kontaktieren Sie Ihren Administrator.", + "Please try again or contact your administrator." : "Bitte erneut versuchen oder kontaktieren Sie Ihren Administrator.", "Current password" : "Aktuelles Passwort", "New password" : "Neues Passwort", "Renew password" : "Passwort erneuern", diff --git a/apps/user_ldap/l10n/de_DE.json b/apps/user_ldap/l10n/de_DE.json index 6461a70d68b..bae12576fa0 100644 --- a/apps/user_ldap/l10n/de_DE.json +++ b/apps/user_ldap/l10n/de_DE.json @@ -61,7 +61,7 @@ "_%n group found_::_%n groups found_" : ["%n Gruppe gefunden","%n Gruppen gefunden"], "> 1000 groups found" : "Mehr als 1000 Gruppen gefunden", "> 1000 users found" : "Mehr als 1000 Benutzer gefunden", - "_%n user found_::_%n users found_" : ["%n Benutzer gefunden","%n Benugtzer gefunden"], + "_%n user found_::_%n users found_" : ["%n Benutzer gefunden","%n Benutzer gefunden"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Das Anzeigename-Attribut des Benutzers konnte nicht gefunden werden. Bitte geben Sie es selbst in den erweiterten LDAP-Einstellungen an.", "Could not find the desired feature" : "Die gewünschte Funktion konnte nicht gefunden werden", "Invalid Host" : "Ungültiger Host", @@ -117,7 +117,7 @@ "Continue" : "Fortsetzen", "Please renew your password." : "Bitte erneuern Sie Ihr Passwort.", "An internal error occurred." : "Es ist ein interner Fehler aufgetreten.", - "Please try again or contact your administrator." : "Bitte versuchen Sie es noch einmal oder kontaktieren Sie Ihren Administrator.", + "Please try again or contact your administrator." : "Bitte erneut versuchen oder kontaktieren Sie Ihren Administrator.", "Current password" : "Aktuelles Passwort", "New password" : "Neues Passwort", "Renew password" : "Passwort erneuern", diff --git a/apps/user_ldap/l10n/el.js b/apps/user_ldap/l10n/el.js index b8373964041..fe02038dc07 100644 --- a/apps/user_ldap/l10n/el.js +++ b/apps/user_ldap/l10n/el.js @@ -60,6 +60,10 @@ OC.L10N.register( "Your password will expire today." : "Το συνθηματικό σας λήγει σήμερα.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Το συνθηματικό σας θα λήξει σε %n ημέρα.","Το συνθηματικό σας θα λήξει σε %n ημέρες."], "LDAP/AD integration" : "LDAP/AD ενσωμάτωση ", + "_%n group found_::_%n groups found_" : ["βρέθηκε %n ομάδα","βρέθηκαν %n ομάδες"], + "> 1000 groups found" : "Βρέθηκαν > 1000 ομάδες", + "> 1000 users found" : "Βρέθηκαν > 1000 χρήστες", + "_%n user found_::_%n users found_" : ["βρέθηκε %n χρήστης","βρέθηκαν %n χρήστες"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Δεν ήταν δυνατή η ανίχνευση της ιδιότητας του εμφανιζόμενου ονόματος χρήστη . Παρακαλώ προσδιορίστε στις προηγμένες ρυθμίσεις LDAP", "Could not find the desired feature" : "Αδυναμία εύρεσης επιθυμητού χαρακτηριστικού", "Invalid Host" : "Άκυρος εξυπηρετητής", @@ -178,6 +182,7 @@ OC.L10N.register( "\"$home\" Placeholder Field" : "\"$home\" Πεδίο Δέσμευσης", "$home in an external storage configuration will be replaced with the value of the specified attribute" : "Το $home σε μια ρύθμιση εξωτερικού χώρου αποθήκευσης θα αντικατασταθεί με την τιμή του καθορισμένου χαρακτηριστικού", "Internal Username" : "Εσωτερικό Όνομα Χρήστη", + "By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [a-zA-Z0-9_.@-]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all DAV services. With this setting, the default behavior can be overridden. Changes will have effect only on newly mapped (added) LDAP users. Leave it empty for default behavior." : "Από προεπιλογή, το εσωτερικό όνομα χρήστη θα δημιουργηθεί από το χαρακτηριστικό UUID. Αυτό διασφαλίζει ότι το όνομα χρήστη είναι μοναδικό και οι χαρακτήρες δεν χρειάζεται να μετατραπούν. Το εσωτερικό όνομα χρήστη έχει τον περιορισμό ότι επιτρέπονται μόνο αυτοί οι χαρακτήρες: [a-zA-Z0-9_.@-]. Άλλοι χαρακτήρες αντικαθίστανται με την ASCII αντιστοιχία τους ή απλώς παραλείπονται. Σε περίπτωση σύγκρουσης προστίθεται/αυξάνεται ένας αριθμός. Το εσωτερικό όνομα χρήστη χρησιμοποιείται για την εσωτερική αναγνώριση ενός χρήστη. Είναι επίσης το προεπιλεγμένο όνομα για τον αρχικό φάκελο του χρήστη. Αποτελεί επίσης μέρος των απομακρυσμένων διευθύνσεων URL, για παράδειγμα για όλες τις υπηρεσίες DAV. Με αυτή τη ρύθμιση, η προεπιλεγμένη συμπεριφορά μπορεί να παρακαμφθεί. Οι αλλαγές θα έχουν αποτέλεσμα μόνο σε πρόσφατα αντιστοιχισμένους (προστιθέμενους) χρήστες LDAP. Αφήστε το κενό για προεπιλεγμένη συμπεριφορά.", "Internal Username Attribute:" : "Ιδιότητα Εσωτερικού Ονόματος Χρήστη:", "Override UUID detection" : "Παράκαμψη ανίχνευσης UUID", "By default, the UUID attribute is automatically detected. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users and groups." : "Από προεπιλογή, το χαρακτηριστικό UUID εντοπίζεται αυτόματα. Το χαρακτηριστικό UUID χρησιμοποιείται για την αναγνώριση χωρίς αμφιβολία χρηστών και ομάδων LDAP. Επίσης, το εσωτερικό όνομα χρήστη θα δημιουργηθεί με βάση το UUID, εφόσον δεν ορίζεται διαφορετικά ανωτέρω. Μπορείτε να παρακάμψετε τη ρύθμιση και να ορίσετε ένα χαρακτηριστικό της επιλογής σας. Θα πρέπει να βεβαιωθείτε ότι το χαρακτηριστικό της επιλογής σας μπορεί να ληφθεί για τους χρήστες και τις ομάδες και ότι είναι μοναδικό. Αφήστε το κενό για την προεπιλεγμένη λειτουργία. Οι αλλαγές θα έχουν ισχύ μόνο σε πρόσφατα αντιστοιχισμένους (προστιθέμενους) χρήστες και ομάδες LDAP.", diff --git a/apps/user_ldap/l10n/el.json b/apps/user_ldap/l10n/el.json index d31d5ce0542..ef1fc78ddc6 100644 --- a/apps/user_ldap/l10n/el.json +++ b/apps/user_ldap/l10n/el.json @@ -58,6 +58,10 @@ "Your password will expire today." : "Το συνθηματικό σας λήγει σήμερα.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Το συνθηματικό σας θα λήξει σε %n ημέρα.","Το συνθηματικό σας θα λήξει σε %n ημέρες."], "LDAP/AD integration" : "LDAP/AD ενσωμάτωση ", + "_%n group found_::_%n groups found_" : ["βρέθηκε %n ομάδα","βρέθηκαν %n ομάδες"], + "> 1000 groups found" : "Βρέθηκαν > 1000 ομάδες", + "> 1000 users found" : "Βρέθηκαν > 1000 χρήστες", + "_%n user found_::_%n users found_" : ["βρέθηκε %n χρήστης","βρέθηκαν %n χρήστες"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Δεν ήταν δυνατή η ανίχνευση της ιδιότητας του εμφανιζόμενου ονόματος χρήστη . Παρακαλώ προσδιορίστε στις προηγμένες ρυθμίσεις LDAP", "Could not find the desired feature" : "Αδυναμία εύρεσης επιθυμητού χαρακτηριστικού", "Invalid Host" : "Άκυρος εξυπηρετητής", @@ -176,6 +180,7 @@ "\"$home\" Placeholder Field" : "\"$home\" Πεδίο Δέσμευσης", "$home in an external storage configuration will be replaced with the value of the specified attribute" : "Το $home σε μια ρύθμιση εξωτερικού χώρου αποθήκευσης θα αντικατασταθεί με την τιμή του καθορισμένου χαρακτηριστικού", "Internal Username" : "Εσωτερικό Όνομα Χρήστη", + "By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [a-zA-Z0-9_.@-]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all DAV services. With this setting, the default behavior can be overridden. Changes will have effect only on newly mapped (added) LDAP users. Leave it empty for default behavior." : "Από προεπιλογή, το εσωτερικό όνομα χρήστη θα δημιουργηθεί από το χαρακτηριστικό UUID. Αυτό διασφαλίζει ότι το όνομα χρήστη είναι μοναδικό και οι χαρακτήρες δεν χρειάζεται να μετατραπούν. Το εσωτερικό όνομα χρήστη έχει τον περιορισμό ότι επιτρέπονται μόνο αυτοί οι χαρακτήρες: [a-zA-Z0-9_.@-]. Άλλοι χαρακτήρες αντικαθίστανται με την ASCII αντιστοιχία τους ή απλώς παραλείπονται. Σε περίπτωση σύγκρουσης προστίθεται/αυξάνεται ένας αριθμός. Το εσωτερικό όνομα χρήστη χρησιμοποιείται για την εσωτερική αναγνώριση ενός χρήστη. Είναι επίσης το προεπιλεγμένο όνομα για τον αρχικό φάκελο του χρήστη. Αποτελεί επίσης μέρος των απομακρυσμένων διευθύνσεων URL, για παράδειγμα για όλες τις υπηρεσίες DAV. Με αυτή τη ρύθμιση, η προεπιλεγμένη συμπεριφορά μπορεί να παρακαμφθεί. Οι αλλαγές θα έχουν αποτέλεσμα μόνο σε πρόσφατα αντιστοιχισμένους (προστιθέμενους) χρήστες LDAP. Αφήστε το κενό για προεπιλεγμένη συμπεριφορά.", "Internal Username Attribute:" : "Ιδιότητα Εσωτερικού Ονόματος Χρήστη:", "Override UUID detection" : "Παράκαμψη ανίχνευσης UUID", "By default, the UUID attribute is automatically detected. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users and groups." : "Από προεπιλογή, το χαρακτηριστικό UUID εντοπίζεται αυτόματα. Το χαρακτηριστικό UUID χρησιμοποιείται για την αναγνώριση χωρίς αμφιβολία χρηστών και ομάδων LDAP. Επίσης, το εσωτερικό όνομα χρήστη θα δημιουργηθεί με βάση το UUID, εφόσον δεν ορίζεται διαφορετικά ανωτέρω. Μπορείτε να παρακάμψετε τη ρύθμιση και να ορίσετε ένα χαρακτηριστικό της επιλογής σας. Θα πρέπει να βεβαιωθείτε ότι το χαρακτηριστικό της επιλογής σας μπορεί να ληφθεί για τους χρήστες και τις ομάδες και ότι είναι μοναδικό. Αφήστε το κενό για την προεπιλεγμένη λειτουργία. Οι αλλαγές θα έχουν ισχύ μόνο σε πρόσφατα αντιστοιχισμένους (προστιθέμενους) χρήστες και ομάδες LDAP.", diff --git a/apps/user_ldap/l10n/es.js b/apps/user_ldap/l10n/es.js index be62c0e646a..8821ad3ac5e 100644 --- a/apps/user_ldap/l10n/es.js +++ b/apps/user_ldap/l10n/es.js @@ -60,6 +60,10 @@ OC.L10N.register( "Your password will expire today." : "Tu contraseña caducará hoy.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Tu contraseña caducará dentro de %n día.","Tu contraseña caducará dentro de %n días.","Tu contraseña caducará dentro de %n días."], "LDAP/AD integration" : "Integración LDAP/AD", + "_%n group found_::_%n groups found_" : ["%n grupo encontrado","%n grupos encontrados","%n grupos encontrados"], + "> 1000 groups found" : "Se encontraron más de 1000 grupos", + "> 1000 users found" : "Se encontraron más de 1000 usuarios", + "_%n user found_::_%n users found_" : ["%n usuario encontrado ","%n usuarios encontrados","%n usuarios encontrados"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "No se ha podido detectar el atributo del nombre ", "Could not find the desired feature" : "No se puede encontrar la función deseada.", "Invalid Host" : "Host no válido", @@ -162,7 +166,7 @@ OC.L10N.register( "Paging chunksize" : "Tamaño de los fragmentos de paginación", "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.)" : "Tamaño de los fragmentos usado para búsquedas LDAP paginadas que pueden devolver resultados voluminosos, como enumeración de usuarios o de grupos. (Si se establece en 0, se deshabilitan las búsquedas LDAP paginadas en esas situaciones.)", "Enable LDAP password changes per user" : "Permitir cambios de contraseñas LDAP por usuario", - "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." : "Permite a usuarios LDAP cambiar su contraseña y permite administradores y administradores de grupos, cambiar la contraseña de sus usuarios LDAP. SOlo funciona cuando las políticas de control de acceso están configuradas de acuerdo a las del servidor LDAP. Como las contraseñas se mandan en texto plano al servidor, LDAP, encripción del transporte debe ser usado y cifrado de las contraseñas debe ser configurado en el servidor LDAP.", + "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." : "Permite a usuarios LDAP cambiar su contraseña y permite administradores y administradores de grupos, cambiar la contraseña de sus usuarios LDAP. Solo funciona cuando las políticas de control de acceso están configuradas de acuerdo a las del servidor LDAP. Como las contraseñas se mandan en texto plano al servidor, LDAP, encripción del transporte debe ser usado y cifrado de las contraseñas debe ser configurado en el servidor LDAP.", "(New password is sent as plain text to LDAP)" : "(La nueva contraseña se envía como texto plano a LDAP)", "Default password policy DN" : "Política de contraseñas por defecto DN", "The DN of a default password policy that will be used for password expiry handling. Works only when LDAP password changes per user are enabled and is only supported by OpenLDAP. Leave empty to disable password expiry handling." : "El DN de una política de contraseñas por defecto que será usado para el manejo de la expiración de contraseñas. Solo funciona cuando los cambios por usuario de la contraseña LDAP están habilitados y solo está aceptada por OpenLDAP. Déjala vacía para deshabilitar el manejo de expiración de contraseñas.", diff --git a/apps/user_ldap/l10n/es.json b/apps/user_ldap/l10n/es.json index 5714162cc28..1d867cae117 100644 --- a/apps/user_ldap/l10n/es.json +++ b/apps/user_ldap/l10n/es.json @@ -58,6 +58,10 @@ "Your password will expire today." : "Tu contraseña caducará hoy.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Tu contraseña caducará dentro de %n día.","Tu contraseña caducará dentro de %n días.","Tu contraseña caducará dentro de %n días."], "LDAP/AD integration" : "Integración LDAP/AD", + "_%n group found_::_%n groups found_" : ["%n grupo encontrado","%n grupos encontrados","%n grupos encontrados"], + "> 1000 groups found" : "Se encontraron más de 1000 grupos", + "> 1000 users found" : "Se encontraron más de 1000 usuarios", + "_%n user found_::_%n users found_" : ["%n usuario encontrado ","%n usuarios encontrados","%n usuarios encontrados"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "No se ha podido detectar el atributo del nombre ", "Could not find the desired feature" : "No se puede encontrar la función deseada.", "Invalid Host" : "Host no válido", @@ -160,7 +164,7 @@ "Paging chunksize" : "Tamaño de los fragmentos de paginación", "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.)" : "Tamaño de los fragmentos usado para búsquedas LDAP paginadas que pueden devolver resultados voluminosos, como enumeración de usuarios o de grupos. (Si se establece en 0, se deshabilitan las búsquedas LDAP paginadas en esas situaciones.)", "Enable LDAP password changes per user" : "Permitir cambios de contraseñas LDAP por usuario", - "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." : "Permite a usuarios LDAP cambiar su contraseña y permite administradores y administradores de grupos, cambiar la contraseña de sus usuarios LDAP. SOlo funciona cuando las políticas de control de acceso están configuradas de acuerdo a las del servidor LDAP. Como las contraseñas se mandan en texto plano al servidor, LDAP, encripción del transporte debe ser usado y cifrado de las contraseñas debe ser configurado en el servidor LDAP.", + "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." : "Permite a usuarios LDAP cambiar su contraseña y permite administradores y administradores de grupos, cambiar la contraseña de sus usuarios LDAP. Solo funciona cuando las políticas de control de acceso están configuradas de acuerdo a las del servidor LDAP. Como las contraseñas se mandan en texto plano al servidor, LDAP, encripción del transporte debe ser usado y cifrado de las contraseñas debe ser configurado en el servidor LDAP.", "(New password is sent as plain text to LDAP)" : "(La nueva contraseña se envía como texto plano a LDAP)", "Default password policy DN" : "Política de contraseñas por defecto DN", "The DN of a default password policy that will be used for password expiry handling. Works only when LDAP password changes per user are enabled and is only supported by OpenLDAP. Leave empty to disable password expiry handling." : "El DN de una política de contraseñas por defecto que será usado para el manejo de la expiración de contraseñas. Solo funciona cuando los cambios por usuario de la contraseña LDAP están habilitados y solo está aceptada por OpenLDAP. Déjala vacía para deshabilitar el manejo de expiración de contraseñas.", diff --git a/apps/user_ldap/l10n/eu.js b/apps/user_ldap/l10n/eu.js index d7839de8ab9..75ee83efc2e 100644 --- a/apps/user_ldap/l10n/eu.js +++ b/apps/user_ldap/l10n/eu.js @@ -60,6 +60,10 @@ OC.L10N.register( "Your password will expire today." : "Zure pasahitza gaur iraungiko da.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Zure pasahitza egun %nean iraungiko da.","Zure pasahitza %n egunetan iraungiko da."], "LDAP/AD integration" : "LDAP/AD integrazioa", + "_%n group found_::_%n groups found_" : ["Talde %n aurkitu da","%n talde aurkitu dira"], + "> 1000 groups found" : "> 1000 talde aurkitu dira", + "> 1000 users found" : "> 1000 erabiltzaile aurkitu dira", + "_%n user found_::_%n users found_" : ["Erabiltzaile %n aurkitu da","%n erabiltzaile aurkitu dira"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Ezin izan da antzeman erabiltzailearen bistaratze izenaren atributua. Mesedez, zehaztu zeure burua LDAP ezarpen aurreratuetan.", "Could not find the desired feature" : "Ezin izan da nahi zen ezaugarria aurkitu", "Invalid Host" : "Baliogabeko ostalaria", diff --git a/apps/user_ldap/l10n/eu.json b/apps/user_ldap/l10n/eu.json index af878fce83e..0744358c6a9 100644 --- a/apps/user_ldap/l10n/eu.json +++ b/apps/user_ldap/l10n/eu.json @@ -58,6 +58,10 @@ "Your password will expire today." : "Zure pasahitza gaur iraungiko da.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Zure pasahitza egun %nean iraungiko da.","Zure pasahitza %n egunetan iraungiko da."], "LDAP/AD integration" : "LDAP/AD integrazioa", + "_%n group found_::_%n groups found_" : ["Talde %n aurkitu da","%n talde aurkitu dira"], + "> 1000 groups found" : "> 1000 talde aurkitu dira", + "> 1000 users found" : "> 1000 erabiltzaile aurkitu dira", + "_%n user found_::_%n users found_" : ["Erabiltzaile %n aurkitu da","%n erabiltzaile aurkitu dira"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Ezin izan da antzeman erabiltzailearen bistaratze izenaren atributua. Mesedez, zehaztu zeure burua LDAP ezarpen aurreratuetan.", "Could not find the desired feature" : "Ezin izan da nahi zen ezaugarria aurkitu", "Invalid Host" : "Baliogabeko ostalaria", diff --git a/apps/user_ldap/l10n/fr.js b/apps/user_ldap/l10n/fr.js index 15b83120b8e..e5be5be3bde 100644 --- a/apps/user_ldap/l10n/fr.js +++ b/apps/user_ldap/l10n/fr.js @@ -60,17 +60,19 @@ OC.L10N.register( "Your password will expire today." : "Votre mot de passe va expirer aujourd'hui.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Votre mot de passe va expirer dans %n jour.","Votre mot de passe va expirer dans %n jours.","Votre mot de passe va expirer dans %n jours."], "LDAP/AD integration" : "Integration LDAP/AD ", + "_%n group found_::_%n groups found_" : ["%n groupe trouvé","%n groupes trouvés","%n groupes trouvés"], + "_%n user found_::_%n users found_" : ["%n utilisateur trouvé","%n utilisateurs trouvés","%n utilisateurs trouvés"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Impossible de détecter l'attribut contenant le nom d'affichage des utilisateurs. Veuillez l'indiquer vous-même dans les paramètres LDAP avancés.", "Could not find the desired feature" : "Impossible de trouver la fonction souhaitée", "Invalid Host" : "Hôte non valide", "LDAP user and group backend" : "Utilisateur LDAP et infrastructure de groupe", "This application enables administrators to connect Nextcloud to an LDAP-based user directory." : "Cette application autorise les administrateurs à connecter Nextcloud à un annuaire LDAP.", - "This application enables administrators to connect Nextcloud to an LDAP-based user directory for authentication and provisioning users, groups and user attributes. Admins can configure this application to connect to one or more LDAP directories or Active Directories via an LDAP interface. Attributes such as user quota, email, avatar pictures, group memberships and more can be pulled into Nextcloud from a directory with the appropriate queries and filters.\n\nA user logs into Nextcloud with their LDAP or AD credentials, and is granted access based on an authentication request handled by the LDAP or AD server. Nextcloud does not store LDAP or AD passwords, rather these credentials are used to authenticate a user and then Nextcloud uses a session for the user ID. More information is available in the LDAP User and Group Backend documentation." : "Cette application permet aux administrateurs de connecter Nextcloud à un répertoire d'utilisateurs LDAP pour l'authentification et le provisionnement des utilisateurs, groupes et des attributs d'utilisateurs. Les administrateurs peuvent configurer cette application pour se connecter à un ou plusieurs répertoires LDAP ou Active Directories via une interface LDAP. Les attributs tels que le quota utilisateur, l'email, les images d'avatar, les adhésions de groupe et plus peuvent être envoyés sur Nextcloud à partir d'un répertoire avec les requêtes et les filtres appropriés.\n\nUn utilisateur se connecte à Nextcloud avec ses identifiants LDAP ou AD et obtient l'accès sur la base d'une demande d'authentification gérée par le serveur LDAP ou AD. Nextcloud ne stocke pas les mots de passe LDAP ou AD, mais ces informations d'identification sont utilisées pour authentifier un utilisateur et Nextcloud utilise ensuite une session pour l'ID utilisateur. De plus amples informations sont disponibles dans la documentation LDAP User and Group Backend.", + "This application enables administrators to connect Nextcloud to an LDAP-based user directory for authentication and provisioning users, groups and user attributes. Admins can configure this application to connect to one or more LDAP directories or Active Directories via an LDAP interface. Attributes such as user quota, email, avatar pictures, group memberships and more can be pulled into Nextcloud from a directory with the appropriate queries and filters.\n\nA user logs into Nextcloud with their LDAP or AD credentials, and is granted access based on an authentication request handled by the LDAP or AD server. Nextcloud does not store LDAP or AD passwords, rather these credentials are used to authenticate a user and then Nextcloud uses a session for the user ID. More information is available in the LDAP User and Group Backend documentation." : "Cette application permet aux administrateurs de connecter Nextcloud à un répertoire d'utilisateurs LDAP pour l'authentification et le provisionnement des utilisateurs, groupes et des attributs d'utilisateurs. Les administrateurs peuvent configurer cette application pour se connecter à un ou plusieurs répertoires LDAP ou Active Directories via une interface LDAP. Les attributs tels que le quota utilisateur, l'e-mail, les images d'avatar, les adhésions de groupe et plus peuvent être envoyés sur Nextcloud à partir d'un répertoire avec les requêtes et les filtres appropriés.\n\nUn utilisateur se connecte à Nextcloud avec ses identifiants LDAP ou AD et obtient l'accès sur la base d'une demande d'authentification gérée par le serveur LDAP ou AD. Nextcloud ne stocke pas les mots de passe LDAP ou AD, mais ces informations d'identification sont utilisées pour authentifier un utilisateur et Nextcloud utilise ensuite une session pour l'ID utilisateur. De plus amples informations sont disponibles dans la documentation LDAP User and Group Backend.", "Test Configuration" : "Tester la configuration", "Help" : "Aide", "Groups meeting these criteria are available in %s:" : "Les groupes respectant ces critères sont disponibles dans %s :", "Only these object classes:" : "Seulement ces classes d'objets :", - "Only from these groups:" : "Seulement dans ces groupes :", + "Only from these groups:" : "Seulement dans ces groupes :", "Search groups" : "Chercher dans les groupes", "Available groups" : "Groupes disponibles", "Selected groups" : "Groupes sélectionnés", @@ -171,7 +173,7 @@ OC.L10N.register( "Leave empty for user's default quota. Otherwise, specify an LDAP/AD attribute." : "Laissez vide pour appliquer le quota par défaut de l'utilisateur. Sinon, spécifiez un attribut LDAP / AD.", "Quota Default" : "Quota par défaut", "Override default quota for LDAP users who do not have a quota set in the Quota Field." : "Remplacez le quota par défaut des utilisateurs LDAP qui ne disposent pas d'un quota dans le champ Quota.", - "Email Field" : "Champ Email", + "Email Field" : "Champ E-mail", "Set the user's email from their LDAP attribute. Leave it empty for default behaviour." : "Définissez le courrier électronique de l'utilisateur à partir de leur attribut LDAP. Laissez le champ vide pour appliquer le comportement par défaut.", "User Home Folder Naming Rule" : "Règle de nommage du répertoire utilisateur", "Leave empty for username (default). Otherwise, specify an LDAP/AD attribute." : "Laisser vide pour le nom d’utilisateur (par défaut). Sinon, spécifiez un attribut LDAP/AD.", diff --git a/apps/user_ldap/l10n/fr.json b/apps/user_ldap/l10n/fr.json index 2ef8e201750..8bc527b933b 100644 --- a/apps/user_ldap/l10n/fr.json +++ b/apps/user_ldap/l10n/fr.json @@ -58,17 +58,19 @@ "Your password will expire today." : "Votre mot de passe va expirer aujourd'hui.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Votre mot de passe va expirer dans %n jour.","Votre mot de passe va expirer dans %n jours.","Votre mot de passe va expirer dans %n jours."], "LDAP/AD integration" : "Integration LDAP/AD ", + "_%n group found_::_%n groups found_" : ["%n groupe trouvé","%n groupes trouvés","%n groupes trouvés"], + "_%n user found_::_%n users found_" : ["%n utilisateur trouvé","%n utilisateurs trouvés","%n utilisateurs trouvés"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Impossible de détecter l'attribut contenant le nom d'affichage des utilisateurs. Veuillez l'indiquer vous-même dans les paramètres LDAP avancés.", "Could not find the desired feature" : "Impossible de trouver la fonction souhaitée", "Invalid Host" : "Hôte non valide", "LDAP user and group backend" : "Utilisateur LDAP et infrastructure de groupe", "This application enables administrators to connect Nextcloud to an LDAP-based user directory." : "Cette application autorise les administrateurs à connecter Nextcloud à un annuaire LDAP.", - "This application enables administrators to connect Nextcloud to an LDAP-based user directory for authentication and provisioning users, groups and user attributes. Admins can configure this application to connect to one or more LDAP directories or Active Directories via an LDAP interface. Attributes such as user quota, email, avatar pictures, group memberships and more can be pulled into Nextcloud from a directory with the appropriate queries and filters.\n\nA user logs into Nextcloud with their LDAP or AD credentials, and is granted access based on an authentication request handled by the LDAP or AD server. Nextcloud does not store LDAP or AD passwords, rather these credentials are used to authenticate a user and then Nextcloud uses a session for the user ID. More information is available in the LDAP User and Group Backend documentation." : "Cette application permet aux administrateurs de connecter Nextcloud à un répertoire d'utilisateurs LDAP pour l'authentification et le provisionnement des utilisateurs, groupes et des attributs d'utilisateurs. Les administrateurs peuvent configurer cette application pour se connecter à un ou plusieurs répertoires LDAP ou Active Directories via une interface LDAP. Les attributs tels que le quota utilisateur, l'email, les images d'avatar, les adhésions de groupe et plus peuvent être envoyés sur Nextcloud à partir d'un répertoire avec les requêtes et les filtres appropriés.\n\nUn utilisateur se connecte à Nextcloud avec ses identifiants LDAP ou AD et obtient l'accès sur la base d'une demande d'authentification gérée par le serveur LDAP ou AD. Nextcloud ne stocke pas les mots de passe LDAP ou AD, mais ces informations d'identification sont utilisées pour authentifier un utilisateur et Nextcloud utilise ensuite une session pour l'ID utilisateur. De plus amples informations sont disponibles dans la documentation LDAP User and Group Backend.", + "This application enables administrators to connect Nextcloud to an LDAP-based user directory for authentication and provisioning users, groups and user attributes. Admins can configure this application to connect to one or more LDAP directories or Active Directories via an LDAP interface. Attributes such as user quota, email, avatar pictures, group memberships and more can be pulled into Nextcloud from a directory with the appropriate queries and filters.\n\nA user logs into Nextcloud with their LDAP or AD credentials, and is granted access based on an authentication request handled by the LDAP or AD server. Nextcloud does not store LDAP or AD passwords, rather these credentials are used to authenticate a user and then Nextcloud uses a session for the user ID. More information is available in the LDAP User and Group Backend documentation." : "Cette application permet aux administrateurs de connecter Nextcloud à un répertoire d'utilisateurs LDAP pour l'authentification et le provisionnement des utilisateurs, groupes et des attributs d'utilisateurs. Les administrateurs peuvent configurer cette application pour se connecter à un ou plusieurs répertoires LDAP ou Active Directories via une interface LDAP. Les attributs tels que le quota utilisateur, l'e-mail, les images d'avatar, les adhésions de groupe et plus peuvent être envoyés sur Nextcloud à partir d'un répertoire avec les requêtes et les filtres appropriés.\n\nUn utilisateur se connecte à Nextcloud avec ses identifiants LDAP ou AD et obtient l'accès sur la base d'une demande d'authentification gérée par le serveur LDAP ou AD. Nextcloud ne stocke pas les mots de passe LDAP ou AD, mais ces informations d'identification sont utilisées pour authentifier un utilisateur et Nextcloud utilise ensuite une session pour l'ID utilisateur. De plus amples informations sont disponibles dans la documentation LDAP User and Group Backend.", "Test Configuration" : "Tester la configuration", "Help" : "Aide", "Groups meeting these criteria are available in %s:" : "Les groupes respectant ces critères sont disponibles dans %s :", "Only these object classes:" : "Seulement ces classes d'objets :", - "Only from these groups:" : "Seulement dans ces groupes :", + "Only from these groups:" : "Seulement dans ces groupes :", "Search groups" : "Chercher dans les groupes", "Available groups" : "Groupes disponibles", "Selected groups" : "Groupes sélectionnés", @@ -169,7 +171,7 @@ "Leave empty for user's default quota. Otherwise, specify an LDAP/AD attribute." : "Laissez vide pour appliquer le quota par défaut de l'utilisateur. Sinon, spécifiez un attribut LDAP / AD.", "Quota Default" : "Quota par défaut", "Override default quota for LDAP users who do not have a quota set in the Quota Field." : "Remplacez le quota par défaut des utilisateurs LDAP qui ne disposent pas d'un quota dans le champ Quota.", - "Email Field" : "Champ Email", + "Email Field" : "Champ E-mail", "Set the user's email from their LDAP attribute. Leave it empty for default behaviour." : "Définissez le courrier électronique de l'utilisateur à partir de leur attribut LDAP. Laissez le champ vide pour appliquer le comportement par défaut.", "User Home Folder Naming Rule" : "Règle de nommage du répertoire utilisateur", "Leave empty for username (default). Otherwise, specify an LDAP/AD attribute." : "Laisser vide pour le nom d’utilisateur (par défaut). Sinon, spécifiez un attribut LDAP/AD.", diff --git a/apps/user_ldap/l10n/ja.js b/apps/user_ldap/l10n/ja.js index 03a83d83a35..7f8455f54b8 100644 --- a/apps/user_ldap/l10n/ja.js +++ b/apps/user_ldap/l10n/ja.js @@ -60,6 +60,10 @@ OC.L10N.register( "Your password will expire today." : "パスワードが今日期限切れになります。", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["パスワードがあと %n日で期限切れになります。"], "LDAP/AD integration" : "LDAP/AD統合", + "_%n group found_::_%n groups found_" : ["グループ%nが見つかりました "], + "> 1000 groups found" : "1000 以上のグループが見つかりました", + "> 1000 users found" : "1000 以上のユーザーが見つかりました", + "_%n user found_::_%n users found_" : ["ユーザー%n が見つかりました"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "ユーザー表示名の属性を検出できませんでした。詳細設定で対応する属性を指定してください。", "Could not find the desired feature" : "望ましい機能は見つかりませんでした", "Invalid Host" : "無効なホスト", diff --git a/apps/user_ldap/l10n/ja.json b/apps/user_ldap/l10n/ja.json index bae170334bb..a177306a5ce 100644 --- a/apps/user_ldap/l10n/ja.json +++ b/apps/user_ldap/l10n/ja.json @@ -58,6 +58,10 @@ "Your password will expire today." : "パスワードが今日期限切れになります。", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["パスワードがあと %n日で期限切れになります。"], "LDAP/AD integration" : "LDAP/AD統合", + "_%n group found_::_%n groups found_" : ["グループ%nが見つかりました "], + "> 1000 groups found" : "1000 以上のグループが見つかりました", + "> 1000 users found" : "1000 以上のユーザーが見つかりました", + "_%n user found_::_%n users found_" : ["ユーザー%n が見つかりました"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "ユーザー表示名の属性を検出できませんでした。詳細設定で対応する属性を指定してください。", "Could not find the desired feature" : "望ましい機能は見つかりませんでした", "Invalid Host" : "無効なホスト", diff --git a/apps/user_ldap/l10n/ko.js b/apps/user_ldap/l10n/ko.js index 9d76222b03a..95edb9e02ec 100644 --- a/apps/user_ldap/l10n/ko.js +++ b/apps/user_ldap/l10n/ko.js @@ -182,6 +182,7 @@ OC.L10N.register( "UUID Attribute for Users:" : "사용자 UUID 속성:", "UUID Attribute for Groups:" : "그룹 UUID 속성:", "Username-LDAP User Mapping" : "사용자 이름-LDAP 사용자 매핑", + "Usernames are used to store and assign metadata. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage." : "사용자 이름은 메타데이터를 저장하고 할당하는 데 사용됩니다. 사용자를 정확히 식별하기 위해서 모든 LDAP 사용자는 내부 사용자 이름을 갖고 있습니다. 이 정보에 접근하려면 사용자 이름과 LDAP 사용자 사이의 연결을 알아야 합니다. 생성된 사용자 이름은 LDAP 사용자의 UUID에 연결됩니다. LDAP에 연결하는 횟수를 줄이기 위하여 DN을 캐시에 저장하지만, 식별에는 사용하지 않습니다. DN이 변경되었을 때 변경 사항이 적용됩니다. 내부 사용자 이름은 항상 사용됩니다. 매핑을 비우면 과거 매핑의 흔적이 남습니다. 매핑을 비우는 것은 설정에 관계 없이 적용되므로 모든 LDAP 설정에 영향을 줍니다! 테스트 및 실험 단계에서만 매핑을 비우고, 상용 환경에서는 매핑을 비우지 마십시오.", "Clear Username-LDAP User Mapping" : "사용자 이름-LDAP 사용자 매핑 비우기", "Clear Groupname-LDAP Group Mapping" : "그룹 이름-LDAP 그룹 매핑 비우기" }, diff --git a/apps/user_ldap/l10n/ko.json b/apps/user_ldap/l10n/ko.json index b380db2d0b5..fccac4895e7 100644 --- a/apps/user_ldap/l10n/ko.json +++ b/apps/user_ldap/l10n/ko.json @@ -180,6 +180,7 @@ "UUID Attribute for Users:" : "사용자 UUID 속성:", "UUID Attribute for Groups:" : "그룹 UUID 속성:", "Username-LDAP User Mapping" : "사용자 이름-LDAP 사용자 매핑", + "Usernames are used to store and assign metadata. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage." : "사용자 이름은 메타데이터를 저장하고 할당하는 데 사용됩니다. 사용자를 정확히 식별하기 위해서 모든 LDAP 사용자는 내부 사용자 이름을 갖고 있습니다. 이 정보에 접근하려면 사용자 이름과 LDAP 사용자 사이의 연결을 알아야 합니다. 생성된 사용자 이름은 LDAP 사용자의 UUID에 연결됩니다. LDAP에 연결하는 횟수를 줄이기 위하여 DN을 캐시에 저장하지만, 식별에는 사용하지 않습니다. DN이 변경되었을 때 변경 사항이 적용됩니다. 내부 사용자 이름은 항상 사용됩니다. 매핑을 비우면 과거 매핑의 흔적이 남습니다. 매핑을 비우는 것은 설정에 관계 없이 적용되므로 모든 LDAP 설정에 영향을 줍니다! 테스트 및 실험 단계에서만 매핑을 비우고, 상용 환경에서는 매핑을 비우지 마십시오.", "Clear Username-LDAP User Mapping" : "사용자 이름-LDAP 사용자 매핑 비우기", "Clear Groupname-LDAP Group Mapping" : "그룹 이름-LDAP 그룹 매핑 비우기" },"pluralForm" :"nplurals=1; plural=0;" diff --git a/apps/user_ldap/l10n/lv.js b/apps/user_ldap/l10n/lv.js index 61eb1d75c98..2c623018a21 100644 --- a/apps/user_ldap/l10n/lv.js +++ b/apps/user_ldap/l10n/lv.js @@ -45,6 +45,7 @@ OC.L10N.register( "Selected groups" : "Izvēlētās grupas", "Edit LDAP Query" : "Labot LDAP vaicājumu", "LDAP Filter:" : "LDAP filtrs:", + "Verify settings and count the groups" : "Pārbaudiet iestatījumus un saskaitiet grupas", "Other Attributes:" : "Citi atribūti:", "Test Loginname" : "Pārbaudiet lietotājvārdu", "Verify settings" : "Pārbaudīt iestatījumus", @@ -60,6 +61,7 @@ OC.L10N.register( "You can specify Base DN for users and groups in the Advanced tab" : "Lietotājiem un grupām var norādīt bāzes DN cilnē “Paplašināti”", "Detect Base DN" : "Noteikt bāzes DN", "Test Base DN" : "Testēt bāzes DN", + "Verify settings and count users" : "Pārbaudiet iestatījumus un saskaitiet lietotājus", "Saving" : "Saglabā", "Back" : "Atpakaļ", "Continue" : "Turpināt", diff --git a/apps/user_ldap/l10n/lv.json b/apps/user_ldap/l10n/lv.json index be82e0717dc..fdebc4b2287 100644 --- a/apps/user_ldap/l10n/lv.json +++ b/apps/user_ldap/l10n/lv.json @@ -43,6 +43,7 @@ "Selected groups" : "Izvēlētās grupas", "Edit LDAP Query" : "Labot LDAP vaicājumu", "LDAP Filter:" : "LDAP filtrs:", + "Verify settings and count the groups" : "Pārbaudiet iestatījumus un saskaitiet grupas", "Other Attributes:" : "Citi atribūti:", "Test Loginname" : "Pārbaudiet lietotājvārdu", "Verify settings" : "Pārbaudīt iestatījumus", @@ -58,6 +59,7 @@ "You can specify Base DN for users and groups in the Advanced tab" : "Lietotājiem un grupām var norādīt bāzes DN cilnē “Paplašināti”", "Detect Base DN" : "Noteikt bāzes DN", "Test Base DN" : "Testēt bāzes DN", + "Verify settings and count users" : "Pārbaudiet iestatījumus un saskaitiet lietotājus", "Saving" : "Saglabā", "Back" : "Atpakaļ", "Continue" : "Turpināt", diff --git a/apps/user_ldap/l10n/pt_BR.js b/apps/user_ldap/l10n/pt_BR.js index af156e526f2..17ab6885797 100644 --- a/apps/user_ldap/l10n/pt_BR.js +++ b/apps/user_ldap/l10n/pt_BR.js @@ -60,6 +60,10 @@ OC.L10N.register( "Your password will expire today." : "Sua senha vai expirar hoje.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Sua senha vai expirar dentro de%n dia.","Sua senha vai expirar dentro de%ndias.","Sua senha vai expirar dentro de%ndias."], "LDAP/AD integration" : "LDAP/AD integração", + "_%n group found_::_%n groups found_" : ["%n grupo encontrado","%n grupo encontrado","%n grupos encontrados"], + "> 1000 groups found" : ">1000 grupos encontrados", + "> 1000 users found" : ">1000 usuários encontrados", + "_%n user found_::_%n users found_" : ["%n > 1000 grupos encontrados","%n usuário encontrado","%n usuários encontrados"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Não foi possível detectar o atributo do nome de exibição do usuário. Por favor, especifique-o você mesmo nas configurações LDAP avançadas.", "Could not find the desired feature" : "Não foi possível encontrar o recurso desejado", "Invalid Host" : "Host inválido", diff --git a/apps/user_ldap/l10n/pt_BR.json b/apps/user_ldap/l10n/pt_BR.json index 128c6d9e653..1471118c394 100644 --- a/apps/user_ldap/l10n/pt_BR.json +++ b/apps/user_ldap/l10n/pt_BR.json @@ -58,6 +58,10 @@ "Your password will expire today." : "Sua senha vai expirar hoje.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Sua senha vai expirar dentro de%n dia.","Sua senha vai expirar dentro de%ndias.","Sua senha vai expirar dentro de%ndias."], "LDAP/AD integration" : "LDAP/AD integração", + "_%n group found_::_%n groups found_" : ["%n grupo encontrado","%n grupo encontrado","%n grupos encontrados"], + "> 1000 groups found" : ">1000 grupos encontrados", + "> 1000 users found" : ">1000 usuários encontrados", + "_%n user found_::_%n users found_" : ["%n > 1000 grupos encontrados","%n usuário encontrado","%n usuários encontrados"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Não foi possível detectar o atributo do nome de exibição do usuário. Por favor, especifique-o você mesmo nas configurações LDAP avançadas.", "Could not find the desired feature" : "Não foi possível encontrar o recurso desejado", "Invalid Host" : "Host inválido", diff --git a/apps/user_ldap/l10n/sk.js b/apps/user_ldap/l10n/sk.js index 7e020dfe5e7..7912014a26d 100644 --- a/apps/user_ldap/l10n/sk.js +++ b/apps/user_ldap/l10n/sk.js @@ -60,6 +60,10 @@ OC.L10N.register( "Your password will expire today." : "Vaše heslo expiruje dnes.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Vaše heslo expiruje o %n deň.","Vaše heslo expiruje o %n dni.","Vaše heslo expiruje o %n dní.","Vaše heslo expiruje o %n dní."], "LDAP/AD integration" : "Integrácia s LDAP/AD", + "_%n group found_::_%n groups found_" : ["%n nájdená skupina","%n nájdené skupiny","%n nájdených skupín","%n nájdených skupín"], + "> 1000 groups found" : "> 1000 nájdených skupín", + "> 1000 users found" : "> 1000 nájdených užívateľov", + "_%n user found_::_%n users found_" : ["%n nájdený užívateľ","%n nájdený užívatelia","%n nájdených užívateľov","%n nájdených užívateľov"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Nepodarilo sa zistiť atribút pre zobrazenie mena používateľa. Zadajte ho sami v rozšírených nastaveniach LDAP.", "Could not find the desired feature" : "Nemožno nájsť požadovanú funkciu", "Invalid Host" : "Neplatný hostiteľ", @@ -178,6 +182,7 @@ OC.L10N.register( "\"$home\" Placeholder Field" : "Výplňová kolónka „$home“", "$home in an external storage configuration will be replaced with the value of the specified attribute" : "$ home bude v nastavení externého úložiska nahradená hodnotou zadaného atribútu ", "Internal Username" : "Interné používateľské meno", + "By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [a-zA-Z0-9_.@-]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all DAV services. With this setting, the default behavior can be overridden. Changes will have effect only on newly mapped (added) LDAP users. Leave it empty for default behavior." : "V predvolenom nastavení sa interné užívateľské meno vytvorí z atribútu UUID. Zabezpečuje jedinečnosť užívateľského mena a znaky nie je potrebné konvertovať. Interné užívateľské meno má obmedzenia, ktoré povoľujú iba tieto znaky: [a-zA-Z0-9 _. @ -]. Ostatné znaky sa nahradia zodpovedajúcimi znakmi ASCII alebo sa jednoducho vynechajú. Pri konfliktoch bude pridané/zvýšené číslo. Interné užívateľské meno sa používa na internú identifikáciu užívateľa. Je to tiež predvolený názov domovského priečinka užívateľa. Je tiež súčasťou URL, napríklad pre všetky služby * DAV. Týmto nastavením môže byť predvolené správanie zmenené. Zmeny majú vplyv iba na novo namapovaných (pridaných) užívateľov LDAP. Nechajte prázdne ak chcete nechať predvolené nastavenie.", "Internal Username Attribute:" : "Atribút interného používateľského mena:", "Override UUID detection" : "Prepísať UUID detekciu", "By default, the UUID attribute is automatically detected. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users and groups." : "V predvolenom nastavení sa atribút UUID deteguje automaticky. Atribút UUID sa používa na jednoznačnú identifikáciu používateľov a skupín z LDAPu. Naviac sa na základe UUID vytvára aj interné používateľské meno, ak nie je nastavené inak. Môžete predvolené nastavenie prepísať a použiť atribút ktorý si sami zvolíte. Musíte sa ale ubezpečiť, že atribút, ktorý vyberiete, bude uvedený pri používateľoch aj pri skupinách a bude jedinečný. Ak voľbu ponecháte prázdnu, použije sa predvolené správanie. Zmena bude mať vplyv len na novo namapovaných (pridaných) používateľov a skupiny z LDAPu.", diff --git a/apps/user_ldap/l10n/sk.json b/apps/user_ldap/l10n/sk.json index abd385b26c7..23234d8d0ec 100644 --- a/apps/user_ldap/l10n/sk.json +++ b/apps/user_ldap/l10n/sk.json @@ -58,6 +58,10 @@ "Your password will expire today." : "Vaše heslo expiruje dnes.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Vaše heslo expiruje o %n deň.","Vaše heslo expiruje o %n dni.","Vaše heslo expiruje o %n dní.","Vaše heslo expiruje o %n dní."], "LDAP/AD integration" : "Integrácia s LDAP/AD", + "_%n group found_::_%n groups found_" : ["%n nájdená skupina","%n nájdené skupiny","%n nájdených skupín","%n nájdených skupín"], + "> 1000 groups found" : "> 1000 nájdených skupín", + "> 1000 users found" : "> 1000 nájdených užívateľov", + "_%n user found_::_%n users found_" : ["%n nájdený užívateľ","%n nájdený užívatelia","%n nájdených užívateľov","%n nájdených užívateľov"], "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Nepodarilo sa zistiť atribút pre zobrazenie mena používateľa. Zadajte ho sami v rozšírených nastaveniach LDAP.", "Could not find the desired feature" : "Nemožno nájsť požadovanú funkciu", "Invalid Host" : "Neplatný hostiteľ", @@ -176,6 +180,7 @@ "\"$home\" Placeholder Field" : "Výplňová kolónka „$home“", "$home in an external storage configuration will be replaced with the value of the specified attribute" : "$ home bude v nastavení externého úložiska nahradená hodnotou zadaného atribútu ", "Internal Username" : "Interné používateľské meno", + "By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [a-zA-Z0-9_.@-]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all DAV services. With this setting, the default behavior can be overridden. Changes will have effect only on newly mapped (added) LDAP users. Leave it empty for default behavior." : "V predvolenom nastavení sa interné užívateľské meno vytvorí z atribútu UUID. Zabezpečuje jedinečnosť užívateľského mena a znaky nie je potrebné konvertovať. Interné užívateľské meno má obmedzenia, ktoré povoľujú iba tieto znaky: [a-zA-Z0-9 _. @ -]. Ostatné znaky sa nahradia zodpovedajúcimi znakmi ASCII alebo sa jednoducho vynechajú. Pri konfliktoch bude pridané/zvýšené číslo. Interné užívateľské meno sa používa na internú identifikáciu užívateľa. Je to tiež predvolený názov domovského priečinka užívateľa. Je tiež súčasťou URL, napríklad pre všetky služby * DAV. Týmto nastavením môže byť predvolené správanie zmenené. Zmeny majú vplyv iba na novo namapovaných (pridaných) užívateľov LDAP. Nechajte prázdne ak chcete nechať predvolené nastavenie.", "Internal Username Attribute:" : "Atribút interného používateľského mena:", "Override UUID detection" : "Prepísať UUID detekciu", "By default, the UUID attribute is automatically detected. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users and groups." : "V predvolenom nastavení sa atribút UUID deteguje automaticky. Atribút UUID sa používa na jednoznačnú identifikáciu používateľov a skupín z LDAPu. Naviac sa na základe UUID vytvára aj interné používateľské meno, ak nie je nastavené inak. Môžete predvolené nastavenie prepísať a použiť atribút ktorý si sami zvolíte. Musíte sa ale ubezpečiť, že atribút, ktorý vyberiete, bude uvedený pri používateľoch aj pri skupinách a bude jedinečný. Ak voľbu ponecháte prázdnu, použije sa predvolené správanie. Zmena bude mať vplyv len na novo namapovaných (pridaných) používateľov a skupiny z LDAPu.", diff --git a/apps/user_ldap/l10n/uk.js b/apps/user_ldap/l10n/uk.js index 8d4d45c7582..7ce76c7bf73 100644 --- a/apps/user_ldap/l10n/uk.js +++ b/apps/user_ldap/l10n/uk.js @@ -1,7 +1,7 @@ OC.L10N.register( "user_ldap", { - "Failed to clear the mappings." : "Не вдалося очистити відображення.", + "Failed to clear the mappings." : "Не вдалося очистити мапування.", "Failed to delete the server configuration" : "Не вдалося вилучити конфігурацію сервера", "Invalid configuration: Anonymous binding is not allowed." : "Неправильна конфігурація. Анонімне приєднання не дозволено.", "Valid configuration, connection established!" : "Правильна конфігурація, з'єднання встановлено!", @@ -32,10 +32,11 @@ OC.L10N.register( "{nthServer}. Server" : "{nthServer}. Сервер", "No object found in the given Base DN. Please revise." : "Не знайдено жодного об’єкта в даному базовому DN. Будь ласка, перегляньте.", "More than 1,000 directory entries available." : "Доступно понад 1000 записів каталогу.", + "_{objectsFound} entry available within the provided Base DN_::_{objectsFound} entries available within the provided Base DN_" : ["Запис {objectsFound} доступний у межах наданого базового DN","{objectsFound} записи доступні в межах наданого базового DN","{objectsFound} записи доступні в межах наданого базового DN","{objectsFound} записи доступні в межах наданого базового DN"], "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "Сталась помилка. Будь ласка, перевірте базове DN, а також налаштування підключення та облікові дані.", "Do you really want to delete the current Server Configuration?" : "Дійсно вилучити поточну конфігурацію сервера ?", "Confirm Deletion" : "Підтвердіть вилучення", - "Mappings cleared successfully!" : "Відображення успішно очищенні!", + "Mappings cleared successfully!" : "Мапування успішно очищено!", "Error while clearing the mappings." : "Помилка при очищенні відображень.", "Anonymous bind is not allowed. Please provide a User DN and Password." : "Анонімне прив'язування не допускається. Укажіть DN користувача та пароль.", "LDAP Operations error. Anonymous bind might not be allowed." : "Помилка операцій LDAP. Анонімне прив’язування може бути заборонено.", @@ -43,16 +44,32 @@ OC.L10N.register( "Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?" : "Перемикання режиму активує автоматичні запити LDAP. Залежно від розміру LDAP, це може зайняти деякий час. Ви все ще хочете змінити режим?", "Mode switch" : "Перемикач режимів", "Select attributes" : "Виберіть атрибути", + "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command-line validation): <br/>" : "Користувача не знайдено. Перевірте свої атрибути входу та ім’я користувача. Ефективний фільтр (для копіювання та вставлення для перевірки командного рядка): <br/>", "User found and settings verified." : "Користувача знайдено і налаштування перевірені.", + "Consider narrowing your search, as it encompassed many users, only the first one of whom will be able to log in." : "Розгляньте можливість звузити пошук, оскільки він охоплює багато користувачів, лише перший з яких зможе ввійти.", + "An unspecified error occurred. Please check log and settings." : "Сталася невизначена помилка. Перевірте журнал і налаштування.", "The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "Фільтр пошуку недійсний, ймовірно, через синтаксичні проблеми, наприклад нерівну кількість відкритих і закритих дужок. Будь ласка, перегляньте.", + "A connection error to LDAP/AD occurred. Please check host, port and credentials." : "Сталася помилка підключення до LDAP/AD. Перевірте хост, порт і облікові дані.", + "The \"%uid\" placeholder is missing. It will be replaced with the login name when querying LDAP/AD." : "Заповнювач \"%uid\" відсутній. Його буде замінено ім’ям для входу під час запиту LDAP/AD.", "Please provide a login name to test against" : "Будь ласка, введіть ім’я для входу для перевірки", + "The group box was disabled, because the LDAP/AD server does not support memberOf." : "Поле групи було вимкнено, оскільки сервер LDAP/AD не підтримує memberOf.", + "Password change rejected. Hint: " : "Зміна пароля відхилена. Підказка: ", + "Please login with the new password" : "Будь ласка, увійдіть з новим паролем", "LDAP User backend" : "Інтерфейс керування користувачами LDAP", "Your password will expire tomorrow." : "Дія вашого пароля завершується завтра.", "Your password will expire today." : "Дія вашого пароля завершується сьогодні.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Дія вашого пароля завершується через %n день.","Дія вашого пароля завершується через %n дні.","Дія вашого пароля завершується через %n днів.","Дія вашого пароля завершується через %n днів."], + "LDAP/AD integration" : "Інтеграція LDAP/AD", + "_%n group found_::_%n groups found_" : ["знайдена група %n","знайдено груп %n","знайдено груп %n","знайдено груп %n "], + "> 1000 groups found" : "> 1000 груп знайдено", + "> 1000 users found" : "> 1000 користувачів знайдено", + "_%n user found_::_%n users found_" : ["знайдено користувач %n","знайдено користувачів %n","знайдено користувачів %n","знайдено користувачів %n"], + "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Не вдалося виявити атрибут відображуваного імені користувача. Будь ласка, вкажіть це самостійно в розширених налаштуваннях LDAP.", "Could not find the desired feature" : "Не вдалося знайти потрібну функцію", "Invalid Host" : "Невірний Host", "LDAP user and group backend" : "Інтерфейс керування користувачами та групами LDAP", + "This application enables administrators to connect Nextcloud to an LDAP-based user directory." : "Ця програма дозволяє адміністраторам підключати Nextcloud до каталогу користувачів на основі LDAP.", + "This application enables administrators to connect Nextcloud to an LDAP-based user directory for authentication and provisioning users, groups and user attributes. Admins can configure this application to connect to one or more LDAP directories or Active Directories via an LDAP interface. Attributes such as user quota, email, avatar pictures, group memberships and more can be pulled into Nextcloud from a directory with the appropriate queries and filters.\n\nA user logs into Nextcloud with their LDAP or AD credentials, and is granted access based on an authentication request handled by the LDAP or AD server. Nextcloud does not store LDAP or AD passwords, rather these credentials are used to authenticate a user and then Nextcloud uses a session for the user ID. More information is available in the LDAP User and Group Backend documentation." : "Ця програма дозволяє адміністраторам підключати Nextcloud до каталогу користувачів на основі LDAP для автентифікації та ініціалізації користувачів, груп і атрибутів користувачів. Адміністратори можуть налаштувати цю програму для підключення до одного або кількох каталогів LDAP або Active Directory через інтерфейс LDAP. Такі атрибути, як квота користувача, електронна пошта, зображення аватара, членство в групах тощо, можна отримати в Nextcloud із каталогу з відповідними запитами та фільтрами. \n\nКористувач входить у Nextcloud за допомогою своїх облікових даних LDAP або AD і отримує доступ на основі запиту автентифікації, який обробляється сервером LDAP або AD. Nextcloud не зберігає паролі LDAP або AD, натомість ці облікові дані використовуються для автентифікації користувача, а потім Nextcloud використовує сеанс для ідентифікатора користувача. Більше інформації доступно в документації користувача та групи LDAP Backend.", "Test Configuration" : "Тестове налаштування", "Help" : "Допомога", "Groups meeting these criteria are available in %s:" : "Групи, що відповідають цим критеріям доступні в %s:", @@ -63,17 +80,23 @@ OC.L10N.register( "Selected groups" : "Обрані групи", "Edit LDAP Query" : "Редагувати запит LDAP", "LDAP Filter:" : "LDAP фільтр:", - "The filter specifies which LDAP groups shall have access to the %s instance." : "Фільтр визначає, які LDAP групи повинні мати доступ до %s примірника.", + "The filter specifies which LDAP groups shall have access to the %s instance." : "Фільтр визначає, які LDAP групи повинні мати доступ до %s сервера.", "Verify settings and count the groups" : "Перевірити налаштування та порахувати групи", "When logging in, %s will find the user based on the following attributes:" : "Під час входу в систему %s знайде користувача за такими атрибутами:", + "LDAP/AD Username:" : "Ім'я користувача LDAP/AD:", + "Allows login against the LDAP/AD username, which is either \"uid\" or \"sAMAccountName\" and will be detected." : "Дозволяє ввійти за допомогою імені користувача LDAP/AD, яке є \"uid\" або \"sAMAccountName\" і буде виявлено.", + "LDAP/AD Email Address:" : "Адреса електронної пошти LDAP/AD:", + "Allows login against an email attribute. \"mail\" and \"mailPrimaryAddress\" allowed." : "Дозволяє входити за допомогою атрибута електронної пошти. дозволено \"mail\" і \"mailPrimaryAddress\".", "Other Attributes:" : "Інші Атрибути:", + "Defines the filter to apply, when login is attempted. \"%%uid\" replaces the username in the login action. Example: \"uid=%%uid\"" : "Визначає фільтр, який застосовуватиметься під час спроби входу. \"%%uid\" замінює ім'я користувача в дії входу. Приклад: \"uid=%%uid\"", "Test Loginname" : "Тестове ім'я при вході", "Verify settings" : "Перевірити налаштування", "%s. Server:" : "%s. Сервер:", "Add a new configuration" : "Додати нову конфігурацію", "Copy current configuration into new directory binding" : "Скопіюйте поточну конфігурацію в новий каталог", - "Delete the current configuration" : "Видалити поточну конфігурацію", + "Delete the current configuration" : "Вилучити поточну конфігурацію", "Host" : "Хост", + "You can omit the protocol, unless you require SSL. If so, start with ldaps://" : "Ви можете не вказувати протокол, якщо вам не потрібен SSL. Якщо так, починайте з ldaps://", "Port" : "Порт", "Detect Port" : "Визначити Порт", "User DN" : "DN Користувача", @@ -87,8 +110,9 @@ OC.L10N.register( "Test Base DN" : "Тест основного DN", "Avoids automatic LDAP requests. Better for bigger setups, but requires some LDAP knowledge." : "Уникати автоматичні запити LDAP. Краще для великих установок, але вимагає деякого LDAP знання.", "Manually enter LDAP filters (recommended for large directories)" : "Вручну введіть фільтри LDAP (рекомендується для великих каталогів)", + "Listing and searching for users is constrained by these criteria:" : "Перелік і пошук користувачів обмежені такими критеріями:", "The most common object classes for users are organizationalPerson, person, user, and inetOrgPerson. If you are not sure which object class to select, please consult your directory admin." : "Найпоширенішими класами об’єктів для користувачів є organizationalPerson, person, user і inetOrgPerson. Якщо ви не впевнені, який клас об’єктів вибрати, зверніться до свого адміністратора каталогу.", - "The filter specifies which LDAP users shall have access to the %s instance." : "Фільтр визначає, які користувачі LDAP повинні мати доступ до примірника %s.", + "The filter specifies which LDAP users shall have access to the %s instance." : "Фільтр визначає, які користувачі LDAP повинні мати доступ до сервера %s.", "Verify settings and count users" : "Перевірити налаштування та порахувати користувачів", "Saving" : "Збереження", "Back" : "Назад", @@ -115,7 +139,7 @@ OC.L10N.register( "Give an optional backup host. It must be a replica of the main LDAP/AD server." : "Вкажіть додатковий резервний сервер. Він повинен бути копією головного LDAP/AD сервера.", "Backup (Replica) Port" : "Порт сервера для резервних копій", "Disable Main Server" : "Вимкнути Головний Сервер", - "Only connect to the replica server." : "Підключити тільки до сервера реплік.", + "Only connect to the replica server." : "З'єднатися тільки із сервером реплік.", "Turn off SSL certificate validation." : "Вимкнути перевірку SSL сертифіката.", "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server." : "Не рекомендується, використовувати його тільки для тестування!\nЯкщо з'єднання працює лише з цією опцією, імпортуйте SSL сертифікат LDAP сервера у ваший %s сервер.", "Cache Time-To-Live" : "Час актуальності Кеша", @@ -124,36 +148,50 @@ OC.L10N.register( "User Display Name Field" : "Поле, яке відображає Ім'я Користувача", "The LDAP attribute to use to generate the user's display name." : "Атрибут LDAP, який використовується для генерації імен користувачів.", "2nd User Display Name Field" : "2-е поле відображуваного імені користувача", - "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Додатково. Атрибут LDAP, який потрібно додати до відображуваного імені в дужках. Результати, напр. »Джон Доу (john.doe@example.org)«.", + "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Додатково. Атрибут LDAP, який потрібно додати до відображуваного імені в дужках. Результати, напр. \"Джон Доу (john.doe@example.org)\".", "Base User Tree" : "Основне Дерево Користувачів", "One User Base DN per line" : "Один Користувач Base DN на рядок", - "User Search Attributes" : "Пошукові Атрибути Користувача", + "User Search Attributes" : "Пошукові атрибутів користувача", "Optional; one attribute per line" : "Додатково; один атрибут на рядок", "Group Display Name Field" : "Поле, яке відображає Ім'я Групи", "The LDAP attribute to use to generate the groups's display name." : "Атрибут LDAP, який використовується для генерації імен груп.", "Base Group Tree" : "Основне Дерево Груп", "One Group Base DN per line" : "Одна Група Base DN на рядок", - "Group Search Attributes" : "Пошукові Атрибути Групи", - "Group-Member association" : "Асоціація Група-Член", - "Dynamic Group Member URL" : "URL-адреса члена динамічної групи", + "Group Search Attributes" : "Пошукові атрибути групи", + "Group-Member association" : "Асоціація \"група-учасник\"", + "Dynamic Group Member URL" : "URL-адреса учасника динамічної групи", "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.)" : "Атрибут LDAP, який об’єктів групи містить URL-адресу пошуку LDAP, яка визначає, які об’єкти належать до групи. (Пусте налаштування вимикає функцію динамічного членства в групі.)", "Nested Groups" : "Вкладені Групи", - "When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" : "При включенні, групи, які містять групи підтримуються. (Працює тільки якщо атрибут члена групи містить DNS.)", + "When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" : "При включенні, групи, які містять групи підтримуються. (Працює тільки якщо атрибут учасника групи містить DN.)", "Paging chunksize" : "Розмір підкачки", "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.)" : "Підкачка використовується для сторінкових пошуків LDAP, які можуть повертати громіздкі результати кількісті користувачів або груп. (Установка його 0 відключає вивантаженя пошуку LDAP в таких ситуаціях.)", + "Enable LDAP password changes per user" : "Дозволити зміну паролів LDAP для кожного користувача", + "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." : "Дозволити користувачам LDAP змінювати свій пароль і дозволити супер (прихованим) адміністраторам і адміністраторам груп змінювати пароль своїх користувачів LDAP. Працює тільки тоді, коли політики контролю доступу налаштовані відповідним чином на LDAP сервері. Оскільки паролі відправляються на LDAP-сервер у відкритому вигляді, необхідно використовувати транспортне шифрування і налаштувати хешування паролів на LDAP-сервері. ", + "(New password is sent as plain text to LDAP)" : "(Новий пароль надсилається як звичайний текст до LDAP)", + "Default password policy DN" : "DN політики паролів за замовчуванням", + "The DN of a default password policy that will be used for password expiry handling. Works only when LDAP password changes per user are enabled and is only supported by OpenLDAP. Leave empty to disable password expiry handling." : "DN політики паролів за умовчанням, яка використовуватиметься для обробки терміну дії пароля. Працює лише тоді, коли ввімкнено зміну пароля LDAP для кожного користувача та підтримується лише OpenLDAP. Залиште пустим, щоб вимкнути обробку терміну дії пароля.", "Special Attributes" : "Спеціальні Атрибути", "Quota Field" : "Поле Квоти", + "Leave empty for user's default quota. Otherwise, specify an LDAP/AD attribute." : "Залиште пустим для квоти користувача за умовчанням. В іншому випадку вкажіть атрибут LDAP/AD.", "Quota Default" : "Квота за замовчанням", + "Override default quota for LDAP users who do not have a quota set in the Quota Field." : "Перевизначити квоту за замовчуванням для користувачів LDAP, які не мають квоти, встановленої в полі квоти.", "Email Field" : "Поле E-mail", - "User Home Folder Naming Rule" : "Правило іменування домашньої теки користувача", + "Set the user's email from their LDAP attribute. Leave it empty for default behaviour." : "Установіть електронну пошту користувача з його атрибута LDAP. Залиште поле порожнім для поведінки за умовчанням.", + "User Home Folder Naming Rule" : "Правило іменування домашнього каталогу користувача", + "Leave empty for username (default). Otherwise, specify an LDAP/AD attribute." : "Залиште порожнім для імені користувача (за замовчуванням). В іншому випадку вкажіть атрибут LDAP/AD.", + "\"$home\" Placeholder Field" : "Поле заповнювача \"$home\".", + "$home in an external storage configuration will be replaced with the value of the specified attribute" : "$home у конфігурації зовнішнього сховища буде замінено на значення вказаного атрибута", "Internal Username" : "Внутрішня Ім'я користувача", + "By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [a-zA-Z0-9_.@-]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all DAV services. With this setting, the default behavior can be overridden. Changes will have effect only on newly mapped (added) LDAP users. Leave it empty for default behavior." : "За замовчуванням внутрішнє ім’я користувача буде створено з атрибута UUID. Це гарантує, що ім’я користувача є унікальним і символи не потрібно конвертувати. Внутрішнє ім’я користувача має обмеження щодо дозволених лише таких символів: [a-zA-Z0-9_.@-]. Інші символи замінюються відповідністю ASCII або просто пропускаються. При зіткненнях число буде додано/збільшено. Внутрішнє ім’я користувача використовується для внутрішньої ідентифікації користувача. Це також назва за замовчуванням для домашнього каталогу користувача. Це також частина віддалених URL-адрес, наприклад, для всіх служб DAV. За допомогою цього параметра поведінку за замовчуванням можна змінити. Зміни діятимуть лише для нещодавно зіставлених (доданих) користувачів LDAP. Залиште поле порожнім для поведінки за замовчуванням.", "Internal Username Attribute:" : "Внутрішня Ім'я користувача, Атрибут:", "Override UUID detection" : "Перекрити вивід UUID ", "By default, the UUID attribute is automatically detected. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users and groups." : "За замовчуванням ownCloud визначає атрибут UUID автоматично. Цей атрибут використовується для того, щоб достовірно ідентифікувати користувачів і групи LDAP. Також на підставі атрибута UUID створюється внутрішнє ім'я користувача, якщо вище не вказано інакше. Ви можете перевизначити це налаштування та вказати свій атрибут за вибором. Ви повинні упевнитися, що обраний вами атрибут може бути вибраний для користувачів і груп, а також те, що він унікальний. Залиште поле порожнім для поведінки за замовчуванням. Зміни вступлять в силу тільки для нових підключених (доданих) користувачів і груп LDAP.", "UUID Attribute for Users:" : "UUID Атрибут для користувачів:", "UUID Attribute for Groups:" : "UUID Атрибут для груп:", "Username-LDAP User Mapping" : "Картографія Імен користувачів-LDAP ", + "Usernames are used to store and assign metadata. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage." : "Імена користувачів використовуються для зберігання та призначення метаданих. Для точної ідентифікації та розпізнавання користувачів кожен користувач LDAP матиме внутрішнє ім’я користувача. Для цього потрібне зіставлення імені користувача з користувачем LDAP. Створене ім’я користувача зіставляється з UUID користувача LDAP. Крім того, DN також кешується, щоб зменшити взаємодію LDAP, але він не використовується для ідентифікації. Якщо DN змінюється, зміни будуть знайдені. Внутрішнє ім'я користувача використовується всюди. Очищення зіставлення залишить залишки всюди. Очищення зіставлення не залежить від конфігурації, воно впливає на всі конфігурації LDAP! Ніколи не очищайте зіставлення у виробничому середовищі, лише на стадії тестування чи експерименту.", "Clear Username-LDAP User Mapping" : "Очистити картографію Імен користувачів-LDAP", - "Clear Groupname-LDAP Group Mapping" : "Очистити картографію Імен груп-LDAP" + "Clear Groupname-LDAP Group Mapping" : "Очистити картографію Імен груп-LDAP", + "By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [a-zA-Z0-9_.@-]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all *DAV services. With this setting, the default behavior can be overridden. Changes will have effect only on newly mapped (added) LDAP users. Leave it empty for default behavior." : "За замовчуванням внутрішнє ім’я користувача буде створено з атрибута UUID. Це гарантує, що ім’я користувача є унікальним і символи не потрібно конвертувати. Внутрішнє ім’я користувача має обмеження щодо дозволених лише таких символів: [a-zA-Z0-9_.@-]. Інші символи замінюються відповідністю ASCII або просто пропускаються. При зіткненнях число буде додано/збільшено. Внутрішнє ім’я користувача використовується для внутрішньої ідентифікації користувача. Це також назва за замовчуванням для домашнього каталогу користувача. Це також частина віддалених URL-адрес, наприклад, для всіх служб *DAV. За допомогою цього параметра поведінку за замовчуванням можна змінити. Зміни діятимуть лише для нещодавно зіставлених (доданих) користувачів LDAP. Залиште поле порожнім для поведінки за замовчуванням." }, "nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);"); diff --git a/apps/user_ldap/l10n/uk.json b/apps/user_ldap/l10n/uk.json index f07864f7ab0..130dd65a1f2 100644 --- a/apps/user_ldap/l10n/uk.json +++ b/apps/user_ldap/l10n/uk.json @@ -1,5 +1,5 @@ { "translations": { - "Failed to clear the mappings." : "Не вдалося очистити відображення.", + "Failed to clear the mappings." : "Не вдалося очистити мапування.", "Failed to delete the server configuration" : "Не вдалося вилучити конфігурацію сервера", "Invalid configuration: Anonymous binding is not allowed." : "Неправильна конфігурація. Анонімне приєднання не дозволено.", "Valid configuration, connection established!" : "Правильна конфігурація, з'єднання встановлено!", @@ -30,10 +30,11 @@ "{nthServer}. Server" : "{nthServer}. Сервер", "No object found in the given Base DN. Please revise." : "Не знайдено жодного об’єкта в даному базовому DN. Будь ласка, перегляньте.", "More than 1,000 directory entries available." : "Доступно понад 1000 записів каталогу.", + "_{objectsFound} entry available within the provided Base DN_::_{objectsFound} entries available within the provided Base DN_" : ["Запис {objectsFound} доступний у межах наданого базового DN","{objectsFound} записи доступні в межах наданого базового DN","{objectsFound} записи доступні в межах наданого базового DN","{objectsFound} записи доступні в межах наданого базового DN"], "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "Сталась помилка. Будь ласка, перевірте базове DN, а також налаштування підключення та облікові дані.", "Do you really want to delete the current Server Configuration?" : "Дійсно вилучити поточну конфігурацію сервера ?", "Confirm Deletion" : "Підтвердіть вилучення", - "Mappings cleared successfully!" : "Відображення успішно очищенні!", + "Mappings cleared successfully!" : "Мапування успішно очищено!", "Error while clearing the mappings." : "Помилка при очищенні відображень.", "Anonymous bind is not allowed. Please provide a User DN and Password." : "Анонімне прив'язування не допускається. Укажіть DN користувача та пароль.", "LDAP Operations error. Anonymous bind might not be allowed." : "Помилка операцій LDAP. Анонімне прив’язування може бути заборонено.", @@ -41,16 +42,32 @@ "Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?" : "Перемикання режиму активує автоматичні запити LDAP. Залежно від розміру LDAP, це може зайняти деякий час. Ви все ще хочете змінити режим?", "Mode switch" : "Перемикач режимів", "Select attributes" : "Виберіть атрибути", + "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command-line validation): <br/>" : "Користувача не знайдено. Перевірте свої атрибути входу та ім’я користувача. Ефективний фільтр (для копіювання та вставлення для перевірки командного рядка): <br/>", "User found and settings verified." : "Користувача знайдено і налаштування перевірені.", + "Consider narrowing your search, as it encompassed many users, only the first one of whom will be able to log in." : "Розгляньте можливість звузити пошук, оскільки він охоплює багато користувачів, лише перший з яких зможе ввійти.", + "An unspecified error occurred. Please check log and settings." : "Сталася невизначена помилка. Перевірте журнал і налаштування.", "The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "Фільтр пошуку недійсний, ймовірно, через синтаксичні проблеми, наприклад нерівну кількість відкритих і закритих дужок. Будь ласка, перегляньте.", + "A connection error to LDAP/AD occurred. Please check host, port and credentials." : "Сталася помилка підключення до LDAP/AD. Перевірте хост, порт і облікові дані.", + "The \"%uid\" placeholder is missing. It will be replaced with the login name when querying LDAP/AD." : "Заповнювач \"%uid\" відсутній. Його буде замінено ім’ям для входу під час запиту LDAP/AD.", "Please provide a login name to test against" : "Будь ласка, введіть ім’я для входу для перевірки", + "The group box was disabled, because the LDAP/AD server does not support memberOf." : "Поле групи було вимкнено, оскільки сервер LDAP/AD не підтримує memberOf.", + "Password change rejected. Hint: " : "Зміна пароля відхилена. Підказка: ", + "Please login with the new password" : "Будь ласка, увійдіть з новим паролем", "LDAP User backend" : "Інтерфейс керування користувачами LDAP", "Your password will expire tomorrow." : "Дія вашого пароля завершується завтра.", "Your password will expire today." : "Дія вашого пароля завершується сьогодні.", "_Your password will expire within %n day._::_Your password will expire within %n days._" : ["Дія вашого пароля завершується через %n день.","Дія вашого пароля завершується через %n дні.","Дія вашого пароля завершується через %n днів.","Дія вашого пароля завершується через %n днів."], + "LDAP/AD integration" : "Інтеграція LDAP/AD", + "_%n group found_::_%n groups found_" : ["знайдена група %n","знайдено груп %n","знайдено груп %n","знайдено груп %n "], + "> 1000 groups found" : "> 1000 груп знайдено", + "> 1000 users found" : "> 1000 користувачів знайдено", + "_%n user found_::_%n users found_" : ["знайдено користувач %n","знайдено користувачів %n","знайдено користувачів %n","знайдено користувачів %n"], + "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Не вдалося виявити атрибут відображуваного імені користувача. Будь ласка, вкажіть це самостійно в розширених налаштуваннях LDAP.", "Could not find the desired feature" : "Не вдалося знайти потрібну функцію", "Invalid Host" : "Невірний Host", "LDAP user and group backend" : "Інтерфейс керування користувачами та групами LDAP", + "This application enables administrators to connect Nextcloud to an LDAP-based user directory." : "Ця програма дозволяє адміністраторам підключати Nextcloud до каталогу користувачів на основі LDAP.", + "This application enables administrators to connect Nextcloud to an LDAP-based user directory for authentication and provisioning users, groups and user attributes. Admins can configure this application to connect to one or more LDAP directories or Active Directories via an LDAP interface. Attributes such as user quota, email, avatar pictures, group memberships and more can be pulled into Nextcloud from a directory with the appropriate queries and filters.\n\nA user logs into Nextcloud with their LDAP or AD credentials, and is granted access based on an authentication request handled by the LDAP or AD server. Nextcloud does not store LDAP or AD passwords, rather these credentials are used to authenticate a user and then Nextcloud uses a session for the user ID. More information is available in the LDAP User and Group Backend documentation." : "Ця програма дозволяє адміністраторам підключати Nextcloud до каталогу користувачів на основі LDAP для автентифікації та ініціалізації користувачів, груп і атрибутів користувачів. Адміністратори можуть налаштувати цю програму для підключення до одного або кількох каталогів LDAP або Active Directory через інтерфейс LDAP. Такі атрибути, як квота користувача, електронна пошта, зображення аватара, членство в групах тощо, можна отримати в Nextcloud із каталогу з відповідними запитами та фільтрами. \n\nКористувач входить у Nextcloud за допомогою своїх облікових даних LDAP або AD і отримує доступ на основі запиту автентифікації, який обробляється сервером LDAP або AD. Nextcloud не зберігає паролі LDAP або AD, натомість ці облікові дані використовуються для автентифікації користувача, а потім Nextcloud використовує сеанс для ідентифікатора користувача. Більше інформації доступно в документації користувача та групи LDAP Backend.", "Test Configuration" : "Тестове налаштування", "Help" : "Допомога", "Groups meeting these criteria are available in %s:" : "Групи, що відповідають цим критеріям доступні в %s:", @@ -61,17 +78,23 @@ "Selected groups" : "Обрані групи", "Edit LDAP Query" : "Редагувати запит LDAP", "LDAP Filter:" : "LDAP фільтр:", - "The filter specifies which LDAP groups shall have access to the %s instance." : "Фільтр визначає, які LDAP групи повинні мати доступ до %s примірника.", + "The filter specifies which LDAP groups shall have access to the %s instance." : "Фільтр визначає, які LDAP групи повинні мати доступ до %s сервера.", "Verify settings and count the groups" : "Перевірити налаштування та порахувати групи", "When logging in, %s will find the user based on the following attributes:" : "Під час входу в систему %s знайде користувача за такими атрибутами:", + "LDAP/AD Username:" : "Ім'я користувача LDAP/AD:", + "Allows login against the LDAP/AD username, which is either \"uid\" or \"sAMAccountName\" and will be detected." : "Дозволяє ввійти за допомогою імені користувача LDAP/AD, яке є \"uid\" або \"sAMAccountName\" і буде виявлено.", + "LDAP/AD Email Address:" : "Адреса електронної пошти LDAP/AD:", + "Allows login against an email attribute. \"mail\" and \"mailPrimaryAddress\" allowed." : "Дозволяє входити за допомогою атрибута електронної пошти. дозволено \"mail\" і \"mailPrimaryAddress\".", "Other Attributes:" : "Інші Атрибути:", + "Defines the filter to apply, when login is attempted. \"%%uid\" replaces the username in the login action. Example: \"uid=%%uid\"" : "Визначає фільтр, який застосовуватиметься під час спроби входу. \"%%uid\" замінює ім'я користувача в дії входу. Приклад: \"uid=%%uid\"", "Test Loginname" : "Тестове ім'я при вході", "Verify settings" : "Перевірити налаштування", "%s. Server:" : "%s. Сервер:", "Add a new configuration" : "Додати нову конфігурацію", "Copy current configuration into new directory binding" : "Скопіюйте поточну конфігурацію в новий каталог", - "Delete the current configuration" : "Видалити поточну конфігурацію", + "Delete the current configuration" : "Вилучити поточну конфігурацію", "Host" : "Хост", + "You can omit the protocol, unless you require SSL. If so, start with ldaps://" : "Ви можете не вказувати протокол, якщо вам не потрібен SSL. Якщо так, починайте з ldaps://", "Port" : "Порт", "Detect Port" : "Визначити Порт", "User DN" : "DN Користувача", @@ -85,8 +108,9 @@ "Test Base DN" : "Тест основного DN", "Avoids automatic LDAP requests. Better for bigger setups, but requires some LDAP knowledge." : "Уникати автоматичні запити LDAP. Краще для великих установок, але вимагає деякого LDAP знання.", "Manually enter LDAP filters (recommended for large directories)" : "Вручну введіть фільтри LDAP (рекомендується для великих каталогів)", + "Listing and searching for users is constrained by these criteria:" : "Перелік і пошук користувачів обмежені такими критеріями:", "The most common object classes for users are organizationalPerson, person, user, and inetOrgPerson. If you are not sure which object class to select, please consult your directory admin." : "Найпоширенішими класами об’єктів для користувачів є organizationalPerson, person, user і inetOrgPerson. Якщо ви не впевнені, який клас об’єктів вибрати, зверніться до свого адміністратора каталогу.", - "The filter specifies which LDAP users shall have access to the %s instance." : "Фільтр визначає, які користувачі LDAP повинні мати доступ до примірника %s.", + "The filter specifies which LDAP users shall have access to the %s instance." : "Фільтр визначає, які користувачі LDAP повинні мати доступ до сервера %s.", "Verify settings and count users" : "Перевірити налаштування та порахувати користувачів", "Saving" : "Збереження", "Back" : "Назад", @@ -113,7 +137,7 @@ "Give an optional backup host. It must be a replica of the main LDAP/AD server." : "Вкажіть додатковий резервний сервер. Він повинен бути копією головного LDAP/AD сервера.", "Backup (Replica) Port" : "Порт сервера для резервних копій", "Disable Main Server" : "Вимкнути Головний Сервер", - "Only connect to the replica server." : "Підключити тільки до сервера реплік.", + "Only connect to the replica server." : "З'єднатися тільки із сервером реплік.", "Turn off SSL certificate validation." : "Вимкнути перевірку SSL сертифіката.", "Not recommended, use it for testing only! If connection only works with this option, import the LDAP server's SSL certificate in your %s server." : "Не рекомендується, використовувати його тільки для тестування!\nЯкщо з'єднання працює лише з цією опцією, імпортуйте SSL сертифікат LDAP сервера у ваший %s сервер.", "Cache Time-To-Live" : "Час актуальності Кеша", @@ -122,36 +146,50 @@ "User Display Name Field" : "Поле, яке відображає Ім'я Користувача", "The LDAP attribute to use to generate the user's display name." : "Атрибут LDAP, який використовується для генерації імен користувачів.", "2nd User Display Name Field" : "2-е поле відображуваного імені користувача", - "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Додатково. Атрибут LDAP, який потрібно додати до відображуваного імені в дужках. Результати, напр. »Джон Доу (john.doe@example.org)«.", + "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Додатково. Атрибут LDAP, який потрібно додати до відображуваного імені в дужках. Результати, напр. \"Джон Доу (john.doe@example.org)\".", "Base User Tree" : "Основне Дерево Користувачів", "One User Base DN per line" : "Один Користувач Base DN на рядок", - "User Search Attributes" : "Пошукові Атрибути Користувача", + "User Search Attributes" : "Пошукові атрибутів користувача", "Optional; one attribute per line" : "Додатково; один атрибут на рядок", "Group Display Name Field" : "Поле, яке відображає Ім'я Групи", "The LDAP attribute to use to generate the groups's display name." : "Атрибут LDAP, який використовується для генерації імен груп.", "Base Group Tree" : "Основне Дерево Груп", "One Group Base DN per line" : "Одна Група Base DN на рядок", - "Group Search Attributes" : "Пошукові Атрибути Групи", - "Group-Member association" : "Асоціація Група-Член", - "Dynamic Group Member URL" : "URL-адреса члена динамічної групи", + "Group Search Attributes" : "Пошукові атрибути групи", + "Group-Member association" : "Асоціація \"група-учасник\"", + "Dynamic Group Member URL" : "URL-адреса учасника динамічної групи", "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.)" : "Атрибут LDAP, який об’єктів групи містить URL-адресу пошуку LDAP, яка визначає, які об’єкти належать до групи. (Пусте налаштування вимикає функцію динамічного членства в групі.)", "Nested Groups" : "Вкладені Групи", - "When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" : "При включенні, групи, які містять групи підтримуються. (Працює тільки якщо атрибут члена групи містить DNS.)", + "When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" : "При включенні, групи, які містять групи підтримуються. (Працює тільки якщо атрибут учасника групи містить DN.)", "Paging chunksize" : "Розмір підкачки", "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.)" : "Підкачка використовується для сторінкових пошуків LDAP, які можуть повертати громіздкі результати кількісті користувачів або груп. (Установка його 0 відключає вивантаженя пошуку LDAP в таких ситуаціях.)", + "Enable LDAP password changes per user" : "Дозволити зміну паролів LDAP для кожного користувача", + "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." : "Дозволити користувачам LDAP змінювати свій пароль і дозволити супер (прихованим) адміністраторам і адміністраторам груп змінювати пароль своїх користувачів LDAP. Працює тільки тоді, коли політики контролю доступу налаштовані відповідним чином на LDAP сервері. Оскільки паролі відправляються на LDAP-сервер у відкритому вигляді, необхідно використовувати транспортне шифрування і налаштувати хешування паролів на LDAP-сервері. ", + "(New password is sent as plain text to LDAP)" : "(Новий пароль надсилається як звичайний текст до LDAP)", + "Default password policy DN" : "DN політики паролів за замовчуванням", + "The DN of a default password policy that will be used for password expiry handling. Works only when LDAP password changes per user are enabled and is only supported by OpenLDAP. Leave empty to disable password expiry handling." : "DN політики паролів за умовчанням, яка використовуватиметься для обробки терміну дії пароля. Працює лише тоді, коли ввімкнено зміну пароля LDAP для кожного користувача та підтримується лише OpenLDAP. Залиште пустим, щоб вимкнути обробку терміну дії пароля.", "Special Attributes" : "Спеціальні Атрибути", "Quota Field" : "Поле Квоти", + "Leave empty for user's default quota. Otherwise, specify an LDAP/AD attribute." : "Залиште пустим для квоти користувача за умовчанням. В іншому випадку вкажіть атрибут LDAP/AD.", "Quota Default" : "Квота за замовчанням", + "Override default quota for LDAP users who do not have a quota set in the Quota Field." : "Перевизначити квоту за замовчуванням для користувачів LDAP, які не мають квоти, встановленої в полі квоти.", "Email Field" : "Поле E-mail", - "User Home Folder Naming Rule" : "Правило іменування домашньої теки користувача", + "Set the user's email from their LDAP attribute. Leave it empty for default behaviour." : "Установіть електронну пошту користувача з його атрибута LDAP. Залиште поле порожнім для поведінки за умовчанням.", + "User Home Folder Naming Rule" : "Правило іменування домашнього каталогу користувача", + "Leave empty for username (default). Otherwise, specify an LDAP/AD attribute." : "Залиште порожнім для імені користувача (за замовчуванням). В іншому випадку вкажіть атрибут LDAP/AD.", + "\"$home\" Placeholder Field" : "Поле заповнювача \"$home\".", + "$home in an external storage configuration will be replaced with the value of the specified attribute" : "$home у конфігурації зовнішнього сховища буде замінено на значення вказаного атрибута", "Internal Username" : "Внутрішня Ім'я користувача", + "By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [a-zA-Z0-9_.@-]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all DAV services. With this setting, the default behavior can be overridden. Changes will have effect only on newly mapped (added) LDAP users. Leave it empty for default behavior." : "За замовчуванням внутрішнє ім’я користувача буде створено з атрибута UUID. Це гарантує, що ім’я користувача є унікальним і символи не потрібно конвертувати. Внутрішнє ім’я користувача має обмеження щодо дозволених лише таких символів: [a-zA-Z0-9_.@-]. Інші символи замінюються відповідністю ASCII або просто пропускаються. При зіткненнях число буде додано/збільшено. Внутрішнє ім’я користувача використовується для внутрішньої ідентифікації користувача. Це також назва за замовчуванням для домашнього каталогу користувача. Це також частина віддалених URL-адрес, наприклад, для всіх служб DAV. За допомогою цього параметра поведінку за замовчуванням можна змінити. Зміни діятимуть лише для нещодавно зіставлених (доданих) користувачів LDAP. Залиште поле порожнім для поведінки за замовчуванням.", "Internal Username Attribute:" : "Внутрішня Ім'я користувача, Атрибут:", "Override UUID detection" : "Перекрити вивід UUID ", "By default, the UUID attribute is automatically detected. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users and groups." : "За замовчуванням ownCloud визначає атрибут UUID автоматично. Цей атрибут використовується для того, щоб достовірно ідентифікувати користувачів і групи LDAP. Також на підставі атрибута UUID створюється внутрішнє ім'я користувача, якщо вище не вказано інакше. Ви можете перевизначити це налаштування та вказати свій атрибут за вибором. Ви повинні упевнитися, що обраний вами атрибут може бути вибраний для користувачів і груп, а також те, що він унікальний. Залиште поле порожнім для поведінки за замовчуванням. Зміни вступлять в силу тільки для нових підключених (доданих) користувачів і груп LDAP.", "UUID Attribute for Users:" : "UUID Атрибут для користувачів:", "UUID Attribute for Groups:" : "UUID Атрибут для груп:", "Username-LDAP User Mapping" : "Картографія Імен користувачів-LDAP ", + "Usernames are used to store and assign metadata. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage." : "Імена користувачів використовуються для зберігання та призначення метаданих. Для точної ідентифікації та розпізнавання користувачів кожен користувач LDAP матиме внутрішнє ім’я користувача. Для цього потрібне зіставлення імені користувача з користувачем LDAP. Створене ім’я користувача зіставляється з UUID користувача LDAP. Крім того, DN також кешується, щоб зменшити взаємодію LDAP, але він не використовується для ідентифікації. Якщо DN змінюється, зміни будуть знайдені. Внутрішнє ім'я користувача використовується всюди. Очищення зіставлення залишить залишки всюди. Очищення зіставлення не залежить від конфігурації, воно впливає на всі конфігурації LDAP! Ніколи не очищайте зіставлення у виробничому середовищі, лише на стадії тестування чи експерименту.", "Clear Username-LDAP User Mapping" : "Очистити картографію Імен користувачів-LDAP", - "Clear Groupname-LDAP Group Mapping" : "Очистити картографію Імен груп-LDAP" + "Clear Groupname-LDAP Group Mapping" : "Очистити картографію Імен груп-LDAP", + "By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [a-zA-Z0-9_.@-]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all *DAV services. With this setting, the default behavior can be overridden. Changes will have effect only on newly mapped (added) LDAP users. Leave it empty for default behavior." : "За замовчуванням внутрішнє ім’я користувача буде створено з атрибута UUID. Це гарантує, що ім’я користувача є унікальним і символи не потрібно конвертувати. Внутрішнє ім’я користувача має обмеження щодо дозволених лише таких символів: [a-zA-Z0-9_.@-]. Інші символи замінюються відповідністю ASCII або просто пропускаються. При зіткненнях число буде додано/збільшено. Внутрішнє ім’я користувача використовується для внутрішньої ідентифікації користувача. Це також назва за замовчуванням для домашнього каталогу користувача. Це також частина віддалених URL-адрес, наприклад, для всіх служб *DAV. За допомогою цього параметра поведінку за замовчуванням можна змінити. Зміни діятимуть лише для нещодавно зіставлених (доданих) користувачів LDAP. Залиште поле порожнім для поведінки за замовчуванням." },"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);" }
\ No newline at end of file diff --git a/apps/user_ldap/l10n/zh_HK.js b/apps/user_ldap/l10n/zh_HK.js index 221969351d6..1c0512e0287 100644 --- a/apps/user_ldap/l10n/zh_HK.js +++ b/apps/user_ldap/l10n/zh_HK.js @@ -69,7 +69,7 @@ OC.L10N.register( "Invalid Host" : "無效的 Host", "LDAP user and group backend" : "LDAP 用戶和群組後端系統", "This application enables administrators to connect Nextcloud to an LDAP-based user directory." : "該應用程序使管理員可以將Nextcloud連接到基於LDAP的用戶目錄。", - "This application enables administrators to connect Nextcloud to an LDAP-based user directory for authentication and provisioning users, groups and user attributes. Admins can configure this application to connect to one or more LDAP directories or Active Directories via an LDAP interface. Attributes such as user quota, email, avatar pictures, group memberships and more can be pulled into Nextcloud from a directory with the appropriate queries and filters.\n\nA user logs into Nextcloud with their LDAP or AD credentials, and is granted access based on an authentication request handled by the LDAP or AD server. Nextcloud does not store LDAP or AD passwords, rather these credentials are used to authenticate a user and then Nextcloud uses a session for the user ID. More information is available in the LDAP User and Group Backend documentation." : "本應用啟用功能使管理員可將 Nextcloud 與基於 LDAP 的用戶目錄連接,進行身分驗證以及提供用戶、群組和用戶屬性。管理員可配置此程序通過 LDAP 接口連接至一個或多個 LDAP 或 Active Directory 目錄。通過適當的查詢和篩選,可將以下屬性從目錄導入 Nextcloud:用戶磁盤配額、電子郵箱、頭像、所屬群組以及更多。用戶可以用其LDAP或AD中的身分登入 Nextcloud, 並根據 LDAP 或 AD 服務的身分驗證獲得訪問權限。Nextcloud 僅使用身分驗證隨後為用戶身分使用會話,但不會儲存LDAP或AD的密碼。您可從 LDAP 用戶及群組後台文檔中獲取更多信息。", + "This application enables administrators to connect Nextcloud to an LDAP-based user directory for authentication and provisioning users, groups and user attributes. Admins can configure this application to connect to one or more LDAP directories or Active Directories via an LDAP interface. Attributes such as user quota, email, avatar pictures, group memberships and more can be pulled into Nextcloud from a directory with the appropriate queries and filters.\n\nA user logs into Nextcloud with their LDAP or AD credentials, and is granted access based on an authentication request handled by the LDAP or AD server. Nextcloud does not store LDAP or AD passwords, rather these credentials are used to authenticate a user and then Nextcloud uses a session for the user ID. More information is available in the LDAP User and Group Backend documentation." : "此應用程式讓管理員可以將 Nextcloud 連線到以 LDAP 為基礎的使用者目錄來進行驗證與提供使用者、群組與使用者屬性。管理員可以設定此應用程式來連線到一個或更多個 LDAP 目錄,或是透過 LDAP 介面的 Active Directories。如使用者配額、電子郵件、虛擬化身圖片、群組成員資格等屬性可以透過有適當查詢與過濾條件的目錄從 Nextcloud 拉取。\n\n使用者以其 LDAP 或 AD 憑證登入 Nextcloud,並根據 LDAP 或 AD 伺服器處理的身份驗證請求來授予存取權限。Nextcloud 並不儲存 LDAP 或 AD 的密碼,而是使用這些憑證來對使用者進行身份驗證,然後 Nextcloud 會使用工作階段作為使用者 ID。更多資訊在 LDAP 使用者與群組後端文件中提供。", "Test Configuration" : "測試此配置", "Help" : "支援", "Groups meeting these criteria are available in %s:" : "符合這些條件的群組可在 %s 找到:", diff --git a/apps/user_ldap/l10n/zh_HK.json b/apps/user_ldap/l10n/zh_HK.json index c30ba105025..b622349c70f 100644 --- a/apps/user_ldap/l10n/zh_HK.json +++ b/apps/user_ldap/l10n/zh_HK.json @@ -67,7 +67,7 @@ "Invalid Host" : "無效的 Host", "LDAP user and group backend" : "LDAP 用戶和群組後端系統", "This application enables administrators to connect Nextcloud to an LDAP-based user directory." : "該應用程序使管理員可以將Nextcloud連接到基於LDAP的用戶目錄。", - "This application enables administrators to connect Nextcloud to an LDAP-based user directory for authentication and provisioning users, groups and user attributes. Admins can configure this application to connect to one or more LDAP directories or Active Directories via an LDAP interface. Attributes such as user quota, email, avatar pictures, group memberships and more can be pulled into Nextcloud from a directory with the appropriate queries and filters.\n\nA user logs into Nextcloud with their LDAP or AD credentials, and is granted access based on an authentication request handled by the LDAP or AD server. Nextcloud does not store LDAP or AD passwords, rather these credentials are used to authenticate a user and then Nextcloud uses a session for the user ID. More information is available in the LDAP User and Group Backend documentation." : "本應用啟用功能使管理員可將 Nextcloud 與基於 LDAP 的用戶目錄連接,進行身分驗證以及提供用戶、群組和用戶屬性。管理員可配置此程序通過 LDAP 接口連接至一個或多個 LDAP 或 Active Directory 目錄。通過適當的查詢和篩選,可將以下屬性從目錄導入 Nextcloud:用戶磁盤配額、電子郵箱、頭像、所屬群組以及更多。用戶可以用其LDAP或AD中的身分登入 Nextcloud, 並根據 LDAP 或 AD 服務的身分驗證獲得訪問權限。Nextcloud 僅使用身分驗證隨後為用戶身分使用會話,但不會儲存LDAP或AD的密碼。您可從 LDAP 用戶及群組後台文檔中獲取更多信息。", + "This application enables administrators to connect Nextcloud to an LDAP-based user directory for authentication and provisioning users, groups and user attributes. Admins can configure this application to connect to one or more LDAP directories or Active Directories via an LDAP interface. Attributes such as user quota, email, avatar pictures, group memberships and more can be pulled into Nextcloud from a directory with the appropriate queries and filters.\n\nA user logs into Nextcloud with their LDAP or AD credentials, and is granted access based on an authentication request handled by the LDAP or AD server. Nextcloud does not store LDAP or AD passwords, rather these credentials are used to authenticate a user and then Nextcloud uses a session for the user ID. More information is available in the LDAP User and Group Backend documentation." : "此應用程式讓管理員可以將 Nextcloud 連線到以 LDAP 為基礎的使用者目錄來進行驗證與提供使用者、群組與使用者屬性。管理員可以設定此應用程式來連線到一個或更多個 LDAP 目錄,或是透過 LDAP 介面的 Active Directories。如使用者配額、電子郵件、虛擬化身圖片、群組成員資格等屬性可以透過有適當查詢與過濾條件的目錄從 Nextcloud 拉取。\n\n使用者以其 LDAP 或 AD 憑證登入 Nextcloud,並根據 LDAP 或 AD 伺服器處理的身份驗證請求來授予存取權限。Nextcloud 並不儲存 LDAP 或 AD 的密碼,而是使用這些憑證來對使用者進行身份驗證,然後 Nextcloud 會使用工作階段作為使用者 ID。更多資訊在 LDAP 使用者與群組後端文件中提供。", "Test Configuration" : "測試此配置", "Help" : "支援", "Groups meeting these criteria are available in %s:" : "符合這些條件的群組可在 %s 找到:", diff --git a/apps/user_ldap/lib/Access.php b/apps/user_ldap/lib/Access.php index 29d60817c02..a64c61f8139 100644 --- a/apps/user_ldap/lib/Access.php +++ b/apps/user_ldap/lib/Access.php @@ -95,8 +95,7 @@ class Access extends LDAPUtility { private $ncUserManager; /** @var LoggerInterface */ private $logger; - /** @var string */ - private $lastCookie = ''; + private string $lastCookie = ''; public function __construct( Connection $connection, @@ -194,15 +193,6 @@ class Access extends LDAPUtility { $this->logger->debug('LDAP resource not available.', ['app' => 'user_ldap']); return false; } - //Cancel possibly running Paged Results operation, otherwise we run in - //LDAP protocol errors - $this->abandonPagedSearch(); - // openLDAP requires that we init a new Paged Search. Not needed by AD, - // but does not hurt either. - $pagingSize = (int)$this->connection->ldapPagingSize; - // 0 won't result in replies, small numbers may leave out groups - // (cf. #12306), 500 is default for paging and should work everywhere. - $maxResults = $pagingSize > 20 ? $pagingSize : 500; $attr = mb_strtolower($attr, 'UTF-8'); // the actual read attribute later may contain parameters on a ranged // request, e.g. member;range=99-199. Depends on server reply. @@ -211,7 +201,7 @@ class Access extends LDAPUtility { $values = []; $isRangeRequest = false; do { - $result = $this->executeRead($dn, $attrToRead, $filter, $maxResults); + $result = $this->executeRead($dn, $attrToRead, $filter); if (is_bool($result)) { // when an exists request was run and it was successful, an empty // array must be returned @@ -258,15 +248,7 @@ class Access extends LDAPUtility { * returned data on a successful usual operation * @throws ServerNotAvailableException */ - public function executeRead(string $dn, string $attribute, string $filter, int $maxResults) { - try { - $this->initPagedSearch($filter, $dn, [$attribute], $maxResults, 0); - } catch (NoMoreResults $e) { - // does not happen, no pagination here since offset is 0, but the - // previous call is needed for a potential reset of the state. - // Tools would still point out a possible NoMoreResults exception. - return false; - } + public function executeRead(string $dn, string $attribute, string $filter) { $dn = $this->helper->DNasBaseParameter($dn); $rr = @$this->invokeLDAPMethod('read', $dn, $filter, [$attribute]); if (!$this->ldap->isResource($rr)) { @@ -511,6 +493,11 @@ class Access extends LDAPUtility { * @throws \Exception */ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped = null, array $record = null) { + static $intermediates = []; + if (isset($intermediates[($isUser ? 'user-' : 'group-') . $fdn])) { + return false; // is a known intermediate + } + $newlyMapped = false; if ($isUser) { $mapper = $this->getUserMapper(); @@ -546,6 +533,7 @@ class Access extends LDAPUtility { $ldapName = $this->readAttribute($fdn, $nameAttribute, $filter); if (!isset($ldapName[0]) || empty($ldapName[0])) { $this->logger->debug('No or empty name for ' . $fdn . ' with filter ' . $filter . '.', ['app' => 'user_ldap']); + $intermediates[($isUser ? 'user-' : 'group-') . $fdn] = true; return false; } $ldapName = $ldapName[0]; @@ -1098,7 +1086,7 @@ class Access extends LDAPUtility { string $filter, string $base, ?array &$attr, - ?int $limit, + ?int $pageSize, ?int $offset ) { // See if we have a resource, in case not cancel with message @@ -1112,13 +1100,13 @@ class Access extends LDAPUtility { //check whether paged search should be attempted try { - $pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, (int)$limit, (int)$offset); + [$pagedSearchOK, $pageSize, $cookie] = $this->initPagedSearch($filter, $base, $attr, (int)$pageSize, (int)$offset); } catch (NoMoreResults $e) { // beyond last results page return false; } - $sr = $this->invokeLDAPMethod('search', $base, $filter, $attr); + $sr = $this->invokeLDAPMethod('search', $base, $filter, $attr, 0, 0, $pageSize, $cookie); $error = $this->ldap->errno($this->connection->getConnectionResource()); if (!$this->ldap->isResource($sr) || $error !== 0) { $this->logger->error('Attempt for Paging? ' . print_r($pagedSearchOK, true), ['app' => 'user_ldap']); @@ -1751,7 +1739,7 @@ class Access extends LDAPUtility { /** * converts a binary ObjectGUID into a string representation * - * @param string $oguid the ObjectGUID in it's binary form as retrieved from AD + * @param string $oguid the ObjectGUID in its binary form as retrieved from AD * @link https://www.php.net/manual/en/function.ldap-get-values-len.php#73198 */ private function convertObjectGUID2Str(string $oguid): string { @@ -1777,8 +1765,8 @@ class Access extends LDAPUtility { /** * the first three blocks of the string-converted GUID happen to be in * reverse order. In order to use it in a filter, this needs to be - * corrected. Furthermore the dashes need to be replaced and \\ preprended - * to every two hax figures. + * corrected. Furthermore the dashes need to be replaced and \\ prepended + * to every two hex figures. * * If an invalid string is passed, it will be returned without change. */ @@ -1910,7 +1898,6 @@ class Access extends LDAPUtility { if ($this->lastCookie === '') { return; } - $this->invokeLDAPMethod('controlPagedResult', 0, false); $this->getPagedSearchResultState(); $this->lastCookie = ''; } @@ -1927,7 +1914,7 @@ class Access extends LDAPUtility { * @return bool */ public function hasMoreResults() { - if (empty($this->lastCookie) && $this->lastCookie !== '0') { + if ($this->lastCookie === '') { // as in RFC 2696, when all results are returned, the cookie will // be empty. return false; @@ -1951,11 +1938,11 @@ class Access extends LDAPUtility { * Prepares a paged search, if possible * * @param string $filter the LDAP filter for the search - * @param string[] $bases an array containing the LDAP subtree(s) that shall be searched + * @param string $base the LDAP subtree that shall be searched * @param string[] $attr optional, when a certain attribute shall be filtered outside * @param int $limit * @param int $offset - * @return bool|true + * @return array{bool, int, string} * @throws ServerNotAvailableException * @throws NoMoreResults */ @@ -1963,29 +1950,29 @@ class Access extends LDAPUtility { string $filter, string $base, ?array $attr, - int $limit, + int $pageSize, int $offset - ): bool { + ): array { $pagedSearchOK = false; - if ($limit !== 0) { + if ($pageSize !== 0) { $this->logger->debug( - 'initializing paged search for filter {filter}, base {base}, attr {attr}, limit {limit}, offset {offset}', + 'initializing paged search for filter {filter}, base {base}, attr {attr}, pageSize {pageSize}, offset {offset}', [ 'app' => 'user_ldap', 'filter' => $filter, 'base' => $base, 'attr' => $attr, - 'limit' => $limit, + 'pageSize' => $pageSize, 'offset' => $offset ] ); - //get the cookie from the search for the previous search, required by LDAP - if (empty($this->lastCookie) && $this->lastCookie !== "0" && ($offset > 0)) { + // Get the cookie from the search for the previous search, required by LDAP + if (($this->lastCookie === '') && ($offset > 0)) { // no cookie known from a potential previous search. We need // to start from 0 to come to the desired page. cookie value // of '0' is valid, because 389ds - $reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit; - $this->search($filter, $base, $attr, $limit, $reOffset, true); + $reOffset = ($offset - $pageSize) < 0 ? 0 : $offset - $pageSize; + $this->search($filter, $base, $attr, $pageSize, $reOffset, true); if (!$this->hasMoreResults()) { // when the cookie is reset with != 0 offset, there are no further // results, so stop. @@ -1996,19 +1983,15 @@ class Access extends LDAPUtility { //since offset = 0, this is a new search. We abandon other searches that might be ongoing. $this->abandonPagedSearch(); } - $pagedSearchOK = true === $this->invokeLDAPMethod( - 'controlPagedResult', $limit, false - ); - if ($pagedSearchOK) { - $this->logger->debug('Ready for a paged search', ['app' => 'user_ldap']); - } - /* ++ Fixing RHDS searches with pages with zero results ++ - * We coudn't get paged searches working with our RHDS for login ($limit = 0), - * due to pages with zero results. - * So we added "&& !empty($this->lastCookie)" to this test to ignore pagination - * if we don't have a previous paged search. - */ - } elseif (!empty($this->lastCookie)) { + $this->logger->debug('Ready for a paged search', ['app' => 'user_ldap']); + return [true, $pageSize, $this->lastCookie]; + /* ++ Fixing RHDS searches with pages with zero results ++ + * We couldn't get paged searches working with our RHDS for login ($limit = 0), + * due to pages with zero results. + * So we added "&& !empty($this->lastCookie)" to this test to ignore pagination + * if we don't have a previous paged search. + */ + } elseif ($this->lastCookie !== '') { // a search without limit was requested. However, if we do use // Paged Search once, we always must do it. This requires us to // initialize it with the configured page size. @@ -2016,11 +1999,10 @@ class Access extends LDAPUtility { // in case someone set it to 0 … use 500, otherwise no results will // be returned. $pageSize = (int)$this->connection->ldapPagingSize > 0 ? (int)$this->connection->ldapPagingSize : 500; - $pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult', - $pageSize, false); + return [true, $pageSize, $this->lastCookie]; } - return $pagedSearchOK; + return [false, $pageSize, '']; } /** diff --git a/apps/user_ldap/lib/AppInfo/Application.php b/apps/user_ldap/lib/AppInfo/Application.php index 79998a580e5..757ac141d3d 100644 --- a/apps/user_ldap/lib/AppInfo/Application.php +++ b/apps/user_ldap/lib/AppInfo/Application.php @@ -31,6 +31,7 @@ use OCA\Files_External\Service\BackendService; use OCA\User_LDAP\Controller\RenewPasswordController; use OCA\User_LDAP\Events\GroupBackendRegistered; use OCA\User_LDAP\Events\UserBackendRegistered; +use OCA\User_LDAP\FilesystemHelper; use OCA\User_LDAP\Group_Proxy; use OCA\User_LDAP\GroupPluginManager; use OCA\User_LDAP\Handler\ExtStorageConfigHandler; @@ -38,6 +39,7 @@ use OCA\User_LDAP\Helper; use OCA\User_LDAP\ILDAPWrapper; use OCA\User_LDAP\LDAP; use OCA\User_LDAP\Notification\Notifier; +use OCA\User_LDAP\User\Manager; use OCA\User_LDAP\User_Proxy; use OCA\User_LDAP\UserPluginManager; use OCP\AppFramework\App; @@ -46,10 +48,17 @@ use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\AppFramework\IAppContainer; use OCP\EventDispatcher\IEventDispatcher; +use OCP\IAvatarManager; +use OCP\IConfig; use OCP\IGroupManager; use OCP\IL10N; +use OCP\Image; use OCP\IServerContainer; +use OCP\IUserManager; use OCP\Notification\IManager as INotificationManager; +use OCP\Share\IManager as IShareManager; +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Application extends App implements IBootstrap { @@ -87,6 +96,24 @@ class Application extends App implements IBootstrap { public function register(IRegistrationContext $context): void { $context->registerNotifierService(Notifier::class); + + $context->registerService( + Manager::class, + function (ContainerInterface $c) { + return new Manager( + $c->get(IConfig::class), + $c->get(FilesystemHelper::class), + $c->get(LoggerInterface::class), + $c->get(IAvatarManager::class), + $c->get(Image::class), + $c->get(IUserManager::class), + $c->get(INotificationManager::class), + $c->get(IShareManager::class), + ); + }, + // the instance is specific to a lazy bound Access instance, thus cannot be shared. + false + ); } public function boot(IBootContext $context): void { diff --git a/apps/user_ldap/lib/DataCollector/LdapDataCollector.php b/apps/user_ldap/lib/DataCollector/LdapDataCollector.php index cb61de96e37..833b314b199 100644 --- a/apps/user_ldap/lib/DataCollector/LdapDataCollector.php +++ b/apps/user_ldap/lib/DataCollector/LdapDataCollector.php @@ -28,12 +28,13 @@ use OCP\AppFramework\Http\Response; use OCP\DataCollector\AbstractDataCollector; class LdapDataCollector extends AbstractDataCollector { - public function startLdapRequest(string $query, array $args): void { + public function startLdapRequest(string $query, array $args, array $backtrace): void { $this->data[] = [ 'start' => microtime(true), 'query' => $query, 'args' => $args, 'end' => microtime(true), + 'backtrace' => $backtrace, ]; } diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php index 5e3d3afc140..81cb30dd25b 100644 --- a/apps/user_ldap/lib/Group_LDAP.php +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -24,6 +24,8 @@ * @author Victor Dubiniuk <dubiniuk@owncloud.com> * @author Vinicius Cubas Brand <vinicius@eita.org.br> * @author Xuanwo <xuanwo@yunify.com> + * @author Carl Schwan <carl@carlschwan.eu> + * @author Côme Chilliet <come.chilliet@nextcloud.com> * * @license AGPL-3.0 * @@ -42,34 +44,30 @@ */ namespace OCA\User_LDAP; -use Closure; use Exception; -use OC; use OCP\Cache\CappedMemoryCache; -use OC\ServerNotAvailableException; -use OCP\Group\Backend\IGetDisplayNameBackend; -use OCP\Group\Backend\IDeleteGroupBackend; use OCP\GroupInterface; +use OCP\Group\Backend\IDeleteGroupBackend; +use OCP\Group\Backend\IGetDisplayNameBackend; +use OC\ServerNotAvailableException; use Psr\Log\LoggerInterface; class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, IGetDisplayNameBackend, IDeleteGroupBackend { - protected $enabled = false; + protected bool $enabled = false; /** @var CappedMemoryCache<string[]> $cachedGroupMembers array of users with gid as key */ protected CappedMemoryCache $cachedGroupMembers; - /** @var CappedMemoryCache<string[]> $cachedGroupsByMember array of groups with uid as key */ + /** @var CappedMemoryCache<array[]> $cachedGroupsByMember array of groups with uid as key */ protected CappedMemoryCache $cachedGroupsByMember; /** @var CappedMemoryCache<string[]> $cachedNestedGroups array of groups with gid (DN) as key */ protected CappedMemoryCache $cachedNestedGroups; - /** @var GroupPluginManager */ - protected $groupPluginManager; - /** @var LoggerInterface */ - protected $logger; + protected GroupPluginManager $groupPluginManager; + protected LoggerInterface $logger; /** * @var string $ldapGroupMemberAssocAttr contains the LDAP setting (in lower case) with the same name */ - protected $ldapGroupMemberAssocAttr; + protected string $ldapGroupMemberAssocAttr; public function __construct(Access $access, GroupPluginManager $groupPluginManager) { parent::__construct($access); @@ -83,20 +81,19 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I $this->cachedGroupsByMember = new CappedMemoryCache(); $this->cachedNestedGroups = new CappedMemoryCache(); $this->groupPluginManager = $groupPluginManager; - $this->logger = OC::$server->get(LoggerInterface::class); + $this->logger = \OCP\Server::get(LoggerInterface::class); $this->ldapGroupMemberAssocAttr = strtolower((string)$gAssoc); } /** - * is user in group? + * Check if user is in group * * @param string $uid uid of the user * @param string $gid gid of the group - * @return bool * @throws Exception * @throws ServerNotAvailableException */ - public function inGroup($uid, $gid) { + public function inGroup($uid, $gid): bool { if (!$this->enabled) { return false; } @@ -240,24 +237,17 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** + * Get group members from dn. + * @psalm-param array<string, bool> $seen List of DN that have already been processed. * @throws ServerNotAvailableException */ - private function _groupMembers(string $dnGroup, ?array &$seen = null): array { - if ($seen === null) { - $seen = []; - // the root entry has to be marked as processed to avoind infinit loops, - // but not included in the results laters on - $excludeFromResult = $dnGroup; - } - // cache only base groups, otherwise groups get additional unwarranted members - $shouldCacheResult = count($seen) === 0; - - static $rawMemberReads = []; // runtime cache for intermediate ldap read results - $allMembers = []; - - if (array_key_exists($dnGroup, $seen)) { + private function _groupMembers(string $dnGroup, array $seen = [], bool &$recursive = false): array { + if (isset($seen[$dnGroup])) { + $recursive = true; return []; } + $seen[$dnGroup] = true; + // used extensively in cron job, caching makes sense for nested groups $cacheKey = '_groupMembers' . $dnGroup; $groupMembers = $this->access->connection->getFromCache($cacheKey); @@ -271,7 +261,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I && $this->access->connection->ldapMatchingRuleInChainState !== Configuration::LDAP_SERVER_FEATURE_UNAVAILABLE ) { $attemptedLdapMatchingRuleInChain = true; - // compatibility hack with servers supporting :1.2.840.113556.1.4.1941:, and others) + // Use matching rule 1.2.840.113556.1.4.1941 if available (LDAP_MATCHING_RULE_IN_CHAIN) $filter = $this->access->combineFilterWithAnd([ $this->access->connection->ldapUserFilter, $this->access->connection->ldapUserDisplayName . '=*', @@ -286,40 +276,50 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I return $carry; }, []); if ($this->access->connection->ldapMatchingRuleInChainState === Configuration::LDAP_SERVER_FEATURE_AVAILABLE) { + $this->access->connection->writeToCache($cacheKey, $result); return $result; } elseif (!empty($memberRecords)) { $this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_AVAILABLE; $this->access->connection->saveConfiguration(); + $this->access->connection->writeToCache($cacheKey, $result); return $result; } // when feature availability is unknown, and the result is empty, continue and test with original approach } - $seen[$dnGroup] = 1; - $members = $rawMemberReads[$dnGroup] ?? null; - if ($members === null) { - $members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr); - $rawMemberReads[$dnGroup] = $members; - } + $allMembers = []; + $members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr); if (is_array($members)) { - $fetcher = function ($memberDN) use (&$seen) { - return $this->_groupMembers($memberDN, $seen); - }; - $allMembers = $this->walkNestedGroups($dnGroup, $fetcher, $members, $seen); + if ((int)$this->access->connection->ldapNestedGroups === 1) { + while ($recordDn = array_shift($members)) { + $nestedMembers = $this->_groupMembers($recordDn, $seen, $recursive); + if (!empty($nestedMembers)) { + // Group, queue its members for processing + $members = array_merge($members, $nestedMembers); + } else { + // User (or empty group, or previously seen group), add it to the member list + $allMembers[] = $recordDn; + } + } + } else { + $allMembers = $members; + } } $allMembers += $this->getDynamicGroupMembers($dnGroup); - if (isset($excludeFromResult)) { - $index = array_search($excludeFromResult, $allMembers, true); - if ($index !== false) { - unset($allMembers[$index]); - } + + $allMembers = array_unique($allMembers); + + // A group cannot be a member of itself + $index = array_search($dnGroup, $allMembers, true); + if ($index !== false) { + unset($allMembers[$index]); } - if ($shouldCacheResult) { + if (!$recursive) { $this->access->connection->writeToCache($cacheKey, $allMembers); - unset($rawMemberReads[$dnGroup]); } + if (isset($attemptedLdapMatchingRuleInChain) && $this->access->connection->ldapMatchingRuleInChainState === Configuration::LDAP_SERVER_FEATURE_UNKNOWN && !empty($allMembers) @@ -327,75 +327,47 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I $this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_UNAVAILABLE; $this->access->connection->saveConfiguration(); } + return $allMembers; } /** + * @return string[] * @throws ServerNotAvailableException */ - private function _getGroupDNsFromMemberOf(string $dn): array { - $groups = $this->access->readAttribute($dn, 'memberOf'); - if (!is_array($groups)) { + private function _getGroupDNsFromMemberOf(string $dn, array &$seen = []): array { + if (isset($seen[$dn])) { return []; } + $seen[$dn] = true; - $fetcher = function ($groupDN) { - if (isset($this->cachedNestedGroups[$groupDN])) { - $nestedGroups = $this->cachedNestedGroups[$groupDN]; - } else { - $nestedGroups = $this->access->readAttribute($groupDN, 'memberOf'); - if (!is_array($nestedGroups)) { - $nestedGroups = []; - } - $this->cachedNestedGroups[$groupDN] = $nestedGroups; - } - return $nestedGroups; - }; - - $groups = $this->walkNestedGroups($dn, $fetcher, $groups); - return $this->filterValidGroups($groups); - } - - private function walkNestedGroups(string $dn, Closure $fetcher, array $list, array &$seen = []): array { - $nesting = (int)$this->access->connection->ldapNestedGroups; - // depending on the input, we either have a list of DNs or a list of LDAP records - // also, the output expects either DNs or records. Testing the first element should suffice. - $recordMode = is_array($list) && isset($list[0]) && is_array($list[0]) && isset($list[0]['dn'][0]); - - if ($nesting !== 1) { - if ($recordMode) { - // the keys are numeric, but should hold the DN - return array_reduce($list, function ($transformed, $record) use ($dn) { - if ($record['dn'][0] != $dn) { - $transformed[$record['dn'][0]] = $record; - } - return $transformed; - }, []); - } - return $list; + if (isset($this->cachedNestedGroups[$dn])) { + return $this->cachedNestedGroups[$dn]; } - while ($record = array_shift($list)) { - $recordDN = $record['dn'][0] ?? $record; - if ($recordDN === $dn || array_key_exists($recordDN, $seen)) { - // Prevent loops - continue; - } - $fetched = $fetcher($record); - $list = array_merge($list, $fetched); - if (!isset($seen[$recordDN]) || is_bool($seen[$recordDN]) && is_array($record)) { - $seen[$recordDN] = $record; + $allGroups = []; + $groups = $this->access->readAttribute($dn, 'memberOf'); + if (is_array($groups)) { + if ((int)$this->access->connection->ldapNestedGroups === 1) { + while ($recordDn = array_shift($groups)) { + $nestedParents = $this->_getGroupDNsFromMemberOf($recordDn, $seen); + $groups = array_merge($groups, $nestedParents); + $allGroups[] = $recordDn; + } + } else { + $allGroups = $groups; } } - // on record mode, filter out intermediate state - return $recordMode ? array_filter($seen, 'is_array') : array_keys($seen); + // We do not perform array_unique here at it is done in getUserGroups later + $this->cachedNestedGroups[$dn] = $allGroups; + return $this->filterValidGroups($allGroups); } /** - * translates a gidNumber into an ownCloud internal name + * Translates a gidNumber into the Nextcloud internal name. * - * @return string|bool + * @return string|false The nextcloud internal name. * @throws Exception * @throws ServerNotAvailableException */ @@ -416,6 +388,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** + * @return string|null|false The name of the group * @throws ServerNotAvailableException * @throws Exception */ @@ -438,9 +411,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** - * returns the entry's gidNumber - * - * @return string|bool + * @return string|bool The entry's gidNumber * @throws ServerNotAvailableException */ private function getEntryGidNumber(string $dn, string $attribute) { @@ -452,7 +423,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** - * @return string|bool + * @return string|bool The group's gidNumber * @throws ServerNotAvailableException */ public function getGroupGidNumber(string $dn) { @@ -460,9 +431,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** - * returns the user's gidNumber - * - * @return string|bool + * @return string|bool The user's gidNumber * @throws ServerNotAvailableException */ public function getUserGidNumber(string $dn) { @@ -497,8 +466,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** - * returns a list of users that have the given group as gid number - * + * @return array A list of users that have the given group as gid number * @throws ServerNotAvailableException */ public function getUsersInGidNumber( @@ -525,7 +493,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I /** * @throws ServerNotAvailableException - * @return bool + * @return false|string */ public function getUserGroupByGid(string $dn) { $groupID = $this->getUserGidNumber($dn); @@ -540,9 +508,9 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** - * translates a primary group ID into an Nextcloud internal name + * Translates a primary group ID into an Nextcloud internal name * - * @return string|bool + * @return string|false * @throws Exception * @throws ServerNotAvailableException */ @@ -567,9 +535,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** - * returns the entry's primary group ID - * - * @return string|bool + * @return string|false The entry's group Id * @throws ServerNotAvailableException */ private function getEntryGroupID(string $dn, string $attribute) { @@ -581,7 +547,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** - * @return string|bool + * @return string|false The entry's primary group Id * @throws ServerNotAvailableException */ public function getGroupPrimaryGroupID(string $dn) { @@ -589,7 +555,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** - * @return string|bool + * @return string|false * @throws ServerNotAvailableException */ public function getUserPrimaryGroupIDs(string $dn) { @@ -669,7 +635,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** - * @return string|bool + * @return string|false * @throws ServerNotAvailableException */ public function getUserPrimaryGroup(string $dn) { @@ -691,7 +657,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I * This function includes groups based on dynamic group membership. * * @param string $uid Name of the user - * @return array with group names + * @return string[] Group names * @throws Exception * @throws ServerNotAvailableException */ @@ -721,7 +687,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I $groupsToMatch = $this->access->fetchListOfGroups( $this->access->connection->ldapGroupFilter, ['dn', $dynamicGroupMemberURL]); foreach ($groupsToMatch as $dynamicGroup) { - if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) { + if (!isset($dynamicGroup[$dynamicGroupMemberURL][0])) { continue; } $pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '('); @@ -762,64 +728,48 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I && $this->ldapGroupMemberAssocAttr !== 'memberuid' && $this->ldapGroupMemberAssocAttr !== 'zimbramailforwardingaddress') { $groupDNs = $this->_getGroupDNsFromMemberOf($userDN); - if (is_array($groupDNs)) { - foreach ($groupDNs as $dn) { - $groupName = $this->access->dn2groupname($dn); - if (is_string($groupName)) { - // be sure to never return false if the dn could not be - // resolved to a name, for whatever reason. - $groups[] = $groupName; - } + foreach ($groupDNs as $dn) { + $groupName = $this->access->dn2groupname($dn); + if (is_string($groupName)) { + // be sure to never return false if the dn could not be + // resolved to a name, for whatever reason. + $groups[] = $groupName; } } + } else { + // uniqueMember takes DN, memberuid the uid, so we need to distinguish + switch ($this->ldapGroupMemberAssocAttr) { + case 'uniquemember': + case 'member': + $uid = $userDN; + break; - if ($primaryGroup !== false) { - $groups[] = $primaryGroup; - } - if ($gidGroupName !== false) { - $groups[] = $gidGroupName; - } - $this->access->connection->writeToCache($cacheKey, $groups); - return $groups; - } - - //uniqueMember takes DN, memberuid the uid, so we need to distinguish - 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; + 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; - } + default: + // just in case + $uid = $userDN; + break; + } - if ($uid !== false) { - if (isset($this->cachedGroupsByMember[$uid])) { - $groups = array_merge($groups, $this->cachedGroupsByMember[$uid]); - } else { + if ($uid !== false) { $groupsByMember = array_values($this->getGroupsByMember($uid)); $groupsByMember = $this->access->nextcloudGroupNames($groupsByMember); - $this->cachedGroupsByMember[$uid] = $groupsByMember; $groups = array_merge($groups, $groupsByMember); } } @@ -838,18 +788,19 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** + * @return array[] * @throws ServerNotAvailableException */ - private function getGroupsByMember(string $dn, array &$seen = null): array { - if ($seen === null) { - $seen = []; - } - if (array_key_exists($dn, $seen)) { - // avoid loops + private function getGroupsByMember(string $dn, array &$seen = []): array { + if (isset($seen[$dn])) { return []; } - $allGroups = []; $seen[$dn] = true; + + if (isset($this->cachedGroupsByMember[$dn])) { + return $this->cachedGroupsByMember[$dn]; + } + $filter = $this->access->connection->ldapGroupMemberAssocAttr . '=' . $dn; if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') { @@ -862,22 +813,24 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I $filter = $this->access->combineFilterWithAnd([$filter, $this->access->connection->ldapGroupFilter]); } + $allGroups = []; $groups = $this->access->fetchListOfGroups($filter, [strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']); - $fetcher = function ($dn) use (&$seen) { - if (is_array($dn) && isset($dn['dn'][0])) { - $dn = $dn['dn'][0]; - } - return $this->getGroupsByMember($dn, $seen); - }; - if (empty($dn)) { - $dn = ""; + if ($nesting === 1) { + while ($record = array_shift($groups)) { + // Note: this has no effect when ldapGroupMemberAssocAttr is uid based + $nestedParents = $this->getGroupsByMember($record['dn'][0], $seen); + $groups = array_merge($groups, $nestedParents); + $allGroups[] = $record; + } + } else { + $allGroups = $groups; } - $allGroups = $this->walkNestedGroups($dn, $fetcher, $groups, $seen); $visibleGroups = $this->filterValidGroups($allGroups); - return array_intersect_key($allGroups, $visibleGroups); + $this->cachedGroupsByMember[$dn] = $visibleGroups; + return $visibleGroups; } /** @@ -919,7 +872,7 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I $groupDN = $this->access->groupname2dn($gid); if (!$groupDN) { - // group couldn't be found, return empty resultset + // group couldn't be found, return empty result-set $this->access->connection->writeToCache($cacheKey, []); return []; } @@ -1176,6 +1129,9 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I } /** + * @template T + * @param array<array-key, T> $listOfGroups + * @return array<array-key, T> * @throws ServerNotAvailableException * @throws Exception */ @@ -1183,7 +1139,11 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I $validGroupDNs = []; foreach ($listOfGroups as $key => $item) { $dn = is_string($item) ? $item : $item['dn'][0]; - $gid = $this->access->dn2groupname($dn); + if (is_array($item) && !isset($item[$this->access->connection->ldapGroupDisplayName][0])) { + continue; + } + $name = $item[$this->access->connection->ldapGroupDisplayName][0] ?? null; + $gid = $this->access->dn2groupname($dn, $name); if (!$gid) { continue; } diff --git a/apps/user_ldap/lib/Group_Proxy.php b/apps/user_ldap/lib/Group_Proxy.php index ea2fcce679c..f8bdae67b72 100644 --- a/apps/user_ldap/lib/Group_Proxy.php +++ b/apps/user_ldap/lib/Group_Proxy.php @@ -34,18 +34,32 @@ use OCP\Group\Backend\INamedBackend; class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGetDisplayNameBackend, INamedBackend, IDeleteGroupBackend { private $backends = []; - private $refBackend = null; + private ?Group_LDAP $refBackend = null; + private Helper $helper; + private GroupPluginManager $groupPluginManager; + private bool $isSetUp = false; public function __construct(Helper $helper, ILDAPWrapper $ldap, GroupPluginManager $groupPluginManager) { parent::__construct($ldap); - $serverConfigPrefixes = $helper->getServerConfigurationPrefixes(true); + $this->helper = $helper; + $this->groupPluginManager = $groupPluginManager; + } + + protected function setup(): void { + if ($this->isSetUp) { + return; + } + + $serverConfigPrefixes = $this->helper->getServerConfigurationPrefixes(true); foreach ($serverConfigPrefixes as $configPrefix) { $this->backends[$configPrefix] = - new \OCA\User_LDAP\Group_LDAP($this->getAccess($configPrefix), $groupPluginManager); + new Group_LDAP($this->getAccess($configPrefix), $this->groupPluginManager); if (is_null($this->refBackend)) { $this->refBackend = &$this->backends[$configPrefix]; } } + + $this->isSetUp = true; } /** @@ -57,6 +71,8 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet * @return mixed the result of the method or false */ protected function walkBackends($id, $method, $parameters) { + $this->setup(); + $gid = $id; $cacheKey = $this->getGroupCacheKey($gid); foreach ($this->backends as $configPrefix => $backend) { @@ -80,6 +96,8 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet * @return mixed the result of the method or false */ protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen) { + $this->setup(); + $gid = $id; $cacheKey = $this->getGroupCacheKey($gid); $prefix = $this->getFromCache($cacheKey); @@ -105,6 +123,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet } protected function activeBackends(): int { + $this->setup(); return count($this->backends); } @@ -131,8 +150,9 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet * if the user exists at all. */ public function getUserGroups($uid) { - $groups = []; + $this->setup(); + $groups = []; foreach ($this->backends as $backend) { $backendGroups = $backend->getUserGroups($uid); if (is_array($backendGroups)) { @@ -149,8 +169,9 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet * @return string[] with user ids */ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { - $users = []; + $this->setup(); + $users = []; foreach ($this->backends as $backend) { $backendUsers = $backend->usersInGroup($gid, $search, $limit, $offset); if (is_array($backendUsers)) { @@ -237,8 +258,9 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet * Returns a list with all groups */ public function getGroups($search = '', $limit = -1, $offset = 0) { - $groups = []; + $this->setup(); + $groups = []; foreach ($this->backends as $backend) { $backendGroups = $backend->getGroups($search, $limit, $offset); if (is_array($backendGroups)) { @@ -269,6 +291,7 @@ class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGet * compared with \OCP\GroupInterface::CREATE_GROUP etc. */ public function implementsActions($actions) { + $this->setup(); //it's the same across all our user backends obviously return $this->refBackend->implementsActions($actions); } diff --git a/apps/user_ldap/lib/ILDAPWrapper.php b/apps/user_ldap/lib/ILDAPWrapper.php index e72d85ac2b9..6ec88effa5f 100644 --- a/apps/user_ldap/lib/ILDAPWrapper.php +++ b/apps/user_ldap/lib/ILDAPWrapper.php @@ -30,7 +30,6 @@ namespace OCA\User_LDAP; interface ILDAPWrapper { - //LDAP functions in use /** @@ -48,21 +47,11 @@ interface ILDAPWrapper { * connect to an LDAP server * @param string $host The host to connect to * @param string $port The port to connect to - * @return mixed a link resource on success, otherwise false + * @return resource|\LDAP\Connection|false a link resource on success, otherwise false */ public function connect($host, $port); /** - * Send LDAP pagination control - * @param resource|\LDAP\Connection $link LDAP link resource - * @param int $pageSize number of results per page - * @param bool $isCritical Indicates whether the pagination is critical of not. - * @param string $cookie structure sent by LDAP server - * @return bool true on success, false otherwise - */ - public function controlPagedResult($link, $pageSize, $isCritical); - - /** * Retrieve the LDAP pagination cookie * @param resource|\LDAP\Connection $link LDAP link resource * @param resource|\LDAP\Result $result LDAP result resource @@ -116,7 +105,7 @@ interface ILDAPWrapper { * Get attributes from a search result entry * @param resource|\LDAP\Connection $link LDAP link resource * @param resource|\LDAP\ResultEntry $result LDAP result resource - * @return array containing the results, false on error + * @return array|false containing the results, false on error * */ public function getAttributes($link, $result); @@ -124,7 +113,7 @@ interface ILDAPWrapper { * Get the DN of a result entry * @param resource|\LDAP\Connection $link LDAP link resource * @param resource|\LDAP\ResultEntry $result LDAP result resource - * @return string containing the DN, false on error + * @return string|false containing the DN, false on error */ public function getDN($link, $result); @@ -132,7 +121,7 @@ interface ILDAPWrapper { * Get all result entries * @param resource|\LDAP\Connection $link LDAP link resource * @param resource|\LDAP\Result $result LDAP result resource - * @return array containing the results, false on error + * @return array|false containing the results, false on error */ public function getEntries($link, $result); @@ -164,7 +153,7 @@ interface ILDAPWrapper { * @param int $limit optional, limits the result entries * @return resource|\LDAP\Result|false an LDAP search result resource, false on error */ - public function search($link, $baseDN, $filter, $attr, $attrsOnly = 0, $limit = 0); + public function search($link, string $baseDN, string $filter, array $attr, int $attrsOnly = 0, int $limit = 0, int $pageSize = 0, string $cookie = ''); /** * Replace the value of a userPassword by $password @@ -176,6 +165,13 @@ interface ILDAPWrapper { public function modReplace($link, $userDN, $password); /** + * Performs a PASSWD extended operation. + * @param resource|\LDAP\Connection $link LDAP link resource + * @return bool|string The generated password if new_password is empty or omitted. Otherwise true on success and false on failure. + */ + public function exopPasswd($link, string $userDN, string $oldPassword, string $password); + + /** * Sets the value of the specified option to be $value * @param resource|\LDAP\Connection $link LDAP link resource * @param int $option a defined LDAP Server option diff --git a/apps/user_ldap/lib/Jobs/CleanUp.php b/apps/user_ldap/lib/Jobs/CleanUp.php index 1fb423b5faf..22067d81a9d 100644 --- a/apps/user_ldap/lib/Jobs/CleanUp.php +++ b/apps/user_ldap/lib/Jobs/CleanUp.php @@ -107,7 +107,7 @@ class CleanUp extends TimedJob { if (isset($arguments['mapping'])) { $this->mapping = $arguments['mapping']; } else { - $this->mapping = new UserMapping($this->db); + $this->mapping = \OCP\Server::get(UserMapping::class); } if (isset($arguments['deletedUsersIndex'])) { diff --git a/apps/user_ldap/lib/Jobs/Sync.php b/apps/user_ldap/lib/Jobs/Sync.php index d9171f4aab7..b231089b79b 100644 --- a/apps/user_ldap/lib/Jobs/Sync.php +++ b/apps/user_ldap/lib/Jobs/Sync.php @@ -357,7 +357,7 @@ class Sync extends TimedJob { if (isset($argument['mapper'])) { $this->mapper = $argument['mapper']; } else { - $this->mapper = new UserMapping($this->dbc); + $this->mapper = \OCP\Server::get(UserMapping::class); } if (isset($argument['connectionFactory'])) { diff --git a/apps/user_ldap/lib/LDAP.php b/apps/user_ldap/lib/LDAP.php index 25cd0954295..5730907b653 100644 --- a/apps/user_ldap/lib/LDAP.php +++ b/apps/user_ldap/lib/LDAP.php @@ -37,21 +37,16 @@ use OCP\Profiler\IProfiler; use OC\ServerNotAvailableException; use OCA\User_LDAP\DataCollector\LdapDataCollector; use OCA\User_LDAP\Exceptions\ConstraintViolationException; -use OCA\User_LDAP\PagedResults\IAdapter; -use OCA\User_LDAP\PagedResults\Php73; +use Psr\Log\LoggerInterface; class LDAP implements ILDAPWrapper { - protected $logFile = ''; - protected $curFunc = ''; - protected $curArgs = []; - - /** @var IAdapter */ - protected $pagedResultsAdapter; + protected string $logFile = ''; + protected array $curArgs = []; + protected LoggerInterface $logger; private ?LdapDataCollector $dataCollector = null; public function __construct(string $logFile = '') { - $this->pagedResultsAdapter = new Php73(); $this->logFile = $logFile; /** @var IProfiler $profiler */ @@ -60,6 +55,8 @@ class LDAP implements ILDAPWrapper { $this->dataCollector = new LdapDataCollector(); $profiler->add($this->dataCollector); } + + $this->logger = \OCP\Server::get(LoggerInterface::class); } /** @@ -73,10 +70,12 @@ class LDAP implements ILDAPWrapper { * {@inheritDoc} */ public function connect($host, $port) { - if (strpos($host, '://') === false) { + $pos = strpos($host, '://'); + if ($pos === false) { $host = 'ldap://' . $host; + $pos = 4; } - if (strpos($host, ':', strpos($host, '://') + 1) === false && !empty($port)) { + if (strpos($host, ':', $pos + 1) === false && !empty($port)) { //ldap_connect ignores port parameter when URLs are passed $host .= ':' . $port; } @@ -87,39 +86,30 @@ class LDAP implements ILDAPWrapper { * {@inheritDoc} */ public function controlPagedResultResponse($link, $result, &$cookie): bool { - $this->preFunctionCall( - $this->pagedResultsAdapter->getResponseCallFunc(), - $this->pagedResultsAdapter->getResponseCallArgs([$link, $result, &$cookie]) - ); - - $result = $this->pagedResultsAdapter->responseCall($link); - $cookie = $this->pagedResultsAdapter->getCookie($link); - - if ($this->isResultFalse($result)) { - $this->postFunctionCall(); + $errorCode = 0; + $errorMsg = ''; + $controls = []; + $matchedDn = null; + $referrals = []; + + /** Cannot use invokeLDAPMethod because arguments are passed by reference */ + $this->preFunctionCall('ldap_parse_result', [$link, $result]); + $success = ldap_parse_result($link, $result, + $errorCode, + $matchedDn, + $errorMsg, + $referrals, + $controls); + if ($errorCode !== 0) { + $this->processLDAPError($link, 'ldap_parse_result', $errorCode, $errorMsg); } - - return $result; - } - - /** - * {@inheritDoc} - */ - public function controlPagedResult($link, $pageSize, $isCritical) { - $fn = $this->pagedResultsAdapter->getRequestCallFunc(); - $this->pagedResultsAdapter->setRequestParameters($link, $pageSize, $isCritical); - if ($fn === null) { - return true; + if ($this->dataCollector !== null) { + $this->dataCollector->stopLastLdapRequest(); } - $this->preFunctionCall($fn, $this->pagedResultsAdapter->getRequestCallArgs($link)); - $result = $this->pagedResultsAdapter->requestCall($link); - - if ($this->isResultFalse($result)) { - $this->postFunctionCall(); - } + $cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] ?? ''; - return $result; + return $success; } /** @@ -146,7 +136,7 @@ class LDAP implements ILDAPWrapper { /** * Splits DN into its component parts * @param string $dn - * @param int @withAttrib + * @param int $withAttrib * @return array|false * @link https://www.php.net/manual/en/function.ldap-explode-dn.php */ @@ -193,14 +183,22 @@ class LDAP implements ILDAPWrapper { * {@inheritDoc} */ public function read($link, $baseDN, $filter, $attr) { - $this->pagedResultsAdapter->setReadArgs($link, $baseDN, $filter, $attr); - return $this->invokeLDAPMethod('read', ...$this->pagedResultsAdapter->getReadArgs($link)); + return $this->invokeLDAPMethod('read', $link, $baseDN, $filter, $attr, 0, -1); } /** * {@inheritDoc} */ - public function search($link, $baseDN, $filter, $attr, $attrsOnly = 0, $limit = 0) { + public function search($link, $baseDN, $filter, $attr, $attrsOnly = 0, $limit = 0, int $pageSize = 0, string $cookie = '') { + $serverControls = [[ + 'oid' => LDAP_CONTROL_PAGEDRESULTS, + 'value' => [ + 'size' => $pageSize, + 'cookie' => $cookie, + ], + 'iscritical' => false, + ]]; + $oldHandler = set_error_handler(function ($no, $message, $file, $line) use (&$oldHandler) { if (strpos($message, 'Partial search results returned: Sizelimit exceeded') !== false) { return true; @@ -209,8 +207,7 @@ class LDAP implements ILDAPWrapper { return true; }); try { - $this->pagedResultsAdapter->setSearchArgs($link, $baseDN, $filter, $attr, $attrsOnly, $limit); - $result = $this->invokeLDAPMethod('search', ...$this->pagedResultsAdapter->getSearchArgs($link)); + $result = $this->invokeLDAPMethod('search', $link, $baseDN, $filter, $attr, $attrsOnly, $limit, -1, LDAP_DEREF_NEVER, $serverControls); restore_error_handler(); return $result; @@ -230,7 +227,7 @@ class LDAP implements ILDAPWrapper { /** * {@inheritDoc} */ - public function exopPasswd($link, $userDN, $oldPassword, $password) { + public function exopPasswd($link, string $userDN, string $oldPassword, string $password) { return $this->invokeLDAPMethod('exop_passwd', $link, $userDN, $oldPassword, $password); } @@ -276,15 +273,14 @@ class LDAP implements ILDAPWrapper { * When using ldap_search we provide an array, in case multiple bases are * configured. Thus, we need to check the array elements. * - * @param $result - * @return bool + * @param mixed $result */ - protected function isResultFalse($result) { + protected function isResultFalse(string $functionName, $result): bool { if ($result === false) { return true; } - if ($this->curFunc === 'ldap_search' && is_array($result)) { + if ($functionName === 'ldap_search' && is_array($result)) { foreach ($result as $singleResult) { if ($singleResult === false) { return true; @@ -296,16 +292,16 @@ class LDAP implements ILDAPWrapper { } /** + * @param array $arguments * @return mixed */ - protected function invokeLDAPMethod() { - $arguments = func_get_args(); - $func = 'ldap_' . array_shift($arguments); + protected function invokeLDAPMethod(string $func, ...$arguments) { + $func = 'ldap_' . $func; if (function_exists($func)) { $this->preFunctionCall($func, $arguments); $result = call_user_func_array($func, $arguments); - if ($this->isResultFalse($result)) { - $this->postFunctionCall(); + if ($this->isResultFalse($func, $result)) { + $this->postFunctionCall($func); } if ($this->dataCollector !== null) { $this->dataCollector->stopLastLdapRequest(); @@ -316,8 +312,12 @@ class LDAP implements ILDAPWrapper { } private function preFunctionCall(string $functionName, array $args): void { - $this->curFunc = $functionName; $this->curArgs = $args; + $this->logger->debug('Calling LDAP function {func} with parameters {args}', [ + 'app' => 'user_ldap', + 'func' => $functionName, + 'args' => json_encode($args), + ]); if ($this->dataCollector !== null) { $args = array_map(function ($item) { @@ -330,14 +330,15 @@ class LDAP implements ILDAPWrapper { return $item; }, $this->curArgs); - $this->dataCollector->startLdapRequest($this->curFunc, $args); + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $this->dataCollector->startLdapRequest($functionName, $args, $backtrace); } if ($this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) { $args = array_map(fn ($item) => (!$this->isResource($item) ? $item : '(resource)'), $this->curArgs); file_put_contents( $this->logFile, - $this->curFunc . '::' . json_encode($args) . "\n", + $functionName . '::' . json_encode($args) . "\n", FILE_APPEND ); } @@ -351,14 +352,14 @@ class LDAP implements ILDAPWrapper { * @throws ServerNotAvailableException * @throws \Exception */ - private function processLDAPError($resource) { - $errorCode = ldap_errno($resource); - if ($errorCode === 0) { - return; - } - $errorMsg = ldap_error($resource); - - if ($this->curFunc === 'ldap_get_entries' + private function processLDAPError($resource, string $functionName, int $errorCode, string $errorMsg): void { + $this->logger->debug('LDAP error {message} ({code}) after calling {func}', [ + 'app' => 'user_ldap', + 'message' => $errorMsg, + 'code' => $errorCode, + 'func' => $functionName, + ]); + if ($functionName === 'ldap_get_entries' && $errorCode === -4) { } elseif ($errorCode === 32) { //for now @@ -373,15 +374,8 @@ class LDAP implements ILDAPWrapper { } elseif ($errorCode === 1) { throw new \Exception('LDAP Operations error', $errorCode); } elseif ($errorCode === 19) { - ldap_get_option($this->curArgs[0], LDAP_OPT_ERROR_STRING, $extended_error); - throw new ConstraintViolationException(!empty($extended_error)?$extended_error:$errorMsg, $errorCode); - } else { - \OC::$server->getLogger()->debug('LDAP error {message} ({code}) after calling {func}', [ - 'app' => 'user_ldap', - 'message' => $errorMsg, - 'code' => $errorCode, - 'func' => $this->curFunc, - ]); + ldap_get_option($resource, LDAP_OPT_ERROR_STRING, $extended_error); + throw new ConstraintViolationException(!empty($extended_error) ? $extended_error : $errorMsg, $errorCode); } } @@ -389,11 +383,11 @@ class LDAP implements ILDAPWrapper { * Called after an ldap method is run to act on LDAP error if necessary * @throw \Exception */ - private function postFunctionCall() { + private function postFunctionCall(string $functionName): void { if ($this->isResource($this->curArgs[0])) { $resource = $this->curArgs[0]; } elseif ( - $this->curFunc === 'ldap_search' + $functionName === 'ldap_search' && is_array($this->curArgs[0]) && $this->isResource($this->curArgs[0][0]) ) { @@ -404,9 +398,14 @@ class LDAP implements ILDAPWrapper { return; } - $this->processLDAPError($resource); + $errorCode = ldap_errno($resource); + if ($errorCode === 0) { + return; + } + $errorMsg = ldap_error($resource); + + $this->processLDAPError($resource, $functionName, $errorCode, $errorMsg); - $this->curFunc = ''; $this->curArgs = []; } } diff --git a/apps/user_ldap/lib/LDAPUtility.php b/apps/user_ldap/lib/LDAPUtility.php index 0b16f74333b..a8e4c16fac7 100644 --- a/apps/user_ldap/lib/LDAPUtility.php +++ b/apps/user_ldap/lib/LDAPUtility.php @@ -25,7 +25,7 @@ namespace OCA\User_LDAP; abstract class LDAPUtility { - protected $ldap; + protected ILDAPWrapper $ldap; /** * constructor, make sure the subclasses call this one! diff --git a/apps/user_ldap/lib/Mapping/AbstractMapping.php b/apps/user_ldap/lib/Mapping/AbstractMapping.php index 9026b8cfb78..8fbad6aae3a 100644 --- a/apps/user_ldap/lib/Mapping/AbstractMapping.php +++ b/apps/user_ldap/lib/Mapping/AbstractMapping.php @@ -27,9 +27,9 @@ namespace OCA\User_LDAP\Mapping; use Doctrine\DBAL\Exception; -use OC\DB\QueryBuilder\QueryBuilder; use OCP\DB\IPreparedStatement; use OCP\DB\QueryBuilder\IQueryBuilder; +use Doctrine\DBAL\Platforms\SqlitePlatform; /** * Class AbstractMapping @@ -219,12 +219,12 @@ abstract class AbstractMapping { $qb = $this->dbc->getQueryBuilder(); $qb->select('owncloud_name', 'ldap_dn_hash', 'ldap_dn') ->from($this->getTableName(false)) - ->where($qb->expr()->in('ldap_dn_hash', $qb->createNamedParameter($hashList, QueryBuilder::PARAM_STR_ARRAY))); + ->where($qb->expr()->in('ldap_dn_hash', $qb->createNamedParameter($hashList, IQueryBuilder::PARAM_STR_ARRAY))); return $qb; } protected function collectResultsFromListOfIdsQuery(IQueryBuilder $qb, array &$results): void { - $stmt = $qb->execute(); + $stmt = $qb->executeQuery(); while ($entry = $stmt->fetch(\Doctrine\DBAL\FetchMode::ASSOCIATIVE)) { $results[$entry['ldap_dn']] = $entry['owncloud_name']; $this->cache[$entry['ldap_dn']] = $entry['owncloud_name']; @@ -239,7 +239,7 @@ abstract class AbstractMapping { public function getListOfIdsByDn(array $fdns): array { $totalDBParamLimit = 65000; $sliceSize = 1000; - $maxSlices = $totalDBParamLimit / $sliceSize; + $maxSlices = $this->dbc->getDatabasePlatform() instanceof SqlitePlatform ? 9 : $totalDBParamLimit / $sliceSize; $results = []; $slice = 1; @@ -261,7 +261,7 @@ abstract class AbstractMapping { } if (!empty($fdnsSlice)) { - $qb->orWhere($qb->expr()->in('ldap_dn_hash', $qb->createNamedParameter($fdnsSlice, QueryBuilder::PARAM_STR_ARRAY))); + $qb->orWhere($qb->expr()->in('ldap_dn_hash', $qb->createNamedParameter($fdnsSlice, IQueryBuilder::PARAM_STR_ARRAY))); } if ($slice % $maxSlices === 0) { diff --git a/apps/user_ldap/lib/Mapping/UserMapping.php b/apps/user_ldap/lib/Mapping/UserMapping.php index 899cc015c9f..ade9c67213a 100644 --- a/apps/user_ldap/lib/Mapping/UserMapping.php +++ b/apps/user_ldap/lib/Mapping/UserMapping.php @@ -22,12 +22,51 @@ */ namespace OCA\User_LDAP\Mapping; +use OCP\HintException; +use OCP\IDBConnection; +use OCP\IRequest; +use OCP\Server; +use OCP\Support\Subscription\IAssertion; + /** * Class UserMapping + * * @package OCA\User_LDAP\Mapping */ class UserMapping extends AbstractMapping { + private IAssertion $assertion; + protected const PROV_API_REGEX = '/\/ocs\/v[1-9].php\/cloud\/(groups|users)/'; + + public function __construct(IDBConnection $dbc, IAssertion $assertion) { + $this->assertion = $assertion; + parent::__construct($dbc); + } + + /** + * @throws HintException + */ + public function map($fdn, $name, $uuid): bool { + try { + $this->assertion->createUserIsLegit(); + } catch (HintException $e) { + static $isProvisioningApi = null; + + if ($isProvisioningApi === null) { + $request = Server::get(IRequest::class); + $isProvisioningApi = \preg_match(self::PROV_API_REGEX, $request->getRequestUri()) === 1; + } + if ($isProvisioningApi) { + // only throw when prov API is being used, since functionality + // should not break for end users (e.g. when sharing). + // On direct API usage, e.g. on users page, this is desired. + throw $e; + } + return false; + } + return parent::map($fdn, $name, $uuid); + } + /** * returns the DB table name which holds the mappings * @return string diff --git a/apps/user_ldap/lib/PagedResults/IAdapter.php b/apps/user_ldap/lib/PagedResults/IAdapter.php deleted file mode 100644 index 31338126e40..00000000000 --- a/apps/user_ldap/lib/PagedResults/IAdapter.php +++ /dev/null @@ -1,130 +0,0 @@ -<?php - -declare(strict_types=1); - -/** - * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.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/>. - * - */ -namespace OCA\User_LDAP\PagedResults; - -interface IAdapter { - - /** - * Methods for initiating Paged Results Control - */ - - /** - * The adapter receives paged result parameters from the client. It may - * store the parameters for later use. - */ - public function setRequestParameters($link, int $pageSize, bool $isCritical): void; - - /** - * The adapter is asked for an function that is being explicitly called to - * send the control parameters to LDAP. If not function has to be called, - * null shall be returned. - * - * It will used by the callee for diagnosis and error handling. - */ - public function getRequestCallFunc(): ?string; - - /** - * The adapter is asked to provide the arguments it would pass to the - * function returned by getRequestCallFunc(). If none shall be called, an - * empty array should be returned. - * - * It will used by the callee for diagnosis and error handling. - */ - public function getRequestCallArgs($link): array; - - /** - * The adapter is asked to do the necessary calls to LDAP, if - * getRequestCallFunc returned a function. If none, it will not be called - * so the return value is best set to false. Otherwise it shall respond - * whether setting the controls was successful. - */ - public function requestCall($link): bool; - - /** - * The adapter shall report which PHP function will be called to process - * the paged results call - * - * It will used by the callee for diagnosis and error handling. - */ - public function getResponseCallFunc(): string; - - /** - * The adapter shall report with arguments will be provided to the LDAP - * function it will call - * - * It will used by the callee for diagnosis and error handling. - */ - public function getResponseCallArgs(array $originalArgs): array; - - /** - * the adapter should do it's LDAP function call and return success state - * - * @param resource|\LDAP\Connection $link LDAP resource - * @return bool - */ - public function responseCall($link): bool; - - /** - * The adapter receives the parameters that were passed to a search - * operation. Typically it wants to save the them for the call proper later - * on. - */ - public function setSearchArgs( - $link, - string $baseDN, - string $filter, - array $attr, - int $attrsOnly, - int $limit - ): void; - - /** - * The adapter shall report which arguments shall be passed to the - * ldap_search function. - */ - public function getSearchArgs($link): array; - - /** - * The adapter receives the parameters that were passed to a read - * operation. Typically it wants to save the them for the call proper later - * on. - */ - public function setReadArgs($link, string $baseDN, string $filter, array $attr): void; - - /** - * The adapter shall report which arguments shall be passed to the - * ldap_read function. - */ - public function getReadArgs($link): array; - - /** - * Returns the current paged results cookie - * - * @param resource|\LDAP\Connection $link LDAP resource - * @return string - */ - public function getCookie($link): string; -} diff --git a/apps/user_ldap/lib/PagedResults/Php73.php b/apps/user_ldap/lib/PagedResults/Php73.php deleted file mode 100644 index 1fc1fcdbab8..00000000000 --- a/apps/user_ldap/lib/PagedResults/Php73.php +++ /dev/null @@ -1,173 +0,0 @@ -<?php - -declare(strict_types=1); - -/** - * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Roeland Jago Douma <roeland@famdouma.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\User_LDAP\PagedResults; - -/** - * Class Php73 - * - * implements paged results support with PHP APIs available from PHP 7.3 - * - * @package OCA\User_LDAP\PagedResults - */ -class Php73 implements IAdapter { - use TLinkId; - - /** @var array */ - protected $linkData = []; - - public function getResponseCallFunc(): string { - return 'ldap_parse_result'; - } - - public function responseCall($link): bool { - $linkId = $this->getLinkId($link); - return ldap_parse_result(...$this->linkData[$linkId]['responseArgs']); - } - - public function getResponseCallArgs(array $originalArgs): array { - $link = array_shift($originalArgs); - $linkId = $this->getLinkId($link); - - if (!isset($this->linkData[$linkId])) { - $this->linkData[$linkId] = []; - } - - $this->linkData[$linkId]['responseErrorCode'] = 0; - $this->linkData[$linkId]['responseErrorMessage'] = ''; - $this->linkData[$linkId]['serverControls'] = []; - $matchedDn = null; - $referrals = []; - - $this->linkData[$linkId]['responseArgs'] = [ - $link, - array_shift($originalArgs), - &$this->linkData[$linkId]['responseErrorCode'], - $matchedDn, - &$this->linkData[$linkId]['responseErrorMessage'], - $referrals, - &$this->linkData[$linkId]['serverControls'] - ]; - - - return $this->linkData[$linkId]['responseArgs']; - } - - public function getCookie($link): string { - $linkId = $this->getLinkId($link); - return $this->linkData[$linkId]['serverControls'][LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] ?? ''; - } - - private function resetCookie(int $linkId): void { - if (isset($this->linkData[$linkId]['serverControls'][LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) { - $this->linkData[$linkId]['serverControls'][LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] = ''; - } - } - - public function getRequestCallFunc(): ?string { - return null; - } - - public function setRequestParameters($link, int $pageSize, bool $isCritical): void { - $linkId = $this->getLinkId($link); - if (!isset($this->linkData[$linkId])) { - $this->linkData[$linkId] = []; - } - $this->linkData[$linkId]['requestArgs'] = []; - $this->linkData[$linkId]['requestArgs']['pageSize'] = $pageSize; - $this->linkData[$linkId]['requestArgs']['isCritical'] = $isCritical; - - if ($pageSize === 0) { - $this->resetCookie($linkId); - } - } - - public function getRequestCallArgs($link): array { - // no separate call - return []; - } - - public function requestCall($link): bool { - // no separate call - return false; - } - - public function setSearchArgs( - $link, - string $baseDN, - string $filter, - array $attr, - int $attrsOnly, - int $limit - ): void { - $linkId = $this->getLinkId($link); - if (!isset($this->linkData[$linkId])) { - $this->linkData[$linkId] = []; - } - - $this->linkData[$linkId]['searchArgs'] = func_get_args(); - $this->preparePagesResultsArgs($linkId, 'searchArgs'); - } - - public function getSearchArgs($link): array { - $linkId = $this->getLinkId($link); - return $this->linkData[$linkId]['searchArgs']; - } - - public function setReadArgs($link, string $baseDN, string $filter, array $attr): void { - $linkId = $this->getLinkId($link); - if (!isset($this->linkData[$linkId])) { - $this->linkData[$linkId] = []; - } - - $this->linkData[$linkId]['readArgs'] = func_get_args(); - $this->linkData[$linkId]['readArgs'][] = 0; // $attrsonly default - $this->linkData[$linkId]['readArgs'][] = -1; // $sizelimit default - } - - public function getReadArgs($link): array { - $linkId = $this->getLinkId($link); - return $this->linkData[$linkId]['readArgs']; - } - - protected function preparePagesResultsArgs(int $linkId, string $methodKey): void { - if (!isset($this->linkData[$linkId]['requestArgs'])) { - return; - } - - $serverControls = [[ - 'oid' => LDAP_CONTROL_PAGEDRESULTS, - 'value' => [ - 'size' => $this->linkData[$linkId]['requestArgs']['pageSize'], - 'cookie' => $this->linkData[$linkId]['serverControls'][LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] ?? '', - ] - ]]; - - $this->linkData[$linkId][$methodKey][] = -1; // timelimit - $this->linkData[$linkId][$methodKey][] = LDAP_DEREF_NEVER; - $this->linkData[$linkId][$methodKey][] = $serverControls; - } -} diff --git a/apps/user_ldap/lib/Proxy.php b/apps/user_ldap/lib/Proxy.php index d9546a163ab..4df6c2683a6 100644 --- a/apps/user_ldap/lib/Proxy.php +++ b/apps/user_ldap/lib/Proxy.php @@ -35,7 +35,9 @@ namespace OCA\User_LDAP; use OCA\User_LDAP\Mapping\GroupMapping; use OCA\User_LDAP\Mapping\UserMapping; use OCA\User_LDAP\User\Manager; -use OCP\Share\IManager; +use OCP\IConfig; +use OCP\IUserManager; +use OCP\Server; use Psr\Log\LoggerInterface; abstract class Proxy { @@ -61,34 +63,18 @@ abstract class Proxy { /** * @param string $configPrefix */ - private function addAccess($configPrefix) { - static $ocConfig; - static $fs; - static $log; - static $avatarM; - static $userMap; - static $groupMap; - static $shareManager; - static $coreUserManager; - static $coreNotificationManager; - static $logger; - if ($fs === null) { - $ocConfig = \OC::$server->getConfig(); - $fs = new FilesystemHelper(); - $avatarM = \OC::$server->getAvatarManager(); - $db = \OC::$server->getDatabaseConnection(); - $userMap = new UserMapping($db); - $groupMap = new GroupMapping($db); - $coreUserManager = \OC::$server->getUserManager(); - $coreNotificationManager = \OC::$server->getNotificationManager(); - $shareManager = \OC::$server->get(IManager::class); - $logger = \OC::$server->get(LoggerInterface::class); - } - $userManager = - new Manager($ocConfig, $fs, $logger, $avatarM, new \OCP\Image(), - $coreUserManager, $coreNotificationManager, $shareManager); + private function addAccess(string $configPrefix): void { + $ocConfig = Server::get(IConfig::class); + $userMap = Server::get(UserMapping::class); + $groupMap = Server::get(GroupMapping::class); + $coreUserManager = Server::get(IUserManager::class); + $logger = Server::get(LoggerInterface::class); + $helper = Server::get(Helper::class); + + $userManager = Server::get(Manager::class); + $connector = new Connection($this->ldap, $configPrefix); - $access = new Access($connector, $this->ldap, $userManager, new Helper($ocConfig, \OC::$server->getDatabaseConnection()), $ocConfig, $coreUserManager, $logger); + $access = new Access($connector, $this->ldap, $userManager, $helper, $ocConfig, $coreUserManager, $logger); $access->setUserMapper($userMap); $access->setGroupMapper($groupMap); self::$accesses[$configPrefix] = $access; diff --git a/apps/user_ldap/lib/User/Manager.php b/apps/user_ldap/lib/User/Manager.php index 655463a0ecd..b1915ab57b5 100644 --- a/apps/user_ldap/lib/User/Manager.php +++ b/apps/user_ldap/lib/User/Manager.php @@ -209,7 +209,7 @@ class Manager { } /** - * @brief returns a User object by it's Nextcloud username + * @brief returns a User object by its Nextcloud username * @param string $id the DN or username of the user * @return \OCA\User_LDAP\User\User|\OCA\User_LDAP\User\OfflineUser|null */ @@ -226,7 +226,7 @@ class Manager { } /** - * @brief returns a User object by it's DN or Nextcloud username + * @brief returns a User object by its DN or Nextcloud username * @param string $id the DN or username of the user * @return \OCA\User_LDAP\User\User|\OCA\User_LDAP\User\OfflineUser|null * @throws \Exception when connection could not be established diff --git a/apps/user_ldap/lib/User_LDAP.php b/apps/user_ldap/lib/User_LDAP.php index 5a445100052..650c974da81 100644 --- a/apps/user_ldap/lib/User_LDAP.php +++ b/apps/user_ldap/lib/User_LDAP.php @@ -45,14 +45,15 @@ use OCA\User_LDAP\Exceptions\NotOnLDAP; use OCA\User_LDAP\User\OfflineUser; use OCA\User_LDAP\User\User; use OCP\IConfig; +use OCP\IUserBackend; use OCP\IUserSession; use OCP\Notification\IManager as INotificationManager; +use OCP\User\Backend\ICountMappedUsersBackend; use OCP\User\Backend\ICountUsersBackend; -use OCP\IUserBackend; use OCP\UserInterface; use Psr\Log\LoggerInterface; -class User_LDAP extends BackendUtility implements IUserBackend, UserInterface, IUserLDAP, ICountUsersBackend { +class User_LDAP extends BackendUtility implements IUserBackend, UserInterface, IUserLDAP, ICountUsersBackend, ICountMappedUsersBackend { /** @var \OCP\IConfig */ protected $ocConfig; @@ -598,6 +599,10 @@ class User_LDAP extends BackendUtility implements IUserBackend, UserInterface, I return $entries; } + public function countMappedUsers(): int { + return $this->access->getUserMapper()->count(); + } + /** * Backend name to be shown in user management * @return string the name of the backend to be shown diff --git a/apps/user_ldap/lib/User_Proxy.php b/apps/user_ldap/lib/User_Proxy.php index 040d4f5aa69..b98fa8da0ff 100644 --- a/apps/user_ldap/lib/User_Proxy.php +++ b/apps/user_ldap/lib/User_Proxy.php @@ -33,15 +33,25 @@ namespace OCA\User_LDAP; use OCA\User_LDAP\User\User; use OCP\IConfig; +use OCP\IUserBackend; use OCP\IUserSession; use OCP\Notification\IManager as INotificationManager; +use OCP\User\Backend\ICountMappedUsersBackend; use OCP\User\Backend\ICountUsersBackend; +use OCP\UserInterface; -class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, IUserLDAP, ICountUsersBackend { +class User_Proxy extends Proxy implements IUserBackend, UserInterface, IUserLDAP, ICountUsersBackend, ICountMappedUsersBackend { private $backends = []; /** @var User_LDAP */ private $refBackend = null; + private bool $isSetUp = false; + private Helper $helper; + private IConfig $ocConfig; + private INotificationManager $notificationManager; + private IUserSession $userSession; + private UserPluginManager $userPluginManager; + public function __construct( Helper $helper, ILDAPWrapper $ldap, @@ -51,15 +61,29 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, UserPluginManager $userPluginManager ) { parent::__construct($ldap); - $serverConfigPrefixes = $helper->getServerConfigurationPrefixes(true); + $this->helper = $helper; + $this->ocConfig = $ocConfig; + $this->notificationManager = $notificationManager; + $this->userSession = $userSession; + $this->userPluginManager = $userPluginManager; + } + + protected function setup(): void { + if ($this->isSetUp) { + return; + } + + $serverConfigPrefixes = $this->helper->getServerConfigurationPrefixes(true); foreach ($serverConfigPrefixes as $configPrefix) { $this->backends[$configPrefix] = - new User_LDAP($this->getAccess($configPrefix), $ocConfig, $notificationManager, $userSession, $userPluginManager); + new User_LDAP($this->getAccess($configPrefix), $this->ocConfig, $this->notificationManager, $this->userSession, $this->userPluginManager); if (is_null($this->refBackend)) { $this->refBackend = &$this->backends[$configPrefix]; } } + + $this->isSetUp = true; } /** @@ -71,6 +95,8 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, * @return mixed the result of the method or false */ protected function walkBackends($id, $method, $parameters) { + $this->setup(); + $uid = $id; $cacheKey = $this->getUserCacheKey($uid); foreach ($this->backends as $configPrefix => $backend) { @@ -99,6 +125,8 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, * @return mixed the result of the method or false */ protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen) { + $this->setup(); + $uid = $id; $cacheKey = $this->getUserCacheKey($uid); $prefix = $this->getFromCache($cacheKey); @@ -129,6 +157,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, } protected function activeBackends(): int { + $this->setup(); return count($this->backends); } @@ -142,6 +171,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, * compared with \OC\User\Backend::CREATE_USER etc. */ public function implementsActions($actions) { + $this->setup(); //it's the same across all our user backends obviously return $this->refBackend->implementsActions($actions); } @@ -152,6 +182,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, * @return string the name of the backend to be shown */ public function getBackendName() { + $this->setup(); return $this->refBackend->getBackendName(); } @@ -164,6 +195,8 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, * @return string[] an array of all uids */ public function getUsers($search = '', $limit = 10, $offset = 0) { + $this->setup(); + //we do it just as the /OC_User implementation: do not play around with limit and offset but ask all backends $users = []; foreach ($this->backends as $backend) { @@ -296,6 +329,8 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, * @return array an array of all displayNames (value) and the corresponding uids (key) */ public function getDisplayNames($search = '', $limit = null, $offset = null) { + $this->setup(); + //we do it just as the /OC_User implementation: do not play around with limit and offset but ask all backends $users = []; foreach ($this->backends as $backend) { @@ -335,6 +370,7 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, * @return bool */ public function hasUserListings() { + $this->setup(); return $this->refBackend->hasUserListings(); } @@ -344,6 +380,8 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, * @return int|bool */ public function countUsers() { + $this->setup(); + $users = false; foreach ($this->backends as $backend) { $backendUsers = $backend->countUsers(); @@ -355,6 +393,19 @@ class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, } /** + * Count the number of mapped users + */ + public function countMappedUsers(): int { + $this->setup(); + + $users = 0; + foreach ($this->backends as $backend) { + $users += $backend->countMappedUsers(); + } + return $users; + } + + /** * Return access for LDAP interaction. * * @param string $uid diff --git a/apps/user_ldap/lib/Wizard.php b/apps/user_ldap/lib/Wizard.php index dcf5f378516..3014ec8e8a7 100644 --- a/apps/user_ldap/lib/Wizard.php +++ b/apps/user_ldap/lib/Wizard.php @@ -20,6 +20,7 @@ * @author Victor Dubiniuk <dubiniuk@owncloud.com> * @author Xuanwo <xuanwo@yunify.com> * @author Vincent Van Houtte <vvh@aplusv.be> + * @author Côme Chilliet <come.chilliet@nextcloud.com> * * @license AGPL-3.0 * @@ -36,22 +37,22 @@ * along with this program. If not, see <http://www.gnu.org/licenses/> * */ + namespace OCA\User_LDAP; use OC\ServerNotAvailableException; +use OCP\IL10N; +use OCP\L10N\IFactory as IL10NFactory; use Psr\Log\LoggerInterface; class Wizard extends LDAPUtility { - /** @var \OCP\IL10N */ - protected static $l; - protected $access; + protected static ?IL10N $l = null; + protected Access $access; + /** @var resource|\LDAP\Connection|null */ protected $cr; - protected $configuration; - protected $result; - protected $resultCache = []; - - /** @var LoggerInterface */ - protected $logger; + protected Configuration $configuration; + protected WizardResult $result; + protected LoggerInterface $logger; public const LRESULT_PROCESSED_OK = 2; public const LRESULT_PROCESSED_INVALID = 3; @@ -66,17 +67,15 @@ class Wizard extends LDAPUtility { public const LDAP_NW_TIMEOUT = 4; - /** - * Constructor - * @param Configuration $configuration an instance of Configuration - * @param ILDAPWrapper $ldap an instance of ILDAPWrapper - * @param Access $access - */ - public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) { + public function __construct( + Configuration $configuration, + ILDAPWrapper $ldap, + Access $access + ) { parent::__construct($ldap); $this->configuration = $configuration; - if (is_null(Wizard::$l)) { - Wizard::$l = \OC::$server->getL10N('user_ldap'); + if (is_null(static::$l)) { + static::$l = \OC::$server->get(IL10NFactory::class)->get('user_ldap'); } $this->access = $access; $this->result = new WizardResult(); @@ -94,7 +93,6 @@ class Wizard extends LDAPUtility { * * @param string $filter the LDAP search filter * @param string $type a string being either 'users' or 'groups'; - * @return int * @throws \Exception */ public function countEntries(string $filter, string $type): int { @@ -124,6 +122,9 @@ class Wizard extends LDAPUtility { return (int)$result; } + /** + * @return WizardResult|false + */ public function countGroups() { $filter = $this->configuration->ldapGroupFilter; @@ -157,10 +158,9 @@ class Wizard extends LDAPUtility { } /** - * @return WizardResult * @throws \Exception */ - public function countUsers() { + public function countUsers(): WizardResult { $filter = $this->access->getFilterForUserCount(); $usersTotal = $this->countEntries($filter, 'users'); @@ -180,26 +180,20 @@ class Wizard extends LDAPUtility { /** * counts any objects in the currently set base dn * - * @return WizardResult * @throws \Exception */ - public function countInBaseDN() { + public function countInBaseDN(): WizardResult { // we don't need to provide a filter in this case $total = $this->countEntries('', 'objects'); - if ($total === false) { - throw new \Exception('invalid results received'); - } $this->result->addChange('ldap_test_base', $total); return $this->result; } /** * counts users with a specified attribute - * @param string $attr - * @param bool $existsCheck - * @return int|bool + * @return int|false */ - public function countUsersWithAttribute($attr, $existsCheck = false) { + public function countUsersWithAttribute(string $attr, bool $existsCheck = false) { $reqs = ['ldapHost', 'ldapBase', 'ldapUserFilter']; if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; @@ -213,7 +207,7 @@ class Wizard extends LDAPUtility { $attr . '=*' ]); - $limit = ($existsCheck === false) ? null : 1; + $limit = $existsCheck ? null : 1; return $this->access->countUsers($filter, ['dn'], $limit); } @@ -221,7 +215,7 @@ class Wizard extends LDAPUtility { /** * detects the display name attribute. If a setting is already present that * returns at least one hit, the detection will be canceled. - * @return WizardResult|bool + * @return WizardResult|false * @throws \Exception */ public function detectUserDisplayNameAttribute() { @@ -312,7 +306,7 @@ class Wizard extends LDAPUtility { } /** - * @return WizardResult + * @return WizardResult|false * @throws \Exception */ public function determineAttributes() { @@ -326,6 +320,10 @@ class Wizard extends LDAPUtility { $attributes = $this->getUserAttributes(); + if (!is_array($attributes)) { + throw new \Exception('Failed to determine user attributes'); + } + natcasesort($attributes); $attributes = array_values($attributes); @@ -341,7 +339,7 @@ class Wizard extends LDAPUtility { /** * detects the available LDAP attributes - * @return array|false The instance's WizardResult instance + * @return array|false * @throws \Exception */ private function getUserAttributes() { @@ -363,8 +361,12 @@ class Wizard extends LDAPUtility { if (!$this->ldap->isResource($rr)) { return false; } + /** @var resource|\LDAP\Result $rr */ $er = $this->ldap->firstEntry($cr, $rr); $attributes = $this->ldap->getAttributes($cr, $er); + if ($attributes === false) { + return false; + } $pureAttributes = []; for ($i = 0; $i < $attributes['count']; $i++) { $pureAttributes[] = $attributes[$i]; @@ -379,8 +381,8 @@ class Wizard extends LDAPUtility { */ public function determineGroupsForGroups() { return $this->determineGroups('ldap_groupfilter_groups', - 'ldapGroupFilterGroups', - false); + 'ldapGroupFilterGroups', + false); } /** @@ -389,18 +391,15 @@ class Wizard extends LDAPUtility { */ public function determineGroupsForUsers() { return $this->determineGroups('ldap_userfilter_groups', - 'ldapUserFilterGroups'); + 'ldapUserFilterGroups'); } /** * detects the available LDAP groups - * @param string $dbKey - * @param string $confKey - * @param bool $testMemberOf * @return WizardResult|false the instance's WizardResult instance * @throws \Exception */ - private function determineGroups($dbKey, $confKey, $testMemberOf = true) { + private function determineGroups(string $dbKey, string $confKey, bool $testMemberOf = true) { $reqs = ['ldapHost', 'ldapBase']; if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; @@ -429,12 +428,9 @@ class Wizard extends LDAPUtility { /** * fetches all groups from LDAP and adds them to the result object * - * @param string $dbKey - * @param string $confKey - * @return array $groupEntries * @throws \Exception */ - public function fetchGroups($dbKey, $confKey) { + public function fetchGroups(string $dbKey, string $confKey): array { $obclasses = ['posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames', 'groupOfUniqueNames']; $filterParts = []; @@ -456,7 +452,7 @@ class Wizard extends LDAPUtility { // detection will fail later $result = $this->access->searchGroups($filter, ['cn', 'dn'], $limit, $offset); foreach ($result as $item) { - if (!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) { + if (!isset($item['cn']) || !is_array($item['cn']) || !isset($item['cn'][0])) { // just in case - no issue known continue; } @@ -481,6 +477,9 @@ class Wizard extends LDAPUtility { return $groupEntries; } + /** + * @return WizardResult|false + */ public function determineGroupMemberAssoc() { $reqs = ['ldapHost', 'ldapGroupFilter']; if (!$this->configuration->usesLdapi()) { @@ -519,17 +518,17 @@ class Wizard extends LDAPUtility { $obclasses = ['groupOfNames', 'groupOfUniqueNames', 'group', 'posixGroup', '*']; $this->determineFeature($obclasses, - 'objectclass', - 'ldap_groupfilter_objectclass', - 'ldapGroupFilterObjectclass', - false); + 'objectclass', + 'ldap_groupfilter_objectclass', + 'ldapGroupFilterObjectclass', + false); return $this->result; } /** * detects the available object classes - * @return WizardResult + * @return WizardResult|false * @throws \Exception */ public function determineUserObjectClasses() { @@ -551,10 +550,10 @@ class Wizard extends LDAPUtility { //if filter is empty, it is probably the first time the wizard is called //then, apply suggestions. $this->determineFeature($obclasses, - 'objectclass', - 'ldap_userfilter_objectclass', - 'ldapUserFilterObjectclass', - empty($filter)); + 'objectclass', + 'ldap_userfilter_objectclass', + 'ldapUserFilterObjectclass', + empty($filter)); return $this->result; } @@ -576,7 +575,7 @@ class Wizard extends LDAPUtility { if ($displayName === '') { $d = $this->configuration->getDefaults(); $this->applyFind('ldap_group_display_name', - $d['ldap_group_display_name']); + $d['ldap_group_display_name']); } $filter = $this->composeLdapFilter(self::LFILTER_GROUP_LIST); @@ -612,7 +611,7 @@ class Wizard extends LDAPUtility { } /** - * @return bool|WizardResult + * @return WizardResult|false * @throws \Exception */ public function getUserLoginFilter() { @@ -634,11 +633,10 @@ class Wizard extends LDAPUtility { } /** - * @return bool|WizardResult - * @param string $loginName + * @return WizardResult|false * @throws \Exception */ - public function testLoginName($loginName) { + public function testLoginName(string $loginName) { $reqs = ['ldapHost', 'ldapBase', 'ldapUserFilter']; if (!$this->configuration->usesLdapi()) { $reqs[] = 'ldapPort'; @@ -651,6 +649,7 @@ class Wizard extends LDAPUtility { if (!$this->ldap->isResource($cr)) { throw new \Exception('connection error'); } + /** @var resource|\LDAP\Connection $cr */ if (mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8') === false) { @@ -680,10 +679,6 @@ class Wizard extends LDAPUtility { $this->checkHost(); $portSettings = $this->getPortSettingsToTry(); - if (!is_array($portSettings)) { - throw new \Exception(print_r($portSettings, true)); - } - //proceed from the best configuration and return on first success foreach ($portSettings as $setting) { $p = $setting['port']; @@ -692,7 +687,7 @@ class Wizard extends LDAPUtility { 'Wiz: trying port '. $p . ', TLS '. $t, ['app' => 'user_ldap'] ); - //connectAndBind may throw Exception, it needs to be catched by the + //connectAndBind may throw Exception, it needs to be caught by the //callee of this method try { @@ -754,7 +749,7 @@ class Wizard extends LDAPUtility { //this did not help :( //Let's see whether we can parse the Host URL and convert the domain to //a base DN - $helper = new Helper(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()); + $helper = \OC::$server->get(Helper::class); $domain = $helper->getDomainFromURL($this->configuration->ldapHost); if (!$domain) { return false; @@ -780,7 +775,7 @@ class Wizard extends LDAPUtility { * @param string $value the (detected) value * */ - private function applyFind($key, $value) { + private function applyFind(string $key, string $value): void { $this->result->addChange($key, $value); $this->configuration->setConfiguration([$key => $value]); } @@ -790,7 +785,7 @@ class Wizard extends LDAPUtility { * field. In this case the port will be stripped off, but also stored as * setting. */ - private function checkHost() { + private function checkHost(): void { $host = $this->configuration->ldapHost; $hostInfo = parse_url($host); @@ -799,14 +794,14 @@ class Wizard extends LDAPUtility { $port = $hostInfo['port']; $host = str_replace(':'.$port, '', $host); $this->applyFind('ldap_host', $host); - $this->applyFind('ldap_port', $port); + $this->applyFind('ldap_port', (string)$port); } } /** * tries to detect the group member association attribute which is * one of 'uniqueMember', 'memberUid', 'member', 'gidNumber' - * @return string|false, string with the attribute name, false on error + * @return string|false string with the attribute name, false on error * @throws \Exception */ private function detectGroupMemberAssoc() { @@ -824,6 +819,7 @@ class Wizard extends LDAPUtility { if (!$this->ldap->isResource($rr)) { return false; } + /** @var resource|\LDAP\Result $rr */ $er = $this->ldap->firstEntry($cr, $rr); while ($this->ldap->isResource($er)) { $this->ldap->getDN($cr, $er); @@ -852,7 +848,7 @@ class Wizard extends LDAPUtility { * @return bool true on success, false otherwise * @throws \Exception */ - private function testBaseDN($base) { + private function testBaseDN(string $base): bool { $cr = $this->getConnection(); if (!$cr) { throw new \Exception('Could not connect to LDAP'); @@ -870,6 +866,7 @@ class Wizard extends LDAPUtility { ); return false; } + /** @var resource|\LDAP\Result $rr */ $entries = $this->ldap->countEntries($cr, $rr); return ($entries !== false) && ($entries > 0); } @@ -883,7 +880,7 @@ class Wizard extends LDAPUtility { * @return bool true if it does, false otherwise * @throws \Exception */ - private function testMemberOf() { + private function testMemberOf(): bool { $cr = $this->getConnection(); if (!$cr) { throw new \Exception('Could not connect to LDAP'); @@ -897,13 +894,12 @@ class Wizard extends LDAPUtility { /** * creates an LDAP Filter from given configuration - * @param integer $filterType int, for which use case the filter shall be created + * @param int $filterType int, for which use case the filter shall be created * can be any of self::LFILTER_USER_LIST, self::LFILTER_LOGIN or * self::LFILTER_GROUP_LIST - * @return string|false string with the filter on success, false otherwise * @throws \Exception */ - private function composeLdapFilter($filterType) { + private function composeLdapFilter(int $filterType): string { $filter = ''; $parts = 0; switch ($filterType) { @@ -933,6 +929,7 @@ class Wizard extends LDAPUtility { if (!$this->ldap->isResource($rr)) { continue; } + /** @var resource|\LDAP\Result $rr */ $er = $this->ldap->firstEntry($cr, $rr); $attrs = $this->ldap->getAttributes($cr, $er); $dn = $this->ldap->getDN($cr, $er); @@ -992,6 +989,9 @@ class Wizard extends LDAPUtility { $loginpart = '=%uid'; $filterUsername = ''; $userAttributes = $this->getUserAttributes(); + if ($userAttributes === false) { + throw new \Exception('Failed to get user attributes'); + } $userAttributes = array_change_key_case(array_flip($userAttributes)); $parts = 0; @@ -1056,24 +1056,24 @@ class Wizard extends LDAPUtility { * * @param int $port the port to connect with * @param bool $tls whether startTLS is to be used - * @return bool * @throws \Exception */ - private function connectAndBind($port, $tls) { + private function connectAndBind(int $port, bool $tls): bool { //connect, does not really trigger any server communication $host = $this->configuration->ldapHost; - $hostInfo = parse_url($host); - if (!$hostInfo) { + $hostInfo = parse_url((string)$host); + if (!is_string($host) || !$hostInfo) { throw new \Exception(self::$l->t('Invalid Host')); } $this->logger->debug( 'Wiz: Attempting to connect', ['app' => 'user_ldap'] ); - $cr = $this->ldap->connect($host, $port); + $cr = $this->ldap->connect($host, (string)$port); if (!$this->ldap->isResource($cr)) { throw new \Exception(self::$l->t('Invalid Host')); } + /** @var resource|\LDAP\Connection $cr */ //set LDAP options $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3); @@ -1089,7 +1089,7 @@ class Wizard extends LDAPUtility { } $this->logger->debug( - 'Wiz: Attemping to Bind', + 'Wiz: Attempting to Bind', ['app' => 'user_ldap'] ); //interesting part: do the bind! @@ -1098,14 +1098,13 @@ class Wizard extends LDAPUtility { $this->configuration->ldapAgentPassword ); $errNo = $this->ldap->errno($cr); - $error = ldap_error($cr); + $error = $this->ldap->error($cr); $this->ldap->unbind($cr); } catch (ServerNotAvailableException $e) { return false; } if ($login === true) { - $this->ldap->unbind($cr); $this->logger->debug( 'Wiz: Bind successful to Port '. $port . ' TLS ' . (int)$tls, ['app' => 'user_ldap'] @@ -1123,9 +1122,9 @@ class Wizard extends LDAPUtility { /** * checks whether a valid combination of agent and password has been * provided (either two values or nothing for anonymous connect) - * @return bool, true if everything is fine, false otherwise + * @return bool true if everything is fine, false otherwise */ - private function checkAgentRequirements() { + private function checkAgentRequirements(): bool { $agent = $this->configuration->ldapAgentName; $pwd = $this->configuration->ldapAgentPassword; @@ -1135,11 +1134,7 @@ class Wizard extends LDAPUtility { ; } - /** - * @param array $reqs - * @return bool - */ - private function checkRequirements($reqs) { + private function checkRequirements(array $reqs): bool { $this->checkAgentRequirements(); foreach ($reqs as $option) { $value = $this->configuration->$option; @@ -1161,7 +1156,7 @@ class Wizard extends LDAPUtility { * yields most result entries * @return array|false an array with the values on success, false otherwise */ - public function cumulativeSearchOnAttribute($filters, $attr, $dnReadLimit = 3, &$maxF = null) { + public function cumulativeSearchOnAttribute(array $filters, string $attr, int $dnReadLimit = 3, ?string &$maxF = null) { $dnRead = []; $foundItems = []; $maxEntries = 0; @@ -1174,6 +1169,7 @@ class Wizard extends LDAPUtility { if (!$this->ldap->isResource($cr)) { return false; } + /** @var resource|\LDAP\Connection $cr */ $lastFilter = null; if (isset($filters[count($filters) - 1])) { $lastFilter = $filters[count($filters) - 1]; @@ -1188,6 +1184,7 @@ class Wizard extends LDAPUtility { if (!$this->ldap->isResource($rr)) { continue; } + /** @var resource|\LDAP\Result $rr */ $entries = $this->ldap->countEntries($cr, $rr); $getEntryFunc = 'firstEntry'; if (($entries !== false) && ($entries > 0)) { @@ -1205,16 +1202,17 @@ class Wizard extends LDAPUtility { $rr = $entry; //will be expected by nextEntry next round $attributes = $this->ldap->getAttributes($cr, $entry); $dn = $this->ldap->getDN($cr, $entry); - if ($dn === false || in_array($dn, $dnRead)) { + if ($attributes === false || $dn === false || in_array($dn, $dnRead)) { continue; } $newItems = []; - $state = $this->getAttributeValuesFromEntry($attributes, - $attr, - $newItems); + $state = $this->getAttributeValuesFromEntry( + $attributes, + $attr, + $newItems + ); $dnReadCount++; $foundItems = array_merge($foundItems, $newItems); - $this->resultCache[$dn][$attr] = $newItems; $dnRead[] = $dn; } while (($state === self::LRESULT_PROCESSED_SKIP || $this->ldap->isResource($entry)) @@ -1234,10 +1232,10 @@ class Wizard extends LDAPUtility { * Configuration class * @param bool $po whether the objectClass with most result entries * shall be pre-selected via the result - * @return array|false list of found items. + * @return array list of found items. * @throws \Exception */ - private function determineFeature($objectclasses, $attr, $dbkey, $confkey, $po = false) { + private function determineFeature(array $objectclasses, string $attr, string $dbkey, string $confkey, bool $po = false): array { $cr = $this->getConnection(); if (!$cr) { throw new \Exception('Could not connect to LDAP'); @@ -1254,7 +1252,7 @@ class Wizard extends LDAPUtility { $availableFeatures = $this->cumulativeSearchOnAttribute($objectclasses, $attr, - $dig, $maxEntryObjC); + $dig, $maxEntryObjC); if (is_array($availableFeatures) && count($availableFeatures) > 0) { natcasesort($availableFeatures); @@ -1284,12 +1282,11 @@ class Wizard extends LDAPUtility { * @param array $result the return value from ldap_get_attributes * @param string $attribute the attribute values to look for * @param array &$known new values will be appended here - * @return int, state on of the class constants LRESULT_PROCESSED_OK, + * @return int state on of the class constants LRESULT_PROCESSED_OK, * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP */ - private function getAttributeValuesFromEntry($result, $attribute, &$known) { - if (!is_array($result) - || !isset($result['count']) + private function getAttributeValuesFromEntry(array $result, string $attribute, array &$known): int { + if (!isset($result['count']) || !$result['count'] > 0) { return self::LRESULT_PROCESSED_INVALID; } @@ -1313,7 +1310,7 @@ class Wizard extends LDAPUtility { } /** - * @return bool|mixed + * @return resource|\LDAP\Connection|false a link resource on success, otherwise false */ private function getConnection() { if (!is_null($this->cr)) { @@ -1325,6 +1322,10 @@ class Wizard extends LDAPUtility { $this->configuration->ldapPort ); + if ($cr === false) { + return false; + } + $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3); $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0); $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT); @@ -1333,8 +1334,8 @@ class Wizard extends LDAPUtility { } $lo = @$this->ldap->bind($cr, - $this->configuration->ldapAgentName, - $this->configuration->ldapAgentPassword); + $this->configuration->ldapAgentName, + $this->configuration->ldapAgentPassword); if ($lo === true) { $this->cr = $cr; return $cr; @@ -1343,10 +1344,7 @@ class Wizard extends LDAPUtility { return false; } - /** - * @return array - */ - private function getDefaultLdapPortSettings() { + private function getDefaultLdapPortSettings(): array { static $settings = [ ['port' => 7636, 'tls' => false], ['port' => 636, 'tls' => false], @@ -1358,10 +1356,7 @@ class Wizard extends LDAPUtility { return $settings; } - /** - * @return array - */ - private function getPortSettingsToTry() { + private function getPortSettingsToTry(): array { //389 ← LDAP / Unencrypted or StartTLS //636 ← LDAPS / SSL //7xxx ← UCS. need to be checked first, because both ports may be open @@ -1384,7 +1379,7 @@ class Wizard extends LDAPUtility { //default ports $portSettings = array_merge($portSettings, - $this->getDefaultLdapPortSettings()); + $this->getDefaultLdapPortSettings()); return $portSettings; } diff --git a/apps/user_ldap/tests/Group_LDAPTest.php b/apps/user_ldap/tests/Group_LDAPTest.php index 6204c22cb9e..edcb4be8819 100644 --- a/apps/user_ldap/tests/Group_LDAPTest.php +++ b/apps/user_ldap/tests/Group_LDAPTest.php @@ -154,7 +154,7 @@ class Group_LDAPTest extends TestCase { ->method('readAttribute') ->willReturnCallback(function ($name) { //the search operation will call readAttribute, thus we need - //to anaylze the "dn". All other times we just need to return + //to analyze the "dn". All other times we just need to return //something that is neither null or false, but once an array //with the users in the group – so we do so all other times for //simplicicity. @@ -724,7 +724,7 @@ class Group_LDAPTest extends TestCase { } /** - * tests that a user listing is complete, if all it's members have the group + * tests that a user listing is complete, if all its members have the group * as their primary. */ public function testUsersInGroupPrimaryMembersOnly() { @@ -770,7 +770,7 @@ class Group_LDAPTest extends TestCase { } /** - * tests that a user listing is complete, if all it's members have the group + * tests that a user listing is complete, if all its members have the group * as their primary. */ public function testUsersInGroupPrimaryAndUnixMembers() { @@ -814,7 +814,7 @@ class Group_LDAPTest extends TestCase { } /** - * tests that a user counting is complete, if all it's members have the group + * tests that a user counting is complete, if all its members have the group * as their primary. */ public function testCountUsersInGroupPrimaryMembersOnly() { @@ -941,20 +941,22 @@ class Group_LDAPTest extends TestCase { $access->connection = $this->createMock(Connection::class); $access->connection->expects($this->any()) ->method('__get') - ->willReturnCallback(function ($name) use ($nestedGroups, $groupFilter) { + ->willReturnCallback(function (string $name) use ($nestedGroups, $groupFilter) { switch ($name) { case 'useMemberOfToDetectMembership': return 0; case 'ldapDynamicGroupMemberURL': return ''; case 'ldapNestedGroups': - return $nestedGroups; + return (int)$nestedGroups; case 'ldapGroupMemberAssocAttr': return 'member'; case 'ldapGroupFilter': return $groupFilter; case 'ldapBaseGroups': return []; + case 'ldapGroupDisplayName': + return 'cn'; } return 1; }); @@ -980,30 +982,44 @@ class Group_LDAPTest extends TestCase { $group1 = [ 'cn' => 'group1', 'dn' => ['cn=group1,ou=groups,dc=domain,dc=com'], + 'member' => [$dn], ]; $group2 = [ 'cn' => 'group2', 'dn' => ['cn=group2,ou=groups,dc=domain,dc=com'], + 'member' => [$dn], + ]; + $group3 = [ + 'cn' => 'group3', + 'dn' => ['cn=group3,ou=groups,dc=domain,dc=com'], + 'member' => [$group2['dn'][0]], ]; - $access->expects($this->once()) + $expectedGroups = ($nestedGroups ? [$group1, $group2, $group3] : [$group1, $group2]); + $expectedGroupsNames = ($nestedGroups ? ['group1', 'group2', 'group3'] : ['group1', 'group2']); + + $access->expects($this->any()) ->method('nextcloudGroupNames') - ->with([$group1, $group2]) - ->willReturn(['group1', 'group2']); + ->with($expectedGroups) + ->willReturn($expectedGroupsNames); $access->expects($nestedGroups ? $this->atLeastOnce() : $this->once()) ->method('fetchListOfGroups') - ->willReturnCallback(function ($filter, $attr, $limit, $offset) use ($nestedGroups, $groupFilter, $group1, $group2) { + ->willReturnCallback(function ($filter, $attr, $limit, $offset) use ($nestedGroups, $groupFilter, $group1, $group2, $group3, $dn) { static $firstRun = true; if (!$nestedGroups) { // When nested groups are enabled, groups cannot be filtered early as it would // exclude intermediate groups. But we can, and should, when working with flat groups. $this->assertTrue(strpos($filter, $groupFilter) !== false); } - if ($firstRun) { - $firstRun = false; - return [$group1, $group2]; + [$memberFilter] = explode('&', $filter); + if ($memberFilter === 'member='.$dn) { + return [$group1, $group2]; + return []; + } elseif ($memberFilter === 'member='.$group2['dn'][0]) { + return [$group3]; + } else { + return []; } - return []; }); $access->expects($this->any()) ->method('dn2groupname') @@ -1012,13 +1028,16 @@ class Group_LDAPTest extends TestCase { }); $access->expects($this->any()) ->method('groupname2dn') - ->willReturnCallback(function (string $gid) use ($group1, $group2) { + ->willReturnCallback(function (string $gid) use ($group1, $group2, $group3) { if ($gid === $group1['cn']) { return $group1['dn'][0]; } if ($gid === $group2['cn']) { return $group2['dn'][0]; } + if ($gid === $group3['cn']) { + return $group3['dn'][0]; + } }); $access->expects($this->any()) ->method('isDNPartOfBase') @@ -1026,10 +1045,10 @@ class Group_LDAPTest extends TestCase { $groupBackend = new GroupLDAP($access, $pluginManager); $groups = $groupBackend->getUserGroups('userX'); - $this->assertEquals(['group1', 'group2'], $groups); + $this->assertEquals($expectedGroupsNames, $groups); $groupsAgain = $groupBackend->getUserGroups('userX'); - $this->assertEquals(['group1', 'group2'], $groupsAgain); + $this->assertEquals($expectedGroupsNames, $groupsAgain); } public function testCreateGroupWithPlugin() { @@ -1274,48 +1293,80 @@ class Group_LDAPTest extends TestCase { public function groupMemberProvider() { $base = 'dc=species,dc=earth'; - $groups0 = [ + $birdsDn = [ 'uid=3723,' . $base, 'uid=8372,' . $base, 'uid=8427,' . $base, 'uid=2333,' . $base, 'uid=4754,' . $base, ]; - $groups1 = [ + $birdsUid = [ '3723', '8372', '8427', '2333', '4754', ]; - $groups2Nested = ['6642', '1424']; - $expGroups2 = array_merge($groups1, $groups2Nested); + $animalsDn = [ + 'uid=lion,' . $base, + 'uid=tiger,' . $base, + ]; + $plantsDn = [ + 'uid=flower,' . $base, + 'uid=tree,' . $base, + ]; + $thingsDn = [ + 'uid=thing1,' . $base, + 'uid=thing2,' . $base, + ]; return [ [ #0 – test DNs - 'cn=Birds,' . $base, - $groups0, - ['cn=Birds,' . $base => $groups0] + ['cn=Birds,' . $base => $birdsDn], + ['cn=Birds,' . $base => $birdsDn] ], [ #1 – test uids - 'cn=Birds,' . $base, - $groups1, - ['cn=Birds,' . $base => $groups1] + ['cn=Birds,' . $base => $birdsUid], + ['cn=Birds,' . $base => $birdsUid] + ], + [ #2 – test simple nested group + ['cn=Animals,' . $base => array_merge($birdsDn, $animalsDn)], + [ + 'cn=Animals,' . $base => array_merge(['cn=Birds,' . $base], $animalsDn), + 'cn=Birds,' . $base => $birdsDn, + ] + ], + [ #3 – test recursive nested group + [ + 'cn=Animals,' . $base => array_merge($birdsDn, $animalsDn), + 'cn=Birds,' . $base => array_merge($birdsDn, $animalsDn), + ], + [ + 'cn=Animals,' . $base => array_merge(['cn=Birds,' . $base,'cn=Birds,' . $base,'cn=Animals,' . $base], $animalsDn), + 'cn=Birds,' . $base => array_merge(['cn=Animals,' . $base,'cn=Birds,' . $base], $birdsDn), + ] + ], + [ #4 – Complicated nested group + ['cn=Things,' . $base => array_merge($birdsDn, $animalsDn, $thingsDn, $plantsDn)], + [ + 'cn=Animals,' . $base => array_merge(['cn=Birds,' . $base], $animalsDn), + 'cn=Birds,' . $base => $birdsDn, + 'cn=Plants,' . $base => $plantsDn, + 'cn=Things,' . $base => array_merge(['cn=Animals,' . $base,'cn=Plants,' . $base], $thingsDn), + ] ], ]; } /** - * @param string $groupDN * @param string[] $expectedMembers - * @param array $groupsInfo * @dataProvider groupMemberProvider */ - public function testGroupMembers($groupDN, $expectedMembers, $groupsInfo = null) { + public function testGroupMembers(array $expectedResult, array $groupsInfo = null) { $access = $this->getAccessMock(); $access->expects($this->any()) ->method('readAttribute') - ->willReturnCallback(function ($group) use ($groupDN, $expectedMembers, $groupsInfo) { + ->willReturnCallback(function ($group) use ($groupsInfo) { if (isset($groupsInfo[$group])) { return $groupsInfo[$group]; } @@ -1325,7 +1376,7 @@ class Group_LDAPTest extends TestCase { $access->connection = $this->createMock(Connection::class); $access->connection->expects($this->any()) ->method('__get') - ->willReturnCallback(function ($name) { + ->willReturnCallback(function (string $name) { if ($name === 'ldapNestedGroups') { return 1; } elseif ($name === 'ldapGroupMemberAssocAttr') { @@ -1338,9 +1389,11 @@ class Group_LDAPTest extends TestCase { $pluginManager = $this->createMock(GroupPluginManager::class); $ldap = new GroupLDAP($access, $pluginManager); - $resultingMembers = $this->invokePrivate($ldap, '_groupMembers', [$groupDN]); + foreach ($expectedResult as $groupDN => $expectedMembers) { + $resultingMembers = $this->invokePrivate($ldap, '_groupMembers', [$groupDN]); - $this->assertEqualsCanonicalizing($expectedMembers, $resultingMembers); + $this->assertEqualsCanonicalizing($expectedMembers, $resultingMembers); + } } public function displayNameProvider() { diff --git a/apps/user_ldap/tests/Mapping/AbstractMappingTest.php b/apps/user_ldap/tests/Mapping/AbstractMappingTest.php index 0d21172445f..8439bcc57c4 100644 --- a/apps/user_ldap/tests/Mapping/AbstractMappingTest.php +++ b/apps/user_ldap/tests/Mapping/AbstractMappingTest.php @@ -82,7 +82,7 @@ abstract class AbstractMappingTest extends \Test\TestCase { } /** - * initalizes environment for a test run and returns an array with + * initializes environment for a test run and returns an array with * test objects. Preparing environment means that all mappings are cleared * first and then filled with test entries. * @return array 0 = \OCA\User_LDAP\Mapping\AbstractMapping, 1 = array of diff --git a/apps/user_ldap/tests/Mapping/UserMappingTest.php b/apps/user_ldap/tests/Mapping/UserMappingTest.php index 081266cf3f1..e585fafb134 100644 --- a/apps/user_ldap/tests/Mapping/UserMappingTest.php +++ b/apps/user_ldap/tests/Mapping/UserMappingTest.php @@ -24,6 +24,7 @@ namespace OCA\User_LDAP\Tests\Mapping; use OCA\User_LDAP\Mapping\UserMapping; +use OCP\Support\Subscription\IAssertion; /** * Class UserMappingTest @@ -34,6 +35,6 @@ use OCA\User_LDAP\Mapping\UserMapping; */ class UserMappingTest extends AbstractMappingTest { public function getMapper(\OCP\IDBConnection $dbMock) { - return new UserMapping($dbMock); + return new UserMapping($dbMock, $this->createMock(IAssertion::class)); } } diff --git a/apps/user_ldap/tests/User/UserTest.php b/apps/user_ldap/tests/User/UserTest.php index d2113de842e..470b0829868 100644 --- a/apps/user_ldap/tests/User/UserTest.php +++ b/apps/user_ldap/tests/User/UserTest.php @@ -493,7 +493,7 @@ class UserTest extends \Test\TestCase { $this->user->updateQuota(); } - //the testUpdateAvatar series also implicitely tests getAvatarImage + //the testUpdateAvatar series also implicitly tests getAvatarImage public function XtestUpdateAvatarJpegPhotoProvided() { $this->access->expects($this->once()) ->method('readAttribute') |