22
33namespace Stackkit \LaravelGoogleCloudTasksQueue ;
44
5- use Ahc \Jwt \JWT ;
65use Google \Cloud \Tasks \V2 \CloudTasksClient ;
6+ use GuzzleHttp \Client ;
77use Illuminate \Http \Request ;
88use Illuminate \Queue \Worker ;
99use Illuminate \Queue \WorkerOptions ;
10+ use Illuminate \Support \Arr ;
11+ use phpseclib \Crypt \RSA ;
12+ use phpseclib \Math \BigInteger ;
1013use Throwable ;
14+ use Firebase \JWT \JWT ;
1115
1216class TaskHandler
1317{
1418 private $ client ;
1519 private $ request ;
20+ private $ guzzle ;
21+ private $ jwt ;
1622
17- public function __construct (CloudTasksClient $ client , Request $ request )
23+ public function __construct (CloudTasksClient $ client , Request $ request, Client $ guzzle , JWT $ jwt )
1824 {
1925 $ this ->client = $ client ;
2026 $ this ->request = $ request ;
27+ $ this ->guzzle = $ guzzle ;
28+ $ this ->jwt = $ jwt ;
2129 }
2230
2331 /**
@@ -36,39 +44,88 @@ public function handle($task = null)
3644 /**
3745 * @throws CloudTasksException
3846 */
39- private function authorizeRequest ()
47+ public function authorizeRequest ()
4048 {
41- $ this ->checkForRequiredHeaders ();
49+ if (!$ this ->request ->hasHeader ('Authorization ' )) {
50+ throw new CloudTasksException ('Missing [Authorization] header ' );
51+ }
4252
43- $ taskName = $ this ->request ->header ('X-Cloudtasks-Taskname ' );
44- $ queueName = $ this ->request ->header ('X-Cloudtasks-Queuename ' );
45- $ authToken = $ this ->request ->header ('X-Stackkit-Auth-Token ' );
53+ // @todo - kill this check with a Mock
54+ if (app ()->environment ('testing ' )) {
55+ return ;
56+ }
4657
47- $ fullQueueName = $ this ->client ->queueName (Config::project (), Config::location (), $ queueName );
58+ $ openIdToken = $ this ->request ->bearerToken ();
59+ $ pubKey = $ this ->getGooglePublicKey ();
4860
49- try {
50- $ this ->client ->getTask ($ fullQueueName . '/tasks/ ' . $ taskName );
51- } catch (Throwable $ e ) {
52- throw new CloudTasksException ('Could not find task ' );
53- }
61+ $ decodedToken = $ this ->jwt ->decode ($ openIdToken , $ pubKey , ['RS256 ' ]);
5462
55- if (decrypt ($ authToken ) != $ taskName ) {
56- throw new CloudTasksException ('Auth token is not valid ' );
57- }
63+ $ this ->validateToken ($ decodedToken );
64+ }
65+
66+ private function getGooglePublicKey ()
67+ {
68+ $ jwksUri = $ this ->getJwksUri ();
69+
70+ $ keys = $ this ->getCertificateKeys ($ jwksUri );
71+
72+ $ firstKey = $ keys [1 ];
73+
74+ $ modulus = $ firstKey ['n ' ];
75+ $ exponent = $ firstKey ['e ' ];
76+
77+ $ rsa = new RSA ();
78+
79+ $ modulus = new BigInteger (JWT ::urlsafeB64Decode ($ modulus ), 256 );
80+ $ exponent = new BigInteger (JWT ::urlsafeB64Decode ($ exponent ), 256 );
81+
82+ $ rsa ->loadKey ([
83+ 'n ' => $ modulus ,
84+ 'e ' => $ exponent
85+ ]);
86+ $ rsa ->setPublicKey ();
87+
88+ return $ rsa ->getPublicKey ();
89+ }
90+
91+ private function getJwksUri ()
92+ {
93+ $ discoveryEndpoint = 'https://accounts.google.com/.well-known/openid-configuration ' ;
94+
95+ $ configurationJson = $ this ->guzzle ->get ($ discoveryEndpoint );
96+
97+ $ configurations = json_decode ($ configurationJson ->getBody (), true );
98+
99+ return Arr::get ($ configurations , 'jwks_uri ' );
100+ }
101+
102+ private function getCertificateKeys ($ jwksUri )
103+ {
104+ $ json = $ this ->guzzle ->get ($ jwksUri );
105+
106+ $ certificates = json_decode ($ json ->getBody (), true );
107+
108+ return Arr::get ($ certificates , 'keys ' );
58109 }
59110
60- private function checkForRequiredHeaders ()
111+ /**
112+ * https://developers.google.com/identity/protocols/oauth2/openid-connect#validatinganidtoken
113+ *
114+ * @param $openIdToken
115+ * @throws CloudTasksException
116+ */
117+ protected function validateToken ($ openIdToken )
61118 {
62- $ headers = [
63- ' X-Cloudtasks-Taskname ' ,
64- ' X-Cloudtasks-Queuename ' ,
65- ' X-Stackkit-Auth-Token ' ,
66- ];
67-
68- foreach ( $ headers as $ header ) {
69- if (! $ this -> request -> hasHeader ( $ header )) {
70- throw new CloudTasksException ( ' Missing [ ' . $ header . ' ] header ' );
71- }
119+ if (! in_array ( $ openIdToken -> iss , [ ' https://accounts.google.com ' , ' accounts.google.com ' ])) {
120+ throw new CloudTasksException ( ' The given OpenID token is not valid ' );
121+ }
122+
123+ if ( $ openIdToken -> aud != Config:: handler ()) {
124+ throw new CloudTasksException ( ' The given OpenID token is not valid ' );
125+ }
126+
127+ if ( $ openIdToken -> exp < time ()) {
128+ throw new CloudTasksException ( ' The given OpenID token has expired ' );
72129 }
73130 }
74131
0 commit comments