Skip to content

Commit

Permalink
feat: enable bridges using env var (#3428)
Browse files Browse the repository at this point in the history
* refactor: bridgefactory, add tests

* refactor: move defaultly enabled bridges to config

* refactor

* refactor

* feat: add support for enabling bridges with env var
  • Loading branch information
dvikan authored Jun 11, 2023
1 parent d9490c6 commit 0a8fe57
Show file tree
Hide file tree
Showing 19 changed files with 160 additions and 163 deletions.
20 changes: 6 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ chown www-data:www-data /var/www/rss-bridge/cache

# Optionally copy over the default config file
cp config.default.ini.php config.ini.php

# Optionally copy over the default whitelist file
cp whitelist.default.txt whitelist.txt
```

Example config for nginx:
Expand Down Expand Up @@ -169,22 +166,17 @@ Learn more in [bridge api](https://rss-bridge.github.io/rss-bridge/Bridge_API/in

### How to enable all bridges

Write an asterisks to `whitelist.txt`:

echo '*' > whitelist.txt

Learn more in [enabling briges](https://rss-bridge.github.io/rss-bridge/For_Hosts/Whitelisting.html)

### How to enable a bridge
enabled_bridges[] = *

Add the bridge name to `whitelist.txt`:
### How to enable some bridges

echo 'FirefoxAddonsBridge' >> whitelist.txt
```
enabled_bridges[] = TwitchBridge
enabled_bridges[] = GettrBridge
```
### How to enable debug mode
Set in `config.ini.php`:

enable_debug_mode = true
Learn more in [debug mode](https://rss-bridge.github.io/rss-bridge/For_Developers/Debug_mode.html).
Expand Down
8 changes: 2 additions & 6 deletions actions/ConnectivityAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,14 @@ public function execute(array $request)
return render_template('connectivity.html.php');
}

$bridgeClassName = $this->bridgeFactory->sanitizeBridgeName($request['bridge']);

if ($bridgeClassName === null) {
throw new \InvalidArgumentException('Bridge name invalid!');
}
$bridgeClassName = $this->bridgeFactory->createBridgeClassName($request['bridge']);

return $this->reportBridgeConnectivity($bridgeClassName);
}

private function reportBridgeConnectivity($bridgeClassName)
{
if (!$this->bridgeFactory->isWhitelisted($bridgeClassName)) {
if (!$this->bridgeFactory->isEnabled($bridgeClassName)) {
throw new \Exception('Bridge is not whitelisted!');
}

Expand Down
2 changes: 1 addition & 1 deletion actions/DetectAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function execute(array $request)
$bridgeFactory = new BridgeFactory();

foreach ($bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
if (!$bridgeFactory->isWhitelisted($bridgeClassName)) {
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
continue;
}

Expand Down
11 changes: 2 additions & 9 deletions actions/DisplayAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,13 @@ public function execute(array $request)
{
$bridgeFactory = new BridgeFactory();

$bridgeClassName = null;
if (isset($request['bridge'])) {
$bridgeClassName = $bridgeFactory->sanitizeBridgeName($request['bridge']);
}

if ($bridgeClassName === null) {
throw new \InvalidArgumentException('Bridge name invalid!');
}
$bridgeClassName = $bridgeFactory->createBridgeClassName($request['bridge'] ?? '');

$format = $request['format'] ?? null;
if (!$format) {
throw new \Exception('You must specify a format!');
}
if (!$bridgeFactory->isWhitelisted($bridgeClassName)) {
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
throw new \Exception('This bridge is not whitelisted');
}

Expand Down
2 changes: 1 addition & 1 deletion actions/FrontpageAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function execute(array $request)

$body = '';
foreach ($bridgeClassNames as $bridgeClassName) {
if ($bridgeFactory->isWhitelisted($bridgeClassName)) {
if ($bridgeFactory->isEnabled($bridgeClassName)) {
$body .= BridgeCard::displayBridgeCard($bridgeClassName, $formats);
$activeBridges++;
} elseif ($showInactive) {
Expand Down
2 changes: 1 addition & 1 deletion actions/ListAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function execute(array $request)
$bridge = $bridgeFactory->create($bridgeClassName);

$list->bridges[$bridgeClassName] = [
'status' => $bridgeFactory->isWhitelisted($bridgeClassName) ? 'active' : 'inactive',
'status' => $bridgeFactory->isEnabled($bridgeClassName) ? 'active' : 'inactive',
'uri' => $bridge->getURI(),
'donationUri' => $bridge->getDonationURI(),
'name' => $bridge->getName(),
Expand Down
11 changes: 2 additions & 9 deletions actions/SetBridgeCacheAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,10 @@ public function execute(array $request)

$bridgeFactory = new BridgeFactory();

$bridgeClassName = null;
if (isset($request['bridge'])) {
$bridgeClassName = $bridgeFactory->sanitizeBridgeName($request['bridge']);
}

if ($bridgeClassName === null) {
throw new \InvalidArgumentException('Bridge name invalid!');
}
$bridgeClassName = $bridgeFactory->createBridgeClassName($request['bridge'] ?? '');

// whitelist control
if (!$bridgeFactory->isWhitelisted($bridgeClassName)) {
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
throw new \Exception('This bridge is not whitelisted', 401);
die;
}
Expand Down
11 changes: 11 additions & 0 deletions config.default.ini.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@

[system]

; Only these bridges are available for feed production
enabled_bridges[] = Youtube
enabled_bridges[] = Twitter
enabled_bridges[] = Telegram
enabled_bridges[] = Reddit
enabled_bridges[] = Filter
enabled_bridges[] = Vk
enabled_bridges[] = FeedMerge
enabled_bridges[] = Twitch
enabled_bridges[] = ThePirateBay

; Defines the timezone used by RSS-Bridge
; Find a list of supported timezones at
; https://www.php.net/manual/en/timezones.php
Expand Down
2 changes: 1 addition & 1 deletion docs/03_For_Hosts/03_Docker_Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,5 @@ services:
If you want to add a bridge that is not part of [`/bridges`](https://github.com/RSS-Bridge/rss-bridge/tree/master/bridges), you can map a folder to the `/config` folder of the `rss-bridge` container.

1. Create a folder in the location of your docker-compose.yml or your general docker working area (in this example it will be `/home/docker/rssbridge/config` ).
2. Copy your [custom bridges](../05_Bridge_API/01_How_to_create_a_new_bridge.md) to the `/home/docker/rssbridge/config` folder. You can also add your custom [whitelist.txt](../03_For_Hosts/05_Whitelisting.md) file and your custom [config.ini.php](../03_For_Hosts/08_Custom_Configuration.md) to this folder.
2. Copy your [custom bridges](../05_Bridge_API/01_How_to_create_a_new_bridge.md) to the `/home/docker/rssbridge/config` folder. Applies also to [config.ini.php](../03_For_Hosts/08_Custom_Configuration.md).
3. Map the folder to `/config` inside the container. To do that, replace the `</local/custom/path>` from the previous examples with `/home/docker/rssbridge/config`
2 changes: 1 addition & 1 deletion docs/03_For_Hosts/04_Heroku_Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ You can simply press the button below to easily deploy RSS Bridge on Heroku and

![image](../images/fork_button.png)

2. To customise what bridges can be used if need, create a `whitelist.txt` file in your fork and follow the instructions given [here](../03_For_Hosts/05_Whitelisting.md). You don’t need to do this if you’re fine with the default bridges.
2. To customise what bridges can be used if need, see [here](../03_For_Hosts/05_Whitelisting.md). You don’t need to do this if you’re fine with the default bridges.

3. [Log in to Heroku](https://dashboard.heroku.com) and create a new app. The app name will be the URL of the RSS Bridge (appname.herokuapp.com)

Expand Down
44 changes: 16 additions & 28 deletions docs/03_For_Hosts/05_Whitelisting.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,26 @@
RSS-Bridge supports whitelists in order to limit the available bridges on your web server.
Modify `config.ini.php` to limit available bridges.

A default whitelist file (`whitelist.default.txt`) is shipped with RSS-Bridge. Please do not edit this file, as it gets replaced when upgrading RSS-Bridge!
## Enable all bridges

You should, however, use this file as template to create your own whitelist (or leave it as is, to keep the default bridges). In order to create your own whitelist perform following actions:

* Copy the file `whitelist.default.txt` in the RSS-Bridge root folder
* Rename the new file to `whitelist.txt`
* Change the lines to satisfy your requirements

RSS-Bridge will automatically detect the `whitelist.txt` and use it. If the file doesn't exist it will default to `whitelist.default.txt` automatically.

# Specific whitelisting
```
enabled_bridges[] = *
```

In order to specifically whitelist bridges, open `whitelist.txt` and add one line for each bridge you want to show. Make sure you use normal [line-feeds](https://en.wikipedia.org/wiki/Newline "Line-feed") at the end of a line (LF not [CRLF](https://en.wikipedia.org/wiki/Carriage_return "Carriage-return line-feed")). The bridge name must match the filename of the bridge in the bridges folder (see [folder structure](../04_For_Developers/03_Folder_structure.md)). The name may or may not include the 'Bridge' part.
## Enable some bridges

**Examples**:
```TEXT
FacebookBridge
WikipediaBridge
TwitterBridge
```
enabled_bridges[] = TwitchBridge
enabled_bridges[] = GettrBridge
```

or
## Enable all bridges (legacy shortcut)

```TEXT
Facebook
Wikipedia
Twitter
```
echo '*' > whitelist.txt
```

# Global whitelisting

In order to globally whitelist all bridges, open the `whitelist.txt` file, remove all contents and just write an asterisk `*` into the file (only this one character).
## Enable some bridges (legacy shortcut)

```TEXT
*
```
```
echo -e "TwitchBridge\nTwitterBridge" > whitelist.txt
```
92 changes: 30 additions & 62 deletions lib/BridgeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,101 +2,69 @@

final class BridgeFactory
{
/** @var array<class-string<BridgeInterface>> */
private $bridgeClassNames = [];

/** @var array<class-string<BridgeInterface>> */
private $whitelist = [];
private $enabledBridges = [];

public function __construct()
{
// create names
// Create all possible bridge class names from fs
foreach (scandir(__DIR__ . '/../bridges/') as $file) {
if (preg_match('/^([^.]+Bridge)\.php$/U', $file, $m)) {
$this->bridgeClassNames[] = $m[1];
}
}

// create whitelist
if (file_exists(WHITELIST)) {
$contents = trim(file_get_contents(WHITELIST));
} elseif (file_exists(WHITELIST_DEFAULT)) {
$contents = trim(file_get_contents(WHITELIST_DEFAULT));
} else {
$contents = '';
$enabledBridges = Configuration::getConfig('system', 'enabled_bridges');
if ($enabledBridges === null) {
throw new \Exception('No bridges are enabled... wtf?');
}

if ($contents === '*') {
// Whitelist all bridges
$this->whitelist = $this->getBridgeClassNames();
} else {
foreach (explode("\n", $contents) as $bridgeName) {
$bridgeClassName = $this->sanitizeBridgeName($bridgeName);
if ($bridgeClassName !== null) {
$this->whitelist[] = $bridgeClassName;
}
foreach ($enabledBridges as $enabledBridge) {
if ($enabledBridge === '*') {
$this->enabledBridges = $this->bridgeClassNames;
break;
}
$this->enabledBridges[] = $this->createBridgeClassName($enabledBridge);
}
}

/**
* @param class-string<BridgeInterface> $name
*/
public function create(string $name): BridgeInterface
{
return new $name();
}

/**
* @return array<class-string<BridgeInterface>>
*/
public function getBridgeClassNames(): array
public function isEnabled(string $bridgeName): bool
{
return $this->bridgeClassNames;
return in_array($bridgeName, $this->enabledBridges);
}

/**
* @param class-string<BridgeInterface>|null $name
*/
public function isWhitelisted(string $name): bool
public function createBridgeClassName(string $bridgeName): ?string
{
return in_array($name, $this->whitelist);
}
$name = self::normalizeBridgeName($bridgeName);
$namesLoweredCase = array_map('strtolower', $this->bridgeClassNames);
$nameLoweredCase = strtolower($name);

/**
* Tries to turn a potentially human produced bridge name into a class name.
*
* @param mixed $name
* @return class-string<BridgeInterface>|null
*/
public function sanitizeBridgeName($name): ?string
{
if (!is_string($name)) {
return null;
if (! in_array($nameLoweredCase, $namesLoweredCase)) {
throw new \Exception(sprintf('Bridge name invalid: %s', $bridgeName));
}

// Trim trailing '.php' if exists
$index = array_search($nameLoweredCase, $namesLoweredCase);

return $this->bridgeClassNames[$index];
}

public static function normalizeBridgeName(string $name)
{
if (preg_match('/(.+)(?:\.php)/', $name, $matches)) {
$name = $matches[1];
}

// Append 'Bridge' suffix if not present.
if (!preg_match('/(Bridge)$/i', $name)) {
$name = sprintf('%sBridge', $name);
}
return $name;
}

// Improve performance for correctly written bridge names
if (in_array($name, $this->getBridgeClassNames())) {
$index = array_search($name, $this->getBridgeClassNames());
return $this->getBridgeClassNames()[$index];
}

// The name is valid if a corresponding bridge file is found on disk
if (in_array(strtolower($name), array_map('strtolower', $this->getBridgeClassNames()))) {
$index = array_search(strtolower($name), array_map('strtolower', $this->getBridgeClassNames()));
return $this->getBridgeClassNames()[$index];
}

return null;
public function getBridgeClassNames(): array
{
return $this->bridgeClassNames;
}
}
Loading

0 comments on commit 0a8fe57

Please sign in to comment.