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;
+ }
}
/**
}
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));
+}
$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());
}
$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;
}
$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());
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 = '<script>alert("title");</script>';
$headerLink = '<script>alert("header_link");</script>';
$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');
}
/**
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));
+ }
}
<label for="updateCheck"> Notify me when a new release is ready</label>
</td>
</tr>
+ <tr>
+ <td valign="top"><b>Enable API</b></td>
+ <td>
+ <input type="checkbox" name="apiEnabled" id="apiEnabled"
+ {if="$api_enabled"}checked{/if}/>
+ <label for="apiEnabled"> Allow third party software to use Shaarli such as mobile application.</label>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top"><b>API secret</b></td>
+ <td>
+ <input type="text" name="apiSecret" id="apiSecret" size="50" value="{$api_secret}" />
+ </td>
+ </tr>
<tr>
<td></td>
<tr><td valign="top"><b>Update:</b></td><td>
<input type="checkbox" name="updateCheck" id="updateCheck" checked="checked"><label for="updateCheck"> Notify me when a new release is ready</label></td>
</tr>
+ <tr>
+ <td valign="top">
+ <b>API:</b>
+ </td>
+ <td>
+ <input type="checkbox" name="enableApi" id="enableApi" checked="checked">
+ <label for="enableApi">
+ Enable Shaarli's API.
+ Allow third party software to use Shaarli such as mobile application.
+ </label>
+ </td>
+ </tr>
<tr><td colspan="2"><input type="submit" name="Save" value="Save config" class="bigbutton"></td></tr>
</table>
</form>