Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Commit

Permalink
Added editing of ACL rules #157
Browse files Browse the repository at this point in the history
  • Loading branch information
the-djmaze committed Aug 20, 2024
1 parent 5987ccd commit e5a176f
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 40 deletions.
75 changes: 71 additions & 4 deletions dev/View/Popup/Folder.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,58 @@ 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');
addObservablesTo(this, {
folder: null, // FolderModel
parentFolder: '',
name: '',
editing: false
editing: false,
adminACL: false
});
this.ACLAllowed = FolderUserStore.hasCapability('ACL');
this.ACL = ko.observableArray();

this.parentFolderSelectList = koComputable(() =>
folderListOptionsBuilder(
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down
58 changes: 58 additions & 0 deletions dev/View/Popup/FolderAcl.js
Original file line number Diff line number Diff line change
@@ -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());
}
}
49 changes: 31 additions & 18 deletions snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/ACL.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
}
}
}
}
Expand Down Expand Up @@ -163,19 +172,23 @@ 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])
&& 'MYRIGHTS' === $oResponse->ResponseList[1]
&& $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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
class ACL implements \JsonSerializable
{
public bool $mine = false;
private string
$identifier,
$rights;
Expand Down Expand Up @@ -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')),
Expand All @@ -63,6 +67,7 @@ public function jsonSerialize()
'mayRename' => $this->hasRight('x'),
'mayDelete' => $this->hasRight('x'),
'maySubmit' => $this->hasRight('p')
*/
];
}

Expand Down
7 changes: 4 additions & 3 deletions snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 16 additions & 15 deletions snappymail/v/0.0.0/app/templates/Views/User/PopupsFolder.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,22 @@ <h3 data-bind="text: folder()?.fullName"></h3>
</div>
<div class="control-group" data-bind="if: ACLAllowed">
<label>ACL</label>
<div data-bind="foreach: ACL">
<details>
<summary data-bind="text: identifier"></summary>
<table class="table table-hover table-bordered" style="width:auto"><tbody>
<tr><td>mayAddItems</td><td class="fontastic" data-bind="text: mayAddItems ? '☑' : '☐'"></td></tr>
<tr><td>mayCreateChild</td><td class="fontastic" data-bind="text: mayCreateChild ? '☑' : '☐'"></td></tr>
<tr><td>mayDelete</td><td class="fontastic" data-bind="text: mayDelete ? '☑' : '☐'"></td></tr>
<tr><td>mayReadItems</td><td class="fontastic" data-bind="text: mayReadItems ? '☑' : '☐'"></td></tr>
<tr><td>mayRemoveItems</td><td class="fontastic" data-bind="text: mayRemoveItems ? '☑' : '☐'"></td></tr>
<tr><td>mayRename</td><td class="fontastic" data-bind="text: mayRename ? '☑' : '☐'"></td></tr>
<tr><td>maySetKeywords</td><td class="fontastic" data-bind="text: maySetKeywords ? '☑' : '☐'"></td></tr>
<tr><td>maySetSeen</td><td class="fontastic" data-bind="text: maySetSeen ? '☑' : '☐'"></td></tr>
<tr><td>maySubmit</td><td class="fontastic" data-bind="text: maySubmit ? '☑' : '☐'"></td></tr>
</tbody></table>
</details>
<div>
<table class="table table-hover" style="width:auto"><tbody>
<!-- ko foreach: folder()?.ACL -->
<tr>
<td data-bind="text: identifier"></td>
<td>
<button class="btn fontastic" data-bind="visible: mine, click: $root.editACL">👁</button>
<button class="btn fontastic" data-bind="visible: !mine(), click: $root.editACL">🖉</button>
<button class="btn fontastic" data-bind="visible: !mine(), click: $root.deleteACL">🗑</button>
</td>
</tr>
<!-- /ko -->
<tr><td></td><td>
<button class="btn fontastic" data-bind="visible: adminACL, click: createACL"></button>
</td></tr>
</tbody></table>
</div>
</div>
</form>
Expand Down
Loading

0 comments on commit e5a176f

Please sign in to comment.