diff --git a/Makefile b/Makefile
index deab47a..16ddb5c 100644
--- a/Makefile
+++ b/Makefile
@@ -193,7 +193,6 @@ buildapp-tests:
ln -s ../ $(app_name) && \
tar cvzfh $(appstore_package_name).tar.gz \
--exclude="$(app_name)/build" \
- --exclude="$(app_name)/Makefile" \
--exclude="$(app_name)/*.log" \
--exclude="$(app_name)/js/node_modules" \
--exclude="$(app_name)/js/tests" \
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 02cb425..ed120de 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -8,85 +8,37 @@
* without the controller part, the stuff after the hash is the method.
* eg. page#index -> OCA\Invitation\Controller\PageController->index()
*
- *
+ *
* General endpoint syntax follows REST good practices:
* /resource/{resourceProperty}?param=value
- *
+ *
* Query parameter names are written camel case:
* eg. GET /remote-users?cloudId=jimmie@rd-1.nl@surf.nl
* Query filter, sorting, pagination, navigation parameter names are written snake case:
- * eg. _next,
- * _sort,
- * timestamp_after ('_after' filter parameter name appended to entity parameter name)
- *
+ * eg. _next,
+ * _sort,
+ * timestamp_after ('_after' filter parameter name appended to parameter (resource property) name)
+ *
*/
declare(strict_types=1);
return [
- 'resources' => [
- 'note' => ['url' => '/notes'],
- 'note_api' => ['url' => '/api/0.1/notes']
- ],
- 'ocs' => [
- ['root' => '/invitation', 'name' => 'ocs#find_by_token', 'url' => '/invitations/{token}', 'verb' => 'GET'],
- ],
'routes' => [
- // bespoke API - invitation
- // ['name' => 'invitation#index', 'url' => '/index', 'verb' => 'GET'],
- ['name' => 'invitation#find_by_token', 'url' => '/invitations/{token}', 'verb' => 'GET'],
- ['name' => 'invitation#find', 'url' => '/invitations', 'verb' => 'GET'],
+ ['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
- // unprotected endpoint /invitation
- ['name' => 'invitation#invitation', 'url' => '/invite/{token}', 'verb' => 'GET'],
- ['name' => 'invitation#invitation_form', 'url' => '/invitation-form', 'verb' => 'GET'],
- ['name' => 'invitation#generate_invite', 'url' => '/generate-invite', 'verb' => 'POST'],
- ['name' => 'invitation#handle_invite', 'url' => '/handle-invite', 'verb' => 'GET'],
- ['name' => 'invitation#accept_invite', 'url' => '/accept-invite/{token}', 'verb' => 'PUT'],
- ['name' => 'invitation#decline_invite', 'url' => '/decline-invite/{token}', 'verb' => 'PUT'],
- ['name' => 'invitation#update', 'url' => '/update-invitation', 'verb' => 'PUT'],
+ ['name' => 'invitation#get_by_token', 'url' => '/invitations/{token}', 'verb' => 'GET'],
+ ['name' => 'invitation#update', 'url' => '/invitations/{token}', 'verb' => 'PUT'],
+ ['name' => 'invitation#find', 'url' => '/invitations', 'verb' => 'GET'],
+ ['name' => 'invitation#generate_invite', 'url' => '/invitations', 'verb' => 'POST'],
- // bespoke API - remote user
- ['name' => 'remote_user#search', 'url' => '/remote-user/search', 'verb' => 'GET'],
- ['name' => 'remote_user#get_remote_user', 'url' => '/remote-user', 'verb' => 'GET'],
-
- // bespoke API - mesh registry
- ['name' => 'mesh_registry#forward_invite', 'url' => '/registry/forward-invite', 'verb' => 'GET'],
-
- // route '/registry/invitation-service-provider' concerns remote invitation service providers
- // returns the properties of the invitation service provider like endpoint, domain, name
+ // unprotected
['name' => 'mesh_registry#invitation_service_provider', 'url' => '/registry/invitation-service-provider', 'verb' => 'GET'],
- // adds a remote invitation service provider
- ['name' => 'mesh_registry#add_invitation_service_provider', 'url' => '/registry/invitation-service-provider', 'verb' => 'POST'],
- // update the properties of this invitation service provider
- ['name' => 'mesh_registry#update_invitation_service_provider', 'url' => '/registry/invitation-service-provider', 'verb' => 'PUT'],
- ['name' => 'mesh_registry#delete_invitation_service_provider', 'url' => '/registry/invitation-service-provider', 'verb' => 'DELETE'],
-
- // route '/registry/invitation-service-providers' returns all providers
- ['name' => 'mesh_registry#invitation_service_providers', 'url' => '/registry/invitation-service-providers', 'verb' => 'GET'],
-
- // route '/endpoint' of this instance
- ['name' => 'mesh_registry#get_endpoint', 'url' => '/registry/endpoint', 'verb' => 'GET'],
- ['name' => 'mesh_registry#set_endpoint', 'url' => '/registry/endpoint', 'verb' => 'PUT'],
-
- // route '/name' of this instance
- ['name' => 'mesh_registry#get_name', 'url' => '/registry/name', 'verb' => 'GET'],
- ['name' => 'mesh_registry#set_name', 'url' => '/registry/name', 'verb' => 'PUT'],
-
- // route '/share-with-invited-users-only' of this instance
- ['name' => 'mesh_registry#get_allow_sharing_with_invited_users_only', 'url' => '/share-with-invited-users-only', 'verb' => 'GET'],
- ['name' => 'mesh_registry#set_allow_sharing_with_invited_users_only', 'url' => '/share-with-invited-users-only', 'verb' => 'PUT'],
+ // unprotected
+ ['name' => 'mesh_registry#get_name', 'url' => '/registry/name', 'verb' => 'GET'],
// OCM - Open Cloud Mesh protocol
- ['name' => 'ocm#invite_accepted', 'url' => '/ocm/invite-accepted', 'verb' => 'POST'],
-
- // miscellaneous endpoints
- ['name' => 'page#wayf', 'url' => '/page/wayf', 'verb' => 'GET'],
- ['name' => 'error#invitation', 'url' => 'error/invitation', 'verb' => 'GET'],
-
- [
- 'name' => 'note_api#preflighted_cors', 'url' => '/api/0.1/{path}',
- 'verb' => 'OPTIONS', 'requirements' => ['path' => '.+']
- ]
+ // unprotected
+ ['name' => 'ocm#invite_accepted', 'url' => '/ocm/invite-accepted', 'verb' => 'POST'],
]
];
diff --git a/composer.json b/composer.json
index 6e3a36f..bc4c02a 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,6 @@
{
"name": "nextcloud/invitation",
- "description": "Enhanced federated sharing between Nextcloud instances.",
+ "description": "Enhanced federated sharing between EFSS instances.",
"type": "project",
"license": "AGPL-3.0-or-later",
"authors": [
@@ -8,6 +8,9 @@
"name": "Antoon Prins"
}
],
+ "require": {
+ "ramsey/uuid": "4.2.3"
+ },
"require-dev": {
"phpunit/phpunit": "^9",
"sabre/dav": "^4.1",
diff --git a/img/app.svg b/img/app.svg
new file mode 100644
index 0000000..f3e08e1
--- /dev/null
+++ b/img/app.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 19bed00..a104114 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -18,6 +18,8 @@ class Application extends App implements IBootstrap
public const CONFIG_ALLOW_SHARING_WITH_INVITED_USERS_ONLY = 'allow_sharing_with_invited_users_only';
+ public const INVITATION_EMAIL_SUBJECT = 'INVITATION_EMAIL_SUBJECT';
+
public function __construct()
{
parent::__construct(self::APP_ID);
diff --git a/lib/Controller/Errors.php b/lib/Controller/Errors.php
new file mode 100644
index 0000000..a328067
--- /dev/null
+++ b/lib/Controller/Errors.php
@@ -0,0 +1,26 @@
+
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace OCA\Invitation\Controller;
+
+use Closure;
+use OCA\Invitation\Service\NoteNotFound;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+
+trait Errors
+{
+ protected function handleNotFound(Closure $callback): DataResponse
+ {
+ try {
+ return new DataResponse($callback());
+ } catch (NoteNotFound $e) {
+ $message = ['message' => $e->getMessage()];
+ return new DataResponse($message, Http::STATUS_NOT_FOUND);
+ }
+ }
+}
diff --git a/lib/Controller/InvitationController.php b/lib/Controller/InvitationController.php
index b0bb0b1..ce6ef5a 100644
--- a/lib/Controller/InvitationController.php
+++ b/lib/Controller/InvitationController.php
@@ -12,119 +12,128 @@
use OCA\Invitation\AppInfo\Application;
use OCA\Invitation\Db\Schema;
use OCA\Invitation\Federation\Invitation;
+use OCA\Invitation\HttpClient;
+use OCA\Invitation\Service\ApplicationConfigurationException;
use OCA\Invitation\Service\InvitationService;
use OCA\Invitation\Service\MeshRegistry\MeshRegistryService;
use OCA\Invitation\Service\NotFoundException;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
-use OCP\ILogger;
+use OCP\IAppConfig;
+use OCP\IConfig;
+use OCP\IL10N;
use OCP\IRequest;
+use OCP\IUserSession;
+use OCP\Template;
+use OCP\Util;
use Psr\Log\LoggerInterface;
+use Ramsey\Uuid\Uuid;
class InvitationController extends Controller
{
- private InvitationService $service;
+ private IConfig $systemConfig;
+ private IAppConfig $appConfig;
+ private IUserSession $session;
+ private InvitationService $invitationService;
+ private MeshRegistryService $meshRegistryService;
+ private IL10N $il10n;
private LoggerInterface $logger;
public function __construct(
IRequest $request,
- InvitationService $service,
+ IConfig $systemConfig,
+ IAppConfig $appConfig,
+ IUserSession $session,
+ InvitationService $invitationService,
+ MeshRegistryService $meshRegistryService,
+ IL10N $il10n,
LoggerInterface $logger
) {
parent::__construct(Application::APP_ID, $request);
- $this->service = $service;
+ $this->systemConfig = $systemConfig;
+ $this->appConfig = $appConfig;
+ $this->session = $session;
+ $this->invitationService = $invitationService;
+ $this->meshRegistryService = $meshRegistryService;
+ $this->il10n = $il10n;
$this->logger = $logger;
}
- // /**
- // *
- // * @NoAdminRequired
- // * @NoCSRFRequired
- // * @return TemplateResponse
- // */
- // public function index(): TemplateResponse
- // {
- // return new TemplateResponse($this->appName, 'invitation.main');
- // }
-
/**
- * Removes the notification that is associated with the invitation with specified token.
+ * Route: GET /invitation/{token}
*
- * @param string $token
- * @return void
+ * @NoAdminRequired
+ * @NoCSRFRequired
*/
- private function removeInvitationNotification(string $token): void
+ public function getByToken(string $token = null): DataResponse
{
- $this->logger->debug(" - removing notification for invitation with token '$token'");
+ if (!isset($token)) {
+ return new DataResponse(
+ [
+ 'message' => AppError::REQUEST_MISSING_PARAMETER,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
try {
- $manager = \OC::$server->getNotificationManager();
- $notification = $manager->createNotification();
- $notification
- ->setApp(Application::APP_ID)
- ->setUser(\OC::$server->getUserSession()->getUser()->getUID())
- ->setObject(MeshRegistryService::PARAM_NAME_TOKEN, $token);
- $manager->markProcessed($notification);
+ $invitation = $this->invitationService->getByToken($token);
+ return new DataResponse(
+ [
+ 'data' => $invitation->jsonSerialize(),
+ ],
+ Http::STATUS_OK,
+ );
+ } catch (NotFoundException $e) {
+ $this->logger->error("invitation not found for token '$token'. Error: " . $e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::INVITATION_NOT_FOUND,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
} catch (Exception $e) {
- $this->logger->error('Remove notification failed: ' . $e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
- throw $e;
+ $this->logger->error("invitation not found for token '$token'. Error: " . $e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
}
}
/**
- * Verify the /invite-accepted response for all required fields.
+ * Route: GET /invitations
+ * Example:
+ * https://rd-1.nl/apps/invitation/invitations?status=open|accepted
+ * Available parameter value operators:
+ * | or
+ * If multiple parameters are specified they all must apply (logical AND).
*
- * @param array $response the response to verify
- * @return bool true if the response is valid, false otherwise
- */
- private function verifiedInviteAcceptedResponse(array $response): bool
- {
- if (!isset($response) || $response[MeshRegistryService::PARAM_NAME_USER_ID] == '') {
- $this->logger->error('/invite-accepted response does not contain the user id of the sender of the invitation.');
- return false;
- }
- if (!isset($response[MeshRegistryService::PARAM_NAME_EMAIL]) || $response[MeshRegistryService::PARAM_NAME_EMAIL] == '') {
- $this->logger->error('/invite-accepted response does not contain the email of the sender of the invitation.');
- return false;
- }
- if (!isset($response[MeshRegistryService::PARAM_NAME_NAME]) || $response[MeshRegistryService::PARAM_NAME_NAME] == '') {
- $this->logger->error('/invite-accepted response does not contain the name of the sender of the invitation.');
- return false;
- }
- return true;
- }
-
- /**
- * example url: https://rd-1.nl/apps/invitation/invitations?status=open,accepted
- *
- * @PublicPage
* @NoAdminRequired
* @NoCSRFRequired
*/
- public function find(string $status = null): DataResponse
+ public function find(string $status = ""): DataResponse
{
try {
$fieldsAndValues = [];
- if (isset($status)) {
- // status param uses the OR operator
+ if ($status != "") {
$fieldsAndValues['status'] = explode('|', $status);
}
if (empty($fieldsAndValues)) {
- $this->logger->error("findAll() - missing query parameter.", ['app' => Application::APP_ID]);
return new DataResponse(
[
- 'success' => false,
- 'error_message' => AppError::REQUEST_MISSING_PARAMETER,
+ 'message' => AppError::REQUEST_MISSING_PARAMETER,
],
Http::STATUS_NOT_FOUND,
);
}
-
- $invitations = $this->service->findAll($fieldsAndValues);
+
+ $invitations = $this->invitationService->findAll($fieldsAndValues);
return new DataResponse(
[
- 'success' => true,
'data' => $invitations,
],
Http::STATUS_OK
@@ -133,8 +142,7 @@ public function find(string $status = null): DataResponse
$this->logger->error('invitations not found for fields: ' . print_r($status, true) . 'Error: ' . $e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
return new DataResponse(
[
- 'success' => false,
- 'error_message' => AppError::ERROR,
+ 'message' => AppError::ERROR,
],
Http::STATUS_NOT_FOUND,
);
@@ -142,46 +150,328 @@ public function find(string $status = null): DataResponse
}
/**
+ * Generates an invite and sends it to the specified email address.
*
* @NoAdminRequired
* @NoCSRFRequired
+ * @param string $email the email address to send the invite to
+ * @param string $recipientName the name of the recipient
+ * @param string $senderName the name of the sender
+ * @param string $message the message for the receiver
+ * @return DataResponse the result
*/
- public function findByToken(string $token = null): DataResponse
+ public function generateInvite(string $email = "", string $recipientName = "", string $senderName = "", string $message = ""): DataResponse
{
- if (!isset($token)) {
- $this->logger->error("findByToken() - missing parameter 'token'.", ['app' => Application::APP_ID]);
+ if ("" == $email) {
return new DataResponse(
[
- 'success' => false,
- 'error_message' => AppError::REQUEST_MISSING_PARAMETER,
+ 'message' => AppError::CREATE_INVITATION_NO_RECIPIENT_EMAIL,
],
- Http::STATUS_NOT_FOUND,
+ Http::STATUS_NOT_FOUND
+ );
+ }
+ if ("" == $recipientName) {
+ return new DataResponse(
+ [
+ 'message' => AppError::CREATE_INVITATION_NO_RECIPIENT_NAME,
+ ],
+ Http::STATUS_NOT_FOUND
);
}
+ if ("" == $senderName) {
+ return new DataResponse(
+ [
+ 'message' => AppError::CREATE_INVITATION_NO_SENDER_NAME,
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ return new DataResponse(
+ [
+ 'message' => AppError::CREATE_INVITATION_EMAIL_INVALID,
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ // check pre-conditions
+ $preConditionFailed = $this->generateInvitePreCondition();
+ if ($preConditionFailed->getStatus() != Http::STATUS_OK) {
+ return $preConditionFailed;
+ }
+
+ if ($email === $this->session->getUser()->getEMailAddress()) {
+ return new DataResponse(
+ [
+ 'message' => AppError::CREATE_INVITATION_EMAIL_IS_OWN_EMAIL,
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ $inviteLink = '';
try {
- $invitation = $this->service->findByToken($token);
+ // generate the token
+ $token = Uuid::uuid4()->toString();
+
+ $params = [
+ MeshRegistryService::PARAM_NAME_TOKEN => $token,
+ MeshRegistryService::PARAM_NAME_PROVIDER_ENDPOINT => $this->meshRegistryService->getInvitationServiceProvider()->getEndpoint(),
+ ];
+
+ // Check for existing open and accepted invitations for the same recipient email
+ // Note that accepted invitations might have another recipient's email set, so there might still already be an existing invitation
+ // but this will be dealt with upon acceptance of this new invitation
+ $fieldsAndValues = [
+ Schema::VINVITATION_STATUS => [Invitation::STATUS_OPEN, Invitation::STATUS_ACCEPTED],
+ Schema::VINVITATION_REMOTE_USER_EMAIL => [$email]
+ ];
+
+ $invitations = $this->invitationService->findAll($fieldsAndValues);
+ if (count($invitations) > 0) {
+ return new DataResponse(
+ [
+ "message" => AppError::CREATE_INVITATION_EXISTS,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+
+ $inviteLink = $this->meshRegistryService->inviteLink($params);
+ } catch (ApplicationConfigurationException $e) {
+ $this->logger->error("An error has occurred: " . $e->getMessage() . " Stacktrace: " . $e->getTraceAsString(), ['app' => Application::APP_ID]);
return new DataResponse(
[
- 'success' => true,
- 'data' => $invitation->jsonSerialize(),
+ 'message' => AppError::APPLICATION_CONFIGURATION_EXCEPTION,
],
- Http::STATUS_OK,
+ Http::STATUS_NOT_FOUND,
);
- } catch (NotFoundException $e) {
- $this->logger->error("invitation not found for token '$token'. Error: " . $e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ } catch (Exception $e) {
+ $this->logger->error("An error has occurred: " . $e->getMessage() . " Stacktrace: " . $e->getTraceAsString(), ['app' => Application::APP_ID]);
return new DataResponse(
[
- 'success' => false,
- 'error_message' => AppError::INVITATION_NOT_FOUND,
+ 'message' => AppError::CREATE_INVITATION_ERROR,
],
Http::STATUS_NOT_FOUND,
);
+ }
+
+ // persist the invite to send
+ $invitation = new Invitation();
+ $invitation->setUserCloudId($this->session->getUser()->getCloudId());
+ $invitation->setToken($token);
+ $invitation->setProviderEndpoint($this->meshRegistryService->getInvitationServiceProvider()->getEndpoint());
+ $invitation->setSenderCloudId($this->session->getUser()->getCloudId());
+ $invitation->setSenderEmail($this->session->getUser()->getEMailAddress());
+ $invitation->setSenderName($senderName);
+ $invitation->setRecipientEmail($email);
+ $invitation->setTimestamp(time());
+ $invitation->setStatus(Invitation::STATUS_NEW);
+
+ try {
+ $mailer = \OC::$server->getMailer();
+ $mail = $mailer->createMessage();
+ $mail->setSubject($this->il10n->t(Application::INVITATION_EMAIL_SUBJECT));
+ $mail->setFrom([$this->getEmailFromAddress('invitation-no-reply')]);
+ $mail->setTo(array($email => $email));
+ $language = 'en'; // actually not used, the email itself is multi language
+ $htmlText = $this->getMailBody($inviteLink, $recipientName, $message, 'html', $language);
+ $mail->setHtmlBody($htmlText);
+ $plainText = $this->getMailBody($inviteLink, $recipientName, $message, 'text', $language);
+ $mail->setPlainBody($plainText);
+ $failedRecipients = $mailer->send($mail);
+ if (sizeof($failedRecipients) > 0) {
+ $this->logger->error(' - failed recipients: ' . print_r($failedRecipients, true), ['app' => Application::APP_ID]);
+ }
} catch (Exception $e) {
- $this->logger->error("invitation not found for token '$token'. Error: " . $e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ // TODO Instead of failing, we could continue and still insert and display the invitation as failed in the list
+ // this would probably work best with a modify and resend option
+
+ // So just continue for now
+ }
+
+ // when all's well set status to open and persist
+ $invitation->setStatus(Invitation::STATUS_OPEN);
+ try {
+ $newInvitation = $this->invitationService->insert($invitation);
+ $this->logger->debug(print_r($newInvitation, true));
+ } catch (Exception $e) {
+ $this->logger->error('An error occurred while generating the invite: ' . $e->getMessage(), ['app' => Application::APP_ID]);
return new DataResponse(
[
- 'success' => false,
- 'error_message' => AppError::ERROR,
+ 'message' => AppError::CREATE_INVITATION_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ if (isset($newInvitation) && $invitation->getId() > 0) {
+ return new DataResponse(
+ [
+ 'data' => [
+ 'token' => $newInvitation->getToken(),
+ 'inviteLink' => $inviteLink,
+ 'email' => $email,
+ 'recipientName' => $recipientName,
+ 'senderName' => $senderName,
+ 'message' => $message,
+ ],
+ ],
+ Http::STATUS_OK
+ );
+ }
+ $this->logger->error("Create invitation failed with no further info.", ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::CREATE_INVITATION_ERROR
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ /**
+ * Accept the received invitation with the specified token.
+ *
+ * @param string $token the token
+ * @return DataResponse if successfull, echoes the invitation token
+ */
+ private function acceptInvite(string $token = ''): DataResponse
+ {
+ try {
+ if ($token == '') {
+ $this->logger->error('acceptInvite: missing parameter token.', ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::REQUEST_MISSING_PARAMETER
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ $invitation = null;
+ try {
+ $invitation = $this->invitationService->getByToken($token);
+ } catch (NotFoundException $e) {
+ $this->logger->error("acceptInvite: invitation not found for token '$token'", ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::INVITATION_NOT_FOUND
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ // check pre-conditions
+ $preConditionFailed = $this->acceptInvitePreCondition();
+ if ($preConditionFailed->getStatus() != Http::STATUS_OK) {
+ return $preConditionFailed;
+ }
+
+ $recipientEndpoint = $this->meshRegistryService->getEndpoint();
+ $recipientCloudID = $this->session->getUser()->getCloudId();
+ $recipientEmail = $this->session->getUser()->getEMailAddress();
+ $recipientName = $this->session->getUser()->getDisplayName();
+ $params = [
+ MeshRegistryService::PARAM_NAME_RECIPIENT_PROVIDER => $recipientEndpoint,
+ MeshRegistryService::PARAM_NAME_TOKEN => $token,
+ MeshRegistryService::PARAM_NAME_USER_ID => $recipientCloudID,
+ MeshRegistryService::PARAM_NAME_EMAIL => $recipientEmail,
+ MeshRegistryService::PARAM_NAME_NAME => $recipientName,
+ ];
+
+ $url = $this->meshRegistryService->getFullInviteAcceptedEndpointURL($invitation->getProviderEndpoint());
+ $httpClient = new HttpClient($this->logger);
+ $response = $httpClient->curlPost($url, $params);
+
+ if (isset($response['success']) && $response['success'] == false) {
+ $this->logger->error('Failed to accept the invitation: /invite-accepted failed with response: ' . print_r($response, true), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => (isset($response['error_message']) ? $response['error_message'] : AppError::HANDLE_INVITATION_ERROR)
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+ // note: beware of the format of response of the OCM call, it has no 'data' field
+ if ($this->verifiedInviteAcceptedResponse($response) == false) {
+ $this->logger->error('Failed to accept the invitation - returned fields not valid: ' . print_r($response, true), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::HANDLE_INVITATION_OCM_INVITE_ACCEPTED_RESPONSE_FIELDS_INVALID
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ // withdraw any previous accepted invitation from the same inviter
+ $existingInvitationsReceived = $this->invitationService->findAll([
+ [Schema::VINVITATION_SENDER_CLOUD_ID => $response[MeshRegistryService::PARAM_NAME_USER_ID]],
+ [Schema::VINVITATION_RECIPIENT_CLOUD_ID => $recipientCloudID],
+ [Schema::VINVITATION_STATUS => Invitation::STATUS_ACCEPTED],
+ ]);
+ $existingInvitationsSent = $this->invitationService->findAll([
+ [Schema::VINVITATION_RECIPIENT_CLOUD_ID => $response[MeshRegistryService::PARAM_NAME_USER_ID]],
+ [Schema::VINVITATION_SENDER_CLOUD_ID => $recipientCloudID],
+ [Schema::VINVITATION_STATUS => Invitation::STATUS_ACCEPTED],
+ ]);
+ $existingInvitations = array_merge($existingInvitationsReceived, $existingInvitationsSent);
+ if (count($existingInvitations) > 0) {
+ foreach ($existingInvitations as $existingInvitation) {
+ $this->logger->debug("A previous invitation for remote user with name " . $response[MeshRegistryService::PARAM_NAME_NAME] . " was accepted already. Withdrawing that one", ['app' => Application::APP_ID]);
+ $updateResult = $this->invitationService->update([
+ Schema::INVITATION_TOKEN => $existingInvitation->getToken(),
+ Schema::INVITATION_STATUS => Invitation::STATUS_WITHDRAWN,
+ ]);
+ if ($updateResult == false) {
+ return new DataResponse(
+ [
+ 'message' => AppError::ACCEPT_INVITE_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ }
+ }
+
+ // all's well, update the open invitation
+ $updateResult = $this->invitationService->update(
+ [
+ Schema::INVITATION_TOKEN => $invitation->getToken(),
+ Schema::INVITATION_SENDER_CLOUD_ID => $response[MeshRegistryService::PARAM_NAME_USER_ID],
+ Schema::INVITATION_SENDER_EMAIL => $response[MeshRegistryService::PARAM_NAME_EMAIL],
+ Schema::INVITATION_SENDER_NAME => $response[MeshRegistryService::PARAM_NAME_NAME],
+ Schema::INVITATION_STATUS => Invitation::STATUS_ACCEPTED,
+ ],
+ true
+ );
+ if ($updateResult == false) {
+ $this->logger->error("Failed to handle /accept-invite (invitation with token '$token' could not be updated).", ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::ACCEPT_INVITE_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ $this->removeInvitationNotification($token);
+
+ return new DataResponse(
+ [
+ 'data' => [
+ "token" => $token,
+ "status" => Invitation::STATUS_ACCEPTED
+ ],
+ ],
+ Http::STATUS_OK
+ );
+ } catch (Exception $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app]' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::ACCEPT_INVITE_ERROR,
],
Http::STATUS_NOT_FOUND,
);
@@ -203,14 +493,17 @@ public function update(string $token, string $status): DataResponse
if (!isset($token) && !isset($status)) {
return new DataResponse(
[
- 'success' => false,
- 'error_message' => AppError::UPDATE_INVITATION_ERROR
+ 'message' => AppError::UPDATE_INVITATION_ERROR
],
Http::STATUS_NOT_FOUND,
);
}
- $result = $this->service->update([
+ if (Invitation::STATUS_ACCEPTED === $status) {
+ return $this->acceptInvite($token);
+ }
+
+ $result = $this->invitationService->update([
Schema::INVITATION_TOKEN => $token,
Schema::INVITATION_STATUS => $status,
]);
@@ -226,18 +519,154 @@ public function update(string $token, string $status): DataResponse
if ($result === true) {
return new DataResponse(
[
- 'success' => true,
- 'data' => $result,
+ 'data' => [
+ "token" => $token,
+ "status" => $status
+ ],
],
Http::STATUS_OK,
);
}
return new DataResponse(
[
- 'success' => false,
- 'error_message' => AppError::UPDATE_INVITATION_ERROR
+ 'message' => AppError::UPDATE_INVITATION_ERROR
],
Http::STATUS_NOT_FOUND,
);
}
+
+ /**
+ * Returns a DataResponse with an error why the precondition failed,
+ * or null when it hasn't.
+ */
+ private function generateInvitePreCondition(): DataResponse
+ {
+ $_userEmail = $this->session->getUser()->getEMailAddress();
+ if (!isset($_userEmail) || $_userEmail === '') {
+ return new DataResponse(
+ [
+ 'message' => AppError::CREATE_INVITATION_ERROR_SENDER_EMAIL_MISSING,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ return new DataResponse(
+ [
+ 'success' => true,
+ ],
+ Http::STATUS_OK,
+ );
+ }
+
+ /**
+ * Get the email from address.
+ * Can be explicitly set using system config: 'invitation_mail_from_address'.
+ * Otherwise uses the default config which uses the optional system config 'mail_from_address' and 'mail_domain' keys.
+ *
+ * @param string $address the address part in 'address@maildomain.com'
+ * @return string
+ */
+ private function getEmailFromAddress(string $address = null)
+ {
+ if (empty($address)) {
+ $address = 'no-reply';
+ }
+ $senderAddress = Util::getDefaultEmailAddress($address);
+ return $this->systemConfig->getSystemValue('invitation_mail_from_address', $senderAddress);
+ }
+
+ /**
+ * Returns the mail body rendered according to the specified target template.
+ * @param string $inviteLink the invite link
+ * @param string $recipientName the name of the recipient
+ * @param string $message additional message to render
+ * @param string $targetTemplate on of 'html', 'text'
+ * @param string $languageCode the language code to use
+ * @return string the rendered body
+ */
+ private function getMailBody(string $inviteLink, string $recipientName, string $message, string $targetTemplate = 'html', string $languageCode = '')
+ {
+ $tmpl = new Template('invitation', "mail/$targetTemplate", '', false, $languageCode);
+ $tmpl->assign('recipientName', $recipientName);
+ $tmpl->assign('fromName', $this->session->getUser()->getDisplayName());
+ $tmpl->assign('inviteLink', $inviteLink);
+ $tmpl->assign('message', $message);
+ return $tmpl->fetchPage();
+ }
+
+ /**
+ * Removes the notification that is associated with the invitation with specified token.
+ *
+ * @param string $token
+ * @return void
+ */
+ private function removeInvitationNotification(string $token): void
+ {
+ $this->logger->debug(" - removing notification for invitation with token '$token'");
+ try {
+ $manager = \OC::$server->getNotificationManager();
+ $notification = $manager->createNotification();
+ $notification
+ ->setApp(Application::APP_ID)
+ ->setUser($this->session->getUser()->getUID())
+ ->setObject(MeshRegistryService::PARAM_NAME_TOKEN, $token);
+ $manager->markProcessed($notification);
+ } catch (Exception $e) {
+ $this->logger->error('Remove notification failed: ' . $e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => InvitationApp::APP_NAME]);
+ throw $e;
+ }
+ }
+
+ /**
+ * Returns a DataResponse with an error why the precondition failed,
+ * or null when it hasn't.
+ */
+ private function acceptInvitePreCondition(): DataResponse
+ {
+ $_userEmail = $this->session->getUser()->getEMailAddress();
+ if (!isset($_userEmail) || $_userEmail === '') {
+ return new DataResponse(
+ [
+ 'message' => AppError::ACCEPT_INVITE_ERROR_RECIPIENT_EMAIL_MISSING,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ $_userName = $this->session->getUser()->getDisplayName();
+ if (!isset($_userName) || $_userName === '') {
+ return new DataResponse(
+ [
+ 'message' => AppError::ACCEPT_INVITE_ERROR_RECIPIENT_NAME_MISSING,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ return new DataResponse(
+ [],
+ Http::STATUS_OK,
+ );
+ }
+
+ /**
+ * Verify the /invite-accepted response for all required fields.
+ *
+ * @param array $response the response to verify
+ * @return bool true if the response is valid, false otherwise
+ */
+ private function verifiedInviteAcceptedResponse(array $response): bool
+ {
+ if (!isset($response) || $response[MeshRegistryService::PARAM_NAME_USER_ID] == '') {
+ $this->logger->error('/invite-accepted response does not contain the user id of the sender of the invitation.');
+ return false;
+ }
+ if (!isset($response[MeshRegistryService::PARAM_NAME_EMAIL]) || $response[MeshRegistryService::PARAM_NAME_EMAIL] == '') {
+ $this->logger->error('/invite-accepted response does not contain the email of the sender of the invitation.');
+ return false;
+ }
+ if (!isset($response[MeshRegistryService::PARAM_NAME_NAME]) || $response[MeshRegistryService::PARAM_NAME_NAME] == '') {
+ $this->logger->error('/invite-accepted response does not contain the name of the sender of the invitation.');
+ return false;
+ }
+ return true;
+ }
}
diff --git a/lib/Controller/MeshRegistryController.php b/lib/Controller/MeshRegistryController.php
new file mode 100644
index 0000000..e58db61
--- /dev/null
+++ b/lib/Controller/MeshRegistryController.php
@@ -0,0 +1,521 @@
+meshRegistryService = $meshRegistryService;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Provides the caller with a list (WAYF page) of mesh EFSS invitation service providers to choose from.
+
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * @param string $token the token
+ * @param string $providerEndpoint the endpoint of the sender
+ * @param string $name the name of the sender
+ * @return Response
+ */
+ public function forwardInvite(string $token = '', string $providerEndpoint = ''): Response
+ {
+ $urlGenerator = \OC::$server->getURLGenerator();
+
+ if ($token == '') {
+ $this->logger->error('Invite is missing the token.', ['app' => Application::APP_ID]);
+ return new RedirectResponse(
+ $urlGenerator->linkToRoute(
+ Application::APP_ID . '.error.invitation',
+ [
+ 'message' => AppError::HANDLE_INVITATION_MISSING_TOKEN
+ ]
+ )
+ );
+ }
+ if ($providerEndpoint == '') {
+ $this->logger->error('Invite is missing the invitation service provider endpoint.', ['app' => Application::APP_ID]);
+ return new RedirectResponse(
+ $urlGenerator->linkToRoute(
+ Application::APP_ID . '.error.invitation',
+ [
+ 'message' => AppError::HANDLE_INVITATION_MISSING_PROVIDER_ENDPOINT
+ ]
+ )
+ );
+ }
+ if (!$this->meshRegistryService->isKnowInvitationServiceProvider($providerEndpoint)) {
+ $this->logger->error("Invitation service provider endpoint '$providerEndpoint' is unknown.", ['app' => Application::APP_ID]);
+ return new RedirectResponse(
+ $urlGenerator->linkToRoute(
+ Application::APP_ID . '.error.invitation',
+ [
+ 'message' => AppError::HANDLE_INVITATION_PROVIDER_UNKNOWN
+ ]
+ )
+ );
+ }
+
+ $urlGenerator = \OC::$server->getURLGenerator();
+ $params = [
+ MeshRegistryService::PARAM_NAME_TOKEN => $token,
+ MeshRegistryService::PARAM_NAME_PROVIDER_ENDPOINT => $providerEndpoint,
+ ];
+ return new RedirectResponse(
+ $urlGenerator->linkToRoute($this->meshRegistryService->getWayfPageRoute(), $params)
+ );
+ }
+
+ /**
+ * Returns the properties of the this invitation service provider.
+ *
+ * @PublicPage
+ * @NoCSRFRequired
+ *
+ * @return DataResponse ['data' => :InvitationServiceProvider]
+ */
+ public function invitationServiceProvider(): DataResponse
+ {
+ try {
+ return new DataResponse(
+ [
+ 'data' => $this->meshRegistryService->getInvitationServiceProvider()->jsonSerialize(),
+ ],
+ Http::STATUS_OK,
+ );
+ } catch (NotFoundException $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_GET_PROVIDER_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ }
+
+ /**
+ * Updates this instance's invitation service provider properties.
+ *
+ * @NoCSRFRequired
+ *
+ * @param string $endpoint
+ * @param string $name
+ * @return DataResponse
+ */
+ public function updateInvitationServiceProvider(string $endpoint, string $name): DataResponse
+ {
+ try {
+ $fieldsArray = ['endpoint' => $endpoint, 'name' => $name];
+
+ $endpoint = "";
+ try {
+ $endpoint = $this->meshRegistryService->getEndpoint();
+ } catch (ApplicationConfigurationException $e) {
+ // no endpoint yet, this is the initialization of this instances provider
+ }
+
+ // check the endpoint connection
+ $url = $this->meshRegistryService->getFullInvitationServiceProviderEndpointUrl($fieldsArray['endpoint']);
+ $httpClient = new HttpClient($this->logger);
+ $response = $httpClient->curlGet($url);
+ if ($response['success'] == false) {
+ $this->logger->error('Failed to call ' . MeshRegistryService::ENDPOINT_INVITATION_SERVICE_PROVIDER . " on endpoint '$endpoint'. Response: " . print_r($response, true), ['app' => Application::APP_ID]);
+ throw new ServiceException("Failed to call endpoint '$endpoint'");
+ }
+
+ $isp = $this->meshRegistryService->updateInvitationServiceProvider($endpoint, $fieldsArray);
+
+ return new DataResponse(
+ [
+ 'data' => $isp->jsonSerialize(),
+ ],
+ Http::STATUS_OK,
+ );
+ } catch (Exception $e) {
+ $this->logger->error($e->getMessage() . " Trace: " . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_SET_ENDPOINT_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ }
+
+ /**
+ * Returns all registered invitation service providers.
+ *
+ * @NoCSRFRequired
+ * @PublicPage
+ * @return DataResponse ['data' => [:InvitationServiceProvider](an array of InvitationServiceProvider objects)]
+ */
+ public function invitationServiceProviders(): DataResponse
+ {
+ try {
+ $providers = $this->meshRegistryService->allInvitationServiceProviders();
+ return new DataResponse(
+ [
+ 'data' => $providers,
+ ],
+ Http::STATUS_OK,
+ );
+ } catch (ServiceException $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_ALL_PROVIDERS_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ }
+
+ /**
+ * Adds a new invitation service provider with the specified endpoint.
+ * The properties of the provider will be requested through the specified endpoint.
+ * If this fails an HTTP error will be returned.
+ *
+ * Note: if the provider already exists it's properties will become updated
+ * through the remote provider /registry/invitation-service-provider call.
+ *
+ * @NoCSRFRequired
+ *
+ * @param string $endpoint the endpoint of the new invitation service provider
+ * @return DataResponse [ ..., 'data' => :InvitationServiceProvider ]
+ */
+ public function addInvitationServiceProvider(string $endpoint): DataResponse
+ {
+ try {
+ // some sanitizing
+ $endpoint = trim(trim($endpoint), '/');
+
+ if ($endpoint === $this->meshRegistryService->getEndpoint()) {
+ return new DataResponse(
+ [
+ 'message' => AppError::SETTINGS_ADD_PROVIDER_IS_NOT_REMOTE_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+
+ // check whether the provider is not already registered
+ try {
+ $provider = $this->meshRegistryService->findInvitationServiceProvider($endpoint);
+ $this->logger->error("The provider with endpoint $endpoint is already registered", ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_ADD_PROVIDER_EXISTS_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ } catch (NotFoundException $e) {
+ // all good
+ }
+
+ $url = $this->meshRegistryService->getFullInvitationServiceProviderEndpointUrl($endpoint);
+ $httpClient = new HttpClient($this->logger);
+ $response = $httpClient->curlGet($url);
+ if ($response['success'] == false) {
+ $this->logger->error('Failed to call ' . MeshRegistryService::ENDPOINT_INVITATION_SERVICE_PROVIDER . " on endpoint '$endpoint'. Response: " . print_r($response, true), ['app' => Application::APP_ID]);
+ throw new ServiceException("Failed to call endpoint '$endpoint'");
+ }
+
+ $data = (array)$response['data'];
+ $verified = $this->verifyInvitationServiceProviderResponse($data);
+ $this->logger->debug(print_r($data, true));
+ if ($verified === true) {
+ $invitationServiceProvider = new InvitationServiceProvider();
+ $invitationServiceProvider->setEndpoint($data[Schema::INVITATION_SERVICE_PROVIDER_ENDPOINT]);
+ $invitationServiceProvider->setDomain($data[Schema::INVITATION_SERVICE_PROVIDER_DOMAIN]);
+ $invitationServiceProvider->setName($data[Schema::INVITATION_SERVICE_PROVIDER_NAME]);
+
+ $invitationServiceProvider = $this->meshRegistryService->addInvitationServiceProvider($invitationServiceProvider);
+
+ return new DataResponse(
+ [
+ 'data' => $invitationServiceProvider->jsonSerialize(),
+ ]
+ );
+ }
+
+ throw new ServiceException(AppError::MESH_REGISTRY_ENDPOINT_INVITATION_SERVICE_PROVIDER_RESPONSE_INVALID);
+ } catch (ServiceException $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ // try to delete the previously inserted new provider
+ $this->meshRegistryService->deleteInvitationServiceProvider($endpoint);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_ADD_PROVIDER_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ } catch (Exception $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ // final effort trying to delete the previously inserted new provider
+ $this->meshRegistryService->deleteInvitationServiceProvider($endpoint);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_ADD_PROVIDER_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ }
+
+ /**
+ * Validates the service provider response fields
+ *
+ * @param array $params
+ * @return bool true if validated, false otherwise
+ */
+ private function verifyInvitationServiceProviderResponse(array $params): bool
+ {
+ if (
+ is_array($params)
+ && isset($params[Schema::INVITATION_SERVICE_PROVIDER_ENDPOINT])
+ && isset($params[Schema::INVITATION_SERVICE_PROVIDER_DOMAIN])
+ && isset($params[Schema::INVITATION_SERVICE_PROVIDER_NAME])
+ ) {
+ return true;
+ }
+ $this->logger->error('Could not validate the response fields. Fields: ' . print_r($params, true), ['app' => Application::APP_ID]);
+ return false;
+ }
+
+ /**
+ * Deletes the invitation service provider with the specified endpoint.
+ *
+ * @NoCSRFRequired
+ *
+ * @param string $endpoint the endpoint of the invitation service provider to delete
+ * @return DataResponse if successfull, echoes the endpoint of the deleted service provider
+ */
+ public function deleteInvitationServiceProvider(string $endpoint): DataResponse
+ {
+ try {
+ $invitationServiceProvider = $this->meshRegistryService->deleteInvitationServiceProvider($endpoint);
+ return new DataResponse(
+ [
+ 'data' => $endpoint
+ ],
+ Http::STATUS_OK,
+ );
+ } catch (ServiceException $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_DELETE_PROVIDER_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ }
+
+ /**
+ * Whether only sharing with invited users is allowed.
+ *
+ * @NoCSRFRequired
+ *
+ * @param bool $allow
+ * @return DataResponse
+ */
+ public function setAllowSharingWithInvitedUsersOnly(bool $allow): DataResponse
+ {
+ try {
+ $result = $this->meshRegistryService->setAllowSharingWithInvitedUsersOnly(boolval($allow));
+ return new DataResponse(
+ [
+ 'data' => $result,
+ ],
+ Http::STATUS_OK
+ );
+ } catch (Exception $e) {
+ $this->logger->error("Unable to set 'allow_sharing_with_invited_users_only' config param. " . $e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_SET_ALLOW_SHARING_WITH_INVITED_USERS_ONLY_ERROR
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+ }
+
+ /**
+ * Returnes this instance's invitation service provider endpoint.
+ *
+ * @PublicPage
+ * @NoCSRFRequired
+ *
+ * @return DataResponse
+ */
+ public function getEndpoint(): DataResponse
+ {
+ try {
+ $endpoint = $this->meshRegistryService->getEndpoint();
+ return new DataResponse(
+ [
+ 'data' => $endpoint,
+ ],
+ Http::STATUS_OK,
+ );
+ } catch (ServiceException $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_GET_ENDPOINT_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ }
+
+ /**
+ * Sets the endpoint of this invitation service provider
+ *
+ * @NoCSRFRequired
+ *
+ * @param string $endpoint
+ * @return DataResponse
+ */
+ public function setEndpoint(string $endpoint): DataResponse
+ {
+ try {
+ $endpoint = $this->meshRegistryService->setEndpoint($endpoint);
+ return new DataResponse(
+ [
+ 'data' => $endpoint,
+ ],
+ Http::STATUS_OK,
+ );
+ } catch (ServiceException $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_GET_ENDPOINT_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ }
+
+ /**
+ * Returnes this instance's invitation service provider name.
+ *
+ * @PublicPage
+ * @NoCSRFRequired
+ *
+ * @return DataResponse
+ */
+ public function getName(): DataResponse
+ {
+ try {
+ $name = $this->meshRegistryService->getName();
+ return new DataResponse(
+ [
+ 'data' => $name,
+ ],
+ Http::STATUS_OK,
+ );
+ } catch (ServiceException $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_GET_NAME_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ }
+
+ /**
+ * Sets this instance's invitation service provider name.
+ *
+ * @NoCSRFRequired
+ *
+ * @param string $name
+ * @return DataResponse
+ */
+ public function setName(string $name): DataResponse
+ {
+ try {
+ $name = $this->meshRegistryService->setName($name);
+ return new DataResponse(
+ [
+ 'data' => $name,
+ ],
+ Http::STATUS_OK,
+ );
+ } catch (ServiceException $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_SET_NAME_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ }
+
+ /**
+ * Whether only sharing with invited users is allowed.
+ *
+ * @NoCSRFRequired
+ *
+ * @return DataResponse
+ */
+ public function getAllowSharingWithInvitedUsersOnly(): DataResponse
+ {
+ try {
+ $result = $this->meshRegistryService->getAllowSharingWithInvitedUsersOnly();
+ return new DataResponse(
+ [
+ 'data' => $result,
+ ],
+ Http::STATUS_OK
+ );
+ } catch (Exception $e) {
+ $this->logger->error("Unable to get 'allow_sharing_with_invited_users_only' config param. " . $e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::MESH_REGISTRY_SET_ALLOW_SHARING_WITH_INVITED_USERS_ONLY_ERROR
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+ }
+}
diff --git a/lib/Controller/OcmController.php b/lib/Controller/OcmController.php
new file mode 100644
index 0000000..e6237a3
--- /dev/null
+++ b/lib/Controller/OcmController.php
@@ -0,0 +1,184 @@
+invitationService = $invitationService;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Inform the sender of the invite that it has been accepted by the recipient.
+ *
+ * A previously established invitation relationship between sender and receiver will be replaced with this new one,
+ * provided there is an actual open invite for this /invite-accepted request.
+ *
+ * @NoCSRFRequired
+ * @PublicPage
+ * @param string $recipientProvider maps to recipient_endpoint in the Invitation entity
+ * @param string $token the invite token
+ * @param string $userID the recipient cloud ID
+ * @param string $email the recipient email
+ * @param string $name the recipient name
+ * @return DataResponse
+ */
+ public function inviteAccepted(
+ string $recipientProvider = '',
+ string $token = '',
+ string $userID = '',
+ string $email = '',
+ string $name = ''
+ ): DataResponse {
+ $this->logger->debug(" - request: " . print_r(\OC::$server->getRequest(), true));
+ if (trim($recipientProvider) == '') {
+ return new DataResponse(
+ [
+ 'message' => 'recipient provider missing'
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+ if ($token == '') {
+ return new DataResponse(
+ [
+ 'message' => 'sender token missing'
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+ if ($userID == '') {
+ return new DataResponse(
+ [
+ 'message' => 'recipient user ID missing'
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+ if ($email == '') {
+ return new DataResponse(
+ [
+ 'message' => 'recipient email missing'
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+ if ($name == '') {
+ return new DataResponse(
+ [
+ 'message' => 'recipient name missing'
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ $invitation = null;
+ try {
+ $invitation = $this->invitationService->getByToken($token, false);
+ } catch (NotFoundException $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::OCM_INVITE_ACCEPTED_NOT_FOUND
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ } catch (ServiceException $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::OCM_INVITE_ACCEPTED_ERROR
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ // check if there are not an established invitation relations already
+ // and remove those
+ $existingInvitationsSent = $this->invitationService->findAll([
+ Schema::VINVITATION_SENDER_CLOUD_ID => [$invitation->getSenderCloudId()],
+ Schema::VINVITATION_RECIPIENT_CLOUD_ID => [$userID],
+ Schema::VINVITATION_STATUS => [Invitation::STATUS_ACCEPTED],
+ ], false);
+ $existingInvitationsReceived = $this->invitationService->findAll([
+ Schema::VINVITATION_RECIPIENT_CLOUD_ID => [$invitation->getSenderCloudId()],
+ Schema::VINVITATION_SENDER_CLOUD_ID => [$userID],
+ Schema::VINVITATION_STATUS => [Invitation::STATUS_ACCEPTED],
+ ], false);
+ $existingInvitations = array_merge($existingInvitationsSent, $existingInvitationsReceived);
+ if (count($existingInvitations) > 0) {
+ foreach ($existingInvitations as $existingInvitation) {
+ $this->logger->debug("A previous established invitation relation exists. Withdrawing that one.", ['app' => Application::APP_ID]);
+ $updateResult = $this->invitationService->update([
+ Schema::INVITATION_TOKEN => $existingInvitation->getToken(),
+ Schema::INVITATION_STATUS => Invitation::STATUS_WITHDRAWN,
+ ], false);
+ if ($updateResult == false) {
+ return new DataResponse(
+ [
+ 'message' => AppError::OCM_INVITE_ACCEPTED_ERROR,
+ ],
+ Http::STATUS_NOT_FOUND,
+ );
+ }
+ }
+ }
+
+ // update the invitation with the receiver's info
+ $updateResult = $this->invitationService->update([
+ Schema::VINVITATION_TOKEN => $invitation->getToken(),
+ Schema::VINVITATION_RECIPIENT_ENDPOINT => $recipientProvider,
+ Schema::VINVITATION_RECIPIENT_CLOUD_ID => $userID,
+ Schema::VINVITATION_RECIPIENT_EMAIL => $email,
+ Schema::VINVITATION_RECIPIENT_NAME => $name,
+ Schema::VINVITATION_STATUS => Invitation::STATUS_ACCEPTED,
+ ], false);
+ if ($updateResult == false) {
+ $this->logger->error("Update failed for invitation with token '$token'", ['app' => Application::APP_ID]);
+ return new DataResponse(
+ [
+ 'message' => AppError::OCM_INVITE_ACCEPTED_ERROR
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ return new DataResponse(
+ [
+ 'userID' => $invitation->getSenderCloudId(),
+ 'email' => $invitation->getSenderEmail(),
+ 'name' => $invitation->getSenderName(),
+ ],
+ Http::STATUS_OK
+ );
+ }
+}
diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php
new file mode 100644
index 0000000..0764eba
--- /dev/null
+++ b/lib/Controller/PageController.php
@@ -0,0 +1,33 @@
+
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace OCA\Invitation\Controller;
+
+use OCA\Invitation\AppInfo\Application;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IRequest;
+use OCP\Util;
+
+class PageController extends Controller
+{
+ public function __construct(IRequest $request)
+ {
+ parent::__construct(Application::APP_ID, $request);
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ */
+ public function index(): TemplateResponse
+ {
+ Util::addScript(Application::APP_ID, 'invitation-main');
+
+ return new TemplateResponse(Application::APP_ID, 'main');
+ }
+}
diff --git a/lib/Federation/InvitationMapper.php b/lib/Federation/InvitationMapper.php
index 46e137c..27f6aee 100644
--- a/lib/Federation/InvitationMapper.php
+++ b/lib/Federation/InvitationMapper.php
@@ -50,7 +50,7 @@ public function find(int $id)
* @return VInvitation
* @throws NotFoundException
*/
- public function findByToken(string $token)
+ public function getByToken(string $token)
{
try {
$qb = $this->db->getQueryBuilder();
@@ -86,6 +86,7 @@ public function findByToken(string $token)
*/
public function findAll(array $criteria): array
{
+ $this->logger->debug(print_r($criteria, true));
$qb = $this->db->getQueryBuilder();
$qb->automaticTablePrefix(false);
$query = $qb->select('*')->from(Schema::VIEW_INVITATIONS, 'i');
@@ -94,7 +95,7 @@ public function findAll(array $criteria): array
if ($i == 0) {
$or = $qb->expr()->orX();
foreach ($values as $value) {
- $or->add($qb->expr()->eq("i.status", $qb->createNamedParameter($value)));
+ $or->add($qb->expr()->eq("i.$field", $qb->createNamedParameter($value)));
}
$query->where($or);
} else {
@@ -106,6 +107,7 @@ public function findAll(array $criteria): array
}
++$i;
}
+ $this->logger->debug($query->getSQL());
$query->addOrderBy(Schema::INVITATION_TIMESTAMP, 'DESC');
return $this->getVInvitations($query->executeQuery()->fetchAll());
@@ -136,7 +138,8 @@ public function updateInvitation(array $fieldsAndValues, string $userCloudID = '
$andWhere->add($qb->expr()->eq('i.' . Schema::INVITATION_USER_CLOUD_ID, $qb->createNamedParameter($userCloudID)));
}
$updateQuery->where($andWhere);
- $result = $updateQuery->executeQuery();
+ $this->logger->debug($updateQuery->getSQL());
+ $result = $updateQuery->executeStatement();
if ($result === 1) {
return true;
}
diff --git a/lib/Federation/InvitationServiceProvider.php b/lib/Federation/InvitationServiceProvider.php
new file mode 100644
index 0000000..ec57c85
--- /dev/null
+++ b/lib/Federation/InvitationServiceProvider.php
@@ -0,0 +1,39 @@
+columnToProperty(Schema::INVITATION_SERVICE_PROVIDER_DOMAIN) => $this->domain,
+ $this->columnToProperty(Schema::INVITATION_SERVICE_PROVIDER_ENDPOINT) => $this->endpoint,
+ $this->columnToProperty(Schema::INVITATION_SERVICE_PROVIDER_NAME) => $this->name,
+ ];
+ }
+}
diff --git a/lib/Federation/InvitationServiceProviderMapper.php b/lib/Federation/InvitationServiceProviderMapper.php
new file mode 100644
index 0000000..298cbf7
--- /dev/null
+++ b/lib/Federation/InvitationServiceProviderMapper.php
@@ -0,0 +1,105 @@
+logger = $logger;
+ }
+
+ /**
+ * Returns the local (this instance's) invitation service provider.
+ *
+ * @param string $endpoint
+ * @return InvitationServiceProvider
+ * @throws NotFoundException
+ */
+ public function getInvitationServiceProvider(string $endpoint): InvitationServiceProvider
+ {
+ try {
+ $qb = $this->db->getQueryBuilder();
+ $result = $qb->select('*')
+ ->from(Schema::TABLE_INVITATION_SERVICE_PROVIDERS, 'dp')
+ ->where($qb->expr()->eq('dp.' . Schema::INVITATION_SERVICE_PROVIDER_ENDPOINT, $qb->createNamedParameter($endpoint)))
+ ->executeQuery()->fetch();
+ if (is_array($result)) {
+ return $this->createInvitationServiceProvider($result);
+ }
+ throw new NotFoundException("Error retrieving the invitation service provider with endpoint '$endpoint'");
+ } catch (Exception $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => InvitationApp::APP_NAME]);
+ throw new NotFoundException("Error retrieving the endpoint provider with endpoint '$endpoint'");
+ }
+ }
+
+ /**
+ * Returns all invitation service providers
+ *
+ * @return array
+ * @throws NotFoundException
+ */
+ public function allInvitationServiceProviders(): array
+ {
+ try {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('*')
+ ->from(Schema::TABLE_INVITATION_SERVICE_PROVIDERS, 'dp');
+ return $this->createInvitationServiceProviders($qb->executeQuery()->fetchAll());
+ } catch (Exception $e) {
+ $this->logger->error($e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => InvitationApp::APP_NAME]);
+ throw new NotFoundException('Error retrieving all invitation service providers');
+ }
+ }
+
+ /**
+ * Builds and returns a new InvitationServiceProvider object from the specified associative array.
+ * @param array $associativeArray
+ * @return InvitationServiceProvider
+ */
+ private function createInvitationServiceProvider(array $associativeArray): InvitationServiceProvider
+ {
+ if (isset($associativeArray) && count($associativeArray) > 0) {
+ $invitationServiceProvider = new InvitationServiceProvider();
+ $invitationServiceProvider->setId($associativeArray['id']);
+ $invitationServiceProvider->setDomain($associativeArray[Schema::INVITATION_SERVICE_PROVIDER_DOMAIN]);
+ $invitationServiceProvider->setEndpoint($associativeArray[Schema::INVITATION_SERVICE_PROVIDER_ENDPOINT]);
+ $invitationServiceProvider->setName($associativeArray[Schema::INVITATION_SERVICE_PROVIDER_NAME]);
+ return $invitationServiceProvider;
+ }
+ $this->logger->error('Unable to create a new InvitationServiceProvider from associative array: ' . print_r($associativeArray, true), ['app' => InvitationApp::APP_NAME]);
+ return null;
+ }
+
+ /**
+ * Builds and returns an array of new InvitationServiceProvider objects from the specified associatives array.
+ * @param array $associativeArrays
+ * @return array
+ */
+ private function createInvitationServiceProviders(array $associativeArrays): array
+ {
+ $invitationServiceProviders = [];
+ if (isset($associativeArrays) && count($associativeArrays) > 0) {
+ foreach ($associativeArrays as $associativeArray) {
+ array_push($invitationServiceProviders, $this->createInvitationServiceProvider($associativeArray));
+ }
+ }
+ return $invitationServiceProviders;
+ }
+}
diff --git a/lib/HttpClient.php b/lib/HttpClient.php
new file mode 100644
index 0000000..9da0dfd
--- /dev/null
+++ b/lib/HttpClient.php
@@ -0,0 +1,90 @@
+logger = $logger;
+ }
+
+ /**
+ * Executes a POST request.
+ *
+ * @param string $url
+ * @param array $params post fields
+ * @return mixed returns an array in the standardized format:
+ * [
+ * 'data' => if success is true
+ * or
+ * 'message' => if success is false
+ * ]
+ * @throws HttpException
+ */
+ public function curlPost(string $url, array $params = [])
+ {
+ try {
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params, JSON_PRETTY_PRINT));
+ $output = json_decode(curl_exec($ch));
+ $info = curl_getinfo($ch);
+ curl_close($ch);
+ if (!isset($output) || $output == false) {
+ $this->logger->debug('curl_getinfo: ' . print_r($info, true));
+ throw new HttpException(AppError::HTTP_POST_CURL_REQUEST_ERROR);
+ }
+ return (array)$output;
+ } catch (HttpException $e) {
+ throw $e;
+ } catch (Exception $e) {
+ throw new HttpException(AppError::HTTP_POST_CURL_REQUEST_ERROR);
+ }
+ }
+
+ /**
+ * Executes a GET request.
+ *
+ * @param string $url
+ * @return mixed returns an array in the standardized format:
+ * [
+ * 'data' => if success is true
+ * or
+ * 'message' => if success is false
+ * ]
+ * @throws HttpException
+ */
+ public function curlGet(string $url)
+ {
+ try {
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
+ $output = json_decode(curl_exec($ch));
+ $info = curl_getinfo($ch);
+ curl_close($ch);
+ if (!isset($output) || $output == false) {
+ $this->logger->debug('curl_getinfo: ' . print_r($info, true));
+ throw new HttpException(AppError::HTTP_GET_CURL_REQUEST_ERROR);
+ }
+ return (array)$output;
+ } catch (HttpException $e) {
+ throw $e;
+ } catch (Exception $e) {
+ throw new HttpException(AppError::HTTP_POST_CURL_REQUEST_ERROR);
+ }
+ }
+}
diff --git a/lib/HttpException.php b/lib/HttpException.php
new file mode 100644
index 0000000..98d1d69
--- /dev/null
+++ b/lib/HttpException.php
@@ -0,0 +1,15 @@
+addIndex([Schema::INVITATION_TOKEN], 'invitation_token_index');
$sql = $this->dbc->getDatabasePlatform()->getCreateTableSQL($table);
- foreach($sql as $statement) {
+ foreach ($sql as $statement) {
$this->logger->debug(print_r($statement, true));
}
$this->dbc->executeStatement($sql[0]);
@@ -153,7 +153,7 @@ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $op
$table->addUniqueIndex([Schema::INVITATION_SERVICE_PROVIDER_ENDPOINT], 'endpoint_index');
$sql = $this->dbc->getDatabasePlatform()->getCreateTableSQL($table);
- foreach($sql as $statement) {
+ foreach ($sql as $statement) {
$this->logger->debug(print_r($statement, true));
}
$this->dbc->executeStatement($sql[0]);
diff --git a/lib/Migration/Version20231130125300.php b/lib/Migration/Version20231130125300.php
index 6f15541..f50e72c 100644
--- a/lib/Migration/Version20231130125300.php
+++ b/lib/Migration/Version20231130125300.php
@@ -14,7 +14,6 @@
*/
class Version20231130125300 extends SimpleMigrationStep
{
-
/** @var IDBConnection */
private $dbc;
/** @var LoggerInterface */
diff --git a/lib/Service/ApplicationConfigurationException.php b/lib/Service/ApplicationConfigurationException.php
new file mode 100644
index 0000000..44b7e1c
--- /dev/null
+++ b/lib/Service/ApplicationConfigurationException.php
@@ -0,0 +1,15 @@
+mapper->findByToken($token);
+ $invitation = $this->mapper->getByToken($token);
} catch (NotFoundException $e) {
$this->logger->error("Invitation not found for token '$token'.", ['app' => Application::APP_ID]);
throw new NotFoundException("An exception occurred trying to retrieve the invitation with token '$token'.");
}
- if ($loginRequired == true && $this->userSession->getUser() == null) {
+ if ($protected == true && $this->userSession->getUser() == null) {
throw new ServiceException("Unable to find invitation, unauthenticated.");
}
if (
- $loginRequired == false
+ $protected == false
|| $this->userSession->getUser()->getCloudId() === $invitation->getUserCloudID()
) {
return $invitation;
@@ -90,9 +90,13 @@ public function findByToken(string $token, bool $loginRequired = true): VInvitat
* @return array
* @throws ServiceException
*/
- public function findAll(array $criteria): array
+ public function findAll(array $criteria, bool $protected = true): array
{
try {
+ // add access restriction
+ if ($protected) {
+ $criteria[Schema::VINVITATION_USER_CLOUD_ID] = [$this->userSession->getUser()->getCloudId()];
+ }
return $this->mapper->findAll($criteria);
} catch (Exception $e) {
$this->logger->error('findAll failed with error: ' . $e->getMessage() . ' Trace: ' . $e->getTraceAsString(), ['app' => Application::APP_ID]);
@@ -121,12 +125,12 @@ public function insert(Invitation $invitation): Invitation
* Updates the invitation according to the specified fields and values.
*
* @param array $fieldsAndValues one of which must be the token
- * @param bool $loginRequired true if we need session user access check, default is true
+ * @param bool $protected true if we need session user access check, default is true
* @return bool true if update succeeded, otherwise false
*/
- public function update(array $fieldsAndValues, bool $loginRequired = true): bool
+ public function update(array $fieldsAndValues, bool $protected = true): bool
{
- if ($loginRequired === true) {
+ if ($protected === true) {
if ($this->userSession->getUser() == null) {
$this->logger->debug('Unable to update invitation, unauthenticated.', ['app' => Application::APP_ID]);
return false;
diff --git a/lib/Service/MeshRegistry/MeshRegistryService.php b/lib/Service/MeshRegistry/MeshRegistryService.php
index a891ad1..178054a 100644
--- a/lib/Service/MeshRegistry/MeshRegistryService.php
+++ b/lib/Service/MeshRegistry/MeshRegistryService.php
@@ -11,18 +11,19 @@
use OCA\Invitation\AppInfo\Application;
use OCA\Invitation\Db\Schema;
use OCA\Invitation\Federation\InvitationServiceProvider;
+use OCA\Invitation\Federation\InvitationServiceProviderMapper;
use OCA\Invitation\Service\ApplicationConfigurationException;
use OCA\Invitation\Service\NotFoundException;
use OCA\Invitation\Service\ServiceException;
use OCP\IConfig;
-use OCP\ILogger;
+use Psr\Log\LoggerInterface;
class MeshRegistryService
{
private string $appName;
private IConfig $config;
private InvitationServiceProviderMapper $invitationServiceProviderMapper;
- private ILogger $logger;
+ private LoggerInterface $logger;
private const ENDPOINT_FORWARD_INVITE = '/registry/forward-invite';
public const ENDPOINT_GET_INVITE = '/invite';
@@ -48,12 +49,12 @@ class MeshRegistryService
public const PARAM_NAME_NAME = 'name';
- public function __construct($appName, IConfig $config, InvitationServiceProviderMapper $invitationServiceProviderMapper)
+ public function __construct($appName, IConfig $config, InvitationServiceProviderMapper $invitationServiceProviderMapper, LoggerInterface $logger)
{
$this->appName = $appName;
$this->config = $config;
$this->invitationServiceProviderMapper = $invitationServiceProviderMapper;
- $this->logger = \OC::$server->getLogger();
+ $this->logger = $logger;
}
/**
@@ -103,7 +104,7 @@ public function getFullAcceptInviteEndpointURL(): string
}
/**
- * Returns the full 'https://{invitation_service_provider}/invite-accepted' endpoint URL of this EFSS instance.
+ * Returns the full 'https://{invitation_service_provider}/ocm/invite-accepted' endpoint URL of this EFSS instance.
*
* @param string $senderHost the host of the sender of the invitation
* @return string the full /invite-accepted endpoint URL
@@ -111,7 +112,7 @@ public function getFullAcceptInviteEndpointURL(): string
public function getFullInviteAcceptedEndpointURL(string $senderInvitationServiceProviderEndpoint = ""): string
{
if ($senderInvitationServiceProviderEndpoint == "") {
- return ['error' => "unable to build full '/invite-accepted' endpoint URL, sender invitation service provider endpoint not specified"];
+ return ['error' => "unable to build full '/ocm/invite-accepted' endpoint URL, sender invitation service provider endpoint not specified"];
}
$endpoint = trim(trim($senderInvitationServiceProviderEndpoint), '/');
$inviteAcceptedEndpoint = trim(trim(self::ENDPOINT_INVITE_ACCEPTED), "/");
diff --git a/lib/Util.php b/lib/Util.php
new file mode 100644
index 0000000..0b516e4
--- /dev/null
+++ b/lib/Util.php
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-- English version below --
+ t('Hallo ' . $_['recipientName'] . ',')); ?>
+
+ U bent uitgenodigd om uw cloud IDs uit te wisselen met .
+ Als u deze uitnodiging accepteerd zult u onderling efficienter gefedereerd bestanden kunnen delen.
+
+
+ Het volgende persoonlijke bericht is meegestuurd met deze uitnodiging:
+
+
+
+
+
+
+
+ Via de onderstaande link kunt u de uitnodiging accepteren of afwijzen.
+ Als u de uitnodiging accepteerd zullen de cloud IDs van u en , nodig voor het creëren van federated shares, onderling worden uitgewisseld.
+
+ Klik om in te loggen in uw Research Drive instance.
+
+ * Wanneer u de afzender van deze email niet kent of u bent niet bekend met Research Drive verzoeken wij u deze email te verwijderen.
+
+ Voor het verkrijgen van een Research Drive account neemt u contact op met de IT service helpdesk van uw organisatie.
+
+ Op de wikipagina vindt u alle informatie over het gebruik van SURF Research Drive.
+ Voor vragen en opmerkingen kunt u contact opnemen met de Research Drive helpdesk via servicedesk@surf.nl.
+
+ Met vriendelijke groet,
+ SURF Research Drive team
+
+
+ t('Hello ' . $_['recipientName'] . ',')); ?>
+
+ You have been invited to exchange cloud IDs with .
+ If you accept this invitation you will be able to create federated shares with each other more efficiently.
+
+
+ The following personal message was send with the invitation:
+
+
+
+
+
+
+
+ Please follow the link below to either accept or decline this invitation.
+ If you accept this invitation, you and will exchange the federated cloud IDs which are needed to create federated shares.
+
+ Click to log into your Research Drive instance.
+
+ * If you do not know the sender of this email or are not familiar with Research Drive, please delete this email.
+
+ To obtain an account on Research Drive you should contact the IT service helpdesk within your organization.
+
+ On the wiki you will find more information about the usage of SURF Research Drive.
+ For questions or remarks, you can contact your Research Drive helpdesk via servicedesk@surf.nl.
+
+ Kind regards,
+ SURF Research Drive team
+
+
\ No newline at end of file
diff --git a/templates/mail/text.php b/templates/mail/text.php
new file mode 100644
index 0000000..6223089
--- /dev/null
+++ b/templates/mail/text.php
@@ -0,0 +1,60 @@
+
+-- English version below --
+
+t('Hallo,')); ?>
+
+U bent uitgenodigd om uw cloud IDs uit te wisselen met .
+Als u deze uitnodiging accepteerd zult u onderling efficienter gefedereerd bestanden kunnen delen.
+
+Het volgende persoonlijke bericht is meegestuurd met deze uitnodiging:
+
+
+
+
+
+
+Via de onderstaande link kunt u de uitnodiging accepteren of afwijzen. Als u de uitnodiging accepteerd zullen de cloud IDs van u en , nodig voor het creëren van federated shares, onderling worden uitgewisseld.
+
+
+
+* Wanneer u de afzender van deze email niet kent of u bent niet bekend met Research Drive verzoeken wij u deze email te verwijderen.
+
+Op de wikipagina (https://wiki.surfnet.nl/display/RDRIVE) vindt u alle informatie over het gebruik van SURF Research Drive.
+Voor vragen en opmerkingen kunt u contact opnemen met de Research Drive helpdesk via servicedesk@surf.nl
+
+Met vriendelijke groet,
+SURF Research Drive team
+
+------------------------------------------------------------------------------------------------------------
+t('Hello,')); ?>
+
+You have been invited to exchange cloud IDs with .
+If you accept this invitation you will be able to create federated shares with each other more efficiently.
+
+The following personal message was send with the invitation:
+
+
+
+
+
+
+Please follow the link below to either accept or decline this invitation. If you accept this invitation, you and will exchange the federated cloud IDs which are needed to create federated shares.
+
+
+
+* If you do not know the sender of this email or are not familiar with Research Drive, please delete this email.
+
+On the wiki (https://wiki.surfnet.nl/display/RDRIVE) you will find more information about the usage of SURF Research Drive.
+For questions or remarks, you can contact your Research Drive helpdesk via servicedesk@surf.nl
+
+Kind regards,
+SURF Research Drive team
+
+
+inc('plain.mail.footer', ['app' => 'core'])); ?>
\ No newline at end of file
diff --git a/templates/main.php b/templates/main.php
new file mode 100644
index 0000000..7fa5416
--- /dev/null
+++ b/templates/main.php
@@ -0,0 +1,8 @@
+
+// SPDX-License-Identifier: AGPL-3.0-or-later
+?>
+