fully importing mspt

Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
This commit is contained in:
Maxence Lange 2022-03-08 07:51:48 -01:00
parent 2e14f5f034
commit 7f246a395c
40 changed files with 7460 additions and 0 deletions

View file

@ -0,0 +1,418 @@
<?php
/*
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @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/>.
*
*/
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\ActivityPub;
use DateTime;
use Exception;
use OC;
use OCA\Circles\Tools\Exceptions\InvalidOriginException;
use OCA\Circles\Tools\Exceptions\ItemNotFoundException;
use OCA\Circles\Tools\Exceptions\MalformedArrayException;
use OCA\Circles\Tools\Exceptions\SignatoryException;
use OCA\Circles\Tools\Exceptions\SignatureException;
use OCA\Circles\Tools\Model\NCRequest;
use OCA\Circles\Tools\Model\NCSignatory;
use OCA\Circles\Tools\Model\NCSignedRequest;
use OCA\Circles\Tools\Model\SimpleDataStore;
use OCA\Circles\Tools\Traits\TNCSignatory;
use OCP\IRequest;
class NCSignature {
const DATE_HEADER = 'D, d M Y H:i:s T';
const DATE_OBJECT = 'Y-m-d\TH:i:s\Z';
const DATE_TTL = 300;
use TNCSignatory;
/** @var int */
private $ttl = self::DATE_TTL;
private $dateHeader = self::DATE_HEADER;
/**
* @param string $body
*
* @return NCSignedRequest
* @throws InvalidOriginException
* @throws MalformedArrayException
* @throws SignatoryException
* @throws SignatureException
*/
public function incomingSignedRequest(string $body = ''): NCSignedRequest {
if ($body === '') {
$body = file_get_contents('php://input');
}
$this->debug('[<<] incoming', ['body' => $body]);
$signedRequest = new NCSignedRequest($body);
$signedRequest->setIncomingRequest(OC::$server->get(IRequest::class));
$this->verifyIncomingRequestTime($signedRequest);
$this->verifyIncomingRequestContent($signedRequest);
$this->setIncomingSignatureHeader($signedRequest);
$this->setIncomingClearSignature($signedRequest);
$this->parseIncomingSignatureHeader($signedRequest);
$this->verifyIncomingRequestSignature($signedRequest);
return $signedRequest;
}
/**
* @param NCRequest $request
* @param NCSignatory $signatory
*
* @return NCSignedRequest
* @throws SignatoryException
*/
public function signOutgoingRequest(NCRequest $request, NCSignatory $signatory): NCSignedRequest {
$signedRequest = new NCSignedRequest($request->getDataBody());
$signedRequest->setOutgoingRequest($request)
->setSignatory($signatory);
$this->setOutgoingSignatureHeader($signedRequest);
$this->setOutgoingClearSignature($signedRequest);
$this->setOutgoingSignedSignature($signedRequest);
$this->signingOutgoingRequest($signedRequest);
return $signedRequest;
}
/**
* @param NCSignedRequest $signedRequest
*
* @throws SignatureException
*/
private function verifyIncomingRequestTime(NCSignedRequest $signedRequest): void {
$request = $signedRequest->getIncomingRequest();
try {
$dTime = new DateTime($request->getHeader('date'));
$signedRequest->setTime($dTime->getTimestamp());
} catch (Exception $e) {
$this->e($e, ['header' => $request->getHeader('date')]);
throw new SignatureException('datetime exception');
}
if ($signedRequest->getTime() < (time() - $this->ttl)) {
throw new SignatureException('object is too old');
}
}
/**
* @param NCSignedRequest $signedRequest
*
* @throws SignatureException
*/
private function verifyIncomingRequestContent(NCSignedRequest $signedRequest): void {
$request = $signedRequest->getIncomingRequest();
if (strlen($signedRequest->getBody()) !== (int)$request->getHeader('content-length')) {
throw new SignatureException('issue with content-length');
}
if ($request->getHeader('digest') !== ''
&& $signedRequest->getDigest() !== $request->getHeader('digest')) {
throw new SignatureException('issue with digest');
}
}
/**
* @param NCSignedRequest $signedRequest
*/
private function setIncomingSignatureHeader(NCSignedRequest $signedRequest): void {
$sign = [];
$request = $signedRequest->getIncomingRequest();
foreach (explode(',', $request->getHeader('Signature')) as $entry) {
if ($entry === '' || !strpos($entry, '=')) {
continue;
}
list($k, $v) = explode('=', $entry, 2);
preg_match('/"([^"]+)"/', $v, $varr);
if ($varr[0] !== null) {
$v = trim($varr[0], '"');
}
$sign[$k] = $v;
}
$signedRequest->setSignatureHeader(new SimpleDataStore($sign));
}
/**
* @param NCSignedRequest $signedRequest
*
* @throws SignatureException
*/
private function setIncomingClearSignature(NCSignedRequest $signedRequest): void {
$request = $signedRequest->getIncomingRequest();
$headers = explode(' ', $signedRequest->getSignatureHeader()->g('headers'));
$enforceHeaders = array_merge(
['content-length', 'date', 'host'],
$this->setupArray('enforceSignatureHeaders')
);
if (!empty(array_diff($enforceHeaders, $headers))) {
throw new SignatureException('missing elements in \'headers\'');
}
$target = strtolower($request->getMethod()) . " " . $request->getRequestUri();
$estimated = ['(request-target): ' . $target];
foreach ($headers as $key) {
$value = $request->getHeader($key);
if (strtolower($key) === 'host') {
$value = $signedRequest->getIncomingRequest()->getServerHost();
}
if ($value === '') {
throw new SignatureException('empty elements in \'headers\'');
}
$estimated[] = $key . ': ' . $value;
}
$signedRequest->setClearSignature(implode("\n", $estimated));
}
/**
* @param NCSignedRequest $signedRequest
*
* @throws MalformedArrayException
* @throws InvalidOriginException
*/
private function parseIncomingSignatureHeader(NCSignedRequest $signedRequest): void {
$data = $signedRequest->getSignatureHeader();
$data->hasKeys(['keyId', 'headers', 'signature'], true);
$signedRequest->setOrigin($this->getKeyOrigin($data->g('keyId')));
$signedRequest->setSignedSignature($data->g('signature'));
}
/**
* @param NCSignedRequest $signedRequest
*
* @throws SignatoryException
* @throws SignatureException
*/
private function verifyIncomingRequestSignature(NCSignedRequest $signedRequest) {
$data = $signedRequest->getSignatureHeader();
try {
$signedRequest->setSignatory($this->retrieveSignatory($data->g('keyId'), false));
$this->verifySignedRequest($signedRequest);
} catch (SignatoryException $e) {
$signedRequest->setSignatory($this->retrieveSignatory($data->g('keyId'), true));
$this->verifySignedRequest($signedRequest);
}
}
/**
* @param NCSignedRequest $signedRequest
*
* @throws SignatureException
*/
private function verifySignedRequest(NCSignedRequest $signedRequest) {
$publicKey = $signedRequest->getSignatory()->getPublicKey();
if ($publicKey === '') {
throw new SignatureException('empty public key');
}
try {
$this->verifyString(
$signedRequest->getClearSignature(),
base64_decode($signedRequest->getSignedSignature()),
$publicKey,
$this->getUsedEncryption($signedRequest)
);
} catch (SignatureException $e) {
$this->debug('signature issue', ['signed' => $signedRequest]);
throw $e;
}
}
/**
* @param NCSignedRequest $signedRequest
*/
private function setOutgoingSignatureHeader(NCSignedRequest $signedRequest): void {
$request = $signedRequest->getOutgoingRequest();
$data = new SimpleDataStore();
$data->s('(request-target)', NCRequest::method($request->getType()) . ' ' . $request->getPath())
->sInt('content-length', strlen($signedRequest->getBody()))
->s('date', gmdate($this->dateHeader))
->s('digest', $signedRequest->getDigest())
->s('host', $request->getHost());
$signedRequest->setSignatureHeader($data);
}
/**
* @param NCSignedRequest $signedRequest
*/
private function setOutgoingClearSignature(NCSignedRequest $signedRequest): void {
$signing = [];
$data = $signedRequest->getSignatureHeader();
foreach ($data->keys() as $element) {
try {
$value = $data->gItem($element);
$signing[] = $element . ': ' . $value;
if ($element !== '(request-target)') {
$signedRequest->getOutgoingRequest()->addHeader($element, $value);
}
} catch (ItemNotFoundException $e) {
}
}
$signedRequest->setClearSignature(implode("\n", $signing));
}
/**
* @param NCSignedRequest $signedRequest
*
* @throws SignatoryException
*/
private function setOutgoingSignedSignature(NCSignedRequest $signedRequest): void {
$clear = $signedRequest->getClearSignature();
$signed = $this->signString($clear, $signedRequest->getSignatory());
$signedRequest->setSignedSignature($signed);
}
/**
* @param NCSignedRequest $signedRequest
*
* @return void
*/
private function signingOutgoingRequest(NCSignedRequest $signedRequest): void {
$headers = array_diff($signedRequest->getSignatureHeader()->keys(), ['(request-target)']);
$signatory = $signedRequest->getSignatory();
$signatureElements = [
'keyId="' . $signatory->getKeyId() . '"',
'algorithm="' . $this->getChosenEncryption($signatory) . '"',
'headers="' . implode(' ', $headers) . '"',
'signature="' . $signedRequest->getSignedSignature() . '"'
];
$signedRequest->getOutgoingRequest()->addHeader('Signature', implode(',', $signatureElements));
}
/**
* @param NCSignedRequest $signedRequest
*
* @return string
*/
private function getUsedEncryption(NCSignedRequest $signedRequest): string {
switch ($signedRequest->getSignatureHeader()->g('algorithm')) {
case 'rsa-sha512':
return NCSignatory::SHA512;
case 'rsa-sha256':
default:
return NCSignatory::SHA256;
}
}
/**
* @param NCSignatory $signatory
*
* @return string
*/
private function getChosenEncryption(NCSignatory $signatory): string {
switch ($signatory->getAlgorithm()) {
case NCSignatory::SHA512:
return 'ras-sha512';
case NCSignatory::SHA256:
default:
return 'ras-sha256';
}
}
/**
* @param NCSignatory $signatory
*
* @return int
*/
public function getOpenSSLAlgo(NCSignatory $signatory): int {
switch ($signatory->getAlgorithm()) {
case NCSignatory::SHA512:
return OPENSSL_ALGO_SHA512;
case NCSignatory::SHA256:
default:
return OPENSSL_ALGO_SHA256;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Db;
/**
* Interface IQueryRow
*
* @package OCA\Circles\Tools\Db
*/
interface IQueryRow {
/**
* import data to feed the model.
*
* @param array $data
*
* @return IQueryRow
*/
public function importFromDatabase(array $data): self;
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class ArrayNotFoundException
*
* @package OCA\Circles\Tools\Exceptions
*/
class ArrayNotFoundException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class DateTimeException
*
* @package OCA\Circles\Tools\Exceptions
*/
class DateTimeException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class InvalidItemException
*
* @package OCA\Circles\Tools\Exceptions
*/
class InvalidItemException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class InvalidOriginException
*
* @package OCA\Circles\Tools\Exceptions
*/
class InvalidOriginException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class ItemNotFoundException
*
* @package OCA\Circles\Tools\Exceptions
*/
class ItemNotFoundException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class MalformedArrayException
*
* @package OCA\Circles\Tools\Exceptions
*/
class MalformedArrayException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class RequestContentException
*
* @package OCA\Circles\Tools\Exceptions
*/
class RequestContentException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class RequestNetworkException
*
* @package OCA\Circles\Tools\Exceptions
*/
class RequestNetworkException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class RequestResultNotJsonException
*
* @package OCA\Circles\Tools\Exceptions
*/
class RequestResultNotJsonException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class RequestResultSizeException
*
* @package OCA\Circles\Tools\Exceptions
*/
class RequestResultSizeException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class RequestServerException
*
* @package OCA\Circles\Tools\Exceptions
*/
class RequestServerException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class RowNotFoundException
*
* @package OCA\Circles\Tools\Exceptions
*/
class RowNotFoundException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class SignatoryException
*
* @package OCA\Circles\Tools\Exceptions
*/
class SignatoryException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class SignatureException
*
* @package OCA\Circles\Tools\Exceptions
*/
class SignatureException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class UnknownTypeException
*
* @package OCA\Circles\Tools\Exceptions
*/
class UnknownTypeException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Exceptions;
use Exception;
/**
* Class WellKnownLinkNotFoundException
*
* @package OCA\Circles\Tools\Exceptions
*/
class WellKnownLinkNotFoundException extends Exception {
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools;
interface IDeserializable {
/**
* @param array $data
*
* @return self
*/
public function import(array $data): self;
}

View file

@ -0,0 +1,176 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Model;
use OCP\Http\Client\IClient;
class NCRequest extends Request {
/** @var IClient */
private $client;
/** @var array */
private $clientOptions = [];
/** @var bool */
private $localAddressAllowed = false;
/** @var NCRequestResult */
private $result;
/** @var NCRequestResult[] */
private $previousResults = [];
/**
* @param IClient $client
*
* @return $this
*/
public function setClient(IClient $client): self {
$this->client = $client;
return $this;
}
/**
* @return IClient
*/
public function getClient(): IClient {
return $this->client;
}
/**
* @return array
*/
public function getClientOptions(): array {
return $this->clientOptions;
}
/**
* @param array $clientOptions
*
* @return self
*/
public function setClientOptions(array $clientOptions): self {
$this->clientOptions = $clientOptions;
return $this;
}
/**
* @return bool
*/
public function isLocalAddressAllowed(): bool {
return $this->localAddressAllowed;
}
/**
* @param bool $allowed
*
* @return self
*/
public function setLocalAddressAllowed(bool $allowed): self {
$this->localAddressAllowed = $allowed;
return $this;
}
/**
* @return bool
*/
public function hasResult(): bool {
return ($this->result !== null);
}
/**
* @return NCRequestResult
*/
public function getResult(): NCRequestResult {
return $this->result;
}
/**
* @param NCRequestResult $result
*
* @return self
*/
public function setResult(NCRequestResult $result): self {
if (!is_null($this->result)) {
$this->previousResults[] = $this->result;
}
$this->result = $result;
return $this;
}
/**
* @return NCRequestResult[]
*/
public function getPreviousResults(): array {
return $this->previousResults;
}
/**
* @return NCRequestResult[]
*/
public function getAllResults(): array {
return array_values(array_merge([$this->getResult()], $this->previousResults));
}
/**
* @return array
*/
public function jsonSerialize(): array {
$result = null;
if ($this->hasResult()) {
$result = $this->getResult();
}
return array_merge(
parent::jsonSerialize(),
[
'clientOptions' => $this->getClientOptions(),
'localAddressAllowed' => $this->isLocalAddressAllowed(),
'result' => $result
]
);
}
}

View file

@ -0,0 +1,325 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Model;
use GuzzleHttp\Exception\BadResponseException;
use JsonSerializable;
use OCA\Circles\Tools\Exceptions\RequestContentException;
use OCA\Circles\Tools\Traits\TArrayTools;
use OCP\Http\Client\IResponse;
class NCRequestResult implements JsonSerializable {
use TArrayTools;
const TYPE_STRING = 0;
const TYPE_BINARY = 1;
const TYPE_JSON = 2;
const TYPE_XRD = 3;
/** @var int */
private $statusCode = 0;
/** @var array */
private $headers = [];
/** @var mixed */
private $content;
/** @var array */
private $contentAsArray = [];
/** @var int */
private $contentType = 0;
/** @var BadResponseException */
private $exception = null;
/**
* NCRequestResult constructor.
*
* @param IResponse|null $response
* @param BadResponseException|null $e
*/
public function __construct(?IResponse $response, ?BadResponseException $e = null) {
if (!is_null($response)) {
$this->setStatusCode($response->getStatusCode());
$this->setContent($response->getBody());
$this->setHeaders($response->getHeaders());
}
if (!is_null($e)) {
$this->setException($e);
}
$this->generateMeta();
}
/**
* @return int
*/
public function getStatusCode(): int {
return $this->statusCode;
}
/**
* @param int $statusCode
*
* @return self
*/
public function setStatusCode(int $statusCode): self {
$this->statusCode = $statusCode;
return $this;
}
/**
* @return array
*/
public function getHeaders(): array {
return $this->headers;
}
/**
* @param array $headers
*
* @return self
*/
public function setHeaders(array $headers): self {
$this->headers = $headers;
return $this;
}
/**
* @param string $key
*
* @return array
*/
public function getHeader(string $key): array {
return $this->getArray($key, $this->headers);
}
public function withinHeader(string $key, string $needle): bool {
foreach ($this->getHeader($key) as $header) {
if (strpos($header, $needle) !== false) {
return true;
}
}
return false;
}
/**
* @param string $content
*
* @return self
*/
public function setContent(string $content): self {
$this->content = $content;
return $this;
}
/**
* @return string
* @throws RequestContentException
*/
public function getContent(): string {
if (is_null($this->content) || !is_string($this->content)) {
throw new RequestContentException();
}
return $this->content;
}
/**
* @return array
*/
public function getAsArray(): array {
if (empty($this->contentAsArray)) {
$this->generateContentAsArray();
}
return $this->contentAsArray;
}
/**
* @return string
*/
public function getBinary() {
return $this->content;
}
/**
* @return int
*/
public function getContentType(): int {
return $this->contentType;
}
/**
* @param int $type
*
* @return $this
*/
public function setContentType(int $type): self {
$this->contentType = $type;
return $this;
}
/**
* @param int $type
*
* @return bool
*/
public function isContentType(int $type): bool {
return ($this->contentType === $type);
}
/**
*
*/
private function generateMeta(): void {
$this->setContentType($this->discoverContentType());
$this->generateContentAsArray();
}
/**
* @return int
*/
private function discoverContentType(): int {
if ($this->withinHeader('Content-Type', 'application/xrd')) {
return self::TYPE_XRD;
}
if ($this->withinHeader('Content-Type', 'application/json')
|| $this->withinHeader('Content-Type', 'application/jrd')
) {
return self::TYPE_JSON;
}
try {
$content = $this->getContent();
} catch (RequestContentException $e) {
return self::TYPE_BINARY;
}
// in case header failure
$arr = json_decode($content, true);
if (is_array($arr)) {
return self::TYPE_JSON;
}
return self::TYPE_STRING;
}
/**
*
*/
private function generateContentAsArray(): void {
try {
$content = $this->getContent();
if ($this->isContentType(self::TYPE_XRD)) {
$xml = simplexml_load_string($content);
$content = json_encode($xml, JSON_UNESCAPED_SLASHES);
}
$arr = json_decode($content, true);
if (is_array($arr)) {
$this->contentAsArray = $arr;
}
} catch (RequestContentException $e) {
}
}
/**
* @param BadResponseException $e
*
* @return self
*/
public function setException(BadResponseException $e): self {
$this->exception = $e;
$this->setStatusCode($e->getResponse()->getStatusCode());
return $this;
}
/**
* @return BadResponseException
*/
public function getException(): BadResponseException {
return $this->exception;
}
/**
* @return bool
*/
public function hasException(): bool {
return (!is_null($this->exception));
}
/**
* @return array
*/
public function jsonSerialize(): array {
try {
$content = $this->getContent();
} catch (RequestContentException $e) {
$content = 'not a string';
}
return [
'statusCode' => $this->getStatusCode(),
'headers' => $this->getHeaders(),
'content' => $content,
'contentAsArray' => $this->contentAsArray,
'contentType' => $this->getContentType()
];
}
}

View file

@ -0,0 +1,297 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Model;
use JsonSerializable;
use OCA\Circles\Tools\Traits\TArrayTools;
class NCSignatory implements JsonSerializable {
use TArrayTools;
const SHA256 = 'sha256';
const SHA512 = 'sha512';
/** @var string */
private $instance = '';
/** @var string */
private $id = '';
/** @var string */
private $keyOwner = '';
/** @var string */
private $keyId = '';
/** @var string */
private $publicKey = '';
/** @var string */
private $privateKey = '';
/** @var array */
private $origData = [];
/** @var string */
private $algorithm = self::SHA256;
/**
* NC22Signatory constructor.
*
* @param string $id
*/
public function __construct(string $id = '') {
$this->id = self::removeFragment($id);
}
/**
* @param string $instance
*
* @return self
*/
public function setInstance(string $instance): self {
$this->instance = $instance;
return $this;
}
/**
* @return string
*/
public function getInstance(): string {
return $this->instance;
}
/**
* @return array
*/
public function getOrigData(): array {
return $this->origData;
}
/**
* /**
* @param array $data
*
* @return $this
*/
public function setOrigData(array $data): self {
$this->origData = $data;
return $this;
}
/**
* @return string
*/
public function getId(): string {
return $this->id;
}
/**
* @param string $id
*
* @return self
*/
public function setId(string $id): self {
$this->id = $id;
return $this;
}
/**
* @param string $keyId
*
* @return self
*/
public function setKeyId(string $keyId): self {
$this->keyId = $keyId;
return $this;
}
/**
* @return string
*/
public function getKeyId(): string {
return $this->keyId;
}
/**
* @param string $keyOwner
*
* @return self
*/
public function setKeyOwner(string $keyOwner): self {
$this->keyOwner = $keyOwner;
return $this;
}
/**
* @return string
*/
public function getKeyOwner(): string {
return $this->keyOwner;
}
/**
* @param string $publicKey
*
* @return self
*/
public function setPublicKey(string $publicKey): self {
$this->publicKey = $publicKey;
return $this;
}
/**
* @param string $privateKey
*
* @return self
*/
public function setPrivateKey(string $privateKey): self {
$this->privateKey = $privateKey;
return $this;
}
/**
* @return string
*/
public function getPublicKey(): string {
return $this->publicKey;
}
/**
* @return string
*/
public function getPrivateKey(): string {
return $this->privateKey;
}
/**
* @return bool
*/
public function hasPublicKey(): bool {
return ($this->publicKey !== '');
}
/**
* @return bool
*/
public function hasPrivateKey(): bool {
return ($this->privateKey !== '');
}
/**
* @param string $algorithm
*
* @return self
*/
public function setAlgorithm(string $algorithm): self {
$this->algorithm = $algorithm;
return $this;
}
/**
* @return string
*/
public function getAlgorithm(): string {
return $this->algorithm;
}
/**
* @param array $data
*
* @return $this
*/
public function import(array $data): self {
if ($this->getId() === '') {
$this->setId($this->get('id', $data));
}
$this->setKeyId($this->get('publicKey.id', $data));
$this->setKeyOwner($this->get('publicKey.owner', $data));
$this->setPublicKey($this->get('publicKey.publicKeyPem', $data));
return $this;
}
/**
* @return array
*/
public function jsonSerialize(): array {
return [
'id' => $this->getId(),
'publicKey' =>
[
'id' => $this->getKeyId(),
'owner' => $this->getKeyOwner(),
'publicKeyPem' => $this->getPublicKey()
]
];
}
/**
* @param string $id
*
* @return string
*/
public static function removeFragment(string $id): string {
$temp = strtok($id, '#');
if (is_string($temp)) {
$id = $temp;
}
return $id;
}
}

View file

@ -0,0 +1,351 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Model;
use JsonSerializable;
use OCP\IRequest;
class NCSignedRequest implements JsonSerializable {
/** @var string */
private $body = '';
/** @var int */
private $time = 0;
/** @var IRequest */
private $incomingRequest;
/** @var NCRequest */
private $outgoingRequest;
/** @var string */
private $origin = '';
/** @var string */
private $digest = '';
/** @var SimpleDataStore */
private $signatureHeader;
/** @var string */
private $host = '';
/** @var string */
private $clearSignature = '';
/** @var string */
private $signedSignature = '';
/** @var NCSignatory */
private $signatory;
public function __construct(string $body = '') {
$this->setBody($body);
}
/**
* IRequest of the incoming request
* incoming
*
* @return IRequest
*/
public function getIncomingRequest(): IRequest {
return $this->incomingRequest;
}
/**
* @param IRequest $request
*
* @return NCSignedRequest
*/
public function setIncomingRequest(IRequest $request): self {
$this->incomingRequest = $request;
return $this;
}
/**
* NCRequest of the outgoing request
* outgoing
*
* @param NCRequest $request
*
* @return NCSignedRequest
*/
public function setOutgoingRequest(NCRequest $request): self {
$this->outgoingRequest = $request;
return $this;
}
/**
* @return NCRequest
*/
public function getOutgoingRequest(): NCRequest {
return $this->outgoingRequest;
}
/**
* Body content of the request
* incoming/outgoing
*
* @return string
*/
public function getBody(): string {
return $this->body;
}
/**
* @param string $body
*
* @return self
*/
public function setBody(string $body): self {
$this->body = $body;
$this->setDigest('SHA-256=' . base64_encode(hash("sha256", utf8_encode($body), true)));
return $this;
}
/**
* Timestamp of the request
* incoming (outgoing ?)
*
* @return int
*/
public function getTime(): int {
return $this->time;
}
/**
* @param int $time
*
* @return self
*/
public function setTime(int $time): self {
$this->time = $time;
return $this;
}
/**
* Origin of the request, based on the keyId
* incoming
*
* @return string
*/
public function getOrigin(): string {
return $this->origin;
}
/**
* @param string $origin
*
* @return self
*/
public function setOrigin(string $origin): self {
$this->origin = $origin;
return $this;
}
/**
* @return string
*/
public function getDigest(): string {
return $this->digest;
}
/**
* @param string $digest
*
* @return $this
*/
public function setDigest(string $digest): self {
$this->digest = $digest;
return $this;
}
/**
* Data from the 'Signature' header
* incoming/outgoing
*
* @return SimpleDataStore
*/
public function getSignatureHeader(): SimpleDataStore {
return $this->signatureHeader;
}
/**
* @param SimpleDataStore $signatureHeader
*
* @return self
*/
public function setSignatureHeader(SimpleDataStore $signatureHeader): self {
$this->signatureHeader = $signatureHeader;
return $this;
}
/**
* _Clear_ value of the Signature.
* incoming/outgoing
*
* - estimated signature on incoming request
* - generated signature on outgoing request
*
* @param string $clearSignature
*
* @return NCSignedRequest
*/
public function setClearSignature(string $clearSignature): self {
$this->clearSignature = $clearSignature;
return $this;
}
/**
* @return string
*/
public function getClearSignature(): string {
return $this->clearSignature;
}
/**
* _Signed_ value of the signature.
* /!\ base64_encoded, not RAW /!\
*
* incoming/outgoing
*
* @param string $signedSignature
*
* @return self
*/
public function setSignedSignature(string $signedSignature): self {
$this->signedSignature = $signedSignature;
return $this;
}
/**
* @return string
*/
public function getSignedSignature(): string {
return $this->signedSignature;
}
/**
* Host/Address to be used in the signature.
* incoming/outgoing
*
* - incoming should set the local address
* - outgoing should set the recipient address
*
* @param string $host
*
* @return NCSignedRequest
*/
public function setHost(string $host): self {
$this->host = $host;
return $this;
}
/**
* @return string
*/
public function getHost(): string {
return $this->host;
}
/**
* Signatory used to sign the request
* incoming/outgoing
*
* @param NCSignatory $signatory
*/
public function setSignatory(NCSignatory $signatory): void {
$this->signatory = $signatory;
}
/**
* @return NCSignatory
*/
public function getSignatory(): NCSignatory {
return $this->signatory;
}
/**
* @return bool
*/
public function hasSignatory(): bool {
return ($this->signatory !== null);
}
/**
* @return array
*/
public function jsonSerialize(): array {
return [
'body' => $this->getBody(),
'time' => $this->getTime(),
'incomingRequest' => ($this->incomingRequest !== null),
'outgoingRequest' => $this->outgoingRequest !== null ? $this->getOutgoingRequest() : false,
'origin' => $this->getOrigin(),
'digest' => $this->getDigest(),
'signatureHeader' => ($this->signatureHeader !== null) ? $this->getSignatureHeader() : false,
'host' => $this->getHost(),
'clearSignature' => $this->getClearSignature(),
'signedSignature' => base64_encode($this->getSignedSignature()),
'signatory' => ($this->hasSignatory()) ? $this->getSignatory() : false,
];
}
}

View file

@ -0,0 +1,201 @@
<?php
/*
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @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/>.
*
*/
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Model;
use JsonSerializable;
use OCA\Circles\Tools\Traits\TArrayTools;
class NCWebfinger implements JsonSerializable {
use TArrayTools;
/** @var string */
private $subject = '';
/** @var array */
private $aliases = [];
/** @var array */
private $properties = [];
/** @var NCWellKnownLink[] */
private $links = [];
/**
* NC22Webfinger constructor.
*
* @param array $json
*/
public function __construct(array $json = []) {
$this->setSubject($this->get('subject', $json));
$this->setAliases($this->getArray('subject', $json));
$this->setProperties($this->getArray('properties', $json));
foreach ($this->getArray('links', $json) as $link) {
$this->addLink(new NCWellKnownLink($link));
}
}
/**
* @return string
*/
public function getSubject(): string {
return $this->subject;
}
/**
* @param string $subject
*
* @return self
*/
public function setSubject(string $subject): self {
$this->subject = $subject;
return $this;
}
/**
* @return array
*/
public function getAliases(): array {
return $this->aliases;
}
/**
* @param array $aliases
*
* @return self
*/
public function setAliases(array $aliases): self {
$this->aliases = $aliases;
return $this;
}
/**
* @return array
*/
public function getProperties(): array {
return $this->properties;
}
/**
* @param array $properties
*
* @return self
*/
public function setProperties(array $properties): self {
$this->properties = $properties;
return $this;
}
/**
* @param string $key
*
* @return string
*/
public function getProperty(string $key): string {
return $this->get($key, $this->properties);
}
/**
* @return NCWellKnownLink[]
*/
public function getLinks(): array {
return $this->links;
}
/**
* @param NCWellKnownLink[] $links
*
* @return self
*/
public function setLinks(array $links): self {
$this->links = $links;
return $this;
}
public function addLink(NCWellKnownLink $link): self {
$this->links[] = $link;
return $this;
}
/**
* @return array
*/
public function jsonSerialize(): array {
return array_filter(
[
'subject' => $this->getSubject(),
'aliases' => $this->getAliases(),
'properties' => $this->getProperties(),
'links' => $this->getLinks()
]
);
}
}

View file

@ -0,0 +1,226 @@
<?php
/*
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2017
* @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/>.
*
*/
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Model;
use JsonSerializable;
use OCA\Circles\Tools\Traits\TArrayTools;
class NCWellKnownLink implements JsonSerializable {
use TArrayTools;
/** @var string */
private $rel = '';
/** @var string */
private $type = '';
/** @var string */
private $href = '';
/** @var array */
private $titles = [];
/** @var array */
private $properties = [];
/**
* NC22WellKnownLink constructor.
*
* @param array $json
*/
public function __construct(array $json = []) {
$this->setRel($this->get('rel', $json));
$this->setType($this->get('type', $json));
$this->setHref($this->get('href', $json));
$this->setTitles($this->getArray('titles', $json));
$this->setProperties($this->getArray('properties', $json));
}
/**
* @return string
*/
public function getRel(): string {
return $this->rel;
}
/**
* @param string $rel
*
* @return self
*/
public function setRel(string $rel): self {
$this->rel = $rel;
return $this;
}
/**
* @return string
*/
public function getType(): string {
return $this->type;
}
/**
* @param string $type
*
* @return self
*/
public function setType(string $type): self {
$this->type = $type;
return $this;
}
/**
* @return string
*/
public function getHref(): string {
return $this->href;
}
/**
* @param string $href
*
* @return self
*/
public function setHref(string $href): self {
$this->href = $href;
return $this;
}
/**
* @return array
*/
public function getTitles(): array {
return $this->titles;
}
/**
* @param array $titles
*
* @return self
*/
public function setTitles(array $titles): self {
$this->titles = $titles;
return $this;
}
/**
* @param string $key
*
* @return string
*/
public function getTitle(string $key): string {
return $this->get($key, $this->properties);
}
/**
* @return array
*/
public function getProperties(): array {
return $this->properties;
}
/**
* @param array $properties
*
* @return self
*/
public function setProperties(array $properties): self {
$this->properties = $properties;
return $this;
}
/**
* @param string $key
*
* @return string
*/
public function getProperty(string $key): string {
return $this->get($key, $this->properties);
}
/**
* @return array
*/
public function jsonSerialize(): array {
return array_filter(
[
'rel' => $this->getRel(),
'type' => $this->getType(),
'href' => $this->getHref(),
'titles' => $this->getTitles(),
'properties' => $this->getProperties()
]
);
}
}

836
lib/Tools/Model/Request.php Normal file
View file

@ -0,0 +1,836 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Model;
use JsonSerializable;
use OCA\Circles\Tools\Traits\TArrayTools;
class Request implements JsonSerializable {
use TArrayTools;
const TYPE_GET = 0;
const TYPE_POST = 1;
const TYPE_PUT = 2;
const TYPE_DELETE = 3;
const QS_VAR_DUPLICATE = 1;
const QS_VAR_ARRAY = 2;
/** @var string */
private $protocol = '';
/** @var array */
private $protocols = ['https'];
/** @var string */
private $host = '';
/** @var int */
private $port = 0;
/** @var string */
private $url = '';
/** @var string */
private $baseUrl = '';
/** @var int */
private $type = 0;
/** @var bool */
private $binary = false;
/** @var bool */
private $verifyPeer = true;
/** @var bool */
private $httpErrorsAllowed = false;
/** @var bool */
private $followLocation = true;
/** @var array */
private $headers = [];
/** @var array */
private $cookies = [];
/** @var array */
private $params = [];
/** @var array */
private $data = [];
/** @var int */
private $queryStringType = self::QS_VAR_DUPLICATE;
/** @var int */
private $timeout = 10;
/** @var string */
private $userAgent = '';
/** @var int */
private $resultCode = 0;
/** @var string */
private $contentType = '';
/**
* Request constructor.
*
* @param string $url
* @param int $type
* @param bool $binary
*/
public function __construct(string $url = '', int $type = 0, bool $binary = false) {
$this->url = $url;
$this->type = $type;
$this->binary = $binary;
}
/**
* @param string $protocol
*
* @return Request
*/
public function setProtocol(string $protocol): Request {
$this->protocols = [$protocol];
return $this;
}
/**
* @param array $protocols
*
* @return Request
*/
public function setProtocols(array $protocols): Request {
$this->protocols = $protocols;
return $this;
}
/**
* @return string[]
*/
public function getProtocols(): array {
return $this->protocols;
}
/**
* @return string
*/
public function getUsedProtocol(): string {
return $this->protocol;
}
/**
* @param string $protocol
*
* @return Request
*/
public function setUsedProtocol(string $protocol): Request {
$this->protocol = $protocol;
return $this;
}
/**
* @return string
* @deprecated - 19 - use getHost();
*/
public function getAddress(): string {
return $this->getHost();
}
/**
* @param string $address
*
* @return Request
* @deprecated - 19 - use setHost();
*/
public function setAddress(string $address): Request {
$this->setHost($address);
return $this;
}
/**
* @return string
*/
public function getHost(): string {
return $this->host;
}
/**
* @param string $host
*
* @return Request
*/
public function setHost(string $host): Request {
$this->host = $host;
return $this;
}
/**
* @return int
*/
public function getPort(): int {
return $this->port;
}
/**
* @param int $port
*
* @return Request
*/
public function setPort(int $port): Request {
$this->port = $port;
return $this;
}
/**
* @param string $instance
*
* @return Request
*/
public function setInstance(string $instance): Request {
if (strpos($instance, ':') === false) {
$this->setHost($instance);
return $this;
}
list($host, $port) = explode(':', $instance, 2);
$this->setHost($host);
if ($port !== '') {
$this->setPort((int)$port);
}
return $this;
}
/**
* @return string
*/
public function getInstance(): string {
$instance = $this->getHost();
if ($this->getPort() > 0) {
$instance .= ':' . $this->getPort();
}
return $instance;
}
/**
* @param string $url
*
* @deprecated - 19 - use basedOnUrl();
*/
public function setAddressFromUrl(string $url) {
$this->basedOnUrl($url);
}
/**
* @param string $url
*/
public function basedOnUrl(string $url) {
$protocol = parse_url($url, PHP_URL_SCHEME);
if ($protocol === null) {
if (strpos($url, '/') > -1) {
list($address, $baseUrl) = explode('/', $url, 2);
$this->setBaseUrl('/' . $baseUrl);
} else {
$address = $url;
}
if (strpos($address, ':') > -1) {
list($address, $port) = explode(':', $address, 2);
$this->setPort((int)$port);
}
$this->setHost($address);
} else {
$this->setProtocols([$protocol]);
$this->setUsedProtocol($protocol);
$this->setHost(parse_url($url, PHP_URL_HOST));
$this->setBaseUrl(parse_url($url, PHP_URL_PATH));
if (is_numeric($port = parse_url($url, PHP_URL_PORT))) {
$this->setPort($port);
}
}
}
/**
* @param string|null $baseUrl
*
* @return Request
*/
public function setBaseUrl(?string $baseUrl): Request {
if ($baseUrl !== null) {
$this->baseUrl = $baseUrl;
}
return $this;
}
/**
* @return bool
*/
public function isBinary(): bool {
return $this->binary;
}
/**
* @param bool $verifyPeer
*
* @return $this
*/
public function setVerifyPeer(bool $verifyPeer): Request {
$this->verifyPeer = $verifyPeer;
return $this;
}
/**
* @return bool
*/
public function isVerifyPeer(): bool {
return $this->verifyPeer;
}
/**
* @param bool $httpErrorsAllowed
*
* @return Request
*/
public function setHttpErrorsAllowed(bool $httpErrorsAllowed): Request {
$this->httpErrorsAllowed = $httpErrorsAllowed;
return $this;
}
/**
* @return bool
*/
public function isHttpErrorsAllowed(): bool {
return $this->httpErrorsAllowed;
}
/**
* @param bool $followLocation
*
* @return $this
*/
public function setFollowLocation(bool $followLocation): Request {
$this->followLocation = $followLocation;
return $this;
}
/**
* @return bool
*/
public function isFollowLocation(): bool {
return $this->followLocation;
}
/**
* @return string
* @deprecated - 19 - use getParametersUrl() + addParam()
*/
public function getParsedUrl(): string {
$url = $this->getPath();
$ak = array_keys($this->getData());
foreach ($ak as $k) {
if (!is_string($this->data[$k])) {
continue;
}
$url = str_replace(':' . $k, $this->data[$k], $url);
}
return $url;
}
/**
* @return string
*/
public function getParametersUrl(): string {
$url = $this->getPath();
$ak = array_keys($this->getParams());
foreach ($ak as $k) {
if (!is_string($this->params[$k])) {
continue;
}
$url = str_replace(':' . $k, $this->params[$k], $url);
}
return $url;
}
/**
* @return string
*/
public function getPath(): string {
return $this->baseUrl . $this->url;
}
/**
* @return string
* @deprecated - 19 - use getPath()
*/
public function getUrl(): string {
return $this->getPath();
}
/**
* @return string
*/
public function getCompleteUrl(): string {
$port = ($this->getPort() > 0) ? ':' . $this->getPort() : '';
return $this->getUsedProtocol() . '://' . $this->getHost() . $port . $this->getParametersUrl();
}
/**
* @return int
*/
public function getType(): int {
return $this->type;
}
public function addHeader($key, $value): Request {
$header = $this->get($key, $this->headers);
if ($header !== '') {
$header .= ', ' . $value;
} else {
$header = $value;
}
$this->headers[$key] = $header;
return $this;
}
/**
* @return array
*/
public function getHeaders(): array {
return array_merge(['User-Agent' => $this->getUserAgent()], $this->headers);
}
/**
* @param array $headers
*
* @return Request
*/
public function setHeaders(array $headers): Request {
$this->headers = $headers;
return $this;
}
/**
* @return array
*/
public function getCookies(): array {
return $this->cookies;
}
/**
* @param array $cookies
*
* @return Request
*/
public function setCookies(array $cookies): Request {
$this->cookies = $cookies;
return $this;
}
/**
* @param int $queryStringType
*
* @return Request
*/
public function setQueryStringType(int $queryStringType): self {
$this->queryStringType = $queryStringType;
return $this;
}
/**
* @return int
*/
public function getQueryStringType(): int {
return $this->queryStringType;
}
/**
* @return array
*/
public function getData(): array {
return $this->data;
}
/**
* @param array $data
*
* @return Request
*/
public function setData(array $data): Request {
$this->data = $data;
return $this;
}
/**
* @param string $data
*
* @return Request
*/
public function setDataJson(string $data): Request {
$this->setData(json_decode($data, true));
return $this;
}
/**
* @param JsonSerializable $data
*
* @return Request
*/
public function setDataSerialize(JsonSerializable $data): Request {
$this->setDataJson(json_encode($data));
return $this;
}
/**
* @return array
*/
public function getParams(): array {
return $this->params;
}
/**
* @param array $params
*
* @return Request
*/
public function setParams(array $params): Request {
$this->params = $params;
return $this;
}
/**
* @param string $k
* @param string $v
*
* @return Request
*/
public function addParam(string $k, string $v): Request {
$this->params[$k] = $v;
return $this;
}
/**
* @param string $k
* @param int $v
*
* @return Request
*/
public function addParamInt(string $k, int $v): Request {
$this->params[$k] = $v;
return $this;
}
/**
* @param string $k
* @param string $v
*
* @return Request
*/
public function addData(string $k, string $v): Request {
$this->data[$k] = $v;
return $this;
}
/**
* @param string $k
* @param int $v
*
* @return Request
*/
public function addDataInt(string $k, int $v): Request {
$this->data[$k] = $v;
return $this;
}
/**
* @return string
*/
public function getDataBody(): string {
return json_encode($this->getData());
}
/**
* @return string
* @deprecated - 19 - use getUrlParams();
*/
public function getUrlData(): string {
if ($this->getData() === []) {
return '';
}
return preg_replace(
'/([(%5B)]{1})[0-9]+([(%5D)]{1})/', '$1$2', http_build_query($this->getData())
);
}
/**
* @return string
* @deprecated - 21 - use getQueryString();
*/
public function getUrlParams(): string {
if ($this->getParams() === []) {
return '';
}
return preg_replace(
'/([(%5B)]{1})[0-9]+([(%5D)]{1})/', '$1$2', http_build_query($this->getParams())
);
}
/**
* @param int $type
*
* @return string
*/
public function getQueryString(): string {
if (empty($this->getParams())) {
return '';
}
switch ($this->getQueryStringType()) {
case self::QS_VAR_ARRAY:
return '?' . http_build_query($this->getParams());
case self::QS_VAR_DUPLICATE:
default:
return '?' . preg_replace(
'/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', http_build_query($this->getParams())
);
}
}
/**
* @return int
*/
public function getTimeout(): int {
return $this->timeout;
}
/**
* @param int $timeout
*
* @return Request
*/
public function setTimeout(int $timeout): Request {
$this->timeout = $timeout;
return $this;
}
/**
* @return string
*/
public function getUserAgent(): string {
return $this->userAgent;
}
/**
* @param string $userAgent
*
* @return Request
*/
public function setUserAgent(string $userAgent): Request {
$this->userAgent = $userAgent;
return $this;
}
/**
* @return int
*/
public function getResultCode(): int {
return $this->resultCode;
}
/**
* @param int $resultCode
*
* @return Request
*/
public function setResultCode(int $resultCode): Request {
$this->resultCode = $resultCode;
return $this;
}
/**
* @return string
*/
public function getContentType(): string {
return $this->contentType;
}
/**
* @param string $contentType
*
* @return Request
*/
public function setContentType(string $contentType): Request {
$this->contentType = $contentType;
return $this;
}
/**
* @return array
*/
public function jsonSerialize(): array {
return [
'protocols' => $this->getProtocols(),
'used_protocol' => $this->getUsedProtocol(),
'port' => $this->getPort(),
'host' => $this->getHost(),
'url' => $this->getPath(),
'timeout' => $this->getTimeout(),
'type' => $this->getType(),
'cookies' => $this->getCookies(),
'headers' => $this->getHeaders(),
'params' => $this->getParams(),
'data' => $this->getData(),
'userAgent' => $this->getUserAgent(),
'followLocation' => $this->isFollowLocation(),
'verifyPeer' => $this->isVerifyPeer(),
'binary' => $this->isBinary()
];
}
/**
* @param string $type
*
* @return int
*/
public static function type(string $type): int {
switch (strtoupper($type)) {
case 'GET':
return self::TYPE_GET;
case 'POST':
return self::TYPE_POST;
case 'PUT':
return self::TYPE_PUT;
case 'DELETE':
return self::TYPE_DELETE;
}
return 0;
}
public static function method(int $type): string {
switch ($type) {
case self::TYPE_GET:
return 'get';
case self::TYPE_POST:
return 'post';
case self::TYPE_PUT:
return 'put';
case self::TYPE_DELETE:
return 'delete';
}
return '';
}
}

View file

@ -0,0 +1,488 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Model;
use JsonSerializable;
use OCA\Circles\Tools\Exceptions\InvalidItemException;
use OCA\Circles\Tools\Exceptions\ItemNotFoundException;
use OCA\Circles\Tools\Exceptions\MalformedArrayException;
use OCA\Circles\Tools\Exceptions\UnknownTypeException;
use OCA\Circles\Tools\IDeserializable;
use OCA\Circles\Tools\Traits\TArrayTools;
class SimpleDataStore implements JsonSerializable {
use TArrayTools;
/** @var array|JsonSerializable */
private $data;
/**
* SimpleDataStore constructor.
*
* @param array|null $data
*/
public function __construct(?array $data = []) {
if (!is_array($data)) {
$data = [];
}
$this->data = $data;
}
/**
* @param array $default
*/
public function default(array $default = []) {
$this->data = array_merge($default, $this->data);
}
/**
* @param string $key
* @param string $value
*
* @return SimpleDataStore
*/
public function s(string $key, string $value): self {
$this->data[$key] = $value;
return $this;
}
/**
* @param string $key
*
* @return string
*/
public function g(string $key): string {
return $this->get($key, $this->data);
}
/**
* @param string $key
*
* @return $this
*/
public function u(string $key): self {
if ($this->hasKey($key)) {
unset($this->data[$key]);
}
return $this;
}
/**
* @param string $key
* @param string $value
*
* @return SimpleDataStore
*/
public function a(string $key, string $value): self {
if (!array_key_exists($key, $this->data)) {
$this->data[$key] = [];
}
$this->data[$key][] = $value;
return $this;
}
/**
* @param string $key
* @param int $value
*
* @return SimpleDataStore
*/
public function sInt(string $key, int $value): self {
$this->data[$key] = $value;
return $this;
}
/**
* @param string $key
*
* @return int
*/
public function gInt(string $key): int {
return $this->getInt($key, $this->data);
}
/**
* @param string $key
* @param int $value
*
* @return SimpleDataStore
*/
public function aInt(string $key, int $value): self {
if (!array_key_exists($key, $this->data)) {
$this->data[$key] = [];
}
$this->data[$key][] = $value;
return $this;
}
/**
* @param string $key
* @param bool $value
*
* @return SimpleDataStore
*/
public function sBool(string $key, bool $value): self {
$this->data[$key] = $value;
return $this;
}
/**
* @param string $key
*
* @return bool
*/
public function gBool(string $key): bool {
return $this->getBool($key, $this->data);
}
/**
* @param string $key
* @param bool $value
*
* @return SimpleDataStore
*/
public function aBool(string $key, bool $value): self {
if (!array_key_exists($key, $this->data)) {
$this->data[$key] = [];
}
$this->data[$key][] = $value;
return $this;
}
/**
* @param string $key
* @param array $values
*
* @return SimpleDataStore
*/
public function sArray(string $key, array $values): self {
$this->data[$key] = $values;
return $this;
}
/**
* @param string $key
*
* @return array
*/
public function gArray(string $key): array {
return $this->getArray($key, $this->data);
}
/**
* @param string $key
* @param array $values
*
* @return SimpleDataStore
*/
public function aArray(string $key, array $values): self {
if (!array_key_exists($key, $this->data)) {
$this->data[$key] = [];
}
$this->data[$key] = array_merge($this->data[$key], $values);
return $this;
}
/**
* @param string $key
* @param JsonSerializable $value
*
* @return SimpleDataStore
*/
public function sObj(string $key, JsonSerializable $value): self {
$this->data[$key] = $value;
return $this;
}
/**
* @param string $key
* @param string $class
*
* @return JsonSerializable[]
*/
public function gObjs(string $key, string $class = ''): array {
$list = $this->gArray($key);
$result = [];
foreach ($list as $item) {
$data = new SimpleDataStore([$key => $item]);
$result[] = $data->gObj($key, $class);
}
return array_filter($result);
}
/**
* @param string $key
* @param string $class
*
* @return null|JsonSerializable
* @throws InvalidItemException
* @throws UnknownTypeException
* @throws ItemNotFoundException
*/
public function gObj(string $key, string $class = ''): ?JsonSerializable {
$type = $this->typeOf($key, $this->data);
if ($type === self::$TYPE_NULL) {
if ($class === '') {
return null;
}
throw new InvalidItemException();
}
if ($type === self::$TYPE_SERIALIZABLE) {
return $this->getObj($key, $this->data);
}
if ($type === self::$TYPE_ARRAY && $class !== '') {
$item = new $class();
if (!$item instanceof IDeserializable && !$item instanceof JsonSerializable) {
throw new InvalidItemException(
$class . ' does not implement IDeserializable and JsonSerializable'
);
}
$item->import($this->getArray($key, $this->data));
return $item;
}
throw new InvalidItemException();
}
/**
* @param string $key
* @param JsonSerializable $value
*
* @return SimpleDataStore
*/
public function aObj(string $key, JsonSerializable $value): self {
if (!array_key_exists($key, $this->data)) {
$this->data[$key] = [];
}
$this->data[$key][] = $value;
return $this;
}
/**
* @param string $key
* @param SimpleDataStore $data
*
* @return $this
*/
public function sData(string $key, SimpleDataStore $data): self {
$this->data[$key] = $data->gAll();
return $this;
}
/**
* @param string $key
* @param SimpleDataStore $data
*
* @return $this
*/
public function aData(string $key, SimpleDataStore $data): self {
if (!array_key_exists($key, $this->data) || !is_array($this->data[$key])) {
$this->data[$key] = [];
}
$this->data[$key][] = $data->gAll();
return $this;
}
/**
* @param string $key
*
* @return SimpleDataStore
*/
public function gData(string $key): SimpleDataStore {
return new SimpleDataStore($this->getArray($key, $this->data));
}
/**
* @param string $key
*
* @return mixed
* @throws ItemNotFoundException
*/
public function gItem(string $key) {
if (!array_key_exists($key, $this->data)) {
throw new ItemNotFoundException();
}
return $this->data[$key];
}
/**
* @return array
*/
public function gAll(): array {
return $this->data;
}
/**
* @param array $data
*
* @return SimpleDataStore
*/
public function sAll(array $data): self {
$this->data = $data;
return $this;
}
public function keys(): array {
return array_keys($this->data);
}
/**
* @param string $key
*
* @return bool
*/
public function hasKey(string $key): bool {
return (array_key_exists($key, $this->data));
}
/**
* @param array $keys
*
* @param bool $must
*
* @return bool
* @throws MalformedArrayException
*/
public function hasKeys(array $keys, bool $must = false): bool {
foreach ($keys as $key) {
if (!$this->haveKey($key)) {
if ($must) {
throw new MalformedArrayException($key . ' missing in ' . json_encode($this->keys()));
}
return false;
}
}
return true;
}
/**
* @param array $keys
* @param bool $must
*
* @return bool
* @throws MalformedArrayException
* @deprecated
*/
public function haveKeys(array $keys, bool $must = false): bool {
return $this->hasKeys($keys, $must);
}
/**
* @param string $key
*
* @return bool
* @deprecated
*/
public function haveKey(string $key): bool {
return $this->hasKey($key);
}
/**
* @param string $json
*
* @return $this
*/
public function json(string $json): self {
$data = json_decode($json, true);
if (is_array($data)) {
$this->data = $data;
}
return $this;
}
/**
* @param JsonSerializable $obj
*
* @return $this
*/
public function obj(JsonSerializable $obj): self {
$this->data = $obj;
return $this;
}
/**
* @return array
*/
public function jsonSerialize(): array {
return $this->data;
}
}

View file

@ -0,0 +1,232 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Model;
class TreeNode {
/** @var self[] */
private $children = [];
/** @var self */
private $parent;
/** @var SimpleDataStore */
private $item;
/** @var self */
private $currentChild;
/** @var bool */
private $displayed = false;
/** @var bool */
private $splited = false;
/**
* NC22TreeNode constructor.
*
* @param self|null $parent
* @param SimpleDataStore $item
*/
public function __construct(?TreeNode $parent, SimpleDataStore $item) {
$this->parent = $parent;
$this->item = $item;
if ($this->parent !== null) {
$this->parent->addChild($this);
}
}
/**
* @return bool
*/
public function isRoot(): bool {
return (is_null($this->parent));
}
/**
* @param array $children
*
* @return TreeNode
*/
public function setChildren(array $children): self {
$this->children = $children;
return $this;
}
/**
* @param TreeNode $child
*
* @return $this
*/
public function addChild(TreeNode $child): self {
$this->children[] = $child;
return $this;
}
/**
* @return SimpleDataStore
*/
public function getItem(): SimpleDataStore {
$this->displayed = true;
return $this->item;
}
/**
* @return TreeNode
*/
public function getParent(): TreeNode {
return $this->parent;
}
/**
* @return $this
*/
public function getRoot(): TreeNode {
if ($this->isRoot()) {
return $this;
}
return $this->getParent()->getRoot();
}
/**
* @return TreeNode[]
*/
public function getPath(): array {
if ($this->isRoot()) {
return [$this];
}
return array_merge($this->parent->getPath(), [$this]);
}
/**
* @return int
*/
public function getLevel(): int {
if ($this->isRoot()) {
return 0;
}
return $this->getParent()->getLevel() + 1;
}
/**
* @return TreeNode|null
*/
public function current(): ?TreeNode {
if (!$this->isDisplayed()) {
return $this;
}
$this->splited = true;
if ($this->initCurrentChild()) {
$next = $this->getCurrentChild()->current();
if (!is_null($next)) {
return $next;
}
}
if (!$this->haveNext()) {
return null;
}
return $this->next();
}
/**
* @return TreeNode
*/
private function next(): TreeNode {
$this->currentChild = array_shift($this->children);
return $this->currentChild;
}
/**
* @return bool
*/
public function haveNext(): bool {
return !empty($this->children);
}
/**
* @return bool
*/
private function initCurrentChild(): bool {
if (is_null($this->currentChild)) {
if (!$this->haveNext()) {
return false;
}
$this->next();
}
return true;
}
/**
* @return TreeNode|null
*/
private function getCurrentChild(): ?TreeNode {
return $this->currentChild;
}
/**
* @return bool
*/
private function isDisplayed(): bool {
return $this->displayed;
}
/**
* @return bool
*/
public function isSplited(): bool {
return $this->splited;
}
}

View file

@ -0,0 +1,432 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Traits;
use Exception;
use JsonSerializable;
use OCA\Circles\Tools\Exceptions\ArrayNotFoundException;
use OCA\Circles\Tools\Exceptions\ItemNotFoundException;
use OCA\Circles\Tools\Exceptions\MalformedArrayException;
use OCA\Circles\Tools\Exceptions\UnknownTypeException;
trait TArrayTools {
public static $TYPE_NULL = 'Null';
public static $TYPE_STRING = 'String';
public static $TYPE_ARRAY = 'Array';
public static $TYPE_BOOLEAN = 'Boolean';
public static $TYPE_INTEGER = 'Integer';
public static $TYPE_SERIALIZABLE = 'Serializable';
/**
* @param string $k
* @param array $arr
* @param string $default
*
* @return string
*/
protected function get(string $k, array $arr, string $default = ''): string {
if (!array_key_exists($k, $arr)) {
$subs = explode('.', $k, 2);
if (sizeof($subs) > 1) {
if (!array_key_exists($subs[0], $arr)) {
return $default;
}
$r = $arr[$subs[0]];
if (!is_array($r)) {
return $default;
}
return $this->get($subs[1], $r, $default);
} else {
return $default;
}
}
if ($arr[$k] === null || !is_string($arr[$k]) && (!is_int($arr[$k]))) {
return $default;
}
return (string)$arr[$k];
}
/**
* @param string $k
* @param array $arr
* @param int $default
*
* @return int
*/
protected function getInt(string $k, array $arr, int $default = 0): int {
if (!array_key_exists($k, $arr)) {
$subs = explode('.', $k, 2);
if (sizeof($subs) > 1) {
if (!array_key_exists($subs[0], $arr)) {
return $default;
}
$r = $arr[$subs[0]];
if (!is_array($r)) {
return $default;
}
return $this->getInt($subs[1], $r, $default);
} else {
return $default;
}
}
if ($arr[$k] === null) {
return $default;
}
return intval($arr[$k]);
}
/**
* @param string $k
* @param array $arr
* @param float $default
*
* @return float
*/
protected function getFloat(string $k, array $arr, float $default = 0): float {
if (!array_key_exists($k, $arr)) {
$subs = explode('.', $k, 2);
if (sizeof($subs) > 1) {
if (!array_key_exists($subs[0], $arr)) {
return $default;
}
$r = $arr[$subs[0]];
if (!is_array($r)) {
return $default;
}
return $this->getFloat($subs[1], $r, $default);
} else {
return $default;
}
}
if ($arr[$k] === null) {
return $default;
}
return intval($arr[$k]);
}
/**
* @param string $k
* @param array $arr
* @param bool $default
*
* @return bool
*/
protected function getBool(string $k, array $arr, bool $default = false): bool {
if (!array_key_exists($k, $arr)) {
$subs = explode('.', $k, 2);
if (sizeof($subs) > 1) {
if (!array_key_exists($subs[0], $arr)) {
return $default;
}
return $this->getBool($subs[1], $arr[$subs[0]], $default);
} else {
return $default;
}
}
if ($arr[$k] === null) {
return $default;
}
if (is_bool($arr[$k])) {
return $arr[$k];
}
$sk = (string)$arr[$k];
if ($sk === '1' || strtolower($sk) === 'true') {
return true;
}
if ($sk === '0' || strtolower($sk) === 'false') {
return false;
}
return $default;
}
/**
* @param string $k
* @param array $arr
* @param JsonSerializable|null $default
*
* @return mixed
*/
protected function getObj(string $k, array $arr, ?JsonSerializable $default = null): ?JsonSerializable {
if (!array_key_exists($k, $arr)) {
$subs = explode('.', $k, 2);
if (sizeof($subs) > 1) {
if (!array_key_exists($subs[0], $arr)) {
return $default;
}
return $this->getObj($subs[1], $arr[$subs[0]], $default);
} else {
return $default;
}
}
return $arr[$k];
}
/**
* @param string $k
* @param array $arr
* @param array $default
*
* @return array
*/
protected function getArray(string $k, array $arr, array $default = []): array {
if (!array_key_exists($k, $arr)) {
$subs = explode('.', $k, 2);
if (sizeof($subs) > 1) {
if (!array_key_exists($subs[0], $arr)) {
return $default;
}
$r = $arr[$subs[0]];
if (!is_array($r)) {
return $default;
}
return $this->getArray($subs[1], $r, $default);
} else {
return $default;
}
}
$r = $arr[$k];
if (!is_array($r) && !is_string($r)) {
return $default;
}
if (is_string($r)) {
$r = json_decode($r, true);
}
if (!is_array($r)) {
return $default;
}
return $r;
}
/**
* @param string $k
* @param array $arr
*
* @return bool
*/
public function validKey(string $k, array $arr): bool {
if (array_key_exists($k, $arr)) {
return true;
}
$subs = explode('.', $k, 2);
if (sizeof($subs) > 1) {
if (!array_key_exists($subs[0], $arr)) {
return false;
}
$r = $arr[$subs[0]];
if (!is_array($r)) {
return false;
}
return $this->validKey($subs[1], $r);
}
return false;
}
/**
* @param string $k
* @param array $arr
* @param array $import
* @param array $default
*
* @return array
*/
protected function getList(string $k, array $arr, array $import, array $default = []): array {
$list = $this->getArray($k, $arr, $default);
$r = [];
[$obj, $method] = $import;
foreach ($list as $item) {
try {
$o = new $obj();
$o->$method($item);
$r[] = $o;
} catch (Exception $e) {
}
}
return $r;
}
/**
* @param string $k
* @param string $value
* @param array $list
*
* @return mixed
* @throws ArrayNotFoundException
*/
protected function extractArray(string $k, string $value, array $list) {
foreach ($list as $arr) {
if (!array_key_exists($k, $arr)) {
continue;
}
if ($arr[$k] === $value) {
return $arr;
}
}
throw new ArrayNotFoundException();
}
/**
* @param string $key
* @param array $arr
* @param bool $root
*
* @return string
* @throws ItemNotFoundException
* @throws UnknownTypeException
*/
public function typeOf(string $key, array $arr, bool $root = true): string {
if (array_key_exists($key, $arr)) {
$item = $arr[$key];
if (is_null($item)) {
return self::$TYPE_NULL;
}
if (is_string($item)) {
return self::$TYPE_STRING;
}
if (is_array($item)) {
return self::$TYPE_ARRAY;
}
if (is_bool($item)) {
return self::$TYPE_BOOLEAN;
}
if (is_int($item)) {
return self::$TYPE_INTEGER;
}
if ($item instanceof JsonSerializable) {
return self::$TYPE_SERIALIZABLE;
}
throw new UnknownTypeException();
}
$subs = explode('.', $key, 2);
if (sizeof($subs) > 1) {
if (!array_key_exists($subs[0], $arr)) {
throw new ItemNotFoundException();
}
$r = $arr[$subs[0]];
if (is_array($r)) {
return $this->typeOf($subs[1], $r);
}
}
throw new ItemNotFoundException();
}
/**
* @param array $keys
* @param array $arr
*
* @throws MalformedArrayException
*/
protected function mustContains(array $keys, array $arr) {
foreach ($keys as $key) {
if (!array_key_exists($key, $arr)) {
throw new MalformedArrayException(
'source: ' . json_encode($arr) . ' - missing key: ' . $key
);
}
}
}
/**
* @param array $arr
*/
protected function cleanArray(array &$arr) {
$arr = array_filter(
$arr,
function ($v) {
if (is_string($v)) {
return ($v !== '');
}
if (is_array($v)) {
return !empty($v);
}
return true;
}
);
}
}

View file

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Traits;
use JsonSerializable;
trait TAsync {
use TNCSetup;
/** @var string */
public static $SETUP_TIME_LIMIT = 'async_time_limit';
/**
* Hacky way to async the rest of the process without keeping client on hold.
*
* @param string $result
*/
public function async(string $result = ''): void {
if (ob_get_contents() !== false) {
ob_end_clean();
}
header('Connection: close');
header('Content-Encoding: none');
ignore_user_abort();
$timeLimit = $this->setupInt(self::$SETUP_TIME_LIMIT);
set_time_limit(($timeLimit > 0) ? $timeLimit : 0);
ob_start();
echo($result);
$size = ob_get_length();
header('Content-Length: ' . $size);
ob_end_flush();
flush();
}
/**
* @param JsonSerializable $obj
*/
public function asyncObj(JsonSerializable $obj): void {
$this->async(json_encode($obj));
}
}

View file

@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Traits;
use OCA\Circles\Tools\Model\TreeNode;
use Symfony\Component\Console\Output\ConsoleOutput;
trait TConsoleTree {
/**
* @param TreeNode $root
* @param callable $method
* @param array $config
*/
public function drawTree(
TreeNode $root,
callable $method,
array $config = [
'height' => 1,
'node-spacing' => 0,
'item-spacing' => 0,
]
): void {
$config = array_merge(
[
'height' => 1,
'node-spacing' => 0,
'item-spacing' => 0
], $config
);
$output = new ConsoleOutput();
while (true) {
$node = $root->current();
if ($node === null) {
return;
}
$path = $node->getPath();
array_pop($path);
$line = $empty = $spacing = '';
$p = 0;
foreach ($path as $k => $i) {
$line .= ' ';
$empty .= ' ';
if ($k === array_key_last($path)) {
if ($i->haveNext()) {
$line .= '├';
$empty .= '│';
} else {
$line .= '└';
$empty .= ' ';
}
$line .= '── ';
$empty .= ' ';
} else {
if ($i->haveNext()) {
$line .= '│';
$empty .= '│';
} else {
$line .= ' ';
$empty .= ' ';
}
$line .= ' ';
$empty .= ' ';
}
$p++;
}
if ($p < $prec) {
for ($i = 0; $i < $config['node-spacing']; $i++) {
$spacing = substr($empty, 0, -3);
if (substr($spacing, -1) === ' ') {
$spacing = substr($spacing, 0, -1) . '│';
}
$output->writeln($spacing);
}
}
$prec = $p;
for ($i = 1; $i <= $config['height']; $i++) {
$draw = $method($node->getItem(), $i);
if ($draw === '') {
continue;
}
if ($i === 1) {
$output->write($line);
} else {
$output->write($empty);
}
$output->writeln($draw);
}
if ($node->haveNext()) {
$empty .= ' │';
}
if (!$node->isSplited() && $node->haveNext()) {
for ($i = 0; $i < $config['node-spacing']; $i++) {
$output->writeln($empty);
}
}
for ($i = 0; $i < $config['item-spacing']; $i++) {
$output->writeln($empty);
}
}
}
}

View file

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Traits;
use JsonSerializable;
use OCA\Circles\Tools\Exceptions\InvalidItemException;
use OCA\Circles\Tools\IDeserializable;
trait TDeserialize {
/**
* @param JsonSerializable $model
*
* @return array
*/
public function serialize(JsonSerializable $model): array {
return json_decode(json_encode($model), true);
}
/**
* @param array $data
*
* @return array
*/
public function serializeArray(array $data): array {
return json_decode(json_encode($data), true);
}
/**
* @param array $data
* @param string $class
*
* @return IDeserializable
* @throws InvalidItemException
*/
public function deserialize(array $data, string $class): IDeserializable {
if ($class instanceof IDeserializable) {
throw new InvalidItemException(get_class($class) . ' does not implement IDeserializable');
}
/** @var IDeserializable $item */
$item = new $class;
$item->import($data);
return $item;
}
/**
* @param array $data
* @param string $class
*
* @return IDeserializable[]
* @throws InvalidItemException
*/
public function deserializeArray(array $data, string $class): array {
$arr = [];
foreach ($data as $entry) {
$arr[] = $this->deserialize($entry, $class);
}
return $arr;
}
/**
* @param string $json
* @param string $class
*
* @return IDeserializable
* @throws InvalidItemException
*/
public function deserializeJson(string $json, string $class): IDeserializable {
$data = json_decode($json, true);
return $this->deserialize($data, $class);
}
}

View file

@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Traits;
use OC;
use OCA\Circles\Tools\Exceptions\SignatoryException;
use OCA\Circles\Tools\Model\NCSignatory;
use OCP\IConfig;
trait TNCLocalSignatory {
use TNCSignatory;
static $SIGNATORIES_APP = 'signatories';
/**
* @param NCSignatory $signatory
* @param bool $generate
*
* @throws SignatoryException
*/
public function fillSimpleSignatory(NCSignatory $signatory, bool $generate = false): void {
$app = $this->setup('app', '', self::$SIGNATORIES_APP);
$signatories = json_decode(OC::$server->get(IConfig::class)->getAppValue($app, 'key_pairs'), true);
if (!is_array($signatories)) {
$signatories = [];
}
$sign = $this->getArray($signatory->getId(), $signatories);
if (!empty($sign)) {
$signatory->setKeyId($this->get('keyId', $sign))
->setKeyOwner($this->get('keyOwner', $sign))
->setPublicKey($this->get('publicKey', $sign))
->setPrivateKey($this->get('privateKey', $sign));
return;
}
if (!$generate) {
throw new SignatoryException('signatory not found');
}
$this->createSimpleSignatory($signatory);
}
/**
* @param NCSignatory $signatory
*/
public function createSimpleSignatory(NCSignatory $signatory): void {
$app = $this->setup('app', '', self::$SIGNATORIES_APP);
$signatory->setKeyId($signatory->getId() . '#main-key');
$signatory->setKeyOwner($signatory->getId());
$this->generateKeys($signatory);
$signatories =
json_decode(OC::$server->get(IConfig::class)->getAppValue($app, 'key_pairs', '[]'), true);
$signatories[$signatory->getId()] = [
'keyId' => $signatory->getKeyId(),
'keyOwner' => $signatory->getKeyOwner(),
'publicKey' => $signatory->getPublicKey(),
'privateKey' => $signatory->getPrivateKey()
];
OC::$server->get(IConfig::class)->setAppValue($app, 'key_pairs', json_encode($signatories));
}
/**
* @param NCSignatory $signatory
*/
public function removeSimpleSignatory(NCSignatory $signatory): void {
$app = $this->setup('app', '', self::$SIGNATORIES_APP);
$signatories = json_decode(OC::$server->get(IConfig::class)->getAppValue($app, 'key_pairs'), true);
if (!is_array($signatories)) {
$signatories = [];
}
unset($signatories[$signatory->getId()]);
OC::$server->get(IConfig::class)->setAppValue($app, 'key_pairs', json_encode($signatories));
}
}

View file

@ -0,0 +1,205 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Traits;
use Exception;
use OC;
use OC\HintException;
use Psr\Log\LoggerInterface;
use Throwable;
trait TNCLogger {
use TNCSetup;
static $EMERGENCY = 4;
static $ALERT = 3;
static $CRITICAL = 3;
static $ERROR = 3;
static $WARNING = 2;
static $NOTICE = 1;
static $INFO = 1;
static $DEBUG = 0;
/**
* @param Throwable $t
* @param array $serializable
*/
public function t(Throwable $t, array $serializable = []): void {
$this->throwable($t, self::$ERROR, $serializable);
}
/**
* @param Throwable $t
* @param int $level
* @param array $serializable
*/
public function throwable(Throwable $t, int $level = 3, array $serializable = []): void {
$message = '';
if (!empty($serializable)) {
$message = json_encode($serializable);
}
$this->logger()
->log(
$level,
$message,
[
'app' => $this->setup('app'),
'exception' => $t
]
);
}
/**
* @param Exception $e
* @param array $serializable
*/
public function e(Exception $e, array $serializable = []): void {
$this->exception($e, self::$ERROR, $serializable);
}
/**
* @param Exception $e
* @param int|array $level
* @param array $serializable
*/
public function exception(Exception $e, $level = 3, array $serializable = []): void {
if (is_array($level) && empty($serializable)) {
$serializable = $level;
$level = 3;
}
$message = '';
if (!empty($serializable)) {
$message = json_encode($serializable);
}
if ($level === self::$DEBUG) {
$level = (int)$this->appConfig('debug_level');
}
$this->logger()
->log(
$level,
$message,
[
'app' => $this->setup('app'),
'exception' => $e
]
);
}
/**
* @param string $message
* @param bool $trace
* @param array $serializable
*/
public function emergency(string $message, bool $trace = false, array $serializable = []): void {
$this->log(self::$EMERGENCY, '[emergency] ' . $message, $trace, $serializable);
}
/**
* @param string $message
* @param bool $trace
* @param array $serializable
*/
public function alert(string $message, bool $trace = false, array $serializable = []): void {
$this->log(self::$ALERT, '[alert] ' . $message, $trace, $serializable);
}
/**
* @param string $message
* @param bool $trace
* @param array $serializable
*/
public function warning(string $message, bool $trace = false, array $serializable = []): void {
$this->log(self::$WARNING, '[warning] ' . $message, $trace, $serializable);
}
/**
* @param string $message
* @param bool $trace
* @param array $serializable
*/
public function notice(string $message, bool $trace = false, array $serializable = []): void {
$this->log(self::$NOTICE, '[notice] ' . $message, $trace, $serializable);
}
/**
* @param string $message
* @param array $serializable
*/
public function debug(string $message, array $serializable = []): void {
$message = '[debug] ' . $message;
$debugLevel = (int)$this->appConfig('debug_level');
$this->log($debugLevel, $message, ($this->appConfig('debug_trace') === '1'), $serializable);
}
/**
* @param int $level
* @param string $message
* @param bool $trace
* @param array $serializable
*/
public function log(int $level, string $message, bool $trace = false, array $serializable = []): void {
$opts = ['app' => $this->setup('app')];
if ($trace) {
$opts['exception'] = new HintException($message, json_encode($serializable));
} elseif (!empty($serializable)) {
$message .= ' -- ' . json_encode($serializable);
}
$this->logger()
->log($level, $message, $opts);
}
/**
* @return LoggerInterface
*/
public function logger(): LoggerInterface {
if (isset($this->logger) && $this->logger instanceof LoggerInterface) {
return $this->logger;
} else {
return OC::$server->get(LoggerInterface::class);
}
}
}

View file

@ -0,0 +1,182 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Traits;
use OCA\Circles\Tools\Model\NCRequest;
use Exception;
use GuzzleHttp\Exception\ClientException;
use OC;
use OCA\Circles\Tools\Exceptions\RequestNetworkException;
use OCA\Circles\Tools\Model\NCRequestResult;
use OCA\Circles\Tools\Model\Request;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\IResponse;
trait TNCRequest {
use TNCLogger;
/**
* @param int $size
*/
public function setMaxDownloadSize(int $size) {
}
/**
* @param NCRequest $request
*
* @return array
* @throws RequestNetworkException
*/
public function retrieveJson(NCRequest $request): array {
$this->doRequest($request);
$requestResult = $request->getResult();
return $requestResult->getAsArray();
}
/**
* @param NCRequest $request
* @param bool $exceptionOnIssue
*
* @throws RequestNetworkException
*/
public function doRequest(NCRequest $request, bool $exceptionOnIssue = true): void {
$request->setClient(
$this->clientService()
->newClient()
);
$this->generationClientOptions($request);
$this->debug('doRequest initiated', ['request' => $request]);
foreach ($request->getProtocols() as $protocol) {
$request->setUsedProtocol($protocol);
try {
$response = $this->useClient($request);
$request->setResult(new NCRequestResult($response));
break;
} catch (ClientException $e) {
$request->setResult(new NCRequestResult(null, $e));
} catch (Exception $e) {
$this->exception($e, self::$DEBUG, ['request' => $request]);
}
}
$this->debug('doRequest done', ['request' => $request]);
if ($exceptionOnIssue && (!$request->hasResult() || $request->getResult()->hasException())) {
throw new RequestNetworkException();
}
}
/**
* @return IClientService
*/
public function clientService(): IClientService {
if (isset($this->clientService) && $this->clientService instanceof IClientService) {
return $this->clientService;
} else {
return OC::$server->get(IClientService::class);
}
}
/**
* @param NCRequest $request
*/
private function generationClientOptions(NCRequest $request) {
$options = [
'headers' => $request->getHeaders(),
'cookies' => $request->getCookies(),
'verify' => $request->isVerifyPeer(),
'timeout' => $request->getTimeout(),
'http_errors' => !$request->isHttpErrorsAllowed()
];
if (!empty($request->getData())) {
$options['body'] = $request->getDataBody();
}
if (!empty($request->getParams())) {
$options['form_params'] = $request->getParams();
}
if ($request->isLocalAddressAllowed()) {
$options['nextcloud']['allow_local_address'] = true;
}
if ($request->isFollowLocation()) {
$options['allow_redirects'] = [
'max' => 10,
'strict' => true,
'referer' => true,
];
} else {
$options['allow_redirects'] = false;
}
$request->setClientOptions($options);
}
/**
* @param NCRequest $request
*
* @return IResponse
* @throws Exception
*/
private function useClient(NCRequest $request): IResponse {
$client = $request->getClient();
switch ($request->getType()) {
case Request::TYPE_POST:
return $client->post($request->getCompleteUrl(), $request->getClientOptions());
case Request::TYPE_PUT:
return $client->put($request->getCompleteUrl(), $request->getClientOptions());
case Request::TYPE_DELETE:
return $client->delete($request->getCompleteUrl(), $request->getClientOptions());
case Request::TYPE_GET:
return $client->get(
$request->getCompleteUrl() . $request->getQueryString(), $request->getClientOptions()
);
default:
throw new Exception('unknown request type ' . json_encode($request));
}
}
}

View file

@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Traits;
use OC;
use OCP\IConfig;
trait TNCSetup {
use TArrayTools;
/** @var array */
private $_setup = [];
/**
* @param string $key
* @param string $value
*
* @param string $default
*
* @return string
*/
public function setup(string $key, string $value = '', string $default = ''): string {
if ($value !== '') {
$this->_setup[$key] = $value;
}
return $this->get($key, $this->_setup, $default);
}
/**
* @param string $key
* @param array $value
* @param array $default
*
* @return array
*/
public function setupArray(string $key, array $value = [], array $default = []): array {
if (!empty($value)) {
$this->_setup[$key] = $value;
}
return $this->getArray($key, $this->_setup, $default);
}
/**
* @param string $key
* @param int $value
* @param int $default
*
* @return int
*/
public function setupInt(string $key, int $value = -999, int $default = 0): int {
if ($value !== -999) {
$this->_setup[$key] = $value;
}
return $this->getInt($key, $this->_setup, $default);
}
/**
* @param string $key
*
* @return string
*/
public function appConfig(string $key): string {
$app = $this->setup('app');
if ($app === '') {
return '';
}
/** @var IConfig $config */
$config = OC::$server->get(IConfig::class);
return $config->getAppValue($app, $key, '');
}
}

View file

@ -0,0 +1,212 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Traits;
use OCA\Circles\Tools\Exceptions\InvalidOriginException;
use OCA\Circles\Tools\Exceptions\RequestNetworkException;
use OCA\Circles\Tools\Exceptions\SignatoryException;
use OCA\Circles\Tools\Exceptions\SignatureException;
use OCA\Circles\Tools\Model\NCRequest;
use OCA\Circles\Tools\Model\NCSignatory;
trait TNCSignatory {
use TNCRequest;
/**
* return Signatory by its Id from cache or from direct request.
* Should be overwritten.
*
* @param string $keyId
* @param bool $refresh
*
* @return NCSignatory
* @throws SignatoryException
*/
public function retrieveSignatory(string $keyId, bool $refresh = false): NCSignatory {
if (!$refresh) {
throw new SignatoryException();
}
$signatory = new NCSignatory($keyId);
$this->downloadSignatory($signatory, $keyId);
return $signatory;
}
/**
* @param NCSignatory $signatory
* @param string $keyId
* @param array $params
* @param NCRequest|null $request
*
* @throws SignatoryException
*/
public function downloadSignatory(
NCSignatory $signatory,
string $keyId = '',
array $params = [],
?NCRequest $request = null
): void {
if (is_null($request)) {
$request = new NCRequest();
$request->setFollowLocation(true);
$request->setTimeout(5);
}
$request->basedOnUrl(($keyId !== '') ? $keyId : $signatory->getId());
$request->setParams($params);
$request->addHeader('Accept', 'application/ld+json');
try {
$this->updateSignatory($signatory, $this->retrieveJson($request), $keyId);
} catch (RequestNetworkException $e) {
$this->debug('network issue while downloading Signatory', ['request' => $request]);
throw new SignatoryException('network issue: ' . $e->getMessage());
}
}
/**
* @param NCSignatory $signatory
* @param array $json
* @param string $keyId
*
* @throws SignatoryException
*/
public function updateSignatory(NCSignatory $signatory, array $json, string $keyId = ''): void {
$signatory->setOrigData($json)
->import($json);
if ($keyId === '') {
$keyId = $signatory->getKeyId();
}
try {
if (($signatory->getId() !== $keyId && $signatory->getKeyId() !== $keyId)
|| $signatory->getId() !== $signatory->getKeyOwner()
|| $this->getKeyOrigin($signatory->getKeyId()) !== $this->getKeyOrigin($signatory->getId())
|| $signatory->getPublicKey() === '') {
$this->debug('invalid format', ['signatory' => $signatory, 'keyId' => $keyId]);
throw new SignatoryException('invalid format');
}
} catch (InvalidOriginException $e) {
throw new SignatoryException('invalid origin');
}
}
/**
* @param string $keyId
*
* @return string
* @throws InvalidOriginException
*/
public function getKeyOrigin(string $keyId) {
$host = parse_url($keyId, PHP_URL_HOST);
if (is_string($host) && ($host !== '')) {
return $host;
}
throw new InvalidOriginException('cannot retrieve origin from ' . $keyId);
}
/**
* @param NCSignatory $signatory
* @param string $digest
* @param int $bits
* @param int $type
*/
public function generateKeys(
NCSignatory $signatory,
string $digest = 'rsa',
int $bits = 2048,
int $type = OPENSSL_KEYTYPE_RSA
) {
$res = openssl_pkey_new(
[
'digest_alg' => $digest,
'private_key_bits' => $bits,
'private_key_type' => $type,
]
);
openssl_pkey_export($res, $privateKey);
$publicKey = openssl_pkey_get_details($res)['key'];
$signatory->setPublicKey($publicKey);
$signatory->setPrivateKey($privateKey);
}
/**
* @param string $clear
* @param NCSignatory $signatory
*
* @return string
* @throws SignatoryException
*/
public function signString(string $clear, NCSignatory $signatory): string {
$privateKey = $signatory->getPrivateKey();
if ($privateKey === '') {
throw new SignatoryException('empty private key');
}
openssl_sign($clear, $signed, $privateKey, $this->getOpenSSLAlgo($signatory));
return base64_encode($signed);
}
/**
* @param string $clear
* @param string $signed
* @param string $publicKey
* @param string $algo
*
* @throws SignatureException
*/
public function verifyString(
string $clear, string $signed, string $publicKey, string $algo = NCSignatory::SHA256
) {
if (openssl_verify($clear, $signed, $publicKey, $algo) !== 1) {
throw new SignatureException('signature issue');
}
}
}

View file

@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Traits;
use OCA\Circles\Tools\Exceptions\RequestNetworkException;
use OCA\Circles\Tools\Exceptions\WellKnownLinkNotFoundException;
use OCA\Circles\Tools\Model\NCRequest;
use OCA\Circles\Tools\Model\NCWebfinger;
use OCA\Circles\Tools\Model\NCWellKnownLink;
use OCA\Circles\Tools\Model\SimpleDataStore;
trait TNCWellKnown {
use TNCRequest;
static $WEBFINGER = '/.well-known/webfinger';
/**
* @param string $host
* @param string $subject
* @param string $rel
*
* @return SimpleDataStore
* @throws RequestNetworkException
* @throws WellKnownLinkNotFoundException
*/
public function getResourceData(string $host, string $subject, string $rel): SimpleDataStore {
$link = $this->getLink($host, $subject, $rel);
$request = new NCRequest('');
$request->basedOnUrl($link->getHref());
$request->addHeader('Accept', $link->getType());
$request->setFollowLocation(true);
$request->setLocalAddressAllowed(true);
$request->setTimeout(5);
$data = $this->retrieveJson($request);
return new SimpleDataStore($data);
}
/**
* @param string $host
* @param string $subject
* @param string $rel
*
* @return NCWellKnownLink
* @throws RequestNetworkException
* @throws WellKnownLinkNotFoundException
*/
public function getLink(string $host, string $subject, string $rel): NCWellKnownLink {
return $this->extractLink($rel, $this->getWebfinger($host, $subject));
}
/**
* @param string $host
* @param string $subject
* @param string $rel
*
* @return NCWebfinger
* @throws RequestNetworkException
*/
public function getWebfinger(string $host, string $subject, string $rel = ''): NCWebfinger {
$request = new NCRequest(self::$WEBFINGER);
$request->setHost($host);
$request->setProtocols(['https', 'http']);
$request->setFollowLocation(true);
$request->setLocalAddressAllowed(true);
$request->setTimeout(5);
$request->addParam('resource', $subject);
if ($rel !== '') {
$request->addParam('rel', $rel);
}
$result = $this->retrieveJson($request);
return new NCWebfinger($result);
}
/**
* @param string $rel
* @param NCWebfinger $webfinger
*
* @return NCWellKnownLink
* @throws WellKnownLinkNotFoundException
*/
public function extractLink(string $rel, NCWebfinger $webfinger): NCWellKnownLink {
foreach ($webfinger->getLinks() as $link) {
if ($link->getRel() === $rel) {
return $link;
}
}
throw new WellKnownLinkNotFoundException();
}
}

View file

@ -0,0 +1,258 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2022
* @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/>.
*
*/
namespace OCA\Circles\Tools\Traits;
use DateTime;
use Exception;
trait TStringTools {
use TArrayTools;
/**
* @param int $length
*
* @return string
*/
protected function token(int $length = 15): string {
$chars = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890';
$str = '';
$max = strlen($chars);
for ($i = 0; $i < $length; $i++) {
try {
$str .= $chars[random_int(0, $max - 2)];
} catch (Exception $e) {
}
}
return $str;
}
/**
* Generate uuid: 2b5a7a87-8db1-445f-a17b-405790f91c80
*
* @param int $length
*
* @return string
*/
protected function uuid(int $length = 0): string {
$uuid = sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff), mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
if ($length > 0) {
if ($length <= 16) {
$uuid = str_replace('-', '', $uuid);
}
$uuid = substr($uuid, 0, $length);
}
return $uuid;
}
/**
* @param string $line
* @param int $length
*
* @return string
*/
protected function cut(string $line, int $length): string {
if (strlen($line) < $length) {
return $line;
}
return substr($line, 0, $length - 5) . ' (..)';
}
/**
* @param string $str1
* @param string $str2
* @param bool $cs case sensitive ?
*
* @return string
*/
protected function commonPart(string $str1, string $str2, bool $cs = true): string {
for ($i = 0; $i < strlen($str1) && $i < strlen($str2); $i++) {
$chr1 = $str1[$i];
$chr2 = $str2[$i];
if (!$cs) {
$chr1 = strtolower($chr1);
$chr2 = strtolower($chr2);
}
if ($chr1 !== $chr2) {
break;
}
}
return substr($str1, 0, $i);
}
/**
* @param string $line
* @param array $params
*
* @return string
*/
protected function feedStringWithParams(string $line, array $params): string {
$ak = array_keys($params);
foreach ($ak as $k) {
$line = str_replace('{' . $k . '}', (string)$params[$k], $line);
}
return $line;
}
/**
* @param int $words
*
* @return string
*/
public function generateRandomSentence(int $words = 5): string {
$sentence = [];
for ($i = 0; $i < $words; $i++) {
$sentence[] = $this->generateRandomWord(rand(2, 12));
}
return implode(' ', $sentence);
}
/**
* @param int $length
*
* @return string
*/
public function generateRandomWord(int $length = 8): string {
$c = ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v'];
$v = ['a', 'e', 'i', 'o', 'u', 'y'];
$word = [];
for ($i = 0; $i <= ($length / 2); $i++) {
$word[] = $c[array_rand($c)];
$word[] = $v[array_rand($v)];
}
return implode('', $word);
}
/**
* @param int $bytes
*
* @return string
*/
public function humanReadable(int $bytes): string {
if ($bytes == 0) {
return '0.00 B';
}
$s = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
$e = floor(log($bytes, 1024));
return round($bytes / pow(1024, $e), 2) . ' ' . $s[$e];
}
/**
* @param int $first
* @param int $second
* @param bool $short
*
* @return string
* @throws Exception
*/
public function getDateDiff(
int $first,
int $second = 0,
bool $short = false,
array $words = []
): string {
if ($second === 0) {
$first = time() - $first;
$second = time();
}
$f = new DateTime('@' . $first);
$s = new DateTime('@' . $second);
$duration = $second - $first;
if ($short) {
$minutes = $this->get('minutes', $words, 'M');
$hours = $this->get('hours', $words, 'H');
$days = $this->get('days', $words, 'D');
if ($duration < 60) {
return $f->diff($s)->format('<1' . $minutes);
}
if ($duration < 3600) {
return $f->diff($s)->format('%i' . $minutes);
}
if ($duration < 86400) {
return $f->diff($s)->format('%h' . $hours . ', %i' . $minutes);
}
return $f->diff($s)->format('%a' . $days . ', %h' . $hours . ', %i' . $minutes);
}
$seconds = $this->get('seconds', $words, 'seconds');
$minutes = $this->get('minutes', $words, 'minutes');
$hours = $this->get('hours', $words, 'hours');
$days = $this->get('days', $words, 'days');
if ($duration < 60) {
return $f->diff($s)->format('%s ' . $seconds);
}
if ($duration < 3600) {
return $f->diff($s)->format('%i ' . $minutes . ' and %s ' . $seconds);
}
if ($duration < 86400) {
return $f->diff($s)->format('%h ' . $hours . ', %i ' . $minutes . ' and %s ' . $seconds);
}
return $f->diff($s)->format(
'%a ' . $days .
', %h ' . $hours .
', %i ' . $minutes .
' and %s ' . $seconds
);
}
}