Page MenuHomePhabricator

No OneTemporary

diff --git a/bin/controllers/url.php b/bin/classes/controllers/UrlController.php
similarity index 97%
rename from bin/controllers/url.php
rename to bin/classes/controllers/UrlController.php
index d8b0624..1a8b47a 100644
--- a/bin/controllers/url.php
+++ b/bin/classes/controllers/UrlController.php
@@ -1,137 +1,139 @@
-<?php
+<?php namespace controllers;
+use BaseController;
+use mail\MailUtils;
use spitfire\exceptions\PublicException;
/*
* The MIT License
*
* Copyright 2020 César de la Cal Bretschneider <cesar@magic3w.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
class URLController extends BaseController
{
/**
* This endpoint is reached whenever a user clicks on a link in an email from
* relay. The endpoint will record that the email was delivered and interacted
* with (which can allow the system to throttle emails to a user who shows no
* interest in them).
*
* The system could, for example, be used to detect whether a user is reading
* the email newsletter they subscribed for. If they are no longer reading the
* messages it may be a good idea to remove them from the subscription list.
*
* @validate GET.url (string required)
*
* @param type $msgid
* @throws PublicException
*/
public function redirect($msgid) {
/*
* Fetch the email we sent to the user in the first place. We need this
* to ensure that we actually redirect the user to a location that was in
* the original email.
*/
$msg = db()->table('outgoing')->get('_id', $msgid)->first();
/*
* Extract the hostname from the recipient email (this allows the system to
* generate reports on a domain basis)
*/
- $_domain = mail\MailUtils::parse($msg->message->to->external)['host'];
+ $_domain = MailUtils::parse($msg->message->to->external)['host'];
$domain = db()->table('statistics\domain')->get('host', $_domain)->first();
/*
* Extract the URL the user is being sent to. This will be URL encoded in
* the url property of the query string.
*/
$url = base64_decode($_GET['url']);
/*
* Check if the URL they're being redirected to was even in the email. This
* prevents using relay as an open redirect to potentially harmful pages.
*/
if (strpos($msg->message->html, $url) === false && strpos($msg->message->plain, $url) === false) {
throw new PublicException('URL ' . $url . ' was not found in the message');
}
/*
* Record the fact that the email was opened. This allows the system to
* consider reducing the amount of emails it sends to a user when they are
* not opening the notifications they're being sent.
*/
$first = !db()->table('statistics\interaction')->get('message', $msg)->where('event', 'click')->first();
$event = db()->table('statistics\interaction')->newRecord();
$event->event = 'click';
$event->first = $first;
$event->domain = $domain;
$event->message = $msg;
$event->created = time();
$event->store();
/*
* In case the user has blocked the pixel to measure opens from their emails
* or has a client that does not support images in emails, we will record the
* action of clicking on the email as an open.
*/
$open = db()->table('statistics\interaction')->get('message', $msg)->where('event', 'open')->first();
if (!$open) {
$oevent = db()->table('statistics\interaction')->newRecord();
$oevent->event = 'open';
$oevent->first = true;
$oevent->domain = $domain;
$oevent->message = $msg;
$oevent->created = time();
$oevent->store();
}
/*
* If the server is configured with a hook system, we can let the application
* know that the email was opened. Generally speaking, this is not very valuable
* information, since generally, the application will link to itself.
*
* But if your application for example wishes to link to another component,
* but whishes to be kept in the loop of the email it sent, you can listen
* for events on mail.outgoing.open.appID.*
*/
if ($msg && $this->hook && $msg->mailbox->internal) {
#Notify the network that an email was opened.
$this->hook->trigger(sprintf('mail.outgoing.open.%s.%s', $msg->mailbox->internal, $msg->_id), [
'id' => $msg->_id,
'to' => $msg->message->to->internal,
'subject' => $msg->message->subject
]);
#Notify the network that an email was clicked on.
$this->hook->trigger(sprintf('mail.outgoing.click.%s.%s', $msg->mailbox->internal, $msg->_id), [
'id' => $msg->_id,
'to' => $msg->message->to->internal,
'subject' => $msg->message->subject
]);
}
$this->response->setBody('Redirecting...')->getHeaders()->redirect($url);
}
}
diff --git a/bin/controllers/route.php b/bin/controllers/route.php
new file mode 100644
index 0000000..aa3a66f
--- /dev/null
+++ b/bin/controllers/route.php
@@ -0,0 +1,111 @@
+<?php
+
+use spitfire\exceptions\HTTPMethodException;
+
+/*
+ * The MIT License
+ *
+ * Copyright 2021 César de la Cal Bretschneider <cesar@magic3w.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ *
+ *
+ * @author César de la Cal Bretschneider <cesar@magic3w.com>
+ */
+class RouteController extends BaseController
+{
+
+ /**
+ *
+ * @validate POST#origin (required in ["inside", "outside"])
+ * @validate POST#to (required string)
+ * @validate POST#dispatchMechanism (required string in["mailbox", "internal", "external", "route", "discard"])
+ * @validate POST#dispatchTarget (required string)
+ * @validate POST#state (string length[0, 100])
+ * @todo Add policy to check whether the endpoint can be used.
+ */
+ public function create()
+ {
+
+ try {
+ if (!$this->request->isPost()) { throw new HTTPMethodException('Not posted'); }
+
+ $record = db()->table('route')->newRecord();
+ $record->origin = $_POST['origin'];
+ $record->to = $_POST['origin'];
+ $record->dispatchMechanism = $_POST['dispatchMechanism'];
+ $record->dispatchTarget = $_POST['dispatchTarget'];
+ $record->owner = $this->authapp->getSrc()->getId();
+ $record->state = $_POST['state']?? null;
+ $record->store();
+
+ $this->view->set('route', $record);
+ }
+ catch (HTTPMethodException $ex) {
+ throw new PublicException('This endpoint requires to be submitted via POST', 400);
+ }
+ }
+
+ /**
+ *
+ * @validate POST#origin (required in ["inside", "outside"])
+ * @validate POST#to (required string)
+ * @validate POST#dispatchMechanism (required string in["mailbox", "internal", "external", "route", "discard"])
+ * @validate POST#dispatchTarget (required string)
+ * @validate POST#state (string length[0, 100])
+ *
+ * @param RouteModel $route The route to update
+ * @todo Add policy to check whether the endpoint can be used.
+ */
+ public function update(RouteModel $route)
+ {
+
+ try {
+ if (!$this->request->isPost()) { throw new HTTPMethodException('Not posted'); }
+
+ $route->origin = $_POST['origin'];
+ $route->to = $_POST['origin'];
+ $route->dispatchMechanism = $_POST['dispatchMechanism'];
+ $route->dispatchTarget = $_POST['dispatchTarget'];
+ $route->owner = $this->authapp->getSrc()->getId();
+ $route->state = $_POST['state']?? null;
+ $route->store();
+
+ $this->view->set('route', $route);
+ }
+ catch (HTTPMethodException $ex) {
+ throw new PublicException('This endpoint requires to be submitted via POST', 400);
+ }
+ }
+
+ /**
+ *
+ * @param RouteModel $route
+ * @todo Use policy to determine whether the user / application is entitled to this
+ */
+ public function delete(RouteModel $route)
+ {
+ $route->delete();
+ $this->view->set('success', true);
+ }
+
+}
diff --git a/bin/models/message.php b/bin/models/message.php
index 368a961..5ffeab1 100644
--- a/bin/models/message.php
+++ b/bin/models/message.php
@@ -1,59 +1,59 @@
<?php
use spitfire\Model;
use spitfire\storage\database\Schema;
/**
* The Email queue model allows the application to send emails in an asynchronous
* way. This will avoid requests to the user being delayed due to the server
* having a high latency connection with the email provider.
*
* Instead, the web server can push the email to a queue and let the email be sent
* at a later point in time. This should improve, both latency and usability.
*
* The message class property is a value specifically used to prevent the server
* from notifying a certain behavior twice. Imagine a user that uploads an auction
* with several slots. The system would send several messages at once, which is
* not the preferred behavior.
*
* @property int $id The id of the message. This is only used to identify the record inside the DBMS
* @property string $to The email address we wish the email to be delivered to
* @property string $subject The subject line of the email being sent
* @property string $body The full HTML message being sent to the user
* @property int $scheduled Timestamp after which the message should be delivered
* @property int $delivered Timestamp the message was delivered, or NULL if it wasn't
*/
class MessageModel extends Model
{
const SUBJECT_LENGTH = 100;
public function definitions(Schema $schema) {
/*
* Indicate who the participants of the message are.
*/
$schema->to = new Reference(MailboxModel::class);
$schema->from = new Reference(MailboxModel::class);
/*
* The subject is transparent to most of relay's operations. It will use the
* from and to addresses to establish connections between the parties.
*/
$schema->subject = new StringField(self::SUBJECT_LENGTH);
/*
* The bodies for the message.
*/
$schema->plain = new TextField();
$schema->html = new TextField();
$schema->created = new IntegerField(true);
- $schema->attachments = new ChildrenField('attachment', 'message');
+ $schema->attachments = new \spitfire\model\fields\ChildrenField('attachment', 'message');
}
public function onbeforesave(): void {
if (!$this->created) { $this->created = time(); }
}
}
diff --git a/bin/models/route.php b/bin/models/route.php
new file mode 100644
index 0000000..099d794
--- /dev/null
+++ b/bin/models/route.php
@@ -0,0 +1,88 @@
+<?php
+
+use spitfire\Model;
+use spitfire\model\fields\IntegerField;
+use spitfire\model\fields\StringField;
+use spitfire\storage\database\Schema;
+
+/*
+ * The MIT License
+ *
+ * Copyright 2021 César de la Cal Bretschneider <cesar@magic3w.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * The route model allows orbital station to determine what action to perform
+ * whenever it is told to dispatch a message.
+ *
+ * This mechanism is a lot more trivial than we originally intended, but it provides
+ * a lot more flexibility, and a good level of privacy.
+ *
+ * The only quirk about this system becomes the handling of guest emails, whenever
+ * the system receives an email that comes from a guest, it will generate a `once`
+ * internal endpoint that will be provided as reply endpoint to the message.
+ *
+ * Since the `once` endpoint is generated every time, the system will pass the last
+ * 4 characters of the MD5 of the email. To allow disambiguation within a small
+ * set of emails but providing a good chance of collisions so a checksum cannot
+ * be inferred to be a single user.
+ *
+ * The receiving application can then decide whether to update the once or not.
+ *
+ * To allow for a good level of privacy + tracking, the application should generate
+ * an endpoint to receive replies from the user. This way for example, it can always
+ * determine which user is replying to the tickets.
+ *
+ * @property string $origin Either inside or outside
+ * @property string $to Where the message is directed to. For externals this must be an email.
+ * @property string $dispatchMechanism Which module the system should use to route the mail
+ * @property string $dispatchTarget The target to direct this message to
+ * @property string $owner The id of the owner client
+ * @property string $state An opaque value that the client can use to identify the route
+ * @property int $expires The timestamp of the expiration
+ * @property int $created The timestamp of the creation
+ *
+ * @author César de la Cal Bretschneider <cesar@magic3w.com>
+ */
+class RouteModel extends Model
+{
+
+ /**
+ *
+ * @param Schema $schema
+ * @return Schema
+ */
+ public function definitions(Schema $schema)
+ {
+ $schema->origin = new EnumField('inside', 'outside');
+ $schema->to = new StringField(255);
+
+ $schema->dispatchMechanism = new EnumField('mailbox', 'internal', 'external', 'route', 'discard');
+ $schema->dispatchTarget = new StringField(255);
+
+ $schema->owner = new StringField(100);
+ $schema->state = new StringField(100);
+
+ $schema->expires = new IntegerField(true);
+ $schema->created = new IntegerField(true);
+ }
+
+}
diff --git a/bin/templates/route/create.json.php b/bin/templates/route/create.json.php
new file mode 100644
index 0000000..cf5764d
--- /dev/null
+++ b/bin/templates/route/create.json.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * The MIT License
+ *
+ * Copyright 2021 César de la Cal Bretschneider <cesar@magic3w.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+current_context()->response->getHeaders()->contentType('json');
+echo json_encode([
+ 'payload' => [
+ 'id' => $route->_id,
+ 'origin' => $route->origin,
+ 'to' => $route->to,
+ 'dispatch' => [
+ 'mechanism' => $route->dispatchMechanism,
+ 'target' => $route->dispatchTarget
+ ],
+ 'state' => $route->state,
+ 'created' => $route->created,
+ 'expires' => $route->expires
+ ]
+]);
diff --git a/bin/templates/route/update.json.php b/bin/templates/route/update.json.php
new file mode 100644
index 0000000..cf5764d
--- /dev/null
+++ b/bin/templates/route/update.json.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * The MIT License
+ *
+ * Copyright 2021 César de la Cal Bretschneider <cesar@magic3w.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+current_context()->response->getHeaders()->contentType('json');
+echo json_encode([
+ 'payload' => [
+ 'id' => $route->_id,
+ 'origin' => $route->origin,
+ 'to' => $route->to,
+ 'dispatch' => [
+ 'mechanism' => $route->dispatchMechanism,
+ 'target' => $route->dispatchTarget
+ ],
+ 'state' => $route->state,
+ 'created' => $route->created,
+ 'expires' => $route->expires
+ ]
+]);

File Metadata

Mime Type
text/x-diff
Expires
Thu, Apr 15, 4:35 AM (3 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5720
Default Alt Text
(19 KB)

Event Timeline