Added authentication method selection in integration setup (#737)
* added oidc based authorization in integration Signed-off-by: Sagar <sagargurung1001@gmail.com> * Added change log Signed-off-by: Sagar <sagargurung1001@gmail.com> * review address PR Signed-off-by: Sagar <sagargurung1001@gmail.com> * Added link to user oidc app Signed-off-by: Sagar <sagargurung1001@gmail.com> --------- Signed-off-by: Sagar <sagargurung1001@gmail.com>
This commit is contained in:
Родитель
fb3c4231fd
Коммит
c1384c2f6c
|
@ -116,10 +116,12 @@ jobs:
|
|||
- name: PHP & Vue Unit Tests
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/nextcloud/groupfolders.git -b ${{ matrix.nextcloudVersion }} server/apps/groupfolders
|
||||
git clone --depth 1 https://github.com/nextcloud/user_oidc.git server/apps/user_oidc
|
||||
mkdir -p server/apps/integration_openproject
|
||||
cp -r `ls -A | grep -v 'server'` server/apps/integration_openproject/
|
||||
cd server
|
||||
./occ a:e groupfolders
|
||||
./occ a:e user_oidc
|
||||
./occ a:e integration_openproject
|
||||
cd apps/integration_openproject
|
||||
# The following if block can be removed once Nextcloud no longer supports PHP 8.0
|
||||
|
|
|
@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||
## [Unreleased]
|
||||
### Changed
|
||||
- Add application's support for Nextcloud 31
|
||||
- Add support for OIDC-based connection between Nextcloud and OpenProject
|
||||
|
||||
## 2.7.2 - 2024-12-16
|
||||
### Fixed
|
||||
|
|
|
@ -142,6 +142,30 @@ class ConfigController extends Controller {
|
|||
$this->config->deleteUserValue($userId, Application::APP_ID, 'refresh_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function resetOauth2Configs(): void {
|
||||
// for oauth2 reset we reset openproject client credential as well as nextcloud client credential
|
||||
$this->config->setAppValue(Application::APP_ID, 'openproject_client_id', "");
|
||||
$this->config->setAppValue(Application::APP_ID, 'openproject_client_secret', "");
|
||||
$this->deleteOauthClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function resetOIDCConfigs(string $userId = null): void {
|
||||
if ($userId === null) {
|
||||
$userId = $this->userId;
|
||||
}
|
||||
$this->config->setAppValue(Application::APP_ID, 'oidc_provider', "");
|
||||
$this->config->setAppValue(Application::APP_ID, 'targeted_audience_client_id', "");
|
||||
$this->config->deleteUserValue($userId, Application::APP_ID, 'user_id');
|
||||
$this->config->deleteUserValue($userId, Application::APP_ID, 'user_name');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* set config values
|
||||
* @NoAdminRequired
|
||||
|
@ -184,6 +208,9 @@ class ConfigController extends Controller {
|
|||
private function setIntegrationConfig(array $values): array {
|
||||
$allowedKeys = [
|
||||
'openproject_instance_url',
|
||||
'authorization_method',
|
||||
'oidc_provider',
|
||||
'targeted_audience_client_id',
|
||||
'openproject_client_id',
|
||||
'openproject_client_secret',
|
||||
'default_enable_navigation',
|
||||
|
@ -233,17 +260,72 @@ class ConfigController extends Controller {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
$oldClientId = $oldClientSecret = '';
|
||||
$oldOpenProjectOauthUrl = $this->config->getAppValue(
|
||||
Application::APP_ID, 'openproject_instance_url', ''
|
||||
);
|
||||
$oldClientId = $this->config->getAppValue(
|
||||
Application::APP_ID, 'openproject_client_id', ''
|
||||
$oldAuthMethod = $this->config->getAppValue(
|
||||
Application::APP_ID, 'authorization_method', ''
|
||||
);
|
||||
$oldClientSecret = $this->config->getAppValue(
|
||||
Application::APP_ID, 'openproject_client_secret', ''
|
||||
// when 'openproject_instance_url' && 'authorization_method' does not have a value at the same time,
|
||||
// it means a full reset is done
|
||||
$runningFullReset = key_exists('openproject_instance_url', $values) &&
|
||||
key_exists('authorization_method', $values) &&
|
||||
!$values['openproject_instance_url'] &&
|
||||
!$values['authorization_method'];
|
||||
|
||||
// determine if the full reset is done when configuration is already with "oauth2"
|
||||
$runningFullResetWithOAuth2Auth = (
|
||||
$runningFullReset &&
|
||||
$oldAuthMethod === OpenProjectAPIService::AUTH_METHOD_OAUTH
|
||||
);
|
||||
|
||||
// determine if the full reset is done when configuration is already with "oidc"
|
||||
$runningFullResetWithOIDCAuth = (
|
||||
$runningFullReset &&
|
||||
$oldAuthMethod === OpenProjectAPIService::AUTH_METHOD_OIDC
|
||||
);
|
||||
if (
|
||||
(key_exists('openproject_client_id', $values) && key_exists('openproject_client_secret', $values))
|
||||
) {
|
||||
$oldClientId = $this->config->getAppValue(
|
||||
Application::APP_ID, 'openproject_client_id', ''
|
||||
);
|
||||
$oldClientSecret = $this->config->getAppValue(
|
||||
Application::APP_ID, 'openproject_client_secret', ''
|
||||
);
|
||||
}
|
||||
// since we can now switch between both authorization method we need to know what we are resetting (either "oauth2" or "oidc" method)
|
||||
// determines if we are switching from "oauth2" to "oidc" auth method
|
||||
$runningOauth2Reset = (
|
||||
key_exists('openproject_client_id', $values) &&
|
||||
!$values['openproject_client_id'] &&
|
||||
key_exists('openproject_client_secret', $values) &&
|
||||
!$values['openproject_client_secret'] &&
|
||||
$oldOpenProjectOauthUrl &&
|
||||
$oldClientId &&
|
||||
$oldClientSecret
|
||||
);
|
||||
|
||||
// determines if we are switching from "oidc" to "oauth2" auth method
|
||||
$runningOIDCReset = false;
|
||||
if (
|
||||
key_exists('oidc_provider', $values) &&
|
||||
key_exists('targeted_audience_client_id', $values)
|
||||
) {
|
||||
$oldOidcProvider = $this->config->getAppValue(
|
||||
Application::APP_ID, 'oidc_provider', ''
|
||||
);
|
||||
$oldTargetedAudienceClient = $this->config->getAppValue(
|
||||
Application::APP_ID, 'targeted_audience_client_id', ''
|
||||
);
|
||||
$runningOIDCReset = (
|
||||
$oldOidcProvider &&
|
||||
$oldTargetedAudienceClient &&
|
||||
!$values['oidc_provider'] &&
|
||||
!$values['targeted_audience_client_id']);
|
||||
}
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
if ($key === 'setup_project_folder' || $key === 'setup_app_password') {
|
||||
continue;
|
||||
|
@ -253,7 +335,7 @@ class ConfigController extends Controller {
|
|||
|
||||
// if the OpenProject OAuth URL has changed
|
||||
if (key_exists('openproject_instance_url', $values)
|
||||
&& $oldOpenProjectOauthUrl !== $values['openproject_instance_url']
|
||||
&& $oldOpenProjectOauthUrl !== $values['openproject_instance_url'] && (!$runningOIDCReset || !$runningFullResetWithOIDCAuth)
|
||||
) {
|
||||
// delete the existing OAuth client if new OAuth URL is passed empty
|
||||
if (
|
||||
|
@ -272,22 +354,6 @@ class ConfigController extends Controller {
|
|||
}
|
||||
}
|
||||
|
||||
$runningFullReset = (
|
||||
|
||||
key_exists('openproject_instance_url', $values) &&
|
||||
|
||||
key_exists('openproject_client_id', $values) &&
|
||||
|
||||
key_exists('openproject_client_secret', $values) &&
|
||||
|
||||
$values['openproject_instance_url'] === null &&
|
||||
|
||||
$values['openproject_client_id'] === null &&
|
||||
|
||||
$values['openproject_client_secret'] === null
|
||||
|
||||
);
|
||||
|
||||
// resetting and keeping the project folder setup should delete the user app password
|
||||
if (key_exists('setup_app_password', $values) && $values['setup_app_password'] === false) {
|
||||
$this->openprojectAPIService->deleteAppPassword();
|
||||
|
@ -301,10 +367,11 @@ class ConfigController extends Controller {
|
|||
$this->config->deleteAppValue(Application::APP_ID, 'oPOAuthTokenRevokeStatus');
|
||||
if (
|
||||
// when the OP client information has changed
|
||||
((key_exists('openproject_client_id', $values) && $values['openproject_client_id'] !== $oldClientId) ||
|
||||
(key_exists('openproject_client_secret', $values) && $values['openproject_client_secret'] !== $oldClientSecret)) ||
|
||||
// when the OP client information is for reset
|
||||
$runningFullReset
|
||||
(!$runningFullResetWithOIDCAuth && ((key_exists('openproject_client_id', $values) && $values['openproject_client_id'] !== $oldClientId) ||
|
||||
(key_exists('openproject_client_secret', $values) && $values['openproject_client_secret'] !== $oldClientSecret))) ||
|
||||
// when the OP client information is reset
|
||||
$runningFullResetWithOAuth2Auth ||
|
||||
$runningOauth2Reset
|
||||
) {
|
||||
$this->userManager->callForAllUsers(function (IUser $user) use (
|
||||
$oldOpenProjectOauthUrl, $oldClientId, $oldClientSecret
|
||||
|
@ -346,6 +413,8 @@ class ConfigController extends Controller {
|
|||
}
|
||||
$this->clearUserInfo($userUID);
|
||||
});
|
||||
} elseif ($runningFullResetWithOIDCAuth) {
|
||||
$this->resetOIDCConfigs();
|
||||
}
|
||||
|
||||
|
||||
|
@ -362,6 +431,18 @@ class ConfigController extends Controller {
|
|||
$this->config->setAppValue(Application::APP_ID, 'fresh_project_folder_setup', "0");
|
||||
}
|
||||
|
||||
// when switching from "oauth2" to "oidc" authorization method
|
||||
if (key_exists('authorization_method', $values) &&
|
||||
$values['authorization_method'] === OpenProjectAPIService::AUTH_METHOD_OIDC && $runningOauth2Reset) {
|
||||
$this->resetOauth2Configs();
|
||||
}
|
||||
|
||||
// when switching from "oidc" to "oauth2" authorization method
|
||||
if (key_exists('authorization_method', $values) &&
|
||||
$values['authorization_method'] === OpenProjectAPIService::AUTH_METHOD_OAUTH && $runningOIDCReset) {
|
||||
$this->resetOIDCConfigs();
|
||||
}
|
||||
|
||||
// if the revoke has failed at least once, the last status is stored in the database
|
||||
// this is not a neat way to give proper information about the revoke status
|
||||
// TODO: find way to report every user's revoke status
|
||||
|
@ -604,13 +685,12 @@ class ConfigController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function checkAdminConfigOk(): DataResponse {
|
||||
$adminConfigStatusWithoutGroupFolderSetupStatus = OpenProjectAPIService::isAdminConfigOk($this->config);
|
||||
$appPasswordSetStatus = $this->openprojectAPIService->hasAppPassword();
|
||||
// Admin config can be set in two parts
|
||||
// 1. config without project folder set up (which is compulsory for integration)
|
||||
// 2. config with project folder set up (which is optional for admin)
|
||||
return new DataResponse([
|
||||
'config_status_without_project_folder' => $adminConfigStatusWithoutGroupFolderSetupStatus,
|
||||
'config_status_without_project_folder' => OpenProjectAPIService::isAdminConfigOk($this->config),
|
||||
'project_folder_setup_status' => $appPasswordSetStatus
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -87,6 +87,29 @@ class OpenProjectAPIController extends Controller {
|
|||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
private function validatePreRequestConditions(): array {
|
||||
$authMethod = $this->config->getAppValue(Application::APP_ID, 'authorization_method', '');
|
||||
if ($authMethod === OpenProjectAPIService::AUTH_METHOD_OAUTH && $this->accessToken === '') {
|
||||
return [
|
||||
'status' => false,
|
||||
'result' => new DataResponse('', Http::STATUS_UNAUTHORIZED)
|
||||
];
|
||||
} elseif ($authMethod === OpenProjectAPIService::AUTH_METHOD_OIDC &&
|
||||
!$this->openprojectAPIService->getOIDCToken()
|
||||
) {
|
||||
return [
|
||||
'status' => false,
|
||||
'result' => new DataResponse('', Http::STATUS_UNAUTHORIZED)
|
||||
];
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return [
|
||||
'status' => false,
|
||||
'result' => new DataResponse('', Http::STATUS_BAD_REQUEST)
|
||||
];
|
||||
}
|
||||
return ['status' => true, 'result' => null];
|
||||
}
|
||||
|
||||
/**
|
||||
* get openproject instance URL
|
||||
* @NoAdminRequired
|
||||
|
@ -96,7 +119,6 @@ class OpenProjectAPIController extends Controller {
|
|||
public function getOpenProjectUrl(): DataResponse {
|
||||
return new DataResponse($this->openprojectUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* get openproject user avatar
|
||||
* @NoAdminRequired
|
||||
|
@ -127,10 +149,9 @@ class OpenProjectAPIController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function getNotifications(): DataResponse {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
$result = $this->openprojectAPIService->getNotifications($this->userId);
|
||||
if (!isset($result['error'])) {
|
||||
|
@ -161,10 +182,9 @@ class OpenProjectAPIController extends Controller {
|
|||
?int $fileId = null,
|
||||
bool $isSmartPicker = false
|
||||
): DataResponse {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
// when the search is done through smart picker we don't want to check if the work package is linkable
|
||||
$result = $this->openprojectAPIService->searchWorkPackage(
|
||||
|
@ -199,12 +219,10 @@ class OpenProjectAPIController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function linkWorkPackageToFile(array $values): DataResponse {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->openprojectAPIService->linkWorkPackageToFile(
|
||||
$values,
|
||||
|
@ -228,10 +246,9 @@ class OpenProjectAPIController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function markNotificationAsRead(int $workpackageId) {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
try {
|
||||
$result = $this->openprojectAPIService->markAllNotificationsOfWorkPackageAsRead(
|
||||
|
@ -258,12 +275,10 @@ class OpenProjectAPIController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function getWorkPackageFileLinks(int $id): DataResponse {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->openprojectAPIService->getWorkPackageFileLinks(
|
||||
$id,
|
||||
|
@ -285,12 +300,10 @@ class OpenProjectAPIController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function deleteFileLink(int $id): DataResponse {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->openprojectAPIService->deleteFileLink(
|
||||
$id,
|
||||
|
@ -316,12 +329,10 @@ class OpenProjectAPIController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function getOpenProjectWorkPackageStatus(string $id): DataResponse {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
|
||||
$result = $this->openprojectAPIService->getOpenProjectWorkPackageStatus(
|
||||
$this->userId, $id
|
||||
);
|
||||
|
@ -343,12 +354,10 @@ class OpenProjectAPIController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function getOpenProjectWorkPackageType(string $id): DataResponse {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
|
||||
$result = $this->openprojectAPIService->getOpenProjectWorkPackageType(
|
||||
$this->userId, $id
|
||||
);
|
||||
|
@ -366,10 +375,9 @@ class OpenProjectAPIController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function getAvailableOpenProjectProjects(?string $searchQuery = null): DataResponse {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
try {
|
||||
$result = $this->openprojectAPIService->getAvailableOpenProjectProjects($this->userId, $searchQuery);
|
||||
|
@ -418,10 +426,9 @@ class OpenProjectAPIController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function getOpenProjectWorkPackageForm(string $projectId, array $body): DataResponse {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
try {
|
||||
$result = $this->openprojectAPIService->getOpenProjectWorkPackageForm($this->userId, $projectId, $body);
|
||||
|
@ -440,10 +447,9 @@ class OpenProjectAPIController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function getAvailableAssigneesOfAProject(string $projectId): DataResponse {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
try {
|
||||
$result = $this->openprojectAPIService->getAvailableAssigneesOfAProject($this->userId, $projectId);
|
||||
|
@ -489,10 +495,9 @@ class OpenProjectAPIController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function createWorkPackage(array $body): DataResponse {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
// we don't want to check if all the data in the body is set or not because
|
||||
// that calculation will be done by the openproject api itself
|
||||
|
@ -518,10 +523,9 @@ class OpenProjectAPIController extends Controller {
|
|||
* @return DataResponse
|
||||
*/
|
||||
public function getOpenProjectConfiguration(): DataResponse {
|
||||
if ($this->accessToken === '') {
|
||||
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
|
||||
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
|
||||
return new DataResponse('', Http::STATUS_BAD_REQUEST);
|
||||
$validatePreRequestResult = $this->validatePreRequestConditions();
|
||||
if (!$validatePreRequestResult['status']) {
|
||||
return $validatePreRequestResult['result'];
|
||||
}
|
||||
try {
|
||||
$result = $this->openprojectAPIService->getOpenProjectConfiguration($this->userId);
|
||||
|
|
|
@ -60,18 +60,26 @@ class OpenProjectWidget implements IWidget {
|
|||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @var OpenProjectAPIService
|
||||
*/
|
||||
private OpenProjectAPIService $openProjectAPIService;
|
||||
|
||||
|
||||
public function __construct(
|
||||
IL10N $l10n,
|
||||
IInitialState $initialStateService,
|
||||
IURLGenerator $url,
|
||||
IConfig $config,
|
||||
IUserSession $userSession
|
||||
IUserSession $userSession,
|
||||
OpenProjectAPIService $openProjectAPIService
|
||||
) {
|
||||
$this->initialStateService = $initialStateService;
|
||||
$this->l10n = $l10n;
|
||||
$this->url = $url;
|
||||
$this->config = $config;
|
||||
$this->user = $userSession->getUser();
|
||||
$this->openProjectAPIService = $openProjectAPIService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,15 +123,25 @@ class OpenProjectWidget implements IWidget {
|
|||
public function load(): void {
|
||||
Util::addScript(Application::APP_ID, Application::APP_ID . '-dashboard');
|
||||
Util::addStyle(Application::APP_ID, 'dashboard');
|
||||
|
||||
$this->initialStateService->provideInitialState('admin-config-status', OpenProjectAPIService::isAdminConfigOk($this->config));
|
||||
|
||||
$oauthConnectionResult = $this->config->getUserValue(
|
||||
$this->user->getUID(), Application::APP_ID, 'oauth_connection_result', ''
|
||||
);
|
||||
$this->config->deleteUserValue(
|
||||
$this->user->getUID(), Application::APP_ID, 'oauth_connection_result'
|
||||
);
|
||||
|
||||
$authorizationMethod = $this->config->getAppValue(Application::APP_ID, 'authorization_method', '');
|
||||
$this->initialStateService->provideInitialState('authorization_method', $authorizationMethod);
|
||||
$this->initialStateService->provideInitialState(
|
||||
'admin_config_ok', OpenProjectAPIService::isAdminConfigOk($this->config)
|
||||
);
|
||||
|
||||
// authorization method can be either a 'oidc' or 'oauth2'
|
||||
// for 'oidc' state to be loaded
|
||||
$token = $this->openProjectAPIService->getOIDCToken();
|
||||
$this->initialStateService->provideInitialState('user-has-oidc-token', $token !== null);
|
||||
|
||||
// for 'oauth2' state to be loaded
|
||||
$this->initialStateService->provideInitialState(
|
||||
'oauth-connection-result', $oauthConnectionResult
|
||||
);
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Nextcloud - openproject
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Sagar Gurung
|
||||
* @copyright Sagar Gurung 2024
|
||||
*/
|
||||
|
||||
namespace OCA\OpenProject;
|
||||
|
||||
use OCA\OpenProject\AppInfo\Application;
|
||||
use OCA\UserOIDC\Event\ExchangedTokenRequestedEvent;
|
||||
use OCP\IConfig;
|
||||
|
||||
class ExchangedTokenRequestedEventHelper {
|
||||
private IConfig $config;
|
||||
|
||||
public function __construct(
|
||||
IConfig $config
|
||||
) {
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ExchangedTokenRequestedEvent
|
||||
*/
|
||||
public function getEvent(): ExchangedTokenRequestedEvent {
|
||||
return new ExchangedTokenRequestedEvent(
|
||||
$this->config->getAppValue(Application::APP_ID, 'targeted_audience_client_id', '')
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,8 +5,10 @@ namespace OCA\OpenProject\Listener;
|
|||
use OCA\Files\Event\LoadAdditionalScriptsEvent;
|
||||
use OCA\OpenProject\AppInfo\Application;
|
||||
use OCA\OpenProject\ServerVersionHelper;
|
||||
use OCA\OpenProject\Service\OpenProjectAPIService;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\IConfig;
|
||||
use OCP\Util;
|
||||
|
||||
/**
|
||||
|
@ -14,7 +16,33 @@ use OCP\Util;
|
|||
*/
|
||||
class LoadAdditionalScriptsListener implements IEventListener {
|
||||
|
||||
/**
|
||||
* @var OpenProjectAPIService
|
||||
*/
|
||||
private $openProjectAPIService;
|
||||
/**
|
||||
* @var IConfig
|
||||
*/
|
||||
private $config;
|
||||
|
||||
public function __construct(
|
||||
IConfig $config,
|
||||
OpenProjectAPIService $openProjectAPIService,
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->openProjectAPIService = $openProjectAPIService;
|
||||
}
|
||||
|
||||
public function handle(Event $event): void {
|
||||
// When user is non oidc based or there is some error when getting token for the targeted client
|
||||
// then we need to hide the oidc based connection for the user
|
||||
// so this check is required
|
||||
if (
|
||||
$this->config->getAppValue(Application::APP_ID, 'authorization_method', '') === OpenProjectAPIService::AUTH_METHOD_OIDC &&
|
||||
!$this->openProjectAPIService->getOIDCToken()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!$event instanceof LoadAdditionalScriptsEvent) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -66,16 +66,30 @@ class LoadSidebarScript implements IEventListener {
|
|||
*/
|
||||
protected $appManager;
|
||||
|
||||
/**
|
||||
* @var OpenProjectAPIService
|
||||
*/
|
||||
private $openProjectAPIService;
|
||||
private IUserSession $userSession;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $userId;
|
||||
|
||||
public function __construct(
|
||||
IInitialState $initialStateService,
|
||||
IConfig $config,
|
||||
IUserSession $userSession,
|
||||
IAppManager $appManager
|
||||
IAppManager $appManager,
|
||||
OpenProjectAPIService $openProjectAPIService,
|
||||
?string $userId
|
||||
) {
|
||||
$this->initialStateService = $initialStateService;
|
||||
$this->config = $config;
|
||||
$this->appManager = $appManager;
|
||||
$this->userId = $userId;
|
||||
$user = $userSession->getUser();
|
||||
$this->openProjectAPIService = $openProjectAPIService;
|
||||
if (strpos(\OC::$server->get(IRequest::class)->getRequestUri(), 'files') !== false) {
|
||||
$this->oauthConnectionResult = $this->config->getUserValue(
|
||||
$user->getUID(), Application::APP_ID, 'oauth_connection_result', ''
|
||||
|
@ -93,6 +107,15 @@ class LoadSidebarScript implements IEventListener {
|
|||
}
|
||||
|
||||
public function handle(Event $event): void {
|
||||
// When user is non oidc based or there is some error when getting token for the targeted client
|
||||
// then we need to hide the oidc based connection for the user
|
||||
// so this check is required
|
||||
if (
|
||||
$this->config->getAppValue(Application::APP_ID, 'authorization_method', '') === OpenProjectAPIService::AUTH_METHOD_OIDC &&
|
||||
!$this->openProjectAPIService->getOIDCToken()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!($event instanceof LoadSidebar)) {
|
||||
return;
|
||||
}
|
||||
|
@ -108,17 +131,25 @@ class LoadSidebarScript implements IEventListener {
|
|||
}
|
||||
Util::addStyle(Application::APP_ID, 'tab');
|
||||
|
||||
$this->initialStateService->provideInitialState('admin-config-status', OpenProjectAPIService::isAdminConfigOk($this->config));
|
||||
$authorizationMethod = $this->config->getAppValue(Application::APP_ID, 'authorization_method', '');
|
||||
$this->initialStateService->provideInitialState('authorization_method', $authorizationMethod);
|
||||
$this->initialStateService->provideInitialState(
|
||||
'openproject-url', $this->config->getAppValue(Application::APP_ID, 'openproject_instance_url')
|
||||
);
|
||||
$this->initialStateService->provideInitialState(
|
||||
'admin_config_ok', OpenProjectAPIService::isAdminConfigOk($this->config)
|
||||
);
|
||||
|
||||
// authorization method can be either a 'oidc' or 'oauth2'
|
||||
// for 'oidc' the user info needs to be set (once token has been exchanged)
|
||||
$this->openProjectAPIService->setUserInfoForOidcBasedAuth($this->userId);
|
||||
|
||||
// for 'oauth2' state to be loaded
|
||||
$this->initialStateService->provideInitialState(
|
||||
'oauth-connection-result', $this->oauthConnectionResult
|
||||
);
|
||||
$this->initialStateService->provideInitialState(
|
||||
'oauth-connection-error-message', $this->oauthConnectionErrorMessage
|
||||
);
|
||||
$this->initialStateService->provideInitialState(
|
||||
'openproject-url',
|
||||
$this->config->getAppValue(Application::APP_ID, 'openproject_instance_url')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,22 +46,42 @@ class OpenProjectReferenceListener implements IEventListener {
|
|||
* @var IConfig
|
||||
*/
|
||||
private $config;
|
||||
/**
|
||||
* @var OpenProjectAPIService
|
||||
*/
|
||||
private $openProjectAPIService;
|
||||
|
||||
public function __construct(
|
||||
IInitialState $initialStateService,
|
||||
IConfig $config
|
||||
IConfig $config,
|
||||
OpenProjectAPIService $openProjectAPIService,
|
||||
) {
|
||||
$this->initialStateService = $initialStateService;
|
||||
$this->config = $config;
|
||||
$this->openProjectAPIService = $openProjectAPIService;
|
||||
}
|
||||
public function handle(Event $event): void {
|
||||
// When user is non oidc based or there is some error when getting token for the targeted client
|
||||
// then we need to hide the oidc based connection for the user
|
||||
// so this check is required
|
||||
if (
|
||||
$this->config->getAppValue(Application::APP_ID, 'authorization_method', '') === OpenProjectAPIService::AUTH_METHOD_OIDC &&
|
||||
!$this->openProjectAPIService->getOIDCToken()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!$event instanceof RenderReferenceEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
Util::addScript(Application::APP_ID, Application::APP_ID . '-reference');
|
||||
$this->initialStateService->provideInitialState('admin-config-status', OpenProjectAPIService::isAdminConfigOk($this->config));
|
||||
|
||||
$adminConfig = [
|
||||
'isAdminConfigOk' => OpenProjectAPIService::isAdminConfigOk($this->config),
|
||||
'authMethod' => $this->config->getAppValue(Application::APP_ID, 'authorization_method', '')
|
||||
];
|
||||
$this->initialStateService->provideInitialState(
|
||||
'admin-config',
|
||||
$adminConfig
|
||||
);
|
||||
$this->initialStateService->provideInitialState(
|
||||
'openproject-url',
|
||||
$this->config->getAppValue(Application::APP_ID, 'openproject_instance_url')
|
||||
|
|
|
@ -109,13 +109,23 @@ class OpenProjectSearchProvider implements IProvider {
|
|||
$offset = $query->getCursor();
|
||||
$offset = $offset ? intval($offset) : 0;
|
||||
$openprojectUrl = OpenProjectAPIService::sanitizeUrl($this->config->getAppValue(Application::APP_ID, 'openproject_instance_url'));
|
||||
$accessToken = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'token');
|
||||
|
||||
$authorizationMethod = $this->config->getAppValue(Application::APP_ID, 'authorization_method', '');
|
||||
$searchEnabled = $this->config->getUserValue(
|
||||
$user->getUID(),
|
||||
Application::APP_ID, 'search_enabled',
|
||||
$this->config->getAppValue(Application::APP_ID, 'default_enable_unified_search', '0')) === '1';
|
||||
if ($accessToken === '' || !$searchEnabled) {
|
||||
return SearchResult::paginated($this->getName(), [], 0);
|
||||
|
||||
if ($authorizationMethod === OpenProjectAPIService::AUTH_METHOD_OAUTH) {
|
||||
$accessToken = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'token');
|
||||
if (!$accessToken || !$searchEnabled) {
|
||||
return SearchResult::paginated($this->getName(), [], 0);
|
||||
}
|
||||
} elseif ($authorizationMethod === OpenProjectAPIService::AUTH_METHOD_OIDC) {
|
||||
$accessToken = $this->service->getOIDCToken();
|
||||
if (!$accessToken || !$searchEnabled) {
|
||||
return SearchResult::paginated($this->getName(), [], 0);
|
||||
}
|
||||
}
|
||||
|
||||
$searchResults = $this->service->searchWorkPackage($user->getUID(), $term, null, false);
|
||||
|
|
|
@ -29,9 +29,12 @@ use OCA\OpenProject\Exception\OpenprojectAvatarErrorException;
|
|||
use OCA\OpenProject\Exception\OpenprojectErrorException;
|
||||
use OCA\OpenProject\Exception\OpenprojectGroupfolderSetupConflictException;
|
||||
use OCA\OpenProject\Exception\OpenprojectResponseException;
|
||||
use OCA\OpenProject\ExchangedTokenRequestedEventHelper;
|
||||
use OCA\TermsOfService\Db\Entities\Signatory;
|
||||
use OCA\TermsOfService\Db\Mapper\SignatoryMapper;
|
||||
use OCA\TermsOfService\Db\Mapper\TermsMapper;
|
||||
use OCA\UserOIDC\Db\ProviderMapper;
|
||||
use OCA\UserOIDC\Exception\TokenExchangeFailedException;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\Encryption\IManager;
|
||||
|
@ -62,6 +65,8 @@ use Psr\Log\LoggerInterface;
|
|||
define('CACHE_TTL', 3600);
|
||||
|
||||
class OpenProjectAPIService {
|
||||
public const AUTH_METHOD_OAUTH = 'oauth2';
|
||||
public const AUTH_METHOD_OIDC = 'oidc';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
@ -133,6 +138,7 @@ class OpenProjectAPIService {
|
|||
private ISecureRandom $random;
|
||||
private IEventDispatcher $eventDispatcher;
|
||||
private AuditLogger $auditLogger;
|
||||
private ExchangedTokenRequestedEventHelper $exchangedTokenRequestedEventHelper;
|
||||
|
||||
public function __construct(
|
||||
string $appName,
|
||||
|
@ -153,7 +159,8 @@ class OpenProjectAPIService {
|
|||
ISubAdmin $subAdminManager,
|
||||
IDBConnection $db,
|
||||
ILogFactory $logFactory,
|
||||
IManager $encryptionManager
|
||||
IManager $encryptionManager,
|
||||
ExchangedTokenRequestedEventHelper $exchangedTokenRequestedEventHelper,
|
||||
) {
|
||||
$this->appName = $appName;
|
||||
$this->avatarManager = $avatarManager;
|
||||
|
@ -174,6 +181,7 @@ class OpenProjectAPIService {
|
|||
$this->db = $db;
|
||||
$this->logFactory = $logFactory;
|
||||
$this->encryptionManager = $encryptionManager;
|
||||
$this->exchangedTokenRequestedEventHelper = $exchangedTokenRequestedEventHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -316,9 +324,13 @@ class OpenProjectAPIService {
|
|||
string $openprojectUserName,
|
||||
string $nextcloudUserId
|
||||
): array {
|
||||
$accessToken = $this->config->getUserValue($nextcloudUserId, Application::APP_ID, 'token');
|
||||
$this->config->getAppValue(Application::APP_ID, 'openproject_client_id');
|
||||
$this->config->getAppValue(Application::APP_ID, 'openproject_client_secret');
|
||||
if ($this->config->getAppValue(Application::APP_ID, 'authorization_method', '') === self::AUTH_METHOD_OIDC) {
|
||||
$accessToken = $this->getOIDCToken();
|
||||
} else {
|
||||
$accessToken = $this->config->getUserValue($nextcloudUserId, Application::APP_ID, 'token');
|
||||
$this->config->getAppValue(Application::APP_ID, 'openproject_client_id');
|
||||
$this->config->getAppValue(Application::APP_ID, 'openproject_client_secret');
|
||||
}
|
||||
$openprojectUrl = $this->config->getAppValue(Application::APP_ID, 'openproject_instance_url');
|
||||
try {
|
||||
$response = $this->rawRequest(
|
||||
|
@ -439,10 +451,14 @@ class OpenProjectAPIService {
|
|||
*/
|
||||
public function request(string $userId,
|
||||
string $endPoint, array $params = [], string $method = 'GET'): array {
|
||||
$accessToken = $this->config->getUserValue($userId, Application::APP_ID, 'token');
|
||||
$refreshToken = $this->config->getUserValue($userId, Application::APP_ID, 'refresh_token');
|
||||
$clientID = $this->config->getAppValue(Application::APP_ID, 'openproject_client_id');
|
||||
$clientSecret = $this->config->getAppValue(Application::APP_ID, 'openproject_client_secret');
|
||||
if ($this->config->getAppValue(Application::APP_ID, 'authorization_method', '') === self::AUTH_METHOD_OIDC) {
|
||||
$accessToken = $this->getOIDCToken();
|
||||
} else {
|
||||
$accessToken = $this->config->getUserValue($userId, Application::APP_ID, 'token');
|
||||
$refreshToken = $this->config->getUserValue($userId, Application::APP_ID, 'refresh_token');
|
||||
$clientID = $this->config->getAppValue(Application::APP_ID, 'openproject_client_id');
|
||||
$clientSecret = $this->config->getAppValue(Application::APP_ID, 'openproject_client_secret');
|
||||
}
|
||||
$openprojectUrl = $this->config->getAppValue(Application::APP_ID, 'openproject_instance_url');
|
||||
if (!$openprojectUrl || !OpenProjectAPIService::validateURL($openprojectUrl)) {
|
||||
return ['error' => 'OpenProject URL is invalid', 'statusCode' => 500];
|
||||
|
@ -460,7 +476,11 @@ class OpenProjectAPIService {
|
|||
$body = (string) $response->getBody();
|
||||
// refresh token if it's invalid and we are using oauth
|
||||
// response can be : 'OAuth2 token is expired!', 'Invalid token!' or 'Not authorized'
|
||||
if ($response->getStatusCode() === 401) {
|
||||
// This condition applies exclusively to the OAuth2 authorization method and not to OIDC authorization,
|
||||
// as token refreshing for OIDC is managed by the 'user_oidc' application.
|
||||
if ($response->getStatusCode() === 401 &&
|
||||
$this->config->getAppValue(Application::APP_ID, 'authorization_method', '') === self::AUTH_METHOD_OAUTH
|
||||
) {
|
||||
$this->logger->info('Trying to REFRESH the access token', ['app' => $this->appName]);
|
||||
// try to refresh the token
|
||||
$result = $this->requestOAuthAccessToken($openprojectUrl, [
|
||||
|
@ -912,13 +932,13 @@ class OpenProjectAPIService {
|
|||
}
|
||||
|
||||
/**
|
||||
* checks if every admin config variables are set
|
||||
* checks if every admin config for oauth2 based authorization variables are set
|
||||
* checks if the oauth instance url is valid
|
||||
*
|
||||
* @param IConfig $config
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAdminConfigOk(IConfig $config):bool {
|
||||
public static function isAdminConfigOkForOauth2(IConfig $config):bool {
|
||||
$clientId = $config->getAppValue(Application::APP_ID, 'openproject_client_id');
|
||||
$clientSecret = $config->getAppValue(Application::APP_ID, 'openproject_client_secret');
|
||||
$oauthInstanceUrl = $config->getAppValue(Application::APP_ID, 'openproject_instance_url');
|
||||
|
@ -930,6 +950,41 @@ class OpenProjectAPIService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if every admin config for oidc based authorization variables are set
|
||||
* checks if the oauth instance url is valid
|
||||
*
|
||||
* @param IConfig $config
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAdminConfigOkForOIDCAuth(IConfig $config):bool {
|
||||
$oidcProvider = $config->getAppValue(Application::APP_ID, 'oidc_provider');
|
||||
$targetAudienceClientId = $config->getAppValue(Application::APP_ID, 'targeted_audience_client_id');
|
||||
$oauthInstanceUrl = $config->getAppValue(Application::APP_ID, 'openproject_instance_url');
|
||||
$checkIfConfigIsSet = !!($oidcProvider) && !!($targetAudienceClientId) && !!($oauthInstanceUrl);
|
||||
if (!$checkIfConfigIsSet) {
|
||||
return false;
|
||||
} else {
|
||||
return self::validateURL($oauthInstanceUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* returns overall admin config status whether it be 'oidc' or 'oauth2'
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAdminConfigOk(IConfig $config): bool {
|
||||
$authMethod = $config->getAppValue(Application::APP_ID, 'authorization_method');
|
||||
if ($authMethod === self::AUTH_METHOD_OAUTH) {
|
||||
return self::isAdminConfigOkForOauth2($config);
|
||||
} elseif ($authMethod === self::AUTH_METHOD_OIDC) {
|
||||
return self::isAdminConfigOkForOIDCAuth($config);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* makes sure the URL has no extra slashes
|
||||
*/
|
||||
|
@ -1339,7 +1394,11 @@ class OpenProjectAPIService {
|
|||
* @return array<mixed>|null
|
||||
*/
|
||||
public function getWorkPackageInfo(string $userId, int $wpId): ?array {
|
||||
$accessToken = $this->config->getUserValue($userId, Application::APP_ID, 'token');
|
||||
if ($this->config->getAppValue(Application::APP_ID, 'authorization_method', '') === self::AUTH_METHOD_OIDC) {
|
||||
$accessToken = $this->getOIDCToken();
|
||||
} else {
|
||||
$accessToken = $this->config->getUserValue($userId, Application::APP_ID, 'token');
|
||||
}
|
||||
if ($accessToken) {
|
||||
$searchResult = $this->searchWorkPackage($userId, null, null, false, $wpId);
|
||||
if (isset($searchResult['error'])) {
|
||||
|
@ -1558,4 +1617,69 @@ class OpenProjectAPIService {
|
|||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getOIDCToken(): ?string {
|
||||
if (!$this->isUserOIDCAppInstalledAndEnabled()) {
|
||||
$this->logger->debug('The user_oidc app is not installed or enabled');
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$event = $this->exchangedTokenRequestedEventHelper->getEvent();
|
||||
/** @psalm-suppress InvalidArgument for dispatchTyped($event)
|
||||
* but new ExchangedTokenRequestedEvent(targeted_audience_client_id) returns event
|
||||
*/
|
||||
$this->eventDispatcher->dispatchTyped($event);
|
||||
} catch (TokenExchangeFailedException $e) {
|
||||
$this->logger->debug('Failed to exchange token: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
$token = $event->getToken();
|
||||
if ($token === null) {
|
||||
$this->logger->debug('ExchangedTokenRequestedEvent event has not been caught by user_oidc');
|
||||
return null;
|
||||
}
|
||||
// token expiration info
|
||||
$this->logger->debug('Obtained a token that expires in ' . $token->getExpiresInFromNow());
|
||||
return $token->getAccessToken();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @return void
|
||||
*/
|
||||
public function setUserInfoForOidcBasedAuth(string $userId): void {
|
||||
$info = $this->request($userId, 'users/me');
|
||||
if (isset($info['lastName'], $info['firstName'], $info['id'])) {
|
||||
$fullName = $info['firstName'] . ' ' . $info['lastName'];
|
||||
$this->config->setUserValue($userId, Application::APP_ID, 'user_id', $info['id']);
|
||||
$this->config->setUserValue($userId, Application::APP_ID, 'user_name', $fullName);
|
||||
}
|
||||
}
|
||||
|
||||
public function getRegisteredOidcProviders(): array {
|
||||
$oidcProviders = [];
|
||||
if ($this->isUserOIDCAppInstalledAndEnabled()) {
|
||||
$providerMapper = new ProviderMapper($this->db);
|
||||
foreach ($providerMapper->getProviders() as $provider) {
|
||||
$oidcProviders[] = $provider->getIdentifier();
|
||||
}
|
||||
}
|
||||
return $oidcProviders;
|
||||
}
|
||||
|
||||
public function isUserOIDCAppInstalledAndEnabled(): bool {
|
||||
return (
|
||||
class_exists('\OCA\UserOIDC\Db\ProviderMapper') &&
|
||||
class_exists('\OCA\UserOIDC\Event\ExchangedTokenRequestedEvent') &&
|
||||
class_exists('\OCA\UserOIDC\Exception\TokenExchangeFailedException') &&
|
||||
$this->appManager->isInstalled(
|
||||
'user_oidc',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ class Admin implements ISettings {
|
|||
public function __construct(IConfig $config,
|
||||
OauthService $oauthService,
|
||||
OpenProjectAPIService $openProjectAPIService,
|
||||
IInitialState $initialStateService) {
|
||||
IInitialState $initialStateService
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->initialStateService = $initialStateService;
|
||||
$this->oauthService = $oauthService;
|
||||
|
@ -59,10 +60,18 @@ class Admin implements ISettings {
|
|||
$projectFolderStatusInformation = $this->openProjectAPIService->getProjectFolderSetupInformation();
|
||||
$isAllTermsOfServiceSignedForUserOpenProject = $this->openProjectAPIService->isAllTermsOfServiceSignedForUserOpenProject();
|
||||
$isAdminAuditConfigurationSetUpCorrectly = $this->openProjectAPIService->isAdminAuditConfigSetCorrectly();
|
||||
|
||||
$adminConfig = [
|
||||
'openproject_client_id' => $clientID,
|
||||
'openproject_client_secret' => $clientSecret,
|
||||
'openproject_instance_url' => $oauthUrl,
|
||||
'authorization_method' => $this->config->getAppValue(Application::APP_ID, 'authorization_method', ''),
|
||||
'authorization_settings' => [
|
||||
'oidc_provider' => $this->config->getAppValue(Application::APP_ID, 'oidc_provider', ''),
|
||||
'targeted_audience_client_id' => $this->config->getAppValue(
|
||||
Application::APP_ID, 'targeted_audience_client_id', ''
|
||||
),
|
||||
],
|
||||
'nc_oauth_client' => $clientInfo,
|
||||
'default_enable_navigation' => $this->config->getAppValue(Application::APP_ID, 'default_enable_navigation', '0') === '1',
|
||||
'default_enable_unified_search' => $this->config->getAppValue(Application::APP_ID, 'default_enable_unified_search', '0') === '1',
|
||||
|
@ -74,13 +83,16 @@ class Admin implements ISettings {
|
|||
'encryption_info' => [
|
||||
'server_side_encryption_enabled' => $this->openProjectAPIService->isServerSideEncryptionEnabled(),
|
||||
'encryption_enabled_for_groupfolders' => $this->config->getAppValue('groupfolders', 'enable_encryption', '') === 'true'
|
||||
]
|
||||
],
|
||||
'oidc_provider' => $this->openProjectAPIService->getRegisteredOidcProviders(),
|
||||
'user_oidc_enabled' => $this->openProjectAPIService->isUserOIDCAppInstalledAndEnabled()
|
||||
];
|
||||
|
||||
$adminConfigStatus = OpenProjectAPIService::isAdminConfigOk($this->config);
|
||||
|
||||
$this->initialStateService->provideInitialState('admin-config', $adminConfig);
|
||||
$this->initialStateService->provideInitialState('admin-config-status', $adminConfigStatus);
|
||||
$this->initialStateService->provideInitialState(
|
||||
'admin-config-status', OpenProjectAPIService::isAdminConfigOk($this->config)
|
||||
);
|
||||
|
||||
|
||||
return new TemplateResponse(Application::APP_ID, 'adminSettings');
|
||||
}
|
||||
|
|
|
@ -25,24 +25,40 @@ class Personal implements ISettings {
|
|||
*/
|
||||
private $userId;
|
||||
|
||||
/**
|
||||
* @var OpenProjectAPIService
|
||||
*/
|
||||
private $openProjectAPIService;
|
||||
|
||||
|
||||
public function __construct(
|
||||
IConfig $config,
|
||||
IInitialState $initialStateService,
|
||||
OpenProjectAPIService $openProjectAPIService,
|
||||
?string $userId) {
|
||||
$this->config = $config;
|
||||
$this->initialStateService = $initialStateService;
|
||||
$this->userId = $userId;
|
||||
$this->openProjectAPIService = $openProjectAPIService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
public function getForm(): TemplateResponse {
|
||||
$token = $this->config->getUserValue($this->userId, Application::APP_ID, 'token');
|
||||
$authorizationMethod = $this->config->getAppValue(Application::APP_ID, 'authorization_method', '');
|
||||
$token = '';
|
||||
if ($authorizationMethod === OpenProjectAPIService::AUTH_METHOD_OIDC) {
|
||||
$token = $this->openProjectAPIService->getOIDCToken();
|
||||
if ($token) {
|
||||
// when connection is oidc based then user information needs to be saved
|
||||
$this->openProjectAPIService->setUserInfoForOidcBasedAuth($this->userId);
|
||||
}
|
||||
}
|
||||
if ($authorizationMethod === OpenProjectAPIService::AUTH_METHOD_OAUTH) {
|
||||
$token = $this->config->getUserValue($this->userId, Application::APP_ID, 'token');
|
||||
}
|
||||
$userName = $this->config->getUserValue($this->userId, Application::APP_ID, 'user_name');
|
||||
|
||||
|
||||
// take the fallback value from the defaults
|
||||
$searchEnabled = $this->config->getUserValue(
|
||||
$this->userId,
|
||||
|
@ -65,6 +81,7 @@ class Personal implements ISettings {
|
|||
];
|
||||
|
||||
$userConfig['admin_config_ok'] = OpenProjectAPIService::isAdminConfigOk($this->config);
|
||||
$userConfig['authorization_method'] = $authorizationMethod;
|
||||
$this->initialStateService->provideInitialState('user-config', $userConfig);
|
||||
|
||||
$oauthConnectionResult = $this->config->getUserValue(
|
||||
|
|
|
@ -31,6 +31,11 @@
|
|||
<referencedClass name="OCA\TermsOfService\Db\Mapper\SignatoryMapper" />
|
||||
<referencedClass name="OCA\TermsOfService\Db\Entities\Signatory" />
|
||||
<referencedClass name="OCA\TermsOfService\Db\Mapper\TermsMapper" />
|
||||
<!-- these classes belong to the user_oidc app, which isn't compulsory, so might not exist while running psalm -->
|
||||
<referencedClass name="OCA\UserOIDC\Db\ProviderMapper" />
|
||||
<referencedClass name="OCA\UserOIDC\Model\Token" />
|
||||
<referencedClass name="OCA\UserOIDC\Event\ExchangedTokenRequestedEvent" />
|
||||
<referencedClass name="OCA\UserOIDC\Exception\TokenExchangeFailedException" />
|
||||
<!-- these classes belong to the activity app, which isn't compulsory, so might not exist while running psalm -->
|
||||
<referencedClass name="OCA\Activity\UserSettings" />
|
||||
<referencedClass name="OCA\Activity\GroupHelperDisabled" />
|
||||
|
@ -83,6 +88,9 @@
|
|||
<referencedClass name="OC\Http\Client\LocalAddressChecker"/>
|
||||
<!-- these are classes form terms_of_service app, which isn't cloned while doing static code analysis -->
|
||||
<referencedClass name="OCA\TermsOfService\Db\Mapper\SignatoryMapper" />
|
||||
<!-- these classes belong to the user_oidc app, which isn't compulsory, so might not exist while running psalm -->
|
||||
<referencedClass name="OCA\UserOIDC\Model\Token" />
|
||||
<referencedClass name="OCA\UserOIDC\Event\ExchangedTokenRequestedEvent" />
|
||||
<!-- these are classes form activity app, which isn't cloned while doing static code analysis -->
|
||||
<referencedClass name="OCA\Activity\UserSettings" />
|
||||
<referencedClass name="OCA\Activity\GroupHelperDisabled" />
|
||||
|
|
|
@ -57,13 +57,153 @@
|
|||
</NcButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openproject-oauth-values">
|
||||
<div class="authorization-method">
|
||||
<FormHeading index="2"
|
||||
:title="t('integration_openproject', 'Authorization method')"
|
||||
:is-complete="isAuthorizationMethodFormComplete"
|
||||
:is-disabled="isAuthorizationFormInDisabledMode"
|
||||
:is-dark-theme="isDarkTheme" />
|
||||
<div v-if="isServerHostFormComplete">
|
||||
<div v-if="isAuthorizationFormInEditMode" class="authorization-method">
|
||||
<div class="authorization-method--description">
|
||||
<p class="title">
|
||||
{{ t('integration_openproject', 'Need help setting this up?') }}
|
||||
</p>
|
||||
<p class="description" v-html="getAuthorizationMethodHintText" /> <!-- eslint-disable-line vue/no-v-html -->
|
||||
</div>
|
||||
<div class="authorization-method--options">
|
||||
<NcCheckboxRadioSwitch class="radio-check"
|
||||
:checked.sync="authorizationMethod.authorizationMethodSet"
|
||||
:value="authMethods.OAUTH2"
|
||||
type="radio">
|
||||
{{ authMethodsLabel.OAUTH2 }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
<NcCheckboxRadioSwitch class="radio-check"
|
||||
:checked.sync="authorizationMethod.authorizationMethodSet"
|
||||
:value="authMethods.OIDC"
|
||||
:disabled="!isOIDCAppInstalledAndEnabled"
|
||||
type="radio">
|
||||
{{ authMethodsLabel.OIDC }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
<p v-if="!isOIDCAppInstalledAndEnabled" class="oidc-app-check-description" v-html="getOIDCAppNotInstalledHintText" /> <!-- eslint-disable-line vue/no-v-html -->
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p class="title">
|
||||
{{ getSelectedAuthenticatedMethod }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<NcButton v-if="isAuthorizationMethodFormInViewMode"
|
||||
data-test-id="reset-authorization-method-btn"
|
||||
@click="setAuthorizationMethodInEditMode">
|
||||
<template #icon>
|
||||
<PencilIcon :size="20" />
|
||||
</template>
|
||||
{{ t('integration_openproject', 'Edit authorization method') }}
|
||||
</NcButton>
|
||||
<NcButton v-if="isAuthorizationFormInEditMode && authorizationMethod.currentAuthorizationMethodSelected !== null"
|
||||
class="mr-2"
|
||||
data-test-id="cancel-edit-auth-method-btn"
|
||||
@click="setAuthorizationMethodToViewMode">
|
||||
{{ t('integration_openproject', 'Cancel') }}
|
||||
</NcButton>
|
||||
<NcButton v-if="isAuthorizationFormInEditMode"
|
||||
data-test-id="submit-auth-method-values-btn"
|
||||
type="primary"
|
||||
:disabled="isAuthorizationMethodSelected"
|
||||
@click="selectAuthorizationMethod">
|
||||
<template #icon>
|
||||
<NcLoadingIcon v-if="loadingAuthorizationMethodForm" class="loading-spinner" :size="20" />
|
||||
<CheckBoldIcon v-else fill-color="#FFFFFF" :size="20" />
|
||||
</template>
|
||||
{{ t('integration_openproject', 'Save') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="authorizationMethod.currentAuthorizationMethodSelected === authMethods.OIDC" class="authorization-settings">
|
||||
<FormHeading index="3"
|
||||
:title="t('integration_openproject', 'Authorization settings')"
|
||||
:is-complete="isAuthorizationSettingFormComplete"
|
||||
:is-disabled="isAuthorizationSettingFormInDisabledMode"
|
||||
:is-dark-theme="isDarkTheme" />
|
||||
<div class="authorization-settings--content">
|
||||
<FieldValue v-if="isAuthorizationSettingsInViewMode"
|
||||
is-required
|
||||
class="pb-1"
|
||||
:title="t('integration_openproject', 'OIDC Provider')"
|
||||
:value="authorizationSetting.oidcProviderSet" />
|
||||
<div v-else class="authorization-settings--content--provider">
|
||||
<p class="authorization-settings--content--label">
|
||||
{{ t('integration_openproject', 'OIDC provider *') }}
|
||||
</p>
|
||||
<div id="select">
|
||||
<NcSelect
|
||||
input-id="provider-search-input"
|
||||
:placeholder="t('integration_openproject', 'Select an OIDC provider')"
|
||||
:options="registeredOidcProviders"
|
||||
:value="getCurrentSelectedOIDCProvider"
|
||||
:filterable="true"
|
||||
:close-on-select="true"
|
||||
:clear-search-on-blur="() => false"
|
||||
:append-to-body="false"
|
||||
:label-outside="true"
|
||||
:input-label="t('integration_openproject', 'OIDC provider')"
|
||||
@option:selected="onSelectOIDCProvider" />
|
||||
</div>
|
||||
<p class="description" v-html="getConfigureOIDCHintText" /> <!-- eslint-disable-line vue/no-v-html -->
|
||||
</div>
|
||||
<FieldValue v-if="isAuthorizationSettingsInViewMode"
|
||||
is-required
|
||||
class="pb-1"
|
||||
:title="t('integration_openproject', 'OpenProject client ID')"
|
||||
:value="state.authorization_settings.targeted_audience_client_id" />
|
||||
<div v-else class="authorization-settings--content--client">
|
||||
<TextInput
|
||||
id="authorization-method-target-client-id"
|
||||
v-model="state.authorization_settings.targeted_audience_client_id"
|
||||
class="py-1"
|
||||
is-required
|
||||
:label="t('integration_openproject', 'OpenProject client ID')"
|
||||
hint-text="You can get this value from Keycloak when you set-up define the client" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<NcButton v-if="isAuthorizationSettingsInViewMode"
|
||||
data-test-id="reset-auth-settings-btn"
|
||||
@click="setAuthorizationSettingInEditMode">
|
||||
<template #icon>
|
||||
<PencilIcon :size="20" />
|
||||
</template>
|
||||
{{ t('integration_openproject', 'Edit athorization settings') }}
|
||||
</NcButton>
|
||||
<NcButton v-if="isAuthorizationSettingInEditMode && authorizationSetting.currentOIDCProviderSelected !== null && authorizationSetting.targetedAudienceClientIdSet !== null"
|
||||
class="mr-2"
|
||||
data-test-id="cancel-edit-auth-setting-btn"
|
||||
@click="setAuthorizationSettingToViewMode">
|
||||
{{ t('integration_openproject', 'Cancel') }}
|
||||
</NcButton>
|
||||
<NcButton v-if="isAuthorizationSettingInEditMode"
|
||||
data-test-id="submit-oidc-auth-settings-values-btn"
|
||||
type="primary"
|
||||
:disabled="isAuthorizationSettingsSelected"
|
||||
@click="saveOIDCAuthSetting">
|
||||
<template #icon>
|
||||
<NcLoadingIcon v-if="loadingAuthorizationMethodForm" class="loading-spinner" :size="20" />
|
||||
<CheckBoldIcon v-else fill-color="#FFFFFF" :size="20" />
|
||||
</template>
|
||||
{{ t('integration_openproject', 'Save') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="authorizationMethod.currentAuthorizationMethodSelected === authMethods.OAUTH2 || authorizationMethod.currentAuthorizationMethodSelected === null" class="openproject-oauth-values">
|
||||
<FormHeading index="3"
|
||||
:title="t('integration_openproject', 'OpenProject OAuth settings')"
|
||||
:is-complete="isOPOAuthFormComplete"
|
||||
:is-disabled="isOPOAuthFormInDisableMode"
|
||||
:is-dark-theme="isDarkTheme" />
|
||||
<div v-if="isServerHostFormComplete">
|
||||
<div v-if="authorizationMethod.currentAuthorizationMethodSelected !== null">
|
||||
<FieldValue v-if="isOPOAuthFormInView"
|
||||
is-required
|
||||
:value="state.openproject_client_id"
|
||||
|
@ -111,8 +251,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nextcloud-oauth-values">
|
||||
<FormHeading index="3"
|
||||
<div v-if="authorizationMethod.currentAuthorizationMethodSelected === authMethods.OAUTH2 || authorizationMethod.currentAuthorizationMethodSelected === null" class="nextcloud-oauth-values">
|
||||
<FormHeading index="4"
|
||||
:title="t('integration_openproject', 'Nextcloud OAuth client')"
|
||||
:is-complete="isNcOAuthFormComplete"
|
||||
:is-disabled="isNcOAuthFormInDisableMode"
|
||||
|
@ -177,7 +317,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="project-folder-setup">
|
||||
<FormHeading index="4"
|
||||
<FormHeading :index="authorizationMethod.currentAuthorizationMethodSelected === authMethods.OIDC ? '4' : '5'"
|
||||
:is-project-folder-setup-heading="true"
|
||||
:title="t('integration_openproject', 'Project folders (recommended)')"
|
||||
:is-setup-complete-without-project-folders="isSetupCompleteWithoutProjectFolders"
|
||||
|
@ -286,7 +426,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div v-if="state.app_password_set">
|
||||
<FormHeading index="5"
|
||||
<FormHeading index="6"
|
||||
:title="t('integration_openproject', 'Project folders application connection')"
|
||||
:is-complete="isOPUserAppPasswordFormComplete"
|
||||
:is-disabled="isOPUserAppPasswordInDisableMode"
|
||||
|
@ -338,7 +478,7 @@
|
|||
</template>
|
||||
{{ t('integration_openproject', 'Reset') }}
|
||||
</NcButton>
|
||||
<div v-if="isIntegrationComplete" class="default-prefs">
|
||||
<div v-if="isIntegrationCompleteWithOauth2 || isIntegrationCompleteWithOIDC" class="default-prefs">
|
||||
<h2>{{ t('integration_openproject', 'Default user settings') }}</h2>
|
||||
<p>
|
||||
{{ t('integration_openproject', 'A new user will receive these defaults and they will be applied to the integration app till the user changes them.') }}
|
||||
|
@ -372,7 +512,13 @@ import { generateUrl } from '@nextcloud/router'
|
|||
import { showSuccess, showError } from '@nextcloud/dialogs'
|
||||
import CheckBoldIcon from 'vue-material-design-icons/CheckBold.vue'
|
||||
import PencilIcon from 'vue-material-design-icons/Pencil.vue'
|
||||
import { NcLoadingIcon, NcCheckboxRadioSwitch, NcButton, NcNoteCard } from '@nextcloud/vue'
|
||||
import {
|
||||
NcLoadingIcon,
|
||||
NcCheckboxRadioSwitch,
|
||||
NcButton,
|
||||
NcNoteCard,
|
||||
NcSelect,
|
||||
} from '@nextcloud/vue'
|
||||
import RestoreIcon from 'vue-material-design-icons/Restore.vue'
|
||||
import AutoRenewIcon from 'vue-material-design-icons/Autorenew.vue'
|
||||
import TextInput from './admin/TextInput.vue'
|
||||
|
@ -380,12 +526,13 @@ import FieldValue from './admin/FieldValue.vue'
|
|||
import FormHeading from './admin/FormHeading.vue'
|
||||
import CheckBox from '../components/settings/CheckBox.vue'
|
||||
import SettingsTitle from '../components/settings/SettingsTitle.vue'
|
||||
import { F_MODES, FORM, USER_SETTINGS } from '../utils.js'
|
||||
import { F_MODES, FORM, USER_SETTINGS, AUTH_METHOD, AUTH_METHOD_LABEL } from '../utils.js'
|
||||
import TermsOfServiceUnsigned from './admin/TermsOfServiceUnsigned.vue'
|
||||
import dompurify from 'dompurify'
|
||||
export default {
|
||||
name: 'AdminSettings',
|
||||
components: {
|
||||
NcSelect,
|
||||
NcButton,
|
||||
FieldValue,
|
||||
FormHeading,
|
||||
|
@ -407,13 +554,15 @@ export default {
|
|||
// server host form is never disabled.
|
||||
// it's either editable or view only
|
||||
server: F_MODES.EDIT,
|
||||
authorizationMethod: F_MODES.DISABLE,
|
||||
authorizationSetting: F_MODES.DISABLE,
|
||||
opOauth: F_MODES.DISABLE,
|
||||
ncOauth: F_MODES.DISABLE,
|
||||
opUserAppPassword: F_MODES.DISABLE,
|
||||
projectFolderSetUp: F_MODES.DISABLE,
|
||||
},
|
||||
isFormCompleted: {
|
||||
server: false, opOauth: false, ncOauth: false, opUserAppPassword: false, projectFolderSetUp: false,
|
||||
server: false, authorizationMethod: false, authorizationSetting: false, opOauth: false, ncOauth: false, opUserAppPassword: false, projectFolderSetUp: false,
|
||||
},
|
||||
buttonTextLabel: {
|
||||
keepCurrentChange: t('integration_openproject', 'Keep current setup'),
|
||||
|
@ -423,6 +572,8 @@ export default {
|
|||
loadingServerHostForm: false,
|
||||
loadingProjectFolderSetup: false,
|
||||
loadingOPOauthForm: false,
|
||||
loadingAuthorizationMethodForm: false,
|
||||
loadingAuthorizationSettingForm: false,
|
||||
isOpenProjectInstanceValid: null,
|
||||
openProjectNotReachableErrorMessage: null,
|
||||
openProjectNotReachableErrorMessageDetails: null,
|
||||
|
@ -446,6 +597,22 @@ export default {
|
|||
isDarkTheme: null,
|
||||
isAllTermsOfServiceSignedForUserOpenProject: true,
|
||||
userSettingDescription: USER_SETTINGS,
|
||||
authMethods: AUTH_METHOD,
|
||||
authMethodsLabel: AUTH_METHOD_LABEL,
|
||||
// here 'Set' defines that the method is selected and saved in database (e.g authorizationMethodSet)
|
||||
// whereas 'Selected' defines that it is the current selection (e.g currentAuthorizationMethodSelected)
|
||||
authorizationMethod: {
|
||||
// default authorization method is set to 'oauth2'
|
||||
authorizationMethodSet: AUTH_METHOD.OAUTH2,
|
||||
currentAuthorizationMethodSelected: null,
|
||||
},
|
||||
authorizationSetting: {
|
||||
oidcProviderSet: null,
|
||||
targetedAudienceClientIdSet: null,
|
||||
currentOIDCProviderSelected: null,
|
||||
currentTargetedAudienceClientIdSelected: null,
|
||||
},
|
||||
registeredOidcProviders: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -473,6 +640,12 @@ export default {
|
|||
isServerHostFormComplete() {
|
||||
return this.isFormCompleted.server
|
||||
},
|
||||
isAuthorizationMethodFormComplete() {
|
||||
return this.isFormCompleted.authorizationMethod
|
||||
},
|
||||
isAuthorizationSettingFormComplete() {
|
||||
return this.isFormCompleted.authorizationSetting
|
||||
},
|
||||
isOPOAuthFormComplete() {
|
||||
return this.isFormCompleted.opOauth
|
||||
},
|
||||
|
@ -488,6 +661,12 @@ export default {
|
|||
isServerHostFormInView() {
|
||||
return this.formMode.server === F_MODES.VIEW
|
||||
},
|
||||
isAuthorizationMethodFormInViewMode() {
|
||||
return this.formMode.authorizationMethod === F_MODES.VIEW
|
||||
},
|
||||
isAuthorizationSettingsInViewMode() {
|
||||
return this.formMode.authorizationSetting === F_MODES.VIEW
|
||||
},
|
||||
isOPOAuthFormInView() {
|
||||
return this.formMode.opOauth === F_MODES.VIEW
|
||||
},
|
||||
|
@ -500,12 +679,24 @@ export default {
|
|||
isOPOAuthFormInDisableMode() {
|
||||
return this.formMode.opOauth === F_MODES.DISABLE
|
||||
},
|
||||
isAuthorizationFormInDisabledMode() {
|
||||
return this.formMode.authorizationMethod === F_MODES.DISABLE
|
||||
},
|
||||
isAuthorizationSettingFormInDisabledMode() {
|
||||
return this.formMode.authorizationSetting === F_MODES.DISABLE
|
||||
},
|
||||
isOPUserAppPasswordFormInEdit() {
|
||||
return this.formMode.opUserAppPassword === F_MODES.EDIT
|
||||
},
|
||||
isProjectFolderSetupFormInEdit() {
|
||||
return this.formMode.projectFolderSetUp === F_MODES.EDIT
|
||||
},
|
||||
isAuthorizationFormInEditMode() {
|
||||
return this.formMode.authorizationMethod === F_MODES.EDIT
|
||||
},
|
||||
isAuthorizationSettingInEditMode() {
|
||||
return this.formMode.authorizationSetting === F_MODES.EDIT
|
||||
},
|
||||
isNcOAuthFormInDisableMode() {
|
||||
return this.formMode.ncOauth === F_MODES.DISABLE
|
||||
},
|
||||
|
@ -563,14 +754,39 @@ export default {
|
|||
const htmlLink = `<a class="link" href="https://www.openproject.org/docs/system-admin-guide/integrations/nextcloud/#files-are-not-encrypted-when-using-nextcloud-server-side-encryption" target="_blank" title="${linkText}">${linkText}</a>`
|
||||
return t('integration_openproject', 'Server-side encryption is active, but encryption for Group Folders is not yet enabled. To ensure secure storage of files in project folders, please follow the configuration steps in the {htmlLink}.', { htmlLink }, null, { escape: false, sanitize: false })
|
||||
},
|
||||
isIntegrationComplete() {
|
||||
getAuthorizationMethodHintText() {
|
||||
const linkText = t('integration_openproject', 'authorization methods you can use with OpenProject')
|
||||
const htmlLink = `<a class="link" href="https://www.openproject.org/docs/system-admin-guide/integrations/nextcloud/#files-are-not-encrypted-when-using-nextcloud-server-side-encryption" target="_blank" title="${linkText}">${linkText}</a>`
|
||||
return t('integration_openproject', 'Please read our guide on {htmlLink}.', { htmlLink }, null, { escape: false, sanitize: false })
|
||||
},
|
||||
getOIDCAppNotInstalledHintText() {
|
||||
const linkText = t('integration_openproject', 'User OIDC')
|
||||
const url = generateUrl('settings/apps/files/user_oidc')
|
||||
const htmlLink = `<a class="link" href="${url}" target="_blank" title="${linkText}">${linkText}</a>`
|
||||
return t('integration_openproject', 'Please install the {htmlLink} app to be able to use Keycloak for authorization with OpenProject.', { htmlLink }, null, { escape: false, sanitize: false })
|
||||
},
|
||||
getConfigureOIDCHintText() {
|
||||
const linkText = t('integration_openproject', 'User OIDC app')
|
||||
const htmlLink = `<a class="link" href="" target="_blank" title="${linkText}">${linkText}</a>`
|
||||
return t('integration_openproject', 'You can configure OIDC providers in the {htmlLink}.', { htmlLink }, null, { escape: false, sanitize: false })
|
||||
},
|
||||
isIntegrationCompleteWithOauth2() {
|
||||
return (this.isServerHostFormComplete
|
||||
&& this.isAuthorizationMethodFormComplete
|
||||
&& this.isOPOAuthFormComplete
|
||||
&& this.isNcOAuthFormComplete
|
||||
&& this.isManagedGroupFolderSetUpComplete
|
||||
&& !this.isOPUserAppPasswordFormInEdit
|
||||
)
|
||||
},
|
||||
isIntegrationCompleteWithOIDC() {
|
||||
return (this.isServerHostFormComplete
|
||||
&& this.isAuthorizationMethodFormComplete
|
||||
&& this.isAuthorizationSettingFormComplete
|
||||
&& this.isManagedGroupFolderSetUpComplete
|
||||
&& !this.isOPUserAppPasswordFormInEdit
|
||||
)
|
||||
},
|
||||
isSetupCompleteWithoutProjectFolders() {
|
||||
if (this.isProjectFolderSetupFormInEdit) {
|
||||
return false
|
||||
|
@ -590,6 +806,30 @@ export default {
|
|||
return this.state.encryption_info.server_side_encryption_enabled
|
||||
&& !this.state.encryption_info.encryption_enabled_for_groupfolders
|
||||
},
|
||||
getSelectedAuthenticatedMethod() {
|
||||
return this.authorizationMethod.authorizationMethodSet === this.authMethods.OIDC
|
||||
? this.authMethodsLabel.OIDC
|
||||
: this.authMethodsLabel.OAUTH2
|
||||
},
|
||||
isAuthorizationMethodSelected() {
|
||||
return this.authorizationMethod.currentAuthorizationMethodSelected === this.authorizationMethod.authorizationMethodSet
|
||||
},
|
||||
isAuthorizationSettingsSelected() {
|
||||
const { oidcProviderSet, currentOIDCProviderSelected } = this.authorizationSetting
|
||||
return currentOIDCProviderSelected === null
|
||||
|| !this.getCurrentSelectedTargetedClientId
|
||||
|| (oidcProviderSet === currentOIDCProviderSelected && this.authorizationSetting.targetedAudienceClientIdSet === this.getCurrentSelectedTargetedClientId)
|
||||
|| (this.authorizationSetting.targetedAudienceClientIdSet === this.getCurrentSelectedTargetedClientId && oidcProviderSet === currentOIDCProviderSelected)
|
||||
},
|
||||
getCurrentSelectedOIDCProvider() {
|
||||
return this.authorizationSetting.currentOIDCProviderSelected
|
||||
},
|
||||
getCurrentSelectedTargetedClientId() {
|
||||
return this.state.authorization_settings.targeted_audience_client_id
|
||||
},
|
||||
isOIDCAppInstalledAndEnabled() {
|
||||
return this.state.user_oidc_enabled
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
|
@ -615,22 +855,70 @@ export default {
|
|||
} else {
|
||||
this.textLabelProjectFolderSetupButton = this.buttonTextLabel.keepCurrentChange
|
||||
}
|
||||
if (this.state.openproject_instance_url && this.state.openproject_client_id && this.state.openproject_client_secret && this.state.nc_oauth_client) {
|
||||
// for oauth2 authorization
|
||||
if (this.state.openproject_instance_url
|
||||
&& this.state.openproject_client_id
|
||||
&& this.state.openproject_client_secret
|
||||
&& this.state.nc_oauth_client
|
||||
) {
|
||||
this.showDefaultManagedProjectFolders = true
|
||||
}
|
||||
// for oidc authorization
|
||||
if (this.state.authorization_method === AUTH_METHOD.OIDC
|
||||
&& this.state.openproject_instance_url
|
||||
&& this.state.authorization_settings.oidc_provider
|
||||
&& this.state.authorization_settings.targeted_audience_client_id
|
||||
) {
|
||||
this.showDefaultManagedProjectFolders = true
|
||||
}
|
||||
if (this.state.fresh_project_folder_setup === false) {
|
||||
this.showDefaultManagedProjectFolders = true
|
||||
}
|
||||
if (this.state.openproject_instance_url) {
|
||||
this.formMode.server = F_MODES.VIEW
|
||||
this.isFormCompleted.server = true
|
||||
}
|
||||
if (this.state.authorization_method) {
|
||||
this.formMode.authorizationMethod = F_MODES.VIEW
|
||||
this.isFormCompleted.authorizationMethod = true
|
||||
this.authorizationMethod.authorizationMethodSet = this.authorizationMethod.currentAuthorizationMethodSelected = this.state.authorization_method
|
||||
}
|
||||
if (this.state.openproject_instance_url && this.state.authorization_method) {
|
||||
if (this.state.authorization_method === AUTH_METHOD.OAUTH2) {
|
||||
if (!this.state.openproject_client_id || !this.state.openproject_client_secret) {
|
||||
this.formMode.authorizationSetting = F_MODES.EDIT
|
||||
}
|
||||
}
|
||||
if (this.state.authorization_method === AUTH_METHOD.OIDC) {
|
||||
if (!this.state.authorization_settings.oidc_provider || !this.state.authorization_settings.targeted_audience_client_id) {
|
||||
this.formMode.authorizationSetting = F_MODES.EDIT
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.state.authorization_method === AUTH_METHOD.OIDC
|
||||
&& this.state.authorization_settings.oidc_provider
|
||||
&& this.state.authorization_settings.targeted_audience_client_id
|
||||
) {
|
||||
this.formMode.authorizationSetting = F_MODES.VIEW
|
||||
this.isFormCompleted.authorizationSetting = true
|
||||
this.authorizationSetting.oidcProviderSet = this.authorizationSetting.currentOIDCProviderSelected = this.state.authorization_settings.oidc_provider
|
||||
this.authorizationSetting.targetedAudienceClientIdSet = this.authorizationSetting.currentTargetedAudienceClientIdSelected = this.state.authorization_settings.targeted_audience_client_id
|
||||
}
|
||||
if (!!this.state.openproject_client_id && !!this.state.openproject_client_secret) {
|
||||
this.formMode.opOauth = F_MODES.VIEW
|
||||
this.isFormCompleted.opOauth = true
|
||||
}
|
||||
if (this.state.openproject_instance_url) {
|
||||
if (!this.state.openproject_client_id || !this.state.openproject_client_secret) {
|
||||
if (!this.state.authorization_method) {
|
||||
this.formMode.authorizationMethod = F_MODES.EDIT
|
||||
}
|
||||
}
|
||||
if (this.state.openproject_instance_url && this.state.authorization_method) {
|
||||
if (!this.state.openproject_client_id && !this.state.openproject_client_secret) {
|
||||
this.formMode.opOauth = F_MODES.EDIT
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.nc_oauth_client) {
|
||||
this.formMode.ncOauth = F_MODES.VIEW
|
||||
this.isFormCompleted.ncOauth = true
|
||||
|
@ -644,7 +932,7 @@ export default {
|
|||
this.formMode.projectFolderSetUp = F_MODES.VIEW
|
||||
this.isFormCompleted.projectFolderSetUp = true
|
||||
}
|
||||
if (this.formMode.ncOauth === F_MODES.VIEW) {
|
||||
if (this.formMode.ncOauth === F_MODES.VIEW || this.formMode.authorizationSetting === F_MODES.VIEW) {
|
||||
this.showDefaultManagedProjectFolders = true
|
||||
}
|
||||
if (this.showDefaultManagedProjectFolders) {
|
||||
|
@ -659,6 +947,10 @@ export default {
|
|||
this.textLabelProjectFolderSetupButton = this.buttonTextLabel.keepCurrentChange
|
||||
}
|
||||
this.isProjectFolderSwitchEnabled = this.currentProjectFolderState === true
|
||||
|
||||
if (this.state.oidc_provider) {
|
||||
this.registeredOidcProviders = this.state.oidc_provider
|
||||
}
|
||||
}
|
||||
},
|
||||
projectFolderSetUpErrorMessageDescription(errorKey) {
|
||||
|
@ -678,12 +970,30 @@ export default {
|
|||
setServerHostFormToViewMode() {
|
||||
this.formMode.server = F_MODES.VIEW
|
||||
},
|
||||
setAuthorizationMethodToViewMode() {
|
||||
this.formMode.authorizationMethod = F_MODES.VIEW
|
||||
this.isFormCompleted.authorizationMethod = true
|
||||
this.authorizationMethod.authorizationMethodSet = this.authorizationMethod.currentAuthorizationMethodSelected
|
||||
},
|
||||
setAuthorizationSettingToViewMode() {
|
||||
this.formMode.authorizationSetting = F_MODES.VIEW
|
||||
this.isFormCompleted.authorizationSetting = true
|
||||
this.state.authorization_settings.targeted_audience_client_id = this.authorizationSetting.currentTargetedAudienceClientIdSelected
|
||||
},
|
||||
setServerHostFormToEditMode() {
|
||||
this.formMode.server = F_MODES.EDIT
|
||||
// set the edit variable to the current saved value
|
||||
this.serverHostUrlForEdit = this.state.openproject_instance_url
|
||||
this.isOpenProjectInstanceValid = null
|
||||
},
|
||||
setAuthorizationMethodInEditMode() {
|
||||
this.formMode.authorizationMethod = F_MODES.EDIT
|
||||
this.isFormCompleted.authorizationMethod = false
|
||||
},
|
||||
setAuthorizationSettingInEditMode() {
|
||||
this.formMode.authorizationSetting = F_MODES.EDIT
|
||||
this.isFormCompleted.authorizationSetting = false
|
||||
},
|
||||
setProjectFolderSetUpToEditMode() {
|
||||
this.formMode.projectFolderSetUp = F_MODES.EDIT
|
||||
this.isFormCompleted.projectFolderSetUp = false
|
||||
|
@ -699,7 +1009,7 @@ export default {
|
|||
async setNCOAuthFormToViewMode() {
|
||||
this.formMode.ncOauth = F_MODES.VIEW
|
||||
this.isFormCompleted.ncOauth = true
|
||||
if (!this.isIntegrationComplete && this.formMode.projectFolderSetUp !== F_MODES.EDIT && this.formMode.opUserAppPassword !== F_MODES.EDIT) {
|
||||
if (!this.isIntegrationCompleteWithOauth2 && this.formMode.projectFolderSetUp !== F_MODES.EDIT && this.formMode.opUserAppPassword !== F_MODES.EDIT) {
|
||||
this.formMode.projectFolderSetUp = F_MODES.EDIT
|
||||
this.showDefaultManagedProjectFolders = true
|
||||
this.isProjectFolderSwitchEnabled = true
|
||||
|
@ -767,8 +1077,8 @@ export default {
|
|||
this.state.openproject_instance_url = this.serverHostUrlForEdit
|
||||
this.formMode.server = F_MODES.VIEW
|
||||
this.isFormCompleted.server = true
|
||||
if (!this.isFormCompleted.opOauth) {
|
||||
this.formMode.opOauth = F_MODES.EDIT
|
||||
if (!this.isFormCompleted.authorizationMethod) {
|
||||
this.formMode.authorizationMethod = F_MODES.EDIT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -787,6 +1097,42 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
async saveAuthorizationMethodValue() {
|
||||
this.isFormStep = FORM.AUTHORIZATION_METHOD
|
||||
this.loadingAuthorizationMethodForm = true
|
||||
const success = await this.saveOPOptions()
|
||||
if (success) {
|
||||
this.authorizationMethod.currentAuthorizationMethodSelected = this.authorizationMethod.authorizationMethodSet
|
||||
this.formMode.authorizationMethod = F_MODES.VIEW
|
||||
this.isFormCompleted.authorizationMethod = true
|
||||
if (this.authorizationMethod.authorizationMethodSet === this.authMethods.OIDC && !this.isFormCompleted.authorizationSetting) {
|
||||
this.formMode.authorizationSetting = F_MODES.EDIT
|
||||
} else {
|
||||
if (!this.isFormCompleted.opOauth) {
|
||||
this.formMode.opOauth = F_MODES.EDIT
|
||||
}
|
||||
}
|
||||
}
|
||||
this.loadingAuthorizationMethodForm = false
|
||||
},
|
||||
async saveOIDCAuthSetting() {
|
||||
this.isFormStep = FORM.AUTHORIZATION_SETTING
|
||||
this.loadingAuthorizationMethodForm = true
|
||||
this.authorizationSetting.oidcProviderSet = this.getCurrentSelectedOIDCProvider
|
||||
this.authorizationSetting.targetedAudienceClientIdSet = this.state.authorization_settings.targeted_audience_client_id
|
||||
const success = await this.saveOPOptions()
|
||||
if (success) {
|
||||
this.formMode.authorizationSetting = F_MODES.VIEW
|
||||
this.isFormCompleted.authorizationSetting = true
|
||||
if (!this.isIntegrationCompleteWithOIDC && this.formMode.projectFolderSetUp !== F_MODES.EDIT && this.formMode.opUserAppPassword !== F_MODES.EDIT) {
|
||||
this.formMode.projectFolderSetUp = F_MODES.EDIT
|
||||
this.showDefaultManagedProjectFolders = true
|
||||
this.isProjectFolderSwitchEnabled = true
|
||||
this.textLabelProjectFolderSetupButton = this.buttonTextLabel.completeWithProjectFolderSetup
|
||||
}
|
||||
}
|
||||
this.loadingAuthorizationMethodForm = false
|
||||
},
|
||||
resetOPOAuthClientValues() {
|
||||
OC.dialogs.confirmDestructive(
|
||||
t('integration_openproject', 'If you proceed you will need to update these settings with the new OpenProject OAuth credentials. Also, all users will need to reauthorize access to their OpenProject account.'),
|
||||
|
@ -805,6 +1151,39 @@ export default {
|
|||
true,
|
||||
)
|
||||
},
|
||||
async selectAuthorizationMethod() {
|
||||
// open the confirmation dialog when only swithing back and forth between two authorization method
|
||||
if (this.isAuthorizationFormInEditMode && this.authorizationMethod.currentAuthorizationMethodSelected !== null) {
|
||||
await OC.dialogs.confirmDestructive(
|
||||
t('integration_openproject', `If you proceed this method, you will have an ${this.authorizationMethod.authorizationMethodSet.toUpperCase()} based authorization configuration which will delete all the configuration setting for current ${this.authorizationMethod.currentAuthorizationMethodSelected.toUpperCase()} based authorization. You can switch back to it anytime.`),
|
||||
t('integration_openproject', 'Switch Authorization Method'),
|
||||
{
|
||||
type: OC.dialogs.YES_NO_BUTTONS,
|
||||
confirm: t('integration_openproject', 'Yes, switch'),
|
||||
confirmClasses: 'error',
|
||||
cancel: t('integration_openproject', 'Cancel'),
|
||||
},
|
||||
async (result) => {
|
||||
if (result) {
|
||||
// here we switch either to oidc or oauth2 configuration
|
||||
const authMethod = this.authorizationMethod.authorizationMethodSet
|
||||
if (authMethod === AUTH_METHOD.OAUTH2) {
|
||||
this.state.authorization_settings.targeted_audience_client_id = null
|
||||
this.authorizationSetting.currentOIDCProviderSelected = null
|
||||
} else {
|
||||
this.state.openproject_client_id = ''
|
||||
this.state.openproject_client_secret = ''
|
||||
}
|
||||
await this.saveAuthorizationMethodValue()
|
||||
}
|
||||
window.location.reload()
|
||||
},
|
||||
true,
|
||||
)
|
||||
return ''
|
||||
}
|
||||
await this.saveAuthorizationMethodValue()
|
||||
},
|
||||
async clearOPOAuthClientValues() {
|
||||
this.isFormStep = FORM.OP_OAUTH
|
||||
this.formMode.opOauth = F_MODES.EDIT
|
||||
|
@ -829,13 +1208,14 @@ export default {
|
|||
},
|
||||
async (result) => {
|
||||
if (result) {
|
||||
await this.resetAllAppValues()
|
||||
const authMethod = this.authorizationMethod.authorizationMethodSet
|
||||
await this.resetAllAppValues(authMethod)
|
||||
}
|
||||
},
|
||||
true,
|
||||
)
|
||||
},
|
||||
async resetAllAppValues() {
|
||||
async resetAllAppValues(authMethod) {
|
||||
// to avoid general console errors, we need to set the form to
|
||||
// editor mode so that we can update the form fields with null values
|
||||
// also, form completeness should be set to false
|
||||
|
@ -843,12 +1223,18 @@ export default {
|
|||
this.isFormCompleted.opOauth = false
|
||||
this.formMode.server = F_MODES.EDIT
|
||||
this.isFormCompleted.server = false
|
||||
this.state.openproject_client_id = null
|
||||
this.state.openproject_client_secret = null
|
||||
this.state.default_enable_navigation = false
|
||||
this.state.openproject_instance_url = null
|
||||
this.state.default_enable_unified_search = false
|
||||
this.oPUserAppPassword = null
|
||||
this.authorizationMethod.authorizationMethodSet = null
|
||||
this.state.openproject_client_id = null
|
||||
this.state.openproject_client_secret = null
|
||||
this.state.openproject_instance_url = null
|
||||
// if the authorization method is "oidc"
|
||||
if (authMethod === AUTH_METHOD.OIDC) {
|
||||
this.state.authorization_settings.targeted_audience_client_id = null
|
||||
this.authorizationSetting.currentOIDCProviderSelected = null
|
||||
}
|
||||
await this.saveOPOptions()
|
||||
window.location.reload()
|
||||
},
|
||||
|
@ -952,21 +1338,46 @@ export default {
|
|||
default_enable_navigation: this.state.default_enable_navigation,
|
||||
default_enable_unified_search: this.state.default_enable_unified_search,
|
||||
}
|
||||
if (this.state.openproject_instance_url === null && this.state.openproject_client_secret === null && this.state.openproject_client_id === null) {
|
||||
// doing whole reset
|
||||
if (this.state.openproject_instance_url === null && this.authorizationMethod.authorizationMethodSet === null) {
|
||||
// by default, it will be an oauth2 reset
|
||||
values = {
|
||||
...values,
|
||||
authorization_method: this.authorizationMethod.authorizationMethodSet,
|
||||
setup_project_folder: false,
|
||||
setup_app_password: false,
|
||||
}
|
||||
if (this.authorizationMethod.currentAuthorizationMethodSelected === AUTH_METHOD.OIDC
|
||||
&& this.authorizationSetting.currentOIDCProviderSelected === null
|
||||
&& this.state.authorization_settings.targeted_audience_client_id === null) {
|
||||
// when reset is oidc
|
||||
values = {
|
||||
...values,
|
||||
oidc_provider: this.getCurrentSelectedOIDCProvider,
|
||||
targeted_audience_client_id: this.getCurrentSelectedTargetedClientId,
|
||||
}
|
||||
}
|
||||
} else if (this.isFormStep === FORM.AUTHORIZATION_SETTING) {
|
||||
values = {
|
||||
oidc_provider: this.getCurrentSelectedOIDCProvider,
|
||||
targeted_audience_client_id: this.getCurrentSelectedTargetedClientId,
|
||||
}
|
||||
} else if (this.isFormStep === FORM.AUTHORIZATION_METHOD) {
|
||||
values = {
|
||||
...values,
|
||||
authorization_method: this.authorizationMethod.authorizationMethodSet,
|
||||
oidc_provider: this.isIntegrationCompleteWithOIDC ? this.getCurrentSelectedOIDCProvider : null,
|
||||
targeted_audience_client_id: this.isIntegrationCompleteWithOIDC ? this.getCurrentSelectedTargetedClientId : null,
|
||||
}
|
||||
} else if (this.isFormStep === FORM.GROUP_FOLDER) {
|
||||
if (!this.isProjectFolderSwitchEnabled) {
|
||||
values = {
|
||||
authorization_method: this.authorizationMethod.authorizationMethodSet,
|
||||
setup_project_folder: false,
|
||||
setup_app_password: false,
|
||||
}
|
||||
} else if (this.isProjectFolderSwitchEnabled === true) {
|
||||
values = {
|
||||
authorization_method: this.authorizationMethod.authorizationMethodSet,
|
||||
setup_project_folder: !this.isProjectFolderAlreadySetup,
|
||||
setup_app_password: this.opUserAppPassword !== true,
|
||||
}
|
||||
|
@ -1136,6 +1547,9 @@ export default {
|
|||
)
|
||||
})
|
||||
},
|
||||
onSelectOIDCProvider(selectedOption) {
|
||||
this.authorizationSetting.currentOIDCProviderSelected = selectedOption
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -1218,11 +1632,45 @@ export default {
|
|||
}
|
||||
.note-card {
|
||||
max-width: 900px;
|
||||
&--info-description, &--error-description, &--warning-description {
|
||||
.link {
|
||||
color: #1a67a3 !important;
|
||||
font-style: normal;
|
||||
}
|
||||
.link {
|
||||
color: #1a67a3 !important;
|
||||
font-style: normal;
|
||||
}
|
||||
.authorization-method {
|
||||
&--description {
|
||||
font-size: 14px;
|
||||
.title {
|
||||
font-weight: 700;
|
||||
}
|
||||
.description {
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
}
|
||||
&--options {
|
||||
margin-top: 1rem;
|
||||
.radio-check {
|
||||
font-weight: 500;
|
||||
}
|
||||
.oidc-app-check-description {
|
||||
margin-left: 2.4rem;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.authorization-settings {
|
||||
&--content {
|
||||
max-width: 550px;
|
||||
&--label {
|
||||
font-weight: 700;
|
||||
font-size: .875rem;
|
||||
}
|
||||
&--client {
|
||||
margin-top: 0.7rem;
|
||||
}
|
||||
}
|
||||
.description {
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
<template>
|
||||
<div class="openproject-prefs section">
|
||||
<SettingsTitle is-setting="personal" />
|
||||
<div v-if="isNonOidcUserConnectedViaOidc" class="demo-error-oidc">
|
||||
{{ t('integration_openproject', 'This feature is not available for this user account :)') }}
|
||||
</div>
|
||||
<div v-if="connected" class="openproject-prefs--connected">
|
||||
<label>
|
||||
<CheckIcon :size="20" />
|
||||
{{ t('integration_openproject', 'Connected as {user}', { user: state.user_name }) }}
|
||||
</label>
|
||||
<NcButton class="openproject-prefs--disconnect" @click="disconnectFromOP()">
|
||||
<NcButton v-if="state.authorization_method === authMethods.OAUTH2" class="openproject-prefs--disconnect" @click="disconnectFromOP()">
|
||||
<template #icon>
|
||||
<CloseIcon :size="23" />
|
||||
</template>
|
||||
|
@ -35,7 +38,7 @@
|
|||
</template>
|
||||
</CheckBox>
|
||||
</div>
|
||||
<OAuthConnectButton v-else :is-admin-config-ok="state.admin_config_ok" />
|
||||
<OAuthConnectButton v-if="showOAuthConnectButton" :is-admin-config-ok="state.admin_config_ok" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -51,7 +54,7 @@ import SettingsTitle from '../components/settings/SettingsTitle.vue'
|
|||
import OAuthConnectButton from './OAuthConnectButton.vue'
|
||||
import CheckBox from './settings/CheckBox.vue'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { checkOauthConnectionResult, USER_SETTINGS } from '../utils.js'
|
||||
import { checkOauthConnectionResult, USER_SETTINGS, AUTH_METHOD } from '../utils.js'
|
||||
import { NcButton } from '@nextcloud/vue'
|
||||
|
||||
export default {
|
||||
|
@ -68,6 +71,7 @@ export default {
|
|||
oauthConnectionErrorMessage: loadState('integration_openproject', 'oauth-connection-error-message'),
|
||||
oauthConnectionResult: loadState('integration_openproject', 'oauth-connection-result'),
|
||||
userSettingDescription: USER_SETTINGS,
|
||||
authMethods: AUTH_METHOD,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -76,6 +80,15 @@ export default {
|
|||
return this.state.token && this.state.token !== ''
|
||||
&& this.state.user_name && this.state.user_name !== ''
|
||||
},
|
||||
isNonOidcUserConnectedViaOidc() {
|
||||
return !!(this.state.authorization_method === AUTH_METHOD.OIDC && this.state.admin_config_ok && !this.state.token)
|
||||
},
|
||||
showOAuthConnectButton() {
|
||||
if (this.connected) {
|
||||
return false
|
||||
}
|
||||
return !(this.state.admin_config_ok === true && this.state.authorization_method === this.authMethods.OIDC)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'state.search_enabled'(newVal) {
|
||||
|
@ -91,7 +104,9 @@ export default {
|
|||
},
|
||||
|
||||
mounted() {
|
||||
checkOauthConnectionResult(this.oauthConnectionResult, this.oauthConnectionErrorMessage)
|
||||
if (this.state.authorization_method === this.authMethods.OAUTH2) {
|
||||
checkOauthConnectionResult(this.oauthConnectionResult, this.oauthConnectionErrorMessage)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -164,5 +179,9 @@ export default {
|
|||
text-align: left;
|
||||
padding: 0;
|
||||
}
|
||||
.demo-error-oidc {
|
||||
color: red;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<div v-if="showConnectButton" class="empty-content--connect-button">
|
||||
<OAuthConnectButton :is-admin-config-ok="isAdminConfigOk" :file-info="fileInfo" />
|
||||
<OAuthConnectButton
|
||||
:is-admin-config-ok="isAdminConfigOk"
|
||||
:file-info="fileInfo" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,7 +33,7 @@ import OpenProjectIcon from '../icons/OpenProjectIcon.vue'
|
|||
import { generateUrl } from '@nextcloud/router'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import OAuthConnectButton from '../OAuthConnectButton.vue'
|
||||
import { STATE } from '../../utils.js'
|
||||
import { AUTH_METHOD, STATE } from '../../utils.js'
|
||||
|
||||
export default {
|
||||
name: 'EmptyContent',
|
||||
|
@ -42,6 +44,10 @@ export default {
|
|||
required: true,
|
||||
default: STATE.OK,
|
||||
},
|
||||
authMethod: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isAdminConfigOk: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
|
@ -79,7 +85,11 @@ export default {
|
|||
return this.state === STATE.OK
|
||||
},
|
||||
showConnectButton() {
|
||||
return [STATE.NO_TOKEN, STATE.ERROR].includes(this.state)
|
||||
// show button when admin config is not configured (either oidc or be oauth2)
|
||||
if (!this.isAdminConfigOk) {
|
||||
return true
|
||||
}
|
||||
return this.authMethod === AUTH_METHOD.OAUTH2 && [STATE.NO_TOKEN, STATE.ERROR].includes(this.state)
|
||||
},
|
||||
emptyContentTitleMessage() {
|
||||
if (this.state === STATE.NO_TOKEN) {
|
||||
|
|
20
src/utils.js
20
src/utils.js
|
@ -43,10 +43,12 @@ export const F_MODES = {
|
|||
|
||||
export const FORM = {
|
||||
SERVER: 0,
|
||||
OP_OAUTH: 1,
|
||||
NC_OAUTH: 2,
|
||||
GROUP_FOLDER: 3,
|
||||
APP_PASSWORD: 4,
|
||||
AUTHORIZATION_METHOD: 1,
|
||||
AUTHORIZATION_SETTING: 2,
|
||||
OP_OAUTH: 3,
|
||||
NC_OAUTH: 4,
|
||||
GROUP_FOLDER: 5,
|
||||
APP_PASSWORD: 6,
|
||||
}
|
||||
|
||||
export const WORKPACKAGES_SEARCH_ORIGIN = {
|
||||
|
@ -63,3 +65,13 @@ export const NO_OPTION_TEXT_STATE = {
|
|||
SEARCHING: 1,
|
||||
RESULT: 2,
|
||||
}
|
||||
|
||||
export const AUTH_METHOD = {
|
||||
OAUTH2: 'oauth2',
|
||||
OIDC: 'oidc',
|
||||
}
|
||||
|
||||
export const AUTH_METHOD_LABEL = {
|
||||
OAUTH2: t('integration_openproject', 'OAuth2 two-way authorization code flow'),
|
||||
OIDC: t('integration_openproject', 'OpenID identity provider'),
|
||||
}
|
||||
|
|
|
@ -6,11 +6,17 @@
|
|||
:loading="isLoading"
|
||||
@markAsRead="onMarkAsRead">
|
||||
<template #empty-content>
|
||||
<EmptyContent v-if="emptyContentMessage"
|
||||
id="openproject-empty-content"
|
||||
:state="state"
|
||||
:dashboard="true"
|
||||
:is-admin-config-ok="isAdminConfigOk" />
|
||||
<div v-if="isNonOidcUserConnectedViaOidc" class="demo-error-oidc">
|
||||
{{ t('integration_openproject', 'This feature is not available for this user account :)') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<EmptyContent v-if="emptyContentMessage"
|
||||
id="openproject-empty-content"
|
||||
:state="state"
|
||||
:auth-method="authMethod"
|
||||
:dashboard="true"
|
||||
:is-admin-config-ok="isAdminConfigOk" />
|
||||
</div>
|
||||
</template>
|
||||
</NcDashboardWidget>
|
||||
</template>
|
||||
|
@ -21,7 +27,7 @@ import { generateUrl } from '@nextcloud/router'
|
|||
import { NcDashboardWidget } from '@nextcloud/vue'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { checkOauthConnectionResult, STATE } from '../utils.js'
|
||||
import { AUTH_METHOD, checkOauthConnectionResult, STATE } from '../utils.js'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import EmptyContent from '../components/tab/EmptyContent.vue'
|
||||
|
||||
|
@ -46,7 +52,9 @@ export default {
|
|||
state: STATE.LOADING,
|
||||
oauthConnectionErrorMessage: loadState('integration_openproject', 'oauth-connection-error-message'),
|
||||
oauthConnectionResult: loadState('integration_openproject', 'oauth-connection-result'),
|
||||
isAdminConfigOk: loadState('integration_openproject', 'admin-config-status'),
|
||||
isAdminConfigOk: loadState('integration_openproject', 'admin_config_ok'),
|
||||
userHasOidcToken: loadState('integration_openproject', 'user-has-oidc-token'),
|
||||
authMethod: loadState('integration_openproject', 'authorization_method'),
|
||||
settingsUrl: generateUrl('/settings/user/openproject'),
|
||||
themingColor: OCA.Theming ? OCA.Theming.color.replace('#', '') : '0082C9',
|
||||
windowVisibility: true,
|
||||
|
@ -56,6 +64,7 @@ export default {
|
|||
icon: 'icon-checkmark',
|
||||
},
|
||||
},
|
||||
authMethods: AUTH_METHOD,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -68,6 +77,9 @@ export default {
|
|||
showMoreUrl() {
|
||||
return this.openprojectUrl + '/notifications'
|
||||
},
|
||||
isNonOidcUserConnectedViaOidc() {
|
||||
return !!(this.authMethod === AUTH_METHOD.OIDC && this.isAdminConfigOk && !this.userHasOidcToken)
|
||||
},
|
||||
items() {
|
||||
const notifications = []
|
||||
for (const key in this.notifications) {
|
||||
|
@ -85,6 +97,10 @@ export default {
|
|||
return notifications
|
||||
},
|
||||
emptyContentMessage() {
|
||||
// for oidc connection currently we do not show any error to user
|
||||
if (this.isNonOidcUserConnectedViaOidc === true) {
|
||||
return
|
||||
}
|
||||
if (this.state === STATE.NO_TOKEN) {
|
||||
return t('integration_openproject', 'No connection with OpenProject')
|
||||
} else if (this.state === STATE.CONNECTION_ERROR) {
|
||||
|
@ -108,7 +124,9 @@ export default {
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
checkOauthConnectionResult(this.oauthConnectionResult, this.oauthConnectionErrorMessage)
|
||||
if (this.authMethod === this.authMethods.OAUTH2) {
|
||||
checkOauthConnectionResult(this.oauthConnectionResult, this.oauthConnectionErrorMessage)
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
|
@ -279,4 +297,9 @@ export default {
|
|||
:deep(.connect-button) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.demo-error-oidc {
|
||||
color: red;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
<EmptyContent
|
||||
id="openproject-empty-content"
|
||||
:state="state"
|
||||
:auth-method="authMethod"
|
||||
:is-multiple-workpackage-linking="true"
|
||||
:is-admin-config-ok="isAdminConfigOk" />
|
||||
</div>
|
||||
|
@ -105,7 +106,8 @@ export default {
|
|||
state: STATE.LOADING,
|
||||
fileInfos: [],
|
||||
alreadyLinkedWorkPackage: [],
|
||||
isAdminConfigOk: loadState('integration_openproject', 'admin-config-status'),
|
||||
isAdminConfigOk: loadState('integration_openproject', 'admin_config_ok'),
|
||||
authMethod: loadState('integration_openproject', 'authorization_method'),
|
||||
oauthConnectionErrorMessage: loadState('integration_openproject', 'oauth-connection-error-message'),
|
||||
oauthConnectionResult: loadState('integration_openproject', 'oauth-connection-result'),
|
||||
searchOrigin: WORKPACKAGES_SEARCH_ORIGIN.LINK_MULTIPLE_FILES_MODAL,
|
||||
|
@ -148,7 +150,9 @@ export default {
|
|||
},
|
||||
|
||||
mounted() {
|
||||
checkOauthConnectionResult(this.oauthConnectionResult, this.oauthConnectionErrorMessage)
|
||||
if (this.authMethod === 'oauth2') {
|
||||
checkOauthConnectionResult(this.oauthConnectionResult, this.oauthConnectionErrorMessage)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async relinkRemainingFilesToWorkPackage() {
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
id="openproject-empty-content"
|
||||
:state="state"
|
||||
:file-info="fileInfo"
|
||||
:auth-method="authMethod"
|
||||
:is-admin-config-ok="isAdminConfigOk" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -76,7 +77,7 @@ import { showSuccess, showError } from '@nextcloud/dialogs'
|
|||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { workpackageHelper } from '../utils/workpackageHelper.js'
|
||||
import { STATE, WORKPACKAGES_SEARCH_ORIGIN, checkOauthConnectionResult } from '../utils.js'
|
||||
import { STATE, WORKPACKAGES_SEARCH_ORIGIN, AUTH_METHOD, checkOauthConnectionResult } from '../utils.js'
|
||||
|
||||
export default {
|
||||
name: 'ProjectsTab',
|
||||
|
@ -96,7 +97,8 @@ export default {
|
|||
workpackages: [],
|
||||
oauthConnectionErrorMessage: loadState('integration_openproject', 'oauth-connection-error-message'),
|
||||
oauthConnectionResult: loadState('integration_openproject', 'oauth-connection-result'),
|
||||
isAdminConfigOk: loadState('integration_openproject', 'admin-config-status'),
|
||||
isAdminConfigOk: loadState('integration_openproject', 'admin_config_ok'),
|
||||
authMethod: loadState('integration_openproject', 'authorization_method'),
|
||||
color: null,
|
||||
openprojectUrl: loadState('integration_openproject', 'openproject-url'),
|
||||
searchOrigin: WORKPACKAGES_SEARCH_ORIGIN.PROJECT_TAB,
|
||||
|
@ -119,7 +121,9 @@ export default {
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
checkOauthConnectionResult(this.oauthConnectionResult, this.oauthConnectionErrorMessage)
|
||||
if (this.authMethod === AUTH_METHOD.OAUTH2) {
|
||||
checkOauthConnectionResult(this.oauthConnectionResult, this.oauthConnectionErrorMessage)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
ref="linkPicker"
|
||||
:is-smart-picker="true"
|
||||
:file-info="fileInfo"
|
||||
:is-disabled="isLoading || !isAdminConfigOk || !isStateOk"
|
||||
:is-disabled="isLoading || !enableSearchInput || !isStateOk"
|
||||
:linked-work-packages="linkedWorkPackages"
|
||||
@submit="onSubmit" />
|
||||
<div id="openproject-empty-content">
|
||||
|
@ -36,9 +36,10 @@
|
|||
<EmptyContent
|
||||
v-else
|
||||
:state="state"
|
||||
:auth-method="adminConfigState.authMethod"
|
||||
:file-info="fileInfo"
|
||||
:is-smart-picker="true"
|
||||
:is-admin-config-ok="isAdminConfigOk" />
|
||||
:is-admin-config-ok="adminConfigState.isAdminConfigOk" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -46,7 +47,7 @@
|
|||
<script>
|
||||
import SearchInput from '../components/tab/SearchInput.vue'
|
||||
import EmptyContent from '../components/tab/EmptyContent.vue'
|
||||
import { STATE } from '../utils.js'
|
||||
import { AUTH_METHOD, STATE } from '../utils.js'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
@ -76,7 +77,7 @@ export default {
|
|||
fileInfo: {},
|
||||
linkedWorkPackages: [],
|
||||
state: STATE.LOADING,
|
||||
isAdminConfigOk: loadState('integration_openproject', 'admin-config-status'),
|
||||
adminConfigState: loadState('integration_openproject', 'admin-config'),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -86,6 +87,15 @@ export default {
|
|||
isLoading() {
|
||||
return this.state === STATE.LOADING
|
||||
},
|
||||
enableSearchInput() {
|
||||
if (this.adminConfigState.authMethod === AUTH_METHOD.OAUTH2 && this.adminConfigState.isAdminConfigOk) {
|
||||
return true
|
||||
}
|
||||
if (this.adminConfigState.authMethod === AUTH_METHOD.OIDC && this.adminConfigState.isAdminConfigOk) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.checkIfOpenProjectIsAvailable()
|
||||
|
@ -95,7 +105,7 @@ export default {
|
|||
this.$emit('submit', data)
|
||||
},
|
||||
async checkIfOpenProjectIsAvailable() {
|
||||
if (!this.isAdminConfigOk) {
|
||||
if (!this.adminConfigState.isAdminConfigOk) {
|
||||
this.state = STATE.ERROR
|
||||
return
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -4,6 +4,7 @@ import { shallowMount, createLocalVue, mount } from '@vue/test-utils'
|
|||
import PersonalSettings from '../../../src/components/PersonalSettings.vue'
|
||||
import * as dialogs from '@nextcloud/dialogs'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { AUTH_METHOD } from '../../../src/utils.js'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
|
@ -90,7 +91,7 @@ describe('PersonalSettings.vue', () => {
|
|||
describe('when username and token are given', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
state: { user_name: 'test', token: '123', admin_config_ok: true },
|
||||
state: { user_name: 'test', token: '123', admin_config_ok: true, authorization_method: AUTH_METHOD.OAUTH2 },
|
||||
})
|
||||
})
|
||||
it('oAuth connect button is not displayed', () => {
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AdminSettings.vue Authorization Method view mode form complete should show field values and hide the form completed with oauth2 auth method 1`] = `
|
||||
Wrapper {
|
||||
"selector": ".authorization-method",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`AdminSettings.vue Authorization Method view mode form complete should show field values and hide the form completed with oidc auth method 1`] = `
|
||||
Wrapper {
|
||||
"selector": ".authorization-method",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`AdminSettings.vue Authorization settings view mode form complete should show field values and hide authorization settings form 1`] = `
|
||||
Wrapper {
|
||||
"selector": ".authorization-settings",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`AdminSettings.vue Nextcloud OAuth values form edit mode should show the form and hide the field values 1`] = `
|
||||
Wrapper {
|
||||
"selector": ".nextcloud-oauth-values",
|
||||
|
@ -31,7 +49,7 @@ Wrapper {
|
|||
`;
|
||||
|
||||
exports[`AdminSettings.vue default user configurations form should be visible when the integration is complete 1`] = `
|
||||
Wrapper {
|
||||
ErrorWrapper {
|
||||
"selector": ".default-prefs",
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* jshint esversion: 8 */
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils'
|
||||
import EmptyContent from '../../../../src/components/tab/EmptyContent.vue'
|
||||
import { STATE } from '../../../../src/utils.js'
|
||||
import { AUTH_METHOD, STATE } from '../../../../src/utils.js'
|
||||
const localVue = createLocalVue()
|
||||
|
||||
describe('EmptyContent.vue', () => {
|
||||
|
@ -55,6 +55,7 @@ function getWrapper(propsData = {}) {
|
|||
propsData: {
|
||||
state: 'ok',
|
||||
isAdminConfigOk: true,
|
||||
authMethod: AUTH_METHOD.OAUTH2,
|
||||
...propsData,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -473,10 +473,11 @@ class ConfigControllerTest extends TestCase {
|
|||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function setAdminConfigStatusDataProvider() {
|
||||
public function setAdminConfigStatusDataProviderForOauth2() {
|
||||
return [
|
||||
[
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => '$client_id',
|
||||
'openproject_client_secret' => '$client_secret',
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
|
@ -485,10 +486,12 @@ class ConfigControllerTest extends TestCase {
|
|||
],
|
||||
[
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => '',
|
||||
'openproject_client_secret' => '$client_secret',
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
], false
|
||||
],
|
||||
false
|
||||
],
|
||||
];
|
||||
}
|
||||
|
@ -498,16 +501,17 @@ class ConfigControllerTest extends TestCase {
|
|||
* @param bool $adminConfigStatus
|
||||
*
|
||||
* @return void
|
||||
* @dataProvider setAdminConfigStatusDataProvider
|
||||
* @dataProvider setAdminConfigStatusDataProviderForOauth2
|
||||
*/
|
||||
public function testSetAdminConfigForDifferentAdminConfigStatus($credsToUpdate, $adminConfigStatus) {
|
||||
public function testSetAdminConfigForDifferentAdminConfigStatusForOauth2($credsToUpdate, $adminConfigStatus) {
|
||||
$userManager = \OC::$server->getUserManager();
|
||||
|
||||
$configMock = $this->getMockBuilder(IConfig::class)->getMock();
|
||||
$configMock
|
||||
->expects($this->exactly(3))
|
||||
->expects($this->exactly(4))
|
||||
->method('setAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'authorization_method', $credsToUpdate['authorization_method']],
|
||||
['integration_openproject', 'openproject_client_id', $credsToUpdate['openproject_client_id']],
|
||||
['integration_openproject', 'openproject_client_secret', $credsToUpdate['openproject_client_secret']],
|
||||
['integration_openproject', 'openproject_instance_url', $credsToUpdate['openproject_instance_url']]
|
||||
|
@ -516,20 +520,24 @@ class ConfigControllerTest extends TestCase {
|
|||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'openproject_instance_url', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'nc_oauth_client_id', ''],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''],
|
||||
['integration_openproject', 'authorization_method'],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'openproject_instance_url']
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
'http://localhost:3000',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'',
|
||||
'',
|
||||
'123',
|
||||
'',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
$credsToUpdate['openproject_client_id'],
|
||||
$credsToUpdate['openproject_client_secret'],
|
||||
$credsToUpdate['openproject_instance_url']
|
||||
|
@ -566,6 +574,129 @@ class ConfigControllerTest extends TestCase {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function setAdminConfigStatusDataProviderForOIDC() {
|
||||
return [
|
||||
[
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => 'test-oidc-provider',
|
||||
'targeted_audience_client_id' => 'test-client',
|
||||
'openproject_instance_url' => 'http://openproject.com'
|
||||
],
|
||||
true
|
||||
],
|
||||
[
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => '',
|
||||
'targeted_audience_client_id' => 'test-client',
|
||||
'openproject_instance_url' => 'http://openproject.com'
|
||||
],
|
||||
false
|
||||
],
|
||||
[
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => '',
|
||||
'targeted_audience_client_id' => '',
|
||||
'openproject_instance_url' => 'http://openproject.com'
|
||||
],
|
||||
false
|
||||
],
|
||||
[
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => 'test-oidc-provider',
|
||||
'targeted_audience_client_id' => '',
|
||||
'openproject_instance_url' => 'http://openproject.com'
|
||||
],
|
||||
false
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $credsToUpdate
|
||||
* @param bool $adminConfigStatus
|
||||
*
|
||||
* @return void
|
||||
* @dataProvider setAdminConfigStatusDataProviderForOIDC
|
||||
*/
|
||||
public function testSetAdminConfigForDifferentAdminConfigStatusForOIDC($credsToUpdate, $adminConfigStatus) {
|
||||
$userManager = \OC::$server->getUserManager();
|
||||
|
||||
$configMock = $this->getMockBuilder(IConfig::class)->getMock();
|
||||
$configMock
|
||||
->expects($this->exactly(4))
|
||||
->method('setAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'authorization_method', $credsToUpdate['authorization_method']],
|
||||
['integration_openproject', 'oidc_provider', $credsToUpdate['oidc_provider']],
|
||||
['integration_openproject', 'targeted_audience_client_id', $credsToUpdate['targeted_audience_client_id']],
|
||||
['integration_openproject', 'openproject_instance_url', $credsToUpdate['openproject_instance_url']]
|
||||
);
|
||||
$configMock
|
||||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'openproject_instance_url', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'oidc_provider'],
|
||||
['integration_openproject', 'targeted_audience_client_id'],
|
||||
['integration_openproject', 'nc_oauth_client_id', ''],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''],
|
||||
['integration_openproject', 'authorization_method'],
|
||||
['integration_openproject', 'oidc_provider'],
|
||||
['integration_openproject', 'targeted_audience_client_id'],
|
||||
['integration_openproject', 'openproject_instance_url']
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
'http://localhost:3000',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'',
|
||||
'',
|
||||
'123',
|
||||
'',
|
||||
OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
$credsToUpdate['oidc_provider'],
|
||||
$credsToUpdate['targeted_audience_client_id'],
|
||||
$credsToUpdate['openproject_instance_url']
|
||||
|
||||
);
|
||||
$apiService = $this->getMockBuilder(OpenProjectAPIService::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$configController = new ConfigController(
|
||||
'integration_openproject',
|
||||
$this->createMock(IRequest::class),
|
||||
$configMock,
|
||||
$this->createMock(IURLGenerator::class),
|
||||
$userManager,
|
||||
$this->l,
|
||||
$apiService,
|
||||
$this->createMock(LoggerInterface::class),
|
||||
$this->createMock(OauthService::class),
|
||||
$this->createMock(SettingsController::class),
|
||||
$this->createMock(IGroupManager::class),
|
||||
$this->createMock(ISecureRandom::class),
|
||||
$this->createMock(ISubAdmin::class),
|
||||
'test101'
|
||||
);
|
||||
|
||||
$result = $configController->setAdminConfig($credsToUpdate);
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
'status' => $adminConfigStatus,
|
||||
'oPOAuthTokenRevokeStatus' => '',
|
||||
"oPUserAppPassword" => null
|
||||
],
|
||||
$result->getData()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
|
@ -574,11 +705,13 @@ class ConfigControllerTest extends TestCase {
|
|||
return [
|
||||
[ // everything changes so delete user values and change the oAuth Client
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'old-openproject_client_id',
|
||||
'openproject_client_secret' => 'old-openproject_client_secret',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'openproject_client_id',
|
||||
'openproject_client_secret' => 'openproject_client_secret',
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
|
@ -588,11 +721,13 @@ class ConfigControllerTest extends TestCase {
|
|||
],
|
||||
[ // only client id changes so delete user values but don't change the oAuth Client
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'old-openproject_client_id',
|
||||
'openproject_client_secret' => 'openproject_client_secret',
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'openproject_client_id',
|
||||
'openproject_client_secret' => 'openproject_client_secret',
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
|
@ -602,11 +737,13 @@ class ConfigControllerTest extends TestCase {
|
|||
],
|
||||
[ // only client secret changes so delete user values but don't change the oAuth Client
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'openproject_client_id',
|
||||
'openproject_client_secret' => 'old-openproject_client_secret',
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'openproject_client_id',
|
||||
'openproject_client_secret' => 'openproject_client_secret',
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
|
@ -616,11 +753,13 @@ class ConfigControllerTest extends TestCase {
|
|||
],
|
||||
[ //only the openproject_instance_url changes so don't delete the user values but change the oAuth Client
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'openproject_client_id',
|
||||
'openproject_client_secret' => 'openproject_client_secret',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'openproject_client_id',
|
||||
'openproject_client_secret' => 'openproject_client_secret',
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
|
@ -630,11 +769,13 @@ class ConfigControllerTest extends TestCase {
|
|||
],
|
||||
[ //everything cleared
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'openproject_client_id',
|
||||
'openproject_client_secret' => 'openproject_client_secret',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => null,
|
||||
'openproject_client_id' => null,
|
||||
'openproject_client_secret' => null,
|
||||
'openproject_instance_url' => null,
|
||||
|
@ -644,11 +785,13 @@ class ConfigControllerTest extends TestCase {
|
|||
],
|
||||
[ //everything cleared with empty strings
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'openproject_client_id',
|
||||
'openproject_client_secret' => 'openproject_client_secret',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => '',
|
||||
'openproject_client_id' => '',
|
||||
'openproject_client_secret' => '',
|
||||
'openproject_instance_url' => '',
|
||||
|
@ -684,20 +827,24 @@ class ConfigControllerTest extends TestCase {
|
|||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'openproject_instance_url', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'nc_oauth_client_id', ''],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'openproject_instance_url']
|
||||
['integration_openproject', 'openproject_instance_url'],
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$oldCreds['openproject_instance_url'],
|
||||
$oldCreds['authorization_method'],
|
||||
$oldCreds['openproject_client_id'],
|
||||
$oldCreds['openproject_client_secret'],
|
||||
'123',
|
||||
'',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
$credsToUpdate['openproject_client_id'],
|
||||
$credsToUpdate['openproject_client_secret'],
|
||||
$credsToUpdate['openproject_instance_url']
|
||||
|
@ -724,18 +871,22 @@ class ConfigControllerTest extends TestCase {
|
|||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'openproject_instance_url', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'openproject_instance_url']
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$oldCreds['openproject_instance_url'],
|
||||
$oldCreds['authorization_method'],
|
||||
$oldCreds['openproject_client_id'],
|
||||
$oldCreds['openproject_client_secret'],
|
||||
'',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
$credsToUpdate['openproject_client_id'],
|
||||
$credsToUpdate['openproject_client_secret'],
|
||||
$credsToUpdate['openproject_instance_url']
|
||||
|
@ -855,6 +1006,7 @@ class ConfigControllerTest extends TestCase {
|
|||
return [
|
||||
[
|
||||
[
|
||||
'authorization_method' => null,
|
||||
'openproject_client_id' => null,
|
||||
'openproject_client_secret' => null,
|
||||
'openproject_instance_url' => null,
|
||||
|
@ -866,6 +1018,7 @@ class ConfigControllerTest extends TestCase {
|
|||
],
|
||||
[
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'client_id_changed',
|
||||
'openproject_client_secret' => 'client_secret_changed',
|
||||
'openproject_instance_url' => 'http://localhost:3000',
|
||||
|
@ -890,6 +1043,7 @@ class ConfigControllerTest extends TestCase {
|
|||
*/
|
||||
public function testSetAdminConfigForOPOAuthTokenRevoke($newConfig, $adminConfigStatus, $mode) {
|
||||
$oldAdminConfig = [
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'some_old_client_id',
|
||||
'openproject_client_secret' => 'some_old_client_secret',
|
||||
'openproject_instance_url' => 'http://localhost:3000',
|
||||
|
@ -917,24 +1071,28 @@ class ConfigControllerTest extends TestCase {
|
|||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'openproject_instance_url', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id', ''],
|
||||
['integration_openproject', 'openproject_client_secret', ''],
|
||||
['integration_openproject', 'nc_oauth_client_id', ''],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''], // for user
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''], // for user
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''], // for the last check
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'openproject_instance_url'],
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$oldAdminConfig['openproject_instance_url'],
|
||||
$oldAdminConfig['authorization_method'],
|
||||
$oldAdminConfig['openproject_client_id'],
|
||||
$oldAdminConfig['openproject_client_secret'],
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
$newConfig['openproject_client_id'],
|
||||
$newConfig['openproject_client_secret'],
|
||||
$newConfig['openproject_instance_url'],
|
||||
|
@ -944,22 +1102,26 @@ class ConfigControllerTest extends TestCase {
|
|||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'openproject_instance_url', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id', ''],
|
||||
['integration_openproject', 'openproject_client_secret', ''],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'openproject_instance_url'],
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$oldAdminConfig['openproject_instance_url'],
|
||||
$oldAdminConfig['authorization_method'],
|
||||
$oldAdminConfig['openproject_client_id'],
|
||||
$oldAdminConfig['openproject_client_secret'],
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
$newConfig['openproject_client_id'],
|
||||
$newConfig['openproject_client_secret'],
|
||||
$newConfig['openproject_instance_url'],
|
||||
|
@ -968,6 +1130,7 @@ class ConfigControllerTest extends TestCase {
|
|||
$configMock
|
||||
->method('setAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'authorization_method', $newConfig['authorization_method']],
|
||||
['integration_openproject', 'openproject_client_id', $newConfig['openproject_client_id']],
|
||||
['integration_openproject', 'openproject_client_secret', $newConfig['openproject_client_secret']],
|
||||
['integration_openproject', 'openproject_instance_url', $newConfig['openproject_instance_url']],
|
||||
|
@ -1069,11 +1232,13 @@ class ConfigControllerTest extends TestCase {
|
|||
*/
|
||||
public function testOPOAuthTokenRevokeErrors($errorCode, $exception, $errMessage) {
|
||||
$oldAdminConfig = [
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'some_old_client_id',
|
||||
'openproject_client_secret' => 'some_old_client_secret',
|
||||
'openproject_instance_url' => 'http://localhost:3000',
|
||||
];
|
||||
$newAdminConfig = [
|
||||
'authorization_method' => '',
|
||||
'openproject_client_id' => '',
|
||||
'openproject_client_secret' => '',
|
||||
'openproject_instance_url' => '',
|
||||
|
@ -1095,20 +1260,24 @@ class ConfigControllerTest extends TestCase {
|
|||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'openproject_instance_url', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id', ''],
|
||||
['integration_openproject', 'openproject_client_secret', ''],
|
||||
['integration_openproject', 'nc_oauth_client_id', ''],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''], // for the last check
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'openproject_instance_url'],
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$oldAdminConfig['openproject_instance_url'],
|
||||
$oldAdminConfig['authorization_method'],
|
||||
$oldAdminConfig['openproject_client_id'],
|
||||
$oldAdminConfig['openproject_client_secret'],
|
||||
'',
|
||||
$errorCode,
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
$newAdminConfig['openproject_client_id'],
|
||||
$newAdminConfig['openproject_client_secret'],
|
||||
$newAdminConfig['openproject_instance_url'],
|
||||
|
@ -1116,6 +1285,7 @@ class ConfigControllerTest extends TestCase {
|
|||
$configMock
|
||||
->method('setAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'authorization_method', $newAdminConfig['authorization_method']],
|
||||
['integration_openproject', 'openproject_client_id', $newAdminConfig['openproject_client_id']],
|
||||
['integration_openproject', 'openproject_client_secret', $newAdminConfig['openproject_client_secret']],
|
||||
['integration_openproject', 'openproject_instance_url', $newAdminConfig['openproject_instance_url']],
|
||||
|
@ -1199,6 +1369,7 @@ class ConfigControllerTest extends TestCase {
|
|||
*/
|
||||
public function testOPOAuthTokenRevokeDoesNotOccurIfNoOPOAuthClientHasChanged() {
|
||||
$oldAdminConfig = [
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'some_old_client_id',
|
||||
'openproject_client_secret' => 'some_old_client_secret',
|
||||
'openproject_instance_url' => 'http://localhost:3000',
|
||||
|
@ -1218,12 +1389,14 @@ class ConfigControllerTest extends TestCase {
|
|||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'openproject_instance_url', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id', ''],
|
||||
['integration_openproject', 'openproject_client_secret', ''],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', '']
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$oldAdminConfig['openproject_instance_url'],
|
||||
$oldAdminConfig['authorization_method'],
|
||||
$oldAdminConfig['openproject_client_id'],
|
||||
$oldAdminConfig['openproject_client_secret'],
|
||||
''
|
||||
|
@ -1285,18 +1458,18 @@ class ConfigControllerTest extends TestCase {
|
|||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'openproject_instance_url', ''],
|
||||
['integration_openproject', 'openproject_client_id', ''],
|
||||
['integration_openproject', 'openproject_client_secret', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'openproject_instance_url']
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
'http://localhost:3000',
|
||||
'some_cilent_id',
|
||||
'some_cilent_secret',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'some_cilent_id',
|
||||
'some_cilent_secret',
|
||||
'http://localhost:3000'
|
||||
|
@ -1351,6 +1524,7 @@ class ConfigControllerTest extends TestCase {
|
|||
);
|
||||
|
||||
$result = $configControllerMock->setAdminConfig([
|
||||
"authorization_method" => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
"setup_project_folder" => true,
|
||||
"setup_app_password" => true
|
||||
]);
|
||||
|
@ -1432,4 +1606,384 @@ class ConfigControllerTest extends TestCase {
|
|||
$data = $result->getData();
|
||||
$this->assertEquals("Database Error!", $data['error']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function setAdminConfigForOIDCAuthSettingProvider() {
|
||||
return [
|
||||
[ // set info if the authorization settings are changed
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => 'old-oidc_provider',
|
||||
'targeted_audience_client_id' => 'old-targeted_audience_client_id',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => 'oidc_provider',
|
||||
'targeted_audience_client_id' => 'targeted_audience_client_id',
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
],
|
||||
false,
|
||||
'change'
|
||||
],
|
||||
[ // set info even if only 'targeted_audience_client_id' authorization settings are changed
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => 'old-oidc_provider',
|
||||
'targeted_audience_client_id' => 'old-targeted_audience_client_id',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => 'old_oidc_provider',
|
||||
'targeted_audience_client_id' => 'new_targeted_audience_client_id',
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
],
|
||||
false,
|
||||
'change'
|
||||
],
|
||||
[ // setinfo even if only 'oidc_provider' authorization settings are changed
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => 'old-oidc_provider',
|
||||
'targeted_audience_client_id' => 'old-targeted_audience_client_id',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => 'new_oidc_provider',
|
||||
'targeted_audience_client_id' => 'old-targeted_audience_client_id',
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
]
|
||||
],
|
||||
[ // set if authorization settings are empty string
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => 'old-oidc_provider',
|
||||
'targeted_audience_client_id' => 'old-targeted_audience_client_id',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => '',
|
||||
'targeted_audience_client_id' => '',
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
]
|
||||
],
|
||||
[ // set if authorization settings are null
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => 'old-oidc_provider',
|
||||
'targeted_audience_client_id' => 'old-targeted_audience_client_id',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => null,
|
||||
'targeted_audience_client_id' => null,
|
||||
'openproject_instance_url' => 'http://openproject.com',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @group ignoreWithPHP8.0
|
||||
* @param array<string> $oldCreds
|
||||
* @param array<string> $credsToUpdate
|
||||
* @param bool $deleteUserValues
|
||||
* @param bool|string $updateNCOAuthClient false => don't touch the client, 'change' => update it, 'delete' => remove it
|
||||
* @return void
|
||||
* @dataProvider setAdminConfigForOIDCAuthSettingProvider
|
||||
*/
|
||||
public function testSetAdminConfigOIDCAuthSetting(
|
||||
$oldCreds, $credsToUpdate
|
||||
) {
|
||||
$userManager = $this->checkForUsersCountBeforeTest();
|
||||
$configMock = $this->getMockBuilder(IConfig::class)->getMock();
|
||||
$oauthServiceMock = $this->createMock(OauthService::class);
|
||||
$oauthSettingsControllerMock = $this->getMockBuilder(SettingsController::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$configMock
|
||||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'openproject_instance_url', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'oidc_provider'],
|
||||
['integration_openproject', 'targeted_audience_client_id'],
|
||||
['integration_openproject', 'nc_oauth_client_id', ''],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'oidc_provider'],
|
||||
['integration_openproject', 'targeted_audience_client_id'],
|
||||
['integration_openproject', 'openproject_instance_url'],
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$oldCreds['openproject_instance_url'],
|
||||
$oldCreds['authorization_method'],
|
||||
$oldCreds['oidc_provider'],
|
||||
$oldCreds['targeted_audience_client_id'],
|
||||
'123',
|
||||
'',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
$credsToUpdate['oidc_provider'],
|
||||
$credsToUpdate['targeted_audience_client_id'],
|
||||
$credsToUpdate['openproject_instance_url']
|
||||
);
|
||||
|
||||
$apiService = $this->getMockBuilder(OpenProjectAPIService::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$configController = new ConfigController(
|
||||
'integration_openproject',
|
||||
$this->createMock(IRequest::class),
|
||||
$configMock,
|
||||
$this->createMock(IURLGenerator::class),
|
||||
$userManager,
|
||||
$this->l,
|
||||
$apiService,
|
||||
$this->createMock(LoggerInterface::class),
|
||||
$oauthServiceMock,
|
||||
$oauthSettingsControllerMock,
|
||||
$this->createMock(IGroupManager::class),
|
||||
$this->createMock(ISecureRandom::class),
|
||||
$this->createMock(ISubAdmin::class),
|
||||
'test101'
|
||||
);
|
||||
$configController->setAdminConfig($credsToUpdate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function setAdminConfigForOAuth2AlreadyConfiguredDataProvider() {
|
||||
return [
|
||||
[ // when switching from oauth2 to oidc, userdata gets deleted along with the nc client information
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'old-openproject_client_id',
|
||||
'openproject_client_secret' => 'old-openproject_client_secret',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'openproject_client_id' => '',
|
||||
'openproject_client_secret' => '',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
]
|
||||
],
|
||||
[ // when resetting with OAUTH2 already configured, userdata gets deleted along with the nc client information
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'openproject_client_id' => 'old-openproject_client_id',
|
||||
'openproject_client_secret' => 'old-openproject_client_secret',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => '',
|
||||
'openproject_client_id' => '',
|
||||
'openproject_client_secret' => '',
|
||||
'openproject_instance_url' => '',
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @group ignoreWithPHP8.0
|
||||
* @param array<string> $oldCreds
|
||||
* @param array<string> $credsToUpdate
|
||||
* @return void
|
||||
* @dataProvider setAdminConfigForOAuth2AlreadyConfiguredDataProvider
|
||||
*/
|
||||
public function testSetAdminConfigForOAuth2AlreadyConfigured(
|
||||
$oldCreds, $credsToUpdate
|
||||
) {
|
||||
$userManager = $this->checkForUsersCountBeforeTest();
|
||||
$this->user1 = $userManager->createUser('test101', 'test101');
|
||||
|
||||
$configMock = $this->getMockBuilder(IConfig::class)->getMock();
|
||||
$oauthServiceMock = $this->createMock(OauthService::class);
|
||||
$oauthSettingsControllerMock = $this->getMockBuilder(SettingsController::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$configMock
|
||||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'openproject_instance_url', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'nc_oauth_client_id', ''],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'openproject_instance_url'],
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$oldCreds['openproject_instance_url'],
|
||||
$oldCreds['authorization_method'],
|
||||
$oldCreds['openproject_client_id'],
|
||||
$oldCreds['openproject_client_secret'],
|
||||
'123',
|
||||
'123',
|
||||
'',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
$credsToUpdate['openproject_client_id'],
|
||||
$credsToUpdate['openproject_client_secret'],
|
||||
$credsToUpdate['openproject_instance_url']
|
||||
);
|
||||
$oauthSettingsControllerMock
|
||||
->expects($this->once())
|
||||
->method('deleteClient')
|
||||
->with(123);
|
||||
$configMock
|
||||
->expects($this->exactly(10)) // 5 times for each user
|
||||
->method('deleteUserValue')
|
||||
->withConsecutive(
|
||||
['admin', 'integration_openproject', 'token'],
|
||||
['admin', 'integration_openproject', 'login'],
|
||||
['admin', 'integration_openproject', 'user_id'],
|
||||
['admin', 'integration_openproject', 'user_name'],
|
||||
['admin', 'integration_openproject', 'refresh_token'],
|
||||
[$this->user1->getUID(), 'integration_openproject', 'token'],
|
||||
[$this->user1->getUID(), 'integration_openproject', 'login'],
|
||||
[$this->user1->getUID(), 'integration_openproject', 'user_id'],
|
||||
[$this->user1->getUID(), 'integration_openproject', 'user_name'],
|
||||
[$this->user1->getUID(), 'integration_openproject', 'refresh_token'],
|
||||
);
|
||||
|
||||
$apiService = $this->getMockBuilder(OpenProjectAPIService::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$configController = new ConfigController(
|
||||
'integration_openproject',
|
||||
$this->createMock(IRequest::class),
|
||||
$configMock,
|
||||
$this->createMock(IURLGenerator::class),
|
||||
$userManager,
|
||||
$this->l,
|
||||
$apiService,
|
||||
$this->createMock(LoggerInterface::class),
|
||||
$oauthServiceMock,
|
||||
$oauthSettingsControllerMock,
|
||||
$this->createMock(IGroupManager::class),
|
||||
$this->createMock(ISecureRandom::class),
|
||||
$this->createMock(ISubAdmin::class),
|
||||
'test101'
|
||||
);
|
||||
$configController->setAdminConfig($credsToUpdate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function setAdminConfigForOIDCAlreadyConfigured() {
|
||||
return [
|
||||
[ // when switching from oidc to oauth2, just the user information get deleted
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => 'old-oidc_provider',
|
||||
'targeted_audience_client_id' => 'old-targeted_audience_client_id',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'oidc_provider' => '',
|
||||
'targeted_audience_client_id' => '',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
]
|
||||
],
|
||||
[ // when switching from oidc to oauth2, just the user information get deleted
|
||||
[
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OIDC,
|
||||
'oidc_provider' => 'old-oidc_provider',
|
||||
'targeted_audience_client_id' => 'old-targeted_audience_client_id',
|
||||
'openproject_instance_url' => 'http://old-openproject.com',
|
||||
],
|
||||
[
|
||||
'authorization_method' => '',
|
||||
'oidc_provider' => '',
|
||||
'targeted_audience_client_id' => '',
|
||||
'openproject_instance_url' => '',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @group ignoreWithPHP8.0
|
||||
* @param array<string> $oldCreds
|
||||
* @param array<string> $credsToUpdate
|
||||
* @return void
|
||||
* @dataProvider setAdminConfigForOIDCAlreadyConfigured
|
||||
*/
|
||||
public function testSetAdminConfigForOIDCAlreadyConfigured(
|
||||
$oldCreds, $credsToUpdate
|
||||
) {
|
||||
$userManager = $this->checkForUsersCountBeforeTest();
|
||||
$configMock = $this->getMockBuilder(IConfig::class)->getMock();
|
||||
$oauthServiceMock = $this->createMock(OauthService::class);
|
||||
$oauthSettingsControllerMock = $this->getMockBuilder(SettingsController::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$configMock
|
||||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'openproject_instance_url', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'oidc_provider'],
|
||||
['integration_openproject', 'targeted_audience_client_id'],
|
||||
['integration_openproject', 'oPOAuthTokenRevokeStatus', ''],
|
||||
['integration_openproject', 'authorization_method', ''],
|
||||
['integration_openproject', 'oidc_provider'],
|
||||
['integration_openproject', 'targeted_audience_client_id'],
|
||||
['integration_openproject', 'openproject_instance_url'],
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
$oldCreds['openproject_instance_url'],
|
||||
$oldCreds['authorization_method'],
|
||||
$oldCreds['oidc_provider'],
|
||||
$oldCreds['targeted_audience_client_id'],
|
||||
'',
|
||||
$credsToUpdate['authorization_method'],
|
||||
$credsToUpdate['oidc_provider'],
|
||||
$credsToUpdate['targeted_audience_client_id'],
|
||||
$credsToUpdate['openproject_instance_url']
|
||||
);
|
||||
$configMock
|
||||
->expects($this->exactly(2))
|
||||
->method('deleteUserValue')
|
||||
->withConsecutive(
|
||||
['test101', 'integration_openproject', 'user_id'],
|
||||
['test101', 'integration_openproject', 'user_name']
|
||||
);
|
||||
|
||||
$apiService = $this->getMockBuilder(OpenProjectAPIService::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$configController = new ConfigController(
|
||||
'integration_openproject',
|
||||
$this->createMock(IRequest::class),
|
||||
$configMock,
|
||||
$this->createMock(IURLGenerator::class),
|
||||
$userManager,
|
||||
$this->l,
|
||||
$apiService,
|
||||
$this->createMock(LoggerInterface::class),
|
||||
$oauthServiceMock,
|
||||
$oauthSettingsControllerMock,
|
||||
$this->createMock(IGroupManager::class),
|
||||
$this->createMock(ISecureRandom::class),
|
||||
$this->createMock(ISubAdmin::class),
|
||||
'test101'
|
||||
);
|
||||
$configController->setAdminConfig($credsToUpdate);
|
||||
}
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -10,6 +10,7 @@
|
|||
|
||||
namespace OCA\OpenProject\Settings;
|
||||
|
||||
use OCA\OpenProject\Service\OpenProjectAPIService;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\IConfig;
|
||||
|
@ -32,11 +33,17 @@ class PersonalTest extends TestCase {
|
|||
*/
|
||||
private $initialState;
|
||||
|
||||
/**
|
||||
* @var MockObject | OpenProjectAPIService
|
||||
*/
|
||||
private $openProjectService;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->config = $this->getMockBuilder(IConfig::class)->getMock();
|
||||
$this->initialState = $this->getMockBuilder(IInitialState::class)->getMock();
|
||||
$this->setting = new Personal($this->config, $this->initialState, "testUser");
|
||||
$this->openProjectService = $this->getMockBuilder(OpenProjectAPIService::class)->disableOriginalConstructor()->getMock();
|
||||
$this->setting = new Personal($this->config, $this->initialState, $this->openProjectService, "testUser");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -96,8 +103,10 @@ class PersonalTest extends TestCase {
|
|||
$this->config
|
||||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'authorization_method'],
|
||||
['integration_openproject', 'default_enable_unified_search'],
|
||||
['integration_openproject', 'default_enable_navigation'],
|
||||
['integration_openproject', 'authorization_method'],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'openproject_instance_url'],
|
||||
|
@ -105,7 +114,9 @@ class PersonalTest extends TestCase {
|
|||
['integration_openproject', 'openproject_instance_url'],
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'0', '0',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
$clientId,
|
||||
$clientSecret,
|
||||
$oauthInstanceUrl,
|
||||
|
@ -125,6 +136,7 @@ class PersonalTest extends TestCase {
|
|||
'search_enabled' => false,
|
||||
'navigation_enabled' => false,
|
||||
'admin_config_ok' => $adminConfigStatus,
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH
|
||||
]
|
||||
],
|
||||
['oauth-connection-result'],
|
||||
|
@ -156,8 +168,10 @@ class PersonalTest extends TestCase {
|
|||
$this->config
|
||||
->method('getAppValue')
|
||||
->withConsecutive(
|
||||
['integration_openproject', 'authorization_method'],
|
||||
['integration_openproject', 'default_enable_unified_search'],
|
||||
['integration_openproject', 'default_enable_navigation'],
|
||||
['integration_openproject', 'authorization_method'],
|
||||
['integration_openproject', 'openproject_client_id'],
|
||||
['integration_openproject', 'openproject_client_secret'],
|
||||
['integration_openproject', 'openproject_instance_url'],
|
||||
|
@ -165,7 +179,9 @@ class PersonalTest extends TestCase {
|
|||
['integration_openproject', 'openproject_instance_url'],
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
'1', '1',
|
||||
OpenProjectAPIService::AUTH_METHOD_OAUTH,
|
||||
"some-client-id",
|
||||
"some-client-secret",
|
||||
"http://localhost",
|
||||
|
@ -182,6 +198,7 @@ class PersonalTest extends TestCase {
|
|||
'search_enabled' => true,
|
||||
'navigation_enabled' => true,
|
||||
'admin_config_ok' => true,
|
||||
'authorization_method' => OpenProjectAPIService::AUTH_METHOD_OAUTH
|
||||
]
|
||||
],
|
||||
['oauth-connection-result'],
|
||||
|
|
Загрузка…
Ссылка в новой задаче