<?php
-
namespace Shaarli\Api;
+use Shaarli\Base64Url;
use Shaarli\Api\Exceptions\ApiAuthorizationException;
/**
- * Class ApiUtils
- *
- * Utility functions for the API.
+ * REST API utilities
*/
class ApiUtils
{
throw new ApiAuthorizationException('Malformed JWT token');
}
- $genSign = hash_hmac('sha512', $parts[0] .'.'. $parts[1], $secret);
+ $genSign = Base64Url::encode(hash_hmac('sha512', $parts[0] .'.'. $parts[1], $secret, true));
if ($parts[2] != $genSign) {
throw new ApiAuthorizationException('Invalid JWT signature');
}
- $header = json_decode(base64_decode($parts[0]));
+ $header = json_decode(Base64Url::decode($parts[0]));
if ($header === null) {
throw new ApiAuthorizationException('Invalid JWT header');
}
- $payload = json_decode(base64_decode($parts[1]));
+ $payload = json_decode(Base64Url::decode($parts[1]));
if ($payload === null) {
throw new ApiAuthorizationException('Invalid JWT payload');
}
throw new ApiAuthorizationException('Invalid JWT issued time');
}
}
+
+ /**
+ * Format a Link for the REST API.
+ *
+ * @param array $link Link data read from the datastore.
+ * @param string $indexUrl Shaarli's index URL (used for relative URL).
+ *
+ * @return array Link data formatted for the REST API.
+ */
+ public static function formatLink($link, $indexUrl)
+ {
+ $out['id'] = $link['id'];
+ // Not an internal link
+ if ($link['url'][0] != '?') {
+ $out['url'] = $link['url'];
+ } else {
+ $out['url'] = $indexUrl . $link['url'];
+ }
+ $out['shorturl'] = $link['shorturl'];
+ $out['title'] = $link['title'];
+ $out['description'] = $link['description'];
+ $out['tags'] = preg_split('/\s+/', $link['tags'], -1, PREG_SPLIT_NO_EMPTY);
+ $out['private'] = $link['private'] == true;
+ $out['created'] = $link['created']->format(\DateTime::ATOM);
+ if (! empty($link['updated'])) {
+ $out['updated'] = $link['updated']->format(\DateTime::ATOM);
+ } else {
+ $out['updated'] = '';
+ }
+ return $out;
+ }
}
require_once 'application/PluginManager.php';
require_once 'application/Router.php';
require_once 'application/Updater.php';
+use \Shaarli\ThemeUtils;
// Ensure the PHP version is supported
try {
$conf = new ConfigManager();
$conf->setEmpty('general.timezone', date_default_timezone_get());
$conf->setEmpty('general.title', 'Shared links on '. escape(index_url($_SERVER)));
-RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl'); // template directory
+RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme').'/'; // template directory
RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory
$pluginManager = new PluginManager($conf);
}
// If session does not exist on server side, or IP address has changed, or session has expired, logout.
if (empty($_SESSION['uid'])
- || ($conf->get('security.session_protection_disabled') == false && $_SESSION['ip'] != allIPs())
+ || ($conf->get('security.session_protection_disabled') === false && $_SESSION['ip'] != allIPs())
|| time() >= $_SESSION['expires_on'])
{
logout();
$tpl->assign('links', $links);
$tpl->assign('rssdate', escape($dayDate->format(DateTime::RSS)));
$tpl->assign('hide_timestamps', $conf->get('privacy.hide_timestamps', false));
- $html = $tpl->draw('dailyrss', $return_string=true);
+ $html = $tpl->draw('dailyrss', true);
echo $html . PHP_EOL;
}
$conf->set('general.timezone', $tz);
$conf->set('general.title', escape($_POST['title']));
$conf->set('general.header_link', escape($_POST['titleLink']));
+ $conf->set('resource.theme', escape($_POST['theme']));
$conf->set('redirector.url', escape($_POST['redirector']));
$conf->set('security.session_protection_disabled', !empty($_POST['disablesessionprotection']));
$conf->set('privacy.default_private_links', !empty($_POST['privateLinkByDefault']));
$conf->set('api.secret', escape($_POST['apiSecret']));
try {
$conf->write(isLoggedIn());
+ invalidateCaches($conf->get('resource.page_cache'));
}
catch(Exception $e) {
error_log(
else // Show the configuration form.
{
$PAGE->assign('title', $conf->get('general.title'));
+ $PAGE->assign('theme', $conf->get('resource.theme'));
+ $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl')));
$PAGE->assign('redirector', $conf->get('redirector.url'));
list($timezone_form, $timezone_js) = generateTimeZoneForm($conf->get('general.timezone'));
$PAGE->assign('timezone_form', $timezone_form);
// REST API routes
$app->group('/api/v1', function() {
$this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo');
+ $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks');
})->add('\Shaarli\Api\ApiMiddleware');
$response = $app->run(true);
namespace Shaarli\Api;
+use Shaarli\Base64Url;
+
+
/**
* Class ApiUtilsTest
*/
*/
public static function generateValidJwtToken($secret)
{
- $header = base64_encode('{
+ $header = Base64Url::encode('{
"typ": "JWT",
"alg": "HS512"
}');
- $payload = base64_encode('{
+ $payload = Base64Url::encode('{
"iat": '. time() .'
}');
- $signature = hash_hmac('sha512', $header .'.'. $payload , $secret);
+ $signature = Base64Url::encode(hash_hmac('sha512', $header .'.'. $payload , $secret, true));
return $header .'.'. $payload .'.'. $signature;
}
*/
public static function generateCustomJwtToken($header, $payload, $secret)
{
- $header = base64_encode($header);
- $payload = base64_encode($payload);
- $signature = hash_hmac('sha512', $header . '.' . $payload, $secret);
+ $header = Base64Url::encode($header);
+ $payload = Base64Url::encode($payload);
+ $signature = Base64Url::encode(hash_hmac('sha512', $header . '.' . $payload, $secret, true));
return $header . '.' . $payload . '.' . $signature;
}
$token = $this->generateCustomJwtToken('{"JSON":1}', '{"iat":' . (time() + 60) . '}', 'secret');
ApiUtils::validateJwtToken($token, 'secret');
}
+
+ /**
+ * Test formatLink() with a link using all useful fields.
+ */
+ public function testFormatLinkComplete()
+ {
+ $indexUrl = 'https://domain.tld/sub/';
+ $link = [
+ 'id' => 12,
+ 'url' => 'http://lol.lol',
+ 'shorturl' => 'abc',
+ 'title' => 'Important Title',
+ 'description' => 'It is very lol<tag>' . PHP_EOL . 'new line',
+ 'tags' => 'blip .blop ',
+ 'private' => '1',
+ 'created' => \DateTime::createFromFormat('Ymd_His', '20170107_160102'),
+ 'updated' => \DateTime::createFromFormat('Ymd_His', '20170107_160612'),
+ ];
+
+ $expected = [
+ 'id' => 12,
+ 'url' => 'http://lol.lol',
+ 'shorturl' => 'abc',
+ 'title' => 'Important Title',
+ 'description' => 'It is very lol<tag>' . PHP_EOL . 'new line',
+ 'tags' => ['blip', '.blop'],
+ 'private' => true,
+ 'created' => '2017-01-07T16:01:02+00:00',
+ 'updated' => '2017-01-07T16:06:12+00:00',
+ ];
+
+ $this->assertEquals($expected, ApiUtils::formatLink($link, $indexUrl));
+ }
+
+ /**
+ * Test formatLink() with only minimal fields filled, and internal link.
+ */
+ public function testFormatLinkMinimalNote()
+ {
+ $indexUrl = 'https://domain.tld/sub/';
+ $link = [
+ 'id' => 12,
+ 'url' => '?abc',
+ 'shorturl' => 'abc',
+ 'title' => 'Note',
+ 'description' => '',
+ 'tags' => '',
+ 'private' => '',
+ 'created' => \DateTime::createFromFormat('Ymd_His', '20170107_160102'),
+ ];
+
+ $expected = [
+ 'id' => 12,
+ 'url' => 'https://domain.tld/sub/?abc',
+ 'shorturl' => 'abc',
+ 'title' => 'Note',
+ 'description' => '',
+ 'tags' => [],
+ 'private' => false,
+ 'created' => '2017-01-07T16:01:02+00:00',
+ 'updated' => '',
+ ];
+
+ $this->assertEquals($expected, ApiUtils::formatLink($link, $indexUrl));
+ }
}