ApiUtils.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. <?php
  2. namespace Shaarli\Api;
  3. use Shaarli\Base64Url;
  4. use Shaarli\Api\Exceptions\ApiAuthorizationException;
  5. /**
  6. * REST API utilities
  7. */
  8. class ApiUtils
  9. {
  10. /**
  11. * Validates a JWT token authenticity.
  12. *
  13. * @param string $token JWT token extracted from the headers.
  14. * @param string $secret API secret set in the settings.
  15. *
  16. * @throws ApiAuthorizationException the token is not valid.
  17. */
  18. public static function validateJwtToken($token, $secret)
  19. {
  20. $parts = explode('.', $token);
  21. if (count($parts) != 3 || strlen($parts[0]) == 0 || strlen($parts[1]) == 0) {
  22. throw new ApiAuthorizationException('Malformed JWT token');
  23. }
  24. $genSign = Base64Url::encode(hash_hmac('sha512', $parts[0] .'.'. $parts[1], $secret, true));
  25. if ($parts[2] != $genSign) {
  26. throw new ApiAuthorizationException('Invalid JWT signature');
  27. }
  28. $header = json_decode(Base64Url::decode($parts[0]));
  29. if ($header === null) {
  30. throw new ApiAuthorizationException('Invalid JWT header');
  31. }
  32. $payload = json_decode(Base64Url::decode($parts[1]));
  33. if ($payload === null) {
  34. throw new ApiAuthorizationException('Invalid JWT payload');
  35. }
  36. if (empty($payload->iat)
  37. || $payload->iat > time()
  38. || time() - $payload->iat > ApiMiddleware::$TOKEN_DURATION
  39. ) {
  40. throw new ApiAuthorizationException('Invalid JWT issued time');
  41. }
  42. }
  43. /**
  44. * Format a Link for the REST API.
  45. *
  46. * @param array $link Link data read from the datastore.
  47. * @param string $indexUrl Shaarli's index URL (used for relative URL).
  48. *
  49. * @return array Link data formatted for the REST API.
  50. */
  51. public static function formatLink($link, $indexUrl)
  52. {
  53. $out['id'] = $link['id'];
  54. // Not an internal link
  55. if ($link['url'][0] != '?') {
  56. $out['url'] = $link['url'];
  57. } else {
  58. $out['url'] = $indexUrl . $link['url'];
  59. }
  60. $out['shorturl'] = $link['shorturl'];
  61. $out['title'] = $link['title'];
  62. $out['description'] = $link['description'];
  63. $out['tags'] = preg_split('/\s+/', $link['tags'], -1, PREG_SPLIT_NO_EMPTY);
  64. $out['private'] = $link['private'] == true;
  65. $out['created'] = $link['created']->format(\DateTime::ATOM);
  66. if (! empty($link['updated'])) {
  67. $out['updated'] = $link['updated']->format(\DateTime::ATOM);
  68. } else {
  69. $out['updated'] = '';
  70. }
  71. return $out;
  72. }
  73. /**
  74. * Convert a link given through a request, to a valid link for LinkDB.
  75. *
  76. * If no URL is provided, it will generate a local note URL.
  77. * If no title is provided, it will use the URL as title.
  78. *
  79. * @param array $input Request Link.
  80. * @param bool $defaultPrivate Request Link.
  81. *
  82. * @return array Formatted link.
  83. */
  84. public static function buildLinkFromRequest($input, $defaultPrivate)
  85. {
  86. $input['url'] = ! empty($input['url']) ? cleanup_url($input['url']) : '';
  87. if (isset($input['private'])) {
  88. $private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN);
  89. } else {
  90. $private = $defaultPrivate;
  91. }
  92. $link = [
  93. 'title' => ! empty($input['title']) ? $input['title'] : $input['url'],
  94. 'url' => $input['url'],
  95. 'description' => ! empty($input['description']) ? $input['description'] : '',
  96. 'tags' => ! empty($input['tags']) ? implode(' ', $input['tags']) : '',
  97. 'private' => $private,
  98. 'created' => new \DateTime(),
  99. ];
  100. return $link;
  101. }
  102. /**
  103. * Update link fields using an updated link object.
  104. *
  105. * @param array $oldLink data
  106. * @param array $newLink data
  107. *
  108. * @return array $oldLink updated with $newLink values
  109. */
  110. public static function updateLink($oldLink, $newLink)
  111. {
  112. foreach (['title', 'url', 'description', 'tags', 'private'] as $field) {
  113. $oldLink[$field] = $newLink[$field];
  114. }
  115. $oldLink['updated'] = new \DateTime();
  116. if (empty($oldLink['url'])) {
  117. $oldLink['url'] = '?' . $oldLink['shorturl'];
  118. }
  119. if (empty($oldLink['title'])) {
  120. $oldLink['title'] = $oldLink['url'];
  121. }
  122. return $oldLink;
  123. }
  124. /**
  125. * Format a Tag for the REST API.
  126. *
  127. * @param string $tag Tag name
  128. * @param int $occurrences Number of links using this tag
  129. *
  130. * @return array Link data formatted for the REST API.
  131. */
  132. public static function formatTag($tag, $occurences)
  133. {
  134. return [
  135. 'name' => $tag,
  136. 'occurrences' => $occurences,
  137. ];
  138. }
  139. }