1010namespace PrivatePackagist \ApiClient \HttpClient \Plugin ;
1111
1212use GuzzleHttp \Psr7 \Request ;
13+ use Psr \Http \Message \RequestInterface ;
1314
1415class RequestSignatureTest extends PluginTestCase
1516{
@@ -39,7 +40,7 @@ public function testPrefixRequestPath()
3940 'POST ' ,
4041 '/packages/?foo=bar ' ,
4142 [
42- 'Authorization ' => ["PACKAGIST-HMAC-SHA256 Key= {$ this ->key }, Timestamp= {$ this ->timestamp }, Cnonce= {$ this ->nonce }, Signature=a6wxBLYrmz4Mwmv/TKBZR5WHFcSCRbsny2frobJMt24 = " ],
43+ 'Authorization ' => ["PACKAGIST-HMAC-SHA256 Key= {$ this ->key }, Timestamp= {$ this ->timestamp }, Cnonce= {$ this ->nonce }, Version=2, Signature=rzwvwGS17Qcmk8UqTefJCHCV188x1/e1iBWG2pB4z1M = " ],
4344 ],
4445 json_encode (['foo ' => 'bar ' ])
4546 );
@@ -49,6 +50,93 @@ public function testPrefixRequestPath()
4950 $ this ->assertEquals ($ expected ->getHeaders (), $ promise ->wait (true )->getHeaders ());
5051 }
5152
53+ public function testSignatureCoversQueryString ()
54+ {
55+ $ requestA = new Request ('GET ' , '/packages/?page=1&limit=10 ' );
56+ $ requestB = new Request ('GET ' , '/packages/?page=2&limit=10 ' );
57+
58+ $ signatureA = $ this ->extractSignature ($ this ->plugin ->handleRequest ($ requestA , $ this ->next , $ this ->first )->wait (true ));
59+ $ signatureB = $ this ->extractSignature ($ this ->plugin ->handleRequest ($ requestB , $ this ->next , $ this ->first )->wait (true ));
60+
61+ $ this ->assertNotSame ($ signatureA , $ signatureB );
62+ }
63+
64+ public function testSignatureIgnoresQueryParamOrder ()
65+ {
66+ $ requestA = new Request ('GET ' , '/packages/?page=1&limit=10 ' );
67+ $ requestB = new Request ('GET ' , '/packages/?limit=10&page=1 ' );
68+
69+ $ signatureA = $ this ->extractSignature ($ this ->plugin ->handleRequest ($ requestA , $ this ->next , $ this ->first )->wait (true ));
70+ $ signatureB = $ this ->extractSignature ($ this ->plugin ->handleRequest ($ requestB , $ this ->next , $ this ->first )->wait (true ));
71+
72+ $ this ->assertSame ($ signatureA , $ signatureB );
73+ }
74+
75+ public function testQueryParamWithAuthFieldNameDoesNotShadowAuthIdentity ()
76+ {
77+ $ request = new Request ('GET ' , '/packages/?key=evil&version=1 ' );
78+
79+ $ header = $ this ->plugin ->handleRequest ($ request , $ this ->next , $ this ->first )->wait (true )->getHeader ('Authorization ' )[0 ];
80+
81+ $ this ->assertStringContainsString ("Key= {$ this ->key }" , $ header );
82+ $ this ->assertStringContainsString ('Version=2 ' , $ header );
83+ }
84+
85+ public function testEmptyQueryStringMatchesNoQueryString ()
86+ {
87+ $ withoutQuery = new Request ('GET ' , '/packages/ ' );
88+ $ withEmptyQuery = new Request ('GET ' , '/packages/? ' );
89+
90+ $ signatureA = $ this ->extractSignature ($ this ->plugin ->handleRequest ($ withoutQuery , $ this ->next , $ this ->first )->wait (true ));
91+ $ signatureB = $ this ->extractSignature ($ this ->plugin ->handleRequest ($ withEmptyQuery , $ this ->next , $ this ->first )->wait (true ));
92+
93+ $ this ->assertSame ($ signatureA , $ signatureB );
94+ }
95+
96+ public function testEmptyValueQueryParamIsSigned ()
97+ {
98+ $ withoutParam = new Request ('GET ' , '/packages/ ' );
99+ $ withEmptyValue = new Request ('GET ' , '/packages/?foo= ' );
100+ $ withValuelessParam = new Request ('GET ' , '/packages/?foo ' );
101+
102+ $ baseline = $ this ->extractSignature ($ this ->plugin ->handleRequest ($ withoutParam , $ this ->next , $ this ->first )->wait (true ));
103+ $ emptyValue = $ this ->extractSignature ($ this ->plugin ->handleRequest ($ withEmptyValue , $ this ->next , $ this ->first )->wait (true ));
104+ $ valueless = $ this ->extractSignature ($ this ->plugin ->handleRequest ($ withValuelessParam , $ this ->next , $ this ->first )->wait (true ));
105+
106+ $ this ->assertNotSame ($ baseline , $ emptyValue );
107+ $ this ->assertSame ($ emptyValue , $ valueless );
108+ }
109+
110+ public function testUrlEncodingIsCanonical ()
111+ {
112+ $ percent = new Request ('GET ' , '/packages/?foo=hello%20world ' );
113+ $ plus = new Request ('GET ' , '/packages/?foo=hello+world ' );
114+
115+ $ signaturePercent = $ this ->extractSignature ($ this ->plugin ->handleRequest ($ percent , $ this ->next , $ this ->first )->wait (true ));
116+ $ signaturePlus = $ this ->extractSignature ($ this ->plugin ->handleRequest ($ plus , $ this ->next , $ this ->first )->wait (true ));
117+
118+ $ this ->assertSame ($ signaturePercent , $ signaturePlus );
119+ }
120+
121+ public function testV2GetDeterministicSignature ()
122+ {
123+ $ request = new Request ('GET ' , 'https://localhost/api/packages/?limit=1&page=2 ' );
124+ $ expected = "PACKAGIST-HMAC-SHA256 Key= {$ this ->key }, Timestamp= {$ this ->timestamp }, Cnonce= {$ this ->nonce }, Version=2, Signature= " ;
125+
126+ $ actual = $ this ->plugin ->handleRequest ($ request , $ this ->next , $ this ->first )->wait (true )->getHeader ('Authorization ' )[0 ];
127+
128+ $ this ->assertStringStartsWith ($ expected , $ actual );
129+ $ this ->assertStringEndsWith ('Signature=RLb/mPCONIcfPp3+Ink+jtxNU6VKyeasf7Zdd7kNO+A= ' , $ actual );
130+ }
131+
132+ private function extractSignature (RequestInterface $ request )
133+ {
134+ $ header = $ request ->getHeader ('Authorization ' )[0 ];
135+ preg_match ('/Signature=([^,]+)/ ' , $ header , $ matches );
136+
137+ return $ matches [1 ];
138+ }
139+
52140 public function testPrefixRequestPathSmoke ()
53141 {
54142 $ request = new Request ('POST ' , '/packages/?foo=bar ' , [], json_encode (['foo ' => 'bar ' ]));
0 commit comments