]> source.dussan.org Git - nextcloud-server.git/commitdiff
allow password protected mail shares
authorBjoern Schiessle <bjoern@schiessle.org>
Tue, 28 Mar 2017 12:39:38 +0000 (14:39 +0200)
committerBjoern Schiessle <bjoern@schiessle.org>
Mon, 3 Apr 2017 08:29:32 +0000 (10:29 +0200)
Signed-off-by: Bjoern Schiessle <bjoern@schiessle.org>
apps/files_sharing/lib/Controller/ShareAPIController.php
apps/sharebymail/lib/ShareByMailProvider.php
apps/sharebymail/templates/altmailpassword.php [new file with mode: 0644]
apps/sharebymail/templates/mailpassword.php [new file with mode: 0644]
apps/sharebymail/tests/ShareByMailProviderTest.php
core/css/share.scss
core/js/sharedialogshareelistview.js
lib/private/Share20/Manager.php

index 80ba7534da0575fe17f7eb8f34aa2ee5f36e4844..bd3535536e420e92812946a48cfd3d530c60ed90 100644 (file)
@@ -742,12 +742,31 @@ class ShareAPIController extends OCSController {
 
                } else {
                        // For other shares only permissions is valid.
-                       if ($permissions === null) {
+                       if ($share->getShareType() !== \OCP\Share::SHARE_TYPE_EMAIL && $permissions === null) {
                                throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
-                       } else {
+                       } elseif ($permissions !== null) {
                                $permissions = (int)$permissions;
                                $share->setPermissions($permissions);
                        }
+
+                       if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
+                               if ($expireDate === '') {
+                                       $share->setExpirationDate(null);
+                               } else if ($expireDate !== null) {
+                                       try {
+                                               $expireDate = $this->parseDate($expireDate);
+                                       } catch (\Exception $e) {
+                                               throw new OCSBadRequestException($e->getMessage());
+                                       }
+                                       $share->setExpirationDate($expireDate);
+                               }
+
+                               if ($password === '') {
+                                       $share->setPassword(null);
+                               } else if ($password !== null) {
+                                       $share->setPassword($password);
+                               }
+                       }
                }
 
                if ($permissions !== null && $share->getShareOwner() !== $this->currentUser) {
index 86f2a8fd49a7ad253376d9051746bfa052eba231..f49a4d07eb9427eda6f18379cc51c5a70361b9d4 100644 (file)
@@ -275,10 +275,10 @@ class ShareByMailProvider implements IShareProvider {
        protected function createMailBody($template, $filename, $link, $owner, $initiator) {
 
                $mailBodyTemplate = new Template('sharebymail', $template, '');
-               $mailBodyTemplate->assign ('filename', $filename);
+               $mailBodyTemplate->assign ('filename', \OCP\Util::sanitizeHTML($filename));
                $mailBodyTemplate->assign ('link', $link);
-               $mailBodyTemplate->assign ('owner', $owner);
-               $mailBodyTemplate->assign ('initiator', $initiator);
+               $mailBodyTemplate->assign ('owner', \OCP\Util::sanitizeHTML($owner));
+               $mailBodyTemplate->assign ('initiator', \OCP\Util::sanitizeHTML($initiator));
                $mailBodyTemplate->assign ('onBehalfOf', $initiator !== $owner);
                $mailBody = $mailBodyTemplate->fetchPage();
 
@@ -290,6 +290,55 @@ class ShareByMailProvider implements IShareProvider {
                        $this->l->t('Failed to create the E-mail'));
        }
 
+       /**
+        * send password to recipient of a mail share
+        *
+        * @param string $filename
+        * @param string $initiator
+        * @param string $shareWith
+        */
+       protected function sendPassword($filename, $initiator, $shareWith, $password) {
+               $initiatorUser = $this->userManager->get($initiator);
+               $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
+               $subject = (string)$this->l->t('Password to access »%s« shared to you by %s', [$filename, $initiatorDisplayName]);
+
+               $message = $this->mailer->createMessage();
+               $htmlBody = $this->createMailBodyToSendPassword('mailpassword', $filename, $initiatorDisplayName, $password);
+               $textBody = $this->createMailBodyToSendPassword('altmailpassword', $filename,$initiatorDisplayName, $password);
+               $message->setTo([$shareWith]);
+               $message->setSubject($subject);
+               $message->setBody($textBody, 'text/plain');
+               $message->setHtmlBody($htmlBody);
+               $this->mailer->send($message);
+
+       }
+
+       /**
+        * create mail body to send password to recipient
+        *
+        * @param string $filename
+        * @param string $initiator
+        * @param string $password
+        * @return string plain text mail
+        * @throws HintException
+        */
+       protected function createMailBodyToSendPassword($template, $filename, $initiator, $password) {
+
+               $mailBodyTemplate = new Template('sharebymail', $template, '');
+               $mailBodyTemplate->assign ('filename', \OCP\Util::sanitizeHTML($filename));
+               $mailBodyTemplate->assign ('password', \OCP\Util::sanitizeHTML($password));
+               $mailBodyTemplate->assign ('initiator', \OCP\Util::sanitizeHTML($initiator));
+               $mailBody = $mailBodyTemplate->fetchPage();
+
+               if (is_string($mailBody)) {
+                       return $mailBody;
+               }
+
+               throw new HintException('Failed to create the E-mail',
+                       $this->l->t('Failed to create the E-mail'));
+       }
+
+
        /**
         * generate share token
         *
@@ -368,19 +417,30 @@ class ShareByMailProvider implements IShareProvider {
         * Update a share
         *
         * @param IShare $share
+        * @param string|null $plainTextPassword
         * @return IShare The share object
         */
-       public function update(IShare $share) {
+       public function update(IShare $share, $plainTextPassword = null) {
+
+               $originalShare = $this->getShareById($share->getId());
+
+               // a real password was given
+               $validPassword = $plainTextPassword !== null && $plainTextPassword !== '';
+
+               if($validPassword && $originalShare->getPassword() !== $share->getPassword()) {
+                       $this->sendPassword($share->getNode()->getName(), $share->getSharedBy(), $share->getSharedWith(), $plainTextPassword);
+               }
                /*
-                * We allow updating the permissions of mail shares
+                * We allow updating the permissions and password of mail shares
                 */
                $qb = $this->dbConnection->getQueryBuilder();
-                       $qb->update('share')
-                               ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
-                               ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
-                               ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
-                               ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
-                               ->execute();
+               $qb->update('share')
+                       ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
+                       ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
+                       ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
+                       ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
+                       ->set('password', $qb->createNamedParameter($share->getPassword()))
+                       ->execute();
 
                return $share;
        }
@@ -625,6 +685,7 @@ class ShareByMailProvider implements IShareProvider {
                $shareTime->setTimestamp((int)$data['stime']);
                $share->setShareTime($shareTime);
                $share->setSharedWith($data['share_with']);
+               $share->setPassword($data['password']);
 
                if ($data['uid_initiator'] !== null) {
                        $share->setShareOwner($data['uid_owner']);
diff --git a/apps/sharebymail/templates/altmailpassword.php b/apps/sharebymail/templates/altmailpassword.php
new file mode 100644 (file)
index 0000000..f6e4c5b
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
+ *
+ * @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/>.
+ *
+ */
+
+/** @var OC_Theme $theme */
+/** @var array $_ */
+print_unescaped($l->t("Hey there,\n\n%s shared »%s« with you.\nYou should have already received a separate mail with a link to access it.\n\nIt is protected with the following password: %s\n\n", [$_['initiator'], $_['filename'], $_['password']]));
+// TRANSLATORS term at the end of a mail
+p($l->t("Cheers!"));
+print_unescaped("\n");
+?>
+
+       --
+<?php p($theme->getName() . ' - ' . $theme->getSlogan()); ?>
+<?php print_unescaped("\n".$theme->getBaseUrl());
diff --git a/apps/sharebymail/templates/mailpassword.php b/apps/sharebymail/templates/mailpassword.php
new file mode 100644 (file)
index 0000000..49a4853
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
+ *
+ * @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/>.
+ *
+ */
+
+/** @var OC_Theme $theme */
+/** @var array $_ */
+?>
+
+<table cellspacing="0" cellpadding="0" border="0" width="100%">
+       <tr><td>
+                       <table cellspacing="0" cellpadding="0" border="0" width="600px">
+                               <tr>
+                                       <td colspan="2" bgcolor="<?php p($theme->getMailHeaderColor());?>">
+                                               <img src="<?php p(\OC::$server->getURLGenerator()->getAbsoluteURL(image_path('', 'logo-mail.png'))); ?>" alt="<?php p($theme->getName()); ?>"/>
+                                       </td>
+                               </tr>
+                               <tr><td colspan="2">&nbsp;</td></tr>
+                               <tr>
+                                       <td width="20px">&nbsp;</td>
+                                       <td style="font-weight:normal; font-size:0.8em; line-height:1.2em; font-family:verdana,'arial',sans;">
+                                               <?php
+                                               print_unescaped($l->t('Hey there,<br><br>%s shared <i>%s</i> with you.<br>You should have already received a separate mail with a link to access it.<br><br>It is protected with the following password: %s<br><br>', [$_['initiator'], $_['filename'], $_['password']]));
+                                               // TRANSLATORS term at the end of a mail
+                                               p($l->t('Cheers!'));
+                                               ?>
+                                       </td>
+                               </tr>
+                               <tr><td colspan="2">&nbsp;</td></tr>
+                               <tr>
+                                       <td width="20px">&nbsp;</td>
+                                       <td style="font-weight:normal; font-size:0.8em; line-height:1.2em; font-family:verdana,'arial',sans;">--<br>
+                                               <?php p($theme->getName()); ?> -
+                                               <?php p($theme->getSlogan()); ?>
+                                               <br><a href="<?php p($theme->getBaseUrl()); ?>"><?php p($theme->getBaseUrl());?></a>
+                                       </td>
+                               </tr>
+                               <tr>
+                                       <td colspan="2">&nbsp;</td>
+                               </tr>
+                       </table>
+               </td></tr>
+</table>
index 65eded3eb7d751e9b3e6fb25aae996fe15368e86..013507fd35f2ab4099addf81ab8fe36ff8673e66 100644 (file)
@@ -32,6 +32,7 @@ use OCP\ILogger;
 use OCP\IURLGenerator;
 use OCP\IUserManager;
 use OCP\Mail\IMailer;
+use OCP\Security\IHasher;
 use OCP\Security\ISecureRandom;
 use OCP\Share\Exceptions\ShareNotFound;
 use OCP\Share\IManager;
index 3ca2e4ea6436bf11b373ca14e17b8217015f588d..2c97af4aa0d836527744de4b08e06f22e586df17 100644 (file)
@@ -188,3 +188,9 @@ a {
 .popovermenu .datepicker {
     margin-left: 35px;
 }
+
+.popovermenu .passwordField {
+       margin-left: 35px;
+       width: inherit !important;
+}
+
index 8f8f3dfe10f986039a70474d150e96912d7084f3..6679078a4955b797789758e208c1fed6c0fcb056 100644 (file)
 /* globals Handlebars */
 
 (function() {
+
+       var PASSWORD_PLACEHOLDER = '**********';
+       var PASSWORD_PLACEHOLDER_MESSAGE = t('core', 'Choose a password for the mail share');
+
        if (!OC.Share) {
                OC.Share = {};
        }
                                                '</div>' +
                                        '</span>' +
                                '</li>' +
-               '<li>' +
+                               '{{#if isMailShare}}' +
+                                       '<li>' +
+                                               '<span class="shareOption menuitem">' +
+                                                       '<input id="password-{{cid}}-{{shareId}}" type="checkbox" name="password" class="password checkbox" {{#if isPasswordSet}}checked="checked"{{/if}}" />' +
+                                                       '<label for="password-{{cid}}-{{shareId}}">{{passwordLabel}}</label>' +
+                                                       '<div class="passwordContainer-{{cid}}-{{shareId}} {{#unless isPasswordSet}}hidden{{/unless}}">' +
+                                                       '    <label for="passwordField-{{cid}}-{{shareId}}" class="hidden-visually" value="{{password}}">{{passwordLabel}}</label>' +
+                                                       '    <input id="passwordField-{{cid}}-{{shareId}}" class="passwordField" type="password" placeholder="{{passwordPlaceholder}}" value="{{passwordValue}}" />' +
+                                                       '    <span class="icon-loading-small hidden"></span>' +
+                                                       '</div>' +
+                                               '</span>' +
+                                       '</li>' +
+                               '{{/if}}' +
+                               '<li>' +
                                        '<a href="#" class="unshare"><span class="icon-loading-small hidden"></span><span class="icon icon-delete"></span><span>{{unshareLabel}}</span></a>' +
                                '</li>' +
                        '</ul>' +
                        'click .unshare': 'onUnshare',
                        'click .icon-more': 'onToggleMenu',
                        'click .permissions': 'onPermissionChange',
-                       'click .expireDate' : 'onExpireDateChange'
+                       'click .expireDate' : 'onExpireDateChange',
+                       'click .password' : 'onMailSharePasswordProtectChange',
+                       'keyup input.passwordField': 'onMailSharePasswordKeyUp',
+                       'focusout input.passwordField': 'onMailSharePasswordEntered'
                },
 
                initialize: function(options) {
                                shareWithTitle = shareWith;
                        }
 
+                       var share = this.model.get('shares')[shareIndex];
+                       var password = share.password;
+                       var hasPassword = password !== null && password !== '';
+
+
                        return _.extend(hasPermissionOverride, {
                                cid: this.cid,
                                hasSharePermission: this.model.hasSharePermission(shareIndex),
                                isRemoteShare: shareType === OC.Share.SHARE_TYPE_REMOTE,
                                isMailShare: shareType === OC.Share.SHARE_TYPE_EMAIL,
                                isCircleShare: shareType === OC.Share.SHARE_TYPE_CIRCLE,
-                               isFileSharedByMail: shareType === OC.Share.SHARE_TYPE_EMAIL && !this.model.isFolder()
+                               isFileSharedByMail: shareType === OC.Share.SHARE_TYPE_EMAIL && !this.model.isFolder(),
+                               isPasswordSet: hasPassword,
+                               passwordPlaceholder: hasPassword ? PASSWORD_PLACEHOLDER : PASSWORD_PLACEHOLDER_MESSAGE,
                        });
                },
 
                                updatePermissionLabel: t('core', 'can change'),
                                deletePermissionLabel: t('core', 'can delete'),
                                expireDateLabel: t('core', 'set expiration data'),
+                               passwordLabel: t('core', 'password protect'),
                                crudsLabel: t('core', 'access control'),
                                triangleSImage: OC.imagePath('core', 'actions/triangle-s'),
                                isResharingAllowed: this.configModel.get('isResharingAllowed'),
                        }
                },
 
+               onMailSharePasswordProtectChange: function(event) {
+                       var element = $(event.target);
+                       var li = element.closest('li[data-share-id]');
+                       var shareId = li.data('share-id');
+                       var passwordContainerClass = '.passwordContainer-' + this.cid + '-' + shareId;
+                       var passwordContainer = $(passwordContainerClass);
+                       var inputClass = '#passwordField-' + this.cid + '-' + shareId;
+                       var passwordField = $(inputClass);
+                       var state = element.prop('checked');
+                       passwordContainer.toggleClass('hidden', !state);
+                       if (!state) {
+                               this.model.updateShare(shareId, {password: ''});
+                               passwordField.attr('value', '');
+                               passwordField.attr('placeholder', PASSWORD_PLACEHOLDER_MESSAGE);
+                       } else {
+                               var passwordField = '#passwordField-' + this.cid + '-' + shareId;
+                               this.$(passwordField).focus();
+                       }
+               },
+
+               onMailSharePasswordKeyUp: function(event) {
+                       if(event.keyCode === 13) {
+                               this.onMailSharePasswordEntered(event);
+                       }
+               },
+
+               onMailSharePasswordEntered: function(event) {
+                       var passwordField = $(event.target);
+                       var li = passwordField.closest('li[data-share-id]');
+                       var shareId = li.data('share-id');
+                       var passwordContainerClass = '.passwordContainer-' + this.cid + '-' + shareId;
+                       var loading = this.$el.find(passwordContainerClass + ' .icon-loading-small');
+                       if (!loading.hasClass('hidden')) {
+                               // still in process
+                               return;
+                       }
+
+                       passwordField.removeClass('error');
+                       var password = passwordField.val();
+                       // in IE9 the password might be the placeholder due to bugs in the placeholders polyfill
+                       if(password === '' || password === PASSWORD_PLACEHOLDER || password === PASSWORD_PLACEHOLDER_MESSAGE) {
+                               return;
+                       }
+
+                       loading
+                               .removeClass('hidden')
+                               .addClass('inlineblock');
+
+
+                       this.model.updateShare(shareId, {
+                               password: password
+                       }, {
+                               error: function(model, msg) {
+                                       // destroy old tooltips
+                                       passwordField.tooltip('destroy');
+                                       loading.removeClass('inlineblock').addClass('hidden');
+                                       passwordField.addClass('error');
+                                       passwordField.attr('title', msg);
+                                       passwordField.tooltip({placement: 'bottom', trigger: 'manual'});
+                                       passwordField.tooltip('show');
+                               },
+                               success: function(model, msg) {
+                                       passwordField.blur();
+                                       passwordField.attr('value', '');
+                                       passwordField.attr('placeholder', PASSWORD_PLACEHOLDER);
+                                       loading.removeClass('inlineblock').addClass('hidden');
+                               }
+                       });
+               },
+
                onPermissionChange: function(event) {
                        event.preventDefault();
                        event.stopPropagation();
index a02eb9205d01f33602fc3720b83b94d7000d8d8a..5eea40d3773397197442aa5c9b36ed9089cd8fc6 100644 (file)
@@ -730,11 +730,30 @@ class Manager implements IManager {
                        }
                }
 
+               $plainTextPassword = null;
+               if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK || $share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
+                       // Password updated.
+                       if ($share->getPassword() !== $originalShare->getPassword()) {
+                               //Verify the password
+                               $this->verifyPassword($share->getPassword());
+
+                               // If a password is set. Hash it!
+                               if ($share->getPassword() !== null) {
+                                       $plainTextPassword = $share->getPassword();
+                                       $share->setPassword($this->hasher->hash($plainTextPassword));
+                               }
+                       }
+               }
+
                $this->pathCreateChecks($share->getNode());
 
                // Now update the share!
                $provider = $this->factory->getProviderForType($share->getShareType());
-               $share = $provider->update($share);
+               if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
+                       $share = $provider->update($share, $plainTextPassword);
+               } else {
+                       $share = $provider->update($share);
+               }
 
                if ($expirationDateUpdated === true) {
                        \OC_Hook::emit('OCP\Share', 'post_set_expiration_date', [
@@ -1091,7 +1110,9 @@ class Manager implements IManager {
         * @return bool
         */
        public function checkPassword(\OCP\Share\IShare $share, $password) {
-               if ($share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK) {
+               $passwordProtected = $share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK
+                       || $share->getShareType() !== \OCP\Share::SHARE_TYPE_EMAIL;
+               if (!$passwordProtected) {
                        //TODO maybe exception?
                        return false;
                }