This patchset: * implements the /invite-accepted endpoint * adds capabilities and inviteAceptDialog to the discovery * adds a FederatedInviteAcceptedEvent https://cs3org.github.io/OCM-API/docs.html?branch=v1.1.0&repo=OCM-API&user=cs3org#/paths/~1invite-accepted/post Co-authored-by: Anna <anna@nextcloud.com> Co-authored-by: Côme Chilliet <come.chilliet@nextcloud.com> Co-authored-by: Joas Schilling <213943+nickvergessen@users.noreply.github.com> Co-authored-by: Navid Shokri <navid.pdp11@gmail.com> Signed-off-by: Micke Nordin <kano@sunet.se>pull/51113/head
parent
acc2311a0d
commit
623f2f0240
@ -0,0 +1,62 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCA\CloudFederationAPI\Db; |
||||
|
||||
use OCP\AppFramework\Db\Entity; |
||||
use OCP\DB\Types; |
||||
|
||||
/** |
||||
* @method bool isAccepted() |
||||
* @method void setAccepted(bool $accepted) |
||||
* @method int|null getAcceptedAt() |
||||
* @method void setAcceptedAt(int $acceptedAt) |
||||
* @method int|null getCreatedAt() |
||||
* @method void setCreatedAt(int $createdAt) |
||||
* @method int|null getExpiredAt() |
||||
* @method void setExpiredAt(int $expiredAt) |
||||
* @method string|null getRecipientEmail() |
||||
* @method void setRecipientEmail(string $recipientEmail) |
||||
* @method string|null getRecipientName() |
||||
* @method void setRecipientName(string $recipientName) |
||||
* @method string|null getRecipientProvider() |
||||
* @method void setRecipientProvider(string $recipientProvider) |
||||
* @method string|null getRecipientUserId() |
||||
* @method void setRecipientUserId(string $recipientUserId) |
||||
* @method string getToken() |
||||
* @method void setToken(string $token) |
||||
* @method string|null getUserId() |
||||
* @method void setUserId(string $userId) |
||||
*/ |
||||
|
||||
class FederatedInvite extends Entity { |
||||
protected bool $accepted = false; |
||||
protected ?int $acceptedAt = 0; |
||||
protected int $createdAt = 0; |
||||
protected ?int $expiredAt = 0; |
||||
protected ?string $recipientEmail = null; |
||||
protected ?string $recipientName = null; |
||||
protected ?string $recipientProvider = null; |
||||
protected ?string $recipientUserId = null; |
||||
protected string $token = ''; |
||||
protected string $userId = ''; |
||||
|
||||
public function __construct() { |
||||
$this->addType('accepted', Types::BOOLEAN); |
||||
$this->addType('acceptedAt', Types::BIGINT); |
||||
$this->addType('createdAt', Types::BIGINT); |
||||
$this->addType('expiredAt', Types::BIGINT); |
||||
$this->addType('recipientEmail', Types::STRING); |
||||
$this->addType('recipientName', Types::STRING); |
||||
$this->addType('recipientProvider', Types::STRING); |
||||
$this->addType('recipientUserId', Types::STRING); |
||||
$this->addType('token', Types::STRING); |
||||
$this->addType('userId', Types::STRING); |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCA\CloudFederationAPI\Db; |
||||
|
||||
use OCP\AppFramework\Db\QBMapper; |
||||
use OCP\IDBConnection; |
||||
|
||||
/** |
||||
* @template-extends QBMapper<FederatedInvite> |
||||
*/ |
||||
class FederatedInviteMapper extends QBMapper { |
||||
public const TABLE_NAME = 'federated_invites'; |
||||
|
||||
public function __construct(IDBConnection $db) { |
||||
parent::__construct($db, self::TABLE_NAME); |
||||
} |
||||
|
||||
public function findByToken(string $token): FederatedInvite { |
||||
$qb = $this->db->getQueryBuilder(); |
||||
$qb->select('*') |
||||
->from('federated_invites') |
||||
->where($qb->expr()->eq('token', $qb->createNamedParameter($token))); |
||||
return $this->findEntity($qb); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,24 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-only |
||||
*/ |
||||
|
||||
namespace OCA\CloudFederationAPI\Events; |
||||
|
||||
use OCA\CloudFederationAPI\Db\FederatedInvite; |
||||
use OCP\EventDispatcher\Event; |
||||
|
||||
class FederatedInviteAcceptedEvent extends Event { |
||||
public function __construct( |
||||
private FederatedInvite $invitation, |
||||
) { |
||||
parent::__construct(); |
||||
} |
||||
|
||||
public function getInvitation(): FederatedInvite { |
||||
return $this->invitation; |
||||
} |
||||
} |
@ -0,0 +1,89 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCA\CloudFederationAPI\Migration; |
||||
|
||||
use Closure; |
||||
use OCP\DB\ISchemaWrapper; |
||||
use OCP\DB\Types; |
||||
use OCP\Migration\IOutput; |
||||
use OCP\Migration\SimpleMigrationStep; |
||||
|
||||
class Version1016Date202502262004 extends SimpleMigrationStep { |
||||
/** |
||||
* @param IOutput $output |
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` |
||||
* @param array $options |
||||
* @return null|ISchemaWrapper |
||||
*/ |
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) { |
||||
/** @var ISchemaWrapper $schema */ |
||||
$schema = $schemaClosure(); |
||||
$table_name = 'federated_invites'; |
||||
|
||||
if (!$schema->hasTable($table_name)) { |
||||
$table = $schema->createTable($table_name); |
||||
$table->addColumn('id', Types::BIGINT, [ |
||||
'autoincrement' => true, |
||||
'notnull' => true, |
||||
'length' => 11, |
||||
'unsigned' => true, |
||||
]); |
||||
$table->addColumn('user_id', Types::STRING, [ |
||||
'notnull' => true, |
||||
'length' => 64, |
||||
|
||||
]); |
||||
// https://saturncloud.io/blog/what-is-the-maximum-length-of-a-url-in-different-browsers/#maximum-url-length-in-different-browsers |
||||
// We use the least common denominator, the minimum length supported by browsers |
||||
$table->addColumn('recipient_provider', Types::STRING, [ |
||||
'notnull' => false, |
||||
'length' => 2083, |
||||
]); |
||||
$table->addColumn('recipient_user_id', Types::STRING, [ |
||||
'notnull' => false, |
||||
'length' => 1024, |
||||
]); |
||||
$table->addColumn('recipient_name', Types::STRING, [ |
||||
'notnull' => false, |
||||
'length' => 1024, |
||||
]); |
||||
// https://www.directedignorance.com/blog/maximum-length-of-email-address |
||||
$table->addColumn('recipient_email', Types::STRING, [ |
||||
'notnull' => false, |
||||
'length' => 320, |
||||
]); |
||||
$table->addColumn('token', Types::STRING, [ |
||||
'notnull' => true, |
||||
'length' => 60, |
||||
]); |
||||
$table->addColumn('accepted', Types::BOOLEAN, [ |
||||
'notnull' => false, |
||||
'default' => false |
||||
]); |
||||
$table->addColumn('created_at', Types::BIGINT, [ |
||||
'notnull' => true, |
||||
]); |
||||
|
||||
$table->addColumn('expired_at', Types::BIGINT, [ |
||||
'notnull' => false, |
||||
]); |
||||
|
||||
$table->addColumn('accepted_at', Types::BIGINT, [ |
||||
'notnull' => false, |
||||
]); |
||||
|
||||
$table->addUniqueConstraint(['token']); |
||||
$table->setPrimaryKey(['id']); |
||||
return $schema; |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
} |
@ -0,0 +1,136 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCA\CloudFederationApi\Tests; |
||||
|
||||
use NCU\Security\Signature\ISignatureManager; |
||||
use OC\OCM\OCMSignatoryManager; |
||||
use OCA\CloudFederationAPI\Config; |
||||
use OCA\CloudFederationAPI\Controller\RequestHandlerController; |
||||
use OCA\CloudFederationAPI\Db\FederatedInvite; |
||||
use OCA\CloudFederationAPI\Db\FederatedInviteMapper; |
||||
use OCA\FederatedFileSharing\AddressHandler; |
||||
use OCP\AppFramework\Http; |
||||
use OCP\AppFramework\Http\JSONResponse; |
||||
use OCP\AppFramework\Utility\ITimeFactory; |
||||
use OCP\EventDispatcher\IEventDispatcher; |
||||
use OCP\Federation\ICloudFederationFactory; |
||||
use OCP\Federation\ICloudFederationProviderManager; |
||||
use OCP\Federation\ICloudIdManager; |
||||
use OCP\IAppConfig; |
||||
use OCP\IGroupManager; |
||||
use OCP\IRequest; |
||||
use OCP\IURLGenerator; |
||||
use OCP\IUser; |
||||
use OCP\IUserManager; |
||||
use PHPUnit\Framework\MockObject\MockObject; |
||||
use Psr\Log\LoggerInterface; |
||||
use Test\TestCase; |
||||
|
||||
class RequestHandlerControllerTest extends TestCase { |
||||
private IRequest&MockObject $request; |
||||
private LoggerInterface&MockObject $logger; |
||||
private IUserManager&MockObject $userManager; |
||||
private IGroupManager&MockObject $groupManager; |
||||
private IURLGenerator&MockObject $urlGenerator; |
||||
private ICloudFederationProviderManager&MockObject $cloudFederationProviderManager; |
||||
private Config&MockObject $config; |
||||
private IEventDispatcher&MockObject $eventDispatcher; |
||||
private FederatedInviteMapper&MockObject $federatedInviteMapper; |
||||
private AddressHandler&MockObject $addressHandler; |
||||
private IAppConfig&MockObject $appConfig; |
||||
private ICloudFederationFactory&MockObject $cloudFederationFactory; |
||||
private ICloudIdManager&MockObject $cloudIdManager; |
||||
private ISignatureManager&MockObject $signatureManager; |
||||
private OCMSignatoryManager&MockObject $signatoryManager; |
||||
private ITimeFactory&MockObject $timeFactory; |
||||
|
||||
private RequestHandlerController $requestHandlerController; |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
|
||||
$this->request = $this->createMock(IRequest::class); |
||||
$this->logger = $this->createMock(LoggerInterface::class); |
||||
$this->userManager = $this->createMock(IUserManager::class); |
||||
$this->groupManager = $this->createMock(IGroupManager::class); |
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class); |
||||
$this->cloudFederationProviderManager = $this->createMock(ICloudFederationProviderManager::class); |
||||
$this->config = $this->createMock(Config::class); |
||||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class); |
||||
$this->federatedInviteMapper = $this->createMock(FederatedInviteMapper::class); |
||||
$this->addressHandler = $this->createMock(AddressHandler::class); |
||||
$this->appConfig = $this->createMock(IAppConfig::class); |
||||
$this->cloudFederationFactory = $this->createMock(ICloudFederationFactory::class); |
||||
$this->cloudIdManager = $this->createMock(ICloudIdManager::class); |
||||
$this->signatureManager = $this->createMock(ISignatureManager::class); |
||||
$this->signatoryManager = $this->createMock(OCMSignatoryManager::class); |
||||
$this->timeFactory = $this->createMock(ITimeFactory::class); |
||||
|
||||
$this->requestHandlerController = new RequestHandlerController( |
||||
'cloud_federation_api', |
||||
$this->request, |
||||
$this->logger, |
||||
$this->userManager, |
||||
$this->groupManager, |
||||
$this->urlGenerator, |
||||
$this->cloudFederationProviderManager, |
||||
$this->config, |
||||
$this->eventDispatcher, |
||||
$this->federatedInviteMapper, |
||||
$this->addressHandler, |
||||
$this->appConfig, |
||||
$this->cloudFederationFactory, |
||||
$this->cloudIdManager, |
||||
$this->signatureManager, |
||||
$this->signatoryManager, |
||||
$this->timeFactory, |
||||
); |
||||
} |
||||
|
||||
public function testInviteAccepted(): void { |
||||
$token = 'token'; |
||||
$userId = 'userId'; |
||||
$invite = new FederatedInvite(); |
||||
$invite->setCreatedAt(1); |
||||
$invite->setUserId($userId); |
||||
$invite->setToken($token); |
||||
|
||||
$this->federatedInviteMapper->expects(self::once()) |
||||
->method('findByToken') |
||||
->with($token) |
||||
->willReturn($invite); |
||||
|
||||
$this->federatedInviteMapper->expects(self::once()) |
||||
->method('update') |
||||
->willReturnArgument(0); |
||||
|
||||
$user = $this->createMock(IUser::class); |
||||
$user->method('getUID') |
||||
->willReturn($userId); |
||||
$user->method('getPrimaryEMailAddress') |
||||
->willReturn('email'); |
||||
$user->method('getDisplayName') |
||||
->willReturn('displayName'); |
||||
|
||||
$this->userManager->expects(self::once()) |
||||
->method('get') |
||||
->with($userId) |
||||
->willReturn($user); |
||||
|
||||
$recipientProvider = 'http://127.0.0.1'; |
||||
$recipientId = 'remote'; |
||||
$recipientEmail = 'remote@example.org'; |
||||
$recipientName = 'Remote Remoteson'; |
||||
$response = ['userID' => $userId, 'email' => 'email', 'name' => 'displayName']; |
||||
$json = new JSONResponse($response, Http::STATUS_OK); |
||||
|
||||
$this->assertEquals($json, $this->requestHandlerController->inviteAccepted($recipientProvider, $token, $recipientId, $recipientEmail, $recipientName)); |
||||
} |
||||
} |
@ -0,0 +1,60 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace OCP\OCM; |
||||
|
||||
/** |
||||
* Version 1.1 and 1.2 extensions to the Open Cloud Mesh Discovery API |
||||
* @link https://github.com/cs3org/OCM-API/ |
||||
* @since 32.0.0 |
||||
*/ |
||||
interface ICapabilityAwareOCMProvider extends IOCMProvider { |
||||
/** |
||||
* get the capabilities |
||||
* |
||||
* @return array |
||||
* @since 32.0.0 |
||||
*/ |
||||
public function getCapabilities(): array; |
||||
|
||||
/** |
||||
* get the provider name |
||||
* |
||||
* @return string |
||||
* @since 32.0.0 |
||||
*/ |
||||
public function getProvider(): string; |
||||
|
||||
/** |
||||
* returns the invite accept dialog |
||||
* |
||||
* @return string |
||||
* @since 32.0.0 |
||||
*/ |
||||
public function getInviteAcceptDialog(): string; |
||||
|
||||
/** |
||||
* set the capabilities |
||||
* |
||||
* @param array $capabilities |
||||
* |
||||
* @return $this |
||||
* @since 32.0.0 |
||||
*/ |
||||
public function setCapabilities(array $capabilities): static; |
||||
|
||||
/** |
||||
* set the invite accept dialog |
||||
* |
||||
* @param string $inviteAcceptDialog |
||||
* |
||||
* @return $this |
||||
* @since 32.0.0 |
||||
*/ |
||||
public function setInviteAcceptDialog(string $inviteAcceptDialog): static; |
||||
} |
Loading…
Reference in new issue