Prepare settings for the API in the admin page and during the install

API settings:
   - api.enabled
   - api.secret

The API settings will be initialized (and the secret generated) with an update method.
This commit is contained in:
ArthurHoaro 2016-07-31 10:46:17 +02:00
parent 624f999fb7
commit cbfdcff261
7 changed files with 142 additions and 2 deletions

View file

@ -256,6 +256,29 @@ public function updateMethodDatastoreIds()
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;
}
}
/**

View file

@ -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));
}

View file

@ -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());

View file

@ -271,7 +271,7 @@ public function testConfigToJsonNothingToDo()
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>';
@ -286,7 +286,43 @@ public function testEscapeConfig()
$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');
}
/**

View file

@ -253,4 +253,21 @@ public function testIsSessionIdInvalid()
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));
}
}

View file

@ -80,6 +80,20 @@
<label for="updateCheck">&nbsp;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">&nbsp;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>

View file

@ -14,6 +14,18 @@ <h1>Shaarli</h1>
<tr><td valign="top"><b>Update:</b></td><td>
<input type="checkbox" name="updateCheck" id="updateCheck" checked="checked"><label for="updateCheck">&nbsp;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">
&nbsp;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>