Completed
Push — master ( bb131b...ea520a )
by Bukashk0zzz
03:03
created

JWTMiddleware::qsh()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 21
nc 2
nop 2
dl 0
loc 30
rs 8.5806
c 0
b 0
f 0
1
<?php declare(strict_types = 1);
2
3
namespace AtlassianConnectBundle\Service;
4
5
use Firebase\JWT\JWT;
6
use GuzzleHttp\Middleware;
7
use GuzzleHttp\Psr7\Request;
8
use Psr\Http\Message\RequestInterface;
9
10
/**
11
 * Class JWTMiddleware
12
 */
13
class JWTMiddleware
14
{
15
    /**
16
     * JWT Authentication middleware for Guzzle
17
     *
18
     * @param string $issuer Add-on key in most cases
19
     * @param string $secret Shared secret
20
     *
21
     * @return callable
22
     */
23
    public static function authTokenMiddleware(string $issuer, string $secret): callable
24
    {
25
        return Middleware::mapRequest(
26
            function (RequestInterface $request) use ($issuer, $secret) {
27
                return new Request(
28
                    $request->getMethod(),
29
                    $request->getUri(),
30
                    \array_merge($request->getHeaders(), ['Authorization' => 'JWT '.static::create($request, $issuer, $secret)]),
31
                    $request->getBody()
32
                );
33
            }
34
        );
35
    }
36
37
    /**
38
     * Create JWT token used by Atlassian REST API request
39
     *
40
     * @param RequestInterface $request
41
     * @param string           $issuer  Key of the add-on
42
     * @param string           $secret  Shared secret of the Tenant
43
     *
44
     * @return string
45
     */
46
    private static function create(RequestInterface $request, string $issuer, string $secret): string
47
    {
48
        $payload = [
49
            'iss' => $issuer,
50
            'iat' => \time(),
51
            'exp' => \strtotime('+1 day'),
52
            'qsh' => static::qsh((string) $request->getUri(), $request->getMethod()),
53
        ];
54
55
        return JWT::encode($payload, $secret);
56
    }
57
58
    /**
59
     * Create Query String Hash
60
     *
61
     * More details:
62
     * https://developer.atlassian.com/static/connect/docs/latest/concepts/understanding-jwt.html#creating-token
63
     *
64
     * @param string $url    URL of the request
65
     * @param string $method HTTP method
66
     *
67
     * @return string
68
     */
69
    private static function qsh(string $url, string $method): string
70
    {
71
        $method = \mb_strtoupper($method);
72
        $parts = \parse_url($url);
73
74
        // Remove "/wiki" part from the path for the Confluence
75
        // Really, I didn't find this part in the docs, but it works
76
        $path = \str_replace('/wiki', '', $parts['path']);
77
        $canonicalQuery = '';
78
        if (!empty($parts['query'])) {
79
            $query = $parts['query'];
80
            $queryParts = \explode('&', $query);
81
            $queryArray = [];
82
            foreach ($queryParts as $queryPart) {
83
                $pieces = \explode('=', $queryPart);
84
                $key = \array_shift($pieces);
85
                $key = \rawurlencode($key);
86
                $value = \mb_substr($queryPart, \mb_strlen($key) + 1);
87
                $value = \rawurlencode($value);
88
                $queryArray[$key][] = $value;
89
            }
90
            \ksort($queryArray);
91
            foreach ($queryArray as $key => $pieceOfQuery) {
92
                $pieceOfQuery = \implode(',', $pieceOfQuery);
93
                $canonicalQuery .= $key.'='.$pieceOfQuery.'&';
94
            }
95
            $canonicalQuery = \rtrim($canonicalQuery, '&');
96
        }
97
98
        return \hash('sha256', \implode('&', [$method, $path, $canonicalQuery]));
99
    }
100
}
101