diff options
Diffstat (limited to 'lib')
42 files changed, 791 insertions, 38 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 50ea9bb3751..caddb528d5a 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -488,6 +488,8 @@ return array( 'OCP\\Security\\ICrypto' => $baseDir . '/lib/public/Security/ICrypto.php', 'OCP\\Security\\IHasher' => $baseDir . '/lib/public/Security/IHasher.php', 'OCP\\Security\\ISecureRandom' => $baseDir . '/lib/public/Security/ISecureRandom.php', + 'OCP\\Security\\VerificationToken\\IVerificationToken' => $baseDir . '/lib/public/Security/VerificationToken/IVerificationToken.php', + 'OCP\\Security\\VerificationToken\\InvalidTokenException' => $baseDir . '/lib/public/Security/VerificationToken/InvalidTokenException.php', 'OCP\\Session\\Exceptions\\SessionNotAvailableException' => $baseDir . '/lib/public/Session/Exceptions/SessionNotAvailableException.php', 'OCP\\Settings\\IIconSection' => $baseDir . '/lib/public/Settings/IIconSection.php', 'OCP\\Settings\\IManager' => $baseDir . '/lib/public/Settings/IManager.php', @@ -1373,6 +1375,8 @@ return array( 'OC\\Security\\RateLimiting\\Limiter' => $baseDir . '/lib/private/Security/RateLimiting/Limiter.php', 'OC\\Security\\SecureRandom' => $baseDir . '/lib/private/Security/SecureRandom.php', 'OC\\Security\\TrustedDomainHelper' => $baseDir . '/lib/private/Security/TrustedDomainHelper.php', + 'OC\\Security\\VerificationToken\\CleanUpJob' => $baseDir . '/lib/private/Security/VerificationToken/CleanUpJob.php', + 'OC\\Security\\VerificationToken\\VerificationToken' => $baseDir . '/lib/private/Security/VerificationToken/VerificationToken.php', 'OC\\Server' => $baseDir . '/lib/private/Server.php', 'OC\\ServerContainer' => $baseDir . '/lib/private/ServerContainer.php', 'OC\\ServerNotAvailableException' => $baseDir . '/lib/private/ServerNotAvailableException.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 2bb6ff22936..727fe0fc956 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -517,6 +517,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\Security\\ICrypto' => __DIR__ . '/../../..' . '/lib/public/Security/ICrypto.php', 'OCP\\Security\\IHasher' => __DIR__ . '/../../..' . '/lib/public/Security/IHasher.php', 'OCP\\Security\\ISecureRandom' => __DIR__ . '/../../..' . '/lib/public/Security/ISecureRandom.php', + 'OCP\\Security\\VerificationToken\\IVerificationToken' => __DIR__ . '/../../..' . '/lib/public/Security/VerificationToken/IVerificationToken.php', + 'OCP\\Security\\VerificationToken\\InvalidTokenException' => __DIR__ . '/../../..' . '/lib/public/Security/VerificationToken/InvalidTokenException.php', 'OCP\\Session\\Exceptions\\SessionNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/Session/Exceptions/SessionNotAvailableException.php', 'OCP\\Settings\\IIconSection' => __DIR__ . '/../../..' . '/lib/public/Settings/IIconSection.php', 'OCP\\Settings\\IManager' => __DIR__ . '/../../..' . '/lib/public/Settings/IManager.php', @@ -1402,6 +1404,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Security\\RateLimiting\\Limiter' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Limiter.php', 'OC\\Security\\SecureRandom' => __DIR__ . '/../../..' . '/lib/private/Security/SecureRandom.php', 'OC\\Security\\TrustedDomainHelper' => __DIR__ . '/../../..' . '/lib/private/Security/TrustedDomainHelper.php', + 'OC\\Security\\VerificationToken\\CleanUpJob' => __DIR__ . '/../../..' . '/lib/private/Security/VerificationToken/CleanUpJob.php', + 'OC\\Security\\VerificationToken\\VerificationToken' => __DIR__ . '/../../..' . '/lib/private/Security/VerificationToken/VerificationToken.php', 'OC\\Server' => __DIR__ . '/../../..' . '/lib/private/Server.php', 'OC\\ServerContainer' => __DIR__ . '/../../..' . '/lib/private/ServerContainer.php', 'OC\\ServerNotAvailableException' => __DIR__ . '/../../..' . '/lib/private/ServerNotAvailableException.php', diff --git a/lib/l10n/cs.js b/lib/l10n/cs.js index 2184bc5f0a4..a4a45ec2606 100644 --- a/lib/l10n/cs.js +++ b/lib/l10n/cs.js @@ -8,6 +8,11 @@ OC.L10N.register( "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Soubory aplikace %1$s nebyly nahrazeny řádně. Ověřte, že se jedná o verzi, která je kompatibilní se serverem.", "Sample configuration detected" : "Bylo zjištěno setrvání u předváděcího nastavení", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Pravděpodobně byla zkopírována nastavení ze vzorových souborů. Toto není podporováno a může poškodit vaši instalaci. Před prováděním změn v souboru config.php si přečtěte dokumentaci", + "%s email verification" : "%s ověřování e-mailem", + "Email verification" : "Ověřování e-mailem", + "Click the following button to confirm your email." : "Pokud chcete potvrdit svůj e-mail, klikněte na následující tlačítko.", + "Click the following link to confirm your email." : "Pokud chcete potvrdit svůj e-mail, klikněte na následující odkaz.", + "Confirm your email" : "Potvrďte svůj e-mail", "Other activities" : "Ostatní aktivity", "%1$s and %2$s" : "%1$s a %2$s", "%1$s, %2$s and %3$s" : "%1$s, %2$s a %3$s", diff --git a/lib/l10n/cs.json b/lib/l10n/cs.json index d09b47ba892..b6be60f6332 100644 --- a/lib/l10n/cs.json +++ b/lib/l10n/cs.json @@ -6,6 +6,11 @@ "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Soubory aplikace %1$s nebyly nahrazeny řádně. Ověřte, že se jedná o verzi, která je kompatibilní se serverem.", "Sample configuration detected" : "Bylo zjištěno setrvání u předváděcího nastavení", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Pravděpodobně byla zkopírována nastavení ze vzorových souborů. Toto není podporováno a může poškodit vaši instalaci. Před prováděním změn v souboru config.php si přečtěte dokumentaci", + "%s email verification" : "%s ověřování e-mailem", + "Email verification" : "Ověřování e-mailem", + "Click the following button to confirm your email." : "Pokud chcete potvrdit svůj e-mail, klikněte na následující tlačítko.", + "Click the following link to confirm your email." : "Pokud chcete potvrdit svůj e-mail, klikněte na následující odkaz.", + "Confirm your email" : "Potvrďte svůj e-mail", "Other activities" : "Ostatní aktivity", "%1$s and %2$s" : "%1$s a %2$s", "%1$s, %2$s and %3$s" : "%1$s, %2$s a %3$s", diff --git a/lib/l10n/de_DE.js b/lib/l10n/de_DE.js index 8e955410de4..ae5617ad42f 100644 --- a/lib/l10n/de_DE.js +++ b/lib/l10n/de_DE.js @@ -8,6 +8,11 @@ OC.L10N.register( "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Die Dateien der App %1$s wurden nicht korrekt ersetzt. Stellen Sie sicher, dass es sich um eine mit dem Server kompatible Version handelt.", "Sample configuration detected" : "Beispielkonfiguration gefunden", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Es wurde festgestellt, dass die Beispielkonfiguration kopiert wurde. Dies kann Ihre Installation zerstören und wird nicht unterstützt. Bitte die Dokumentation lesen, bevor Änderungen an der config.php vorgenommen werden.", + "%s email verification" : "%s E-Mail Überprüfung", + "Email verification" : "E-Mail Überprüfung", + "Click the following button to confirm your email." : "Klicken Sie auf die folgende Schaltfläche, um Ihre E-Mail zu bestätigen.", + "Click the following link to confirm your email." : "Auf den nachfolgenden Link klicken um Ihre E-Mail-Adresse zu bestätigen", + "Confirm your email" : "Ihre E-Mail-Adresse bestätigen", "Other activities" : "Andere Aktivitäten", "%1$s and %2$s" : "%1$s und %2$s", "%1$s, %2$s and %3$s" : "%1$s, %2$s und %3$s", diff --git a/lib/l10n/de_DE.json b/lib/l10n/de_DE.json index 06b7ec9375d..bb19ba32f83 100644 --- a/lib/l10n/de_DE.json +++ b/lib/l10n/de_DE.json @@ -6,6 +6,11 @@ "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Die Dateien der App %1$s wurden nicht korrekt ersetzt. Stellen Sie sicher, dass es sich um eine mit dem Server kompatible Version handelt.", "Sample configuration detected" : "Beispielkonfiguration gefunden", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Es wurde festgestellt, dass die Beispielkonfiguration kopiert wurde. Dies kann Ihre Installation zerstören und wird nicht unterstützt. Bitte die Dokumentation lesen, bevor Änderungen an der config.php vorgenommen werden.", + "%s email verification" : "%s E-Mail Überprüfung", + "Email verification" : "E-Mail Überprüfung", + "Click the following button to confirm your email." : "Klicken Sie auf die folgende Schaltfläche, um Ihre E-Mail zu bestätigen.", + "Click the following link to confirm your email." : "Auf den nachfolgenden Link klicken um Ihre E-Mail-Adresse zu bestätigen", + "Confirm your email" : "Ihre E-Mail-Adresse bestätigen", "Other activities" : "Andere Aktivitäten", "%1$s and %2$s" : "%1$s und %2$s", "%1$s, %2$s and %3$s" : "%1$s, %2$s und %3$s", diff --git a/lib/l10n/eu.js b/lib/l10n/eu.js index 2e1b864b05b..f281fbdb093 100644 --- a/lib/l10n/eu.js +++ b/lib/l10n/eu.js @@ -8,6 +8,11 @@ OC.L10N.register( "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "%1$s aplikazioaren fitxategiak ez dira behar bezala ordezkatu. Ziurtatu zerbitzariarekin bateragarria den bertsioa dela.", "Sample configuration detected" : "Adibide-ezarpena detektatua", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Detektatu da adibide-ezarpena kopiatu dela. Honek zure instalazioa apur dezake eta ez da onartzen. Irakurri dokumentazioa config.php fitxategia aldatu aurretik.", + "%s email verification" : "%sposta elektronikoaren egiaztapena", + "Email verification" : "Posta elektronikoaren egiaztapena", + "Click the following button to confirm your email." : "Egin klik hurrengo botoian zure posta elektronikoa berresteko.", + "Click the following link to confirm your email." : "Egin klik esteka honetan zure posta elektronikoa berresteko.", + "Confirm your email" : "Berretsi zure posta elektronikoa", "Other activities" : "Beste jarduerak", "%1$s and %2$s" : "%1$s eta %2$s", "%1$s, %2$s and %3$s" : "%1$s, %2$s eta %3$s", diff --git a/lib/l10n/eu.json b/lib/l10n/eu.json index 1785fbdb36d..00c66fa85b4 100644 --- a/lib/l10n/eu.json +++ b/lib/l10n/eu.json @@ -6,6 +6,11 @@ "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "%1$s aplikazioaren fitxategiak ez dira behar bezala ordezkatu. Ziurtatu zerbitzariarekin bateragarria den bertsioa dela.", "Sample configuration detected" : "Adibide-ezarpena detektatua", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Detektatu da adibide-ezarpena kopiatu dela. Honek zure instalazioa apur dezake eta ez da onartzen. Irakurri dokumentazioa config.php fitxategia aldatu aurretik.", + "%s email verification" : "%sposta elektronikoaren egiaztapena", + "Email verification" : "Posta elektronikoaren egiaztapena", + "Click the following button to confirm your email." : "Egin klik hurrengo botoian zure posta elektronikoa berresteko.", + "Click the following link to confirm your email." : "Egin klik esteka honetan zure posta elektronikoa berresteko.", + "Confirm your email" : "Berretsi zure posta elektronikoa", "Other activities" : "Beste jarduerak", "%1$s and %2$s" : "%1$s eta %2$s", "%1$s, %2$s and %3$s" : "%1$s, %2$s eta %3$s", diff --git a/lib/l10n/pl.js b/lib/l10n/pl.js index 9bb62032f3a..5b2ad429bfd 100644 --- a/lib/l10n/pl.js +++ b/lib/l10n/pl.js @@ -8,6 +8,11 @@ OC.L10N.register( "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Pliki aplikacji %1$s nie zostały poprawnie zastąpione. Upewnij się, że jest to wersja zgodna z serwerem.", "Sample configuration detected" : "Wykryto przykładową konfigurację", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Wykryto, że przykładowa konfiguracja została skopiowana. Może to spowodować przerwanie instalacji, która nie jest wspierana. Przeczytaj dokumentację przed dokonaniem zmian w pliku config.php", + "%s email verification" : "Weryfikacja adresu e-mail %s", + "Email verification" : "Weryfikacja adresu e-mail", + "Click the following button to confirm your email." : "Kliknij poniższy przycisk, aby potwierdzić swój adres e-mail.", + "Click the following link to confirm your email." : "Kliknij poniższy link, aby potwierdzić swój adres e-mail.", + "Confirm your email" : "Potwierdź swój adres e-mail", "Other activities" : "Inne czynności", "%1$s and %2$s" : "%1$s i %2$s", "%1$s, %2$s and %3$s" : "%1$s, %2$s i %3$s", diff --git a/lib/l10n/pl.json b/lib/l10n/pl.json index 7c2cbb0beda..5bd59da033e 100644 --- a/lib/l10n/pl.json +++ b/lib/l10n/pl.json @@ -6,6 +6,11 @@ "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Pliki aplikacji %1$s nie zostały poprawnie zastąpione. Upewnij się, że jest to wersja zgodna z serwerem.", "Sample configuration detected" : "Wykryto przykładową konfigurację", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Wykryto, że przykładowa konfiguracja została skopiowana. Może to spowodować przerwanie instalacji, która nie jest wspierana. Przeczytaj dokumentację przed dokonaniem zmian w pliku config.php", + "%s email verification" : "Weryfikacja adresu e-mail %s", + "Email verification" : "Weryfikacja adresu e-mail", + "Click the following button to confirm your email." : "Kliknij poniższy przycisk, aby potwierdzić swój adres e-mail.", + "Click the following link to confirm your email." : "Kliknij poniższy link, aby potwierdzić swój adres e-mail.", + "Confirm your email" : "Potwierdź swój adres e-mail", "Other activities" : "Inne czynności", "%1$s and %2$s" : "%1$s i %2$s", "%1$s, %2$s and %3$s" : "%1$s, %2$s i %3$s", diff --git a/lib/l10n/sl.js b/lib/l10n/sl.js index 42fa20db8ec..2146e485be5 100644 --- a/lib/l10n/sl.js +++ b/lib/l10n/sl.js @@ -112,6 +112,8 @@ OC.L10N.register( "Open »%s«" : "Odpri »%s«", "%1$s via %2$s" : "%1$s prek %2$s", "You are not allowed to share %s" : "Omogočanje souporabe %s brez ustreznih dovoljenj ni mogoče.", + "Files cannot be shared with delete permissions" : "Souporaba datotek z nastavljenim dovoljenjem za brisanje ni mogoča", + "Files cannot be shared with create permissions" : "Souporaba datotek z nastavljenim dovoljenjem za ustvarjanje ni mogoča", "Expiration date is in the past" : "Datum preteka je že mimo!", "Sharing is only allowed with group members" : "Souporaba je dovoljena le med člani skupine", "Sharing %s failed, because this item is already shared with user %s" : "Nastavljanje souporabe %s je spodletelo, ker je predmet že v souporabi z uporabnikom %s.", @@ -179,6 +181,7 @@ OC.L10N.register( "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Programa »%1$s« ni mogoče namestiti zaradi nerešenih odvisnosti: %2$s", "a safe home for all your data" : "Varno okolje za vaše podatke!", "File is currently busy, please try again later" : "Datoteka je trenutno v uporabi. Poskusite znova kasneje.", + "Cannot read file" : "Datoteke ni mogoče prebrati.", "Application is not enabled" : "Program ni omogočen", "Authentication error" : "Napaka overjanja", "Token expired. Please reload page." : "Žeton je pretekel. Stran je treba ponovno naložiti.", diff --git a/lib/l10n/sl.json b/lib/l10n/sl.json index 967510b0f06..2af4d2a2aae 100644 --- a/lib/l10n/sl.json +++ b/lib/l10n/sl.json @@ -110,6 +110,8 @@ "Open »%s«" : "Odpri »%s«", "%1$s via %2$s" : "%1$s prek %2$s", "You are not allowed to share %s" : "Omogočanje souporabe %s brez ustreznih dovoljenj ni mogoče.", + "Files cannot be shared with delete permissions" : "Souporaba datotek z nastavljenim dovoljenjem za brisanje ni mogoča", + "Files cannot be shared with create permissions" : "Souporaba datotek z nastavljenim dovoljenjem za ustvarjanje ni mogoča", "Expiration date is in the past" : "Datum preteka je že mimo!", "Sharing is only allowed with group members" : "Souporaba je dovoljena le med člani skupine", "Sharing %s failed, because this item is already shared with user %s" : "Nastavljanje souporabe %s je spodletelo, ker je predmet že v souporabi z uporabnikom %s.", @@ -177,6 +179,7 @@ "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Programa »%1$s« ni mogoče namestiti zaradi nerešenih odvisnosti: %2$s", "a safe home for all your data" : "Varno okolje za vaše podatke!", "File is currently busy, please try again later" : "Datoteka je trenutno v uporabi. Poskusite znova kasneje.", + "Cannot read file" : "Datoteke ni mogoče prebrati.", "Application is not enabled" : "Program ni omogočen", "Authentication error" : "Napaka overjanja", "Token expired. Please reload page." : "Žeton je pretekel. Stran je treba ponovno naložiti.", diff --git a/lib/l10n/tr.js b/lib/l10n/tr.js index ef24b74558e..b02aeb5d0fb 100644 --- a/lib/l10n/tr.js +++ b/lib/l10n/tr.js @@ -8,6 +8,11 @@ OC.L10N.register( "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "%1$s uygulamasının dosyaları doğru şekilde değiştirilmedi. Sunucu ile uyumlu dosyaların yüklü olduğundan emin olun.", "Sample configuration detected" : "Örnek yapılandırma algılandı", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Örnek yapılandırmanın kopyalanmış olabileceği tespit edildi. Bu durum kurulumunuzu bozabilir ve desteklenmez. Lütfen config.php dosyasında değişiklik yapmadan önce belgeleri okuyun", + "%s email verification" : "%s e-posta doğrulaması", + "Email verification" : "E-posta doğrulaması", + "Click the following button to confirm your email." : "E-posta adresinizi doğrulamak için aşağıdaki düğmeye tıklayın.", + "Click the following link to confirm your email." : "E-posta adresinizi doğrulamak için aşağıdaki bağlantıya tıklayın", + "Confirm your email" : "E-posta adresinizi doğrulayın", "Other activities" : "Diğer işlemler", "%1$s and %2$s" : "%1$s ve %2$s", "%1$s, %2$s and %3$s" : "%1$s, %2$s ve %3$s", diff --git a/lib/l10n/tr.json b/lib/l10n/tr.json index 8fad00d0024..c676ef5b3c7 100644 --- a/lib/l10n/tr.json +++ b/lib/l10n/tr.json @@ -6,6 +6,11 @@ "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "%1$s uygulamasının dosyaları doğru şekilde değiştirilmedi. Sunucu ile uyumlu dosyaların yüklü olduğundan emin olun.", "Sample configuration detected" : "Örnek yapılandırma algılandı", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Örnek yapılandırmanın kopyalanmış olabileceği tespit edildi. Bu durum kurulumunuzu bozabilir ve desteklenmez. Lütfen config.php dosyasında değişiklik yapmadan önce belgeleri okuyun", + "%s email verification" : "%s e-posta doğrulaması", + "Email verification" : "E-posta doğrulaması", + "Click the following button to confirm your email." : "E-posta adresinizi doğrulamak için aşağıdaki düğmeye tıklayın.", + "Click the following link to confirm your email." : "E-posta adresinizi doğrulamak için aşağıdaki bağlantıya tıklayın", + "Confirm your email" : "E-posta adresinizi doğrulayın", "Other activities" : "Diğer işlemler", "%1$s and %2$s" : "%1$s ve %2$s", "%1$s, %2$s and %3$s" : "%1$s, %2$s ve %3$s", diff --git a/lib/l10n/zh_CN.js b/lib/l10n/zh_CN.js index daefb4cf3a1..9d6074444b9 100644 --- a/lib/l10n/zh_CN.js +++ b/lib/l10n/zh_CN.js @@ -8,6 +8,11 @@ OC.L10N.register( "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "应用%1$s的文件替换不正确。请确认版本与当前服务器兼容。", "Sample configuration detected" : "示例配置检测", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接把 config.php 的样例文件直接复制使用。这可能会破坏您的安装。在对 config.php 进行修改之前请先阅读相关文档。", + "%s email verification" : "%s 电子邮件验证", + "Email verification" : "电子邮件验证", + "Click the following button to confirm your email." : "点击下方按钮确认电子邮件地址", + "Click the following link to confirm your email." : "点击下方链接确认电子邮件地址", + "Confirm your email" : "确认电子邮件地址", "Other activities" : "其他动态", "%1$s and %2$s" : "%1$s 和 %2$s", "%1$s, %2$s and %3$s" : "%1$s,%2$s 和 %3$s", diff --git a/lib/l10n/zh_CN.json b/lib/l10n/zh_CN.json index 50fb8e1109f..851b3b3c2e2 100644 --- a/lib/l10n/zh_CN.json +++ b/lib/l10n/zh_CN.json @@ -6,6 +6,11 @@ "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "应用%1$s的文件替换不正确。请确认版本与当前服务器兼容。", "Sample configuration detected" : "示例配置检测", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接把 config.php 的样例文件直接复制使用。这可能会破坏您的安装。在对 config.php 进行修改之前请先阅读相关文档。", + "%s email verification" : "%s 电子邮件验证", + "Email verification" : "电子邮件验证", + "Click the following button to confirm your email." : "点击下方按钮确认电子邮件地址", + "Click the following link to confirm your email." : "点击下方链接确认电子邮件地址", + "Confirm your email" : "确认电子邮件地址", "Other activities" : "其他动态", "%1$s and %2$s" : "%1$s 和 %2$s", "%1$s, %2$s and %3$s" : "%1$s,%2$s 和 %3$s", diff --git a/lib/l10n/zh_HK.js b/lib/l10n/zh_HK.js index 94866af0545..94b90efea92 100644 --- a/lib/l10n/zh_HK.js +++ b/lib/l10n/zh_HK.js @@ -8,6 +8,11 @@ OC.L10N.register( "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "應用程式 %1$s 中的檔案沒有被正確取代,請確認它的版本與伺服器相容。", "Sample configuration detected" : "您目前正在使用範例設定", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接複製了範例設定來使用,這樣的安裝很可能會無法運作,請閱讀說明文件後對 config.php 進行適當的修改", + "%s email verification" : "%s 電郵地址驗證", + "Email verification" : "電郵地址驗證", + "Click the following button to confirm your email." : "單擊以下按鈕以確認您的電郵地址。", + "Click the following link to confirm your email." : "單擊以下連結以確認您的電郵地址。", + "Confirm your email" : "確認您的電郵地址", "Other activities" : "其它活動", "%1$s and %2$s" : "%1$s 和 %2$s", "%1$s, %2$s and %3$s" : "%1$s、%2$s 和 %3$s", diff --git a/lib/l10n/zh_HK.json b/lib/l10n/zh_HK.json index 1616629d3c0..4dbe81cc58a 100644 --- a/lib/l10n/zh_HK.json +++ b/lib/l10n/zh_HK.json @@ -6,6 +6,11 @@ "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "應用程式 %1$s 中的檔案沒有被正確取代,請確認它的版本與伺服器相容。", "Sample configuration detected" : "您目前正在使用範例設定", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接複製了範例設定來使用,這樣的安裝很可能會無法運作,請閱讀說明文件後對 config.php 進行適當的修改", + "%s email verification" : "%s 電郵地址驗證", + "Email verification" : "電郵地址驗證", + "Click the following button to confirm your email." : "單擊以下按鈕以確認您的電郵地址。", + "Click the following link to confirm your email." : "單擊以下連結以確認您的電郵地址。", + "Confirm your email" : "確認您的電郵地址", "Other activities" : "其它活動", "%1$s and %2$s" : "%1$s 和 %2$s", "%1$s, %2$s and %3$s" : "%1$s、%2$s 和 %3$s", diff --git a/lib/l10n/zh_TW.js b/lib/l10n/zh_TW.js index 4a1517b455c..ce96a8df81a 100644 --- a/lib/l10n/zh_TW.js +++ b/lib/l10n/zh_TW.js @@ -8,6 +8,11 @@ OC.L10N.register( "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "應用程式 %1$s 中的檔案沒有被正確取代,請確認它的版本與伺服器相容。", "Sample configuration detected" : "您目前正在使用範例設定", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接複製了範例設定來使用,這樣的安裝很可能會無法運作,請閱讀說明文件後對 config.php 進行適當的修改", + "%s email verification" : "%s 電子郵件驗證", + "Email verification" : "電子郵件驗證", + "Click the following button to confirm your email." : "點擊以下按鈕以確認您的電子郵件。", + "Click the following link to confirm your email." : "點擊以下連結以確認您的電子郵件。", + "Confirm your email" : "確認您的電子郵件", "Other activities" : "其它活動", "%1$s and %2$s" : "%1$s 和 %2$s", "%1$s, %2$s and %3$s" : "%1$s, %2$s 和 %3$s", diff --git a/lib/l10n/zh_TW.json b/lib/l10n/zh_TW.json index e428bbe0d07..77557697f51 100644 --- a/lib/l10n/zh_TW.json +++ b/lib/l10n/zh_TW.json @@ -6,6 +6,11 @@ "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "應用程式 %1$s 中的檔案沒有被正確取代,請確認它的版本與伺服器相容。", "Sample configuration detected" : "您目前正在使用範例設定", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接複製了範例設定來使用,這樣的安裝很可能會無法運作,請閱讀說明文件後對 config.php 進行適當的修改", + "%s email verification" : "%s 電子郵件驗證", + "Email verification" : "電子郵件驗證", + "Click the following button to confirm your email." : "點擊以下按鈕以確認您的電子郵件。", + "Click the following link to confirm your email." : "點擊以下連結以確認您的電子郵件。", + "Confirm your email" : "確認您的電子郵件", "Other activities" : "其它活動", "%1$s and %2$s" : "%1$s 和 %2$s", "%1$s, %2$s and %3$s" : "%1$s, %2$s 和 %3$s", diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php index 9fc5accfa08..a3f971df6a1 100644 --- a/lib/private/Accounts/AccountManager.php +++ b/lib/private/Accounts/AccountManager.php @@ -32,6 +32,7 @@ */ namespace OC\Accounts; +use Exception; use InvalidArgumentException; use libphonenumber\NumberParseException; use libphonenumber\PhoneNumber; @@ -45,9 +46,17 @@ use OCP\Accounts\IAccountPropertyCollection; use OCP\Accounts\PropertyDoesNotExistException; use OCP\BackgroundJob\IJobList; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Defaults; use OCP\IConfig; use OCP\IDBConnection; +use OCP\IL10N; +use OCP\IURLGenerator; use OCP\IUser; +use OCP\L10N\IFactory; +use OCP\Mail\IMailer; +use OCP\Security\ICrypto; +use OCP\Security\VerificationToken\IVerificationToken; +use OCP\Util; use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -88,17 +97,46 @@ class AccountManager implements IAccountManager { /** @var LoggerInterface */ private $logger; - - public function __construct(IDBConnection $connection, - IConfig $config, - EventDispatcherInterface $eventDispatcher, - IJobList $jobList, - LoggerInterface $logger) { + /** @var IVerificationToken */ + private $verificationToken; + /** @var IMailer */ + private $mailer; + /** @var Defaults */ + private $defaults; + /** @var IL10N */ + private $l10n; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var ICrypto */ + private $crypto; + /** @var IFactory */ + private $l10nfactory; + + public function __construct( + IDBConnection $connection, + IConfig $config, + EventDispatcherInterface $eventDispatcher, + IJobList $jobList, + LoggerInterface $logger, + IVerificationToken $verificationToken, + IMailer $mailer, + Defaults $defaults, + IFactory $factory, + IURLGenerator $urlGenerator, + ICrypto $crypto + ) { $this->connection = $connection; $this->config = $config; $this->eventDispatcher = $eventDispatcher; $this->jobList = $jobList; $this->logger = $logger; + $this->verificationToken = $verificationToken; + $this->mailer = $mailer; + $this->defaults = $defaults; + $this->urlGenerator = $urlGenerator; + $this->crypto = $crypto; + // DIing IL10N results in a dependency loop + $this->l10nfactory = $factory; } /** @@ -337,7 +375,6 @@ class AccountManager implements IAccountManager { /** * check if we need to ask the server for email verification, if yes we create a cronjob - * */ protected function checkEmailVerification(IAccount $updatedAccount, array $oldData): void { try { @@ -358,11 +395,73 @@ class AccountManager implements IAccountManager { ] ); + $property->setVerified(self::VERIFICATION_IN_PROGRESS); + } + } + + protected function checkLocalEmailVerification(IAccount $updatedAccount, array $oldData): void { + $mailCollection = $updatedAccount->getPropertyCollection(self::COLLECTION_EMAIL); + foreach ($mailCollection->getProperties() as $property) { + if ($property->getLocallyVerified() !== self::NOT_VERIFIED) { + continue; + } + if ($this->sendEmailVerificationEmail($updatedAccount->getUser(), $property->getValue())) { + $property->setLocallyVerified(self::VERIFICATION_IN_PROGRESS); + } + } + } + + protected function sendEmailVerificationEmail(IUser $user, string $email): bool { + $ref = \substr(hash('sha256', $email), 0, 8); + $key = $this->crypto->encrypt($email); + $token = $this->verificationToken->create($user, 'verifyMail' . $ref, $email); + $link = $this->urlGenerator->linkToRouteAbsolute('provisioning_api.Verification.verifyMail', + [ + 'userId' => $user->getUID(), + 'token' => $token, + 'key' => $key + ]); + $emailTemplate = $this->mailer->createEMailTemplate('core.EmailVerification', [ + 'link' => $link, + ]); - $property->setVerified(self::VERIFICATION_IN_PROGRESS); + if (!$this->l10n) { + $this->l10n = $this->l10nfactory->get('core'); } + + $emailTemplate->setSubject($this->l10n->t('%s email verification', [$this->defaults->getName()])); + $emailTemplate->addHeader(); + $emailTemplate->addHeading($this->l10n->t('Email verification')); + + $emailTemplate->addBodyText( + htmlspecialchars($this->l10n->t('Click the following button to confirm your email.')), + $this->l10n->t('Click the following link to confirm your email.') + ); + + $emailTemplate->addBodyButton( + htmlspecialchars($this->l10n->t('Confirm your email')), + $link, + false + ); + $emailTemplate->addFooter(); + + try { + $message = $this->mailer->createMessage(); + $message->setTo([$email => $user->getDisplayName()]); + $message->setFrom([Util::getDefaultEmailAddress('verification-noreply') => $this->defaults->getName()]); + $message->useTemplate($emailTemplate); + $this->mailer->send($message); + } catch (Exception $e) { + // Log the exception and continue + $this->logger->info('Failed to send verification mail', [ + 'app' => 'core', + 'exception' => $e + ]); + return false; + } + return true; } /** @@ -406,7 +505,6 @@ class AccountManager implements IAccountManager { } } - /** * add new user to accounts table * @@ -435,6 +533,12 @@ class AccountManager implements IAccountManager { foreach ($data as $dataRow) { $propertyName = $dataRow['name']; unset($dataRow['name']); + + if (isset($dataRow['locallyVerified']) && $dataRow['locallyVerified'] === self::NOT_VERIFIED) { + // do not write default value, save DB space + unset($dataRow['locallyVerified']); + } + if (!$this->isCollection($propertyName)) { $preparedData[$propertyName] = $dataRow; continue; @@ -511,7 +615,6 @@ class AccountManager implements IAccountManager { continue; } - $query->setParameter('name', $property['name']) ->setParameter('value', $property['value'] ?? ''); $query->executeStatement(); @@ -587,6 +690,7 @@ class AccountManager implements IAccountManager { $data['verified'] ?? self::NOT_VERIFIED, '' ); + $p->setLocallyVerified($data['locallyVerified'] ?? self::NOT_VERIFIED); $collection->addProperty($p); return $collection; @@ -599,6 +703,10 @@ class AccountManager implements IAccountManager { $account->setPropertyCollection($this->arrayDataToCollection($account, $accountData)); } else { $account->setProperty($accountData['name'], $accountData['value'] ?? '', $accountData['scope'] ?? self::SCOPE_LOCAL, $accountData['verified'] ?? self::NOT_VERIFIED); + if (isset($accountData['locallyVerified'])) { + $property = $account->getProperty($accountData['name']); + $property->setLocallyVerified($accountData['locallyVerified']); + } } } return $account; @@ -640,14 +748,17 @@ class AccountManager implements IAccountManager { $oldData = $this->getUser($account->getUser(), false); $this->updateVerificationStatus($account, $oldData); $this->checkEmailVerification($account, $oldData); + $this->checkLocalEmailVerification($account, $oldData); $data = []; foreach ($account->getAllProperties() as $property) { + /** @var IAccountProperty $property */ $data[] = [ 'name' => $property->getName(), 'value' => $property->getValue(), 'scope' => $property->getScope(), 'verified' => $property->getVerified(), + 'locallyVerified' => $property->getLocallyVerified(), ]; } diff --git a/lib/private/Accounts/AccountProperty.php b/lib/private/Accounts/AccountProperty.php index 1a21baf9698..0e6356e9e92 100644 --- a/lib/private/Accounts/AccountProperty.php +++ b/lib/private/Accounts/AccountProperty.php @@ -27,6 +27,7 @@ declare(strict_types=1); */ namespace OC\Accounts; +use InvalidArgumentException; use OCP\Accounts\IAccountManager; use OCP\Accounts\IAccountProperty; @@ -42,6 +43,8 @@ class AccountProperty implements IAccountProperty { private $verified; /** @var string */ private $verificationData; + /** @var string */ + private $locallyVerified = IAccountManager::NOT_VERIFIED; public function __construct(string $name, string $value, string $scope, string $verified, string $verificationData) { $this->name = $name; @@ -90,7 +93,7 @@ class AccountProperty implements IAccountProperty { IAccountManager::SCOPE_PRIVATE, IAccountManager::SCOPE_PUBLISHED ])) { - throw new \InvalidArgumentException('Invalid scope'); + throw new InvalidArgumentException('Invalid scope'); } $this->scope = $newScope; return $this; @@ -178,4 +181,20 @@ class AccountProperty implements IAccountProperty { public function getVerificationData(): string { return $this->verificationData; } + + public function setLocallyVerified(string $verified): IAccountProperty { + if (!in_array($verified, [ + IAccountManager::NOT_VERIFIED, + IAccountManager::VERIFICATION_IN_PROGRESS, + IAccountManager::VERIFIED, + ])) { + throw new InvalidArgumentException('Provided verification value is invalid'); + } + $this->locallyVerified = $verified; + return $this; + } + + public function getLocallyVerified(): string { + return $this->locallyVerified; + } } diff --git a/lib/private/Accounts/AccountPropertyCollection.php b/lib/private/Accounts/AccountPropertyCollection.php index eb92536a6a0..3aed76d8746 100644 --- a/lib/private/Accounts/AccountPropertyCollection.php +++ b/lib/private/Accounts/AccountPropertyCollection.php @@ -84,6 +84,15 @@ class AccountPropertyCollection implements IAccountPropertyCollection { return $this; } + public function getPropertyByValue(string $value): ?IAccountProperty { + foreach ($this->properties as $i => $property) { + if ($property->getValue() === $value) { + return $property; + } + } + return null; + } + public function removePropertyByValue(string $value): IAccountPropertyCollection { foreach ($this->properties as $i => $property) { if ($property->getValue() === $value) { diff --git a/lib/private/Collaboration/Collaborators/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php index e3e4b37f383..9ed94082f0d 100644 --- a/lib/private/Collaboration/Collaborators/UserPlugin.php +++ b/lib/private/Collaboration/Collaborators/UserPlugin.php @@ -157,7 +157,7 @@ class UserPlugin implements ISearchPlugin { $userStatuses = $this->userStatusManager->getUserStatuses(array_keys($users)); foreach ($users as $uid => $user) { $userDisplayName = $user->getDisplayName(); - $userEmail = $user->getEMailAddress(); + $userEmail = $user->getSystemEMailAddress(); $uid = (string) $uid; $status = []; @@ -244,7 +244,7 @@ class UserPlugin implements ISearchPlugin { if ($addUser) { $status = []; $uid = $user->getUID(); - $userEmail = $user->getEMailAddress(); + $userEmail = $user->getSystemEMailAddress(); if (array_key_exists($user->getUID(), $userStatuses)) { $userStatus = $userStatuses[$user->getUID()]; $status = [ diff --git a/lib/private/DB/AdapterMySQL.php b/lib/private/DB/AdapterMySQL.php index 43da88b4b74..b4be5c2e96a 100644 --- a/lib/private/DB/AdapterMySQL.php +++ b/lib/private/DB/AdapterMySQL.php @@ -25,7 +25,7 @@ namespace OC\DB; class AdapterMySQL extends Adapter { /** @var string */ - protected $charset; + protected $collation; /** * @param string $tableName @@ -39,16 +39,16 @@ class AdapterMySQL extends Adapter { } public function fixupStatement($statement) { - $statement = str_replace(' ILIKE ', ' COLLATE ' . $this->getCharset() . '_general_ci LIKE ', $statement); + $statement = str_replace(' ILIKE ', ' COLLATE ' . $this->getCollation() . ' LIKE ', $statement); return $statement; } - protected function getCharset() { - if (!$this->charset) { + protected function getCollation(): string { + if (!$this->collation) { $params = $this->conn->getParams(); - $this->charset = isset($params['charset']) ? $params['charset'] : 'utf8'; + $this->collation = $params['collation'] ?? (($params['charset'] ?? 'utf8') . '_general_ci'); } - return $this->charset; + return $this->collation; } } diff --git a/lib/private/DB/ConnectionFactory.php b/lib/private/DB/ConnectionFactory.php index 53e488b5f09..b4c7597f6d4 100644 --- a/lib/private/DB/ConnectionFactory.php +++ b/lib/private/DB/ConnectionFactory.php @@ -89,6 +89,10 @@ class ConnectionFactory { if ($this->config->getValue('mysql.utf8mb4', false)) { $this->defaultConnectionParams['mysql']['charset'] = 'utf8mb4'; } + $collationOverride = $this->config->getValue('mysql.collation', null); + if ($collationOverride) { + $this->defaultConnectionParams['mysql']['collation'] = $collationOverride; + } } /** diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php index 3a0f45bcde7..e917ad3ad3a 100644 --- a/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php +++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php @@ -31,7 +31,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder; class MySqlExpressionBuilder extends ExpressionBuilder { /** @var string */ - protected $charset; + protected $collation; /** * @param ConnectionAdapter $connection @@ -41,7 +41,7 @@ class MySqlExpressionBuilder extends ExpressionBuilder { parent::__construct($connection, $queryBuilder); $params = $connection->getInner()->getParams(); - $this->charset = isset($params['charset']) ? $params['charset'] : 'utf8'; + $this->collation = $params['collation'] ?? (($params['charset'] ?? 'utf8') . '_general_ci'); } /** @@ -50,6 +50,6 @@ class MySqlExpressionBuilder extends ExpressionBuilder { public function iLike($x, $y, $type = null): string { $x = $this->helper->quoteColumnName($x); $y = $this->helper->quoteColumnName($y); - return $this->expressionBuilder->comparison($x, ' COLLATE ' . $this->charset . '_general_ci LIKE', $y); + return $this->expressionBuilder->comparison($x, ' COLLATE ' . $this->collation . ' LIKE', $y); } } diff --git a/lib/private/Mail/EMailTemplate.php b/lib/private/Mail/EMailTemplate.php index efe1a6eef1d..a83f7787829 100644 --- a/lib/private/Mail/EMailTemplate.php +++ b/lib/private/Mail/EMailTemplate.php @@ -568,7 +568,7 @@ EOF; * * @param string $text Text of button; Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email * @param string $url URL of button - * @param string $plainText Text of button in plain text version + * @param string|false $plainText Text of button in plain text version * if empty the $text is used, if false none will be used * * @since 12.0.0 diff --git a/lib/private/Security/VerificationToken/CleanUpJob.php b/lib/private/Security/VerificationToken/CleanUpJob.php new file mode 100644 index 00000000000..331172898ec --- /dev/null +++ b/lib/private/Security/VerificationToken/CleanUpJob.php @@ -0,0 +1,90 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2021 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 <https://www.gnu.org/licenses/>. + * + */ + +namespace OC\Security\VerificationToken; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IUserManager; +use OCP\Security\VerificationToken\InvalidTokenException; +use OCP\Security\VerificationToken\IVerificationToken; + +class CleanUpJob extends \OCP\BackgroundJob\Job { + + /** @var int */ + protected $runNotBefore; + /** @var string */ + protected $userId; + /** @var string */ + protected $subject; + /** @var string */ + protected $pwdPrefix; + /** @var IConfig */ + private $config; + /** @var IVerificationToken */ + private $verificationToken; + /** @var IUserManager */ + private $userManager; + + public function __construct(ITimeFactory $time, IConfig $config, IVerificationToken $verificationToken, IUserManager $userManager) { + parent::__construct($time); + $this->config = $config; + $this->verificationToken = $verificationToken; + $this->userManager = $userManager; + } + + public function setArgument($argument) { + parent::setArgument($argument); + $args = \json_decode($argument); + $this->userId = (string)$args['userId']; + $this->subject = (string)$args['subject']; + $this->pwdPrefix = (string)$args['pp']; + $this->runNotBefore = (int)$args['notBefore']; + } + + protected function run($argument) { + try { + $user = $this->userManager->get($this->userId); + if ($user === null) { + return; + } + $this->verificationToken->check('irrelevant', $user, $this->subject, $this->pwdPrefix); + } catch (InvalidTokenException $e) { + if ($e->getCode() === InvalidTokenException::TOKEN_EXPIRED) { + // make sure to only remove expired tokens + $this->config->deleteUserValue($this->userId, 'core', $this->subject); + } + } + } + + public function execute($jobList, ILogger $logger = null) { + if ($this->time->getTime() >= $this->runNotBefore) { + $jobList->remove($this, $this->argument); + parent::execute($jobList, $logger); + } + } +} diff --git a/lib/private/Security/VerificationToken/VerificationToken.php b/lib/private/Security/VerificationToken/VerificationToken.php new file mode 100644 index 00000000000..c85e0e7b5a1 --- /dev/null +++ b/lib/private/Security/VerificationToken/VerificationToken.php @@ -0,0 +1,129 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2021 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 <https://www.gnu.org/licenses/>. + * + */ + +namespace OC\Security\VerificationToken; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\IJobList; +use OCP\IConfig; +use OCP\IUser; +use OCP\Security\ICrypto; +use OCP\Security\ISecureRandom; +use OCP\Security\VerificationToken\InvalidTokenException; +use OCP\Security\VerificationToken\IVerificationToken; +use function json_encode; + +class VerificationToken implements IVerificationToken { + protected const TOKEN_LIFETIME = 60 * 60 * 24 * 7; + + /** @var IConfig */ + private $config; + /** @var ICrypto */ + private $crypto; + /** @var ITimeFactory */ + private $timeFactory; + /** @var ISecureRandom */ + private $secureRandom; + /** @var IJobList */ + private $jobList; + + public function __construct( + IConfig $config, + ICrypto $crypto, + ITimeFactory $timeFactory, + ISecureRandom $secureRandom, + IJobList $jobList + ) { + $this->config = $config; + $this->crypto = $crypto; + $this->timeFactory = $timeFactory; + $this->secureRandom = $secureRandom; + $this->jobList = $jobList; + } + + /** + * @throws InvalidTokenException + */ + protected function throwInvalidTokenException(int $code): void { + throw new InvalidTokenException($code); + } + + public function check(string $token, ?IUser $user, string $subject, string $passwordPrefix = '', bool $expiresWithLogin = false): void { + if ($user === null || !$user->isEnabled()) { + $this->throwInvalidTokenException(InvalidTokenException::USER_UNKNOWN); + } + + $encryptedToken = $this->config->getUserValue($user->getUID(), 'core', $subject, null); + if ($encryptedToken === null) { + $this->throwInvalidTokenException(InvalidTokenException::TOKEN_NOT_FOUND); + } + + try { + $decryptedToken = $this->crypto->decrypt($encryptedToken, $passwordPrefix.$this->config->getSystemValue('secret')); + } catch (\Exception $e) { + $this->throwInvalidTokenException(InvalidTokenException::TOKEN_DECRYPTION_ERROR); + } + + $splitToken = explode(':', $decryptedToken ?? ''); + if (count($splitToken) !== 2) { + $this->throwInvalidTokenException(InvalidTokenException::TOKEN_INVALID_FORMAT); + } + + if ($splitToken[0] < ($this->timeFactory->getTime() - self::TOKEN_LIFETIME) + || ($expiresWithLogin && $user->getLastLogin() > $splitToken[0])) { + $this->throwInvalidTokenException(InvalidTokenException::TOKEN_EXPIRED); + } + + if (!hash_equals($splitToken[1], $token)) { + $this->throwInvalidTokenException(InvalidTokenException::TOKEN_MISMATCH); + } + } + + public function create(IUser $user, string $subject, string $passwordPrefix = ''): string { + $token = $this->secureRandom->generate( + 21, + ISecureRandom::CHAR_DIGITS. + ISecureRandom::CHAR_LOWER. + ISecureRandom::CHAR_UPPER + ); + $tokenValue = $this->timeFactory->getTime() .':'. $token; + $encryptedValue = $this->crypto->encrypt($tokenValue, $passwordPrefix . $this->config->getSystemValue('secret')); + $this->config->setUserValue($user->getUID(), 'core', $subject, $encryptedValue); + $jobArgs = json_encode([ + 'userId' => $user->getUID(), + 'subject' => $subject, + 'pp' => $passwordPrefix, + 'notBefore' => $this->timeFactory->getTime() + self::TOKEN_LIFETIME * 2, // multiply to provide a grace period + ]); + $this->jobList->add(CleanUpJob::class, $jobArgs); + + return $token; + } + + public function delete(string $token, IUser $user, string $subject): void { + $this->config->deleteUserValue($user->getUID(), 'core', $subject); + } +} diff --git a/lib/private/Server.php b/lib/private/Server.php index a70b25d90af..a9ea07c27d3 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -135,6 +135,7 @@ use OC\Security\CSRF\TokenStorage\SessionStorage; use OC\Security\Hasher; use OC\Security\SecureRandom; use OC\Security\TrustedDomainHelper; +use OC\Security\VerificationToken\VerificationToken; use OC\Session\CryptoWrapper; use OC\Share20\ProviderFactory; use OC\Share20\ShareHelper; @@ -224,6 +225,7 @@ use OCP\Security\ICredentialsManager; use OCP\Security\ICrypto; use OCP\Security\IHasher; use OCP\Security\ISecureRandom; +use OCP\Security\VerificationToken\IVerificationToken; use OCP\Share\IShareHelper; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; @@ -805,6 +807,8 @@ class Server extends ServerContainer implements IServerContainer { /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('SecureRandom', \OCP\Security\ISecureRandom::class); + $this->registerAlias(IVerificationToken::class, VerificationToken::class); + $this->registerAlias(ICrypto::class, Crypto::class); /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('Crypto', ICrypto::class); diff --git a/lib/private/Setup.php b/lib/private/Setup.php index a4873e63aa9..c24d417f8cf 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -439,7 +439,7 @@ class Setup { // Set email for admin if (!empty($options['adminemail'])) { - $config->setUserValue($user->getUID(), 'settings', 'email', $options['adminemail']); + $user->setSystemEMailAddress($options['adminemail']); } } diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 7047c32e339..da8de81208e 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -44,6 +44,7 @@ namespace OC\Share20; use OC\Cache\CappedMemoryCache; use OC\Files\Mount\MoveableMount; use OC\Share20\Exception\ProviderException; +use OCA\Files_Sharing\AppInfo\Application; use OCA\Files_Sharing\ISharedStorage; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\File; @@ -788,7 +789,15 @@ class Manager implements IManager { } // Generate the target - $target = $this->config->getSystemValue('share_folder', '/') . '/' . $share->getNode()->getName(); + $defaultShareFolder = $this->config->getSystemValue('share_folder', '/'); + $allowCustomShareFolder = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true); + if ($allowCustomShareFolder) { + $shareFolder = $this->config->getUserValue($share->getSharedWith(), Application::APP_ID, 'share_folder', $defaultShareFolder); + } else { + $shareFolder = $defaultShareFolder; + } + + $target = $shareFolder . '/' . $share->getNode()->getName(); $target = \OC\Files\Filesystem::normalizePath($target); $share->setTarget($target); diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index 1827be61a7a..3e30861f2a4 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -700,6 +700,7 @@ class Manager extends PublicEmitter implements IUserManager { * @since 9.1.0 */ public function getByEmail($email) { + // looking for 'email' only (and not primary_mail) is intentional $userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email); $users = array_map(function ($uid) { diff --git a/lib/private/User/User.php b/lib/private/User/User.php index f17824f51b9..5fa1272f95c 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -34,10 +34,12 @@ */ namespace OC\User; +use InvalidArgumentException; use OC\Accounts\AccountManager; use OC\Avatar\AvatarManager; use OC\Hooks\Emitter; use OC_Helper; +use OCP\Accounts\IAccountManager; use OCP\EventDispatcher\IEventDispatcher; use OCP\Group\Events\BeforeUserRemovedEvent; use OCP\Group\Events\UserRemovedEvent; @@ -55,6 +57,8 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; class User implements IUser { + /** @var IAccountManager */ + protected $accountManager; /** @var string */ private $uid; @@ -165,25 +169,62 @@ class User implements IUser { } /** - * set the email address of the user - * - * @param string|null $mailAddress - * @return void - * @since 9.0.0 + * @inheritDoc */ public function setEMailAddress($mailAddress) { - $oldMailAddress = $this->getEMailAddress(); + $this->setSystemEMailAddress($mailAddress); + } + + /** + * @inheritDoc + */ + public function setSystemEMailAddress(string $mailAddress): void { + $oldMailAddress = $this->getSystemEMailAddress(); + + if ($mailAddress === '') { + $this->config->deleteUserValue($this->uid, 'settings', 'email'); + } else { + $this->config->setUserValue($this->uid, 'settings', 'email', $mailAddress); + } + + $primaryAddress = $this->getPrimaryEMailAddress(); + if ($primaryAddress === $mailAddress) { + // on match no dedicated primary settings is necessary + $this->setPrimaryEMailAddress(''); + } + if ($oldMailAddress !== $mailAddress) { - if ($mailAddress === '') { - $this->config->deleteUserValue($this->uid, 'settings', 'email'); - } else { - $this->config->setUserValue($this->uid, 'settings', 'email', $mailAddress); - } $this->triggerChange('eMailAddress', $mailAddress, $oldMailAddress); } } /** + * @inheritDoc + */ + public function setPrimaryEMailAddress(string $mailAddress): void { + if ($mailAddress === '') { + $this->config->deleteUserValue($this->uid, 'settings', 'primary_email'); + return; + } + + $this->ensureAccountManager(); + $account = $this->accountManager->getAccount($this); + $property = $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL) + ->getPropertyByValue($mailAddress); + + if ($property === null || $property->getLocallyVerified() !== IAccountManager::VERIFIED) { + throw new InvalidArgumentException('Only verified emails can be set as primary'); + } + $this->config->setUserValue($this->uid, 'settings', 'primary_email', $mailAddress); + } + + private function ensureAccountManager() { + if (!$this->accountManager instanceof IAccountManager) { + $this->accountManager = \OC::$server->get(IAccountManager::class); + } + } + + /** * returns the timestamp of the user's last login or 0 if the user did never * login * @@ -390,10 +431,24 @@ class User implements IUser { * @since 9.0.0 */ public function getEMailAddress() { + return $this->getPrimaryEMailAddress() ?? $this->getSystemEMailAddress(); + } + + /** + * @inheritDoc + */ + public function getSystemEMailAddress(): ?string { return $this->config->getUserValue($this->uid, 'settings', 'email', null); } /** + * @inheritDoc + */ + public function getPrimaryEMailAddress(): ?string { + return $this->config->getUserValue($this->uid, 'settings', 'primary_email', null); + } + + /** * get the users' quota * * @return string diff --git a/lib/public/Accounts/IAccountProperty.php b/lib/public/Accounts/IAccountProperty.php index 20505f299dd..94866fc4807 100644 --- a/lib/public/Accounts/IAccountProperty.php +++ b/lib/public/Accounts/IAccountProperty.php @@ -115,4 +115,24 @@ interface IAccountProperty extends \JsonSerializable { * @since 22.0.0 */ public function getVerificationData(): string; + + /** + * Set the instance-based verification status of a property + * + * @since 23.0.0 + * + * @param string $verified must be one of the verification constants of IAccountManager + * @return IAccountProperty + * @throws InvalidArgumentException + */ + public function setLocallyVerified(string $verified): IAccountProperty; + + /** + * Get the instance-based verification status of a property + * + * @since 23.0.0 + * + * @return string + */ + public function getLocallyVerified(): string; } diff --git a/lib/public/Accounts/IAccountPropertyCollection.php b/lib/public/Accounts/IAccountPropertyCollection.php index 779fb1299b4..0d4c416cbaa 100644 --- a/lib/public/Accounts/IAccountPropertyCollection.php +++ b/lib/public/Accounts/IAccountPropertyCollection.php @@ -89,4 +89,13 @@ interface IAccountPropertyCollection extends JsonSerializable { * @since 22.0.0 */ public function removePropertyByValue(string $value): IAccountPropertyCollection; + + /** + * retrieves a property identified by its value. null, if none was found. + * + * Returns only the first property if there are more with the same value. + * + * @since 23.0.0 + */ + public function getPropertyByValue(string $value): ?IAccountProperty; } diff --git a/lib/public/IUser.php b/lib/public/IUser.php index 7e75704ed5b..1a1d1e44d8a 100644 --- a/lib/public/IUser.php +++ b/lib/public/IUser.php @@ -27,6 +27,8 @@ */ namespace OCP; +use InvalidArgumentException; + /** * Interface IUser * @@ -157,7 +159,7 @@ interface IUser { public function setEnabled(bool $enabled = true); /** - * get the users email address + * get the user's email address * * @return string|null * @since 9.0.0 @@ -165,6 +167,35 @@ interface IUser { public function getEMailAddress(); /** + * get the user's system email address + * + * The system mail address may be read only and may be set from different + * sources like LDAP, SAML or simply the admin. + * + * Use this getter only when the system address is needed. For picking the + * proper address to e.g. send a mail to, use getEMailAddress(). + * + * @return string|null + * @since 23.0.0 + */ + public function getSystemEMailAddress(): ?string; + + /** + * get the user's preferred email address + * + * The primary mail address may be set be the user to specify a different + * email address where mails by Nextcloud are sent to. It is not necessarily + * set. + * + * Use this getter only when the primary address is needed. For picking the + * proper address to e.g. send a mail to, use getEMailAddress(). + * + * @return string|null + * @since 23.0.0 + */ + public function getPrimaryEMailAddress(): ?string; + + /** * get the avatar image if it exists * * @param int $size @@ -184,13 +215,43 @@ interface IUser { /** * set the email address of the user * + * It is an alias to setSystemEMailAddress() + * * @param string|null $mailAddress * @return void * @since 9.0.0 + * @deprecated 23.0.0 use setSystemEMailAddress() or setPrimaryEMailAddress() */ public function setEMailAddress($mailAddress); /** + * Set the system email address of the user + * + * This is supposed to be used when the email is set from different sources + * (i.e. other user backends, admin). + * + * @since 23.0.0 + */ + public function setSystemEMailAddress(string $mailAddress): void; + + /** + * Set the primary email address of the user. + * + * This method should be typically called when the user is changing their + * own primary address and is not allowed to change their system email. + * + * The mail address provided here must be already registered as an + * additional mail in the user account and also be verified locally. Also + * an empty string is allowed to delete this preference. + * + * @throws InvalidArgumentException when the provided email address does not + * satisfy constraints. + * + * @since 23.0.0 + */ + public function setPrimaryEMailAddress(string $mailAddress): void; + + /** * get the users' quota in human readable form. If a specific quota is not * set for the user, the default value is returned. If a default setting * was not set otherwise, it is return as 'none', i.e. quota is not limited. diff --git a/lib/public/IUserManager.php b/lib/public/IUserManager.php index c6cad6f0549..e5c220af40c 100644 --- a/lib/public/IUserManager.php +++ b/lib/public/IUserManager.php @@ -196,6 +196,8 @@ interface IUserManager { public function callForSeenUsers(\Closure $callback); /** + * returns all users having the provided email set as system email address + * * @param string $email * @return IUser[] * @since 9.1.0 diff --git a/lib/public/Mail/IEMailTemplate.php b/lib/public/Mail/IEMailTemplate.php index 39d6661b624..2d776549814 100644 --- a/lib/public/Mail/IEMailTemplate.php +++ b/lib/public/Mail/IEMailTemplate.php @@ -130,7 +130,7 @@ interface IEMailTemplate { * * @param string $text Text of button; Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email * @param string $url URL of button - * @param string $plainText Text of button in plain text version + * @param string|false $plainText Text of button in plain text version * if empty the $text is used, if false none will be used * * @since 12.0.0 diff --git a/lib/public/Security/VerificationToken/IVerificationToken.php b/lib/public/Security/VerificationToken/IVerificationToken.php new file mode 100644 index 00000000000..e1d9203ec3b --- /dev/null +++ b/lib/public/Security/VerificationToken/IVerificationToken.php @@ -0,0 +1,62 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2021 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 <https://www.gnu.org/licenses/>. + * + */ + +namespace OCP\Security\VerificationToken; + +use OCP\IUser; + +/** + * @since 23.0.0 + */ +interface IVerificationToken { + + /** + * Checks whether the a provided tokent matches a stored token and its + * constraints. An InvalidTokenException is thrown on issues, otherwise + * the check is successful. + * + * null can be passed as $user, but mind that this is for conveniently + * passing the return of IUserManager::getUser() to this method. When + * $user is null, InvalidTokenException is thrown for all the issued + * tokens are user related. + * + * @throws InvalidTokenException + * @since 23.0.0 + */ + public function check(string $token, ?IUser $user, string $subject, string $passwordPrefix = '', bool $expiresWithLogin = false): void; + + /** + * @since 23.0.0 + */ + public function create(IUser $user, string $subject, string $passwordPrefix = ''): string; + + /** + * Deletes the token identified by the provided parameters + * + * @since 23.0.0 + */ + public function delete(string $token, IUser $user, string $subject): void; +} diff --git a/lib/public/Security/VerificationToken/InvalidTokenException.php b/lib/public/Security/VerificationToken/InvalidTokenException.php new file mode 100644 index 00000000000..5c8144c5c74 --- /dev/null +++ b/lib/public/Security/VerificationToken/InvalidTokenException.php @@ -0,0 +1,74 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2021 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 <https://www.gnu.org/licenses/>. + * + */ + +namespace OCP\Security\VerificationToken; + +/** @since 23.0.0 */ +class InvalidTokenException extends \Exception { + + /** + * @since 23.0.0 + */ + public function __construct(int $code) { + parent::__construct('', $code); + } + + /** + * @var int + * @since 23.0.0 + */ + public const USER_UNKNOWN = 1; + + /** + * @var int + * @since 23.0.0 + */ + public const TOKEN_NOT_FOUND = 2; + + /** + * @var int + * @since 23.0.0 + */ + public const TOKEN_DECRYPTION_ERROR = 3; + + /** + * @var int + * @since 23.0.0 + */ + public const TOKEN_INVALID_FORMAT = 4; + + /** + * @var int + * @since 23.0.0 + */ + public const TOKEN_EXPIRED = 5; + + /** + * @var int + * @since 23.0.0 + */ + public const TOKEN_MISMATCH = 6; +} |