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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ SPDX-FileCopyrightText = "2026 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "AGPL-3.0-or-later"

[[annotations]]
path = ["img/app-dark.svg", "img/app.svg", "img/screenshot1.jpg", "img/screenshot2.jpg", "img/screenshot3.jpg"]
path = ["img/app-dark.svg", "img/app.svg", "img/app.old.svg", "img/screenshot1.jpg", "img/screenshot2.jpg", "img/screenshot3.jpg"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2026 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "AGPL-3.0-or-later"
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<name>Element/Matrix integration</name>
<summary>Integration of Element/Matrix</summary>
<description><![CDATA[Matrix integration lets you send files from Nextcloud Files to Matrix rooms.]]></description>
<version>1.0.0</version>
<version>1.1.0</version>
<licence>agpl</licence>
<author>Julien Veyssier</author>
<namespace>Matrix</namespace>
Expand Down
11 changes: 6 additions & 5 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 0 additions & 37 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,8 @@
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\Services\IAppConfig;
use OCP\Config\IUserConfig;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IL10N;
use OCP\INavigationManager;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Util;

Expand All @@ -36,7 +32,6 @@ public function register(IRegistrationContext $context): void {
}

public function boot(IBootContext $context): void {
$context->injectFn(Closure::fromCallable([$this, 'registerNavigation']));
$context->injectFn(Closure::fromCallable([$this, 'loadFilesPlugin']));
}

Expand All @@ -55,36 +50,4 @@ public function loadFilesPlugin(
}
}
}

public function registerNavigation(
IUserSession $userSession,
IUserConfig $userConfig,
IAppConfig $appConfig,
): void {
$user = $userSession->getUser();
if ($user !== null) {
$userId = $user->getUID();
$container = $this->getContainer();
$navlinkDefault = $appConfig->getAppValueString('navlink_default', lazy: true);
if ($userConfig->getValueString($userId, self::APP_ID, 'navigation_enabled', $navlinkDefault) === '1') {
$adminOauthUrl = $appConfig->getAppValueString('oauth_instance_url', lazy: true);
$matrixUrl = $userConfig->getValueString($userId, self::APP_ID, 'url', $adminOauthUrl) ?: $adminOauthUrl;
if ($matrixUrl === '') {
return;
}
$container->get(INavigationManager::class)->add(function () use ($container, $matrixUrl) {
$urlGenerator = $container->get(IURLGenerator::class);
$l10n = $container->get(IL10N::class);
return [
'id' => self::APP_ID,
'order' => 10,
'href' => $matrixUrl,
'icon' => $urlGenerator->imagePath(self::APP_ID, 'app.svg'),
'name' => $l10n->t('Matrix'),
'target' => '_blank',
];
});
}
}
}
}
81 changes: 51 additions & 30 deletions lib/Controller/ConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ public function isUserConnected(): DataResponse {
$token = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'token');
$clientId = $this->appConfig->getAppValueString('client_id', lazy: true);
$usePopup = $this->appConfig->getAppValueString('use_popup', '0', lazy: true) === '1';
$registeredClientUrl = $this->appConfig->getAppValueString('registered_client_url', lazy: true);
$oauthPossible = $this->isOAuthPossible($adminOauthUrl, $clientId, $registeredClientUrl);
$adminOauthApiUrl = $this->appConfig->getAppValueString('oauth_instance_api_url', lazy: true);
$registeredClientApiUrl = $this->appConfig->getAppValueString('registered_client_api_url', lazy: true);
$oauthPossible = $this->isOAuthPossible($adminOauthApiUrl, $clientId, $registeredClientApiUrl);

