Compare commits

..

15 Commits

Author SHA1 Message Date
OpenCloud Devops
239ce6a794 🎉 Release 4.0.3 (#2281) 2026-02-05 17:11:52 +01:00
Andre Duffeck
4f36445048 Merge pull request #2275 from aduffeck/bump-reva-2.40.3
Bump reva 2.40.3
2026-02-05 16:14:26 +01:00
André Duffeck
312eacd46a Bump latest tag 2026-02-05 15:35:05 +01:00
André Duffeck
3c906e1dae Bump reva 2026-02-05 15:34:11 +01:00
OpenCloud Devops
fc5ee78dc8 🎉 Release 4.0.2 (#2037)
* 🎉 Release 4.0.2

* 🎉 Release 4.0.2

* 🎉 Release 4.0.2

* 🎉 Release 4.0.2

* 🎉 Release 4.0.2
2026-02-05 14:52:46 +01:00
Andre Duffeck
913c51d8d5 Merge pull request #2274 from aduffeck/bump-reva-v2.40.2
Bump reva
2026-02-05 12:53:12 +01:00
André Duffeck
052ee8910d Bump reva 2026-02-05 11:45:04 +01:00
Viktor Scharf
ed285049dc [tests-only] port tests to stable #2087 #2039 (#2185)
* [tests-only] test: fix API tests (#2087)

* test: use placeholder value

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* test: fix antivirus test expectations

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

---------

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* skip collaborativePosix tests in CI (#2039)

---------

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>
Co-authored-by: Sawjan Gurung <saw.jan.grg3e@gmail.com>
2026-01-21 07:25:05 +01:00
Sawjan Gurung
867da079a8 [full-ci][tests-only] test: fix some test flakiness (#2003)
* test: check content after upload

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

test: check content with retry

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* test: check empty body before json decoding

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* test: wait post-processing for webdav requests if applicable

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* test: check token before doing request

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

test: check body before json decoding

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

test: add wait step

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

---------

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>
2025-12-16 14:43:39 +05:45
Sawjan Gurung
389638878a Merge pull request #2007 from opencloud-eu/ci/fix-translation-pipeline
ci: fix translation pipeline
2025-12-16 14:43:39 +05:45
Saw-jan
949f14a278 test: add test to check mismatch offset during TUS upload
Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>
2025-12-16 14:43:39 +05:45
Saw-jan
27450c97bb test: proper resource existence check
Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>
2025-12-16 14:43:39 +05:45
OpenCloud Devops
ac072bee8f 🎉 Release 4.0.1 (#1984)
* 🎉 Release 4.0.1

* 🎉 Release 4.0.1

* 🎉 Release 4.0.1

* 🎉 Release 4.0.1

* 🎉 Release 4.0.1

* 🎉 Release 4.0.1
2025-12-15 14:52:02 +01:00
Viktor Scharf
fa4cd8e279 bump-version-4.0.1 (#2028)
apiAntivirus/antivirus.feature:228 was green in the previous run
2025-12-15 14:39:51 +01:00
Prashant Gurung
7bdbd9c474 skip test related pipelines for ready-release-go PRs (#2011) (#2018)
Signed-off-by: prashant-gurung899 <prasantgrg777@gmail.com>
2025-12-12 17:29:13 +05:45
25 changed files with 487 additions and 229 deletions

View File

@@ -137,7 +137,8 @@ config = {
"suites": [
"apiGraph",
"apiServiceAvailability",
"collaborativePosix",
# skip tests for collaborativePosix. see https://github.com/opencloud-eu/opencloud/issues/2036
#"collaborativePosix",
],
"skip": False,
"withRemotePhp": [True],
@@ -479,6 +480,10 @@ def main(ctx):
if ctx.build.event == "cron" and ctx.build.sender == "translation-sync":
return translation_sync(ctx)
is_release_pr = (ctx.build.event == "pull_request" and ctx.build.sender == "openclouders" and "🎉 release" in ctx.build.title.lower())
if is_release_pr:
return [licenseCheck(ctx)]
build_release_helpers = \
readyReleaseGo()
@@ -2343,11 +2348,12 @@ def translation_sync(ctx):
"image": OC_CI_GOLANG,
"commands": [
"make l10n-read",
"mkdir tx && cd tx",
"curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash",
". ~/.profile",
"export PATH=$PATH:$(pwd) && cd ..",
"make l10n-push",
"make l10n-pull",
"rm tx",
"rm -rf tx",
"make l10n-clean",
],
"environment": {

View File

@@ -1,5 +1,53 @@
# Changelog
## [4.0.3](https://github.com/opencloud-eu/opencloud/releases/tag/v4.0.3) - 2026-02-05
### ❤️ Thanks to all contributors! ❤️
@aduffeck
### 🐛 Bug Fixes
- Bump reva 2.40.3 [[#2275](https://github.com/opencloud-eu/opencloud/pull/2275)]
## [4.0.2](https://github.com/opencloud-eu/opencloud/releases/tag/v4.0.2) - 2026-02-05
### ❤️ Thanks to all contributors! ❤️
@ScharfViktor, @aduffeck, @fschade, @kulmann, @micbar, @prashant-gurung899, @saw-jan
### ✅ Tests
- [tests-only] port tests to stable #2087 #2039 [[#2185](https://github.com/opencloud-eu/opencloud/pull/2185)]
- [full-ci][tests-only] port test fixes [[#2017](https://github.com/opencloud-eu/opencloud/pull/2017)]
- [stable-4.0] Port #2011 [[#2018](https://github.com/opencloud-eu/opencloud/pull/2018)]
### 🐛 Bug Fixes
- [stable-4.0] fix: build time edition channels #2001 [[#2010](https://github.com/opencloud-eu/opencloud/pull/2010)]
- [stable-4.0] fix: enforce trailing slash for server url [[#2002](https://github.com/opencloud-eu/opencloud/pull/2002)]
- [stable-4.0] fix: enhance resource creation with detailed process information (#1978) [[#2000](https://github.com/opencloud-eu/opencloud/pull/2000)]
### 📦️ Dependencies
- Bump reva [[#2274](https://github.com/opencloud-eu/opencloud/pull/2274)]
## [4.0.1](https://github.com/opencloud-eu/opencloud/releases/tag/v4.0.1) - 2025-12-15
### ❤️ Thanks to all contributors! ❤️
@ScharfViktor, @fschade, @kulmann, @micbar, @prashant-gurung899
### ✅ Tests
- [stable-4.0] Port #2011 [[#2018](https://github.com/opencloud-eu/opencloud/pull/2018)]
### 🐛 Bug Fixes
- [stable-4.0] fix: build time edition channels #2001 [[#2010](https://github.com/opencloud-eu/opencloud/pull/2010)]
- [stable-4.0] fix: enforce trailing slash for server url [[#2002](https://github.com/opencloud-eu/opencloud/pull/2002)]
- [stable-4.0] fix: enhance resource creation with detailed process information (#1978) [[#2000](https://github.com/opencloud-eu/opencloud/pull/2000)]
## [4.0.0](https://github.com/opencloud-eu/opencloud/releases/tag/v4.0.0) - 2025-12-01
### ❤️ Thanks to all contributors! ❤️

2
go.mod
View File

@@ -64,7 +64,7 @@ require (
github.com/open-policy-agent/opa v1.10.1
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76
github.com/opencloud-eu/reva/v2 v2.40.1
github.com/opencloud-eu/reva/v2 v2.40.3
github.com/opensearch-project/opensearch-go/v4 v4.5.0
github.com/orcaman/concurrent-map v1.0.0
github.com/pkg/errors v0.9.1

4
go.sum
View File

@@ -963,8 +963,8 @@ github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9 h1:dIft
github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9/go.mod h1:JWyDC6H+5oZRdUJUgKuaye+8Ph5hEs6HVzVoPKzWSGI=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76 h1:vD/EdfDUrv4omSFjrinT8Mvf+8D7f9g4vgQ2oiDrVUI=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
github.com/opencloud-eu/reva/v2 v2.40.1 h1:QwMkbGMhwDSwfk2WxbnTpIig2BugPBaVFjWcy2DSU3U=
github.com/opencloud-eu/reva/v2 v2.40.1/go.mod h1:DGH08n2mvtsQLkt8o15FV6m51FwSJJGhjR8Ty+iIJww=
github.com/opencloud-eu/reva/v2 v2.40.3 h1:gFiBzI/Mq10zekZXvAXOGWRPzTjzHVhBt5W1B5JJMnE=
github.com/opencloud-eu/reva/v2 v2.40.3/go.mod h1:DGH08n2mvtsQLkt8o15FV6m51FwSJJGhjR8Ty+iIJww=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=

View File

@@ -34,7 +34,7 @@ var (
// LatestTag is the latest released version plus the dev meta version.
// Will be overwritten by the release pipeline
// Needs a manual change for every tagged release
LatestTag = "4.0.0-rc.3+dev"
LatestTag = "4.0.3+dev"
// Date indicates the build date.
// This has been removed, it looks like you can only replace static strings with recent go versions

View File

@@ -33,6 +33,7 @@ use SimpleXMLElement;
use Sabre\Xml\LibXMLException;
use Sabre\Xml\Reader;
use GuzzleHttp\Pool;
use Symfony\Component\HttpFoundation\Response;
/**
* Helper for HTTP requests
@@ -74,7 +75,6 @@ class HttpRequestHelper {
* than download it all up-front.
* @param int|null $timeout
* @param Client|null $client
* @param string|null $bearerToken
*
* @return ResponseInterface
* @throws GuzzleException
@@ -92,8 +92,42 @@ class HttpRequestHelper {
bool $stream = false,
?int $timeout = 0,
?Client $client = null,
?string $bearerToken = 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/use/$user";
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,
@@ -160,6 +194,24 @@ class HttpRequestHelper {
}
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"])
&& \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;
}
@@ -203,13 +255,6 @@ class HttpRequestHelper {
} else {
$debugResponses = false;
}
// use basic auth for 'public' user or no user
if ($user === 'public' || $user === null || $user === '') {
$bearerToken = null;
} else {
$useBearerToken = TokenHelper::useBearerToken();
$bearerToken = $useBearerToken ? TokenHelper::getTokens($user, $password, $url)['access_token'] : null;
}
$sendRetryLimit = self::numRetriesOnHttpTooEarly();
$sendCount = 0;
@@ -228,7 +273,6 @@ class HttpRequestHelper {
$stream,
$timeout,
$client,
$bearerToken,
);
if ($response->getStatusCode() >= 400
@@ -256,7 +300,8 @@ class HttpRequestHelper {
// 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.
self::debugResponse($response);
echo "[INFO] Received '" . $response->getStatusCode() .
"' status code, retrying request ($sendCount)...\n";
\sleep(1);
}
} while ($loopAgain);

View File

@@ -30,6 +30,26 @@ use Psr\Http\Message\ResponseInterface;
* A helper class for configuring OpenCloud 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

View File

@@ -84,7 +84,9 @@ class TokenHelper {
$tokenData = [
'access_token' => $refreshedToken['access_token'],
'refresh_token' => $refreshedToken['refresh_token'],
'expires_at' => time() + 300 // 5 minutes
// 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;
@@ -100,7 +102,9 @@ class TokenHelper {
$tokenData = [
'access_token' => $tokens['access_token'],
'refresh_token' => $tokens['refresh_token'],
'expires_at' => time() + 290 // set expiry to 290 seconds to allow for some buffer
// 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

View File

@@ -923,4 +923,45 @@ class WebDavHelper {
$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";
}
}

View File

@@ -2026,8 +2026,12 @@ class FeatureContext extends BehatVariablesContext {
if ($response === null) {
$response = $this->getResponse();
}
$body = (string)$response->getBody();
if (!$body) {
return [];
}
return \json_decode(
(string)$response->getBody(),
$body,
true
);
}

View File

@@ -377,7 +377,7 @@ class NotificationContext implements Context {
}
/**
* @Then /^user "([^"]*)" should (?:get|have) a notification with subject "([^"]*)" and message:$/
* @Then /^user "([^"]*)" should get a notification with subject "([^"]*)" and message:$/
*
* @param string $user
* @param string $subject
@@ -411,10 +411,10 @@ class NotificationContext implements Context {
throw new \Exception("Notification was not found even after retrying for 5 seconds.");
}
$expectedMessage = $table->getColumnsHash()[0]['message'];
Assert::assertSame(
Assert::assertStringStartsWith(
$expectedMessage,
$actualMessage,
__METHOD__ . "expected message to be '$expectedMessage' but found'$actualMessage'"
__METHOD__ . "expected message to start with '$expectedMessage' but found'$actualMessage'"
);
}
@@ -441,10 +441,10 @@ class NotificationContext implements Context {
if (\count($notification) === 1) {
$actualMessage = str_replace(["\r", "\r"], " ", $notification[0]->message);
$expectedMessage = $table->getColumnsHash()[0]['message'];
Assert::assertSame(
Assert::assertStringStartsWith(
$expectedMessage,
$actualMessage,
__METHOD__ . "expected message to be '$expectedMessage' but found'$actualMessage'"
__METHOD__ . "expected message to start with '$expectedMessage' but found'$actualMessage'"
);
$response = $this->userDeletesNotification($user);
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
@@ -462,7 +462,7 @@ class NotificationContext implements Context {
}
/**
* @Then user :user should not have a notification related to resource :resource with subject :subject
* @Then user :user should not get a notification related to resource :resource with subject :subject
*
* @param string $user
* @param string $resource

View File

@@ -68,6 +68,7 @@ class OcConfigContext implements Context {
$response->getStatusCode(),
"Failed to set async upload with delayed post processing"
);
OcConfigHelper::setPostProcessingDelay($delayTime);
}
/**
@@ -90,6 +91,9 @@ class OcConfigContext implements Context {
$response->getStatusCode(),
"Failed to set config $configVariable=$configValue"
);
if ($configVariable === "POSTPROCESSING_DELAY") {
OcConfigHelper::setPostProcessingDelay($configValue);
}
}
/**
@@ -184,6 +188,9 @@ class OcConfigContext implements Context {
$envs = [];
foreach ($table->getHash() as $row) {
$envs[$row['config']] = $row['value'];
if ($row['config'] === "POSTPROCESSING_DELAY") {
OcConfigHelper::setPostProcessingDelay($row['value']);
}
}
$response = OcConfigHelper::reConfigureOc($envs);
@@ -200,6 +207,7 @@ class OcConfigContext implements Context {
* @return void
*/
public function rollbackOc(): void {
OcConfigHelper::setPostProcessingDelay('0');
$response = OcConfigHelper::rollbackOc();
Assert::assertEquals(
200,

View File

@@ -607,7 +607,7 @@ trait Provisioning {
Assert::assertEquals(
201,
$response->getStatusCode(),
__METHOD__ . " cannot create user '$userName' using Graph API.\nResponse:" .
__METHOD__ . " cannot create user '$userName'.\nResponse:" .
json_encode($this->getJsonDecodedResponse($response))
);
@@ -1083,7 +1083,7 @@ trait Provisioning {
Assert::assertEquals(
201,
$response->getStatusCode(),
__METHOD__ . " cannot create user '$user' using Graph API.\nResponse:" .
__METHOD__ . " cannot create user '$user'.\nResponse:" .
json_encode($this->getJsonDecodedResponse($response))
);
$userId = $this->getJsonDecodedResponse($response)['id'];

View File

@@ -750,6 +750,9 @@ class SpacesContext implements Context {
} else {
$rawBody = $this->featureContext->getResponse()->getBody()->getContents();
}
if (!$rawBody) {
throw new Exception(__METHOD__ . " - Response body is empty");
}
$drives = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
if (isset($drives["value"])) {
$drives = $drives["value"];

View File

@@ -216,6 +216,44 @@ class TUSContext implements Context {
);
}
/**
* @When user :user sends a chunk to the last created TUS Location with offset :offset and data :data with retry on offset mismatch using the WebDAV API
*
* @param string $user
* @param string $offset
* @param string $data
*
* @return void
*
* @throws GuzzleException
* @throws JsonException
*/
public function userSendsAChunkToTUSLocationWithOffsetAndDataWithRetryOnOffsetMismatch(
string $user,
string $offset,
string $data,
): void {
$resourceLocation = $this->getLastTusResourceLocation();
$retried = 0;
do {
$tryAgain = false;
$response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data);
// retry on 409 Conflict (Offset mismatch during TUS upload)
if ($response->getStatusCode() === 409) {
$tryAgain = true;
}
$tryAgain = $tryAgain && $retried < HttpRequestHelper::numRetriesOnHttpTooEarly();
if ($tryAgain) {
$retried += 1;
echo "Offset mismatch during TUS upload, retrying ($retried)...\n";
// wait 1s and try again
\sleep(1);
}
} while ($tryAgain);
$this->featureContext->setResponse($response);
}
/**
* @When user :user sends a chunk to the last created TUS Location with offset :offset and data :data using the WebDAV API
*

View File

@@ -25,6 +25,7 @@ use GuzzleHttp\Exception\GuzzleException;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Stream\StreamInterface;
use TestHelpers\OcConfigHelper;
use TestHelpers\OcHelper;
use TestHelpers\UploadHelper;
use TestHelpers\WebDavHelper;
@@ -743,6 +744,7 @@ trait WebDav {
/**
* @When the user waits for :time seconds for postprocessing to finish
* @When the user waits for :time seconds
*
* @param int $time
*
@@ -973,6 +975,61 @@ trait WebDav {
$this->checkDownloadedContentMatches($content, '', $response);
}
/**
* check file content with retry
*
* @param string $user
* @param string $fileName
* @param string $content
*
* @return void
* @throws Exception
*/
public function checkFileContentWithRetry(string $user, string $fileName, string $content): void {
$retried = 0;
do {
$tryAgain = false;
$response = $this->downloadFileAsUserUsingPassword($this->getActualUsername($user), $fileName);
$status = $response->getStatusCode();
$downloadedContent = $response->getBody()->getContents();
if ($status !== 200) {
$tryAgain = true;
$message = "Expected '200' but got '$status'";
} elseif ($downloadedContent !== $content) {
$tryAgain = true;
$message = "Expected content '$content' but got '$downloadedContent'";
}
$tryAgain = $tryAgain && $retried < HttpRequestHelper::numRetriesOnHttpTooEarly();
if ($tryAgain) {
$retried += 1;
echo "[INFO] File content mismatch. $message, checking content again ($retried)...\n";
// break the loop if status is 425 as the request will already be retried
if ($status === HttpRequestHelper::HTTP_TOO_EARLY) {
break;
}
// wait 1s and try again
\sleep(1);
}
} while ($tryAgain);
$this->theHTTPStatusCodeShouldBe(200, '', $response);
$this->checkDownloadedContentMatches($content, '', $response);
}
/**
* @Then as :user the final content of file :fileName should be :content
*
* @param string $user
* @param string $fileName
* @param string $content
*
* @return void
*/
public function asUserFinalContentOfFileShouldBe(string $user, string $fileName, string $content): void {
$this->checkFileContentWithRetry($user, $fileName, $content);
}
/**
* @Then /^the content of the following files for user "([^"]*)" should be "([^"]*)"$/
*
@@ -1264,20 +1321,18 @@ trait WebDav {
$type
);
$statusCode = $response->getStatusCode();
if ($statusCode < 400 || $statusCode > 499) {
try {
$responseXmlObject = HttpRequestHelper::getResponseXml(
$response,
__METHOD__
);
} catch (Exception $e) {
Assert::fail(
"$entry '$path' should not exist. But API returned $statusCode without XML in the body"
);
}
// when checking path with '..' it may return 405 Method Not Allowed
if ($statusCode === 404 || $statusCode === 405) {
return;
}
if ($statusCode === 207) {
$responseXmlObject = HttpRequestHelper::getResponseXml(
$response,
__METHOD__
);
Assert::assertTrue(
$this->isEtagValid($this->getEtagFromResponseXmlObject($responseXmlObject)),
"$entry '$path' should not exist. But API returned $statusCode without an etag in the body"
"$entry '$path' should not exist but found with invalid etag."
);
$isCollection = $responseXmlObject->xpath("//d:prop/d:resourcetype/d:collection");
if (\count($isCollection) === 0) {
@@ -1291,7 +1346,11 @@ trait WebDav {
"$entry '$path' should not exist. But it does."
);
}
return;
}
Assert::fail(
"$entry '$path' should not exist. But API returned $statusCode without XML in the body"
);
}
/**
@@ -2270,6 +2329,11 @@ trait WebDav {
"HTTP status code was not 201 or 204 while trying to upload file '$destination' for user '$user'",
$response
);
// check uploaded content only if post-processing delay is not configured
if (OcConfigHelper::getPostProcessingDelay() === 0) {
$this->checkFileContentWithRetry($user, $destination, $content);
}
return $response->getHeader('oc-fileid');
}

View File

@@ -36,13 +36,13 @@ Feature: antivirus
| <message> |
And as "Alice" file "<new-file-name>" should not exist
Examples:
| dav-path-version | file-name | new-file-name | message |
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| dav-path-version | file-name | new-file-name | message |
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
Scenario Outline: upload a file with virus and a file without virus
@@ -64,13 +64,13 @@ Feature: antivirus
Cheers.
"""
Examples:
| dav-path-version | file-name | new-file-name | message |
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| dav-path-version | file-name | new-file-name | message |
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
Scenario Outline: upload a file with virus in chunks
@@ -83,8 +83,8 @@ Feature: antivirus
# antivirus service can scan files during post-processing. on demand scanning is currently not available
Then the HTTP status code should be "201"
And user "Alice" should get a notification with subject "Virus found" and message:
| message |
| Virus found in myChunkedFile.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| message |
| Virus found in myChunkedFile.txt. Upload not possible. Virus: |
And as "Alice" file "/myChunkedFile.txt" should not exist
Examples:
| dav-path-version |
@@ -110,13 +110,13 @@ Feature: antivirus
| <message> |
And as "Alice" file "/uploadFolder/<new-file-name>" should not exist
Examples:
| dav-path-version | file-name | new-file-name | message |
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| dav-path-version | file-name | new-file-name | message |
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
@issue-10331
Scenario Outline: public uploads a file with the virus to a password-protected public share
@@ -136,13 +136,13 @@ Feature: antivirus
| <message> |
And as "Alice" file "/uploadFolder/<new-file-name>" should not exist
Examples:
| dav-path-version | file-name | new-file-name | message |
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| dav-path-version | file-name | new-file-name | message |
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
Scenario Outline: upload a file with virus to a user share
@@ -164,13 +164,13 @@ Feature: antivirus
And as "Brian" file "/Shares/uploadFolder/<new-file-name>" should not exist
And as "Alice" file "/uploadFolder/<new-file-name>" should not exist
Examples:
| dav-path-version | file-name | new-file-name | message |
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| dav-path-version | file-name | new-file-name | message |
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
Scenario Outline: upload a file with virus to a group share
@@ -194,13 +194,13 @@ Feature: antivirus
And as "Brian" file "/Shares/uploadFolder/<new-file-name>" should not exist
And as "Alice" file "/uploadFolder/<new-file-name>" should not exist
Examples:
| dav-path-version | file-name | new-file-name | message |
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| dav-path-version | file-name | new-file-name | message |
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
Scenario Outline: upload a file with virus to a project space
@@ -223,9 +223,9 @@ Feature: antivirus
And for user "Alice" the space "new-space" should not contain these entries:
| /<new-file-name> |
Examples:
| file-name | new-file-name | message |
| eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| file-name | new-file-name | message |
| eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
Scenario Outline: upload a file with virus to a shared project space
@@ -248,9 +248,9 @@ Feature: antivirus
And for user "Alice" the space "new-space" should not contain these entries:
| /<new-file-name> |
Examples:
| file-name | new-file-name | message |
| eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| file-name | new-file-name | message |
| eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
| eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
@env-config @issue-6494
Scenario Outline: upload a file with virus by setting antivirus infected file handling config to continue
@@ -272,8 +272,8 @@ Feature: antivirus
When user "Alice" uploads file "filesForUpload/filesWithVirus/eicar.com" to "/aFileWithVirus.txt" using the WebDAV API
Then the HTTP status code should be "201"
And user "Alice" should get a notification with subject "Virus found" and message:
| message |
| Virus found in aFileWithVirus.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| message |
| Virus found in aFileWithVirus.txt. Upload not possible. Virus: |
And as "Alice" file "/aFileWithVirus.txt" should not exist
Examples:
| dav-path-version |
@@ -306,12 +306,12 @@ Feature: antivirus
And as "Alice" file "/aFileWithVirus.txt" should not exist
Examples:
| dav-path-version | language | subject | message |
| old | es | Virus encontrado | Virus encontrado en aFileWithVirus.txt. La subida no ha sido posible. Virus: Win.Test.EICAR_HDB-1 |
| new | es | Virus encontrado | Virus encontrado en aFileWithVirus.txt. La subida no ha sido posible. Virus: Win.Test.EICAR_HDB-1 |
| spaces | es | Virus encontrado | Virus encontrado en aFileWithVirus.txt. La subida no ha sido posible. Virus: Win.Test.EICAR_HDB-1 |
| old | de | Virus gefunden | In aFileWithVirus.txt wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen. Grund: Win.Test.EICAR_HDB-1 |
| new | de | Virus gefunden | In aFileWithVirus.txt wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen. Grund: Win.Test.EICAR_HDB-1 |
| spaces | de | Virus gefunden | In aFileWithVirus.txt wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen. Grund: Win.Test.EICAR_HDB-1 |
| old | es | Virus encontrado | Virus encontrado en aFileWithVirus.txt. La subida no ha sido posible. Virus: |
| new | es | Virus encontrado | Virus encontrado en aFileWithVirus.txt. La subida no ha sido posible. Virus: |
| spaces | es | Virus encontrado | Virus encontrado en aFileWithVirus.txt. La subida no ha sido posible. Virus: |
| old | de | Virus gefunden | In aFileWithVirus.txt wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen. Grund: |
| new | de | Virus gefunden | In aFileWithVirus.txt wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen. Grund: |
| spaces | de | Virus gefunden | In aFileWithVirus.txt wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen. Grund: |
@issue-enterprise-5709
Scenario Outline: try to create a version of file by uploading virus content
@@ -321,8 +321,8 @@ Feature: antivirus
When user "Alice" uploads file with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to "test.txt" using the WebDAV API
Then the HTTP status code should be "204"
And user "Alice" should get a notification with subject "Virus found" and message:
| message |
| Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| message |
| Virus found in test.txt. Upload not possible. Virus: |
And as "Alice" file "/test.txt" should exist
And the version folder of file "/test.txt" for user "Alice" should contain "1" element
And the content of file "/test.txt" for user "Alice" should be "hello nepal"
@@ -348,8 +348,8 @@ Feature: antivirus
When the public overwrites file "test.txt" with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" using the public WebDAV API
Then the HTTP status code should be "204"
And user "Alice" should get a notification with subject "Virus found" and message:
| message |
| Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| message |
| Virus found in test.txt. Upload not possible. Virus: |
And the content of file "/test.txt" for user "Alice" should be "hello"
Examples:
| dav-path-version |
@@ -375,8 +375,8 @@ Feature: antivirus
When user "Brian" uploads file with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to "Shares/test.txt" using the WebDAV API
Then the HTTP status code should be "204"
And user "Brian" should get a notification with subject "Virus found" and message:
| message |
| Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| message |
| Virus found in test.txt. Upload not possible. Virus: |
And the content of file "/test.txt" for user "Alice" should be "hello"
And the content of file "Shares/test.txt" for user "Brian" should be "hello"
Examples:
@@ -409,15 +409,15 @@ Feature: antivirus
When user "Brian" uploads file with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to "Shares/uploadFolder/test.txt" using the WebDAV API
Then the HTTP status code should be "204"
And user "Brian" should get a notification for resource "test.txt" with subject "Virus found" and message:
| message |
| Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| message |
| Virus found in test.txt. Upload not possible. Virus: |
And the content of file "Shares/uploadFolder/test.txt" for user "Brian" should be "this is a test file."
And the content of file "uploadFolder/test.txt" for user "Alice" should be "this is a test file."
When user "Brian" uploads file with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to "Shares/test.txt" using the WebDAV API
Then the HTTP status code should be "204"
And user "Brian" should get a notification for resource "test.txt" with subject "Virus found" and message:
| message |
| Virus found in test.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| message |
| Virus found in test.txt. Upload not possible. Virus: |
And the content of file "Shares/test.txt" for user "Brian" should be "this is a test file."
And the content of file "/test.txt" for user "Alice" should be "this is a test file."
Examples:
@@ -436,8 +436,8 @@ Feature: antivirus
When user "Alice" uploads a file inside space "new-space" with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to ".space/readme.md" using the WebDAV API
Then the HTTP status code should be "204"
And user "Alice" should get a notification with subject "Virus found" and message:
| message |
| Virus found in readme.md. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| message |
| Virus found in readme.md. Upload not possible. Virus: |
And for user "Alice" the content of the file ".space/readme.md" of the space "new-space" should be "Here you can add a description for this Space."
@@ -457,8 +457,8 @@ Feature: antivirus
When user "Brian" uploads a file inside space "new-space" with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to ".space/readme.md" using the WebDAV API
Then the HTTP status code should be "204"
And user "Brian" should get a notification with subject "Virus found" and message:
| message |
| Virus found in readme.md. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| message |
| Virus found in readme.md. Upload not possible. Virus: |
And for user "Brian" the content of the file ".space/readme.md" of the space "new-space" should be "Here you can add a description for this Space."
And for user "Alice" the content of the file ".space/readme.md" of the space "new-space" should be "Here you can add a description for this Space."
@@ -477,7 +477,7 @@ Feature: antivirus
When user "Brian" uploads a file "filesForUpload/filesWithVirus/eicar.com" to "text.txt" in space "new-space" using the WebDAV API
Then the HTTP status code should be "204"
And user "Brian" should get a notification with subject "Virus found" and message:
| message |
| Virus found in text.txt. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
| message |
| Virus found in text.txt. Upload not possible. Virus: |
And for user "Brian" the content of the file "/text.txt" of the space "new-space" should be "hello world"
And for user "Alice" the content of the file "/text.txt" of the space "new-space" should be "hello world"

View File

@@ -27,10 +27,10 @@ Feature: Delete notification
Scenario: delete a notification
When user "Brian" deletes a notification related to resource "my_data" with subject "Resource shared"
Then the HTTP status code should be "200"
And user "Brian" should have a notification with subject "Resource shared" and message:
And user "Brian" should get a notification with subject "Resource shared" and message:
| message |
| Alice Hansen shared textfile1.txt with you |
But user "Brian" should not have a notification related to resource "my_data" with subject "Resource shared"
But user "Brian" should not get a notification related to resource "my_data" with subject "Resource shared"
Scenario: delete all notifications

View File

@@ -29,7 +29,7 @@ Feature: create a resources using collaborative posixfs
Scenario: create file
When the administrator creates the file "test.txt" with content "content" for user "Alice" on the POSIX filesystem
Then the command should be successful
And the content of file "/test.txt" for user "Alice" should be "content"
And as "Alice" the final content of file "test.txt" should be "content"
Scenario: create large file
@@ -41,21 +41,22 @@ Feature: create a resources using collaborative posixfs
Scenario: creates files sequentially in a folder
When the administrator creates 50 files sequentially in the directory "firstFolder" for user "Alice" on the POSIX filesystem
Then the command should be successful
And the content of file "/firstFolder/file_1.txt" for user "Alice" should be "file 1 content"
And the content of file "/firstFolder/file_50.txt" for user "Alice" should be "file 50 content"
And as "Alice" the final content of file "/firstFolder/file_1.txt" should be "file 1 content"
And as "Alice" the final content of file "/firstFolder/file_50.txt" should be "file 50 content"
Scenario: creates files in parallel in a folder
When the administrator creates 100 files in parallel in the directory "firstFolder" for user "Alice" on the POSIX filesystem
Then the command should be successful
And the content of file "/firstFolder/parallel_1.txt" for user "Alice" should be "parallel file 1 content"
And the content of file "/firstFolder/parallel_100.txt" for user "Alice" should be "parallel file 100 content"
And as "Alice" the final content of file "/firstFolder/parallel_1.txt" should be "parallel file 1 content"
And as "Alice" the final content of file "/firstFolder/parallel_100.txt" should be "parallel file 100 content"
Scenario: edit file
Given user "Alice" has uploaded file with content "content" to "test.txt"
When the administrator puts the content "new" into the file "test.txt" in the POSIX storage folder of user "Alice"
Then the content of file "/test.txt" for user "Alice" should be "contentnew"
Then the command should be successful
And as "Alice" the final content of file "test.txt" should be "contentnew"
Scenario: read file content
@@ -68,28 +69,28 @@ Feature: create a resources using collaborative posixfs
Given user "Alice" has uploaded file with content "content" to "test.txt"
When the administrator copies the file "test.txt" to the folder "firstFolder" for user "Alice" on the POSIX filesystem
Then the command should be successful
And the content of file "/firstFolder/test.txt" for user "Alice" should be "content"
And as "Alice" the final content of file "/firstFolder/test.txt" should be "content"
Scenario: rename file
Given user "Alice" has uploaded file with content "content" to "test.txt"
When the administrator renames the file "test.txt" to "new-name.txt" for user "Alice" on the POSIX filesystem
Then the command should be successful
And the content of file "/new-name.txt" for user "Alice" should be "content"
And as "Alice" the final content of file "/new-name.txt" should be "content"
Scenario: rename a created file
Given the administrator has created the file "test.txt" with content "content" for user "Alice" on the POSIX filesystem
When the administrator renames the file "test.txt" to "test.md" for user "Alice" on the POSIX filesystem
Then the command should be successful
And the content of file "/test.md" for user "Alice" should be "content"
And as "Alice" the final content of file "/test.md" should be "content"
Scenario: move file to folder
Given user "Alice" has uploaded file with content "content" to "test.txt"
When the administrator moves the file "test.txt" to the folder "firstFolder" for user "Alice" on the POSIX filesystem
Then the command should be successful
And the content of file "/firstFolder/test.txt" for user "Alice" should be "content"
And as "Alice" the final content of file "/firstFolder/test.txt" should be "content"
And as "Alice" file "/test.txt" should not exist
@@ -187,4 +188,4 @@ Feature: create a resources using collaborative posixfs
And the administrator renames the file "test.txt" to "renamed.txt" for user "Alice" on the POSIX filesystem
And the administrator checks the attribute "user.oc.name" of file "renamed.txt" for user "Alice" on the POSIX filesystem
Then the command output should contain "renamed.txt"
And the content of file "/renamed.txt" for user "Alice" should be "content"
And as "Alice" the final content of file "/renamed.txt" should be "content"

View File

@@ -202,7 +202,7 @@ Feature: capabilities
"properties": {
"edition": {
"type": "string",
"enum": ["dev"]
"enum": ["%edition%"]
},
"product": {
"type": "string",
@@ -240,7 +240,7 @@ Feature: capabilities
},
"edition": {
"type": "string",
"enum": ["dev"]
"enum": ["%edition%"]
},
"product": {
"type": "string",

View File

@@ -58,7 +58,7 @@ Feature: default capabilities for normal user
"const": "%versionstring%"
},
"edition": {
"const": "dev"
"const": "%edition%"
},
"productname": {
"const": "%productname%"

View File

@@ -50,8 +50,7 @@ Feature: low level tests for upload of chunks
| Upload-Metadata | filename ZmlsZS50eHQ= |
When user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "123" using the WebDAV API
And user "Alice" sends a chunk to the last created TUS Location with offset "3" and data "4567890" using the WebDAV API
And the user waits for "2" seconds for postprocessing to finish
And user "Alice" sends a chunk to the last created TUS Location with offset "3" and data "0000000" using the WebDAV API
And user "Alice" sends a chunk to the last created TUS Location with offset "3" and data "0000000" with retry on offset mismatch using the WebDAV API
Then the HTTP status code should be "404"
And the content of file "/file.txt" for user "Alice" should be "1234567890"
Examples:
@@ -61,6 +60,22 @@ Feature: low level tests for upload of chunks
| spaces |
Scenario Outline: send last chunk with mismatch offset
Given using <dav-path-version> DAV path
And user "Alice" has created a new TUS resource on the WebDAV API with these headers:
| Upload-Length | 10 |
# ZmlsZS50eHQ= is the base64 encode of file.txt
| Upload-Metadata | filename ZmlsZS50eHQ= |
When user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "123" using the WebDAV API
And user "Alice" sends a chunk to the last created TUS Location with offset "2" and data "34567890" using the WebDAV API
Then the HTTP status code should be "409"
Examples:
| dav-path-version |
| old |
| new |
| spaces |
Scenario Outline: start with uploading not at the beginning of the file
Given using <dav-path-version> DAV path
And user "Alice" has created a new TUS resource on the WebDAV API with these headers:

View File

@@ -21,6 +21,7 @@ package auth
import (
"context"
"fmt"
"path/filepath"
"strings"
"time"
@@ -130,8 +131,13 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
}
func resolveLightweightScope(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, user *userpb.User, client gateway.GatewayAPIClient, mgr token.Manager) error {
refString, err := storagespace.FormatReference(ref)
if err != nil {
// cannot format reference, so cannot be valid
return errtypes.PermissionDenied("invalid reference")
}
// Check if this ref is cached
key := "lw:" + user.Id.OpaqueId + scopeDelimiter + getRefKey(ref)
key := "lw:" + user.Id.OpaqueId + scopeDelimiter + refString
if _, err := scopeExpansionCache.Get(key); err == nil {
return nil
}
@@ -164,13 +170,7 @@ func resolvePublicShare(ctx context.Context, ref *provider.Reference, scope *aut
return err
}
if err := checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil {
return nil
}
// Some services like wopi don't access the shared resource relative to the
// share root but instead relative to the shared resources parent.
return checkRelativeReference(ctx, ref, share.ResourceId, client)
return checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr)
}
func resolveOCMShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error {
@@ -184,54 +184,7 @@ func resolveOCMShare(ctx context.Context, ref *provider.Reference, scope *authpb
ref.ResourceId = share.GetResourceId()
}
if err := checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil {
return nil
}
// Some services like wopi don't access the shared resource relative to the
// share root but instead relative to the shared resources parent.
return checkRelativeReference(ctx, ref, share.ResourceId, client)
}
// checkRelativeReference checks if the shared resource is being accessed via a relative reference
// e.g.:
// storage: abcd, space: efgh
// /root (id: efgh)
// - New file.txt (id: ijkl) <- shared resource
//
// If the requested reference looks like this:
// Reference{ResourceId: {StorageId: "abcd", SpaceId: "efgh"}, Path: "./New file.txt"}
// then the request is considered relative and this function would return true.
// Only references which are relative to the immediate parent of a resource are considered valid.
func checkRelativeReference(ctx context.Context, requested *provider.Reference, sharedResourceID *provider.ResourceId, client gateway.GatewayAPIClient) error {
sRes, err := client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: sharedResourceID}})
if err != nil {
return err
}
if sRes.Status.Code != rpc.Code_CODE_OK {
return statuspkg.NewErrorFromCode(sRes.Status.Code, "auth interceptor")
}
sharedResource := sRes.Info
// Is this a shared space
if sharedResource.ParentId == nil {
// Is the requested resource part of the shared space?
if requested.ResourceId.StorageId != sharedResource.Id.StorageId || requested.ResourceId.SpaceId != sharedResource.Id.SpaceId {
return errtypes.PermissionDenied("space access forbidden via public link")
}
} else {
parentID := sharedResource.ParentId
parentID.StorageId = sharedResource.Id.StorageId
if !utils.ResourceIDEqual(parentID, requested.ResourceId) && utils.MakeRelativePath(sharedResource.Path) != requested.Path {
return errtypes.PermissionDenied("access forbidden via public link")
}
}
key := storagespace.FormatResourceID(sharedResourceID) + scopeDelimiter + getRefKey(requested)
_ = scopeExpansionCache.SetWithExpire(key, nil, scopeCacheExpiration*time.Second)
return nil
return checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr)
}
func resolveUserShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error {
@@ -245,8 +198,14 @@ func resolveUserShare(ctx context.Context, ref *provider.Reference, scope *authp
}
func checkCacheForNestedResource(ctx context.Context, ref *provider.Reference, resource *provider.ResourceId, client gateway.GatewayAPIClient, mgr token.Manager) error {
refString, err := storagespace.FormatReference(ref)
if err != nil {
// cannot format reference, so cannot be valid
return errtypes.PermissionDenied("invalid reference")
}
// Check if this ref is cached
key := storagespace.FormatResourceID(resource) + scopeDelimiter + getRefKey(ref)
key := storagespace.FormatResourceID(resource) + scopeDelimiter + refString
if _, err := scopeExpansionCache.Get(key); err == nil {
return nil
}
@@ -270,40 +229,25 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent
return false, statuspkg.NewErrorFromCode(statResponse.Status.Code, "auth interceptor")
}
pathResp, err := client.GetPath(ctx, &provider.GetPathRequest{ResourceId: statResponse.GetInfo().GetId()})
if err != nil {
return false, err
}
if pathResp.Status.Code != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
}
parentPath := pathResp.Path
parentInfo := statResponse.GetInfo()
childPath := ref.GetPath()
if childPath != "" && childPath != "." && strings.HasPrefix(childPath, parentPath) {
// if the request is relative from the root, we can return directly
return true, nil
}
// The request is not relative to the root. We need to find out if the requested resource is child of the `parent` (coming from token scope)
// We need to find out if the requested resource is child of the `parent` (coming from token scope)
// We mint a token as the owner of the public share and try to stat the reference
// TODO(ishank011): We need to find a better alternative to this
// NOTE: did somebody say service accounts? ...
var user *userpb.User
if statResponse.GetInfo().GetOwner().GetType() == userpb.UserType_USER_TYPE_SPACE_OWNER {
if parentInfo.GetOwner().GetType() == userpb.UserType_USER_TYPE_SPACE_OWNER {
// fake a space owner user
user = &userpb.User{
Id: statResponse.GetInfo().GetOwner(),
Id: parentInfo.GetOwner(),
}
} else {
userResp, err := client.GetUser(ctx, &userpb.GetUserRequest{UserId: statResponse.Info.Owner, SkipFetchingUserGroups: true})
userResp, err := client.GetUser(ctx, &userpb.GetUserRequest{UserId: parentInfo.GetOwner(), SkipFetchingUserGroups: true})
if err != nil || userResp.Status.Code != rpc.Code_CODE_OK {
return false, err
}
user = userResp.User
}
scope, err := scope.AddOwnerScope(map[string]*authpb.Scope{})
if err != nil {
return false, err
@@ -329,6 +273,24 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent
if childStat.GetStatus().GetCode() != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(childStat.Status.Code, "auth interceptor")
}
childInfo := childStat.GetInfo()
// child can only be a nested resource if it is within the same space as parent
if childInfo.GetId().GetStorageId() != parentInfo.GetId().GetStorageId() ||
childInfo.GetId().GetSpaceId() != parentInfo.GetId().GetSpaceId() {
return false, nil
}
// Both resources are in the same space, now check paths
pathResp, err := client.GetPath(ctx, &provider.GetPathRequest{ResourceId: statResponse.GetInfo().GetId()})
if err != nil {
return false, err
}
if pathResp.Status.Code != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
}
parentPath := pathResp.Path
pathResp, err = client.GetPath(ctx, &provider.GetPathRequest{ResourceId: childStat.GetInfo().GetId()})
if err != nil {
return false, err
@@ -336,10 +298,12 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent
if pathResp.GetStatus().GetCode() != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
}
childPath = pathResp.Path
return strings.HasPrefix(childPath, parentPath), nil
childPath := pathResp.Path
rel, err := filepath.Rel(parentPath, childPath)
if err != nil {
return false, err
}
return !strings.HasPrefix(rel, ".."), nil
}
func extractRefFromListProvidersReq(v *registry.ListStorageProvidersRequest) (*provider.Reference, bool) {
@@ -513,17 +477,3 @@ func extractShareRef(req interface{}) (*collaboration.ShareReference, bool) {
}
return nil, false
}
func getRefKey(ref *provider.Reference) string {
if ref.GetPath() != "" {
return ref.Path
}
if ref.GetResourceId() != nil {
return storagespace.FormatResourceID(ref.ResourceId)
}
// on malicious request both path and rid could be empty
// we still should not panic
return ""
}

View File

@@ -591,6 +591,17 @@ func (n *Node) readOwner(ctx context.Context) (*userpb.UserId, error) {
return nil, err
}
// lookup Tenant in extended attributes
attr, err = n.SpaceRoot.XattrString(ctx, prefixes.SpaceTenantIDAttr)
switch {
case err == nil:
owner.TenantId = attr
case metadata.IsAttrUnset(err):
// ignore
default:
return nil, err
}
// lookup type in extended attributes
attr, err = n.SpaceRoot.XattrString(ctx, prefixes.OwnerTypeAttr)
switch {

2
vendor/modules.txt vendored
View File

@@ -1370,7 +1370,7 @@ github.com/opencloud-eu/icap-client
# github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76
## explicit; go 1.18
github.com/opencloud-eu/libre-graph-api-go
# github.com/opencloud-eu/reva/v2 v2.40.1
# github.com/opencloud-eu/reva/v2 v2.40.3
## explicit; go 1.24.1
github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace
github.com/opencloud-eu/reva/v2/cmd/revad/runtime