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 @@
+
+
+