Initial QSfera import
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2018 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers\Asserts;
|
||||
|
||||
use PHPUnit\Framework\Assert;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* WebDAV related asserts
|
||||
*/
|
||||
class WebDav extends Assert {
|
||||
/**
|
||||
*
|
||||
* @param string|null $element exception|message|reason
|
||||
* @param string|null $expectedValue
|
||||
* @param array|null $responseXmlArray
|
||||
* @param string|null $extraErrorText
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function assertDavResponseElementIs(
|
||||
?string $element,
|
||||
?string $expectedValue,
|
||||
?array $responseXmlArray,
|
||||
?string $extraErrorText = ''
|
||||
): void {
|
||||
if ($extraErrorText !== '') {
|
||||
$extraErrorText = $extraErrorText . " ";
|
||||
}
|
||||
self::assertArrayHasKey(
|
||||
'value',
|
||||
$responseXmlArray,
|
||||
$extraErrorText . "responseXml does not have key 'value'"
|
||||
);
|
||||
if ($element === "exception") {
|
||||
$result = $responseXmlArray['value'][0]['value'];
|
||||
} elseif ($element === "message") {
|
||||
$result = $responseXmlArray['value'][1]['value'];
|
||||
} elseif ($element === "reason") {
|
||||
$result = $responseXmlArray['value'][3]['value'];
|
||||
} else {
|
||||
self::fail(
|
||||
__METHOD__ . " element must be one of exception, response or reason. But '$element' was passed in."
|
||||
);
|
||||
}
|
||||
|
||||
self::assertEquals(
|
||||
$expectedValue,
|
||||
$result,
|
||||
__METHOD__ . " " . $extraErrorText . "Expected '$expectedValue' in element $element got '$result'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param SimpleXMLElement $responseXmlObject
|
||||
* @param array|null $expectedShareTypes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function assertResponseContainsShareTypes(
|
||||
SimpleXMLElement $responseXmlObject,
|
||||
?array $expectedShareTypes
|
||||
): void {
|
||||
foreach ($expectedShareTypes as $row) {
|
||||
$xmlPart = $responseXmlObject->xpath(
|
||||
"//d:prop/oc:share-types/oc:share-type[.=" . $row[0] . "]"
|
||||
);
|
||||
self::assertNotEmpty(
|
||||
$xmlPart,
|
||||
"cannot find share-type '" . $row[0] . "'"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Niraj Acharya <niraj@jankaritech.com>
|
||||
* @copyright Copyright (c) 2024 Niraj Acharya niraj@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A helper class for managing Auth App API requests
|
||||
*/
|
||||
class AuthAppHelper {
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getAuthAppEndpoint(): string {
|
||||
return "/auth-app/tokens";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public static function listAllAppAuthTokensForUser(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password
|
||||
): ResponseInterface {
|
||||
$url = $baseUrl . self::getAuthAppEndpoint();
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$url,
|
||||
null,
|
||||
"GET",
|
||||
$user,
|
||||
$password,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param array $params
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public static function createAppAuthToken(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
array $params,
|
||||
): ResponseInterface {
|
||||
$url = $baseUrl . self::getAuthAppEndpoint() . "?"
|
||||
. http_build_query($params);
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$url,
|
||||
null,
|
||||
"POST",
|
||||
$user,
|
||||
$password,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $token
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public static function deleteAppAuthToken(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $token
|
||||
): ResponseInterface {
|
||||
$url = $baseUrl . self::getAuthAppEndpoint() . "?token=$token";
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$url,
|
||||
null,
|
||||
"DELETE",
|
||||
$user,
|
||||
$password,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Sajan Gurung <sajan@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Context\Environment\InitializedContextEnvironment;
|
||||
use Behat\Behat\Context\Exception\ContextNotFoundException;
|
||||
use Behat\Behat\Hook\Scope\ScenarioScope;
|
||||
|
||||
/**
|
||||
* Helper for Behat environment configuration
|
||||
*
|
||||
*/
|
||||
class BehatHelper {
|
||||
/**
|
||||
* @param ScenarioScope $scope
|
||||
* @param InitializedContextEnvironment $environment
|
||||
* @param string $class
|
||||
*
|
||||
* @return Context
|
||||
*/
|
||||
public static function getContext(
|
||||
ScenarioScope $scope,
|
||||
InitializedContextEnvironment $environment,
|
||||
string $class
|
||||
): Context {
|
||||
try {
|
||||
return $environment->getContext($class);
|
||||
} catch (ContextNotFoundException $e) {
|
||||
$context = new $class();
|
||||
$environment->registerContext($context);
|
||||
if (\method_exists($context, 'before')) {
|
||||
$context->before($scope);
|
||||
}
|
||||
return $environment->getContext($class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Sajan Gurung <sajan@jankaritech.com>
|
||||
* @copyright Copyright (c) 2024 Sajan Gurung sajan@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TestHelpers\OcConfigHelper;
|
||||
|
||||
/**
|
||||
* A helper class for running КуСфера CLI commands
|
||||
*/
|
||||
class CliHelper {
|
||||
/**
|
||||
* @param array $body
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function runCommand(array $body): ResponseInterface {
|
||||
$url = OcConfigHelper::getWrapperUrl() . "/command";
|
||||
return OcConfigHelper::sendRequest($url, "POST", \json_encode($body));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Amrita Shrestha <amrita@jankaritech.com>
|
||||
* @copyright Copyright (c) 2024 Amrita Shrestha amrita@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A helper class for managing wopi requests
|
||||
*/
|
||||
class CollaborationHelper {
|
||||
/**
|
||||
* @param string $fileId
|
||||
* @param string $app
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string|null $viewMode
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function sendPOSTRequestToAppOpen(
|
||||
string $fileId,
|
||||
string $app,
|
||||
string $username,
|
||||
string $password,
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
?string $viewMode = null,
|
||||
): ResponseInterface {
|
||||
$url = $baseUrl . "/app/open?app_name=$app&file_id=$fileId";
|
||||
if ($viewMode) {
|
||||
$url .= "&view_mode=$viewMode";
|
||||
}
|
||||
|
||||
return HttpRequestHelper::post(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$username,
|
||||
$password,
|
||||
['Content-Type' => 'application/json']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $parentContainerId
|
||||
* @param string $file
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function createFile(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password,
|
||||
string $parentContainerId,
|
||||
string $file,
|
||||
?array $headers = null
|
||||
): ResponseInterface {
|
||||
$url = $baseUrl . "/app/new?parent_container_id=$parentContainerId&filename=$file";
|
||||
return HttpRequestHelper::post(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Prajwol Amatya <prajwol@jankaritech.com>
|
||||
* @copyright Copyright (c) 2023 Prajwol Amatya prajwol@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A helper class for managing emails
|
||||
*/
|
||||
class EmailHelper {
|
||||
/**
|
||||
* @param string $emailAddress
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMailBoxFromEmail(string $emailAddress): string {
|
||||
return explode("@", $emailAddress)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host and port where Email messages can be read and deleted
|
||||
* by the test runner.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getLocalEmailUrl(): string {
|
||||
$localEmailHost = self::getLocalEmailHost();
|
||||
$emailPort = \getenv('EMAIL_PORT');
|
||||
if ($emailPort === false) {
|
||||
$emailPort = "9000";
|
||||
}
|
||||
return "http://$localEmailHost:$emailPort";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host name or address of the Email server as seen from the
|
||||
* point of view of the system-under-test.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getEmailHost(): string {
|
||||
$emailHost = \getenv('EMAIL_HOST');
|
||||
if ($emailHost === false) {
|
||||
$emailHost = "127.0.0.1";
|
||||
}
|
||||
return $emailHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host name or address of the Email server as seen from the
|
||||
* point of view of the test runner.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getLocalEmailHost(): string {
|
||||
$localEmailHost = \getenv('LOCAL_EMAIL_HOST');
|
||||
if ($localEmailHost === false) {
|
||||
$localEmailHost = self::getEmailHost();
|
||||
}
|
||||
return $localEmailHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns general response information about the provided mailbox
|
||||
* A mailbox is created automatically in InBucket for every unique email sender|receiver
|
||||
*
|
||||
* @param string $mailBox
|
||||
* @param string|null $xRequestId
|
||||
*
|
||||
* @return array
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getMailBoxInformation(string $mailBox, ?string $xRequestId = null): array {
|
||||
$response = HttpRequestHelper::get(
|
||||
self::getLocalEmailUrl() . "/api/v1/mailbox/" . $mailBox,
|
||||
$xRequestId,
|
||||
null,
|
||||
null,
|
||||
['Content-Type' => 'application/json']
|
||||
);
|
||||
return \json_decode($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* returns body content of a specific email (mailBox) with email ID (mailbox Id)
|
||||
*
|
||||
* @param string $mailBox
|
||||
* @param string $mailboxId
|
||||
* @param string|null $xRequestId
|
||||
*
|
||||
* @return object
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getBodyOfAnEmailById(
|
||||
string $mailBox,
|
||||
string $mailboxId,
|
||||
?string $xRequestId = null
|
||||
): object {
|
||||
$response = HttpRequestHelper::get(
|
||||
self::getLocalEmailUrl() . "/api/v1/mailbox/" . $mailBox . "/" . $mailboxId,
|
||||
$xRequestId,
|
||||
null,
|
||||
null,
|
||||
['Content-Type' => 'application/json']
|
||||
);
|
||||
return \json_decode($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the body of the last received email for the provided receiver
|
||||
*
|
||||
* @param string $emailAddress
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getBodyOfLastEmail(
|
||||
string $emailAddress,
|
||||
string $xRequestId,
|
||||
): string {
|
||||
$mailBox = self::getMailBoxFromEmail($emailAddress);
|
||||
$emails = self::getMailboxInformation($mailBox, $xRequestId);
|
||||
if (!empty($emails)) {
|
||||
$emailId = \array_pop($emails)->id;
|
||||
$response = self::getBodyOfAnEmailById($mailBox, $emailId, $xRequestId);
|
||||
$body = \str_replace(
|
||||
"\r\n",
|
||||
"\n",
|
||||
\quoted_printable_decode($response->body->text . "\n" . $response->body->html)
|
||||
);
|
||||
return $body;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all the emails for the provided mailbox
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $mailBox
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function deleteAllEmails(
|
||||
string $url,
|
||||
string $mailBox,
|
||||
string $xRequestId,
|
||||
): ResponseInterface {
|
||||
return HttpRequestHelper::delete(
|
||||
$url . "/api/v1/mailbox/" . $mailBox,
|
||||
$xRequestId,
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Sajan Gurung <sajan@jankaritech.com>
|
||||
* @copyright Copyright (c) 2023, ownCloud GmbH
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Helper for logging HTTP requests and responses
|
||||
*/
|
||||
class HttpLogger {
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getLogDir(): string {
|
||||
return __DIR__ . '/../logs';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getFailedLogPath(): string {
|
||||
return self::getLogDir() . "/failed.log";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getScenarioLogPath(): string {
|
||||
return self::getLogDir() . "/scenario.log";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $logFile
|
||||
* @param string $logMessage
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function writeLog(string $logFile, string $logMessage): void {
|
||||
$file = \fopen($logFile, 'a+') or die('Cannot open file: ' . $logFile);
|
||||
\fwrite($file, $logMessage);
|
||||
\fclose($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function logRequest(RequestInterface $request): void {
|
||||
$method = $request->getMethod();
|
||||
$path = $request->getUri()->getPath();
|
||||
$query = $request->getUri()->getQuery();
|
||||
$body = $request->getBody();
|
||||
|
||||
$headers = "";
|
||||
foreach ($request->getHeaders() as $key => $value) {
|
||||
$headers = $key . ": " . $value[0] . "\n";
|
||||
}
|
||||
|
||||
$logMessage = "\t\t_______________________________________________________________________\n\n";
|
||||
$logMessage .= "\t\t==> REQUEST [" . self::getCurrentDateTime() . "]\n";
|
||||
$logMessage .= "\t\t$method $path\n";
|
||||
$logMessage .= $query ? "\t\tQUERY: $query\n" : "";
|
||||
$logMessage .= "\t\t$headers";
|
||||
|
||||
if ($body->getSize() > 0) {
|
||||
$logMessage .= "\t\t==> REQ BODY\n";
|
||||
$logMessage .= "\t\t$body\n";
|
||||
}
|
||||
$logMessage .= "\n";
|
||||
self::writeLog(self::getScenarioLogPath(), $logMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function logResponse(ResponseInterface $response): void {
|
||||
$statusCode = $response->getStatusCode();
|
||||
$statusMessage = $response->getReasonPhrase();
|
||||
$body = $response->getBody();
|
||||
$headers = "";
|
||||
|
||||
foreach ($response->getHeaders() as $key => $value) {
|
||||
$headers = $key . ": " . $value[0] . "\n";
|
||||
}
|
||||
|
||||
$logMessage = "\t\t<== RESPONSE [" . self::getCurrentDateTime() . "]\n";
|
||||
$logMessage .= "\t\t$statusCode $statusMessage\n";
|
||||
$logMessage .= "\t\t$headers";
|
||||
|
||||
if ($body->getSize() > 0) {
|
||||
$logMessage .= "\t\t<== RES BODY\n";
|
||||
foreach (\explode("\n", \strval($body)) as $line) {
|
||||
$logMessage .= "\t\t$line\n";
|
||||
}
|
||||
}
|
||||
// rewind the body stream so that later code can read from the start.
|
||||
$response->getBody()->rewind();
|
||||
|
||||
$logMessage = \rtrim($logMessage) . "\n\n";
|
||||
self::writeLog(self::getScenarioLogPath(), $logMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current date and time in format: 1999-01-31T23:01:59
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getCurrentDateTime(): string {
|
||||
return date('Y-m-d\TG:i:s');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,766 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use SimpleXMLElement;
|
||||
use Sabre\Xml\LibXMLException;
|
||||
use Sabre\Xml\Reader;
|
||||
use GuzzleHttp\Pool;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Helper for HTTP requests
|
||||
*/
|
||||
class HttpRequestHelper {
|
||||
public const HTTP_TOO_EARLY = 425;
|
||||
public const HTTP_CONFLICT = 409;
|
||||
|
||||
/**
|
||||
* Some systems-under-test do async post-processing of operations like upload,
|
||||
* move, etc. If a client does a request on the resource before the post-processing
|
||||
* is finished, then the server should return HTTP_TOO_EARLY "425". Clients are
|
||||
* expected to retry the request "some time later" (tm).
|
||||
*
|
||||
* On such systems, when HTTP_TOO_EARLY status is received, the test code will
|
||||
* retry the request at 1-second intervals until either some other HTTP status
|
||||
* is received or the retry-limit is reached.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function numRetriesOnHttpTooEarly(): int {
|
||||
// Currently reva and КуСфера may return HTTP_TOO_EARLY
|
||||
// So try up to 10 times before giving up.
|
||||
return STANDARD_RETRY_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $method
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param mixed $body
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param bool $stream Set to true to stream a response rather
|
||||
* than download it all up-front.
|
||||
* @param int|null $timeout
|
||||
* @param Client|null $client
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function sendRequestOnce(
|
||||
string $url,
|
||||
?string $xRequestId = null,
|
||||
?string $method = 'GET',
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = null,
|
||||
$body = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
bool $stream = false,
|
||||
?int $timeout = 0,
|
||||
?Client $client = null,
|
||||
): ResponseInterface {
|
||||
$bearerToken = null;
|
||||
if (TokenHelper::useBearerToken() && $user && $user !== 'public') {
|
||||
$bearerToken = TokenHelper::getTokens($user, $password, $url)['access_token'];
|
||||
// check token is still valid
|
||||
$parsedUrl = parse_url($url);
|
||||
$baseUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host'];
|
||||
$baseUrl .= isset($parsedUrl['port']) ? ':' . $parsedUrl['port'] : '';
|
||||
$testUrl = $baseUrl . "/graph/v1.0/me";
|
||||
if (OcHelper::isTestingOnReva()) {
|
||||
$url = $baseUrl . "/ocs/v2.php/cloud/users/$user";
|
||||
}
|
||||
// check token validity with a GET request
|
||||
$c = self::createClient(
|
||||
$user,
|
||||
$password,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream,
|
||||
$timeout,
|
||||
$bearerToken
|
||||
);
|
||||
$testReq = self::createRequest($testUrl, $xRequestId, 'GET');
|
||||
try {
|
||||
$testRes = $c->send($testReq);
|
||||
} catch (RequestException $ex) {
|
||||
$testRes = $ex->getResponse();
|
||||
if ($testRes && $testRes->getStatusCode() === Response::HTTP_UNAUTHORIZED) {
|
||||
// token is invalid or expired, get a new one
|
||||
echo "[INFO] Bearer token expired or invalid, getting a new one...\n";
|
||||
TokenHelper::clearAllTokens();
|
||||
$bearerToken = TokenHelper::getTokens($user, $password, $url)['access_token'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($client === null) {
|
||||
$client = self::createClient(
|
||||
$user,
|
||||
$password,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream,
|
||||
$timeout,
|
||||
$bearerToken
|
||||
);
|
||||
}
|
||||
|
||||
if (WebdavHelper::isDAVRequest($url) && \str_starts_with($url, OcHelper::getServerUrl())) {
|
||||
$urlHasRemotePhp = \str_contains($url, 'remote.php');
|
||||
if (!WebDavHelper::withRemotePhp() && $urlHasRemotePhp) {
|
||||
throw new Exception("remote.php is disabled but found in the URL: $url");
|
||||
}
|
||||
if (WebDavHelper::withRemotePhp() && !$urlHasRemotePhp) {
|
||||
throw new Exception("remote.php is enabled but not found in the URL: $url");
|
||||
}
|
||||
|
||||
if ($headers && \array_key_exists("Destination", $headers)) {
|
||||
if (!WebDavHelper::withRemotePhp() && $urlHasRemotePhp) {
|
||||
throw new Exception("remote.php is disabled but found in the URL: $url");
|
||||
}
|
||||
if (WebDavHelper::withRemotePhp() && !$urlHasRemotePhp) {
|
||||
throw new Exception("remote.php is enabled but not found in the URL: $url");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$request = self::createRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$method,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
|
||||
if ((\getenv('DEBUG_ACCEPTANCE_REQUESTS') !== false) || (\getenv('DEBUG_ACCEPTANCE_API_CALLS') !== false)) {
|
||||
$debugRequests = true;
|
||||
} else {
|
||||
$debugRequests = false;
|
||||
}
|
||||
|
||||
if ($debugRequests) {
|
||||
self::debugRequest($request, $user, $password);
|
||||
}
|
||||
|
||||
// The exceptions that might happen here include:
|
||||
// ConnectException - in that case there is no response. Don't catch the exception.
|
||||
// RequestException - if there is something in the response then pass it back.
|
||||
// Otherwise, re-throw the exception.
|
||||
// GuzzleException - something else unexpected happened. Don't catch the exception.
|
||||
try {
|
||||
$response = $client->send($request);
|
||||
} catch (RequestException $ex) {
|
||||
$response = $ex->getResponse();
|
||||
|
||||
//if the response was null for some reason do not return it but re-throw
|
||||
if ($response === null) {
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
HttpLogger::logResponse($response);
|
||||
|
||||
// wait for post-processing to finish if applicable
|
||||
if (WebdavHelper::isDAVRequest($url)
|
||||
&& \str_starts_with($url, OcHelper::getServerUrl())
|
||||
&& \in_array($method, ["PUT", "MOVE", "COPY", "MKCOL"])
|
||||
&& \in_array($response->getStatusCode(), [Response::HTTP_CREATED, Response::HTTP_NO_CONTENT])
|
||||
&& OcConfigHelper::getPostProcessingDelay() === 0
|
||||
) {
|
||||
if (\in_array($method, ["MOVE", "COPY"])) {
|
||||
$url = $headers['Destination'];
|
||||
}
|
||||
WebDavHelper::waitForPostProcessingToFinish(
|
||||
$url,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $method
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param mixed $body
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param bool $stream Set to true to stream a response rather
|
||||
* than download it all up-front.
|
||||
* @param int|null $timeout
|
||||
* @param Client|null $client
|
||||
* @param bool|null $isGivenStep
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function sendRequest(
|
||||
?string $url,
|
||||
?string $xRequestId,
|
||||
?string $method = 'GET',
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = null,
|
||||
$body = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
bool $stream = false,
|
||||
?int $timeout = 0,
|
||||
?Client $client = null,
|
||||
?bool $isGivenStep = false
|
||||
): ResponseInterface {
|
||||
if ((\getenv('DEBUG_ACCEPTANCE_RESPONSES') !== false) || (\getenv('DEBUG_ACCEPTANCE_API_CALLS') !== false)) {
|
||||
$debugResponses = true;
|
||||
} else {
|
||||
$debugResponses = false;
|
||||
}
|
||||
|
||||
$sendRetryLimit = self::numRetriesOnHttpTooEarly();
|
||||
$sendCount = 0;
|
||||
$sendExceptionHappened = false;
|
||||
do {
|
||||
$response = self::sendRequestOnce(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$method,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream,
|
||||
$timeout,
|
||||
$client,
|
||||
);
|
||||
|
||||
if ($response->getStatusCode() >= 400
|
||||
&& $response->getStatusCode() !== self::HTTP_TOO_EARLY
|
||||
&& $response->getStatusCode() !== self::HTTP_CONFLICT
|
||||
) {
|
||||
$sendExceptionHappened = true;
|
||||
}
|
||||
|
||||
if ($debugResponses) {
|
||||
self::debugResponse($response);
|
||||
}
|
||||
$sendCount = $sendCount + 1;
|
||||
// Here we check if the response has status code 425 or is a 409 gotten from a Given step
|
||||
// HTTP_TOO_EARLY (425) can happen if async processing of a previous request is still happening.
|
||||
// For example, if a test uploads a file and then immediately tries to download it.
|
||||
// HTTP_CONFLICT (409) can happen if the user has just been created in the previous step.
|
||||
// The OCS API might not "realize" yet that the user exists. A folder creation (MKCOL) or maybe even
|
||||
// a file upload might return 409.
|
||||
// In all these cases we can try the API request again after a short time.
|
||||
$loopAgain = !$sendExceptionHappened && ($response->getStatusCode() === self::HTTP_TOO_EARLY ||
|
||||
($response->getStatusCode() === self::HTTP_CONFLICT && $isGivenStep)) &&
|
||||
$sendCount <= $sendRetryLimit;
|
||||
if ($loopAgain) {
|
||||
// we need to repeat the send request, because we got HTTP_TOO_EARLY or HTTP_CONFLICT
|
||||
// wait 1 second before sending again, to give the server some time
|
||||
// to finish whatever post-processing it might be doing.
|
||||
echo "[INFO] Received '" . $response->getStatusCode() .
|
||||
"' status code, retrying request ($sendCount)...\n";
|
||||
\sleep(1);
|
||||
}
|
||||
} while ($loopAgain);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print details about the request.
|
||||
*
|
||||
* @param RequestInterface|null $request
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function debugRequest(?RequestInterface $request, ?string $user, ?string $password): void {
|
||||
print("### AUTH: $user:$password\n");
|
||||
print("### REQUEST: " . $request->getMethod() . " " . $request->getUri() . "\n");
|
||||
self::printHeaders($request->getHeaders());
|
||||
self::printBody($request->getBody());
|
||||
print("\n### END REQUEST\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Print details about the response.
|
||||
*
|
||||
* @param ResponseInterface|null $response
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function debugResponse(?ResponseInterface $response): void {
|
||||
print("### RESPONSE\n");
|
||||
print("Status: " . $response->getStatusCode() . "\n");
|
||||
self::printHeaders($response->getHeaders());
|
||||
self::printBody($response->getBody());
|
||||
print("\n### END RESPONSE\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Print details about the headers.
|
||||
*
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function printHeaders(?array $headers): void {
|
||||
if ($headers) {
|
||||
print("Headers:\n");
|
||||
foreach ($headers as $header => $value) {
|
||||
if (\is_array($value)) {
|
||||
print($header . ": " . \implode(', ', $value) . "\n");
|
||||
} else {
|
||||
print($header . ": " . $value . "\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print("Headers: none\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print details about the body.
|
||||
*
|
||||
* @param StreamInterface|null $body
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function printBody(?StreamInterface $body): void {
|
||||
print("Body:\n");
|
||||
\var_dump($body->getContents());
|
||||
// Rewind the stream so that later code can read from the start.
|
||||
$body->rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the requests to the server in parallel.
|
||||
* This function takes an array of requests and an optional client.
|
||||
* It will send all the requests to the server using the Pool object in guzzle.
|
||||
*
|
||||
* @param array|null $requests
|
||||
* @param Client|null $client
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function sendBatchRequest(
|
||||
?array $requests,
|
||||
?Client $client
|
||||
): array {
|
||||
return Pool::batch($client, $requests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Guzzle Client
|
||||
* This creates a client object that can be used later to send a request object(s)
|
||||
*
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param bool $stream Set to true to stream a response rather
|
||||
* than download it all up-front.
|
||||
* @param int|null $timeout
|
||||
* @param string|null $bearerToken
|
||||
*
|
||||
* @return Client
|
||||
*/
|
||||
public static function createClient(
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
?bool $stream = false,
|
||||
?int $timeout = 0,
|
||||
?string $bearerToken = null
|
||||
): Client {
|
||||
$options = [];
|
||||
if ($bearerToken !== null) {
|
||||
$options['headers']['Authorization'] = 'Bearer ' . $bearerToken;
|
||||
} elseif ($user !== null) {
|
||||
$options['auth'] = [$user, $password];
|
||||
}
|
||||
if ($config !== null) {
|
||||
$options['config'] = $config;
|
||||
}
|
||||
if ($cookies !== null) {
|
||||
$options['cookies'] = $cookies;
|
||||
}
|
||||
$options['stream'] = $stream;
|
||||
$options['verify'] = false;
|
||||
$options['timeout'] = $timeout ?: self::getRequestTimeout();
|
||||
return new Client($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTTP request based on given parameters.
|
||||
* This creates a RequestInterface object that can be used with a client to send a request.
|
||||
* This enables us to create multiple requests in advance so that we can send them to the server at once in parallel.
|
||||
*
|
||||
* @param string|null $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $method
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param string|array $body either the actual string to send in the body,
|
||||
* or an array of key-value pairs to be converted
|
||||
* into a body with http_build_query.
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public static function createRequest(
|
||||
?string $url,
|
||||
?string $xRequestId = '',
|
||||
?string $method = 'GET',
|
||||
?array $headers = null,
|
||||
$body = null
|
||||
): RequestInterface {
|
||||
if ($headers === null) {
|
||||
$headers = [];
|
||||
}
|
||||
if ($xRequestId !== '') {
|
||||
$headers['X-Request-ID'] = $xRequestId;
|
||||
}
|
||||
if (\is_array($body)) {
|
||||
// When creating the client, it is possible to set 'form_params' and
|
||||
// the Client constructor sorts out doing this http_build_query stuff.
|
||||
// But 'new Request' does not have the flexibility to do that.
|
||||
// So we need to do it here.
|
||||
$body = \http_build_query($body, '', '&');
|
||||
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
|
||||
$request = new Request(
|
||||
$method,
|
||||
$url,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
HttpLogger::logRequest($request);
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* same as HttpRequestHelper::sendRequest() but with "GET" as method
|
||||
*
|
||||
* @param string|null $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param mixed $body
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param boolean $stream
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @see HttpRequestHelper::sendRequest()
|
||||
*/
|
||||
public static function get(
|
||||
?string $url,
|
||||
?string $xRequestId,
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = null,
|
||||
$body = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
?bool $stream = false
|
||||
): ResponseInterface {
|
||||
return self::sendRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
'GET',
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* same as HttpRequestHelper::sendRequest() but with "POST" as method
|
||||
*
|
||||
* @param string|null $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param mixed $body
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param boolean $stream
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @see HttpRequestHelper::sendRequest()
|
||||
*/
|
||||
public static function post(
|
||||
?string $url,
|
||||
?string $xRequestId,
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = null,
|
||||
$body = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
?bool $stream = false
|
||||
): ResponseInterface {
|
||||
return self::sendRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
'POST',
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* same as HttpRequestHelper::sendRequest() but with "PUT" as method
|
||||
*
|
||||
* @param string|null $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param mixed $body
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param boolean $stream
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @see HttpRequestHelper::sendRequest()
|
||||
*/
|
||||
public static function put(
|
||||
?string $url,
|
||||
?string $xRequestId,
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = null,
|
||||
$body = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
?bool $stream = false
|
||||
): ResponseInterface {
|
||||
return self::sendRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
'PUT',
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* same as HttpRequestHelper::sendRequest() but with "DELETE" as method
|
||||
*
|
||||
* @param string|null $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param mixed $body
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param boolean $stream
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @see HttpRequestHelper::sendRequest()
|
||||
*
|
||||
*/
|
||||
public static function delete(
|
||||
?string $url,
|
||||
?string $xRequestId,
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = null,
|
||||
$body = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
?bool $stream = false
|
||||
): ResponseInterface {
|
||||
return self::sendRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
'DELETE',
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the response as XML and returns a SimpleXMLElement with these
|
||||
* registered namespaces:
|
||||
* | prefix | namespace |
|
||||
* | d | DAV: |
|
||||
* | oc | http://owncloud.org/ns |
|
||||
* | ocs | http://open-collaboration-services.org/ns |
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
* @param string|null $exceptionText text to put at the front of exception messages
|
||||
*
|
||||
* @return SimpleXMLElement
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getResponseXml(ResponseInterface $response, ?string $exceptionText = ''): SimpleXMLElement {
|
||||
// rewind just to make sure we can reparse it in case it was parsed already...
|
||||
$response->getBody()->rewind();
|
||||
$contents = $response->getBody()->getContents();
|
||||
try {
|
||||
$responseXmlObject = new SimpleXMLElement($contents);
|
||||
$responseXmlObject->registerXPathNamespace(
|
||||
'ocs',
|
||||
'http://open-collaboration-services.org/ns'
|
||||
);
|
||||
$responseXmlObject->registerXPathNamespace(
|
||||
'oc',
|
||||
'http://owncloud.org/ns'
|
||||
);
|
||||
$responseXmlObject->registerXPathNamespace(
|
||||
'd',
|
||||
'DAV:'
|
||||
);
|
||||
return $responseXmlObject;
|
||||
} catch (Exception $e) {
|
||||
if ($exceptionText !== '') {
|
||||
$exceptionText = $exceptionText . ' ';
|
||||
}
|
||||
if ($contents === '') {
|
||||
throw new Exception($exceptionText . "Received empty response where XML was expected");
|
||||
}
|
||||
$message = $exceptionText . "Exception parsing response body: \"" . $contents . "\"";
|
||||
throw new Exception($message, 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parses the body content of $response and returns an array representing the XML
|
||||
* This function returns an array with the following three elements:
|
||||
* * name - The root element name.
|
||||
* * value - The value for the root element.
|
||||
* * attributes - An array of attributes.
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function parseResponseAsXml(ResponseInterface $response): array {
|
||||
// rewind so that we can reparse it if it was parsed already
|
||||
$response->getBody()->rewind();
|
||||
$body = $response->getBody()->getContents();
|
||||
$parsedResponse = [];
|
||||
if ($body && \substr($body, 0, 1) === '<') {
|
||||
try {
|
||||
$reader = new Reader();
|
||||
$reader->xml($body);
|
||||
$parsedResponse = $reader->parse();
|
||||
} catch (LibXMLException $e) {
|
||||
// Sometimes the body can be a real page of HTML and text.
|
||||
// So it may not be a complete ordinary piece of XML.
|
||||
// The XML parse might fail with an exception message like:
|
||||
// Opening and ending tag mismatch: link line 31 and head.
|
||||
}
|
||||
}
|
||||
return $parsedResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public static function getRequestTimeout(): int {
|
||||
$timeout = \getenv("REQUEST_TIMEOUT");
|
||||
return (int)$timeout ?: HTTP_REQUEST_TIMEOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns json decoded body content of a json response as an object
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getJsonDecodedResponseBodyContent(ResponseInterface $response): mixed {
|
||||
return json_decode($response->getBody()->getContents(), null, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function sendScenarioLineReferencesInXRequestId(): bool {
|
||||
return (\getenv("SEND_SCENARIO_LINE_REFERENCES") === "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getXRequestIdRegex(): string {
|
||||
if (self::sendScenarioLineReferencesInXRequestId()) {
|
||||
return '/^[a-zA-Z]+\/[a-zA-Z]+\.feature:\d+(-\d+)?$/';
|
||||
}
|
||||
$host = gethostname();
|
||||
return "/^$host\/.*$/";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Sajan Gurung <sajan@jankaritech.com>
|
||||
* @copyright Copyright (c) 2023 Sajan Gurung sajan@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A helper class for configuring КуСфера server
|
||||
*/
|
||||
class OcConfigHelper {
|
||||
public static $postProcessingDelay = 0;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public static function getPostProcessingDelay(): int {
|
||||
return self::$postProcessingDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $postProcessingDelay
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setPostProcessingDelay(string $postProcessingDelay): void {
|
||||
// extract number from string
|
||||
$delay = (int) filter_var($postProcessingDelay, FILTER_SANITIZE_NUMBER_INT);
|
||||
self::$postProcessingDelay = $delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param ?string $body
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function sendRequest(
|
||||
string $url,
|
||||
string $method,
|
||||
?string $body = ""
|
||||
): ResponseInterface {
|
||||
$client = HttpRequestHelper::createClient();
|
||||
$request = new Request(
|
||||
$method,
|
||||
$url,
|
||||
[],
|
||||
$body
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $client->send($request);
|
||||
} catch (ConnectException $e) {
|
||||
throw new \Error(
|
||||
"Cannot connect to the ocwrapper at the moment,"
|
||||
. "make sure that ocwrapper is running before proceeding with the test run.\n"
|
||||
. $e->getMessage()
|
||||
);
|
||||
} catch (GuzzleException $ex) {
|
||||
$response = $ex->getResponse();
|
||||
|
||||
if ($response === null) {
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getWrapperUrl(): string {
|
||||
$url = \getenv("OC_WRAPPER_URL");
|
||||
if ($url === false) {
|
||||
$url = "http://localhost:5200";
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $envs
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function reConfigureOc(array $envs): ResponseInterface {
|
||||
$url = self::getWrapperUrl() . "/config";
|
||||
return self::sendRequest($url, "PUT", \json_encode($envs));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function rollbackOc(): ResponseInterface {
|
||||
$url = self::getWrapperUrl() . "/rollback";
|
||||
return self::sendRequest($url, "DELETE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function stopQsfera(): ResponseInterface {
|
||||
$url = self::getWrapperUrl() . "/stop";
|
||||
return self::sendRequest($url, "POST");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function startQsfera(): ResponseInterface {
|
||||
$url = self::getWrapperUrl() . "/start";
|
||||
return self::sendRequest($url, "POST");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2020 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
/**
|
||||
* Class StorageDriver
|
||||
*
|
||||
* @package TestHelpers
|
||||
*/
|
||||
abstract class StorageDriver {
|
||||
public const DECOMPOSED = "DECOMPOSED";
|
||||
public const EOS = "EOS";
|
||||
public const OWNCLOUD = "OWNCLOUD";
|
||||
public const DECOMPOSEDS3 = "DECOMPOSEDS3";
|
||||
public const POSIX = "POSIX";
|
||||
}
|
||||
|
||||
/**
|
||||
* Class OcHelper
|
||||
*
|
||||
* Helper functions that are needed to run tests on КуСфера server
|
||||
*
|
||||
* @package TestHelpers
|
||||
*/
|
||||
class OcHelper {
|
||||
public const STORAGE_DRIVERS = [
|
||||
StorageDriver::DECOMPOSED,
|
||||
StorageDriver::EOS,
|
||||
StorageDriver::OWNCLOUD,
|
||||
StorageDriver::DECOMPOSEDS3,
|
||||
StorageDriver::POSIX
|
||||
];
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getServerUrl(): string {
|
||||
if (\getenv('TEST_SERVER_URL')) {
|
||||
return \getenv('TEST_SERVER_URL');
|
||||
}
|
||||
return 'https://localhost:9200';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getFederatedServerUrl(): string {
|
||||
if (\getenv('TEST_SERVER_FED_URL')) {
|
||||
return \getenv('TEST_SERVER_FED_URL');
|
||||
}
|
||||
return 'https://localhost:10200';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getCollaborationServiceUrl(): string {
|
||||
if (\getenv("COLLABORATION_SERVICE_URL")) {
|
||||
return \getenv("COLLABORATION_SERVICE_URL");
|
||||
}
|
||||
return "http://localhost:9300";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isTestingOnReva(): bool {
|
||||
return (\getenv("TEST_REVA") === "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUsingPreparedLdapUsers(): bool {
|
||||
return (\getenv("USE_PREPARED_LDAP_USERS") === "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|string false if no command given or the command as string
|
||||
*/
|
||||
public static function getDeleteUserDataCommand() {
|
||||
$cmd = \getenv("DELETE_USER_DATA_CMD");
|
||||
if ($cmd === false || \trim($cmd) === "") {
|
||||
return false;
|
||||
}
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getStorageDriver(): string {
|
||||
$storageDriver = (\getenv("STORAGE_DRIVER"));
|
||||
if ($storageDriver === false) {
|
||||
return StorageDriver::DECOMPOSED;
|
||||
}
|
||||
$storageDriver = \strtoupper($storageDriver);
|
||||
if (!\in_array($storageDriver, self::STORAGE_DRIVERS)) {
|
||||
throw new Exception(
|
||||
"Invalid storage driver. " .
|
||||
"STORAGE_DRIVER must be '" . \join(", ", self::STORAGE_DRIVERS) . "'"
|
||||
);
|
||||
}
|
||||
return $storageDriver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $users
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function deleteRevaUserData(?array $users = []): void {
|
||||
$deleteCmd = self::getDeleteUserDataCommand();
|
||||
|
||||
if (self::getStorageDriver() === StorageDriver::POSIX) {
|
||||
\exec($deleteCmd);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($users as $user) {
|
||||
if (\is_array($user)) {
|
||||
$user = $user["actualUsername"];
|
||||
}
|
||||
if ($deleteCmd === false) {
|
||||
if (self::getStorageDriver() === StorageDriver::OWNCLOUD) {
|
||||
self::recurseRmdir(self::getOcRevaDataRoot() . $user);
|
||||
}
|
||||
continue;
|
||||
} elseif (self::getStorageDriver() === StorageDriver::EOS) {
|
||||
$deleteCmd = \str_replace(
|
||||
"%s",
|
||||
$user[0] . '/' . $user,
|
||||
$deleteCmd
|
||||
);
|
||||
} else {
|
||||
$deleteCmd = \sprintf($deleteCmd, $user);
|
||||
}
|
||||
\exec($deleteCmd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for Recursive Copy of file/folder
|
||||
* For more info check this out https://gist.github.com/gserrano/4c9648ec9eb293b9377b
|
||||
*
|
||||
* @param string|null $source
|
||||
* @param string|null $destination
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function recurseCopy(?string $source, ?string $destination): void {
|
||||
$dir = \opendir($source);
|
||||
@\mkdir($destination);
|
||||
while (($file = \readdir($dir)) !== false) {
|
||||
if (($file != '.') && ($file != '..')) {
|
||||
if (\is_dir($source . '/' . $file)) {
|
||||
self::recurseCopy($source . '/' . $file, $destination . '/' . $file);
|
||||
} else {
|
||||
\copy($source . '/' . $file, $destination . '/' . $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
\closedir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public static function getLdapPort(): int {
|
||||
$port = \getenv("REVA_LDAP_PORT");
|
||||
return $port ? (int)$port : 636;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function useSsl(): bool {
|
||||
$useSsl = \getenv("REVA_LDAP_USESSL");
|
||||
if ($useSsl === false) {
|
||||
return (self::getLdapPort() === 636);
|
||||
} else {
|
||||
return $useSsl === "true";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getBaseDN(): string {
|
||||
$dn = \getenv("REVA_LDAP_BASE_DN");
|
||||
return $dn ?: "dc=example,dc=org";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getGroupsOU(): string {
|
||||
$ou = \getenv("REVA_LDAP_GROUPS_OU");
|
||||
return $ou ?: "TestGroups";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getUsersOU(): string {
|
||||
$ou = \getenv("REVA_LDAP_USERS_OU");
|
||||
return $ou ?: "TestUsers";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getGroupSchema(): string {
|
||||
$schema = \getenv("REVA_LDAP_GROUP_SCHEMA");
|
||||
return $schema ?: "rfc2307";
|
||||
}
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getHostname(): string {
|
||||
$hostname = \getenv("REVA_LDAP_HOSTNAME");
|
||||
return $hostname ?: "localhost";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getBindDN(): string {
|
||||
$dn = \getenv("REVA_LDAP_BIND_DN");
|
||||
return $dn ?: "cn=admin,dc=example,dc=org";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getBindPassword(): string {
|
||||
$pw = \getenv("REVA_LDAP_BIND_PASSWORD");
|
||||
return $pw ?: "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function getOcRevaDataRoot(): string {
|
||||
$root = \getenv("OC_REVA_DATA_ROOT");
|
||||
if ($root === false || $root === "") {
|
||||
$root = "/var/tmp/qsfera/qsfera/";
|
||||
}
|
||||
if (!\file_exists($root)) {
|
||||
echo "WARNING: reva data root folder ($root) does not exist\n";
|
||||
}
|
||||
return $root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $dir
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function recurseRmdir(?string $dir): bool {
|
||||
if (\file_exists($dir) === true) {
|
||||
$files = \array_diff(\scandir($dir), ['.', '..']);
|
||||
foreach ($files as $file) {
|
||||
if (\is_dir("$dir/$file")) {
|
||||
self::recurseRmdir("$dir/$file");
|
||||
} else {
|
||||
\unlink("$dir/$file");
|
||||
}
|
||||
}
|
||||
return \rmdir($dir);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* On Eos storage backend when the user data is cleared after test run
|
||||
* Running another test immediately fails. So Send this request to create user home directory
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $xRequestId
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function createEOSStorageHome(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $xRequestId = ''
|
||||
): void {
|
||||
HttpRequestHelper::get(
|
||||
$baseUrl . "/ocs/v2.php/apps/notifications/api/v1/notifications",
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Viktor Scharf <scharf.vi@gmail.com>
|
||||
* @copyright Copyright (c) 2024 Viktor Scharf <scharf.vi@gmail.com>
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A helper class for managing federation server requests
|
||||
*/
|
||||
class OcmHelper {
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getRequestHeaders(): array {
|
||||
return [
|
||||
'Content-Type' => 'application/json',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getFullUrl(string $baseUrl, string $path): string {
|
||||
$fullUrl = $baseUrl;
|
||||
if (\substr($fullUrl, -1) !== '/') {
|
||||
$fullUrl .= '/';
|
||||
}
|
||||
$fullUrl .= 'sciencemesh/' . $path;
|
||||
return $fullUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string|null $email
|
||||
* @param string|null $description
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function createInvitation(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password,
|
||||
?string $email = null,
|
||||
?string $description = null
|
||||
): ResponseInterface {
|
||||
$body = [
|
||||
"description" => $description,
|
||||
"recipient" => $email
|
||||
];
|
||||
$url = self::getFullUrl($baseUrl, 'generate-invite');
|
||||
return HttpRequestHelper::post(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
self::getRequestHeaders(),
|
||||
\json_encode($body)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $token
|
||||
* @param string $providerDomain
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function acceptInvitation(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password,
|
||||
string $token,
|
||||
string $providerDomain
|
||||
): ResponseInterface {
|
||||
$body = [
|
||||
"token" => $token,
|
||||
"providerDomain" => $providerDomain
|
||||
];
|
||||
$url = self::getFullUrl($baseUrl, 'accept-invite');
|
||||
return HttpRequestHelper::post(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
self::getRequestHeaders(),
|
||||
\json_encode($body)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function findAcceptedUsers(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password
|
||||
): ResponseInterface {
|
||||
$url = self::getFullUrl($baseUrl, 'find-accepted-users');
|
||||
return HttpRequestHelper::get(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
self::getRequestHeaders()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function listInvite(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password
|
||||
): ResponseInterface {
|
||||
$url = self::getFullUrl($baseUrl, 'list-invite');
|
||||
return HttpRequestHelper::get(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
self::getRequestHeaders()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $userId
|
||||
* @param string $idp
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function deleteConnection(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password,
|
||||
string $userId,
|
||||
string $idp
|
||||
): ResponseInterface {
|
||||
$url = self::getFullUrl($baseUrl, 'delete-accepted-user');
|
||||
$body = [
|
||||
"idp" => $idp,
|
||||
"user_id" => $userId
|
||||
];
|
||||
return HttpRequestHelper::delete(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
self::getRequestHeaders(),
|
||||
\json_encode($body)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Helper to make requests to the OCS API
|
||||
*
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
*/
|
||||
class OcsApiHelper {
|
||||
/**
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user if set to null no authentication header will be sent
|
||||
* @param string|null $password
|
||||
* @param string|null $method HTTP Method
|
||||
* @param string|null $path
|
||||
* @param string|null $xRequestId
|
||||
* @param mixed $body array of key, value pairs e.g ['value' => 'yes']
|
||||
* @param int|null $ocsApiVersion (1|2) default 2
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function sendRequest(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $method,
|
||||
?string $path,
|
||||
?string $xRequestId = '',
|
||||
$body = [],
|
||||
?int $ocsApiVersion = 2,
|
||||
?array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = $baseUrl;
|
||||
if (\substr($fullUrl, -1) !== '/') {
|
||||
$fullUrl .= '/';
|
||||
}
|
||||
$fullUrl .= "ocs/v$ocsApiVersion.php" . $path;
|
||||
$headers['OCS-APIREQUEST'] = true;
|
||||
return HttpRequestHelper::sendRequest($fullUrl, $xRequestId, $method, $user, $password, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $method HTTP Method
|
||||
* @param string|null $path
|
||||
* @param string|null $xRequestId
|
||||
* @param mixed $body array of key, value pairs e.g ['value' => 'yes']
|
||||
* @param int|null $ocsApiVersion (1|2) default 2
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public static function createOcsRequest(
|
||||
?string $baseUrl,
|
||||
?string $method,
|
||||
?string $path,
|
||||
?string $xRequestId = '',
|
||||
$body = [],
|
||||
?int $ocsApiVersion = 2,
|
||||
?array $headers = []
|
||||
): RequestInterface {
|
||||
$fullUrl = $baseUrl;
|
||||
if (\substr($fullUrl, -1) !== '/') {
|
||||
$fullUrl .= '/';
|
||||
}
|
||||
$fullUrl .= "ocs/v$ocsApiVersion.php" . $path;
|
||||
return HttpRequestHelper::createRequest(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$method,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Sajan Gurung <sajan@jankaritech.com>
|
||||
* @copyright Copyright (c) 2024 Sajan Gurung sajan@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
/**
|
||||
* A helper class for КуСфера settings
|
||||
*/
|
||||
class SettingsHelper {
|
||||
private static string $settingsEndpoint = '/api/v0/settings/';
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function buildFullUrl(string $baseUrl, string $path): string {
|
||||
return $baseUrl . self::$settingsEndpoint . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getBundlesList(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId,
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = self::buildFullUrl($baseUrl, "bundles-list");
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
"{}"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $bundleName
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getBundleByName(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $bundleName,
|
||||
string $xRequestId
|
||||
): array {
|
||||
$response = self::getBundlesList($baseUrl, $user, $password, $xRequestId);
|
||||
Assert::assertEquals(201, $response->getStatusCode(), "Failed to get bundles list");
|
||||
|
||||
$bundlesList = HttpRequestHelper::getJsonDecodedResponseBodyContent($response);
|
||||
foreach ($bundlesList->bundles as $value) {
|
||||
if ($value->displayName === $bundleName) {
|
||||
return json_decode(json_encode($value), true);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getRolesList(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId,
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = self::buildFullUrl($baseUrl, "roles-list");
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
"{}"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $assigneeId
|
||||
* @param string $roleId
|
||||
* @param string $xRequestId
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function assignRoleToUser(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $assigneeId,
|
||||
string $roleId,
|
||||
string $xRequestId,
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = self::buildFullUrl($baseUrl, "assignments-add");
|
||||
$body = json_encode(["account_uuid" => $assigneeId, "role_id" => $roleId], JSON_THROW_ON_ERROR);
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $userId
|
||||
* @param string $xRequestId
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getAssignmentsList(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $userId,
|
||||
string $xRequestId,
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = self::buildFullUrl($baseUrl, "assignments-list");
|
||||
$body = json_encode(["account_uuid" => $userId], JSON_THROW_ON_ERROR);
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getValuesList(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId,
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = self::buildFullUrl($baseUrl, "values-list");
|
||||
$body = json_encode(["account_uuid" => "me"], JSON_THROW_ON_ERROR);
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getAutoAcceptSharesSettingValue(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId
|
||||
): bool {
|
||||
$response = self::getValuesList($baseUrl, $user, $password, $xRequestId);
|
||||
Assert::assertEquals(201, $response->getStatusCode(), "Failed to get values list");
|
||||
|
||||
$valuesList = HttpRequestHelper::getJsonDecodedResponseBodyContent($response);
|
||||
|
||||
if (empty($valuesList)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($valuesList->values as $value) {
|
||||
if ($value->identifier->setting === "auto-accept-shares") {
|
||||
return $value->value->boolValue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getLanguageSettingValue(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId
|
||||
): string {
|
||||
$response = self::getValuesList($baseUrl, $user, $password, $xRequestId);
|
||||
Assert::assertEquals(201, $response->getStatusCode(), "Failed to get values list");
|
||||
|
||||
$valuesList = HttpRequestHelper::getJsonDecodedResponseBodyContent($response);
|
||||
|
||||
// if no language is set, the request body is empty return English as the default language
|
||||
if (empty($valuesList)) {
|
||||
return "en";
|
||||
}
|
||||
foreach ($valuesList->values as $value) {
|
||||
if ($value->identifier->setting === "language") {
|
||||
return $value->value->listValue->values[0]->stringValue;
|
||||
}
|
||||
}
|
||||
// if a language setting was still not found, return English
|
||||
return "en";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $body
|
||||
* @param string $xRequestId
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function updateSettings(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $body,
|
||||
string $xRequestId,
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = self::buildFullUrl($baseUrl, "values-save");
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use Behat\Testwork\Hook\Scope\HookScope;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Exception;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* Helper to set up UI / Integration tests
|
||||
*
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
*/
|
||||
class SetupHelper extends \PHPUnit\Framework\Assert {
|
||||
private static ?string $baseUrl = null;
|
||||
private static ?string $adminUsername = null;
|
||||
private static ?string $adminPassword = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param HookScope $scope
|
||||
*
|
||||
* @return array of suite context parameters
|
||||
*/
|
||||
public static function getSuiteParameters(HookScope $scope): array {
|
||||
return $scope->getEnvironment()->getSuite()
|
||||
->getSettings() ['context'] ['parameters'];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $baseUrl
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init(
|
||||
?string $adminUsername,
|
||||
?string $adminPassword,
|
||||
?string $baseUrl
|
||||
): void {
|
||||
foreach (\func_get_args() as $variableToCheck) {
|
||||
if (!\is_string($variableToCheck)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"mandatory argument missing or wrong type ($variableToCheck => "
|
||||
. \gettype($variableToCheck) . ")"
|
||||
);
|
||||
}
|
||||
}
|
||||
self::$adminUsername = $adminUsername;
|
||||
self::$adminPassword = $adminPassword;
|
||||
self::$baseUrl = \rtrim($baseUrl, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
*
|
||||
* @return SimpleXMLElement
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getSysInfo(
|
||||
?string $baseUrl,
|
||||
?string $adminUsername,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = ''
|
||||
): SimpleXMLElement {
|
||||
$result = OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
"GET",
|
||||
"/apps/testing/api/v1/sysinfo",
|
||||
$xRequestId
|
||||
);
|
||||
if ($result->getStatusCode() !== 200) {
|
||||
throw new \Exception(
|
||||
"could not get sysinfo " . $result->getReasonPhrase()
|
||||
);
|
||||
}
|
||||
return HttpRequestHelper::getResponseXml($result, __METHOD__)->data;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getServerRoot(
|
||||
?string $baseUrl,
|
||||
?string $adminUsername,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = ''
|
||||
): string {
|
||||
$sysInfo = self::getSysInfo(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
$xRequestId
|
||||
);
|
||||
// server_root is a SimpleXMLElement object that "wraps" a string.
|
||||
/// We want the bare string.
|
||||
return (string) $sysInfo->server_root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $callerName
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function checkAdminUsername(?string $adminUsername, ?string $callerName): ?string {
|
||||
if (self::$adminUsername === null
|
||||
&& $adminUsername === null
|
||||
) {
|
||||
throw new Exception(
|
||||
"$callerName called without adminUsername - pass the username or call SetupHelper::init"
|
||||
);
|
||||
}
|
||||
if ($adminUsername === null) {
|
||||
$adminUsername = self::$adminUsername;
|
||||
}
|
||||
return $adminUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $callerName
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function checkAdminPassword(?string $adminPassword, ?string $callerName): string {
|
||||
if (self::$adminPassword === null
|
||||
&& $adminPassword === null
|
||||
) {
|
||||
throw new Exception(
|
||||
"$callerName called without adminPassword - pass the password or call SetupHelper::init"
|
||||
);
|
||||
}
|
||||
if ($adminPassword === null) {
|
||||
$adminPassword = self::$adminPassword;
|
||||
}
|
||||
return $adminPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $callerName
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function checkBaseUrl(?string $baseUrl, ?string $callerName): ?string {
|
||||
if (self::$baseUrl === null
|
||||
&& $baseUrl === null
|
||||
) {
|
||||
throw new Exception(
|
||||
"$callerName called without baseUrl - pass the baseUrl or call SetupHelper::init"
|
||||
);
|
||||
}
|
||||
if ($baseUrl === null) {
|
||||
$baseUrl = self::$baseUrl;
|
||||
}
|
||||
return $baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $dirPathFromServerRoot e.g. 'apps2/myapp/appinfo'
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function mkDirOnServer(
|
||||
?string $dirPathFromServerRoot,
|
||||
?string $xRequestId = '',
|
||||
?string $baseUrl = null,
|
||||
?string $adminUsername = null,
|
||||
?string $adminPassword = null
|
||||
): void {
|
||||
$baseUrl = self::checkBaseUrl($baseUrl, "mkDirOnServer");
|
||||
$adminUsername = self::checkAdminUsername($adminUsername, "mkDirOnServer");
|
||||
$adminPassword = self::checkAdminPassword($adminPassword, "mkDirOnServer");
|
||||
$result = OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
"POST",
|
||||
"/apps/testing/api/v1/dir",
|
||||
$xRequestId,
|
||||
['dir' => $dirPathFromServerRoot]
|
||||
);
|
||||
|
||||
if ($result->getStatusCode() !== 200) {
|
||||
throw new \Exception(
|
||||
"could not create directory $dirPathFromServerRoot " . $result->getReasonPhrase()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $dirPathFromServerRoot e.g. 'apps2/myapp/appinfo'
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function rmDirOnServer(
|
||||
?string $dirPathFromServerRoot,
|
||||
?string $xRequestId = '',
|
||||
?string $baseUrl = null,
|
||||
?string $adminUsername = null,
|
||||
?string $adminPassword = null
|
||||
): void {
|
||||
$baseUrl = self::checkBaseUrl($baseUrl, "rmDirOnServer");
|
||||
$adminUsername = self::checkAdminUsername($adminUsername, "rmDirOnServer");
|
||||
$adminPassword = self::checkAdminPassword($adminPassword, "rmDirOnServer");
|
||||
$result = OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
"DELETE",
|
||||
"/apps/testing/api/v1/dir",
|
||||
$xRequestId,
|
||||
['dir' => $dirPathFromServerRoot]
|
||||
);
|
||||
|
||||
if ($result->getStatusCode() !== 200) {
|
||||
throw new \Exception(
|
||||
"could not delete directory $dirPathFromServerRoot " . $result->getReasonPhrase()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $filePathFromServerRoot e.g. 'app2/myapp/appinfo/info.xml'
|
||||
* @param string|null $content
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function createFileOnServer(
|
||||
?string $filePathFromServerRoot,
|
||||
?string $content,
|
||||
?string $xRequestId = '',
|
||||
?string $baseUrl = null,
|
||||
?string $adminUsername = null,
|
||||
?string $adminPassword = null
|
||||
): void {
|
||||
$baseUrl = self::checkBaseUrl($baseUrl, "createFileOnServer");
|
||||
$adminUsername = self::checkAdminUsername($adminUsername, "createFileOnServer");
|
||||
$adminPassword = self::checkAdminPassword($adminPassword, "createFileOnServer");
|
||||
$result = OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
"POST",
|
||||
"/apps/testing/api/v1/file",
|
||||
$xRequestId,
|
||||
[
|
||||
'file' => $filePathFromServerRoot,
|
||||
'content' => $content
|
||||
]
|
||||
);
|
||||
|
||||
if ($result->getStatusCode() !== 200) {
|
||||
throw new \Exception(
|
||||
"could not create file $filePathFromServerRoot " . $result->getReasonPhrase()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $filePathFromServerRoot e.g. 'app2/myapp/appinfo/info.xml'
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function deleteFileOnServer(
|
||||
?string $filePathFromServerRoot,
|
||||
?string $xRequestId = '',
|
||||
?string $baseUrl = null,
|
||||
?string $adminUsername = null,
|
||||
?string $adminPassword = null
|
||||
): void {
|
||||
$baseUrl = self::checkBaseUrl($baseUrl, "deleteFileOnServer");
|
||||
$adminUsername = self::checkAdminUsername($adminUsername, "deleteFileOnServer");
|
||||
$adminPassword = self::checkAdminPassword($adminPassword, "deleteFileOnServer");
|
||||
$result = OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
"DELETE",
|
||||
"/apps/testing/api/v1/file",
|
||||
$xRequestId,
|
||||
[
|
||||
'file' => $filePathFromServerRoot
|
||||
]
|
||||
);
|
||||
|
||||
if ($result->getStatusCode() !== 200) {
|
||||
throw new \Exception(
|
||||
"could not delete file $filePathFromServerRoot " . $result->getReasonPhrase()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $fileInCore e.g. 'app2/myapp/appinfo/info.xml'
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function readFileFromServer(
|
||||
?string $fileInCore,
|
||||
?string $xRequestId = '',
|
||||
?string $baseUrl = null,
|
||||
?string $adminUsername = null,
|
||||
?string $adminPassword = null
|
||||
): string {
|
||||
$baseUrl = self::checkBaseUrl($baseUrl, "readFile");
|
||||
$adminUsername = self::checkAdminUsername(
|
||||
$adminUsername,
|
||||
"readFile"
|
||||
);
|
||||
$adminPassword = self::checkAdminPassword(
|
||||
$adminPassword,
|
||||
"readFile"
|
||||
);
|
||||
|
||||
$response = OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
'GET',
|
||||
"/apps/testing/api/v1/file?file=$fileInCore",
|
||||
$xRequestId
|
||||
);
|
||||
self::assertSame(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
"Failed to read the file $fileInCore"
|
||||
);
|
||||
$localContent = HttpRequestHelper::getResponseXml($response, __METHOD__);
|
||||
$localContent = (string)$localContent->data->element->contentUrlEncoded;
|
||||
return \urldecode($localContent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* manage Shares via OCS API
|
||||
*
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
*/
|
||||
class SharingHelper {
|
||||
public const PERMISSION_TYPES = [
|
||||
'read' => 1,
|
||||
'update' => 2,
|
||||
'create' => 4,
|
||||
'delete' => 8,
|
||||
'invite' => 0
|
||||
];
|
||||
|
||||
public const SHARE_TYPES = [
|
||||
'user' => 0,
|
||||
'group' => 1,
|
||||
'public_link' => 3,
|
||||
'federated' => 6,
|
||||
];
|
||||
|
||||
public const SHARE_STATES = [
|
||||
'accepted' => 0,
|
||||
'pending' => 1,
|
||||
'rejected' => 2,
|
||||
'declined' => 2, // declined is a synonym for rejected.
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $baseUrl baseURL of the КуСфера installation without /ocs.
|
||||
* @param string $user user that creates the share.
|
||||
* @param string $password password of the user that creates the share.
|
||||
* @param string $path The path to the file or folder which should be shared.
|
||||
* @param string|int $shareType The type of the share. This can be one of:
|
||||
* 0 = user, 1 = group, 3 = public (link),
|
||||
* 6 = federated (cloud share).
|
||||
* Pass either the number or the keyword.
|
||||
* @param string $xRequestId
|
||||
* @param string|null $shareWith The user or group id with which the file should
|
||||
* be shared.
|
||||
* @param boolean|null $publicUpload Whether to allow public upload to a public
|
||||
* shared folder.
|
||||
* @param string|null $sharePassword The password to protect the public link
|
||||
* share with.
|
||||
* @param string|int|string[]|int[]|null $permissions The permissions to set on the share.
|
||||
* 1 = read; 2 = update; 4 = create;
|
||||
* 8 = delete; 16 = share
|
||||
* (default: 31, for public shares: 1)
|
||||
* Pass either the (total) number or array of numbers,
|
||||
* or any of the above keywords or array of keywords.
|
||||
* @param string|null $linkName A (human-readable) name for the share,
|
||||
* which can be up to 64 characters in length.
|
||||
* @param string|null $expireDate An expire date for public link shares.
|
||||
* This argument expects a date string
|
||||
* in the format 'YYYY-MM-DD'.
|
||||
* @param string|null $space_ref
|
||||
* @param int $ocsApiVersion
|
||||
* @param int $sharingApiVersion
|
||||
* @param string $sharingApp
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws InvalidArgumentException|GuzzleException
|
||||
*/
|
||||
public static function createShare(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $path,
|
||||
$shareType,
|
||||
string $xRequestId = '',
|
||||
?string $shareWith = null,
|
||||
?bool $publicUpload = false,
|
||||
?string $sharePassword = null,
|
||||
$permissions = null,
|
||||
?string $linkName = null,
|
||||
?string $expireDate = null,
|
||||
?string $space_ref = null,
|
||||
int $ocsApiVersion = 1,
|
||||
int $sharingApiVersion = 1,
|
||||
string $sharingApp = 'files_sharing'
|
||||
): ResponseInterface {
|
||||
$fd = [];
|
||||
foreach ([$path, $baseUrl, $user, $password] as $variableToCheck) {
|
||||
if (!\is_string($variableToCheck)) {
|
||||
throw new InvalidArgumentException(
|
||||
"mandatory argument missing or wrong type ($variableToCheck => "
|
||||
. \gettype($variableToCheck) . ")"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($permissions !== null) {
|
||||
$fd['permissions'] = self::getPermissionSum($permissions);
|
||||
} elseif ($shareType !== "public_link") {
|
||||
// sharing without permissions should automatically set the permission to 15 for share and 1 for public link
|
||||
$fd['permissions'] = 15;
|
||||
}
|
||||
|
||||
if (!\in_array($ocsApiVersion, [1, 2], true)) {
|
||||
throw new InvalidArgumentException(
|
||||
"invalid ocsApiVersion ($ocsApiVersion)"
|
||||
);
|
||||
}
|
||||
if (!\in_array($sharingApiVersion, [1, 2], true)) {
|
||||
throw new InvalidArgumentException(
|
||||
"invalid sharingApiVersion ($sharingApiVersion)"
|
||||
);
|
||||
}
|
||||
|
||||
$fullUrl = $baseUrl;
|
||||
if (\substr($fullUrl, -1) !== '/') {
|
||||
$fullUrl .= '/';
|
||||
}
|
||||
$fullUrl .= "ocs/v$ocsApiVersion.php/apps/$sharingApp/api/v$sharingApiVersion/shares";
|
||||
|
||||
$fd['path'] = $path;
|
||||
$fd['shareType'] = self::getShareType($shareType);
|
||||
|
||||
if ($shareWith !== null) {
|
||||
$fd['shareWith'] = $shareWith;
|
||||
}
|
||||
if ($publicUpload !== null) {
|
||||
$fd['publicUpload'] = $publicUpload;
|
||||
}
|
||||
if ($sharePassword !== null) {
|
||||
$fd['password'] = $sharePassword;
|
||||
}
|
||||
if ($linkName !== null) {
|
||||
$fd['name'] = $linkName;
|
||||
}
|
||||
if ($expireDate !== null) {
|
||||
$fd['expireDate'] = $expireDate;
|
||||
}
|
||||
if ($space_ref !== null) {
|
||||
$fd['space_ref'] = $space_ref;
|
||||
}
|
||||
$headers = ['OCS-APIREQUEST' => 'true'];
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$fd
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the permission sum (int) from the given permissions.
|
||||
* Permissions can be passed in as int, string or array of int or string
|
||||
* 'read' => 1
|
||||
* 'update' => 2
|
||||
* 'create' => 4
|
||||
* 'delete' => 8
|
||||
* 'share' => 16
|
||||
* 'invite' => 0
|
||||
*
|
||||
* @param string[]|string|int|int[] $permissions
|
||||
*
|
||||
* @return int
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
*/
|
||||
public static function getPermissionSum($permissions): int {
|
||||
if (\is_numeric($permissions)) {
|
||||
// Allow any permission number so that test scenarios can
|
||||
// specifically test invalid permission values
|
||||
return (int) $permissions;
|
||||
}
|
||||
if (!\is_array($permissions)) {
|
||||
$permissions = [$permissions];
|
||||
}
|
||||
$permissionSum = 0;
|
||||
foreach ($permissions as $permission) {
|
||||
if (\array_key_exists($permission, self::PERMISSION_TYPES)) {
|
||||
$permissionSum += self::PERMISSION_TYPES[$permission];
|
||||
} elseif (\in_array($permission, self::PERMISSION_TYPES, true)) {
|
||||
$permissionSum += $permission;
|
||||
} else {
|
||||
throw new InvalidArgumentException(
|
||||
"invalid permission type ($permission)"
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($permissionSum < 0 || $permissionSum > 31) {
|
||||
throw new InvalidArgumentException(
|
||||
"invalid permission total ($permissionSum)"
|
||||
);
|
||||
}
|
||||
return $permissionSum;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the share type number corresponding to the share type keyword
|
||||
*
|
||||
* @param string|int $shareType a keyword from SHARE_TYPES or a share type number
|
||||
*
|
||||
* @return int
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
*/
|
||||
public static function getShareType($shareType): int {
|
||||
if (\array_key_exists($shareType, self::SHARE_TYPES)) {
|
||||
return self::SHARE_TYPES[$shareType];
|
||||
} else {
|
||||
if (\ctype_digit($shareType)) {
|
||||
$shareType = (int) $shareType;
|
||||
}
|
||||
$key = \array_search($shareType, self::SHARE_TYPES, true);
|
||||
if ($key !== false) {
|
||||
return self::SHARE_TYPES[$key];
|
||||
}
|
||||
throw new InvalidArgumentException(
|
||||
"invalid share type ($shareType)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param SimpleXMLElement $responseXmlObject
|
||||
* @param string $errorMessage
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
public static function getLastShareIdFromResponse(
|
||||
SimpleXMLElement $responseXmlObject,
|
||||
string $errorMessage = "cannot find share id in response"
|
||||
): string {
|
||||
$xmlPart = $responseXmlObject->xpath("//data/element[last()]/id");
|
||||
|
||||
if (!\is_array($xmlPart) || (\count($xmlPart) === 0)) {
|
||||
throw new Exception($errorMessage);
|
||||
}
|
||||
return $xmlPart[0]->__toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* @author Sagar Gurung <sagar@jankatitech.com>
|
||||
*
|
||||
*/
|
||||
namespace TestHelpers;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class SpaceNotFoundException
|
||||
* Exception when space id for a user is not found
|
||||
*/
|
||||
class SpaceNotFoundException extends Exception {
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
<?php
|
||||
/**
|
||||
* @author Viktor Scharf <v.scharf@qsfera.eu>
|
||||
* @copyright Copyright (c) 2025 Viktor Scharf <v.scharf@qsfera.eu>
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
/**
|
||||
* Helper for obtaining bearer tokens for users
|
||||
*/
|
||||
class TokenHelper {
|
||||
private const LOGON_URL = '/signin/v1/identifier/_/logon';
|
||||
private const REDIRECT_URL = '/oidc-callback.html';
|
||||
private const TOKEN_URL = '/konnect/v1/token';
|
||||
|
||||
// Static cache [username => token_data]
|
||||
private static array $tokenCache = [];
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function useBearerToken(): bool {
|
||||
return \getenv('USE_BEARER_TOKEN') === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts base URL from a full URL
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return string the base URL
|
||||
*/
|
||||
private static function extractBaseUrl(string $url): string {
|
||||
return preg_replace('#(https?://[^/]+).*#', '$1', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access and refresh tokens for a user
|
||||
* Uses cache to avoid unnecessary requests
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $url
|
||||
*
|
||||
* @return array ['access_token' => string, 'refresh_token' => string, 'expires_at' => int]
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getTokens(string $username, string $password, string $url): array {
|
||||
// Extract base URL. I need to send $url to get correct server in case of multiple servers (ocm suite)
|
||||
$baseUrl = self::extractBaseUrl($url);
|
||||
$cacheKey = $username . '|' . $baseUrl;
|
||||
|
||||
// Check cache
|
||||
if (isset(self::$tokenCache[$cacheKey])) {
|
||||
$cachedToken = self::$tokenCache[$cacheKey];
|
||||
|
||||
// Check if access token has expired
|
||||
if (time() < $cachedToken['expires_at']) {
|
||||
return $cachedToken;
|
||||
}
|
||||
|
||||
$refreshedToken = self::refreshToken($cachedToken['refresh_token'], $baseUrl);
|
||||
$tokenData = [
|
||||
'access_token' => $refreshedToken['access_token'],
|
||||
'refresh_token' => $refreshedToken['refresh_token'],
|
||||
// set expiry to 240 (4 minutes) seconds to allow for some buffer
|
||||
// token actually expires in 300 seconds (5 minutes)
|
||||
'expires_at' => time() + 240
|
||||
];
|
||||
self::$tokenCache[$cacheKey] = $tokenData;
|
||||
return $tokenData;
|
||||
}
|
||||
|
||||
// Get new tokens
|
||||
$cookieJar = new CookieJar();
|
||||
|
||||
$continueUrl = self::getAuthorizedEndPoint($username, $password, $baseUrl, $cookieJar);
|
||||
$code = self::getCode($continueUrl, $baseUrl, $cookieJar);
|
||||
$tokens = self::getToken($code, $baseUrl, $cookieJar);
|
||||
|
||||
$tokenData = [
|
||||
'access_token' => $tokens['access_token'],
|
||||
'refresh_token' => $tokens['refresh_token'],
|
||||
// set expiry to 240 (4 minutes) seconds to allow for some buffer
|
||||
// token actually expires in 300 seconds (5 minutes)
|
||||
'expires_at' => time() + 240
|
||||
];
|
||||
|
||||
// Save to cache
|
||||
self::$tokenCache[$cacheKey] = $tokenData;
|
||||
|
||||
return $tokenData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh token
|
||||
*
|
||||
* @param string $refreshToken
|
||||
* @param string $baseUrl
|
||||
*
|
||||
* @return array
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function refreshToken(string $refreshToken, string $baseUrl): array {
|
||||
$client = new Client(
|
||||
[
|
||||
'verify' => false,
|
||||
'http_errors' => false,
|
||||
'allow_redirects' => false
|
||||
]
|
||||
);
|
||||
|
||||
$response = $client->post(
|
||||
$baseUrl . self::TOKEN_URL,
|
||||
[
|
||||
'form_params' => [
|
||||
'client_id' => 'web',
|
||||
'refresh_token' => $refreshToken,
|
||||
'grant_type' => 'refresh_token'
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
'Token refresh failed: Expected status code 200 but received ' . $response->getStatusCode()
|
||||
);
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
Assert::assertArrayHasKey('access_token', $data, 'Missing access_token in refresh response');
|
||||
Assert::assertArrayHasKey('refresh_token', $data, 'Missing refresh_token in refresh response');
|
||||
|
||||
return [
|
||||
'access_token' => $data['access_token'],
|
||||
'refresh_token' => $data['refresh_token']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cached tokens for a specific user
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $url
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearUserTokens(string $username, string $url): void {
|
||||
$baseUrl = self::extractBaseUrl($url);
|
||||
$cacheKey = $username . '|' . $baseUrl;
|
||||
unset(self::$tokenCache[$cacheKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached tokens
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearAllTokens(): void {
|
||||
self::$tokenCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $baseUrl
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function makeLoginRequest(
|
||||
string $username,
|
||||
string $password,
|
||||
string $baseUrl,
|
||||
CookieJar $cookieJar
|
||||
): \Psr\Http\Message\ResponseInterface {
|
||||
$client = new Client(
|
||||
[
|
||||
'verify' => false,
|
||||
'http_errors' => false,
|
||||
'allow_redirects' => false,
|
||||
'cookies' => $cookieJar
|
||||
]
|
||||
);
|
||||
|
||||
return $client->post(
|
||||
$baseUrl . self::LOGON_URL,
|
||||
[
|
||||
'headers' => [
|
||||
'Kopano-Konnect-XSRF' => '1',
|
||||
'Referer' => $baseUrl,
|
||||
'Content-Type' => 'application/json'
|
||||
],
|
||||
'json' => [
|
||||
'params' => [$username, $password, '1'],
|
||||
'hello' => [
|
||||
'scope' => 'openid profile offline_access email',
|
||||
'client_id' => 'web',
|
||||
'redirect_uri' => $baseUrl . self::REDIRECT_URL,
|
||||
'flow' => 'oidc'
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1: Login and get continue_uri
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $baseUrl
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getAuthorizedEndPoint(
|
||||
string $username,
|
||||
string $password,
|
||||
string $baseUrl,
|
||||
CookieJar $cookieJar
|
||||
): string {
|
||||
$response = self::makeLoginRequest($username, $password, $baseUrl, $cookieJar);
|
||||
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
'Logon failed: Expected status code 200 but received: ' . $response->getStatusCode()
|
||||
);
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
Assert::assertArrayHasKey(
|
||||
'hello',
|
||||
$data,
|
||||
'Logon response does not contain "hello" object'
|
||||
);
|
||||
|
||||
Assert::assertArrayHasKey(
|
||||
'continue_uri',
|
||||
$data['hello'],
|
||||
'Missing continue_uri in logon response'
|
||||
);
|
||||
|
||||
return $data['hello']['continue_uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2: Authorization and get code
|
||||
*
|
||||
* @param string $continueUrl
|
||||
* @param string $baseUrl
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getCode(string $continueUrl, string $baseUrl, CookieJar $cookieJar): string {
|
||||
$client = new Client(
|
||||
[
|
||||
'verify' => false,
|
||||
'http_errors' => false,
|
||||
'allow_redirects' => false, // Disable automatic redirects
|
||||
'cookies' => $cookieJar
|
||||
]
|
||||
);
|
||||
|
||||
$params = [
|
||||
'client_id' => 'web',
|
||||
'prompt' => 'none',
|
||||
'redirect_uri' => $baseUrl . self::REDIRECT_URL,
|
||||
'response_mode' => 'query',
|
||||
'response_type' => 'code',
|
||||
'scope' => 'openid profile offline_access email'
|
||||
];
|
||||
|
||||
$response = $client->get(
|
||||
$continueUrl,
|
||||
[
|
||||
'query' => $params
|
||||
]
|
||||
);
|
||||
|
||||
Assert::assertEquals(
|
||||
302,
|
||||
$response->getStatusCode(),
|
||||
'Authorization request failed: Expected status code 302 but received: ' . $response->getStatusCode()
|
||||
);
|
||||
|
||||
$location = $response->getHeader('Location')[0] ?? '';
|
||||
Assert::assertNotEmpty($location, 'Missing Location header in authorization response');
|
||||
|
||||
parse_str(parse_url($location, PHP_URL_QUERY), $queryParams);
|
||||
Assert::assertArrayHasKey('code', $queryParams, 'Missing code parameter in redirect URL');
|
||||
return $queryParams['code'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3: Get token
|
||||
*
|
||||
* @param string $code
|
||||
* @param string $baseUrl
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
private static function getToken(string $code, string $baseUrl, CookieJar $cookieJar): array {
|
||||
$client = new Client(
|
||||
[
|
||||
'verify' => false,
|
||||
'http_errors' => false,
|
||||
'allow_redirects' => false,
|
||||
'cookies' => $cookieJar
|
||||
]
|
||||
);
|
||||
|
||||
$response = $client->post(
|
||||
$baseUrl . self::TOKEN_URL,
|
||||
[
|
||||
'form_params' => [
|
||||
'client_id' => 'web',
|
||||
'code' => $code,
|
||||
'redirect_uri' => $baseUrl . self::REDIRECT_URL,
|
||||
'grant_type' => 'authorization_code'
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
'Token request failed: Expected status code 200 but received: ' . $response->getStatusCode()
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
Assert::assertArrayHasKey('access_token', $data, 'Missing access_token in token response');
|
||||
Assert::assertArrayHasKey('refresh_token', $data, 'Missing refresh_token in token response');
|
||||
|
||||
return [
|
||||
'access_token' => $data['access_token'],
|
||||
'refresh_token' => $data['refresh_token']
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Talank Baral <talank@jankaritech.com>
|
||||
* @copyright Copyright (c) 2021 Talank Baral talank@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
/**
|
||||
* Class TranslationHelper
|
||||
*
|
||||
* Helper functions that are needed to run tests in different languages
|
||||
*
|
||||
* @package TestHelpers
|
||||
*/
|
||||
class TranslationHelper {
|
||||
/**
|
||||
* @param string|null $language
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getLanguage(?string $language): ?string {
|
||||
if (!isset($language)) {
|
||||
if (\getenv('OC_LANGUAGE') !== false) {
|
||||
$language = \getenv('OC_LANGUAGE');
|
||||
}
|
||||
}
|
||||
return $language;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Helper for Uploads
|
||||
*
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
*/
|
||||
class UploadHelper extends Assert {
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl URL of КуСфера
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $source
|
||||
* @param string|null $destination
|
||||
* @param string|null $xRequestId
|
||||
* @param array|null $headers
|
||||
* @param int|null $davPathVersionToUse (1|2)
|
||||
* @param bool $doChunkUpload
|
||||
* @param int|null $noOfChunks how many chunks to upload
|
||||
* @param bool|null $isGivenStep
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function upload(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $source,
|
||||
?string $destination,
|
||||
?string $xRequestId = '',
|
||||
?array $headers = [],
|
||||
?int $davPathVersionToUse = 1,
|
||||
bool $doChunkUpload = false,
|
||||
?int $noOfChunks = 1,
|
||||
?bool $isGivenStep = false,
|
||||
): ResponseInterface {
|
||||
if (!$doChunkUpload) {
|
||||
$data = \file_get_contents($source);
|
||||
return WebDavHelper::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PUT",
|
||||
$destination,
|
||||
$headers,
|
||||
null,
|
||||
$xRequestId,
|
||||
$data,
|
||||
$davPathVersionToUse,
|
||||
"files",
|
||||
null,
|
||||
"basic",
|
||||
false,
|
||||
0,
|
||||
null,
|
||||
[],
|
||||
null,
|
||||
$isGivenStep
|
||||
);
|
||||
}
|
||||
|
||||
//prepare chunking
|
||||
$chunks = self::chunkFile($source, $noOfChunks);
|
||||
$chunkingId = 'chunking-' . \rand(1000, 9999);
|
||||
$result = null;
|
||||
|
||||
//upload chunks
|
||||
foreach ($chunks as $index => $chunk) {
|
||||
$filename = $destination . "-" . $chunkingId . "-" . \count($chunks) . '-' . $index;
|
||||
$result = WebDavHelper::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PUT",
|
||||
$filename,
|
||||
$headers,
|
||||
null,
|
||||
$xRequestId,
|
||||
$chunk,
|
||||
$davPathVersionToUse,
|
||||
"files",
|
||||
null,
|
||||
"basic",
|
||||
false,
|
||||
0,
|
||||
null,
|
||||
[],
|
||||
null,
|
||||
$isGivenStep
|
||||
);
|
||||
if ($result->getStatusCode() >= 400) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
self::assertNotNull($result, __METHOD__ . " chunking was requested but no upload was done.");
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* cut the file in multiple chunks
|
||||
* returns an array of chunks with the content of the file
|
||||
*
|
||||
* @param string|null $file
|
||||
* @param int|null $noOfChunks
|
||||
*
|
||||
* @return array $string
|
||||
*/
|
||||
public static function chunkFile(?string $file, ?int $noOfChunks = 1): array {
|
||||
$size = \filesize($file);
|
||||
$chunkSize = \ceil($size / $noOfChunks);
|
||||
$chunks = [];
|
||||
$fp = \fopen($file, 'r');
|
||||
while (!\feof($fp) && \ftell($fp) < $size) {
|
||||
$chunks[] = \fread($fp, (int)$chunkSize);
|
||||
}
|
||||
\fclose($fp);
|
||||
if (\count($chunks) === 0) {
|
||||
// chunk an empty file
|
||||
$chunks[] = '';
|
||||
}
|
||||
return $chunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a File with a specific size
|
||||
*
|
||||
* @param string|null $name full path of the file to create
|
||||
* @param int|null $size
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function createFileSpecificSize(?string $name, ?int $size): void {
|
||||
if (\file_exists($name)) {
|
||||
\unlink($name);
|
||||
}
|
||||
$file = \fopen($name, 'w');
|
||||
\fseek($file, \max($size - 1, 0), SEEK_CUR);
|
||||
if ($size) {
|
||||
\fwrite($file, 'a'); // write a dummy char at SIZE position
|
||||
}
|
||||
\fclose($file);
|
||||
self::assertEquals(
|
||||
1,
|
||||
\file_exists($name)
|
||||
);
|
||||
self::assertEquals(
|
||||
$size,
|
||||
\filesize($name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a File with a specific text content
|
||||
*
|
||||
* @param string|null $name full path of the file to create
|
||||
* @param string|null $text
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function createFileWithText(?string $name, ?string $text): void {
|
||||
$file = \fopen($name, 'w');
|
||||
\fwrite($file, $text);
|
||||
\fclose($file);
|
||||
self::assertEquals(
|
||||
1,
|
||||
\file_exists($name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the path of the acceptance tests directory
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getAcceptanceTestsDir(): string {
|
||||
return \dirname(__FILE__) . "/../";
|
||||
}
|
||||
|
||||
/**
|
||||
* get the path of the filesForUpload directory
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getFilesForUploadDir(): string {
|
||||
return \dirname(__FILE__) . "/../filesForUpload/";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Helper to administrate users (and groups) through the provisioning API
|
||||
*
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
*/
|
||||
class UserHelper {
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string $user
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param string $adminUser
|
||||
* @param string $adminPassword
|
||||
* @param string $xRequestId
|
||||
* @param int|null $ocsApiVersion
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function editUser(
|
||||
?string $baseUrl,
|
||||
string $user,
|
||||
string $key,
|
||||
string $value,
|
||||
string $adminUser,
|
||||
string $adminPassword,
|
||||
string $xRequestId = '',
|
||||
?int $ocsApiVersion = 2
|
||||
): ResponseInterface {
|
||||
return OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
"PUT",
|
||||
"/cloud/users/" . $user,
|
||||
$xRequestId,
|
||||
["key" => $key, "value" => $value],
|
||||
$ocsApiVersion
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send batch requests to edit the user.
|
||||
* This will send multiple requests in parallel to the server which will be faster in comparison to waiting for each request to complete.
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param array|null $editData ['user' => '', 'key' => '', 'value' => '']
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $ocsApiVersion
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function editUserBatch(
|
||||
?string $baseUrl,
|
||||
?array $editData,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = '',
|
||||
?int $ocsApiVersion = 2
|
||||
): array {
|
||||
$requests = [];
|
||||
$client = HttpRequestHelper::createClient(
|
||||
$adminUser,
|
||||
$adminPassword
|
||||
);
|
||||
|
||||
foreach ($editData as $data) {
|
||||
$path = "/cloud/users/" . $data['user'];
|
||||
$body = ["key" => $data['key'], 'value' => $data["value"]];
|
||||
// Create the OCS API requests and push them to an array.
|
||||
$requests[] = OcsApiHelper::createOcsRequest(
|
||||
$baseUrl,
|
||||
'PUT',
|
||||
$path,
|
||||
$xRequestId,
|
||||
$body
|
||||
);
|
||||
}
|
||||
// Send the array of requests at once in parallel.
|
||||
$results = HttpRequestHelper::sendBatchRequest($requests, $client);
|
||||
|
||||
foreach ($results as $e) {
|
||||
if ($e instanceof ClientException) {
|
||||
$httpStatusCode = $e->getResponse()->getStatusCode();
|
||||
$reasonPhrase = $e->getResponse()->getReasonPhrase();
|
||||
throw new Exception(
|
||||
"Unexpected failure when editing a user: HTTP status $httpStatusCode HTTP reason $reasonPhrase"
|
||||
);
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $userName
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $ocsApiVersion
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getUser(
|
||||
?string $baseUrl,
|
||||
?string $userName,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = '',
|
||||
?int $ocsApiVersion = 2
|
||||
): ResponseInterface {
|
||||
return OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
"GET",
|
||||
"/cloud/users/" . $userName,
|
||||
$xRequestId,
|
||||
[],
|
||||
$ocsApiVersion
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $userName
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $ocsApiVersion
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function deleteUser(
|
||||
?string $baseUrl,
|
||||
?string $userName,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = '',
|
||||
?int $ocsApiVersion = 2
|
||||
): ResponseInterface {
|
||||
return OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
"DELETE",
|
||||
"/cloud/users/" . $userName,
|
||||
$xRequestId,
|
||||
[],
|
||||
$ocsApiVersion
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $group
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $ocsApiVersion (1|2)
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function addUserToGroup(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $group,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = '',
|
||||
?int $ocsApiVersion = 2
|
||||
): ResponseInterface {
|
||||
return OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
"POST",
|
||||
"/cloud/users/" . $user . "/groups",
|
||||
$xRequestId,
|
||||
['groupid' => $group],
|
||||
$ocsApiVersion
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $group
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $ocsApiVersion (1|2)
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function removeUserFromGroup(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $group,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId,
|
||||
?int $ocsApiVersion = 2
|
||||
): ResponseInterface {
|
||||
return OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
"DELETE",
|
||||
"/cloud/users/" . $user . "/groups",
|
||||
$xRequestId,
|
||||
['groupid' => $group],
|
||||
$ocsApiVersion
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $search
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getGroups(
|
||||
?string $baseUrl,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = '',
|
||||
?string $search =""
|
||||
): ResponseInterface {
|
||||
return OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
"GET",
|
||||
"/cloud/groups?search=" . $search,
|
||||
$xRequestId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $search
|
||||
*
|
||||
* @return string[]
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public static function getGroupsAsArray(
|
||||
?string $baseUrl,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = '',
|
||||
?string $search = ""
|
||||
): array {
|
||||
$result = self::getGroups(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
$xRequestId,
|
||||
$search
|
||||
);
|
||||
$groups = HttpRequestHelper::getResponseXml($result, __METHOD__)->xpath(".//groups")[0];
|
||||
$return = [];
|
||||
foreach ($groups as $group) {
|
||||
$return[] = $group->__toString();
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,967 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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 TestHelpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Helper to make WebDav Requests
|
||||
*
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
*/
|
||||
class WebDavHelper {
|
||||
public const DAV_VERSION_OLD = 1;
|
||||
public const DAV_VERSION_NEW = 2;
|
||||
public const DAV_VERSION_SPACES = 3;
|
||||
|
||||
/**
|
||||
* @var array of users with their different space ids
|
||||
*/
|
||||
public static array $spacesIdRef = [];
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function withRemotePhp(): bool {
|
||||
// use remote.php by default
|
||||
return \getenv("WITH_REMOTE_PHP") !== "false";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $urlPath
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function prefixRemotePhp(string $urlPath): string {
|
||||
if (self::withRemotePhp()) {
|
||||
return "remote.php/$urlPath";
|
||||
}
|
||||
return $urlPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isDAVRequest(string $url): bool {
|
||||
$found = \preg_match("/(\bwebdav\b|\bdav\b)/", $url);
|
||||
return (bool)$found;
|
||||
}
|
||||
|
||||
/**
|
||||
* clear space id reference for user
|
||||
*
|
||||
* @param string|null $user
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function removeSpaceIdReferenceForUser(
|
||||
?string $user
|
||||
): void {
|
||||
if (\array_key_exists($user, self::$spacesIdRef)) {
|
||||
unset(self::$spacesIdRef[$user]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespaceString
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function parseNamespace(string $namespaceString): object {
|
||||
//calculate the namespace prefix and namespace
|
||||
$matches = [];
|
||||
\preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches);
|
||||
return (object)["namespace" => $matches[2], "prefix" => $matches[1]];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the id of a file
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $path
|
||||
* @param string|null $spaceId
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $davPathVersionToUse
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public static function getFileIdForPath(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $path,
|
||||
?string $spaceId = null,
|
||||
?string $xRequestId = '',
|
||||
?int $davPathVersionToUse = self::DAV_VERSION_NEW
|
||||
): string {
|
||||
$body
|
||||
= '<?xml version="1.0"?>
|
||||
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||
<d:prop>
|
||||
<oc:fileid />
|
||||
</d:prop>
|
||||
</d:propfind>';
|
||||
$response = self::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PROPFIND",
|
||||
$path,
|
||||
null,
|
||||
$spaceId,
|
||||
$xRequestId,
|
||||
$body,
|
||||
$davPathVersionToUse
|
||||
);
|
||||
\preg_match(
|
||||
'/\<oc:fileid\>([^\<]*)\<\/oc:fileid\>/',
|
||||
$response->getBody()->getContents(),
|
||||
$matches
|
||||
);
|
||||
|
||||
if (!isset($matches[1])) {
|
||||
throw new Exception("could not find fileId of $path");
|
||||
}
|
||||
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns body for propfind
|
||||
*
|
||||
* @param array|null $properties
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getBodyForPropfind(?array $properties): string {
|
||||
$propertyBody = "";
|
||||
$extraNamespaces = "";
|
||||
foreach ($properties as $namespaceString => $property) {
|
||||
if (\is_int($namespaceString)) {
|
||||
//default namespace prefix if the property has no array key
|
||||
//also used if no prefix is given in the property value
|
||||
$namespacePrefix = null;
|
||||
} else {
|
||||
$ns = self::parseNamespace($namespaceString);
|
||||
$namespacePrefix = $ns->prefix;
|
||||
$extraNamespaces .= " xmlns:$namespacePrefix=\"$ns->namespace\" ";
|
||||
}
|
||||
//if a namespace prefix is given in the property value use that
|
||||
if (\strpos($property, ":") !== false) {
|
||||
$propertyParts = \explode(":", $property);
|
||||
$namespacePrefix = $propertyParts[0];
|
||||
$property = $propertyParts[1];
|
||||
}
|
||||
|
||||
if ($namespacePrefix) {
|
||||
$propertyBody .= "<$namespacePrefix:$property/>";
|
||||
} else {
|
||||
$propertyBody .= "<$property/>";
|
||||
}
|
||||
}
|
||||
$body = "<?xml version=\"1.0\"?>
|
||||
<d:propfind
|
||||
xmlns:d=\"DAV:\"
|
||||
xmlns:oc=\"http://owncloud.org/ns\"
|
||||
xmlns:ocs=\"http://open-collaboration-services.org/ns\"
|
||||
$extraNamespaces>
|
||||
<d:prop>$propertyBody</d:prop>
|
||||
</d:propfind>";
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* sends a PROPFIND request
|
||||
* with these registered namespaces:
|
||||
* | prefix | namespace |
|
||||
* | d | DAV: |
|
||||
* | oc | http://owncloud.org/ns |
|
||||
* | ocs | http://open-collaboration-services.org/ns |
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $path
|
||||
* @param string[] $properties
|
||||
* string can contain namespace prefix,
|
||||
* if no prefix is given 'd:' is used as prefix
|
||||
* if an associative array is used, then the key will be used as namespace
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $folderDepth
|
||||
* @param string|null $spaceId
|
||||
* @param string|null $type
|
||||
* @param int|null $davPathVersionToUse
|
||||
* @param string|null $doDavRequestAsUser
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function propfind(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $path,
|
||||
?array $properties,
|
||||
?string $xRequestId = '',
|
||||
?string $folderDepth = '1',
|
||||
?string $spaceId = null,
|
||||
?string $type = "files",
|
||||
?int $davPathVersionToUse = self::DAV_VERSION_NEW,
|
||||
?string $doDavRequestAsUser = null,
|
||||
?array $headers = []
|
||||
): ResponseInterface {
|
||||
$body = self::getBodyForPropfind($properties);
|
||||
$folderDepth = (string) $folderDepth;
|
||||
if ($folderDepth !== '0' && $folderDepth !== '1' && $folderDepth !== 'infinity') {
|
||||
if ($folderDepth !== '') {
|
||||
throw new InvalidArgumentException('Invalid depth value ' . $folderDepth);
|
||||
}
|
||||
$folderDepth = '1'; // КуСфера server's default value
|
||||
}
|
||||
$headers['Depth'] = $folderDepth;
|
||||
return self::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PROPFIND",
|
||||
$path,
|
||||
$headers,
|
||||
$spaceId,
|
||||
$xRequestId,
|
||||
$body,
|
||||
$davPathVersionToUse,
|
||||
$type,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
[],
|
||||
$doDavRequestAsUser
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $path
|
||||
* @param string|null $propertyName
|
||||
* @param string|null $propertyValue
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $namespaceString string containing prefix and namespace
|
||||
* e.g "x1='http://whatever.org/ns'"
|
||||
* @param int|null $davPathVersionToUse
|
||||
* @param string|null $type
|
||||
* @param string|null $spaceId
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function proppatch(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $path,
|
||||
?string $propertyName,
|
||||
?string $propertyValue,
|
||||
?string $xRequestId = '',
|
||||
?string $namespaceString = null,
|
||||
?int $davPathVersionToUse = self::DAV_VERSION_NEW,
|
||||
?string $type="files",
|
||||
?string $spaceId = null,
|
||||
): ResponseInterface {
|
||||
if ($namespaceString !== null) {
|
||||
$ns = self::parseNamespace($namespaceString);
|
||||
$propertyBody = "<$ns->prefix:$propertyName" .
|
||||
" xmlns:$ns->prefix=\"$ns->namespace\">" .
|
||||
"$propertyValue" .
|
||||
"</$ns->prefix:$propertyName>";
|
||||
} else {
|
||||
$propertyBody = "<$propertyName>$propertyValue</$propertyName>";
|
||||
}
|
||||
$body = "<?xml version=\"1.0\"?>
|
||||
<d:propertyupdate xmlns:d=\"DAV:\"
|
||||
xmlns:oc=\"http://owncloud.org/ns\">
|
||||
<d:set>
|
||||
<d:prop>$propertyBody</d:prop>
|
||||
</d:set>
|
||||
</d:propertyupdate>";
|
||||
return self::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PROPPATCH",
|
||||
$path,
|
||||
[],
|
||||
$spaceId,
|
||||
$xRequestId,
|
||||
$body,
|
||||
$davPathVersionToUse,
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets namespace-prefix, namespace url and propName from provided namespaceString or property
|
||||
* or otherwise use default
|
||||
*
|
||||
* @param string $namespaceString
|
||||
* @param string $property
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getPropertyWithNamespaceInfo(string $namespaceString = "", string $property = ""): array {
|
||||
$namespace = "";
|
||||
$namespacePrefix = "";
|
||||
if (\is_int($namespaceString)) {
|
||||
//default namespace prefix if the property has no array key
|
||||
//also used if no prefix is given in the property value
|
||||
$namespacePrefix = "d";
|
||||
$namespace = "DAV:";
|
||||
} elseif ($namespaceString) {
|
||||
$ns = self::parseNamespace($namespaceString);
|
||||
$namespacePrefix = $ns->prefix;
|
||||
$namespace = $ns->namespace;
|
||||
}
|
||||
//if a namespace prefix is given in the property value use that
|
||||
if ($property && \strpos($property, ":")) {
|
||||
$propertyParts = \explode(":", $property);
|
||||
$namespacePrefix = $propertyParts[0];
|
||||
$property = $propertyParts[1];
|
||||
}
|
||||
return [$namespacePrefix, $namespace, $property];
|
||||
}
|
||||
|
||||
/**
|
||||
* sends HTTP request PROPPATCH method with multiple properties
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string $path
|
||||
* @param array|null $propertiesArray
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $davPathVersion
|
||||
* @param string|null $namespaceString
|
||||
* @param string|null $type
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function proppatchWithMultipleProps(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
string $path,
|
||||
?array $propertiesArray,
|
||||
?string $xRequestId = '',
|
||||
?int $davPathVersion = null,
|
||||
?string $namespaceString = null,
|
||||
?string $type="files"
|
||||
): ResponseInterface {
|
||||
$propertyBody = "";
|
||||
foreach ($propertiesArray as $propertyArray) {
|
||||
$property = $propertyArray["propertyName"];
|
||||
$value = $propertyArray["propertyValue"];
|
||||
|
||||
if ($namespaceString !== null) {
|
||||
$matches = [];
|
||||
[$namespacePrefix, $namespace, $property] = self::getPropertyWithNamespaceInfo(
|
||||
$namespaceString,
|
||||
$property
|
||||
);
|
||||
$propertyBody .= "\n\t<$namespacePrefix:$property>" .
|
||||
"$value" .
|
||||
"</$namespacePrefix:$property>";
|
||||
} else {
|
||||
$propertyBody .= "<$property>$value</$property>";
|
||||
}
|
||||
}
|
||||
$body = "<?xml version=\"1.0\"?>
|
||||
<d:propertyupdate xmlns:d=\"DAV:\"
|
||||
xmlns:oc=\"http://owncloud.org/ns\">
|
||||
<d:set>
|
||||
<d:prop>$propertyBody
|
||||
</d:prop>
|
||||
</d:set>
|
||||
</d:propertyupdate>";
|
||||
return self::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PROPPATCH",
|
||||
$path,
|
||||
[],
|
||||
null,
|
||||
$xRequestId,
|
||||
$body,
|
||||
$davPathVersion,
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the response to listing a folder (collection)
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $path
|
||||
* @param string|null $folderDepth
|
||||
* @param string|null $spaceId
|
||||
* @param string|null $xRequestId
|
||||
* @param string[] $properties
|
||||
* @param string|null $type
|
||||
* @param int|null $davPathVersionToUse
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function listFolder(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $path,
|
||||
?string $folderDepth,
|
||||
?string $spaceId = null,
|
||||
?string $xRequestId = '',
|
||||
?array $properties = null,
|
||||
?string $type = "files",
|
||||
?int $davPathVersionToUse = self::DAV_VERSION_NEW
|
||||
): ResponseInterface {
|
||||
if (!$properties) {
|
||||
$properties = [
|
||||
'd:getetag', 'd:resourcetype'
|
||||
];
|
||||
}
|
||||
return self::propfind(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
$path,
|
||||
$properties,
|
||||
$xRequestId,
|
||||
$folderDepth,
|
||||
$spaceId,
|
||||
$type,
|
||||
$davPathVersionToUse
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates UUIDv4
|
||||
* Example: 123e4567-e89b-12d3-a456-426614174000
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function generateUUIDv4(): string {
|
||||
// generate 16 bytes (128 bits) of random data or use the data passed into the function.
|
||||
$data = random_bytes(16);
|
||||
\assert(\strlen($data) == 16);
|
||||
|
||||
$data[6] = \chr(\ord($data[6]) & 0x0f | 0x40); // set version to 0100
|
||||
$data[8] = \chr(\ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
|
||||
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||
}
|
||||
|
||||
/**
|
||||
* fetches personal space id for provided user
|
||||
*
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getPersonalSpaceIdForUser(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId
|
||||
): string {
|
||||
if (\array_key_exists($user, self::$spacesIdRef) && \array_key_exists("personal", self::$spacesIdRef[$user])) {
|
||||
return self::$spacesIdRef[$user]["personal"];
|
||||
}
|
||||
|
||||
$personalSpaceId = '';
|
||||
if (!OcHelper::isTestingOnReva()) {
|
||||
$response = GraphHelper::getMySpaces($baseUrl, $user, $password, '', $xRequestId);
|
||||
Assert::assertEquals(200, $response->getStatusCode(), "Cannot list drives for user '$user'");
|
||||
|
||||
$drives = HttpRequestHelper::getJsonDecodedResponseBodyContent($response);
|
||||
foreach ($drives->value as $drive) {
|
||||
if ($drive->driveType === "personal") {
|
||||
$personalSpaceId = $drive->id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$personalSpaceId) {
|
||||
// the graph endpoint did not give a useful answer
|
||||
// try getting the information from the webdav endpoint
|
||||
$fullUrl = "$baseUrl/" . self::getDavPath(self::DAV_VERSION_NEW, $user);
|
||||
$response = HttpRequestHelper::sendRequest(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
'PROPFIND',
|
||||
$user,
|
||||
$password
|
||||
);
|
||||
Assert::assertEquals(
|
||||
207,
|
||||
$response->getStatusCode(),
|
||||
"PROPFIND for user '$user' failed so the personal space id cannot be discovered"
|
||||
);
|
||||
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml(
|
||||
$response,
|
||||
__METHOD__
|
||||
);
|
||||
$xmlPart = $responseXmlObject->xpath("/d:multistatus/d:response[1]/d:propstat/d:prop/oc:spaceid");
|
||||
Assert::assertNotEmpty(
|
||||
$xmlPart,
|
||||
"The 'oc:spaceid' for user '$user' was not found in the PROPFIND response"
|
||||
);
|
||||
|
||||
$personalSpaceId = $xmlPart[0]->__toString();
|
||||
}
|
||||
|
||||
Assert::assertNotEmpty($personalSpaceId, "The personal space id for user '$user' was not found");
|
||||
|
||||
self::$spacesIdRef[$user] = [];
|
||||
self::$spacesIdRef[$user]["personal"] = $personalSpaceId;
|
||||
return $personalSpaceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* First checks if a user exists to return its space ID
|
||||
* In case of any exception, it returns a fake space ID
|
||||
*
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public static function getPersonalSpaceIdForUserOrFakeIfNotFound(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId
|
||||
): string {
|
||||
if (\str_starts_with($user, "non-exist") || \str_starts_with($user, "nonexist")) {
|
||||
return self::generateUUIDv4();
|
||||
}
|
||||
|
||||
return self::getPersonalSpaceIdForUser(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
$xRequestId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* sends a DAV request
|
||||
*
|
||||
* @param string|null $baseUrl URL of КуСфера e.g. http://localhost:8080
|
||||
* @param string|null $user
|
||||
* @param string|null $password or token when bearer auth is used
|
||||
* @param string|null $method PUT, GET, DELETE, etc.
|
||||
* @param string|null $path
|
||||
* @param array|null $headers
|
||||
* @param string|null $spaceId
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null|resource|StreamInterface $body
|
||||
* @param int|null $davPathVersionToUse (1|2|3)
|
||||
* @param string|null $type of request
|
||||
* @param string|null $sourceIpAddress to initiate the request from
|
||||
* @param string|null $authType basic|bearer
|
||||
* @param bool $stream Set to true to stream a response rather
|
||||
* than download it all up-front.
|
||||
* @param int|null $timeout
|
||||
* @param Client|null $client
|
||||
* @param array|null $urlParameter to concatenate with path
|
||||
* @param string|null $doDavRequestAsUser run the DAV as this user, if null it is the same as $user
|
||||
* @param bool $isGivenStep is set to true if makeDavRequest is called from a "given" step
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function makeDavRequest(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $method,
|
||||
?string $path,
|
||||
?array $headers,
|
||||
?string $spaceId = null,
|
||||
?string $xRequestId = '',
|
||||
$body = null,
|
||||
?int $davPathVersionToUse = self::DAV_VERSION_OLD,
|
||||
?string $type = "files",
|
||||
?string $sourceIpAddress = null,
|
||||
?string $authType = "basic",
|
||||
?bool $stream = false,
|
||||
?int $timeout = 0,
|
||||
?Client $client = null,
|
||||
?array $urlParameter = [],
|
||||
?string $doDavRequestAsUser = null,
|
||||
?bool $isGivenStep = false,
|
||||
): ResponseInterface {
|
||||
$baseUrl = self::sanitizeUrl($baseUrl, true);
|
||||
|
||||
// We need to manipulate and use path as a string.
|
||||
// So ensure that it is a string to avoid any type-conversion errors.
|
||||
if ($path === null) {
|
||||
$path = "";
|
||||
}
|
||||
|
||||
// get space id if testing with spaces dav
|
||||
if ($spaceId === null && $davPathVersionToUse === self::DAV_VERSION_SPACES
|
||||
&& !\in_array($type, ["public-files", "versions"])
|
||||
) {
|
||||
$path = \ltrim($path, "/");
|
||||
if (\str_starts_with($path, "Shares/")) {
|
||||
$spaceId = GraphHelper::SHARES_SPACE_ID;
|
||||
$path = "/" . preg_replace("/^Shares\//", "", $path);
|
||||
} else {
|
||||
$spaceId = self::getPersonalSpaceIdForUserOrFakeIfNotFound(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
$xRequestId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$suffixPath = $user;
|
||||
if ($davPathVersionToUse === self::DAV_VERSION_SPACES
|
||||
&& !\in_array($type, ["archive", "versions", "public-files"])
|
||||
) {
|
||||
$suffixPath = $spaceId;
|
||||
} elseif ($type === "versions") {
|
||||
// $path is file-id in case of versions
|
||||
$suffixPath = $path;
|
||||
}
|
||||
|
||||
$davPath = self::getDavPath($davPathVersionToUse, $suffixPath, $type);
|
||||
|
||||
//replace %, # and ? and in the path, Guzzle will not encode them
|
||||
$urlSpecialChar = [['%', '#', '?'], ['%25', '%23', '%3F']];
|
||||
$path = \str_replace($urlSpecialChar[0], $urlSpecialChar[1], $path);
|
||||
|
||||
if (!empty($urlParameter)) {
|
||||
$urlParameter = \http_build_query($urlParameter, '', '&');
|
||||
$path .= '?' . $urlParameter;
|
||||
}
|
||||
$fullUrl = self::sanitizeUrl("$baseUrl/$davPath");
|
||||
// NOTE: no need to append path for archive and versions endpoints
|
||||
if (!\in_array($type, ["archive", "versions"])) {
|
||||
$fullUrl .= "/" . \ltrim($path, "/");
|
||||
}
|
||||
|
||||
if ($authType === 'bearer') {
|
||||
$headers['Authorization'] = 'Bearer ' . $password;
|
||||
$user = null;
|
||||
$password = null;
|
||||
}
|
||||
if ($type === "public-files") {
|
||||
if ($password === null || $password === "") {
|
||||
$user = null;
|
||||
} else {
|
||||
$user = "public";
|
||||
}
|
||||
}
|
||||
$config = null;
|
||||
if ($sourceIpAddress !== null) {
|
||||
$config = [ 'curl' => [ CURLOPT_INTERFACE => $sourceIpAddress ]];
|
||||
}
|
||||
|
||||
if ($headers !== null) {
|
||||
foreach ($headers as $key => $value) {
|
||||
//? and # need to be encoded in the Destination URL
|
||||
if ($key === "Destination") {
|
||||
$headers[$key] = \str_replace(
|
||||
$urlSpecialChar[0],
|
||||
$urlSpecialChar[1],
|
||||
$value
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$method,
|
||||
$doDavRequestAsUser ?? $user,
|
||||
$password,
|
||||
$headers,
|
||||
$body,
|
||||
$config,
|
||||
null,
|
||||
$stream,
|
||||
$timeout,
|
||||
$client,
|
||||
$isGivenStep
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the dav path
|
||||
*
|
||||
* @param int $davPathVersion (1|2|3)
|
||||
* @param string|null $userOrItemIdOrSpaceIdOrToken 'user' or 'file-id' or 'space-id' or 'public-token'
|
||||
* @param string|null $type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDavPath(
|
||||
int $davPathVersion,
|
||||
?string $userOrItemIdOrSpaceIdOrToken = null,
|
||||
?string $type = "files"
|
||||
): string {
|
||||
switch ($type) {
|
||||
case 'archive':
|
||||
return self::prefixRemotePhp("dav/archive/$userOrItemIdOrSpaceIdOrToken/files");
|
||||
case 'versions':
|
||||
return self::prefixRemotePhp("dav/meta/$userOrItemIdOrSpaceIdOrToken/v");
|
||||
case 'comments':
|
||||
return self::prefixRemotePhp("dav/comments/files");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ($davPathVersion === self::DAV_VERSION_SPACES) {
|
||||
if ($type === "trash-bin") {
|
||||
if ($userOrItemIdOrSpaceIdOrToken === null) {
|
||||
throw new InvalidArgumentException("Space ID is required for trash-bin endpoint");
|
||||
}
|
||||
return self::prefixRemotePhp("dav/spaces/trash-bin/$userOrItemIdOrSpaceIdOrToken");
|
||||
} elseif ($type === "public-files") {
|
||||
// spaces DAV path doesn't have own public-files endpoint
|
||||
return self::prefixRemotePhp("dav/public-files/$userOrItemIdOrSpaceIdOrToken");
|
||||
}
|
||||
// return spaces root path if spaceid is null
|
||||
// REPORT request uses spaces root path
|
||||
if ($userOrItemIdOrSpaceIdOrToken === null) {
|
||||
return self::prefixRemotePhp("dav/spaces");
|
||||
}
|
||||
return self::prefixRemotePhp("dav/spaces/$userOrItemIdOrSpaceIdOrToken");
|
||||
} else {
|
||||
if ($type === "trash-bin") {
|
||||
// Since there is no trash bin endpoint for old dav version,
|
||||
// new dav version's endpoint is used here.
|
||||
return self::prefixRemotePhp("dav/trash-bin/$userOrItemIdOrSpaceIdOrToken");
|
||||
}
|
||||
if ($davPathVersion === self::DAV_VERSION_OLD) {
|
||||
return self::prefixRemotePhp("webdav");
|
||||
} elseif ($davPathVersion === self::DAV_VERSION_NEW) {
|
||||
if ($type === "files") {
|
||||
return self::prefixRemotePhp("dav/files/$userOrItemIdOrSpaceIdOrToken");
|
||||
} elseif ($type === "public-files") {
|
||||
return self::prefixRemotePhp("dav/public-files/$userOrItemIdOrSpaceIdOrToken");
|
||||
}
|
||||
return self::prefixRemotePhp("dav");
|
||||
}
|
||||
throw new InvalidArgumentException("Invalid DAV path: $davPathVersion");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* make sure there are no double-slashes in the URL
|
||||
*
|
||||
* @param string|null $url
|
||||
* @param bool|null $trailingSlash forces a trailing slash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function sanitizeUrl(?string $url, ?bool $trailingSlash = false): string {
|
||||
if ($trailingSlash === true) {
|
||||
$url = $url . "/";
|
||||
} else {
|
||||
$url = \rtrim($url, "/");
|
||||
}
|
||||
return \preg_replace("/([^:]\/)\/+/", '$1', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* get Mtime of File in a public link share
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $fileName
|
||||
* @param string|null $token
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $davVersionToUse
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public static function getMtimeOfFileInPublicLinkShare(
|
||||
?string $baseUrl,
|
||||
?string $fileName,
|
||||
?string $token,
|
||||
?string $xRequestId = '',
|
||||
?int $davVersionToUse = self::DAV_VERSION_NEW
|
||||
): string {
|
||||
$response = self::propfind(
|
||||
$baseUrl,
|
||||
null,
|
||||
null,
|
||||
"$token/$fileName",
|
||||
['d:getlastmodified'],
|
||||
$xRequestId,
|
||||
'1',
|
||||
null,
|
||||
"public-files",
|
||||
$davVersionToUse
|
||||
);
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml(
|
||||
$response,
|
||||
__METHOD__
|
||||
);
|
||||
$xmlPart = $responseXmlObject->xpath("//d:getlastmodified");
|
||||
|
||||
return $xmlPart[0]->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* get Mtime of a resource
|
||||
*
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $resource
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $davPathVersionToUse
|
||||
* @param string|null $spaceId
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getMtimeOfResource(
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $baseUrl,
|
||||
?string $resource,
|
||||
?string $xRequestId = '',
|
||||
?int $davPathVersionToUse = self::DAV_VERSION_NEW,
|
||||
?string $spaceId = null,
|
||||
): string {
|
||||
$response = self::propfind(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
$resource,
|
||||
["d:getlastmodified"],
|
||||
$xRequestId,
|
||||
"0",
|
||||
$spaceId,
|
||||
"files",
|
||||
$davPathVersionToUse
|
||||
);
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml(
|
||||
$response,
|
||||
__METHOD__
|
||||
);
|
||||
$xmlPart = $responseXmlObject->xpath("//d:getlastmodified");
|
||||
Assert::assertArrayHasKey(
|
||||
0,
|
||||
$xmlPart,
|
||||
__METHOD__
|
||||
. " XML part does not have key 0. Expected a value at index 0 of 'xmlPart' but, found: "
|
||||
. json_encode($xmlPart)
|
||||
);
|
||||
$mtime = new DateTime($xmlPart[0]->__toString());
|
||||
return $mtime->format('U');
|
||||
}
|
||||
|
||||
/**
|
||||
* wait until the reqeust doesn't return 425 anymore
|
||||
*
|
||||
* @param string $url
|
||||
* @param ?string $user
|
||||
* @param ?string $password
|
||||
* @param ?array $headers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function waitForPostProcessingToFinish(
|
||||
string $url,
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = [],
|
||||
): void {
|
||||
$retried = 0;
|
||||
do {
|
||||
$response = HttpRequestHelper::sendRequest(
|
||||
$url,
|
||||
'check-425-status',
|
||||
'GET',
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
);
|
||||
$statusCode = $response->getStatusCode();
|
||||
if ($statusCode !== 425) {
|
||||
return;
|
||||
}
|
||||
$tryAgain = $retried < HttpRequestHelper::numRetriesOnHttpTooEarly();
|
||||
if ($tryAgain) {
|
||||
$retried += 1;
|
||||
echo "[INFO] Waiting for post processing to finish, attempt ($retried)...\n";
|
||||
// wait 1s and try again
|
||||
\sleep(1);
|
||||
}
|
||||
} while ($tryAgain);
|
||||
echo "[ERROR] 10 seconds timeout! Post processing did not finish in time.\n";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user