RequestSigner::getUrlToSign()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 5
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace IonBazan\AliyunSigner;
6
7
use DateTime;
8
use DateTimeInterface;
9
use DateTimeZone;
10
use IonBazan\AliyunSigner\Digest\DigestInterface;
11
use IonBazan\AliyunSigner\Digest\HmacSHA256Digest;
12
use Psr\Http\Message\RequestInterface;
13
use Ramsey\Uuid\Uuid;
14
15
use function sprintf;
16
17
class RequestSigner
18
{
19
    public const HEADER_X_CA_SIGNATURE = 'X-Ca-Signature';
20
    public const HEADER_X_CA_SIGNATURE_METHOD = 'X-Ca-Signature-Method';
21
    public const HEADER_X_CA_SIGNATURE_HEADERS = 'X-Ca-Signature-Headers';
22
    public const HEADER_X_CA_TIMESTAMP = 'X-Ca-Timestamp';
23
    public const HEADER_X_CA_NONCE = 'X-Ca-Nonce';
24
    public const HEADER_X_CA_KEY = 'X-Ca-Key';
25
    public const HEADER_X_CA_STAGE = 'X-Ca-Stage';
26
    public const HEADER_DATE = 'Date';
27
    public const HEADER_CONTENT_MD5 = 'Content-MD5';
28
29
    private array $signatureHeaders = [
30
        self::HEADER_X_CA_KEY,
31
        self::HEADER_X_CA_NONCE,
32
        self::HEADER_X_CA_SIGNATURE_METHOD,
33
        self::HEADER_X_CA_TIMESTAMP,
34
        self::HEADER_X_CA_STAGE,
35
    ];
36
37
    public function __construct(private readonly Key $key, private readonly DigestInterface $digest = new HmacSHA256Digest())
38
    {
39
    }
40
41
    public function signRequest(RequestInterface $request, ?DateTimeInterface $date = null, ?string $nonce = null): RequestInterface
42
    {
43
        $nonce ??= Uuid::uuid4()->toString();
44
45
        $date = DateTime::createFromInterface($date ?? new DateTime())->setTimezone(new DateTimeZone('UTC'));
46
        $timeString = $date->format(DATE_RFC7231);
47
48
        $body = $request->getBody()->getContents();
49
        $contentMd5 = $body !== '' ? base64_encode(md5($body, true)) : '';
50
51
        $request = $request->withHeader(self::HEADER_DATE, $timeString)
52
            ->withHeader(self::HEADER_CONTENT_MD5, $contentMd5)
53
            ->withHeader(self::HEADER_X_CA_SIGNATURE_METHOD, $this->digest->getMethod())
54
            ->withHeader(self::HEADER_X_CA_TIMESTAMP, $date->format('Uv'))
55
            ->withHeader(self::HEADER_X_CA_KEY, $this->key->id)
56
            ->withHeader(self::HEADER_X_CA_NONCE, $nonce);
57
58
        $headers = $this->getHeadersToSign($request);
59
60
        $textToSign = implode("\n", [
61
            strtoupper($request->getMethod()),
0 ignored issues
show
Bug introduced by
The method getMethod() does not exist on Psr\Http\Message\MessageInterface. It seems like you code against a sub-type of Psr\Http\Message\MessageInterface such as Psr\Http\Message\RequestInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

61
            strtoupper($request->/** @scrutinizer ignore-call */ getMethod()),
Loading history...
62
            $request->getHeaderLine('accept'),
63
            $contentMd5,
64
            $request->getHeaderLine('content-type'),
65
            $timeString,
66
            implode("\n", $headers),
67
            $this->getUrlToSign($request),
68
        ]);
69
70
        $signature = $this->digest->sign($textToSign, $this->key->secret);
71
72
        return $request->withHeader(self::HEADER_X_CA_SIGNATURE, $signature)
0 ignored issues
show
Bug Best Practice introduced by
The expression return $request->withHea... array_keys($headers))) returns the type Psr\Http\Message\MessageInterface which includes types incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
73
            ->withHeader(self::HEADER_X_CA_SIGNATURE_HEADERS, implode(',', array_keys($headers)));
74
    }
75
76
    /**
77
     * @param string[] $headers
78
     */
79
    public function setSignatureHeaders(array $headers): void
80
    {
81
        $this->signatureHeaders = $headers;
82
    }
83
84
    public function addSignatureHeader(string $header): void
85
    {
86
        $this->signatureHeaders[] = $header;
87
    }
88
89
    protected function getUrlToSign(RequestInterface $request): string
90
    {
91
        $query = urldecode($request->getUri()->getQuery());
92
93
        return $request->getUri()->getPath().($query !== '' ? '?'.$query : '');
94
    }
95
96
    protected function getHeadersToSign(RequestInterface $request): array
97
    {
98
        $headersToSign = [];
99
        foreach ($this->signatureHeaders as $headerName) {
100
            $headerName = strtolower($headerName);
101
            if ($request->hasHeader($headerName)) {
102
                $headersToSign[$headerName] = sprintf('%s:%s', $headerName, $request->getHeaderLine($headerName));
103
            }
104
        }
105
106
        ksort($headersToSign);
107
108
        return $headersToSign;
109
    }
110
}
111