IonBazan /
aliyun-http-signer
| 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
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
|
|||
| 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 |