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; 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); 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('feed.rss_permalinks', !empty($_POST['enableRssPermalinks']));
$conf->set('updates.check_updates', !empty($_POST['updateCheck'])); $conf->set('updates.check_updates', !empty($_POST['updateCheck']));
$conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks'])); $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks']));
$conf->set('api.enabled', !empty($_POST['apiEnabled']));
$conf->set('api.secret', escape($_POST['apiSecret']));
try { try {
$conf->write(isLoggedIn()); $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_rss_permalinks', $conf->get('feed.rss_permalinks', false));
$PAGE->assign('enable_update_check', $conf->get('updates.check_updates', true)); $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('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'); $PAGE->renderPage('configure');
exit; exit;
} }
@ -1952,6 +1956,14 @@ function install($conf)
$conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER))); $conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER)));
} }
$conf->set('updates.check_updates', !empty($_POST['updateCheck'])); $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 { try {
// Everything is ok, let's create config file. // Everything is ok, let's create config file.
$conf->write(isLoggedIn()); $conf->write(isLoggedIn());

View file

@ -289,6 +289,42 @@ public function testEscapeConfig()
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');
}
/** /**
* Test updateMethodDatastoreIds(). * Test updateMethodDatastoreIds().
*/ */

View file

@ -253,4 +253,21 @@ public function testIsSessionIdInvalid()
is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=') 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> <label for="updateCheck">&nbsp;Notify me when a new release is ready</label>
</td> </td>
</tr> </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> <tr>
<td></td> <td></td>

View file

@ -14,6 +14,18 @@ <h1>Shaarli</h1>
<tr><td valign="top"><b>Update:</b></td><td> <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> <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>
<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> <tr><td colspan="2"><input type="submit" name="Save" value="Save config" class="bigbutton"></td></tr>
</table> </table>
</form> </form>