]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Merge pull request #727 from ArthurHoaro/api/getlinks
authorArthurHoaro <arthur@hoa.ro>
Sun, 15 Jan 2017 15:49:50 +0000 (16:49 +0100)
committerGitHub <noreply@github.com>
Sun, 15 Jan 2017 15:49:50 +0000 (16:49 +0100)
REST API: implement getLinks service

1  2 
application/api/ApiUtils.php
index.php
tests/api/ApiUtilsTest.php

index a419c39669a1a44bde14cb7baf8a3b3471041d50,d024291988db8b7a439e3a0e7986749fa629467a..d40158652d1f66680cfd5ceea17623b9a8eef5a8
@@@ -1,11 -1,13 +1,11 @@@
  <?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;
+     }
  }
diff --combined index.php
index a54dfb1d06f89231a863cc44fa1e3d8894974569,ff24ed7eca2762b4c531570efd2aa97b3ee37753..beb1cbca592aefae1071254c9ab95c04ea635526
+++ b/index.php
@@@ -79,7 -79,6 +79,7 @@@ require_once 'application/Utils.php'
  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 {
@@@ -123,7 -122,7 +123,7 @@@ if (isset($_COOKIE['shaarli']) && !is_s
  $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);
@@@ -204,7 -203,7 +204,7 @@@ function setup_login_state($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();
@@@ -618,7 -617,7 +618,7 @@@ function showDailyRSS($conf) 
          $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;
      }
@@@ -1125,7 -1124,6 +1125,7 @@@ function renderPage($conf, $pluginManag
              $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);
@@@ -2237,6 -2232,7 +2237,7 @@@ $app = new \Slim\App($container)
  // 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);
index 4b2fa3b2719a289597adad2d42028f49e613c721,516ee6860edd169ba2cf6ea962669e59a0296896..b4431d1be66d8ed34e75ffdaed18965625717140
@@@ -2,9 -2,6 +2,9 @@@
  
  namespace Shaarli\Api;
  
 +use Shaarli\Base64Url;
 +
 +
  /**
   * Class ApiUtilsTest
   */
@@@ -27,14 -24,14 +27,14 @@@ class ApiUtilsTest extends \PHPUnit_Fra
       */
      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;
      }
  
@@@ -49,9 -46,9 +49,9 @@@
       */
      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));
+     }
  }