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?>