XF 2.1 Custom obfuscated link for controller

Rasmus Vind

Well-known member
I'm in the process of writing an add-on which will have non-discoverable links to entities. To stay compatible with the same add-on for XF1, the link looks something like this:
Code:
index.php?pastebin/0123456789abcd23213112abcd123412/
The ID of an entity is a 32 character hash.
I did the following in my route:
Code:
:str<paste_id>
But this means that all my actions in the controller stop working. Is there a way to define something like this:
Code:
:str<paste_id>{32}
to say that it is only an item ID if it is 32 characters long.
Otherwise I see no other way to do this than this:
PHP:
<?php
namespace VindIT\Pastebin\Pub\Controller;
class MyController extends \XF\Pub\Controller\AbstractController
{
    public function actionIndex(\XF\Mvc\ParameterBag $params)
    {
        if ($params->paste_id == 'my-action')
        {
            return $this->rerouteController(__CLASS__, $params->paste_id, $params);
        }
        return $this->message('View entity: ' . $params->paste_id);
    }
    public function actionMyAction(\XF\Mvc\ParameterBag $params)
    {
        return $this->message('My action');
    }
}
But it feels a bit like a hack. In XF1 I had a way to actually make my own logic in the Route_Prefix class:
PHP:
<?php
class Rapbin_Route_Prefix_Pastebin implements XenForo_Route_Interface
{
    public function match($routePath, Zend_Controller_Request_Http $request, XenForo_Router $router)
    {
        $action = $router->resolveActionWithStringParam($routePath, $request, 'paste_id');
        $pasteId = $request->getParam('paste_id');
        if ($pasteId)
        {
            $normalId = $this->_getPasteModel()->readObfuscatedId($pasteId);
            $request->setParam('paste_id', $normalId);
        }
        return $router->getRouteMatch('Rapbin_ControllerPublic_Pastebin', $action, 'pastebin');
    }
    public function buildLink($originalPrefix, $outputPrefix, $action, $extension, $data, array &$extraParams)
    {
        if (! empty($data['paste_id']))
        {
            $data['paste_id'] = $this->_getPasteModel()->makeObfuscatedId($data['paste_id']);
        }
        return XenForo_Link::buildBasicLinkWithStringParam($outputPrefix, $action, $extension, $data, 'paste_id');
    }
    protected function _getPasteModel()
    {
        return XenForo_Model::create('Rapbin_Model_Paste');
    }
}
Is there a similar way to do this in XF2?

Thanks for your time.
 
Okay, I have found a solution that seems less hacky compared to what I showed in the OP. For anyone trying to do the same, here is what I did:

PHP:
    public function actionIndex(\XF\Mvc\ParameterBag $params)
    {
        $paste = $this->assertViewablePaste($params->paste_id);
        $requestUri = $this->request->getRequestUri();
        if ($paste->isAuthor())
        {
            $this->assertCanonicalUrl($this->buildLink('pastebin', $paste));
        }
        else if (preg_match('#\b([a-f0-9]{32,40})\b#', $requestUri, $match) && $match[1] == $paste->hash)
        {
            return $this->view('VindIT/Pastebin:PasteView', 'paste_view', [
                'paste' => $paste,
            ]);
        }
        else
        {
            return $this->noPermission();
        }
    }
And the router has this format string ":int<paste_id,hash>".

This makes the URL contain the item ID and its secret hash and upon displaying an item, it checks the hash from the URI to verify that it wasn't modified. In case the visitor is the author, it tries to be helpful and redirects to the correct link. In case of non-author, it gives an error. This hardens against users doing a scan of IDs from end to end.

I will go with this solution unless I come across something more elegant.
 
Top Bottom