diff options
31 files changed, 973 insertions, 65 deletions
diff --git a/apps/dav/lib/Upload/AssemblyStream.php b/apps/dav/lib/Upload/AssemblyStream.php index 3ba24bf60fd..24417851f2f 100644 --- a/apps/dav/lib/Upload/AssemblyStream.php +++ b/apps/dav/lib/Upload/AssemblyStream.php @@ -57,6 +57,9 @@ class AssemblyStream implements \Icewind\Streams\File { /** @var int */ private $currentNode = 0; + /** @var int */ + private $currentNodeRead = 0; + /** * @param string $path * @param string $mode @@ -110,10 +113,16 @@ class AssemblyStream implements \Icewind\Streams\File { do { $data = fread($this->currentStream, $count); $read = strlen($data); + $this->currentNodeRead += $read; if (feof($this->currentStream)) { fclose($this->currentStream); + $currentNodeSize = $this->nodes[$this->currentNode]->getSize(); + if ($this->currentNodeRead < $currentNodeSize) { + throw new \Exception('Stream from assembly node shorter than expected, got ' . $this->currentNodeRead . ' bytes, expected ' . $currentNodeSize); + } $this->currentNode++; + $this->currentNodeRead = 0; if ($this->currentNode < count($this->nodes)) { $this->currentStream = $this->getStream($this->nodes[$this->currentNode]); } else { diff --git a/apps/files/js/filemultiselectmenu.js b/apps/files/js/filemultiselectmenu.js index d587d1fbdb2..d50fe28eace 100644 --- a/apps/files/js/filemultiselectmenu.js +++ b/apps/files/js/filemultiselectmenu.js @@ -9,21 +9,6 @@ */ (function() { - var TEMPLATE_MENU = - '<ul>' + - '{{#each items}}' + - '<li class="item-{{name}}">' + - '<a href="#" class="menuitem action {{name}} permanent" data-action="{{name}}">' + - '{{#if iconClass}}' + - '<span class="icon {{iconClass}}"></span>' + - '{{else}}' + - '<span class="no-icon"></span>' + - '{{/if}}' + - '<span class="label">{{displayName}}</span>' + - '</a></li>' + - '{{/each}}' + - '</ul>'; - var FileMultiSelectMenu = OC.Backbone.View.extend({ tagName: 'div', className: 'filesSelectMenu popovermenu bubble menu-center', @@ -34,12 +19,12 @@ events: { 'click a.action': '_onClickAction' }, - template: Handlebars.compile(TEMPLATE_MENU), + /** * Renders the menu with the currently set items */ render: function() { - this.$el.html(this.template({ + this.$el.html(OCA.Files.Templates['filemultiselectmenu']({ items: this._scopes })); }, diff --git a/apps/files/js/filesummary.js b/apps/files/js/filesummary.js index ed369ff0723..c866ccb4ff5 100644 --- a/apps/files/js/filesummary.js +++ b/apps/files/js/filesummary.js @@ -196,7 +196,7 @@ * * handlebars -n OCA.Files.FileSummary.Templates filesummary.handlebars -f filesummary_template.js */ - return OCA.Files.FileSummary.Templates['filesummary'](_.extend({ + return OCA.Files.Templates['filesummary'](_.extend({ connectorLabel: t('files', '{dirs} and {files}', {dirs: '', files: ''}) }, data)); }, diff --git a/apps/files/js/filesummary_template.js b/apps/files/js/filesummary_template.js deleted file mode 100644 index 67a4d6b4e86..00000000000 --- a/apps/files/js/filesummary_template.js +++ /dev/null @@ -1,10 +0,0 @@ -(function() { - var template = Handlebars.template, templates = OCA.Files.FileSummary.Templates = OCA.Files.FileSummary.Templates || {}; -templates['filesummary'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { - var helper; - - return "<span class=\"info\">\n <span class=\"dirinfo\"></span>\n <span class=\"connector\">" - + container.escapeExpression(((helper = (helper = helpers.connectorLabel || (depth0 != null ? depth0.connectorLabel : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"connectorLabel","hash":{},"data":data}) : helper))) - + "</span>\n <span class=\"fileinfo\"></span>\n <span class=\"hiddeninfo\"></span>\n <span class=\"filter\"></span>\n</span>\n"; -},"useData":true}); -})();
\ No newline at end of file diff --git a/apps/files/js/merged-index.json b/apps/files/js/merged-index.json index 5b2227b1d3a..e891d10bdae 100644 --- a/apps/files/js/merged-index.json +++ b/apps/files/js/merged-index.json @@ -1,12 +1,12 @@ [ "app.js", + "templates.js", "file-upload.js", "newfilemenu.js", "jquery.fileupload.js", "jquery-visibility.js", "fileinfomodel.js", "filesummary.js", - "filesummary_template.js", "filemultiselectmenu.js", "breadcrumb.js", "filelist.js", diff --git a/apps/files/js/templates.js b/apps/files/js/templates.js new file mode 100644 index 00000000000..8ada62b6d60 --- /dev/null +++ b/apps/files/js/templates.js @@ -0,0 +1,39 @@ +(function() { + var template = Handlebars.template, templates = OCA.Files.Templates = OCA.Files.Templates || {}; +templates['filemultiselectmenu'] = template({"1":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + + return " <li class=\"item-" + + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper))) + + "\">\n <a href=\"#\" class=\"menuitem action " + + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper))) + + " permanent\" data-action=\"" + + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper))) + + "\">\n" + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.iconClass : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(4, data, 0),"data":data})) != null ? stack1 : "") + + " <span class=\"label\">" + + alias4(((helper = (helper = helpers.displayName || (depth0 != null ? depth0.displayName : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"displayName","hash":{},"data":data}) : helper))) + + "</span>\n </a>\n </li>\n"; +},"2":function(container,depth0,helpers,partials,data) { + var helper; + + return " <span class=\"icon " + + container.escapeExpression(((helper = (helper = helpers.iconClass || (depth0 != null ? depth0.iconClass : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"iconClass","hash":{},"data":data}) : helper))) + + "\"></span>\n"; +},"4":function(container,depth0,helpers,partials,data) { + return " <span class=\"no-icon\"></span>\n"; +},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { + var stack1; + + return "<ul>\n" + + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.items : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "</ul>\n"; +},"useData":true}); +templates['filesummary'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { + var helper; + + return "<span class=\"info\">\n <span class=\"dirinfo\"></span>\n <span class=\"connector\">" + + container.escapeExpression(((helper = (helper = helpers.connectorLabel || (depth0 != null ? depth0.connectorLabel : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"connectorLabel","hash":{},"data":data}) : helper))) + + "</span>\n <span class=\"fileinfo\"></span>\n <span class=\"hiddeninfo\"></span>\n <span class=\"filter\"></span>\n</span>\n"; +},"useData":true}); +})();
\ No newline at end of file diff --git a/apps/files/js/templates/detailsview.handlebars.js b/apps/files/js/templates/detailsview.handlebars.js deleted file mode 100644 index c109da77a63..00000000000 --- a/apps/files/js/templates/detailsview.handlebars.js +++ /dev/null @@ -1,28 +0,0 @@ -(function() { - var template = Handlebars.template, templates = OCA.Files.Templates = OCA.Files.Templates || {}; -templates['detailsview'] = template({"1":function(container,depth0,helpers,partials,data) { - var stack1; - - return "<ul class=\"tabHeaders\">\n" - + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.tabHeaders : depth0),{"name":"each","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + "</ul>\n"; -},"2":function(container,depth0,helpers,partials,data) { - var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; - - return " <li class=\"tabHeader\" data-tabid=\"" - + alias4(((helper = (helper = helpers.tabId || (depth0 != null ? depth0.tabId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"tabId","hash":{},"data":data}) : helper))) - + "\" data-tabindex=\"" - + alias4(((helper = (helper = helpers.tabIndex || (depth0 != null ? depth0.tabIndex : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"tabIndex","hash":{},"data":data}) : helper))) - + "\">\n <a href=\"#\">" - + alias4(((helper = (helper = helpers.label || (depth0 != null ? depth0.label : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"label","hash":{},"data":data}) : helper))) - + "</a>\n </li>\n"; -},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { - var stack1, helper, alias1=depth0 != null ? depth0 : {}; - - return "<div class=\"detailFileInfoContainer\"></div>\n" - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.tabHeaders : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + "<div class=\"tabsContainer\">\n</div>\n<a class=\"close icon-close\" href=\"#\" alt=\"" - + container.escapeExpression(((helper = (helper = helpers.closeLabel || (depth0 != null ? depth0.closeLabel : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"closeLabel","hash":{},"data":data}) : helper))) - + "\"></a>\n"; -},"useData":true}); -})(); diff --git a/apps/files/js/templates/filemultiselectmenu.handlebars b/apps/files/js/templates/filemultiselectmenu.handlebars new file mode 100644 index 00000000000..9a723920db9 --- /dev/null +++ b/apps/files/js/templates/filemultiselectmenu.handlebars @@ -0,0 +1,14 @@ +<ul> + {{#each items}} + <li class="item-{{name}}"> + <a href="#" class="menuitem action {{name}} permanent" data-action="{{name}}"> + {{#if iconClass}} + <span class="icon {{iconClass}}"></span> + {{else}} + <span class="no-icon"></span> + {{/if}} + <span class="label">{{displayName}}</span> + </a> + </li> + {{/each}} +</ul> diff --git a/apps/files/js/filesummary.handlebars b/apps/files/js/templates/filesummary.handlebars index f975a2a7737..f975a2a7737 100644 --- a/apps/files/js/filesummary.handlebars +++ b/apps/files/js/templates/filesummary.handlebars diff --git a/apps/files_sharing/lib/Controller/ShareController.php b/apps/files_sharing/lib/Controller/ShareController.php index 9057f7abaed..2cc34dde8bb 100644 --- a/apps/files_sharing/lib/Controller/ShareController.php +++ b/apps/files_sharing/lib/Controller/ShareController.php @@ -397,7 +397,7 @@ class ShareController extends AuthPublicShareController { // JS required for folders \OCP\Util::addStyle('files', 'merged'); \OCP\Util::addScript('files', 'filesummary'); - \OCP\Util::addScript('files', 'filesummary_template'); + \OCP\Util::addScript('files', 'templates'); \OCP\Util::addScript('files', 'breadcrumb'); \OCP\Util::addScript('files', 'fileinfomodel'); \OCP\Util::addScript('files', 'newfilemenu'); diff --git a/apps/twofactor_backupcodes/composer/composer/autoload_classmap.php b/apps/twofactor_backupcodes/composer/composer/autoload_classmap.php index d21fc1ab775..da57cbb94c2 100644 --- a/apps/twofactor_backupcodes/composer/composer/autoload_classmap.php +++ b/apps/twofactor_backupcodes/composer/composer/autoload_classmap.php @@ -8,18 +8,22 @@ $baseDir = $vendorDir; return array( 'OCA\\TwoFactorBackupCodes\\Activity\\Provider' => $baseDir . '/../lib/Activity/Provider.php', 'OCA\\TwoFactorBackupCodes\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php', + 'OCA\\TwoFactorBackupCodes\\BackgroundJob\\RememberBackupCodesJob' => $baseDir . '/../lib/BackgroundJob/RememberBackupCodesJob.php', 'OCA\\TwoFactorBackupCodes\\Controller\\SettingsController' => $baseDir . '/../lib/Controller/SettingsController.php', 'OCA\\TwoFactorBackupCodes\\Db\\BackupCode' => $baseDir . '/../lib/Db/BackupCode.php', 'OCA\\TwoFactorBackupCodes\\Db\\BackupCodeMapper' => $baseDir . '/../lib/Db/BackupCodeMapper.php', 'OCA\\TwoFactorBackupCodes\\Event\\CodesGenerated' => $baseDir . '/../lib/Event/CodesGenerated.php', 'OCA\\TwoFactorBackupCodes\\Listener\\ActivityPublisher' => $baseDir . '/../lib/Listener/ActivityPublisher.php', + 'OCA\\TwoFactorBackupCodes\\Listener\\ClearNotifications' => $baseDir . '/../lib/Listener/ClearNotifications.php', 'OCA\\TwoFactorBackupCodes\\Listener\\IListener' => $baseDir . '/../lib/Listener/IListener.php', + 'OCA\\TwoFactorBackupCodes\\Listener\\ProviderEnabled' => $baseDir . '/../lib/Listener/ProviderEnabled.php', 'OCA\\TwoFactorBackupCodes\\Listener\\RegistryUpdater' => $baseDir . '/../lib/Listener/RegistryUpdater.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170607104347' => $baseDir . '/../lib/Migration/Version1002Date20170607104347.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170607113030' => $baseDir . '/../lib/Migration/Version1002Date20170607113030.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170919123342' => $baseDir . '/../lib/Migration/Version1002Date20170919123342.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170926101419' => $baseDir . '/../lib/Migration/Version1002Date20170926101419.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20180821043638' => $baseDir . '/../lib/Migration/Version1002Date20180821043638.php', + 'OCA\\TwoFactorBackupCodes\\Notifications\\Notifier' => $baseDir . '/../lib/Notifications/Notifier.php', 'OCA\\TwoFactorBackupCodes\\Provider\\BackupCodesProvider' => $baseDir . '/../lib/Provider/BackupCodesProvider.php', 'OCA\\TwoFactorBackupCodes\\Service\\BackupCodeStorage' => $baseDir . '/../lib/Service/BackupCodeStorage.php', 'OCA\\TwoFactorBackupCodes\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php', diff --git a/apps/twofactor_backupcodes/composer/composer/autoload_static.php b/apps/twofactor_backupcodes/composer/composer/autoload_static.php index e1b9fb90077..164ba30e830 100644 --- a/apps/twofactor_backupcodes/composer/composer/autoload_static.php +++ b/apps/twofactor_backupcodes/composer/composer/autoload_static.php @@ -23,18 +23,22 @@ class ComposerStaticInitTwoFactorBackupCodes public static $classMap = array ( 'OCA\\TwoFactorBackupCodes\\Activity\\Provider' => __DIR__ . '/..' . '/../lib/Activity/Provider.php', 'OCA\\TwoFactorBackupCodes\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php', + 'OCA\\TwoFactorBackupCodes\\BackgroundJob\\RememberBackupCodesJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/RememberBackupCodesJob.php', 'OCA\\TwoFactorBackupCodes\\Controller\\SettingsController' => __DIR__ . '/..' . '/../lib/Controller/SettingsController.php', 'OCA\\TwoFactorBackupCodes\\Db\\BackupCode' => __DIR__ . '/..' . '/../lib/Db/BackupCode.php', 'OCA\\TwoFactorBackupCodes\\Db\\BackupCodeMapper' => __DIR__ . '/..' . '/../lib/Db/BackupCodeMapper.php', 'OCA\\TwoFactorBackupCodes\\Event\\CodesGenerated' => __DIR__ . '/..' . '/../lib/Event/CodesGenerated.php', 'OCA\\TwoFactorBackupCodes\\Listener\\ActivityPublisher' => __DIR__ . '/..' . '/../lib/Listener/ActivityPublisher.php', + 'OCA\\TwoFactorBackupCodes\\Listener\\ClearNotifications' => __DIR__ . '/..' . '/../lib/Listener/ClearNotifications.php', 'OCA\\TwoFactorBackupCodes\\Listener\\IListener' => __DIR__ . '/..' . '/../lib/Listener/IListener.php', + 'OCA\\TwoFactorBackupCodes\\Listener\\ProviderEnabled' => __DIR__ . '/..' . '/../lib/Listener/ProviderEnabled.php', 'OCA\\TwoFactorBackupCodes\\Listener\\RegistryUpdater' => __DIR__ . '/..' . '/../lib/Listener/RegistryUpdater.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170607104347' => __DIR__ . '/..' . '/../lib/Migration/Version1002Date20170607104347.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170607113030' => __DIR__ . '/..' . '/../lib/Migration/Version1002Date20170607113030.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170919123342' => __DIR__ . '/..' . '/../lib/Migration/Version1002Date20170919123342.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20170926101419' => __DIR__ . '/..' . '/../lib/Migration/Version1002Date20170926101419.php', 'OCA\\TwoFactorBackupCodes\\Migration\\Version1002Date20180821043638' => __DIR__ . '/..' . '/../lib/Migration/Version1002Date20180821043638.php', + 'OCA\\TwoFactorBackupCodes\\Notifications\\Notifier' => __DIR__ . '/..' . '/../lib/Notifications/Notifier.php', 'OCA\\TwoFactorBackupCodes\\Provider\\BackupCodesProvider' => __DIR__ . '/..' . '/../lib/Provider/BackupCodesProvider.php', 'OCA\\TwoFactorBackupCodes\\Service\\BackupCodeStorage' => __DIR__ . '/..' . '/../lib/Service/BackupCodeStorage.php', 'OCA\\TwoFactorBackupCodes\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php', diff --git a/apps/twofactor_backupcodes/lib/AppInfo/Application.php b/apps/twofactor_backupcodes/lib/AppInfo/Application.php index d2541d87627..f5d0139dbd9 100644 --- a/apps/twofactor_backupcodes/lib/AppInfo/Application.php +++ b/apps/twofactor_backupcodes/lib/AppInfo/Application.php @@ -28,9 +28,16 @@ namespace OCA\TwoFactorBackupCodes\AppInfo; use OCA\TwoFactorBackupCodes\Db\BackupCodeMapper; use OCA\TwoFactorBackupCodes\Event\CodesGenerated; use OCA\TwoFactorBackupCodes\Listener\ActivityPublisher; +use OCA\TwoFactorBackupCodes\Listener\ClearNotifications; use OCA\TwoFactorBackupCodes\Listener\IListener; +use OCA\TwoFactorBackupCodes\Listener\ProviderEnabled; use OCA\TwoFactorBackupCodes\Listener\RegistryUpdater; +use OCA\TwoFactorBackupCodes\Notifications\Notifier; use OCP\AppFramework\App; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\Authentication\TwoFactorAuth\RegistryEvent; +use OCP\IL10N; +use OCP\Notification\IManager; use OCP\Util; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -44,6 +51,7 @@ class Application extends App { */ public function register() { $this->registerHooksAndEvents(); + $this->registerNotification(); } /** @@ -60,12 +68,34 @@ class Application extends App { $listeners = [ $container->query(ActivityPublisher::class), $container->query(RegistryUpdater::class), + $container->query(ClearNotifications::class), ]; foreach ($listeners as $listener) { $listener->handle($event); } }); + + $eventDispatcher->addListener(IRegistry::EVENT_PROVIDER_ENABLED, function(RegistryEvent $event) use ($container) { + /** @var IListener $listener */ + $listener = $container->query(ProviderEnabled::class); + $listener->handle($event); + }); + } + + public function registerNotification() { + $container = $this->getContainer(); + /** @var IManager $manager */ + $manager = $container->query(IManager::class); + $manager->registerNotifier( + function() use ($container) { + return $container->query(Notifier::class); + }, + function () use ($container) { + $l = $container->query(IL10N::class); + return ['id' => 'twofactor_backupcodes', 'name' => $l->t('Second-factor backup codes')]; + } + ); } public function deleteUser($params) { diff --git a/apps/twofactor_backupcodes/lib/BackgroundJob/RememberBackupCodesJob.php b/apps/twofactor_backupcodes/lib/BackgroundJob/RememberBackupCodesJob.php new file mode 100644 index 00000000000..1f227061feb --- /dev/null +++ b/apps/twofactor_backupcodes/lib/BackgroundJob/RememberBackupCodesJob.php @@ -0,0 +1,92 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\TwoFactorBackupCodes\BackgroundJob; + +use OC\BackgroundJob\TimedJob; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\BackgroundJob\IJobList; +use OCP\IUserManager; +use OCP\Notification\IManager; + +class RememberBackupCodesJob extends TimedJob { + + /** @var IRegistry */ + private $registry; + + /** @var IUserManager */ + private $userManager; + + /** @var ITimeFactory */ + private $time; + + /** @var IManager */ + private $notificationManager; + + /** @var IJobList */ + private $jobList; + + public function __construct(IRegistry $registry, + IUserManager $userManager, + ITimeFactory $timeFactory, + IManager $notificationManager, + IJobList $jobList) { + $this->registry = $registry; + $this->userManager = $userManager; + $this->time = $timeFactory; + $this->notificationManager = $notificationManager; + $this->jobList = $jobList; + + $this->setInterval(60*60*24*14); + } + + protected function run($argument) { + $uid = $argument['uid']; + $user = $this->userManager->get($uid); + + if ($user === null) { + // We can't run with an invalid user + return; + } + + $providers = $this->registry->getProviderStates($user); + if (isset($providers['backup_codes']) && $providers['backup_codes'] === true) { + // Backup codes already generated lets remove this job + $this->jobList->remove(self::class, $argument); + return; + } + + $date = new \DateTime(); + $date->setTimestamp($this->time->getTime()); + + $notification = $this->notificationManager->createNotification(); + $notification->setApp('twofactor_backupcodes') + ->setUser($user->getUID()) + ->setDateTime($date) + ->setObject('create', 'codes') + ->setSubject('create_backupcodes'); + $this->notificationManager->notify($notification); + } +} diff --git a/apps/twofactor_backupcodes/lib/Listener/ClearNotifications.php b/apps/twofactor_backupcodes/lib/Listener/ClearNotifications.php new file mode 100644 index 00000000000..ad7fd188ebc --- /dev/null +++ b/apps/twofactor_backupcodes/lib/Listener/ClearNotifications.php @@ -0,0 +1,51 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\TwoFactorBackupCodes\Listener; + +use OCA\TwoFactorBackupCodes\Event\CodesGenerated; +use OCP\Notification\IManager; +use Symfony\Component\EventDispatcher\Event; + +class ClearNotifications implements IListener { + + /** @var IManager */ + private $manager; + + public function __construct(IManager $manager) { + $this->manager = $manager; + } + + public function handle(Event $event) { + if (!($event instanceof CodesGenerated)) { + return; + } + + $notification = $this->manager->createNotification(); + $notification->setApp('twofactor_backupcodes') + ->setUser($event->getUser()->getUID()) + ->setObject('create', 'codes'); + $this->manager->markProcessed($notification); + } +} diff --git a/apps/twofactor_backupcodes/lib/Listener/ProviderEnabled.php b/apps/twofactor_backupcodes/lib/Listener/ProviderEnabled.php new file mode 100644 index 00000000000..48cbef66f1b --- /dev/null +++ b/apps/twofactor_backupcodes/lib/Listener/ProviderEnabled.php @@ -0,0 +1,61 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\TwoFactorBackupCodes\Listener; + +use OCA\TwoFactorBackupCodes\BackgroundJob\RememberBackupCodesJob; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\Authentication\TwoFactorAuth\RegistryEvent; +use OCP\BackgroundJob\IJobList; +use Symfony\Component\EventDispatcher\Event; + +class ProviderEnabled implements IListener { + + /** @var IRegistry */ + private $registry; + + /** @var IJobList */ + private $jobList; + + public function __construct(IRegistry $registry, + IJobList $jobList) { + $this->registry = $registry; + $this->jobList = $jobList; + } + + public function handle(Event $event) { + if (!($event instanceof RegistryEvent)) { + return; + } + + $providers = $this->registry->getProviderStates($event->getUser()); + if (isset($providers['backup_codes']) && $providers['backup_codes'] === true) { + // Backup codes already generated nothing to do here + return; + } + + $this->jobList->add(RememberBackupCodesJob::class, ['uid' => $event->getUser()->getUID()]); + } + +} diff --git a/apps/twofactor_backupcodes/lib/Notifications/Notifier.php b/apps/twofactor_backupcodes/lib/Notifications/Notifier.php new file mode 100644 index 00000000000..3d5fedd93ea --- /dev/null +++ b/apps/twofactor_backupcodes/lib/Notifications/Notifier.php @@ -0,0 +1,64 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\TwoFactorBackupCodes\Notifications; + +use OCP\L10N\IFactory; +use OCP\Notification\INotification; +use OCP\Notification\INotifier; + +class Notifier implements INotifier { + + /** @var IFactory */ + private $factory; + + public function __construct(IFactory $factory) { + $this->factory = $factory; + } + + public function prepare(INotification $notification, $languageCode) { + if ($notification->getApp() !== 'twofactor_backupcodes') { + // Not my app => throw + throw new \InvalidArgumentException(); + } + + // Read the language from the notification + $l = $this->factory->get('twofactor_backupcodes', $languageCode); + + switch ($notification->getSubject()) { + case 'create_backupcodes': + $notification->setParsedSubject( + $l->t('Generate backup codes') + )->setParsedMessage( + $l->t('You have enabled two-factor authentication but have not yet generated backup codes. Be sure to do this in case you lose access to your second factor.') + ); + return $notification; + + default: + // Unknown subject => Unknown notification => throw + throw new \InvalidArgumentException(); + } + } + +} diff --git a/apps/twofactor_backupcodes/tests/Service/BackupCodeStorageTest.php b/apps/twofactor_backupcodes/tests/Service/BackupCodeStorageTest.php index 7d47b4ea721..679f111a75b 100644 --- a/apps/twofactor_backupcodes/tests/Service/BackupCodeStorageTest.php +++ b/apps/twofactor_backupcodes/tests/Service/BackupCodeStorageTest.php @@ -23,6 +23,8 @@ namespace OCA\TwoFactorBackupCodes\Tests\Service; use OCA\TwoFactorBackupCodes\Service\BackupCodeStorage; +use OCP\Notification\IManager; +use OCP\Notification\INotification; use Test\TestCase; /** @@ -36,10 +38,18 @@ class BackupCodeStorageTest extends TestCase { /** @var string */ private $testUID = 'test123456789'; + /** @var IManager|\PHPUnit_Framework_MockObject_MockObject */ + private $notificationManager; + protected function setUp() { parent::setUp(); $this->storage = \OC::$server->query(BackupCodeStorage::class); + + $this->notificationManager = $this->createMock(IManager::class); + $this->notificationManager->method('createNotification') + ->willReturn(\OC::$server->query(IManager::class)->createNotification()); + $this->overwriteService(IManager::class, $this->notificationManager); } public function testSimpleWorkFlow() { @@ -48,6 +58,15 @@ class BackupCodeStorageTest extends TestCase { ->method('getUID') ->will($this->returnValue($this->testUID)); + $this->notificationManager->expects($this->once()) + ->method('markProcessed') + ->with($this->callback(function (INotification $notification) { + return $notification->getUser() === $this->testUID && + $notification->getObjectType() === 'create' && + $notification->getObjectId() === 'codes' && + $notification->getApp() === 'twofactor_backupcodes'; + })); + // Create codes $codes = $this->storage->createCodes($user, 5); $this->assertCount(5, $codes); diff --git a/apps/twofactor_backupcodes/tests/Unit/BackgroundJob/RememberBackupCodesJobTest.php b/apps/twofactor_backupcodes/tests/Unit/BackgroundJob/RememberBackupCodesJobTest.php new file mode 100644 index 00000000000..0e23e032bd8 --- /dev/null +++ b/apps/twofactor_backupcodes/tests/Unit/BackgroundJob/RememberBackupCodesJobTest.php @@ -0,0 +1,153 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\TwoFactorBackupCodes\Tests\Unit\BackgroundJob; + +use OCA\TwoFactorBackupCodes\BackgroundJob\RememberBackupCodesJob; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\BackgroundJob\IJobList; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Notification\IManager; +use OCP\Notification\INotification; +use Test\TestCase; + +class RememberBackupCodesJobTest extends TestCase { + + /** @var IRegistry|\PHPUnit\Framework\MockObject\MockObject */ + private $registry; + + /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ + private $userManager; + + /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ + private $time; + + /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */ + private $notificationManager; + + /** @var IJobList|\PHPUnit\Framework\MockObject\MockObject */ + private $jobList; + + /** @var RememberBackupCodesJob */ + private $job; + + public function setUp() { + parent::setUp(); + + $this->registry = $this->createMock(IRegistry::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->time = $this->createMock(ITimeFactory::class); + $this->time->method('getTime') + ->willReturn(10000000); + $this->notificationManager = $this->createMock(IManager::class); + $this->jobList = $this->createMock(IJobList::class); + + $this->job = new RememberBackupCodesJob( + $this->registry, + $this->userManager, + $this->time, + $this->notificationManager, + $this->jobList + ); + } + + public function testInvalidUID() { + $this->userManager->method('get') + ->with('invalidUID') + ->willReturn(null); + + $this->notificationManager->expects($this->never()) + ->method($this->anything()); + $this->jobList->expects($this->never()) + ->method($this->anything()); + + $this->invokePrivate($this->job, 'run', [['uid' => 'invalidUID']]); + } + + public function testBackupCodesGenerated() { + $user = $this->createMock(IUser::class); + $user->method('getUID') + ->willReturn('validUID'); + $this->userManager->method('get') + ->with('validUID') + ->willReturn($user); + + $this->registry->method('getProviderStates') + ->with($user) + ->willReturn([ + 'backup_codes' => true + ]); + + $this->jobList->expects($this->once()) + ->method('remove') + ->with( + RememberBackupCodesJob::class, + ['uid' => 'validUID'] + ); + + $this->notificationManager->expects($this->never()) + ->method($this->anything()); + + $this->invokePrivate($this->job, 'run', [['uid' => 'validUID']]); + } + + public function testNotificationSend() { + $user = $this->createMock(IUser::class); + $user->method('getUID') + ->willReturn('validUID'); + $this->userManager->method('get') + ->with('validUID') + ->willReturn($user); + + $this->registry->method('getProviderStates') + ->with($user) + ->willReturn([ + 'backup_codes' => false + ]); + + $this->jobList->expects($this->never()) + ->method($this->anything()); + + $date = new \DateTime(); + $date->setTimestamp($this->time->getTime()); + + $this->notificationManager->method('createNotification') + ->willReturn(\OC::$server->query(IManager::class)->createNotification()); + + $this->notificationManager->expects($this->once()) + ->method('notify') + ->with($this->callback(function (INotification $n) { + return $n->getApp() === 'twofactor_backupcodes' && + $n->getUser() === 'validUID' && + $n->getDateTime()->getTimestamp() === 10000000 && + $n->getObjectType() === 'create' && + $n->getObjectId() === 'codes' && + $n->getSubject() === 'create_backupcodes'; + })); + + $this->invokePrivate($this->job, 'run', [['uid' => 'validUID']]); + } +} diff --git a/apps/twofactor_backupcodes/tests/Unit/Listener/ClearNotificationsTest.php b/apps/twofactor_backupcodes/tests/Unit/Listener/ClearNotificationsTest.php new file mode 100644 index 00000000000..123c008cbbb --- /dev/null +++ b/apps/twofactor_backupcodes/tests/Unit/Listener/ClearNotificationsTest.php @@ -0,0 +1,77 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\TwoFactorBackupCodes\Tests\Unit\Listener; + +use OCA\TwoFactorBackupCodes\Event\CodesGenerated; +use OCA\TwoFactorBackupCodes\Listener\ClearNotifications; +use OCP\IUser; +use OCP\Notification\IManager; +use OCP\Notification\INotification; +use Symfony\Component\EventDispatcher\Event; +use Test\TestCase; + +class ClearNotificationsTest extends TestCase { + + /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */ + private $notificationManager; + + /** @var ClearNotifications */ + private $listener; + + protected function setUp() { + parent::setUp(); + + $this->notificationManager = $this->createMock(IManager::class); + $this->notificationManager->method('createNotification') + ->willReturn(\OC::$server->query(IManager::class)->createNotification()); + + $this->listener = new ClearNotifications($this->notificationManager); + } + + public function testHandleGenericEvent() { + $event = $this->createMock(Event::class); + $this->notificationManager->expects($this->never()) + ->method($this->anything()); + + $this->listener->handle($event); + } + + public function testHandleCodesGeneratedEvent() { + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('fritz'); + $event = new CodesGenerated($user); + + $this->notificationManager->expects($this->once()) + ->method('markProcessed') + ->with($this->callback(function(INotification $n) { + return $n->getUser() === 'fritz' && + $n->getApp() === 'twofactor_backupcodes' && + $n->getObjectType() === 'create' && + $n->getObjectId() === 'codes'; + })); + + $this->listener->handle($event); + } +} diff --git a/apps/twofactor_backupcodes/tests/Unit/Listener/ProviderEnabledTest.php b/apps/twofactor_backupcodes/tests/Unit/Listener/ProviderEnabledTest.php new file mode 100644 index 00000000000..c824ad8e87a --- /dev/null +++ b/apps/twofactor_backupcodes/tests/Unit/Listener/ProviderEnabledTest.php @@ -0,0 +1,107 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\TwoFactorBackupCodes\Tests\Unit\Listener; + +use OCA\TwoFactorBackupCodes\BackgroundJob\RememberBackupCodesJob; +use OCA\TwoFactorBackupCodes\Listener\ProviderEnabled; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\Authentication\TwoFactorAuth\RegistryEvent; +use OCP\BackgroundJob\IJobList; +use OCP\IUser; +use Symfony\Component\EventDispatcher\Event; +use Test\TestCase; + +class ProviderEnabledTest extends TestCase { + + /** @var IRegistry|\PHPUnit\Framework\MockObject\MockObject */ + private $registy; + + /** @var IJobList|\PHPUnit\Framework\MockObject\MockObject */ + private $jobList; + + /** @var ProviderEnabled */ + private $listener; + + protected function setUp() { + parent::setUp(); + + $this->registy = $this->createMock(IRegistry::class); + $this->jobList = $this->createMock(IJobList::class); + + $this->listener = new ProviderEnabled($this->registy, $this->jobList); + } + + public function testHandleGenericEvent() { + $event = $this->createMock(Event::class); + $this->jobList->expects($this->never()) + ->method($this->anything()); + + $this->listener->handle($event); + } + + public function testHandleCodesGeneratedEventAlraedyBackupcodes() { + $user = $this->createMock(IUser::class); + $user->method('getUID') + ->willReturn('myUID'); + $event = $this->createMock(RegistryEvent::class); + $event->method('getUser') + ->willReturn($user); + + $this->registy->method('getProviderStates') + ->with($user) + ->willReturn([ + 'backup_codes' => true, + ]); + + $this->jobList->expects($this->never()) + ->method($this->anything()); + + $this->listener->handle($event); + } + + public function testHandleCodesGeneratedEventNoBackupcodes() { + $user = $this->createMock(IUser::class); + $user->method('getUID') + ->willReturn('myUID'); + $event = $this->createMock(RegistryEvent::class); + $event->method('getUser') + ->willReturn($user); + + $this->registy->method('getProviderStates') + ->with($user) + ->willReturn([ + 'backup_codes' => false, + ]); + + $this->jobList->expects($this->once()) + ->method('add') + ->with( + $this->equalTo(RememberBackupCodesJob::class), + $this->equalTo(['uid' => 'myUID']) + ); + + $this->listener->handle($event); + } +} diff --git a/apps/twofactor_backupcodes/tests/Unit/Notification/NotifierTest.php b/apps/twofactor_backupcodes/tests/Unit/Notification/NotifierTest.php new file mode 100644 index 00000000000..508fa453e16 --- /dev/null +++ b/apps/twofactor_backupcodes/tests/Unit/Notification/NotifierTest.php @@ -0,0 +1,120 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl> + * + * @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\TwoFactorBackupCodes\Tests\Unit\Notification; + +use OCA\TwoFactorBackupCodes\Notifications\Notifier; +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\Notification\INotification; +use Test\TestCase; + +class NotifierTest extends TestCase { + /** @var Notifier */ + protected $notifier; + + /** @var IFactory|\PHPUnit\Framework\MockObject\MockObject */ + protected $factory; + /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ + protected $l; + + protected function setUp() { + parent::setUp(); + + $this->l = $this->createMock(IL10N::class); + $this->l->expects($this->any()) + ->method('t') + ->willReturnCallback(function($string, $args) { + return vsprintf($string, $args); + }); + $this->factory = $this->createMock(IFactory::class); + $this->factory->expects($this->any()) + ->method('get') + ->willReturn($this->l); + + $this->notifier = new Notifier( + $this->factory + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testPrepareWrongApp() { + /** @var INotification|\PHPUnit_Framework_MockObject_MockObject $notification */ + $notification = $this->createMock(INotification::class); + $notification->expects($this->once()) + ->method('getApp') + ->willReturn('notifications'); + $notification->expects($this->never()) + ->method('getSubject'); + + $this->notifier->prepare($notification, 'en'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testPrepareWrongSubject() { + /** @var INotification|\PHPUnit_Framework_MockObject_MockObject $notification */ + $notification = $this->createMock(INotification::class); + $notification->expects($this->once()) + ->method('getApp') + ->willReturn('twofactor_backupcodes'); + $notification->expects($this->once()) + ->method('getSubject') + ->willReturn('wrong subject'); + + $this->notifier->prepare($notification, 'en'); + } + + public function testPrepare() { + /** @var \OCP\Notification\INotification|\PHPUnit_Framework_MockObject_MockObject $notification */ + $notification = $this->createMock(INotification::class); + + $notification->expects($this->once()) + ->method('getApp') + ->willReturn('twofactor_backupcodes'); + $notification->expects($this->once()) + ->method('getSubject') + ->willReturn('create_backupcodes'); + + $this->factory->expects($this->once()) + ->method('get') + ->with('twofactor_backupcodes', 'nl') + ->willReturn($this->l); + + $notification->expects($this->once()) + ->method('setParsedSubject') + ->with('Generate backup codes') + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setParsedMessage') + ->with('You have enabled two-factor authentication but have not yet generated backup codes. Be sure to do this in case you lose access to your second factor.') + ->willReturnSelf(); + + $return = $this->notifier->prepare($notification, 'nl'); + $this->assertEquals($notification, $return); + } +} diff --git a/build/compile-handlebars-templates.sh b/build/compile-handlebars-templates.sh index 80e2e661f4d..585406c4e8f 100755 --- a/build/compile-handlebars-templates.sh +++ b/build/compile-handlebars-templates.sh @@ -11,7 +11,7 @@ handlebars -n OC.Settings.Templates settings/js/authtoken.handlebars -f setting handlebars -n OC.ContactsMenu.Templates core/js/contactsmenu -f core/js/contactsmenu_templates.js # Files app -handlebars -n OCA.Files.FileSummary.Templates apps/files/js/filesummary.handlebars -f apps/files/js/filesummary_template.js +handlebars -n OCA.Files.Templates apps/files/js/templates -f apps/files/js/templates.js if [[ $(git diff --name-only) ]]; then echo "Please submit your compiled handlebars templates" diff --git a/core/css/header.scss b/core/css/header.scss index 01e9e0ff481..4c0f05f9cc6 100644 --- a/core/css/header.scss +++ b/core/css/header.scss @@ -357,10 +357,17 @@ nav[role='navigation'] { &:active { color: var(--color-primary-text); - img, #expandDisplayName { - border: 2px solid $color-primary-text; - margin-top: -2px; - margin-left: -2px; + #expandDisplayName, + .avatardiv{ + border-radius: 50%; + border: 2px solid var(--color-primary-text); + margin: -2px; + } + .avatardiv{ + background-color: var(--color-primary-text); + } + #expandDisplayName { + opacity: 1; } } @@ -383,6 +390,7 @@ nav[role='navigation'] { #expandDisplayName { padding: 8px; opacity: .6; + cursor: pointer; /* full opacity for gear icon if active */ #body-settings & { diff --git a/core/css/inputs.scss b/core/css/inputs.scss index 99083ff1a57..4195cb1dea0 100644 --- a/core/css/inputs.scss +++ b/core/css/inputs.scss @@ -723,6 +723,7 @@ input { z-index: 1; /* above input */ background-color: var(--color-main-background); cursor: pointer; + line-height: 17px; } /* displayed text if tag limit reached */ .multiselect__strong, diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index c35dfe4b3ab..d77cc6797fc 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -74,6 +74,7 @@ return array( 'OCP\\Authentication\\TwoFactorAuth\\IProvider' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvider.php', 'OCP\\Authentication\\TwoFactorAuth\\IProvidesCustomCSP' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvidesCustomCSP.php', 'OCP\\Authentication\\TwoFactorAuth\\IRegistry' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IRegistry.php', + 'OCP\\Authentication\\TwoFactorAuth\\RegistryEvent' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/RegistryEvent.php', 'OCP\\Authentication\\TwoFactorAuth\\TwoFactorException' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/TwoFactorException.php', 'OCP\\AutoloadNotAllowedException' => $baseDir . '/lib/public/AutoloadNotAllowedException.php', 'OCP\\BackgroundJob' => $baseDir . '/lib/public/BackgroundJob.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index acffc1c842b..06899d408ec 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -104,6 +104,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\Authentication\\TwoFactorAuth\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvider.php', 'OCP\\Authentication\\TwoFactorAuth\\IProvidesCustomCSP' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvidesCustomCSP.php', 'OCP\\Authentication\\TwoFactorAuth\\IRegistry' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IRegistry.php', + 'OCP\\Authentication\\TwoFactorAuth\\RegistryEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/RegistryEvent.php', 'OCP\\Authentication\\TwoFactorAuth\\TwoFactorException' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/TwoFactorException.php', 'OCP\\AutoloadNotAllowedException' => __DIR__ . '/../../..' . '/lib/public/AutoloadNotAllowedException.php', 'OCP\\BackgroundJob' => __DIR__ . '/../../..' . '/lib/public/BackgroundJob.php', diff --git a/lib/private/Authentication/TwoFactorAuth/Registry.php b/lib/private/Authentication/TwoFactorAuth/Registry.php index 2fc90e5d6d9..2f905441953 100644 --- a/lib/private/Authentication/TwoFactorAuth/Registry.php +++ b/lib/private/Authentication/TwoFactorAuth/Registry.php @@ -29,15 +29,23 @@ namespace OC\Authentication\TwoFactorAuth; use OC\Authentication\TwoFactorAuth\Db\ProviderUserAssignmentDao; use OCP\Authentication\TwoFactorAuth\IProvider; use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\Authentication\TwoFactorAuth\RegistryEvent; use OCP\IUser; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; class Registry implements IRegistry { /** @var ProviderUserAssignmentDao */ private $assignmentDao; - public function __construct(ProviderUserAssignmentDao $assignmentDao) { + /** @var EventDispatcherInterface */ + private $dispatcher; + + public function __construct(ProviderUserAssignmentDao $assignmentDao, + EventDispatcherInterface $dispatcher) { $this->assignmentDao = $assignmentDao; + $this->dispatcher = $dispatcher; } public function getProviderStates(IUser $user): array { @@ -46,10 +54,16 @@ class Registry implements IRegistry { public function enableProviderFor(IProvider $provider, IUser $user) { $this->assignmentDao->persist($provider->getId(), $user->getUID(), 1); + + $event = new RegistryEvent($provider, $user); + $this->dispatcher->dispatch(self::EVENT_PROVIDER_ENABLED, $event); } public function disableProviderFor(IProvider $provider, IUser $user) { $this->assignmentDao->persist($provider->getId(), $user->getUID(), 0); + + $event = new RegistryEvent($provider, $user); + $this->dispatcher->dispatch(self::EVENT_PROVIDER_DISABLED, $event); } public function cleanUp(string $providerId) { diff --git a/lib/public/Authentication/TwoFactorAuth/IRegistry.php b/lib/public/Authentication/TwoFactorAuth/IRegistry.php index 5d97c57bcf2..c033ad91245 100644 --- a/lib/public/Authentication/TwoFactorAuth/IRegistry.php +++ b/lib/public/Authentication/TwoFactorAuth/IRegistry.php @@ -39,6 +39,10 @@ use OCP\IUser; */ interface IRegistry { + + const EVENT_PROVIDER_ENABLED = self::class . '::enable'; + const EVENT_PROVIDER_DISABLED = self::class . '::disable'; + /** * Get a key-value map of providers and their enabled/disabled state for * the given user. diff --git a/lib/public/Authentication/TwoFactorAuth/RegistryEvent.php b/lib/public/Authentication/TwoFactorAuth/RegistryEvent.php new file mode 100644 index 00000000000..9a005c9cd5d --- /dev/null +++ b/lib/public/Authentication/TwoFactorAuth/RegistryEvent.php @@ -0,0 +1,62 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 OCP\Authentication\TwoFactorAuth; + +use OCP\IUser; +use Symfony\Component\EventDispatcher\Event; + +/** + * @since 15.0.0 + */ +class RegistryEvent extends Event { + + /** @var IProvider */ + private $provider; + + /** @IUser */ + private $user; + + /** + * @since 15.0.0 + */ + public function __construct(IProvider $provider, IUser $user) { + $this->provider = $provider; + $this->user = $user; + } + + /** + * @since 15.0.0 + */ + public function getProvider(): IProvider { + return $this->provider; + } + + /** + * @since 15.0.0 + */ + public function getUser(): IUser { + return $this->user; + } +} diff --git a/tests/lib/Authentication/TwoFactorAuth/RegistryTest.php b/tests/lib/Authentication/TwoFactorAuth/RegistryTest.php index 3d2941e009a..08498738fa1 100644 --- a/tests/lib/Authentication/TwoFactorAuth/RegistryTest.php +++ b/tests/lib/Authentication/TwoFactorAuth/RegistryTest.php @@ -27,8 +27,11 @@ namespace Test\Authentication\TwoFactorAuth; use OC\Authentication\TwoFactorAuth\Db\ProviderUserAssignmentDao; use OC\Authentication\TwoFactorAuth\Registry; use OCP\Authentication\TwoFactorAuth\IProvider; +use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\Authentication\TwoFactorAuth\RegistryEvent; use OCP\IUser; use PHPUnit_Framework_MockObject_MockObject; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Test\TestCase; class RegistryTest extends TestCase { @@ -39,12 +42,16 @@ class RegistryTest extends TestCase { /** @var Registry */ private $registry; + /** @var EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $dispatcher; + protected function setUp() { parent::setUp(); $this->dao = $this->createMock(ProviderUserAssignmentDao::class); + $this->dispatcher = $this->createMock(EventDispatcherInterface::class); - $this->registry = new Registry($this->dao); + $this->registry = new Registry($this->dao, $this->dispatcher); } public function testGetProviderStates() { @@ -68,6 +75,15 @@ class RegistryTest extends TestCase { $this->dao->expects($this->once())->method('persist')->with('p1', 'user123', true); + $this->dispatcher->expects($this->once()) + ->method('dispatch') + ->with( + $this->equalTo(IRegistry::EVENT_PROVIDER_ENABLED), + $this->callback(function(RegistryEvent $e) use ($user, $provider) { + return $e->getUser() === $user && $e->getProvider() === $provider; + }) + ); + $this->registry->enableProviderFor($provider, $user); } @@ -79,6 +95,16 @@ class RegistryTest extends TestCase { $this->dao->expects($this->once())->method('persist')->with('p1', 'user123', false); + + $this->dispatcher->expects($this->once()) + ->method('dispatch') + ->with( + $this->equalTo(IRegistry::EVENT_PROVIDER_DISABLED), + $this->callback(function(RegistryEvent $e) use ($user, $provider) { + return $e->getUser() === $user && $e->getProvider() === $provider; + }) + ); + $this->registry->disableProviderFor($provider, $user); } |