Improved ACL handling #157

This commit is contained in:
the-djmaze 2024-02-13 10:44:38 +01:00
parent 7e304daee0
commit 306d3ec7b5
8 changed files with 110 additions and 116 deletions

View file

@ -402,8 +402,6 @@ export class FolderModel extends AbstractModel {
}
),
canBeEdited: () => !this.type() && this.exists/* && this.selectable()*/,
isSystemFolder: () => this.type()
| (FolderUserStore.allowKolab() && !!this.kolabType() & !SettingsUserStore.unhideKolabFolders()),
@ -484,8 +482,7 @@ export class FolderModel extends AbstractModel {
}
edit() {
// this.canBeEdited() && this.editing(true);
this.canBeEdited() && showScreenPopup(FolderPopupView, [this]);
showScreenPopup(FolderPopupView, [this]);
}
unedit() {

View file

@ -15,6 +15,7 @@
}
.folder-name {
cursor: pointer;
word-break: break-all;
white-space: pre-wrap;
}
@ -24,10 +25,6 @@
opacity: 0.6;
}
.folder-name.can-be-edited {
cursor: pointer;
}
select {
width: auto;
}

View file

@ -1,8 +1,7 @@
import { addObservablesTo } from 'External/ko';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
import { addObservablesTo } from 'External/ko';
import Remote from 'Remote/User/Fetch';
import { FolderUserStore } from 'Stores/User/Folder';
export class FolderPopupView extends AbstractViewPopup {
constructor() {
@ -10,7 +9,7 @@ export class FolderPopupView extends AbstractViewPopup {
addObservablesTo(this, {
folder: null // FolderModel
});
this.ACLAllowed = FolderUserStore.hasCapability('ACL');
this.ACL = ko.observableArray();
}
@ -26,19 +25,14 @@ export class FolderPopupView extends AbstractViewPopup {
beforeShow(folder) {
this.ACL([]);
folder.editing(true);
this.folder(folder);
Remote.request('FolderACL', (iError, data) => {
this.ACLAllowed && Remote.request('FolderACL', (iError, data) => {
if (!iError && data.Result) {
this.ACL(
Object.entries(data.Result).map(([key, value]) => {
value.identifier = key;
return value;
})
);
this.ACL(Object.values(data.Result));
}
}, {
folder: folder.fullName
});
!folder.type() && folder.exists && folder.selectable() && folder.editing(true);
this.folder(folder);
}
}

View file

@ -26,19 +26,24 @@ trait ACL
*/
public function ACLAllow(string $sFolderName, string $command) : bool
{
if ($this->ACLDisabled) {
if ($this->ACLDisabled || !$this->hasCapability('ACL')) {
return false;
}
// The "RIGHTS=" capability MUST NOT include any of the rights defined in RFC 2086:
// "l", "r", "s", "w", "i", "p", "a", "c", "d", and the digits ("0" .. "9")
// So it is: RIGHTS=texk
$mainRights = \str_split($this->CapabilityValue('RIGHTS') ?: '');
if ($this->hasCapability('ACL') || $mainRights) {
// $mainRights = \str_split($this->CapabilityValue('RIGHTS') ?: '');
if ('MYRIGHTS' === $command) {
// at least one of the "l", "r", "i", "k", "x", "a" rights is required
return true;
}
if (\in_array($command, ['GETACL','SETACL','LISTRIGHTS','DELETEACL'])) {
return true;
}
$rights = $this->FolderMyRights($sFolderName);
if ($rights) {
switch ($command)
@ -80,14 +85,23 @@ trait ACL
return $rights->hasRight('a');
}
}
return true;
}
private function FolderACLRequest(string $sFolderName, string $sCommand, array $aParams) : \MailSo\Imap\ResponseCollection
{
if ($this->ACLAllow($sFolderName, $sCommand)) try {
return $this->SendRequestGetResponse($sCommand, $aParams);
} catch (\Throwable $oException) {
// Error in IMAP command $sCommand: ACLs disabled
$this->ACLDisabled = true;
throw $oException;
}
return !\in_array($command, ['GETACL','SETACL','LISTRIGHTS','DELETEACL','MYRIGHTS']);
}
public function FolderSetACL(string $sFolderName, string $sIdentifier, string $sAccessRights) : void
{
// if ($this->ACLAllow($sFolderName, 'SETACL')) {
$this->SendRequestGetResponse('SETACL', array(
$this->FolderACLRequest($sFolderName, 'SETACL', array(
$this->EscapeFolderName($sFolderName),
$this->EscapeString($sIdentifier),
$this->EscapeString($sAccessRights)
@ -96,8 +110,7 @@ trait ACL
public function FolderDeleteACL(string $sFolderName, string $sIdentifier) : void
{
// if ($this->ACLAllow($sFolderName, 'DELETEACL')) {
$this->SendRequestGetResponse('DELETEACL', array(
$this->FolderACLRequest($sFolderName, 'DELETEACL', array(
$this->EscapeFolderName($sFolderName),
$this->EscapeString($sIdentifier)
));
@ -105,9 +118,8 @@ trait ACL
public function FolderGetACL(string $sFolderName) : array
{
// if ($this->ACLAllow($sFolderName, 'GETACL')) {
$oResponses = $this->SendRequestGetResponse('GETACL', array($this->EscapeFolderName($sFolderName)));
$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
@ -118,7 +130,10 @@ trait ACL
{
$c = \count($oResponse->ResponseList);
for ($i = 3; $i < $c; $i += 2) {
$aResult[$oResponse->ResponseList[$i]] = new ACLResponse(\str_split($oResponse->ResponseList[$i+1]));
$aResult[] = new ACLResponse(
$oResponse->ResponseList[$i],
$oResponse->ResponseList[$i+1]
);
}
}
}
@ -127,8 +142,7 @@ trait ACL
public function FolderListRights(string $sFolderName, string $sIdentifier) : ?ACLResponse
{
// if ($this->ACLAllow($sFolderName, 'LISTRIGHTS')) {
$oResponses = $this->SendRequestGetResponse('LISTRIGHTS', array(
$oResponses = $this->FolderACLRequest($sFolderName, 'LISTRIGHTS', array(
$this->EscapeFolderName($sFolderName),
$this->EscapeString($sIdentifier)
));
@ -140,7 +154,10 @@ trait ACL
&& $sIdentifier === $oResponse->ResponseList[3]
)
{
return static::aclRightsToClass(\array_slice($oResponse->ResponseList, 4));
foreach (\array_slice($oResponse->ResponseList, 4) as $rule) {
$result = \array_merge($result, \str_split($rule));
}
return new ACLResponse($sIdentifier, \implode('', \array_unique($result)));
}
}
return null;
@ -148,10 +165,7 @@ trait ACL
public function FolderMyRights(string $sFolderName) : ?ACLResponse
{
// if ($this->ACLAllow($sFolderName, 'MYRIGHTS')) {
// if ($this->hasCapability('ACL')) {
try {
$oResponses = $this->SendRequestGetResponse('MYRIGHTS', array($this->EscapeFolderName($sFolderName)));
$oResponses = $this->FolderACLRequest($sFolderName, 'MYRIGHTS', array($this->EscapeFolderName($sFolderName)));
foreach ($oResponses as $oResponse) {
if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType
&& isset($oResponse->ResponseList[3])
@ -159,24 +173,9 @@ trait ACL
&& $sFolderName === $oResponse->ResponseList[2]
)
{
return static::aclRightsToClass(\array_slice($oResponse->ResponseList, 3));
return new ACLResponse('', $oResponse->ResponseList[3]);
}
}
} catch (\Throwable $oException) {
// \MailSo\Imap\Exceptions\NegativeResponseException: Error in IMAP command MYRIGHTS: ACLs disabled
$this->ACLDisabled = true;
throw $oException;
}
return null;
}
private static function aclRightsToClass(array $rules) : ACLResponse
{
$result = array();
foreach ($rules as $rule) {
$result = \array_merge($result, \str_split($rule));
}
return new ACLResponse(\array_unique($result));
}
}

View file

@ -576,7 +576,7 @@ trait Folders
}
/*
// RFC 4314
if ($this->hasCapability('ACL') || $this->CapabilityValue('RIGHTS')) {
if ($this->hasCapability('ACL')) {
foreach ($oFolderCollection as $oFolder) {
if ($oFolder->Selectable()) try {
$oFolder->myRights = $this->FolderMyRights($oFolder->FullName);

View file

@ -17,18 +17,26 @@ namespace MailSo\Imap\Responses;
*/
class ACL implements \JsonSerializable
{
private array $rights;
private string
$identifier,
$rights;
function __construct(array $rights)
function __construct(string $identifier, string $rights)
{
$this->identifier = $identifier;
$this->rights = $rights;
}
public function identifier() : string
{
return $this->identifier;
}
/** PHP 8.1
public function hasRight(string|\MailSo\Imap\Enumerations\FolderACL $right)
{
if ($right instanceof \BackedEnum) {
return \in_array($right->value, $this->rights);
return \str_contains($this->rights, $right->value);
}
*/
public function hasRight(string $right) : bool
@ -37,13 +45,15 @@ class ACL implements \JsonSerializable
if (\defined($const)) {
$right = \constant($const);
}
return \in_array($right, $this->rights);
return \str_contains($this->rights, $right);
}
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return [
'identifier' => $this->identifier,
'rights' => $this->rights,
'mayReadItems' => ($this->hasRight('l') && $this->hasRight('r')),
'mayAddItems' => $this->hasRight('i'),
'mayRemoveItems' => ($this->hasRight('t') && $this->hasRight('e')),
@ -52,8 +62,7 @@ class ACL implements \JsonSerializable
'mayCreateChild' => $this->hasRight('k'),
'mayRename' => $this->hasRight('x'),
'mayDelete' => $this->hasRight('x'),
'maySubmit' => $this->hasRight('p'),
'raw' => \implode('', $this->rights)
'maySubmit' => $this->hasRight('p')
];
}

View file

@ -4,7 +4,7 @@
</header>
<form id="folderform" class="modal-body form-horizontal" autocomplete="off" spellcheck="false" data-bind="submit: submitForm">
<div data-bind="with: folder">
<div class="control-group">
<div class="control-group" data-bind="if: editing">
<label data-i18n="GLOBAL/NAME"></label>
<input name="folder" type="text"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
@ -30,7 +30,7 @@
}"></div>
<!-- /ko -->
</div>
<div class="control-group" data-bind="if: ACL">
<div class="control-group" data-bind="if: ACLAllowed">
<label>ACL</label>
<div data-bind="foreach: ACL">
<details>

View file

@ -1,8 +1,6 @@
<tr data-bind="css: { 'selectable': selectable, 'system': isSystemFolder }">
<td data-bind="css: 'deep-' + deep">
<span class="folder-name" data-bind="text: name, css: { 'can-be-edited': canBeEdited },
click: edit,
tooltipErrorTip: errorMsg"></span>
<span class="folder-name" data-bind="text: name, click: edit, tooltipErrorTip: errorMsg"></span>
<span class="folder-system-name" data-bind="text: nameInfo, visible: isSystemFolder"></span>
<span class="folder-size" data-bind="text: friendlySize, visible: size"></span>
</td>