Browse Source

Add basic authentication support (#728)

* Move configuration in its own class in order to reduce the verbosity of index.php
* Add authentication mechanism using HTTP auth
* Add a method to get the config parameters
* Remove the installation checks from the index page
* Log all failed authentication attempts
Teromene 4 years ago
parent
commit
937ea49271
5 changed files with 164 additions and 89 deletions
  1. 18 1
      config.default.ini.php
  2. 8 88
      index.php
  3. 31 0
      lib/Authentication.php
  4. 105 0
      lib/Configuration.php
  5. 2 0
      lib/RssBridge.php

+ 18 - 1
config.default.ini.php

@@ -24,4 +24,21 @@ name = "Hidden proxy name"
 ; Allow users to disable proxy usage for specific requests.
 ; true  = enabled
 ; false = disabled (default)
-by_bridge = false
+by_bridge = false
+
+[authentication]
+
+; Enables authentication for all requests to this RSS-Bridge instance.
+;
+; Warning: You'll have to upgrade existing feeds after enabling this option!
+;
+; true  = enabled
+; false = disabled (default)
+enable = false
+
+; The username for authentication. Insert this name when prompted for login.
+username = ""
+
+; The password for authentication. Insert this password when prompted for login.
+; Use a strong password to prevent others from guessing your login!
+password = ""

+ 8 - 88
index.php

@@ -1,67 +1,21 @@
 <?php
-/*
-TODO :
-- factorize the annotation system
-- factorize to adapter : Format, Bridge, Cache(actually code is almost the same)
-- implement annotation cache for entrance page
-- Cache : I think logic must be change as least to avoid to reconvert object from json in FileCache case.
-- add namespace to avoid futur problem ?
-- see FIXME mentions in the code
-- implement header('X-Cached-Version: '.date(DATE_ATOM, filemtime($cachefile)));
-*/
-
-if(!file_exists('config.default.ini.php'))
-	die('The default configuration file "config.default.ini.php" is missing!');
-
-$config = parse_ini_file('config.default.ini.php', true, INI_SCANNER_TYPED);
-if(!$config)
-	die('Error parsing config.default.ini.php');
-
-if(file_exists('config.ini.php')) {
-	// Replace default configuration with custom settings
-	foreach(parse_ini_file('config.ini.php', true, INI_SCANNER_TYPED) as $header => $section) {
-		foreach($section as $key => $value) {
-			// Skip unknown sections and keys
-			if(array_key_exists($header, $config) && array_key_exists($key, $config[$header])) {
-				$config[$header][$key] = $value;
-			}
-		}
-	}
-}
-
-if(!is_string($config['proxy']['url']))
-	die('Parameter [proxy] => "url" is not a valid string! Please check "config.ini.php"!');
-
-if(!empty($config['proxy']['url']))
-	define('PROXY_URL', $config['proxy']['url']);
-
-if(!is_bool($config['proxy']['by_bridge']))
-	die('Parameter [proxy] => "by_bridge" is not a valid Boolean! Please check "config.ini.php"!');
-
-define('PROXY_BYBRIDGE', $config['proxy']['by_bridge']);
-
-if(!is_string($config['proxy']['name']))
-	die('Parameter [proxy] => "name" is not a valid string! Please check "config.ini.php"!');
-
-define('PROXY_NAME', $config['proxy']['name']);
-
-if(!is_bool($config['cache']['custom_timeout']))
-	die('Parameter [cache] => "custom_timeout" is not a valid Boolean! Please check "config.ini.php"!');
-
-define('CUSTOM_CACHE_TIMEOUT', $config['cache']['custom_timeout']);
+require_once __DIR__ . '/lib/RssBridge.php';
 
-// Defines the minimum required PHP version for RSS-Bridge
 define('PHP_VERSION_REQUIRED', '5.6.0');
 
-date_default_timezone_set('UTC');
-error_reporting(0);
-
 // Specify directory for cached files (using FileCache)
 define('CACHE_DIR', __DIR__ . '/cache');
 
 // Specify path for whitelist file
 define('WHITELIST_FILE', __DIR__ . '/whitelist.txt');
 
+Configuration::verifyInstallation();
+Configuration::loadConfiguration();
+
+Authentication::showPromptIfNeeded();
+
+date_default_timezone_set('UTC');
+error_reporting(0);
 
 /*
 Move the CLI arguments to the $_GET array, in order to be able to use
@@ -91,40 +45,6 @@ if(file_exists('DEBUG')) {
 	}
 }
 
-require_once __DIR__ . '/lib/RssBridge.php';
-
-// Check PHP version
-if(version_compare(PHP_VERSION, PHP_VERSION_REQUIRED) === -1)
-	die('RSS-Bridge requires at least PHP version ' . PHP_VERSION_REQUIRED . '!');
-
-// extensions check
-if(!extension_loaded('openssl'))
-	die('"openssl" extension not loaded. Please check "php.ini"');
-
-if(!extension_loaded('libxml'))
-	die('"libxml" extension not loaded. Please check "php.ini"');
-
-if(!extension_loaded('mbstring'))
-	die('"mbstring" extension not loaded. Please check "php.ini"');
-
-if(!extension_loaded('simplexml'))
-	die('"simplexml" extension not loaded. Please check "php.ini"');
-
-if(!extension_loaded('curl'))
-	die('"curl" extension not loaded. Please check "php.ini"');
-
-// configuration checks
-if(ini_get('allow_url_fopen') !== "1")
-	die('"allow_url_fopen" is not set to "1". Please check "php.ini');
-
-// Check cache folder permissions (write permissions required)
-if(!is_writable(CACHE_DIR))
-	die('RSS-Bridge does not have write permissions for ' . CACHE_DIR . '!');
-
-// Check whitelist file permissions (only in DEBUG mode)
-if(!file_exists(WHITELIST_FILE) && !is_writable(dirname(WHITELIST_FILE)))
-	die('RSS-Bridge does not have write permissions for ' . WHITELIST_FILE . '!');
-
 // FIXME : beta test UA spoofing, please report any blacklisting by PHP-fopen-unfriendly websites
 
 $userAgent = 'Mozilla/5.0(X11; Linux x86_64; rv:30.0)';

+ 31 - 0
lib/Authentication.php

@@ -0,0 +1,31 @@
+<?php
+class Authentication {
+
+	public static function showPromptIfNeeded() {
+
+		if(Configuration::getConfig('authentication', 'enable') === true) {
+			if(!Authentication::verifyPrompt()) {
+				header('WWW-Authenticate: Basic realm="RSS-Bridge"');
+				header('HTTP/1.0 401 Unauthorized');
+				die('Please authenticate in order to access this instance !');
+			}
+
+		}
+
+	}
+
+	public static function verifyPrompt() {
+
+		if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
+			if(Configuration::getConfig('authentication', 'username') === $_SERVER['PHP_AUTH_USER']
+				&& Configuration::getConfig('authentication', 'password') === $_SERVER['PHP_AUTH_PW']) {
+				return true;
+			} else {
+				error_log('[RSS-Bridge] Failed authentication attempt from ' . $_SERVER['REMOTE_ADDR']);
+			}
+		}
+		return false;
+
+	}
+
+}

+ 105 - 0
lib/Configuration.php

@@ -0,0 +1,105 @@
+<?php
+class Configuration {
+
+	public static $config = null;
+
+	public static function verifyInstallation() {
+
+		// Check PHP version
+		if(version_compare(PHP_VERSION, PHP_VERSION_REQUIRED) === -1)
+			die('RSS-Bridge requires at least PHP version ' . PHP_VERSION_REQUIRED . '!');
+
+		// extensions check
+		if(!extension_loaded('openssl'))
+			die('"openssl" extension not loaded. Please check "php.ini"');
+
+		if(!extension_loaded('libxml'))
+			die('"libxml" extension not loaded. Please check "php.ini"');
+
+		if(!extension_loaded('mbstring'))
+			die('"mbstring" extension not loaded. Please check "php.ini"');
+
+		if(!extension_loaded('simplexml'))
+			die('"simplexml" extension not loaded. Please check "php.ini"');
+
+		if(!extension_loaded('curl'))
+			die('"curl" extension not loaded. Please check "php.ini"');
+
+		// configuration checks
+		if(ini_get('allow_url_fopen') !== "1")
+			die('"allow_url_fopen" is not set to "1". Please check "php.ini');
+
+		// Check cache folder permissions (write permissions required)
+		if(!is_writable(CACHE_DIR))
+			die('RSS-Bridge does not have write permissions for ' . CACHE_DIR . '!');
+
+		// Check whitelist file permissions (only in DEBUG mode)
+		if(!file_exists(WHITELIST_FILE) && !is_writable(dirname(WHITELIST_FILE)))
+			die('RSS-Bridge does not have write permissions for ' . WHITELIST_FILE . '!');
+
+	}
+
+	public static function loadConfiguration() {
+
+		if(!file_exists('config.default.ini.php'))
+			die('The default configuration file "config.default.ini.php" is missing!');
+
+		Configuration::$config = parse_ini_file('config.default.ini.php', true, INI_SCANNER_TYPED);
+		if(!Configuration::$config)
+			die('Error parsing config.default.ini.php');
+
+		if(file_exists('config.ini.php')) {
+			// Replace default configuration with custom settings
+			foreach(parse_ini_file('config.ini.php', true, INI_SCANNER_TYPED) as $header => $section) {
+				foreach($section as $key => $value) {
+					// Skip unknown sections and keys
+					if(array_key_exists($header, Configuration::$config) && array_key_exists($key, Configuration::$config[$header])) {
+						Configuration::$config[$header][$key] = $value;
+					}
+				}
+			}
+		}
+
+		if(!is_string(self::getConfig('proxy', 'url')))
+			die('Parameter [proxy] => "url" is not a valid string! Please check "config.ini.php"!');
+
+		if(!empty(self::getConfig('proxy', 'url')))
+			define('PROXY_URL', self::getConfig('proxy', 'url'));
+
+		if(!is_bool(self::getConfig('proxy', 'by_bridge')))
+			die('Parameter [proxy] => "by_bridge" is not a valid Boolean! Please check "config.ini.php"!');
+
+		define('PROXY_BYBRIDGE', self::getConfig('proxy', 'by_bridge'));
+
+		if(!is_string(self::getConfig('proxy', 'name')))
+			die('Parameter [proxy] => "name" is not a valid string! Please check "config.ini.php"!');
+
+		define('PROXY_NAME', self::getConfig('proxy', 'name'));
+
+		if(!is_bool(self::getConfig('cache', 'custom_timeout')))
+			die('Parameter [cache] => "custom_timeout" is not a valid Boolean! Please check "config.ini.php"!');
+
+		define('CUSTOM_CACHE_TIMEOUT', self::getConfig('cache', 'custom_timeout'));
+
+		if(!is_bool(self::getConfig('authentication', 'enable')))
+			die('Parameter [authentication] => "enable" is not a valid Boolean! Please check "config.ini.php"!');
+
+		if(!is_string(self::getConfig('authentication', 'username')))
+			die('Parameter [authentication] => "username" is not a valid string! Please check "config.ini.php"!');
+
+		if(!is_string(self::getConfig('authentication', 'password')))
+			die('Parameter [authentication] => "password" is not a valid string! Please check "config.ini.php"!');
+
+	}
+
+	public static function getConfig($category, $key) {
+
+		if(array_key_exists($category, self::$config) && array_key_exists($key, self::$config[$category])) {
+			return self::$config[$category][$key];
+		}
+
+		return null;
+
+	}
+
+}

+ 2 - 0
lib/RssBridge.php

@@ -14,6 +14,8 @@ require __DIR__ . '/Bridge.php';
 require __DIR__ . '/BridgeAbstract.php';
 require __DIR__ . '/FeedExpander.php';
 require __DIR__ . '/Cache.php';
+require __DIR__ . '/Authentication.php';
+require __DIR__ . '/Configuration.php';
 
 require __DIR__ . '/validation.php';
 require __DIR__ . '/html.php';