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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,17 @@ class Application extends App implements IBootstrap {
public const APP_ID = 'integration_openproject';
public const OPEN_PROJECT_ENTITIES_NAME = 'OpenProject';
public const OPENPROJECT_ALL_GROUP_NAME = 'OpenProjectNoAutomaticProjectFolders';
public const OPENPROJECT_API_SCOPES = ['api_v3'];
public const OPENPROJECT_API_SCOPES = ['api_v3'];

public const AUTH_METHOD_OAUTH = 'oauth2';
public const AUTH_METHOD_OIDC = 'oidc';
public const NEXTCLOUD_HUB_OIDC_PROVIDER_TYPE = "nextcloud_hub";
public const NEXTCLOUD_HUB_OIDC_PROVIDER_LABEL = "Nextcloud Hub";
public const EXTERNAL_OIDC_PROVIDER_TYPE = "external";

public const MIN_SUPPORTED_USER_OIDC_APP_VERSION = '7.2.0';
public const MIN_SUPPORTED_OIDC_APP_VERSION = '1.14.1';
public const MIN_SUPPORTED_GROUPFOLDERS_APP_VERSION = '1.0.0';

// default app name
private const DEFAULT_APP_NAMES = [
Expand Down Expand Up @@ -154,8 +164,8 @@ public function registerNavigation(IUserSession $userSession): void {
$userId,
self::APP_ID,
'navigation_enabled',
$this->config->getAppValue(Application::APP_ID, 'default_enable_navigation', '0')) === '1') {
$openprojectUrl = $this->config->getAppValue(Application::APP_ID, 'openproject_instance_url', '');
$this->config->getAppValue(self::APP_ID, 'default_enable_navigation', '0')) === '1') {
$openprojectUrl = $this->config->getAppValue(self::APP_ID, 'openproject_instance_url', '');
if ($openprojectUrl !== '') {
$container->get(INavigationManager::class)->add(function () use ($container, $openprojectUrl) {
$urlGenerator = $container->get(IURLGenerator::class);
Expand Down
16 changes: 8 additions & 8 deletions lib/Controller/ConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,13 @@ private function setIntegrationConfig(array $values): array {
// determine if the full reset is done when configuration is already with "oauth2"
$runningFullResetWithOAuth2Auth = (
$runningFullReset &&
$oldAuthMethod === OpenProjectAPIService::AUTH_METHOD_OAUTH
$oldAuthMethod === Application::AUTH_METHOD_OAUTH
);

// determine if the full reset is done when configuration is already with "oidc"
$runningFullResetWithOIDCAuth = (
$runningFullReset &&
$oldAuthMethod === OpenProjectAPIService::AUTH_METHOD_OIDC
$oldAuthMethod === Application::AUTH_METHOD_OIDC
);
if (
(key_exists('openproject_client_id', $values) && key_exists('openproject_client_secret', $values))
Expand Down Expand Up @@ -379,13 +379,13 @@ private function setIntegrationConfig(array $values): array {

// when switching from "oauth2" to "oidc" authorization method
if (key_exists('authorization_method', $values) &&
$values['authorization_method'] === OpenProjectAPIService::AUTH_METHOD_OIDC && $runningOauth2Reset) {
$values['authorization_method'] === Application::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) {
$values['authorization_method'] === Application::AUTH_METHOD_OAUTH && $runningOIDCReset) {
$this->resetOIDCConfigs();
}

Expand Down Expand Up @@ -637,15 +637,15 @@ public function setUpIntegration(?array $values): DataResponse {
// NOTE: this is for compatibility with older versions of the app
// when the authorization_method is not provided, set default to "oauth2"
if (\is_array($values) && !\array_key_exists('authorization_method', $values)) {
$values['authorization_method'] = OpenProjectAPIService::AUTH_METHOD_OAUTH;
$values['authorization_method'] = Application::AUTH_METHOD_OAUTH;
}
// For nextcloud_hub setup, set OIDC provider to Nextcloud Hub if not provided
if (
$values['authorization_method'] === OpenProjectAPIService::AUTH_METHOD_OIDC
&& $values['sso_provider_type'] === SettingsService::NEXTCLOUDHUB_OIDC_PROVIDER_TYPE
$values['authorization_method'] === Application::AUTH_METHOD_OIDC
&& $values['sso_provider_type'] === Application::NEXTCLOUD_HUB_OIDC_PROVIDER_TYPE
&& (!\array_key_exists('oidc_provider', $values) || !$values['oidc_provider'])
) {
$values['oidc_provider'] = SettingsService::NEXTCLOUDHUB_OIDC_PROVIDER_LABEL;
$values['oidc_provider'] = Application::NEXTCLOUD_HUB_OIDC_PROVIDER_LABEL;
}

// check all required settings
Expand Down
2 changes: 1 addition & 1 deletion lib/Listener/LoadAdditionalScriptsListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function handle(Event $event): void {
// 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->config->getAppValue(Application::APP_ID, 'authorization_method', '') === Application::AUTH_METHOD_OIDC &&
!$this->openProjectAPIService->getAccessToken($this->userId)
) {
return;
Expand Down
2 changes: 1 addition & 1 deletion lib/Listener/OpenProjectReferenceListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function handle(Event $event): void {
// 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->config->getAppValue(Application::APP_ID, 'authorization_method', '') === Application::AUTH_METHOD_OIDC &&
!$this->openProjectAPIService->getAccessToken($this->userId)
) {
return;
Expand Down
2 changes: 1 addition & 1 deletion lib/Migration/Version2900Date20250718065820.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
!(empty($opClientId) || empty($opClientSecret) || empty($ncClientId));

if (!$authenticationMethod && $hasCompleteOAuthSetup) {
$this->config->setAppValue(Application::APP_ID, 'authorization_method', OpenProjectAPIService::AUTH_METHOD_OAUTH);
$this->config->setAppValue(Application::APP_ID, 'authorization_method', Application::AUTH_METHOD_OAUTH);
}

return null;
Expand Down
41 changes: 17 additions & 24 deletions lib/Service/OpenProjectAPIService.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,6 @@
define('CACHE_TTL', 3600);

class OpenProjectAPIService {
public const AUTH_METHOD_OAUTH = 'oauth2';
public const AUTH_METHOD_OIDC = 'oidc';
public const MIN_SUPPORTED_USER_OIDC_APP_VERSION = '7.2.0';
public const MIN_SUPPORTED_OIDC_APP_VERSION = '1.14.1';
public const MIN_SUPPORTED_GROUPFOLDERS_APP_VERSION = '1.0.0';
public const NEXTCLOUD_HUB_PROVIDER = "nextcloud_hub";

// 1 hour expiration
public const DEFAULT_ACCESS_TOKEN_EXPIRATION = 3600;

Expand Down Expand Up @@ -925,7 +918,7 @@ public static function isAdminConfigOkForOauth2(IConfig $config):bool {
$authMethod = $config->getAppValue(Application::APP_ID, 'authorization_method');
// NOTE: For backwards compability, check the auth method only if provided
// version: 2.8 -> 2.9
if ($authMethod && $authMethod !== self::AUTH_METHOD_OAUTH) {
if ($authMethod && $authMethod !== Application::AUTH_METHOD_OAUTH) {
return false;
}

Expand All @@ -947,7 +940,7 @@ public static function isAdminConfigOkForOIDCAuth(IConfig $config):bool {
}

$authMethod = $config->getAppValue(Application::APP_ID, 'authorization_method');
if ($authMethod !== self::AUTH_METHOD_OIDC) {
if ($authMethod !== Application::AUTH_METHOD_OIDC) {
return false;
}

Expand All @@ -961,17 +954,17 @@ public static function isAdminConfigOkForOIDCAuth(IConfig $config):bool {
}

// check for nextcloud_hub sso
if ($ssoProviderType === SettingsService::NEXTCLOUDHUB_OIDC_PROVIDER_TYPE) {
if ($ssoProviderType === Application::NEXTCLOUD_HUB_OIDC_PROVIDER_TYPE) {
return !empty($targetAudienceClientId);
}

// check for external sso without token exchange
if ($ssoProviderType === SettingsService::EXTERNAL_OIDC_PROVIDER_TYPE && $tokenExchange === false) {
if ($ssoProviderType === Application::EXTERNAL_OIDC_PROVIDER_TYPE && $tokenExchange === false) {
return true;
}

// check for external sso with token exchange
if ($ssoProviderType === SettingsService::EXTERNAL_OIDC_PROVIDER_TYPE && $tokenExchange === true) {
if ($ssoProviderType === Application::EXTERNAL_OIDC_PROVIDER_TYPE && $tokenExchange === true) {
return !empty($targetAudienceClientId);
}

Expand All @@ -986,11 +979,11 @@ public static function isAdminConfigOkForOIDCAuth(IConfig $config):bool {
public static function isAdminConfigOk(IConfig $config): bool {
$authMethod = $config->getAppValue(Application::APP_ID, 'authorization_method');

if ($authMethod === self::AUTH_METHOD_OAUTH) {
if ($authMethod === Application::AUTH_METHOD_OAUTH) {
return self::isAdminConfigOkForOauth2($config);
}

if ($authMethod === self::AUTH_METHOD_OIDC) {
if ($authMethod === Application::AUTH_METHOD_OIDC) {
return self::isAdminConfigOkForOIDCAuth($config);
}

Expand Down Expand Up @@ -1206,7 +1199,7 @@ public function isGroupfoldersAppSupported(): bool {
$appVersion = $this->appManager->getAppVersion('groupfolders');
return (
$this->isGroupfoldersAppEnabled() &&
version_compare($appVersion, self::MIN_SUPPORTED_GROUPFOLDERS_APP_VERSION) >= 0
version_compare($appVersion, Application::MIN_SUPPORTED_GROUPFOLDERS_APP_VERSION) >= 0
);
}

Expand Down Expand Up @@ -1664,7 +1657,7 @@ public function getOpenProjectConfiguration(string $userId): array {
*/
public function getOIDCToken(string $userId): string {
$authorizationMethod = $this->config->getAppValue(Application::APP_ID, 'authorization_method');
if ($authorizationMethod !== SettingsService::AUTH_METHOD_OIDC) {
if ($authorizationMethod !== Application::AUTH_METHOD_OIDC) {
return '';
}
if (!$this->isUserOIDCAppInstalledAndEnabled()) {
Expand All @@ -1689,7 +1682,7 @@ public function getOIDCToken(string $userId): string {
}

$SSOProviderType = $this->config->getAppValue(Application::APP_ID, 'sso_provider_type');
if ($SSOProviderType === self::NEXTCLOUD_HUB_PROVIDER) {
if ($SSOProviderType === Application::NEXTCLOUD_HUB_OIDC_PROVIDER_TYPE) {
$oidcClientId = $this->config->getAppValue(Application::APP_ID, 'targeted_audience_client_id');
$clientTokenType = '';
try {
Expand Down Expand Up @@ -1747,7 +1740,7 @@ public function getAccessToken(?string $userId): string {
$authMethod = $this->config->getAppValue(Application::APP_ID, 'authorization_method');

if ($token && !$this->isAccessTokenExpired($userId)) {
if ($authMethod === SettingsService::AUTH_METHOD_OIDC) {
if ($authMethod === Application::AUTH_METHOD_OIDC) {
$this->initUserInfo($userId, $token);
}
return $token;
Expand All @@ -1756,15 +1749,15 @@ public function getAccessToken(?string $userId): string {
if ($token) {
$this->logger->debug('Token has expired.', ['app' => $this->appName]);
$this->logger->debug('Refreshing access token.', ['app' => $this->appName]);
if ($authMethod === SettingsService::AUTH_METHOD_OIDC) {
if ($authMethod === Application::AUTH_METHOD_OIDC) {
$this->config->deleteUserValue($userId, Application::APP_ID, 'user_name');
$this->config->deleteUserValue($userId, Application::APP_ID, 'user_id');
}
}

// For OAuth2 setup, only try to refresh the expired token.
// Token exchange needs to be initiated from the UI.
if ($authMethod === SettingsService::AUTH_METHOD_OAUTH && $token) {
if ($authMethod === Application::AUTH_METHOD_OAUTH && $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');
Expand All @@ -1784,7 +1777,7 @@ public function getAccessToken(?string $userId): string {
return '';
}
return $result['access_token'];
} elseif ($authMethod === SettingsService::AUTH_METHOD_OIDC) {
} elseif ($authMethod === Application::AUTH_METHOD_OIDC) {
$token = $this->getOIDCToken($userId);
if ($token) {
$this->initUserInfo($userId, $token);
Expand Down Expand Up @@ -1855,7 +1848,7 @@ class_exists('\OCA\UserOIDC\Event\ExchangedTokenRequestedEvent') &&
class_exists('\OCA\UserOIDC\Event\ExternalTokenRequestedEvent') &&
class_exists('\OCA\UserOIDC\Event\InternalTokenRequestedEvent') &&
class_exists('\OCA\UserOIDC\User\Backend') &&
version_compare($userOidcVersion, self::MIN_SUPPORTED_USER_OIDC_APP_VERSION) >= 0
version_compare($userOidcVersion, Application::MIN_SUPPORTED_USER_OIDC_APP_VERSION) >= 0
);
}

Expand All @@ -1867,7 +1860,7 @@ public function isOIDCAppSupported(): bool {
$appVersion = $this->appManager->getAppVersion('oidc');
return (
$this->isOIDCAppEnabled() &&
version_compare($appVersion, self::MIN_SUPPORTED_OIDC_APP_VERSION) >= 0
version_compare($appVersion, Application::MIN_SUPPORTED_OIDC_APP_VERSION) >= 0
);
}

Expand All @@ -1876,7 +1869,7 @@ public function isOIDCAppSupported(): bool {
*/
public function isOIDCUser(): bool {
$SSOProviderType = $this->config->getAppValue(Application::APP_ID, 'sso_provider_type');
if ($SSOProviderType === self::NEXTCLOUD_HUB_PROVIDER) {
if ($SSOProviderType === Application::NEXTCLOUD_HUB_OIDC_PROVIDER_TYPE) {
return true;
}

Expand Down
19 changes: 7 additions & 12 deletions lib/Service/SettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,11 @@
use OCP\Security\ISecureRandom;

class SettingsService {
public const AUTH_METHOD_OAUTH = 'oauth2';
public const AUTH_METHOD_OIDC = 'oidc';
public const NEXTCLOUDHUB_OIDC_PROVIDER_TYPE = "nextcloud_hub";
public const EXTERNAL_OIDC_PROVIDER_TYPE = "external";
public const NEXTCLOUDHUB_OIDC_PROVIDER_LABEL = "Nextcloud Hub";
// <setting_name> => <data_type>
private const GENERAL_ADMIN_SETTINGS = [
// general settings
'openproject_instance_url' => 'string',
'authorization_method' => [self::AUTH_METHOD_OAUTH, self::AUTH_METHOD_OIDC],
'authorization_method' => [Application::AUTH_METHOD_OAUTH, Application::AUTH_METHOD_OIDC],
'default_enable_navigation' => 'boolean',
'default_enable_unified_search' => 'boolean',
// groupfolders settings
Expand All @@ -36,7 +31,7 @@ class SettingsService {
'openproject_client_secret' => 'string',
];
private const OIDC_ADMIN_SETTINGS = [
'sso_provider_type' => [self::NEXTCLOUDHUB_OIDC_PROVIDER_TYPE, self::EXTERNAL_OIDC_PROVIDER_TYPE],
'sso_provider_type' => [Application::NEXTCLOUD_HUB_OIDC_PROVIDER_TYPE, Application::EXTERNAL_OIDC_PROVIDER_TYPE],
'oidc_provider' => 'string',
'targeted_audience_client_id' => 'string',
'token_exchange' => 'boolean',
Expand Down Expand Up @@ -111,26 +106,26 @@ public function validateAdminSettingsForm(?array $values, bool $completeSetup =
throw new InvalidArgumentException("'authorization_method' setting is missing");
}
$authMethod = $values['authorization_method'];
if (!\in_array($authMethod, [self::AUTH_METHOD_OAUTH, self::AUTH_METHOD_OIDC])) {
if (!\in_array($authMethod, [Application::AUTH_METHOD_OAUTH, Application::AUTH_METHOD_OIDC])) {
throw new InvalidArgumentException('Invalid authorization method');
}
if ($authMethod === self::AUTH_METHOD_OAUTH) {
if ($authMethod === Application::AUTH_METHOD_OAUTH) {
$settings = $this->getCompleteOAuthSettings();
} else {
$settings = $this->getCompleteOIDCSettings();
if (!\array_key_exists('sso_provider_type', $values)) {
throw new InvalidArgumentException(
"Incomplete settings: 'sso_provider_type' is required with '"
. self::AUTH_METHOD_OIDC
. Application::AUTH_METHOD_OIDC
. "' method"
);
}
if ($values['sso_provider_type'] === self::NEXTCLOUDHUB_OIDC_PROVIDER_TYPE) {
if ($values['sso_provider_type'] === Application::NEXTCLOUD_HUB_OIDC_PROVIDER_TYPE) {
// for 'nextcloud_hub' type
// 'oidc_provider' and 'token_exchange' settings are not required
$settingsToSkip[] = 'oidc_provider';
$settingsToSkip[] = 'token_exchange';
} elseif ($values['sso_provider_type'] === self::EXTERNAL_OIDC_PROVIDER_TYPE) {
} elseif ($values['sso_provider_type'] === Application::EXTERNAL_OIDC_PROVIDER_TYPE) {
if (!\array_key_exists('token_exchange', $values)) {
throw new InvalidArgumentException(
"Incomplete settings: 'token_exchange' is required with external provider"
Expand Down
10 changes: 5 additions & 5 deletions lib/Settings/Admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public function getForm(): TemplateResponse {
// and there is existing complete Oauth2 setup
$authenticationMethod = $this->config->getAppValue(Application::APP_ID, 'authorization_method', '');
if (!$authenticationMethod && OpenProjectAPIService::isAdminConfigOkForOauth2($this->config)) {
$authenticationMethod = OpenProjectAPIService::AUTH_METHOD_OAUTH;
$authenticationMethod = Application::AUTH_METHOD_OAUTH;
$this->config->setAppValue(Application::APP_ID, 'authorization_method', $authenticationMethod);
}

Expand Down Expand Up @@ -111,25 +111,25 @@ public function getForm(): TemplateResponse {
'oidc_providers' => $this->openProjectAPIService->getRegisteredOidcProviders(),
'user_oidc_enabled' => $this->openProjectAPIService->isUserOIDCAppInstalledAndEnabled(),
'user_oidc_supported' => $this->openProjectAPIService->isUserOIDCAppSupported(),
'user_oidc_minimum_version' => OpenProjectAPIService::MIN_SUPPORTED_USER_OIDC_APP_VERSION,
'user_oidc_minimum_version' => Application::MIN_SUPPORTED_USER_OIDC_APP_VERSION,
'apps' => [
'oidc' => [
'name' => $this->openProjectAPIService->getAppsName('oidc'),
'enabled' => $this->openProjectAPIService->isOIDCAppEnabled(),
'supported' => $this->openProjectAPIService->isOIDCAppSupported(),
'minimum_version' => OpenProjectAPIService::MIN_SUPPORTED_OIDC_APP_VERSION,
'minimum_version' => Application::MIN_SUPPORTED_OIDC_APP_VERSION,
],
'user_oidc' => [
'name' => $this->openProjectAPIService->getAppsName('user_oidc'),
'enabled' => $this->openProjectAPIService->isUserOIDCAppInstalledAndEnabled(),
'supported' => $this->openProjectAPIService->isUserOIDCAppSupported(),
'minimum_version' => OpenProjectAPIService::MIN_SUPPORTED_USER_OIDC_APP_VERSION,
'minimum_version' => Application::MIN_SUPPORTED_USER_OIDC_APP_VERSION,
],
'groupfolders' => [
'name' => $this->openProjectAPIService->getAppsName('groupfolders'),
'enabled' => $this->openProjectAPIService->isGroupfoldersAppEnabled(),
'supported' => $this->openProjectAPIService->isGroupfoldersAppSupported(),
'minimum_version' => OpenProjectAPIService::MIN_SUPPORTED_GROUPFOLDERS_APP_VERSION,
'minimum_version' => Application::MIN_SUPPORTED_GROUPFOLDERS_APP_VERSION,
],
],
];
Expand Down
3 changes: 1 addition & 2 deletions lib/TokenEventFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
namespace OCA\OpenProject;

use OCA\OpenProject\AppInfo\Application;
use OCA\OpenProject\Service\OpenProjectAPIService;
use OCA\UserOIDC\Event\ExchangedTokenRequestedEvent as ExchangedTokenEvent;
use OCA\UserOIDC\Event\ExternalTokenRequestedEvent as ExternalTokenEvent;
use OCA\UserOIDC\Event\InternalTokenRequestedEvent as InternalTokenEvent;
Expand All @@ -33,7 +32,7 @@ public function getEvent(): InternalTokenEvent|ExternalTokenEvent|ExchangedToken

// If the SSO provider is Nextcloud Hub,
// get token from internal IDP (oidc)
if ($SSOProviderType === OpenProjectAPIService::NEXTCLOUD_HUB_PROVIDER) {
if ($SSOProviderType === Application::NEXTCLOUD_HUB_OIDC_PROVIDER_TYPE) {
return new InternalTokenEvent($targetAudience, Application::OPENPROJECT_API_SCOPES, $targetAudience);
}

Expand Down
Loading
Loading