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
![]() |
|||
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
|
|||
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 |