diff options
-rw-r--r-- | application/Updater.php | 23 | ||||
-rw-r--r-- | application/Utils.php | 26 | ||||
-rw-r--r-- | index.php | 12 | ||||
-rw-r--r-- | tests/Updater/UpdaterTest.php | 40 | ||||
-rw-r--r-- | tests/UtilsTest.php | 17 | ||||
-rw-r--r-- | tpl/configure.html | 14 | ||||
-rw-r--r-- | tpl/install.html | 12 |
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 | */ | ||
252 | function 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 | } | ||
@@ -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"> Notify me when a new release is ready</label> | 80 | <label for="updateCheck"> 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"> 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"> Notify me when a new release is ready</label></td> | 15 | <input type="checkbox" name="updateCheck" id="updateCheck" checked="checked"><label for="updateCheck"> 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 | 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> |