Skip to content

Commit

Permalink
Introduced hotlink prevention & hotlink management
Browse files Browse the repository at this point in the history
  • Loading branch information
lindseydiloreto committed Apr 9, 2024
1 parent 53a94a4 commit 64bb988
Show file tree
Hide file tree
Showing 18 changed files with 326 additions and 38 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## Unreleased

> [!WARNING]
> [Hotlinking](https://plugins.doublesecretagency.com/digital-download/hotlinking/) from other websites is **prevented by default** for all new links. Hotlinking can be enabled either on the "Settings" page, or when configuring an individual token.
### Added
- Introduced hotlink prevention (and enabled it by default).
- Made it possible to [allow hotlinks](https://plugins.doublesecretagency.com/digital-download/hotlinking/#allowing-hotlinks) from other websites.

### Changed
- [Hotlinking](https://plugins.doublesecretagency.com/digital-download/hotlinking/) is now prevented by default.

## 2.2.1 - 2022-08-02

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ module.exports = {
{text: 'Storing a Token', link: '/storing-a-token/'},
{text: 'Short Download Links', link: '/short-download-links/'},
{text: 'Get Link Data from a Token', link: '/get-link-data-from-a-token/'},
{text: 'Hotlinking', link: '/hotlinking/'},
]
},
{
Expand All @@ -55,6 +56,7 @@ module.exports = {
'storing-a-token',
'short-download-links',
'get-link-data-from-a-token',
'hotlinking',
],
}
}
Expand Down
Binary file added docs/.vuepress/public/images/allow-hotlinks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 31 additions & 6 deletions docs/creating-a-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,15 @@ $token = DigitalDownload::$plugin->digitalDownload->createToken($asset, [
- **asset** - The downloadable Asset.
- **options** - A key-value set of options.

| Option | Default | Description
|:-------------|:---------|:------------------
| expires | `'P14D'` | Time until asset key expires. Accepts any valid PHP interval specification.
| maxDownloads | `0` | Maximum number of downloads allowed. (0 = unlimited)
| requireUser | _null_ | If downloads are restricted to specific users and/or user groups.
| headers | `{}` | Associative array of optional HTTP headers to send during download.
| Option | Default | Description |
|:--------------------------------|:---------|:----------------------------------------------------------------------------|
| expires | `'P14D'` | Time until asset key expires. Accepts any valid PHP interval specification. |
| maxDownloads | `0` | Maximum number of downloads allowed. (0 = unlimited) |
| [requireUser](#requireuser) | _null_ | If downloads are restricted to specific users and/or user groups. |
| [allowHotlinks](#allowhotlinks) | `'none'` | Whether to allow [hotlinking](/hotlinking/) of download link. |
| headers | `{}` | Associative array of optional HTTP headers to send during download. |

### `requireUser`

The `requireUser` option can take on a variety of forms:

Expand All @@ -68,3 +71,25 @@ null // Anyone can download (default)

['mygroup', 467] // Mix & match users and groups
```

### `allowHotlinks`

There are three ways to use the `allowHotlinks` option:

#### Prevent hotlinking from all other websites. (default)
```twig
'allowHotlinks': 'none'
```

#### Allow hotlinking from all other websites.
```twig
'allowHotlinks': 'all'
```

#### Allow hotlinking from specified sites (an array of domains).
```twig
'allowHotlinks': [
'friendlysite.com',
'anotherfriendlysite.com'
]
```
31 changes: 31 additions & 0 deletions docs/hotlinking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
description: Protect your download links by preventing downloads from other domains.
---

# Hotlinking

Hotlinking (also known as leeching) is when another website links directly to your downloadable content. For a variety of reasons, you probably don't want your content to be downloadable from other websites.

:::warning Prevented by default since v2.3 (in Craft 4) and v3.1 (in Craft 5)
Once hotlink prevention was introduced, prevention became the default behavior for all new links.
:::

## Allowing Hotlinks

Hotlinking from other websites is **forbidden by default.** When necessary, there are two ways to configure hotlinking:

### Option 1 - Configure availability per each link

For maximum control, configure the availability of individual links when [creating each token](/creating-a-token/#createtoken-asset-options).

Use the `allowHotlinks` option to configure exactly how hotlinks should be allowed.

### Option 2 - Configure availability globally via plugin's Settings

For global control, edit the plugin's Settings page. Set the "Allow Hotlinks" field to manage the availability of all links in the system.

<img width="520" :src="$withBase('/images/allow-hotlinks.png')" class="dropshadow" alt="">

If the option to "only hotlink from specified sites" is selected, you'll then be prompted to specify a whitelist of friendly domains.

<img width="520" :src="$withBase('/images/hotlink-domain-whitelist.png')" class="dropshadow" alt="">
2 changes: 1 addition & 1 deletion src/DigitalDownload.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class DigitalDownload extends Plugin
/**
* @inheritdoc
*/
public string $schemaVersion = '2.1.0';
public string $schemaVersion = '3.1.0';

/**
* @inheritdoc
Expand Down
1 change: 1 addition & 0 deletions src/migrations/Install.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ protected function createTables(): void
'id' => $this->primaryKey(),
'assetId' => $this->integer()->notNull(),
'token' => $this->string(),
'allowHotlinks' => $this->string(),
'headers' => $this->text(),
'enabled' => $this->boolean()->defaultValue(true),
'expires' => $this->dateTime(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/**
* Digital Download plugin for Craft CMS
*
* Provide secure digital download links to your files.
*
* @author Double Secret Agency
* @link https://www.doublesecretagency.com/
* @copyright Copyright (c) 2016 Double Secret Agency
*/

namespace doublesecretagency\digitaldownload\migrations;

use craft\db\Migration;
use yii\base\NotSupportedException;

/**
* Migration: Add allowHotlinks column
* @since 3.1.0
*/
class m240408_000000_digitalDownload_addAllowHotlinksColumn extends Migration
{

/**
* @inheritdoc
* @throws NotSupportedException
*/
public function safeUp(): void
{
$table = '{{%digitaldownload_tokens}}';
if (!$this->db->columnExists($table, 'allowHotlinks')) {
$this->addColumn($table, 'allowHotlinks', $this->string()->after('token'));
}
$this->update($table, ['allowHotlinks' => 'all']);
}

/**
* @inheritdoc
*/
public function safeDown(): bool
{
echo "m240408_000000_digitalDownload_addAllowHotlinksColumn cannot be reverted.\n";

return false;
}

}
5 changes: 5 additions & 0 deletions src/models/Link.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ class Link extends Model
*/
public string $token;

/**
* @var string|array Which domains (besides this one) can download linked files.
*/
public string|array $allowHotlinks = 'none';

/**
* @var string Optionally append or replace download headers.
*/
Expand Down
10 changes: 10 additions & 0 deletions src/models/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ class Settings extends Model
*/
public string $shortPath = 'download';

/**
* @var string Which domains (besides this one) can download linked files.
*/
public string $allowHotlinks = 'none';

/**
* @var array List of domains allowed to hotlink downloadable files.
*/
public array $hotlinksWhitelist = [];

/**
* @var bool Whether to keep a detailed log of all downloads.
*/
Expand Down
3 changes: 2 additions & 1 deletion src/records/Token.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* @property int $id
* @property int $assetId
* @property string $token
* @property string|array $allowHotlinks
* @property string $headers
* @property int $enabled
* @property DateTime $expires
Expand Down Expand Up @@ -54,7 +55,7 @@ public static function tableName(): string
*/
public function getAsset(): ActiveQueryInterface
{
return $this->hasOne(Asset::class, ['id' => 'assetId']);
return self::hasOne(Asset::class, ['id' => 'assetId']);
}

}
2 changes: 1 addition & 1 deletion src/resources/js/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function updateDemoPath() {
$demoPath.html(demoPath);
}

// =========================================================================
// ========================================================================= //

// Update path while typing
$shortPath.on('keyup', function() {
Expand Down
6 changes: 3 additions & 3 deletions src/services/DigitalDownloadService.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public function createToken(Asset $file, array $options = []): string
return DigitalDownload::$plugin->digitalDownload_token->createToken($file, $options);
}

// =========================================================================
// ========================================================================= //

/**
* Generates a URL to download the file.
Expand Down Expand Up @@ -122,7 +122,7 @@ public function link(Asset|string $token, array|string $options = [], string $la
return Template::raw("<a href=\"{$url}\">{$label}</a>");
}

// =========================================================================
// ========================================================================= //

/**
* Get the link data from an existing token.
Expand Down Expand Up @@ -183,7 +183,7 @@ public function cleanup(): void
$this->disableExpiredLinks();
}

// =========================================================================
// ========================================================================= //

/**
* Ensures that we're working with a proper token
Expand Down
Loading

0 comments on commit 64bb988

Please sign in to comment.