1<?php 2namespace IMSGlobal\LTI; 3 4use Firebase\JWT\JWT; 5 6class LTI_Service_Connector { 7 8 const NEXT_PAGE_REGEX = "/^Link:.*<([^>]*)>; ?rel=\"next\"/i"; 9 10 private $registration; 11 private $access_tokens = []; 12 13 public function __construct(LTI_Registration $registration) { 14 $this->registration = $registration; 15 } 16 17 public function get_access_token($scopes) { 18 19 // Don't fetch the same key more than once. 20 sort($scopes); 21 $scope_key = md5(implode('|', $scopes)); 22 if (isset($this->access_tokens[$scope_key])) { 23 return $this->access_tokens[$scope_key]; 24 } 25 26 // Build up JWT to exchange for an auth token 27 $client_id = $this->registration->get_client_id(); 28 $jwt_claim = [ 29 "iss" => $client_id, 30 "sub" => $client_id, 31 "aud" => $this->registration->get_auth_server(), 32 "iat" => time() - 5, 33 "exp" => time() + 60, 34 "jti" => 'lti-service-token' . hash('sha256', random_bytes(64)) 35 ]; 36 37 // Sign the JWT with our private key (given by the platform on registration) 38 $jwt = JWT::encode($jwt_claim, $this->registration->get_tool_private_key(), 'RS256', $this->registration->get_kid()); 39 40 // Build auth token request headers 41 $auth_request = [ 42 'grant_type' => 'client_credentials', 43 'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', 44 'client_assertion' => $jwt, 45 'scope' => implode(' ', $scopes) 46 ]; 47 48 // Make request to get auth token 49 $ch = curl_init(); 50 curl_setopt($ch, CURLOPT_URL, $this->registration->get_auth_token_url()); 51 curl_setopt($ch, CURLOPT_POST, 1); 52 curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($auth_request)); 53 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 54 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 55 $resp = curl_exec($ch); 56 $token_data = json_decode($resp, true); 57 curl_close ($ch); 58 59 return $this->access_tokens[$scope_key] = $token_data['access_token']; 60 } 61 62 public function make_service_request($scopes, $method, $url, $body = null, $content_type = 'application/json', $accept = 'application/json') { 63 $ch = curl_init(); 64 $headers = [ 65 'Authorization: Bearer ' . $this->get_access_token($scopes), 66 'Accept:' . $accept, 67 ]; 68 curl_setopt($ch, CURLOPT_URL, $url); 69 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 70 curl_setopt($ch, CURLOPT_HEADER, 1); 71 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 72 if ($method === 'POST') { 73 curl_setopt($ch, CURLOPT_POST, 1); 74 curl_setopt($ch, CURLOPT_POSTFIELDS, strval($body)); 75 $headers[] = 'Content-Type: ' . $content_type; 76 } 77 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 78 $response = curl_exec($ch); 79 if (curl_errno($ch)){ 80 echo 'Request Error:' . curl_error($ch); 81 } 82 $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); 83 curl_close ($ch); 84 85 $resp_headers = substr($response, 0, $header_size); 86 $resp_body = substr($response, $header_size); 87 return [ 88 'headers' => array_filter(explode("\r\n", $resp_headers)), 89 'body' => json_decode($resp_body, true), 90 ]; 91 } 92} 93?>