From e5a176f5eac12108d037e88f29112ce8a59692d4 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 20 Aug 2024 13:05:06 +0200 Subject: [PATCH] Added editing of ACL rules #157 --- dev/View/Popup/Folder.js | 75 +++++++++++++++++- dev/View/Popup/FolderAcl.js | 58 ++++++++++++++ .../libraries/MailSo/Imap/Commands/ACL.php | 49 +++++++----- .../libraries/MailSo/Imap/Responses/ACL.php | 5 ++ .../libraries/RainLoop/Actions/Folders.php | 7 +- .../templates/Views/User/PopupsFolder.html | 31 ++++---- .../templates/Views/User/PopupsFolderACL.html | 78 +++++++++++++++++++ 7 files changed, 263 insertions(+), 40 deletions(-) create mode 100644 dev/View/Popup/FolderAcl.js create mode 100644 snappymail/v/0.0.0/app/templates/Views/User/PopupsFolderACL.html diff --git a/dev/View/Popup/Folder.js b/dev/View/Popup/Folder.js index 4dcdb04426..62b10689bc 100644 --- a/dev/View/Popup/Folder.js +++ b/dev/View/Popup/Folder.js @@ -10,6 +10,47 @@ import { folderListOptionsBuilder, sortFolders } from 'Common/Folders'; import { initOnStartOrLangChange, i18n, getNotification } from 'Common/Translator'; import { defaultOptionsAfterRender } from 'Common/Utils'; +import { FolderACLPopupView } from 'View/Popup/FolderAcl'; +import { showScreenPopup } from 'Knoin/Knoin'; + +import { AbstractCollectionModel } from 'Model/AbstractCollection'; +export class FolderACLModel extends AbstractCollectionModel +{ + static reviveFromJson(json) { + return super.reviveFromJson(json, rights => FolderACLRightsModel.reviveFromJson(rights)); + } +} + +import { AbstractModel } from 'Knoin/AbstractModel'; +export class FolderACLRightsModel extends AbstractModel { + constructor() { + super(); + addObservablesTo(this, { + identifier: '', + mine: false + }); + // The "RIGHTS=" capability MUST NOT include any of the rights defined in RFC 2086. + // That way we know it supports RFC 4314 +// this.rights = ko.observableArray(/*'alrswipcd'.split('')*/); + this.rights = ko.observableArray(/*'alrswipkxte'.split('')*/); + } + + static reviveFromJson(json) { + json.rights = json.rights.split(''); + return super.reviveFromJson(json); + } + + get mayReadItems() { return this.rights.includes('l') && this.rights.includes('r'); } + get mayAddItems() { return this.rights.includes('i'); } + get mayRemoveItems() { return this.rights.includes('t') && this.rights.includes('e'); } + get maySetSeen() { return this.rights.includes('s'); } + get maySetKeywords() { return this.rights.includes('w'); } + get mayCreateChild() { return this.rights.includes('k'); } + get mayRename() { return this.rights.includes('x'); } + get mayDelete() { return this.rights.includes('x'); } + get maySubmit() { return this.rights.includes('p'); } +} + export class FolderPopupView extends AbstractViewPopup { constructor() { super('Folder'); @@ -17,10 +58,10 @@ export class FolderPopupView extends AbstractViewPopup { folder: null, // FolderModel parentFolder: '', name: '', - editing: false + editing: false, + adminACL: false }); this.ACLAllowed = FolderUserStore.hasCapability('ACL'); - this.ACL = ko.observableArray(); this.parentFolderSelectList = koComputable(() => folderListOptionsBuilder( @@ -51,6 +92,8 @@ export class FolderPopupView extends AbstractViewPopup { }); this.defaultOptionsAfterRender = defaultOptionsAfterRender; + this.editACL = this.editACL.bind(this); + this.deleteACL = this.deleteACL.bind(this); } afterHide() { @@ -135,11 +178,35 @@ export class FolderPopupView extends AbstractViewPopup { this.close(); } + createACL() + { + showScreenPopup(FolderACLPopupView, [this.folder(), new FolderACLRightsModel]); + } + + editACL(acl) + { + showScreenPopup(FolderACLPopupView, [this.folder(), acl]); + } + + deleteACL(acl) + { + Remote.request('FolderDeleteACL', + (iError, data) => !iError && data.Result && this.folder().ACL.remove(acl), + { + folder: this.folder().fullName, + identifier: acl.identifier + } + ); + } + beforeShow(folder) { - this.ACL([]); + folder.ACL || (folder.ACL = ko.observableArray()); + this.adminACL(false); this.ACLAllowed && Remote.request('FolderACL', (iError, data) => { if (!iError && data.Result) { - this.ACL(Object.values(data.Result)); + folder.ACL(FolderACLModel.reviveFromJson(data.Result)); +// data.Result.map(aItem => FolderACLRightsModel.reviveFromJson(aItem)).filter(v => v) + this.adminACL(folder.ACL()[0].rights.includes('a')); } }, { folder: folder.fullName diff --git a/dev/View/Popup/FolderAcl.js b/dev/View/Popup/FolderAcl.js new file mode 100644 index 0000000000..786fdb21b8 --- /dev/null +++ b/dev/View/Popup/FolderAcl.js @@ -0,0 +1,58 @@ +import { AbstractViewPopup } from 'Knoin/AbstractViews'; +import { addObservablesTo } from 'External/ko'; +import Remote from 'Remote/User/Fetch'; + +export class FolderACLPopupView extends AbstractViewPopup { + constructor() { + super('FolderACL'); + addObservablesTo(this, { + create: false, + mine: false, + folderName: '', + identifier: '' + }); + this.rights = ko.observableArray(); + } + + submitForm(/*form*/) { + if (!this.mine()) { + const rights = this.rights(); + Remote.request('FolderSetACL', + (iError, data) => { + if (!iError && data.Result) { + const acl = this.acl; + if (!acl.identifier) { + this.folder.ACL.push(acl); + } + acl.rights = rights; + } + }, { + folder: this.folderName(), + identifier: this.identifier(), + rights: rights.join('') + } + ); + } + this.close(); + } + + beforeShow(folder, acl) { + this.folder = folder; + this.create(!acl.identifier()); + this.mine(acl.mine()); + this.acl = acl; +/* + this.ACLAllowed && Remote.request('FolderIdentifierRights', (iError, data) => { + if (!iError && data.Result) { + this.rights(data.Result.rights.split('')); + } + }, { + folder: folder.fullName, + identifier: acl.identifier + }); +*/ + this.folderName(folder.fullName); + this.identifier(acl.identifier()); + this.rights(acl.rights()); + } +} diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/ACL.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/ACL.php index 109079887e..103bf3b1e6 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/ACL.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/ACL.php @@ -91,6 +91,7 @@ public function ACLAllow(string $sFolderName, string $command) : bool private function FolderACLRequest(string $sFolderName, string $sCommand, array $aParams) : \MailSo\Imap\ResponseCollection { if ($this->ACLAllow($sFolderName, $sCommand)) try { +// \array_unshift($aParams, $this->EscapeFolderName($sFolderName)); return $this->SendRequestGetResponse($sCommand, $aParams); } catch (\Throwable $oException) { // Error in IMAP command $sCommand: ACLs disabled @@ -119,21 +120,29 @@ public function FolderDeleteACL(string $sFolderName, string $sIdentifier) : void public function FolderGetACL(string $sFolderName) : array { $aResult = array(); - $oResponses = $this->FolderACLRequest($sFolderName, 'GETACL', array($this->EscapeFolderName($sFolderName))); - foreach ($oResponses as $oResponse) { - // * ACL INBOX.shared demo@snappymail.eu akxeilprwtscd foobar@snappymail.eu akxeilprwtscd demo2@snappymail.eu lrwstipekxacd - if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType - && isset($oResponse->ResponseList[4]) - && 'ACL' === $oResponse->ResponseList[1] - && $sFolderName === $oResponse->ResponseList[2] - ) - { - $c = \count($oResponse->ResponseList); - for ($i = 3; $i < $c; $i += 2) { - $aResult[] = new ACLResponse( - $oResponse->ResponseList[$i], - $oResponse->ResponseList[$i+1] - ); + $response = $this->FolderMyRights($sFolderName); + $aResult[] = $response; + if ($response->hasRight('a')) { + // Else error: "NOPERM You lack administrator privileges on this mailbox." + $oResponses = $this->FolderACLRequest($sFolderName, 'GETACL', array($this->EscapeFolderName($sFolderName))); + foreach ($oResponses as $oResponse) { + // * ACL INBOX.shared demo@snappymail.eu akxeilprwtscd demo2@snappymail.eu lrwstipekxacd + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType + && isset($oResponse->ResponseList[4]) + && 'ACL' === $oResponse->ResponseList[1] + && $sFolderName === $oResponse->ResponseList[2] + ) + { + $c = \count($oResponse->ResponseList); + for ($i = 3; $i < $c; $i += 2) { + $response = new ACLResponse( + $oResponse->ResponseList[$i], + $oResponse->ResponseList[$i+1] + ); + if ($response->identifier() != $this->Settings->username) { + $aResult[] = $response; + } + } } } } @@ -163,9 +172,10 @@ public function FolderListRights(string $sFolderName, string $sIdentifier) : ?AC return null; } - public function FolderMyRights(string $sFolderName) : ?ACLResponse + public function FolderMyRights(string $sFolderName) : ACLResponse { $oResponses = $this->FolderACLRequest($sFolderName, 'MYRIGHTS', array($this->EscapeFolderName($sFolderName))); + $rights = ''; foreach ($oResponses as $oResponse) { if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType && isset($oResponse->ResponseList[3]) @@ -173,9 +183,12 @@ public function FolderMyRights(string $sFolderName) : ?ACLResponse && $sFolderName === $oResponse->ResponseList[2] ) { - return new ACLResponse('', $oResponse->ResponseList[3]); + $rights = $oResponse->ResponseList[3]; + break; } } - return null; + $response = new ACLResponse($this->Settings->username, $rights); + $response->mine = true; + return $response; } } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Responses/ACL.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Responses/ACL.php index 7f5ef5d6fe..bc5b6e8e56 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Responses/ACL.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Responses/ACL.php @@ -17,6 +17,7 @@ */ class ACL implements \JsonSerializable { + public bool $mine = false; private string $identifier, $rights; @@ -52,8 +53,11 @@ public function hasRight(string $right) : bool public function jsonSerialize() { return [ + '@Object' => 'Object/FolderACLRights', 'identifier' => $this->identifier, 'rights' => $this->rights, + 'mine' => $this->mine, +/* 'mayReadItems' => ($this->hasRight('l') && $this->hasRight('r')), 'mayAddItems' => $this->hasRight('i'), 'mayRemoveItems' => ($this->hasRight('t') && $this->hasRight('e')), @@ -63,6 +67,7 @@ public function jsonSerialize() 'mayRename' => $this->hasRight('x'), 'mayDelete' => $this->hasRight('x'), 'maySubmit' => $this->hasRight('p') +*/ ]; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php index 1c91eec9b7..a88d371167 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php @@ -413,11 +413,12 @@ public function DoSystemFoldersUpdate() : array public function DoFolderACL() : array { $this->initMailClientConnection(); - return $this->DefaultResponse( - $this->ImapClient()->FolderGetACL( + return $this->DefaultResponse([ + '@Object' => 'Collection/FolderACL', + '@Collection' => $this->ImapClient()->FolderGetACL( $this->GetActionParam('folder', '') ) - ); + ]); } public function DoFolderDeleteACL() : array diff --git a/snappymail/v/0.0.0/app/templates/Views/User/PopupsFolder.html b/snappymail/v/0.0.0/app/templates/Views/User/PopupsFolder.html index 03131c2d6d..03478644e2 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/PopupsFolder.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/PopupsFolder.html @@ -60,21 +60,22 @@

-
-
- - - - - - - - - - - -
mayAddItems
mayCreateChild
mayDelete
mayReadItems
mayRemoveItems
mayRename
maySetKeywords
maySetSeen
maySubmit
-
+
+ + + + + + + + +
+ + + +
+ +
diff --git a/snappymail/v/0.0.0/app/templates/Views/User/PopupsFolderACL.html b/snappymail/v/0.0.0/app/templates/Views/User/PopupsFolderACL.html new file mode 100644 index 0000000000..b72e095b39 --- /dev/null +++ b/snappymail/v/0.0.0/app/templates/Views/User/PopupsFolderACL.html @@ -0,0 +1,78 @@ +
+ × +

ACL:

+
+ +