diff --git a/.drone.star b/.drone.star
index 9fda683de2..2b9be9b12e 100644
--- a/.drone.star
+++ b/.drone.star
@@ -242,6 +242,17 @@ config = {
"GATEWAY_GRPC_ADDR": "0.0.0.0:9142",
},
},
+ "authApp": {
+ "suites": [
+ "apiAuthApp",
+ ],
+ "skip": False,
+ "withRemotePhp": [True],
+ "extraServerEnvironment": {
+ "OCIS_ADD_RUN_SERVICES": "auth-app",
+ "PROXY_ENABLE_APP_AUTH": True,
+ },
+ },
"cliCommands": {
"suites": [
"cliCommands",
diff --git a/tests/acceptance/TestHelpers/AuthAppHelper.php b/tests/acceptance/TestHelpers/AuthAppHelper.php
new file mode 100644
index 0000000000..61d76d5ddd
--- /dev/null
+++ b/tests/acceptance/TestHelpers/AuthAppHelper.php
@@ -0,0 +1,93 @@
+
+ * @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
+ *
+ */
+
+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 string $expiration
+ *
+ * @return ResponseInterface
+ */
+ public static function createAppAuthToken(string $baseUrl, string $user, string $password, string $expiration): ResponseInterface {
+ $url = $baseUrl . self::getAuthAppEndpoint() . "?expiry=$expiration";
+ 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,
+ );
+ }
+}
diff --git a/tests/acceptance/bootstrap/AuthAppContext.php b/tests/acceptance/bootstrap/AuthAppContext.php
new file mode 100644
index 0000000000..ad95dc02db
--- /dev/null
+++ b/tests/acceptance/bootstrap/AuthAppContext.php
@@ -0,0 +1,104 @@
+
+ * @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
+ *
+ */
+
+use Behat\Behat\Context\Context;
+use Behat\Behat\Hook\Scope\BeforeScenarioScope;
+use TestHelpers\BehatHelper;
+use GuzzleHttp\Exception\GuzzleException;
+use TestHelpers\AuthAppHelper;
+
+require_once 'bootstrap.php';
+
+/**
+ * AuthApp context
+ */
+class AuthAppContext implements Context {
+ private FeatureContext $featureContext;
+
+ /**
+ * @BeforeScenario
+ *
+ * @param BeforeScenarioScope $scope
+ *
+ * @return void
+ */
+ 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');
+ }
+
+ /**
+ * @When user :user creates app token with expiration time :expiration using the auth-app API
+ *
+ * @param string $user
+ * @param string $expiration
+ *
+ * @return void
+ */
+ public function userCreatesAppTokenWithExpirationTimeUsingTheAuthAppApi(string $user, string $expiration): void {
+ $this->featureContext->setResponse(
+ AuthAppHelper::createAppAuthToken(
+ $this->featureContext->getBaseUrl(),
+ $this->featureContext->getActualUsername($user),
+ $this->featureContext->getPasswordForUser($user),
+ $expiration,
+ )
+ );
+ }
+
+ /**
+ * @Given user :user has created app token with expiration time :expiration
+ *
+ * @param string $user
+ * @param string $expiration
+ *
+ * @return void
+ */
+ public function userHasCreatedAppTokenWithExpirationTime(string $user, string $expiration): void {
+ $response = AuthAppHelper::createAppAuthToken(
+ $this->featureContext->getBaseUrl(),
+ $this->featureContext->getActualUsername($user),
+ $this->featureContext->getPasswordForUser($user),
+ $expiration,
+ );
+ $this->featureContext->theHTTPStatusCodeShouldBe(200, "", $response);
+ }
+
+ /**
+ * @When user :user lists all created tokens using the auth-app API
+ *
+ * @param string $user
+ *
+ * @return void
+ */
+ public function userListsAllCreatedTokensUsingTheAuthAppApi(string $user): void {
+ $this->featureContext->setResponse(
+ AuthAppHelper::listAllAppAuthTokensForUser(
+ $this->featureContext->getBaseUrl(),
+ $this->featureContext->getActualUsername($user),
+ $this->featureContext->getPasswordForUser($user),
+ )
+ );
+ }
+}
diff --git a/tests/acceptance/bootstrap/FeatureContext.php b/tests/acceptance/bootstrap/FeatureContext.php
index 1b11fe8d34..b7144e1e97 100644
--- a/tests/acceptance/bootstrap/FeatureContext.php
+++ b/tests/acceptance/bootstrap/FeatureContext.php
@@ -1132,9 +1132,11 @@ class FeatureContext extends BehatVariablesContext {
Assert::fail("'$validator' should be an object not an array");
}
Assert::assertFalse($value->allOf || $value->anyOf, "'allOf' and 'anyOf' are not allowed in array");
- Assert::assertNotNull($value->oneOf, "'oneOf' is required to assert more than one elements");
- Assert::assertTrue(\is_array($value->oneOf), "'oneOf' should be an array");
- Assert::assertEquals($schemaObj->maxItems, \count($value->oneOf), "Expected " . $schemaObj->maxItems . " 'oneOf' items but got " . \count($value->oneOf));
+ if ($value->oneOf) {
+ Assert::assertNotNull($value->oneOf, "'oneOf' is required to assert more than one elements");
+ Assert::assertTrue(\is_array($value->oneOf), "'oneOf' should be an array");
+ Assert::assertEquals($schemaObj->maxItems, \count($value->oneOf), "Expected " . $schemaObj->maxItems . " 'oneOf' items but got " . \count($value->oneOf));
+ }
}
Assert::assertTrue(\is_object($value), "'$validator' should be an object when expecting 1 element");
break;
@@ -1226,7 +1228,7 @@ class FeatureContext extends BehatVariablesContext {
$errors = $this->getJsonSchemaErrors($e);
$messages = ["JSON Schema validation failed:"];
- $previousPointer = '';
+ $previousPointer = null;
$errorCount = 0;
foreach ($errors as $error) {
$expected = $error->constraint;
@@ -1236,6 +1238,9 @@ class FeatureContext extends BehatVariablesContext {
$dataPointer = \str_replace("/", ".", \trim($error->getDataPointer(), "/"));
$pointer = \str_contains($schemaPointer, "additionalProperties") ? $dataPointer : $schemaPointer;
+ if ($pointer === '') {
+ $pointer = "{root}";
+ }
if ($pointer === $previousPointer) {
continue;
}
diff --git a/tests/acceptance/config/behat.yml b/tests/acceptance/config/behat.yml
index 8d35def18d..04a57217c7 100644
--- a/tests/acceptance/config/behat.yml
+++ b/tests/acceptance/config/behat.yml
@@ -424,6 +424,14 @@ default:
- FeatureContext: *common_feature_context_params
- OcisConfigContext:
+ apiAuthApp:
+ paths:
+ - "%paths.base%/../features/apiAuthApp"
+ context: *common_ldap_suite_context
+ contexts:
+ - FeatureContext: *common_feature_context_params
+ - AuthAppContext:
+
cliCommands:
paths:
- "%paths.base%/../features/cliCommands"
diff --git a/tests/acceptance/features/apiAuthApp/token.feature b/tests/acceptance/features/apiAuthApp/token.feature
new file mode 100644
index 0000000000..53de35f218
--- /dev/null
+++ b/tests/acceptance/features/apiAuthApp/token.feature
@@ -0,0 +1,67 @@
+Feature: create auth token
+ As a user
+ I want to create App Tokens
+ So that I can use 3rd party apps
+
+ Background:
+ Given user "Alice" has been created with default attributes
+
+
+ Scenario: user creates app token
+ When user "Alice" creates app token with expiration time "72h" using the auth-app API
+ Then the HTTP status code should be "200"
+ And the JSON data of the response should match
+ """
+ {
+ "type": "object",
+ "required": [
+ "token",
+ "expiration_date",
+ "created_date",
+ "label"
+ ],
+ "properties": {
+ "token": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9]{16}$"
+ },
+ "label": {
+ "const": "Generated via API"
+ }
+ }
+ }
+ """
+
+
+ Scenario: user lists app tokens
+ Given user "Alice" has created app token with expiration time "72h"
+ And user "Alice" has created app token with expiration time "2h"
+ When user "Alice" lists all created tokens using the auth-app API
+ Then the HTTP status code should be "200"
+ And the JSON data of the response should match
+ """
+ {
+ "type": "array",
+ "minItems": 2,
+ "maxItems": 2,
+ "uniqueItems": true,
+ "items": {
+ "type": "object",
+ "required": [
+ "token",
+ "expiration_date",
+ "created_date",
+ "label"
+ ],
+ "properties": {
+ "token": {
+ "type": "string",
+ "pattern": "^\\$2a\\$11\\$[A-Za-z0-9./]{53}$"
+ },
+ "label": {
+ "const": "Generated via API"
+ }
+ }
+ }
+ }
+ """