aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/api/ApiUtils.php
blob: fc5ecaf1e75931d3a2006dae957be7129bb7df1e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
<?php
namespace Shaarli\Api;

use Shaarli\Base64Url;
use Shaarli\Api\Exceptions\ApiAuthorizationException;

/**
 * REST API utilities
 */
class ApiUtils
{
    /**
     * Validates a JWT token authenticity.
     *
     * @param string $token JWT token extracted from the headers.
     * @param string $secret API secret set in the settings.
     *
     * @throws ApiAuthorizationException the token is not valid.
     */
    public static function validateJwtToken($token, $secret)
    {
        $parts = explode('.', $token);
        if (count($parts) != 3 || strlen($parts[0]) == 0 || strlen($parts[1]) == 0) {
            throw new ApiAuthorizationException('Malformed JWT token');
        }

        $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(Base64Url::decode($parts[0]));
        if ($header === null) {
            throw new ApiAuthorizationException('Invalid JWT header');
        }

        $payload = json_decode(Base64Url::decode($parts[1]));
        if ($payload === null) {
            throw new ApiAuthorizationException('Invalid JWT payload');
        }

        if (empty($payload->iat)
            || $payload->iat > time()
            || time() - $payload->iat > ApiMiddleware::$TOKEN_DURATION
        ) {
            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;
    }

    /**
     * Convert a link given through a request, to a valid link for LinkDB.
     *
     * If no URL is provided, it will generate a local note URL.
     * If no title is provided, it will use the URL as title.
     *
     * @param array  $input          Request Link.
     * @param bool   $defaultPrivate Request Link.
     *
     * @return array Formatted link.
     */
    public static function buildLinkFromRequest($input, $defaultPrivate)
    {
        $input['url'] = ! empty($input['url']) ? cleanup_url($input['url']) : '';
        if (isset($input['private'])) {
            $private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN);
        } else {
            $private = $defaultPrivate;
        }

        $link = [
            'title'         => ! empty($input['title']) ? $input['title'] : $input['url'],
            'url'           => $input['url'],
            'description'   => ! empty($input['description']) ? $input['description'] : '',
            'tags'          => ! empty($input['tags']) ? implode(' ', $input['tags']) : '',
            'private'       => $private,
            'created'       => new \DateTime(),
        ];
        return $link;
    }

    /**
     * Update link fields using an updated link object.
     *
     * @param array $oldLink data
     * @param array $newLink data
     *
     * @return array $oldLink updated with $newLink values
     */
    public static function updateLink($oldLink, $newLink)
    {
        foreach (['title', 'url', 'description', 'tags', 'private'] as $field) {
            $oldLink[$field] = $newLink[$field];
        }
        $oldLink['updated'] = new \DateTime();

        if (empty($oldLink['url'])) {
            $oldLink['url'] = '?' . $oldLink['shorturl'];
        }

        if (empty($oldLink['title'])) {
            $oldLink['title'] = $oldLink['url'];
        }

        return $oldLink;
    }

    /**
     * Format a Tag for the REST API.
     *
     * @param string $tag         Tag name
     * @param int    $occurrences Number of links using this tag
     *
     * @return array Link data formatted for the REST API.
     */
    public static function formatTag($tag, $occurences)
    {
        return [
            'name'       => $tag,
            'occurrences' => $occurences,
        ];
    }
}