Skip to content

Commit

Permalink
Merge branch 'pu/ps/t/2513' into 'main'
Browse files Browse the repository at this point in the history
feature(MatrixSynapseIntegrator): manage matrix user lifecycle

See merge request tine20/tine20!6614
  • Loading branch information
pschuele committed Feb 24, 2025
2 parents fe46b3d + eb738ee commit 6fb8d33
Show file tree
Hide file tree
Showing 14 changed files with 508 additions and 13 deletions.
5 changes: 2 additions & 3 deletions tests/tine20/MatrixSynapseIntegrator/AllTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
* @package MatrixSynapseIntegrator
* @license http://www.gnu.org/licenses/agpl.html
* @copyright Copyright (c) 2020 Metaways Infosystems GmbH (http://www.metaways.de)
* @copyright Copyright (c) 2020-2025 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Paul Mehrer <[email protected]>
*/

Expand All @@ -15,13 +15,12 @@
*/
class MatrixSynapseIntegrator_AllTests
{


public static function suite ()
{
$suite = new PHPUnit\Framework\TestSuite('All MatrixSynapseIntegrator tests');

$suite->addTestSuite(MatrixSynapseIntegrator_ControllerTests::class);
$suite->addTestSuite(MatrixSynapseIntegrator_Controller_UserTests::class);

return $suite;
}
Expand Down
26 changes: 26 additions & 0 deletions tests/tine20/MatrixSynapseIntegrator/Backend/CorporalMock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/**
* MatrixSynapseIntegrator Backend
*
* @package MatrixSynapseIntegrator
* @subpackage Backend
* @license http =>//www.gnu.org/licenses/agpl.html AGPL Version 3
* @copyright Copyright (c) 2025 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Philipp Schüle <[email protected]>
*/

/**
* MatrixSynapseIntegrator Backend Mock
*
* @package MatrixSynapseIntegrator
* @subpackage Backend
*/
class MatrixSynapseIntegrator_Backend_CorporalMock extends MatrixSynapseIntegrator_Backend_Corporal
{
public function push(Tinebase_Model_FullUser $user): bool
{
$this->_policy = $this->_getPolicy($user);
return true;
}
}
82 changes: 82 additions & 0 deletions tests/tine20/MatrixSynapseIntegrator/Controller/UserTests.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

/**
* Tine 2.0 - http://www.tine20.org
*
* @package MatrixSynapseIntegrator
* @subpackage Test
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @copyright Copyright (c) 2025 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Philipp Schüle <[email protected]>
*/

class MatrixSynapseIntegrator_Controller_UserTests extends TestCase
{
public function setUp(): void
{
parent::setUp();

MatrixSynapseIntegrator_Controller_User::getInstance()->setBackend(new MatrixSynapseIntegrator_Backend_CorporalMock());
}

public function tearDown(): void
{
parent::tearDown();

MatrixSynapseIntegrator_Controller_User::destroyInstance();
}

public function testCreateUser(): Tinebase_Model_FullUser
{
$user = $this->_createTestUser([
'xprops' => [
MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID => '@{user.id}:matrix.domain',
MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE => true,
]
]);

self::assertTrue($user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE]);
self::assertEquals('@' . $user->getId() . ':matrix.domain',
$user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID]);

// assert corporal policy json
$backend = MatrixSynapseIntegrator_Controller_User::getInstance()->getBackend();
$policy = $backend->getPushedPolicy();
self::assertArrayHasKey('users', $policy);
self::assertCount(1, $policy['users']);
self::assertArrayHasKey('authType', $policy['users'][0]);
self::assertEquals('sha1', $policy['users'][0]['authType']);
$userData = $policy['users'][0];
self::assertEquals($user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID], $userData['id']);
self::assertEquals($user->accountDisplayName, $userData['displayName']);
return $user;
}

public function testUpdateUser()
{
$user = $this->testCreateUser();
$user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE] = false;
$updatedUser = Admin_Controller_User::getInstance()->update($user);
self::assertFalse($updatedUser->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE]);
$this->_assertInactiveUserInPolicy();
}

