diff --git a/appinfo/info.xml b/appinfo/info.xml
index e2145280e36f317ab0c527d5c487300995054460..fede48170ddee9a0978bd8a94bb4e417b194fab5 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -10,7 +10,7 @@
- 10.1.0
+ 10.1.1
agpl
Murena SAS
EcloudAccounts
diff --git a/lib/Cron/TwoFactorStateChangeJob.php b/lib/Cron/TwoFactorStateChangeJob.php
new file mode 100644
index 0000000000000000000000000000000000000000..dbdd32ae31ce0717e82af3a53c4e2835b4861c2f
--- /dev/null
+++ b/lib/Cron/TwoFactorStateChangeJob.php
@@ -0,0 +1,61 @@
+jobList = $jobList;
+ $this->ssoService = $ssoService;
+ $this->logger = $logger;
+
+ $this->setAllowParallelRuns(true);
+ }
+
+ protected function run($arguments) {
+ $enabled = $arguments[self::ENABLED_KEY];
+ $username = $arguments[self::USERNAME_KEY];
+ $tryCount = $arguments[self::TRYCOUNT_KEY];
+
+ try {
+ $this->ssoService->handle2FAStateChange($enabled, $username);
+ } catch (Exception $e) {
+ if ($tryCount > 2) {
+ $this->logger->logException($e, ['app' => Application::APP_ID]);
+ return;
+ }
+
+ $tryCount = $tryCount + 1;
+ $arguments[self::TRYCOUNT_KEY] = $tryCount;
+ $this->jobList->scheduleAfter(
+ TwoFactorStateChangeJob::class,
+ $arguments,
+ self::INTERVAL_INBETWEEN_JOB_IN_SEC
+ );
+ }
+ }
+}
diff --git a/lib/Listeners/TwoFactorStateChangedListener.php b/lib/Listeners/TwoFactorStateChangedListener.php
index 323088419da3ee42974d184396f9070abf0d8e87..0ae5e8e340024d645e5f5e0d78d9613ef4e852ef 100644
--- a/lib/Listeners/TwoFactorStateChangedListener.php
+++ b/lib/Listeners/TwoFactorStateChangedListener.php
@@ -5,27 +5,27 @@ declare(strict_types=1);
namespace OCA\EcloudAccounts\Listeners;
use OCA\EcloudAccounts\AppInfo\Application;
-use OCA\EcloudAccounts\Db\TwoFactorMapper;
+use OCA\EcloudAccounts\Cron\TwoFactorStateChangeJob;
use OCA\EcloudAccounts\Service\SSOService;
use OCA\TwoFactorTOTP\Event\StateChanged;
use OCP\App\IAppManager;
+use OCP\BackgroundJob\IJobList;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\ILogger;
class TwoFactorStateChangedListener implements IEventListener {
private IAppManager $appManager;
- private TwoFactorMapper $twoFactorMapper;
private SSOService $ssoService;
+ private IJobList $jobList;
private ILogger $logger;
private const TWOFACTOR_APP_ID = 'twofactor_totp';
-
- public function __construct(IAppManager $appManager, SSOService $ssoService, TwoFactorMapper $twoFactorMapper, ILogger $logger) {
+ public function __construct(IAppManager $appManager, IJobList $jobList, SSOService $ssoService, ILogger $logger) {
$this->appManager = $appManager;
$this->ssoService = $ssoService;
- $this->twoFactorMapper = $twoFactorMapper;
+ $this->jobList = $jobList;
$this->logger = $logger;
}
@@ -37,18 +37,19 @@ class TwoFactorStateChangedListener implements IEventListener {
$user = $event->getUser();
$username = $user->getUID();
+ $enabled = $event->isEnabled();
try {
- // When state change event is fired by user disabling 2FA, delete existing 2FA credentials and return
- // i.e. disable 2FA for user at SSO
- if (!$event->isEnabled()) {
- $this->ssoService->deleteCredentials($username);
- return;
- }
-
- $secret = $this->twoFactorMapper->getSecret($username);
- $this->ssoService->migrateCredential($username, $secret);
+ $this->ssoService->handle2FAStateChange($enabled, $username);
} catch (Exception $e) {
$this->logger->logException($e, ['app' => Application::APP_ID]);
+
+ // faced exception. Initiate BG job to retry again.
+ $arguments = [
+ TwoFactorStateChangeJob::ENABLED_KEY => $event->isEnabled(),
+ TwoFactorStateChangeJob::USERNAME_KEY => $username,
+ TwoFactorStateChangeJob::TRYCOUNT_KEY => 0
+ ];
+ $this->jobList->add(TwoFactorStateChangeJob::class, $arguments);
}
}
}
diff --git a/lib/Service/SSOService.php b/lib/Service/SSOService.php
index 8436d8eb440d5da2a15d51e0256e625ae3c6dd17..ee17ac45b5ed8ce8e475a08a8a59eee55d999dff 100644
--- a/lib/Service/SSOService.php
+++ b/lib/Service/SSOService.php
@@ -4,6 +4,7 @@
namespace OCA\EcloudAccounts\Service;
use OCA\EcloudAccounts\AppInfo\Application;
+use OCA\EcloudAccounts\Db\TwoFactorMapper;
use OCA\EcloudAccounts\Exception\SSOAdminAccessTokenException;
use OCA\EcloudAccounts\Exception\SSOAdminAPIException;
use OCP\IConfig;
@@ -25,6 +26,7 @@ class SSOService {
private ICrypto $crypto;
private IFactory $l10nFactory;
private IUserManager $userManager;
+ private TwoFactorMapper $twoFactorMapper;
private string $mainDomain;
private string $legacyDomain;
@@ -33,7 +35,7 @@ class SSOService {
private const USERS_ENDPOINT = '/users';
private const CREDENTIALS_ENDPOINT = '/users/{USER_ID}/credentials';
- public function __construct($appName, IConfig $config, CurlService $curlService, ICrypto $crypto, IFactory $l10nFactory, IUserManager $userManager, ILogger $logger) {
+ public function __construct($appName, IConfig $config, CurlService $curlService, ICrypto $crypto, IFactory $l10nFactory, IUserManager $userManager, TwoFactorMapper $twoFactorMapper, ILogger $logger) {
$this->appName = $appName;
$this->config = $config;
@@ -53,6 +55,7 @@ class SSOService {
$this->logger = $logger;
$this->l10nFactory = $l10nFactory;
$this->userManager = $userManager;
+ $this->twoFactorMapper = $twoFactorMapper;
$this->mainDomain = $this->config->getSystemValue("main_domain");
$this->legacyDomain = $this->config->getSystemValue("legacy_domain");
@@ -64,7 +67,7 @@ class SSOService {
public function migrateCredential(string $username, string $secret) : void {
if($this->isNotCurrentUser($username)) {
- $this->getUserId($username);
+ $this->setupUserId($username);
}
$this->deleteCredentials($username);
@@ -84,7 +87,7 @@ class SSOService {
public function deleteCredentials(string $username) : void {
if($this->isNotCurrentUser($username)) {
- $this->getUserId($username);
+ $this->setupUserId($username);
}
$credentialIds = $this->getCredentialIds();
@@ -100,7 +103,7 @@ class SSOService {
public function logout(string $username) : void {
if($this->isNotCurrentUser($username)) {
- $this->getUserId($username);
+ $this->setupUserId($username);
}
$url = $this->ssoConfig['admin_rest_api_url'] . self::USERS_ENDPOINT . '/' . $this->currentUserId . '/logout';
@@ -109,6 +112,18 @@ class SSOService {
$this->callSSOAPI($url, 'POST', [], 204);
}
+ public function handle2FAStateChange(bool $enabled, string $username) : void {
+ // When state change event is fired by user disabling 2FA, delete existing 2FA credentials and return
+ // i.e. disable 2FA for user at SSO
+ if (!$enabled) {
+ $this->deleteCredentials($username);
+ return;
+ }
+
+ $secret = $this->twoFactorMapper->getSecret($username);
+ $this->migrateCredential($username, $secret);
+ }
+
private function getCredentialIds() : array {
$url = $this->ssoConfig['admin_rest_api_url'] . self::CREDENTIALS_ENDPOINT;
$url = str_replace('{USER_ID}', $this->currentUserId, $url);
@@ -159,7 +174,19 @@ class SSOService {
return $credentialEntry;
}
- private function getUserId(string $username) : void {
+ private function setupUserId(string $username) : void {
+ $user = $this->userManager->get($username);
+ $savedOIDCUid = $this->config->getUserValue($user->getUID(), 'oidc_login', 'oidc_uid');
+
+ if ($savedOIDCUid !== null && trim($savedOIDCUid) !== '') {
+ $this->currentUserId = $savedOIDCUid;
+ return;
+ }
+
+ $this->retrieveUserId($username);
+ }
+
+ private function retrieveUserId(string $username) {
$user = $this->userManager->get($username);
if ($user === null) {
throw new SSOAdminAPIException('Error: no user exists in cloud with username ' . $username);