aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--application/Updater.php23
-rw-r--r--application/Utils.php26
-rw-r--r--index.php12
-rw-r--r--tests/Updater/UpdaterTest.php40
-rw-r--r--tests/UtilsTest.php17
-rw-r--r--tpl/configure.html14
-rw-r--r--tpl/install.html12
7 files changed, 142 insertions, 2 deletions
diff --git a/application/Updater.php b/application/Updater.php
index f0d02814..38de3350 100644
--- a/application/Updater.php
+++ b/application/Updater.php
@@ -256,6 +256,29 @@ class Updater
256 256
257 return true; 257 return true;
258 } 258 }
259
260 /**
261 * Initialize API settings:
262 * - api.enabled: true
263 * - api.secret: generated secret
264 */
265 public function updateMethodApiSettings()
266 {
267 if ($this->conf->exists('api.secret')) {
268 return true;
269 }
270
271 $this->conf->set('api.enabled', true);
272 $this->conf->set(
273 'api.secret',
274 generate_api_secret(
275 $this->conf->get('credentials.login'),
276 $this->conf->get('credentials.salt')
277 )
278 );
279 $this->conf->write($this->isLoggedIn);
280 return true;
281 }
259} 282}
260 283
261/** 284/**
diff --git a/application/Utils.php b/application/Utils.php
index 0a5b476e..62902341 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -231,3 +231,29 @@ function autoLocale($headerLocale)
231 } 231 }
232 setlocale(LC_ALL, $attempts); 232 setlocale(LC_ALL, $attempts);
233} 233}
234
235/**
236 * Generates a default API secret.
237 *
238 * Note that the random-ish methods used in this function are predictable,
239 * which makes them NOT suitable for crypto.
240 * BUT the random string is salted with the salt and hashed with the username.
241 * It makes the generated API secret secured enough for Shaarli.
242 *
243 * PHP 7 provides random_int(), designed for cryptography.
244 * More info: http://stackoverflow.com/questions/4356289/php-random-string-generator
245
246 * @param string $username Shaarli login username
247 * @param string $salt Shaarli password hash salt
248 *
249 * @return string|bool Generated API secret, 12 char length.
250 * Or false if invalid parameters are provided (which will make the API unusable).
251 */
252function generate_api_secret($username, $salt)
253{
254 if (empty($username) || empty($salt)) {
255 return false;
256 }
257
258 return str_shuffle(substr(hash_hmac('sha512', uniqid($salt), $username), 10, 12));
259}
diff --git a/index.php b/index.php
index cc448352..25e37b32 100644
--- a/index.php
+++ b/index.php
@@ -1142,6 +1142,8 @@ function renderPage($conf, $pluginManager)
1142 $conf->set('feed.rss_permalinks', !empty($_POST['enableRssPermalinks'])); 1142 $conf->set('feed.rss_permalinks', !empty($_POST['enableRssPermalinks']));
1143 $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); 1143 $conf->set('updates.check_updates', !empty($_POST['updateCheck']));
1144 $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks'])); 1144 $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks']));
1145 $conf->set('api.enabled', !empty($_POST['apiEnabled']));
1146 $conf->set('api.secret', escape($_POST['apiSecret']));
1145 try { 1147 try {
1146 $conf->write(isLoggedIn()); 1148 $conf->write(isLoggedIn());
1147 } 1149 }
@@ -1170,6 +1172,8 @@ function renderPage($conf, $pluginManager)
1170 $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false)); 1172 $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false));
1171 $PAGE->assign('enable_update_check', $conf->get('updates.check_updates', true)); 1173 $PAGE->assign('enable_update_check', $conf->get('updates.check_updates', true));
1172 $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false)); 1174 $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false));
1175 $PAGE->assign('api_enabled', $conf->get('api.enabled', true));
1176 $PAGE->assign('api_secret', $conf->get('api.secret'));
1173 $PAGE->renderPage('configure'); 1177 $PAGE->renderPage('configure');
1174 exit; 1178 exit;
1175 } 1179 }
@@ -1952,6 +1956,14 @@ function install($conf)
1952 $conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER))); 1956 $conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER)));
1953 } 1957 }
1954 $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); 1958 $conf->set('updates.check_updates', !empty($_POST['updateCheck']));
1959 $conf->set('api.enabled', !empty($_POST['enableApi']));
1960 $conf->set(
1961 'api.secret',
1962 generate_api_secret(
1963 $this->conf->get('credentials.login'),
1964 $this->conf->get('credentials.salt')
1965 )
1966 );
1955 try { 1967 try {
1956 // Everything is ok, let's create config file. 1968 // Everything is ok, let's create config file.
1957 $conf->write(isLoggedIn()); 1969 $conf->write(isLoggedIn());
diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php
index 4948fe52..0171daad 100644
--- a/tests/Updater/UpdaterTest.php
+++ b/tests/Updater/UpdaterTest.php
@@ -271,7 +271,7 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
271 public function testEscapeConfig() 271 public function testEscapeConfig()
272 { 272 {
273 $sandbox = 'sandbox/config'; 273 $sandbox = 'sandbox/config';
274 copy(self::$configFile .'.json.php', $sandbox .'.json.php'); 274 copy(self::$configFile . '.json.php', $sandbox . '.json.php');
275 $this->conf = new ConfigManager($sandbox); 275 $this->conf = new ConfigManager($sandbox);
276 $title = '<script>alert("title");</script>'; 276 $title = '<script>alert("title");</script>';
277 $headerLink = '<script>alert("header_link");</script>'; 277 $headerLink = '<script>alert("header_link");</script>';
@@ -286,7 +286,43 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
286 $this->assertEquals(escape($title), $this->conf->get('general.title')); 286 $this->assertEquals(escape($title), $this->conf->get('general.title'));
287 $this->assertEquals(escape($headerLink), $this->conf->get('general.header_link')); 287 $this->assertEquals(escape($headerLink), $this->conf->get('general.header_link'));
288 $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url')); 288 $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url'));
289 unlink($sandbox .'.json.php'); 289 unlink($sandbox . '.json.php');
290 }
291
292 /**
293 * Test updateMethodApiSettings(): create default settings for the API (enabled + secret).
294 */
295 public function testUpdateApiSettings()
296 {
297 $confFile = 'sandbox/config';
298 copy(self::$configFile .'.json.php', $confFile .'.json.php');
299 $conf = new ConfigManager($confFile);
300 $updater = new Updater(array(), array(), $conf, true);
301
302 $this->assertFalse($conf->exists('api.enabled'));
303 $this->assertFalse($conf->exists('api.secret'));
304 $updater->updateMethodApiSettings();
305 $conf->reload();
306 $this->assertTrue($conf->get('api.enabled'));
307 $this->assertTrue($conf->exists('api.secret'));
308 unlink($confFile .'.json.php');
309 }
310
311 /**
312 * Test updateMethodApiSettings(): already set, do nothing.
313 */
314 public function testUpdateApiSettingsNothingToDo()
315 {
316 $confFile = 'sandbox/config';
317 copy(self::$configFile .'.json.php', $confFile .'.json.php');
318 $conf = new ConfigManager($confFile);
319 $conf->set('api.enabled', false);
320 $conf->set('api.secret', '');
321 $updater = new Updater(array(), array(), $conf, true);
322 $updater->updateMethodApiSettings();
323 $this->assertFalse($conf->get('api.enabled'));
324 $this->assertEmpty($conf->get('api.secret'));
325 unlink($confFile .'.json.php');
290 } 326 }
291 327
292 /** 328 /**
diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php
index 6a7870c4..0cf9a921 100644
--- a/tests/UtilsTest.php
+++ b/tests/UtilsTest.php
@@ -253,4 +253,21 @@ class UtilsTest extends PHPUnit_Framework_TestCase
253 is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=') 253 is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
254 ); 254 );
255 } 255 }
256
257 /**
258 * Test generateSecretApi.
259 */
260 public function testGenerateSecretApi()
261 {
262 $this->assertEquals(12, strlen(generate_api_secret('foo', 'bar')));
263 }
264
265 /**
266 * Test generateSecretApi with invalid parameters.
267 */
268 public function testGenerateSecretApiInvalid()
269 {
270 $this->assertFalse(generate_api_secret('', ''));
271 $this->assertFalse(generate_api_secret(false, false));
272 }
256} 273}
diff --git a/tpl/configure.html b/tpl/configure.html
index 983bcd08..a015770e 100644
--- a/tpl/configure.html
+++ b/tpl/configure.html
@@ -80,6 +80,20 @@
80 <label for="updateCheck">&nbsp;Notify me when a new release is ready</label> 80 <label for="updateCheck">&nbsp;Notify me when a new release is ready</label>
81 </td> 81 </td>
82 </tr> 82 </tr>
83 <tr>
84 <td valign="top"><b>Enable API</b></td>
85 <td>
86 <input type="checkbox" name="apiEnabled" id="apiEnabled"
87 {if="$api_enabled"}checked{/if}/>
88 <label for="apiEnabled">&nbsp;Allow third party software to use Shaarli such as mobile application.</label>
89 </td>
90 </tr>
91 <tr>
92 <td valign="top"><b>API secret</b></td>
93 <td>
94 <input type="text" name="apiSecret" id="apiSecret" size="50" value="{$api_secret}" />
95 </td>
96 </tr>
83 97
84 <tr> 98 <tr>
85 <td></td> 99 <td></td>
diff --git a/tpl/install.html b/tpl/install.html
index 88eb540e..eda4c54d 100644
--- a/tpl/install.html
+++ b/tpl/install.html
@@ -14,6 +14,18 @@
14 <tr><td valign="top"><b>Update:</b></td><td> 14 <tr><td valign="top"><b>Update:</b></td><td>
15 <input type="checkbox" name="updateCheck" id="updateCheck" checked="checked"><label for="updateCheck">&nbsp;Notify me when a new release is ready</label></td> 15 <input type="checkbox" name="updateCheck" id="updateCheck" checked="checked"><label for="updateCheck">&nbsp;Notify me when a new release is ready</label></td>
16 </tr> 16 </tr>
17 <tr>
18 <td valign="top">
19 <b>API:</b>
20 </td>
21 <td>
22 <input type="checkbox" name="enableApi" id="enableApi" checked="checked">
23 <label for="enableApi">
24 &nbsp;Enable Shaarli's API.
25 Allow third party software to use Shaarli such as mobile application.
26 </label>
27 </td>
28 </tr>
17 <tr><td colspan="2"><input type="submit" name="Save" value="Save config" class="bigbutton"></td></tr> 29 <tr><td colspan="2"><input type="submit" name="Save" value="Save config" class="bigbutton"></td></tr>
18 </table> 30 </table>
19 </form> 31 </form>