1<?php
2
3use dokuwiki\Extension\Plugin;
4
5/**
6 * DokuWiki Plugin cosmocode (Helper Component)
7 *
8 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
9 * @author Andreas Gohr <dokuwiki@cosmocode.de>
10 */
11class helper_plugin_cosmocode_partner extends Plugin
12{
13    const HOST = 'https://partnerapi.cosmocode.de/';
14
15    /**
16     * Construct the URL at which a extension can be downloaded
17     *
18     * @param string $base
19     * @param string $token
20     * @return string
21     */
22    public function getDownloadUrl($base, $token)
23    {
24        return self::HOST . 'download/' . $base . '.zip?token=' . $token;
25    }
26
27    /**
28     * Talk to the API to get the list of extensions
29     *
30     * Results are cached for 24 hours
31     *
32     * @return array
33     * @throws Exception
34     */
35    public function getExtensions()
36    {
37        $http = new \dokuwiki\HTTP\DokuHTTPClient();
38        $url = self::HOST . 'feed';
39        $domain = parse_url(self::HOST, PHP_URL_HOST);
40
41        $tokens = $this->getTokens();
42        if ($tokens) {
43            $http->headers['x-token'] = join(',', array_keys($tokens));
44        }
45        $http->headers['x-wiki-id'] = md5(auth_cookiesalt());
46
47        $cache = getCacheName($url . join(',', array_keys($tokens)), '.json');
48        if (@filemtime($cache) > time() - 60 * 60 * 24) {
49            $data = io_readFile($cache, false);
50        } else {
51            $data = $http->get($url);
52            if ($data === false) {
53                $data = $http->resp_body;
54                $decoded = json_decode($data, true);
55                if ($decoded && isset($decoded['error'])) {
56                    throw new \RuntimeException(
57                        sprintf($this->getLang('error_api'), $decoded['error'])
58                    );
59                } else {
60                    throw new \RuntimeException(
61                        sprintf($this->getLang('error_connect'), $domain, $http->error)
62                    );
63                }
64            }
65            io_saveFile($cache, $data);
66        }
67        return json_decode($data, true);
68    }
69
70    /**
71     * Get the tokens from the config
72     *
73     * Decodes the payload and filters out expired tokens
74     *
75     * @return array
76     */
77    public function getTokens()
78    {
79        $lines = $this->getConf('tokens');
80        if (!$lines) return [];
81        $lines = explode("\n", $lines);
82
83        $tokens = [];
84        foreach ($lines as $token) {
85            $token = trim($token);
86            if (!$token) continue;
87            $decoded = $this->decodeJWT($token);
88            if (!$decoded) continue;
89            if (!isset($decoded['exp'])) continue;
90            if ($decoded['exp'] < time()) continue;
91
92            $tokens[$token] = $decoded;
93        }
94
95        return $tokens;
96    }
97
98    /**
99     * Decode the payload of a JWT token
100     *
101     * Does not validate expiration or signature
102     *
103     * @link https://www.converticacommerce.com/support-maintenance/security/php-one-liner-decode-jwt-json-web-tokens/
104     * @param $jwt
105     * @return array
106     */
107    protected function decodeJWT($jwt)
108    {
109        return json_decode(base64_decode(str_replace('_', '/', str_replace('-', '+', explode('.', $jwt)[1]))), true);
110    }
111
112}
113