diff --git a/application/Updater.php b/application/Updater.php index f0d0281..38de335 100644 --- a/application/Updater.php +++ b/application/Updater.php @@ -256,6 +256,29 @@ class Updater return true; } + + /** + * Initialize API settings: + * - api.enabled: true + * - api.secret: generated secret + */ + public function updateMethodApiSettings() + { + if ($this->conf->exists('api.secret')) { + return true; + } + + $this->conf->set('api.enabled', true); + $this->conf->set( + 'api.secret', + generate_api_secret( + $this->conf->get('credentials.login'), + $this->conf->get('credentials.salt') + ) + ); + $this->conf->write($this->isLoggedIn); + return true; + } } /** diff --git a/application/Utils.php b/application/Utils.php index 0a5b476..6290234 100644 --- a/application/Utils.php +++ b/application/Utils.php @@ -231,3 +231,29 @@ function autoLocale($headerLocale) } setlocale(LC_ALL, $attempts); } + +/** + * Generates a default API secret. + * + * Note that the random-ish methods used in this function are predictable, + * which makes them NOT suitable for crypto. + * BUT the random string is salted with the salt and hashed with the username. + * It makes the generated API secret secured enough for Shaarli. + * + * PHP 7 provides random_int(), designed for cryptography. + * More info: http://stackoverflow.com/questions/4356289/php-random-string-generator + + * @param string $username Shaarli login username + * @param string $salt Shaarli password hash salt + * + * @return string|bool Generated API secret, 12 char length. + * Or false if invalid parameters are provided (which will make the API unusable). + */ +function generate_api_secret($username, $salt) +{ + if (empty($username) || empty($salt)) { + return false; + } + + return str_shuffle(substr(hash_hmac('sha512', uniqid($salt), $username), 10, 12)); +} diff --git a/index.php b/index.php index cc44835..25e37b3 100644 --- a/index.php +++ b/index.php @@ -1142,6 +1142,8 @@ function renderPage($conf, $pluginManager) $conf->set('feed.rss_permalinks', !empty($_POST['enableRssPermalinks'])); $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks'])); + $conf->set('api.enabled', !empty($_POST['apiEnabled'])); + $conf->set('api.secret', escape($_POST['apiSecret'])); try { $conf->write(isLoggedIn()); } @@ -1170,6 +1172,8 @@ function renderPage($conf, $pluginManager) $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false)); $PAGE->assign('enable_update_check', $conf->get('updates.check_updates', true)); $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false)); + $PAGE->assign('api_enabled', $conf->get('api.enabled', true)); + $PAGE->assign('api_secret', $conf->get('api.secret')); $PAGE->renderPage('configure'); exit; } @@ -1952,6 +1956,14 @@ function install($conf) $conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER))); } $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); + $conf->set('api.enabled', !empty($_POST['enableApi'])); + $conf->set( + 'api.secret', + generate_api_secret( + $this->conf->get('credentials.login'), + $this->conf->get('credentials.salt') + ) + ); try { // Everything is ok, let's create config file. $conf->write(isLoggedIn()); diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php index 4948fe5..0171daa 100644 --- a/tests/Updater/UpdaterTest.php +++ b/tests/Updater/UpdaterTest.php @@ -271,7 +271,7 @@ $GLOBALS[\'privateLinkByDefault\'] = true;'; public function testEscapeConfig() { $sandbox = 'sandbox/config'; - copy(self::$configFile .'.json.php', $sandbox .'.json.php'); + copy(self::$configFile . '.json.php', $sandbox . '.json.php'); $this->conf = new ConfigManager($sandbox); $title = ''; $headerLink = ''; @@ -286,7 +286,43 @@ $GLOBALS[\'privateLinkByDefault\'] = true;'; $this->assertEquals(escape($title), $this->conf->get('general.title')); $this->assertEquals(escape($headerLink), $this->conf->get('general.header_link')); $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url')); - unlink($sandbox .'.json.php'); + unlink($sandbox . '.json.php'); + } + + /** + * Test updateMethodApiSettings(): create default settings for the API (enabled + secret). + */ + public function testUpdateApiSettings() + { + $confFile = 'sandbox/config'; + copy(self::$configFile .'.json.php', $confFile .'.json.php'); + $conf = new ConfigManager($confFile); + $updater = new Updater(array(), array(), $conf, true); + + $this->assertFalse($conf->exists('api.enabled')); + $this->assertFalse($conf->exists('api.secret')); + $updater->updateMethodApiSettings(); + $conf->reload(); + $this->assertTrue($conf->get('api.enabled')); + $this->assertTrue($conf->exists('api.secret')); + unlink($confFile .'.json.php'); + } + + /** + * Test updateMethodApiSettings(): already set, do nothing. + */ + public function testUpdateApiSettingsNothingToDo() + { + $confFile = 'sandbox/config'; + copy(self::$configFile .'.json.php', $confFile .'.json.php'); + $conf = new ConfigManager($confFile); + $conf->set('api.enabled', false); + $conf->set('api.secret', ''); + $updater = new Updater(array(), array(), $conf, true); + $updater->updateMethodApiSettings(); + $this->assertFalse($conf->get('api.enabled')); + $this->assertEmpty($conf->get('api.secret')); + unlink($confFile .'.json.php'); } /** diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php index 6a7870c..0cf9a92 100644 --- a/tests/UtilsTest.php +++ b/tests/UtilsTest.php @@ -253,4 +253,21 @@ class UtilsTest extends PHPUnit_Framework_TestCase is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=') ); } + + /** + * Test generateSecretApi. + */ + public function testGenerateSecretApi() + { + $this->assertEquals(12, strlen(generate_api_secret('foo', 'bar'))); + } + + /** + * Test generateSecretApi with invalid parameters. + */ + public function testGenerateSecretApiInvalid() + { + $this->assertFalse(generate_api_secret('', '')); + $this->assertFalse(generate_api_secret(false, false)); + } } diff --git a/tpl/configure.html b/tpl/configure.html index 983bcd0..a015770 100644 --- a/tpl/configure.html +++ b/tpl/configure.html @@ -80,6 +80,20 @@ + + Enable API + + + + + + + API secret + + + + diff --git a/tpl/install.html b/tpl/install.html index 88eb540..eda4c54 100644 --- a/tpl/install.html +++ b/tpl/install.html @@ -14,6 +14,18 @@ Update: + + + API: + + + + + +