return new DataResponse([
'connected' => $matrixUrl !== '' && $token !== '',
Expand Down Expand Up @@ -99,15 +100,19 @@ public function getFilesToSend(): DataResponse {
*/
#[NoAdminRequired]
public function startOauth(string $oauthOrigin = 'settings'): DataResponse {
$oauthMatrixUrl = $this->appConfig->getAppValueString('oauth_instance_url', lazy: true);
$adminOauthUrl = $this->appConfig->getAppValueString('oauth_instance_url', lazy: true);
$clientId = $this->appConfig->getAppValueString('client_id', lazy: true);
$registeredClientUrl = $this->appConfig->getAppValueString('registered_client_url', lazy: true);
$adminOauthApiUrl = $this->appConfig->getAppValueString('oauth_instance_api_url', lazy: true);
$registeredClientApiUrl = $this->appConfig->getAppValueString('registered_client_api_url', lazy: true);

if ($oauthMatrixUrl === '' || $clientId === '' || !$this->isAdminOauthClientCompatible($oauthMatrixUrl, $registeredClientUrl)) {
if ($adminOauthUrl === ''
|| $clientId === ''
|| $adminOauthApiUrl === ''
|| $registeredClientApiUrl !== $adminOauthApiUrl) {
return new DataResponse(['error' => $this->l->t('OAuth is not configured')], Http::STATUS_BAD_REQUEST);
}

$authMetadata = $this->matrixAPIService->getAuthMetadata($oauthMatrixUrl);
$authMetadata = $this->matrixAPIService->getAuthMetadata($adminOauthApiUrl);
if (isset($authMetadata['error'])) {
return new DataResponse($authMetadata, Http::STATUS_BAD_REQUEST);
}
Expand Down Expand Up @@ -169,7 +174,15 @@ public function setConfig(array $values): DataResponse {
}
} else {
if ($key === 'url') {
$value = $this->matrixAPIService->normalizeMatrixUrl($value);
if ($value === '') {
$this->userConfig->deleteUserConfig($this->userId, Application::APP_ID, 'url');
$this->userConfig->deleteUserConfig($this->userId, Application::APP_ID, 'api_url');
continue;
} else {
$value = $this->matrixAPIService->normalizeMatrixUrl($value);
$matrixApiUrl = $this->matrixAPIService->resolveMatrixUrl($value);
$this->userConfig->setValueString($this->userId, Application::APP_ID, 'api_url', $matrixApiUrl);
}
}
$this->userConfig->setValueString($this->userId, Application::APP_ID, $key, $value);
}
Expand Down Expand Up @@ -200,13 +213,23 @@ public function setAdminConfig(array $values): DataResponse {
continue;
}
if ($key === 'oauth_instance_url') {
$value = $this->matrixAPIService->normalizeMatrixUrl($value);
if ($value === '') {
$this->appConfig->deleteAppValue('oauth_instance_url');
$this->appConfig->deleteAppValue('oauth_instance_api_url');
continue;
} else {
$value = $this->matrixAPIService->normalizeMatrixUrl($value);
$matrixApiUrl = $this->matrixAPIService->resolveMatrixUrl($value);
$this->appConfig->setAppValueString('oauth_instance_api_url', $matrixApiUrl, lazy: true);
}
}
$this->appConfig->setAppValueString($key, $value, lazy: true);
}

if ($clientIdWasUpdated) {
$this->appConfig->deleteAppValue('registered_client_url');
// when the client ID is changed, we need to update the registered client API URL
$adminOauthApiUrl = $this->appConfig->getAppValueString('oauth_instance_api_url', lazy: true);
$this->appConfig->setAppValueString('registered_client_api_url', $adminOauthApiUrl, lazy: true);
}

return new DataResponse([]);
Expand All @@ -231,7 +254,9 @@ public function setSensitiveAdminConfig(array $values): DataResponse {
}

if ($clientSecretWasUpdated) {
$this->appConfig->deleteAppValue('registered_client_url');
// when the client secret is changed, we need to update the registered client API URL
$adminOauthApiUrl = $this->appConfig->getAppValueString('oauth_instance_api_url', lazy: true);
$this->appConfig->setAppValueString('registered_client_api_url', $adminOauthApiUrl, lazy: true);
}

return new DataResponse([]);
Expand All @@ -242,12 +267,14 @@ public function setSensitiveAdminConfig(array $values): DataResponse {
*/
#[PasswordConfirmationRequired]
public function registerAdminOauthClient(string $oauth_instance_url): DataResponse {
$matrixUrl = $this->matrixAPIService->normalizeMatrixUrl($oauth_instance_url);
if ($matrixUrl === '') {
$oauthUrl = $this->matrixAPIService->normalizeMatrixUrl($oauth_instance_url);
$storedAdminOauthUrl = $this->appConfig->getAppValueString('oauth_instance_url', lazy: true);
$storedAdminOauthApiUrl = $this->appConfig->getAppValueString('oauth_instance_api_url', lazy: true);
if ($oauthUrl === '' || $storedAdminOauthUrl !== $oauthUrl) {
return new DataResponse(['error' => $this->l->t('Please provide a Matrix OAuth server URL first')], Http::STATUS_BAD_REQUEST);
}

$authMetadata = $this->matrixAPIService->getAuthMetadata($matrixUrl);
$authMetadata = $this->matrixAPIService->getAuthMetadata($storedAdminOauthApiUrl);
if (isset($authMetadata['error'])) {
return new DataResponse($authMetadata, Http::STATUS_BAD_REQUEST);
}
Expand Down Expand Up @@ -277,23 +304,21 @@ public function registerAdminOauthClient(string $oauth_instance_url): DataRespon
return new DataResponse(['error' => $message], Http::STATUS_BAD_REQUEST);
}

$this->appConfig->setAppValueString('oauth_instance_url', $matrixUrl, lazy: true);
$this->appConfig->setAppValueString('client_id', $clientId, lazy: true);
$resolvedMatrixUrl = $this->matrixAPIService->resolveMatrixUrl($matrixUrl);
$clientSecret = (string)($registrationResponse['client_secret'] ?? '');
if ($clientSecret !== '') {
$this->appConfig->setAppValueString('client_secret', $clientSecret, lazy: true, sensitive: true);
} else {
$this->appConfig->deleteAppValue('client_secret');
}
$this->appConfig->setAppValueString('registered_client_url', $resolvedMatrixUrl, lazy: true);
$this->appConfig->setAppValueString('registered_client_api_url', $storedAdminOauthApiUrl, lazy: true);

return new DataResponse([
'client_id' => $clientId,
'client_secret' => $clientSecret !== '' ? 'dummySecret' : '',
'oauth_instance_url' => $matrixUrl,
'oauth_instance_api_url' => $resolvedMatrixUrl,
'registered_client_url' => $resolvedMatrixUrl,
'oauth_instance_url' => $oauthUrl,
'oauth_instance_api_url' => $storedAdminOauthApiUrl,
'registered_client_api_url' => $storedAdminOauthApiUrl,
]);
}

Expand Down Expand Up @@ -336,6 +361,7 @@ public function oauthRedirect(
$oauthOrigin = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'oauth_origin');
$redirectUri = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'redirect_uri');
$matrixUrl = $this->appConfig->getAppValueString('oauth_instance_url', lazy: true);
$adminOauthApiUrl = $this->appConfig->getAppValueString('oauth_instance_api_url', lazy: true);
$clientId = $this->appConfig->getAppValueString('client_id', lazy: true);
$clientSecret = $this->appConfig->getAppValueString('client_secret', lazy: true);
$usePopup = $this->appConfig->getAppValueString('use_popup', '0', lazy: true) === '1';
Expand All @@ -353,7 +379,7 @@ public function oauthRedirect(
return $this->redirectToSettingsError($this->l->t('Error during OAuth exchanges'));
}

$authMetadata = $this->matrixAPIService->getAuthMetadata($matrixUrl);
$authMetadata = $this->matrixAPIService->getAuthMetadata($adminOauthApiUrl);
if (isset($authMetadata['error'])) {
return $this->redirectToSettingsError($authMetadata['error']);
}
Expand Down Expand Up @@ -454,21 +480,16 @@ private function redirectToSettingsError(string $message): RedirectResponse {
);
}

private function isOAuthPossible(string $adminOauthUrl, string $clientId, string $registeredClientUrl): bool {
if ($adminOauthUrl === '' || $clientId === '' || !$this->isAdminOauthClientCompatible($adminOauthUrl, $registeredClientUrl)) {
private function isOAuthPossible(string $adminOauthApiUrl, string $clientId, string $registeredClientApiUrl): bool {
if ($adminOauthApiUrl === ''
|| $clientId === ''
|| $registeredClientApiUrl === ''
|| $registeredClientApiUrl !== $adminOauthApiUrl) {
return false;
}
return true;
}

private function isAdminOauthClientCompatible(string $adminOauthUrl, string $registeredClientUrl): bool {
if ($registeredClientUrl === '') {
return true;
}

return $this->matrixAPIService->sameMatrixServer($registeredClientUrl, $adminOauthUrl);
}

private function generateOauthDeviceId(): string {
$alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~';
$alphabetLength = strlen($alphabet);
Expand Down
2 changes: 1 addition & 1 deletion lib/Controller/MatrixAPIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function __construct(
*/
#[NoAdminRequired]
public function getMatrixUrl(): DataResponse {
return new DataResponse($this->matrixAPIService->getMatrixUrl($this->userId));
return new DataResponse($this->matrixAPIService->getUserMatrixApiUrl($this->userId));
}

/**
Expand Down
44 changes: 44 additions & 0 deletions lib/Migration/Version010100Date20260430145222.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Matrix\Migration;

use Closure;
use OCA\Matrix\AppInfo\Application;
use OCP\Config\IUserConfig;
use OCP\IAppConfig;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

class Version010100Date20260430145222 extends SimpleMigrationStep {

public function __construct(
private IUserConfig $userConfig,
private IAppConfig $appConfig,
) {
}

/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
*/
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
$this->userConfig->deleteApp(Application::APP_ID);
foreach ([
'oauth_instance_url',
'oauth_instance_api_url',
'registered_client_url',
'registered_client_api_url',
'client_id',
'client_secret',
] as $key) {
$this->appConfig->deleteKey(Application::APP_ID, $key);
}
}
}
Loading
Loading