Token

You can simply add your own token or override existing ones and change their priorities. Your token has to extend the AbstractToken class.

Register Token

You can add following method call to your ext_localconf.php file:

\Leuchtfeuer\SecureDownloads\Registry\TokenRegistry::register(
    'tx_securedownloads_default',
    \Leuchtfeuer\SecureDownloads\Domain\Transfer\Token\DefaultToken::class,
    0,
    false
);

Instead of tx_securedownloads_default you can use your own unique identifier. The second argument of that method contains the class of your token. The third one mirrors the priority of your token and you can override existing tokens when you set the fourth argument of this method to true.

Example

An example of how to register your own token can be found in the example extension. This example token uses an RSA key pair to sign the token.

Register the Token

ext_localconf.php

\Leuchtfeuer\SecureDownloads\Registry\TokenRegistry::register(
    'tx_evenmoresecuredownloads_rsa',
    \Flossels\EvenMoreSecureDownloads\Domain\Transfer\Token\RsaToken::class,
    50,
    false
);

The Token

Classes/Domain/Transfer/Token/RsaToken.php

class RsaToken extends AbstractToken
{
    const PRIVATE_KEY_FILE = 'EXT:secure_downloads_example/Resources/Private/Keys/private.key';

    const PUBLIC_KEY_FILE = 'EXT:secure_downloads_example/Resources/Private/Keys/public.key';

    const CLAIMS = ['user', 'groups', 'file', 'page'];

    protected $extensionConfiguration;

    public function __construct()
    {
        parent::__construct();

        $this->extensionConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class);
    }

    public function encode(?array $payload = null): string
    {
        $builder = new Builder();
        $builder->issuedBy($this->getIssuer());
        $builder->permittedFor($this->getPermittedFor());
        $builder->issuedAt($this->getIat());
        $builder->canOnlyBeUsedAfter($this->getIat());
        $builder->expiresAt($this->getExp());

        foreach (self::CLAIMS as $claim) {
            $getter = 'get' . ucfirst($claim);
            $builder->withClaim($claim, $this->$getter());
        }

        $signer = new Sha256();
        $key = new Key('file://' . GeneralUtility::getFileAbsFileName(self::PRIVATE_KEY_FILE));
        (new ClaimRepository())->addClaim($this);

        return (string)$builder->getToken($signer, $key);
    }

    public function getHash(): string
    {
        return md5(parent::getHash() . $this->getExp());
    }

    public function log(array $parameters = []): void
    {
        // TODO: Implement log() method.
    }

    public function decode(string $jsonWebToken): void
    {
        if (empty($jsonWebToken)) {
            throw new \Exception('Token is empty.', 1588852881);
        }

        $parsedToken = (new Parser())->parse($jsonWebToken);

        if (!$parsedToken->validate($this->getValidationData())) {
            throw new \Exception('Could not validate data.', 1588852940);
        }

        $signer = new Sha256();
        $key = new Key('file://' . GeneralUtility::getFileAbsFileName(self::PUBLIC_KEY_FILE));

        if (!$parsedToken->verify($signer, $key)) {
            throw new \Exception('Could not verify data.', 1588852970);
        }

        foreach ($parsedToken->getClaims() ?? [] as $claim) {
            /** @var $value Claim */
            if (property_exists(__CLASS__, $claim->getName())) {
                $property = $claim->getName();
                $this->$property = $claim->getValue();
            }
        }
    }

    protected function getIssuer(): string
    {
        $environmentService = GeneralUtility::makeInstance(EnvironmentService::class);

        if ($environmentService->isEnvironmentInFrontendMode()) {
            try {
                $pageId = (int)$GLOBALS['TSFE']->id;
                $base = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId)->getBase();

                if ($base->getScheme() !== null) {
                    $issuer = sprintf('%s://%s', $base->getScheme(), $base->getHost());
                } else {
                    // Base of site configuration might be "/" so we have to retrieve the domain from the ENV
                    $issuer = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST');
                }
            } catch (SiteNotFoundException $exception) {
                $issuer = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST');
            }
        } elseif ($environmentService->isEnvironmentInBackendMode()) {
            $issuer = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST');
        }

        return $issuer ?? '';
    }

    protected function getPermittedFor(): string
    {
        return $this->extensionConfiguration->getDocumentRootPath() . $this->extensionConfiguration->getLinkPrefix();
    }

    protected function getValidationData(): ValidationData
    {
        $validationData = new ValidationData();
        $validationData->setIssuer($this->getIssuer());
        $validationData->setAudience($this->getPermittedFor());

        return $validationData;
    }
}