13
13
14
14
/**
15
15
* @phpstan-type ParsedUrl array{'scheme'?: string, 'host'?: string, 'port'?: int, 'user'?: string, 'pass'?: string, 'path'?: string, 'query'?: string, 'fragment'?: string}
16
- * @phpstan-type ClaimsSet array{'iss': string, 'aud': string|null, 'iat': int, 'exp': int, 'sub': string, 'meth': string, 'mod': int, 'val': int}
16
+ * @phpstan-type ClaimsSet array{'iss': string, 'aud': string|null, 'iat': int, 'exp': int, 'sub': string, 'meth': array<int, string> , 'mod': int, 'val': int}
17
17
*/
18
18
class SignedUrl implements Plugin
19
19
{
@@ -65,6 +65,7 @@ public function signUrl(
65
65
int $ mode = self ::MODE_REQUEST ,
66
66
int $ value = self ::VALUE_ENABLE
67
67
): string {
68
+ /** @var ParsedUrl|false $parsedUrl */
68
69
$ parsedUrl = parse_url ($ url );
69
70
70
71
if ($ parsedUrl === false ) {
@@ -100,7 +101,7 @@ public function getToken(
100
101
'iat ' => $ this ->timestamp ?? time (),
101
102
'exp ' => $ expire ,
102
103
'sub ' => $ url ,
103
- 'meth ' => self ::HTTP_METHOD_GET ,
104
+ 'meth ' => [ self ::HTTP_METHOD_GET ] ,
104
105
'mod ' => $ mode ,
105
106
'val ' => $ value ,
106
107
];
@@ -143,30 +144,30 @@ public function verifyRequest(bool $allowRedirect = false, ?string $url = null,
143
144
$ url = $ url ?? $ this ->urlFromGlobal ();
144
145
$ method = $ method ?? $ _SERVER ['REQUEST_METHOD ' ];
145
146
146
- [$ allowedMethod , $ mode , $ value , $ expires ] = $ this ->verifyUrl ($ url );
147
+ [$ allowedMethods , $ mode , $ value , $ expires ] = $ this ->verifyUrl ($ url );
147
148
148
- if (strcasecmp ( $ method , $ allowedMethod ) !== 0 ) {
149
+ if (in_array ( strtolower ( $ method) , $ allowedMethods , true ) === false ) {
149
150
throw new SignedUrlVerificationException ('HTTP method doesn \'t match signed HTTP method ' );
150
151
}
151
152
152
153
return [$ mode , $ value , $ expires ];
153
154
}
154
155
155
156
/**
156
- * @return array{string, int, int, int}
157
+ * @return array{array<int, string> , int, int, int}
157
158
*/
158
159
public function verifyUrl (string $ url , bool $ allowRedirect = false ): array
159
160
{
160
- /** @noinspection CallableParameterUseCaseInTypeContextInspection */
161
- $ url = parse_url ($ url );
161
+ /** @var ParsedUrl|false $parsedUrl */
162
+ $ parsedUrl = parse_url ($ url );
162
163
163
- if ($ url === false ) {
164
+ if ($ parsedUrl === false ) {
164
165
throw new SignedUrlVerificationException ('Url is invalid ' );
165
166
}
166
167
167
168
// Parse token from Query string
168
169
// Note: Not parsed by parse_str() to prevent broke URL (repeated arguments like `?same_arg=1&same_arg=2`)
169
- $ query = $ url ['query ' ] ?? '' ;
170
+ $ query = $ parsedUrl ['query ' ] ?? '' ;
170
171
if (preg_match (
171
172
'/(?<token_key>[?&] ' . self ::URL_QUERY_TOKEN_KEY . '=)(?<token>[^&]+)(?:$|(?<remaining>&.*$))/D ' ,
172
173
$ query ,
@@ -181,34 +182,34 @@ public function verifyUrl(string $url, bool $allowRedirect = false): array
181
182
$ remainingOffset = $ matches ['remaining ' ][1 ] ?? null ;
182
183
183
184
// Parse token – when token invalid, no URL canonicalization proceed
184
- [$ allowedUrl , $ allowedMethod , $ mode , $ value , $ expires ] = $ this ->verifyToken ($ token );
185
+ [$ allowedUrl , $ allowedMethods , $ mode , $ value , $ expires ] = $ this ->verifyToken ($ token );
185
186
186
187
// Some apps modifing URL
187
188
if ($ remainingOffset !== null ) {
188
189
if ($ allowRedirect === false ) {
189
190
throw new SignedUrlVerificationException ('URL contains unallowed queries after Signing Token ' );
190
191
}
191
192
192
- $ canonicalUrl = $ this ->buildUrl (['query ' => substr ($ query , 0 , $ remainingOffset )] + $ url );
193
+ $ canonicalUrl = $ this ->buildUrl (['query ' => substr ($ query , 0 , $ remainingOffset )] + $ parsedUrl );
193
194
$ this ->sendRedirectResponse ($ canonicalUrl );
194
195
}
195
196
196
- $ signedUrl = $ this ->buildUrl (['query ' => substr ($ query , 0 , $ tokenOffset )] + $ url );
197
+ $ signedUrl = $ this ->buildUrl (['query ' => substr ($ query , 0 , $ tokenOffset )] + $ parsedUrl );
197
198
198
199
if ($ signedUrl !== $ allowedUrl ) {
199
200
throw new SignedUrlVerificationException ('URL doesn \'t match signed URL ' );
200
201
}
201
202
202
- return [$ allowedMethod , $ mode , $ value , $ expires ];
203
+ return [$ allowedMethods , $ mode , $ value , $ expires ];
203
204
}
204
205
205
206
/**
206
- * @return array{string, string, int, int, int}
207
+ * @return array{string, array<int, string> , int, int, int}
207
208
*/
208
209
public function verifyToken (string $ token ): array
209
210
{
210
211
try {
211
- /** @var ClaimsSet */
212
+ /** @var ClaimsSet $payload */
212
213
$ payload = JWT ::decode ($ token , $ this ->key , [$ this ->algorithm ]);
213
214
} catch (RuntimeException $ e ) {
214
215
throw new SignedUrlVerificationException ('JWT Token invalid ' , 0 , $ e );
@@ -247,11 +248,11 @@ public function verifyToken(string $token): array
247
248
}
248
249
249
250
return [
250
- ( string ) $ payload ->sub ,
251
- ( string ) $ payload ->meth ,
251
+ $ payload ->sub ,
252
+ $ payload ->meth ,
252
253
$ payload ->mod ,
253
254
$ payload ->val ,
254
- ( int ) $ payload ->exp
255
+ $ payload ->exp
255
256
];
256
257
}
257
258
0 commit comments