Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions tests/acceptance/TestHelpers/KeycloakHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,49 @@ public static function setAccessTokenForKeycloakOcisUser(array $user): array {
return json_decode($tokenResponse->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
}

/**
* @return array
* @throws GuzzleException
* @throws JsonException
*/
public static function getRealm(): array {
$url = self::getKeycloakUrl() . '/admin/realms/oCIS';
$response = HttpRequestHelper::get(
$url,
null,
null,
[
'Authorization' => 'Bearer ' . self::getAdminAccessToken(),
],
);
return json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
}

/**
* @param string $key
* @param string $value
*
* @return ResponseInterface
* @throws GuzzleException
* @throws JsonException
*/
public static function updateRealmAttribute(string $key, string $value): ResponseInterface {
$realm = self::getRealm();
$attributes = $realm['attributes'] ?? [];
$attributes[$key] = $value;
$url = self::getKeycloakUrl() . '/admin/realms/oCIS';
return HttpRequestHelper::put(
$url,
null,
null,
[
'Authorization' => 'Bearer ' . self::getAdminAccessToken(),
'Content-Type' => 'application/json',
],
json_encode(['attributes' => $attributes], JSON_THROW_ON_ERROR),
);
}

/**
* @param string $uuid
*
Expand Down
67 changes: 60 additions & 7 deletions tests/acceptance/bootstrap/OcisConfigContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\ResponseInterface;
use TestHelpers\HttpRequestHelper;
use TestHelpers\KeycloakHelper;
use TestHelpers\OcisConfigHelper;
use TestHelpers\GraphHelper;
use PHPUnit\Framework\Assert;
Expand Down Expand Up @@ -110,11 +113,9 @@ public function theConfigHasBeenSetTo(
$response = OcisConfigHelper::reConfigureOcis($envs);
}

$resBody = $response->getBody()->getContents();
Assert::assertEquals(
200,
$response->getStatusCode(),
"Failed to set config $configVariable=$configValue. Response: $resBody",
$this->assertOcisRestarted(
$response,
"Failed to set config $configVariable=$configValue.",
);
}

Expand Down Expand Up @@ -346,11 +347,63 @@ public function rollback(): void {
*/
public function rollbackOcis(): void {
$response = OcisConfigHelper::rollbackOcis();
$resBody = $response->getBody()->getContents();
$this->assertOcisRestarted(
$response,
"Failed to rollback ocis server. Check if oCIS is started with ociswrapper.",
);
}

/**
* Asserts that an oCIS restart triggered by ociswrapper succeeded.
* In vault mode the wrapper's admin health-check uses basic-auth which is
* disabled (OIDC role assignment only), so the wrapper always returns HTTP 500
* even after a successful restart. When Keycloak/vault mode is detected we
* poll the unauthenticated proxy debug readyz endpoint instead of failing.
*
* @param ResponseInterface $response
* @param string $errorMessage
*
* @return void
*/
private function assertOcisRestarted(ResponseInterface $response, string $errorMessage): void {
if ($response->getStatusCode() === 200) {
return;
}
if (KeycloakHelper::isTestingWithKeycloak()) {
$this->waitForOcisProxyReady();
return;
}
Assert::assertEquals(
200,
$response->getStatusCode(),
"Failed to rollback ocis server. Check if oCIS is started with ociswrapper. Response: $resBody",
$errorMessage . " Response: " . $response->getBody()->getContents(),
);
}

/**
* Poll the unauthenticated proxy debug readyz endpoint until oCIS is ready.
* Used in vault/Keycloak mode where basic-auth health checks are unavailable.
*
* @param int $timeoutSeconds
*
* @return void
*/
private function waitForOcisProxyReady(int $timeoutSeconds = 60): void {
$readyzUrl = 'http://localhost:9205/readyz';
$deadline = time() + $timeoutSeconds;
while (time() < $deadline) {
try {
$response = HttpRequestHelper::get($readyzUrl);
if ($response->getStatusCode() === 200) {
return;
}
} catch (\Exception $e) {
// not ready yet, keep polling
}
sleep(1);
}
throw new \RuntimeException(
"Timed out after {$timeoutSeconds}s waiting for oCIS proxy readyz at {$readyzUrl}",
);
}

Expand Down
123 changes: 123 additions & 0 deletions tests/acceptance/bootstrap/VaultContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

declare(strict_types=1);
/**
* ownCloud
*
* @author Prajwol Amatya <prajwol@jankaritech.com>
* @copyright Copyright (c) 2026 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/>
*
*/

use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use GuzzleHttp\Exception\GuzzleException;
use JsonException;
use PHPUnit\Framework\Assert;
use TestHelpers\BehatHelper;
use TestHelpers\KeycloakHelper;

require_once 'bootstrap.php';

/**
* Context for ocis vault specific steps
*/
class VaultContext implements Context {
private FeatureContext $featureContext;
private array $originalRealmAttributes = [];

/**
* @BeforeScenario
*
* @param BeforeScenarioScope $scope
*
* @return void
*
* @throws Exception
*/
public function before(BeforeScenarioScope $scope): void {
// Get the environment
$environment = $scope->getEnvironment();
// Get all the contexts you need in this context
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
}

/**
* @Given the administrator has set the Keycloak realm attribute :key to :value
*
* @param string $key
* @param string $value
*
* @return void
* @throws GuzzleException
* @throws JsonException
*/
public function theAdministratorHasSetTheKeycloakRealmAttributeTo(string $key, string $value): void {
$realm = KeycloakHelper::getRealm();
$this->originalRealmAttributes[$key] = $realm['attributes'][$key] ?? null;
$response = KeycloakHelper::updateRealmAttribute($key, $value);
Assert::assertEquals(
204,
$response->getStatusCode(),
"Failed to update Keycloak realm attribute $key. Response: " . $response->getBody()->getContents(),
);
}

/**
* @AfterScenario @keycloak-config
*
* @return void
* @throws GuzzleException
* @throws JsonException
*/
public function restoreKeycloakRealmAttributes(): void {
foreach ($this->originalRealmAttributes as $key => $value) {
if ($value === null) {
continue;
}
KeycloakHelper::updateRealmAttribute($key, $value);
}
$this->originalRealmAttributes = [];
}

/**
* @Then user :user should have acr value :acr
*
* @param string $user
* @param string $acr
*
* @return void
* @throws Exception
*/
public function userShouldHaveAcrVaule(string $user, string $acr): void {
$access_token = $this->featureContext->getOcisUserToken($user)['token']['accessToken'];

// Decode JWT token
$parts = explode('.', $access_token);
if (\count($parts) !== 3) {
throw new Exception("Invalid JWT token format.");
}
$payload = $parts[1];
$decodedPayload = base64_decode(strtr($payload, '-_', '+/'), true);
$payloadArray = json_decode($decodedPayload, true);
$actualAcr = $payloadArray['acr'] ?? null;
Assert::assertEquals(
$acr,
$actualAcr,
"Exected acr value to be $acr but got $actualAcr",
);
}
}
2 changes: 2 additions & 0 deletions tests/acceptance/config/behat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,8 @@ default:
context: *common_ldap_suite_context
contexts:
- FeatureContext: *common_feature_context_params
- VaultContext:
- OcisConfigContext:

cliCommands:
paths:
Expand Down
9 changes: 9 additions & 0 deletions tests/acceptance/features/apiVault/vault.feature
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,12 @@ Feature: vault
And for user "Alice" the space "Personal" should not contain these entries:
| vaultFolder |
| vaultFile.txt |

@env-config @keycloak-config
Scenario: user can set custom auth level names
Given the administrator has set the Keycloak realm attribute "acr.loa.map" to '{"regular":"1","testing":"2"}'
And the config "OCIS_MFA_AUTH_LEVEL_NAMES" has been set to "testing"
And user "Alice" has logged in via web UI
When user "Alice" uploads a file inside space "Personal" with content "some content" to "vaultFile.txt" in vault using the WebDAV API
Then the HTTP status code should be "201"
And user "Alice" should have acr value "testing"