protected function _assertInactiveUserInPolicy()
{
$backend = MatrixSynapseIntegrator_Controller_User::getInstance()->getBackend();
$policy = $backend->getPushedPolicy();
self::assertArrayHasKey('users', $policy);
self::assertCount(1, $policy['users']);
self::assertArrayHasKey('active', $policy['users'][0]);
self::assertFalse($policy['users'][0]['active']);
}

public function testDeleteUser()
{
$user = $this->testCreateUser();
// user deletion need the confirmation header
Admin_Controller_User::getInstance()->setRequestContext(['confirm' => true]);
Admin_Controller_User::getInstance()->delete([$user->getId()]);
$this->_assertInactiveUserInPolicy();
}
}
4 changes: 2 additions & 2 deletions tests/tine20/MatrixSynapseIntegrator/ControllerTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class MatrixSynapseIntegrator_ControllerTests extends TestCase


public function setUp(): void
{
{
parent::setUp();

$this->_oldRequest = Tinebase_Core::getContainer()->get(RequestInterface::class);
Expand All @@ -41,7 +41,7 @@ public function setUp(): void
}

public function tearDown(): void
{
{
Tinebase_Core::getContainer()->set(RequestInterface::class, $this->_oldRequest);

if ($this->_originalTestUser) {
Expand Down

This file was deleted.

6 changes: 5 additions & 1 deletion tine20/Admin/js/user/EditDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,7 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
name: 'password_must_change',
plugins: [this.mustChangeTriggerPlugin]
});

var config = {
xtype: 'tabpanel',
deferredRender: false,
Expand All @@ -985,6 +985,10 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
border: false,
frame: true,
layout: 'hfit',
plugins: [{
ptype: 'ux.itemregistry',
key: 'Admin-UserEditDialog-RecordForm'
}],
items: [{
xtype: 'columnform',
labelAlign: 'top',
Expand Down
5 changes: 3 additions & 2 deletions tine20/Felamimail/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ class Felamimail_Controller extends Tinebase_Controller_Event
private static $_instance = NULL;

/**
* constructor (get current user)
* constructor
*/
private function __construct() {
private function __construct()
{
}

/**
Expand Down
118 changes: 118 additions & 0 deletions tine20/MatrixSynapseIntegrator/Backend/Corporal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

/**
* MatrixSynapseIntegrator Backend
*
* @package MatrixSynapseIntegrator
* @subpackage Backend
* @license http =>//www.gnu.org/licenses/agpl.html AGPL Version 3
* @copyright Copyright (c) 2025 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Philipp Schüle <[email protected]>
*/

/**
* MatrixSynapseIntegrator Backend
*
* @package MatrixSynapseIntegrator
* @subpackage Backend
*/
class MatrixSynapseIntegrator_Backend_Corporal
{
protected array $_policy = [];

protected const CORPORAL_ENDPOINT = '_matrix/corporal/policy';

public function push(Tinebase_Model_FullUser $user): bool
{
$this->_policy = $this->_getPolicy($user);
$this->pushPolicyToCorporal($this->_policy);

return true;
}

protected function _getHttpClient(): Zend_Http_Client
{
$matrixHomeServer = MatrixSynapseIntegrator_Config::getInstance()->get(
MatrixSynapseIntegrator_Config::HOME_SERVER_URL);
$corporalUrl = $matrixHomeServer . '/' . self::CORPORAL_ENDPOINT;

return Tinebase_Core::getHttpClient($corporalUrl);
}

/**
* @see https://github.com/devture/matrix-corporal/blob/master/docs/http-api.md#policy-submission-endpoint
*
* @param array $policy
* @return bool
* @throws Zend_Http_Client_Exception
*/
protected function pushPolicyToCorporal(array $policy): bool
{
$client = $this->_getHttpClient();
$client->setHeaders([
'Authorization' => 'Bearer ' . MatrixSynapseIntegrator_Config::getInstance()->get(
MatrixSynapseIntegrator_Config::CORPORAL_SHARED_AUTH_TOKEN),
'Content-Type' => 'application/json',
]);
$client->setRawData(json_encode($policy));

if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
. ' Pushing policy to ' . $client->getUri());
}

$client->request(Zend_Http_Client::PUT);

$response = $client->getLastResponse();
if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
. ' Response status: ' . $response->getStatus());
}

return $response->isSuccessful();
}

protected function _getPolicy(Tinebase_Model_FullUser $user): array
{
// TODO allow to configure defaults/flags

return [
"schemaVersion" => 2,
"flags" => [
"allowCustomUserDisplayNames" => true,
"allowCustomUserAvatars" => true,
"allowCustomPassthroughUserPasswords" => true,
"forbidRoomCreation" => false,
"forbidEncryptedRoomCreation" => false,
"forbidUnencryptedRoomCreation" => false
],
"users" => [
$this->_getUserPolicy($user)
],
];
}

protected function _getUserPolicy(Tinebase_Model_FullUser $user): array
{

return [
"id" => $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ID],
"active" => $user->is_deleted === 0 && $user->xprops()[MatrixSynapseIntegrator_Config::USER_XPROP_MATRIX_ACTIVE],
"displayName" => $user->accountDisplayName,
"forbidRoomCreation" => false,
"authType" => "sha1",
"authCredential" => Tinebase_User::getInstance()->getPasswordHashByLoginname($user->accountLoginName),
// "authType" => "plain",
// "avatarUri" => "https://example.com/john.jpg",
// "joinedRooms" => [
// {"roomId": "!roomA:example.com", "powerLevel": 0},
// {"roomId": "!roomB:example.com", "powerLevel": 50}
// ],
];
}

public function getPushedPolicy(): array
{
return $this->_policy;
}
}
34 changes: 31 additions & 3 deletions tine20/MatrixSynapseIntegrator/Config.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<?php

/**
* Tine 2.0
*
* @package MatrixSynapseIntegrator
* @subpackage Config
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @author Paul Mehrer <[email protected]>
* @copyright Copyright (c) 2020 Metaways Infosystems GmbH (http://www.metaways.de)
* @copyright Copyright (c) 2020-2025 Metaways Infosystems GmbH (http://www.metaways.de)
*/

/**
Expand All @@ -18,9 +19,14 @@
*/
class MatrixSynapseIntegrator_Config extends Tinebase_Config_Abstract
{
const APP_NAME = 'MatrixSynapseIntegrator';
public const APP_NAME = 'MatrixSynapseIntegrator';

public const MATRIX_DOMAIN = 'matrixDomain';
public const HOME_SERVER_URL = 'homeServerUrl';
public const CORPORAL_SHARED_AUTH_TOKEN = 'corporalSharedAuthToken';

const MATRIX_DOMAIN = 'matrixDomain';
public const USER_XPROP_MATRIX_ID = 'matrixId';
public const USER_XPROP_MATRIX_ACTIVE = 'matrixActive';

/**
* (non-PHPdoc)
Expand All @@ -43,6 +49,28 @@ class MatrixSynapseIntegrator_Config extends Tinebase_Config_Abstract
self::SETBYADMINMODULE => true,
self::SETBYSETUPMODULE => true,
],
self::HOME_SERVER_URL => [
//_('Home Server URL')
self::LABEL => 'Home Server URL',
//_('Home Server URL')
self::DESCRIPTION => 'Home Server URL',
self::TYPE => self::TYPE_STRING,
self::CLIENTREGISTRYINCLUDE => true,
self::SETBYADMINMODULE => true,
self::SETBYSETUPMODULE => true,
self::DEFAULT_STR => 'https://matrix.mydomain',
],
self::CORPORAL_SHARED_AUTH_TOKEN => [
//_('Corporal Shared Auth Token')
self::LABEL => 'Corporal Shared Auth Token',
//_('Corporal Shared Auth Token')
self::DESCRIPTION => 'Corporal Shared Auth Token',
self::TYPE => self::TYPE_STRING,
self::CLIENTREGISTRYINCLUDE => false,
self::SETBYADMINMODULE => false,
self::SETBYSETUPMODULE => false,
self::DEFAULT_STR => 'abc',
],
];

/**
Expand Down
Loading

0 comments on commit 6fb8d33

Please sign in to comment.