/
babelium_gateway.php
258 lines (216 loc) · 9.51 KB
/
babelium_gateway.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
<?php
/**
* Babelium Project open source collaborative second language oral practice - http://www.babeliumproject.com
*
* Copyright (c) 2012 GHyM and by respective authors (see below).
*
* This file is part of Babelium Project.
*
* Babelium Project is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Babelium Project is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class babelium_gateway{
private $_curlHeaders = array();
private $_curlHeaderHttpStatusCode;
private $_curlHeaderHttpStatusMessage;
private $_curlResponse;
private $_curlOutput;
/**
* Parses the output of cURL. The headers found in this output are stored in the $_curlHeaders class property.
* The response object, which should be a JSON object, is stored in the $_curlResponse class property.
* @param string $output
* A string that contains the output of the last cURL execution
* @throws moodle_exception
* The response did not contain the required JSON object
*/
private function parseCurlOutput($output){
$this->_curlResponse = null;
foreach(preg_split("/(\r?\n)/", $output, 0) as $line){
if(!empty($line)){
if(preg_match('/(^{.+)/', $line, $matches)){
$this->_curlResponse = $matches[1];
} else {
$this->_curlHeaders[] = $line;
}
}
}
$this->parseResponseHeaders();
}
/**
* Searches for a HTTP status code in the response headers. If no headers are returned
* or the status code is different to 200 it throws an error
* @throws moodle_exception
* The response had no headers or the status code is not 200
*/
private function parseResponseHeaders(){
$this->_curlHeaderHttpStatusCode = 500;
$this->_curlHeaderHttpStatusMessage = 'Internal server error';
if($this->_curlHeaders && is_array($this->_curlHeaders) && count($this->_curlHeaders) > 0){
foreach($this->_curlHeaders as $h){
if(preg_match("/^HTTP\/\d.\d (\d+)(.*)/",$h,$matches)){
$this->_curlHeaderHttpStatusCode = trim($matches[1]);
$this->_curlHeaderHttpStatusMessage = trim($matches[2]);
break;
}
}
}
}
/**
* Makes API requests using cURL and a signed header
* @param String $method
* The API method to which the request is going to be made
* @param mixed $parameters
* The parameters of the API request can be either an associative array or simple data types
* @return mixed $result
* An array of data when the API request was successful, false on error.
* Make sure you use the identical operator to check the response (!==FALSE) some response may be 0
* @throws moodle_exception
* The required configuration parameters of the API are not set
*/
public function newServiceCall($method,$parameters = null){
global $USER, $CFG;
//Test if connection settings have the correct format
$config_fields = array('filter_babelium_serverdomain',
'filter_babelium_serverport',
'filter_babelium_apidomain',
'filter_babelium_apiendpoint',
'filter_babelium_accesskey',
'filter_babelium_secretaccesskey');
foreach($config_fields as $cfield){
if(!isset($CFG->$cfield) || empty($CFG->$cfield)){
$this->display_error('babeliumErrorConfigParameters');
}
if(($cfield == 'filter_babelium_accesskey' && strlen($CFG->$cfield)!=20 ) ||
($cfield == 'filter_babelium_secretaccesskey' && strlen($CFG->$cfield)!=40)){
$this->display_error('babeliumErrorConfigParameters');
}
}
$commProtocol = 'http://';
$request = array();
$request['method'] = $method;
if($parameters != null && is_array($parameters) && count($parameters) > 0){
$request['parameters'] = $parameters;
}
//Date timestamp formated following one of the standards allowed for HTTP 1.1 date headers (DATE_RFC1123)
$date = date("D, d M Y H:i:s O");
$referer = $_SERVER['HTTP_REFERER'];
$pieces = parse_url($referer);
$originhost = $_SERVER['HTTP_HOST'];
$origin = $pieces['scheme'] . "://" . $originhost;
$request['header']['date'] = $date;
$signature = "BMP ".$CFG->filter_babelium_accesskey.":".$this->generateAuthorization($method, $date, $originhost, $CFG->filter_babelium_secretaccesskey);
$request['header']['authorization'] = $signature;
//See this workaround if the query parameters are written with & : http://es.php.net/manual/es/function.http-build-query.php#102324
$request = http_build_query($request,'', '&');
$web_domain = $CFG->filter_babelium_serverdomain;
$api_domain = $CFG->filter_babelium_apidomain;
$api_endpoint = $CFG->filter_babelium_apiendpoint;
$api_url = $commProtocol . $api_domain . '/' . $api_endpoint;
$query_string = $api_url . '?' . $method;
//Check if proxy (if used) should be bypassed for this url
$proxybypass = function_exists('is_proxybypass') ? is_proxybypass($query_string) : false;
//Prepare the cURL request
if (!$ch = curl_init($query_string)) {
debugging('Can not init curl.');
return false;
}
//curl_setopt($ch, CURLOPT_URL, $query_string);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_REFERER, $referer);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Origin: $origin"));
//Check for proxy configuration
if (!empty($CFG->proxyhost) and !$proxybypass) {
// SOCKS supported in PHP5 only
if (!empty($CFG->proxytype) and ($CFG->proxytype == 'SOCKS5')) {
if (defined('CURLPROXY_SOCKS5')) {
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
} else {
curl_close($ch);
debugging("SOCKS5 proxy is not supported in PHP4.", DEBUG_ALL);
return false;
}
}
curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, false);
if (empty($CFG->proxyport)) {
curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost);
} else {
curl_setopt($ch, CURLOPT_PROXY, $CFG->proxyhost.':'.$CFG->proxyport);
}
if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
if (defined('CURLOPT_PROXYAUTH')) {
// any proxy authentication if PHP 5.1
curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
}
}
}
$result = curl_exec($ch);
curl_close($ch);
//Parses the response output to separate the headers from the body of the response
$this->parseCurlOutput($result);
//Add the service call to the activity log to better track down possible problems
$this->activity_log($USER->id,$USER->username, "service_call",$query_string, $method, is_array($parameters)?implode('&',$parameters):$parameters, $date, $_SERVER['HTTP_HOST'], $signature, $this->_curlHeaderHttpStatusCode, $this->_curlHeaderHttpStatusMessage);
//If the body of the response is empty or the HTTP status code is not 200 display an error message
if(!$this->_curlResponse || $this->_curlHeaderHttpStatusCode != 200)
$this->display_error('babeliumApiErrorCode'.$this->_curlHeaderHttpStatusCode);
$result = json_decode($this->_curlResponse,true);
if($result['status'] == 'success' && $result['response'])
$result = $result['response'];
else
$result = false;
return $result;
}
/**
* Makes a unique signature for each API request
* @param String $method
* The API method to which the request is going to be made
* @param String $date
* The current date of the calling server formatted following the RFC1123 specification
* @return String $signature
* The authorization header of the request
*/
private function generateAuthorization($method, $date, $origin, $skey){
$stringtosign = utf8_encode($method . "\n" . $date . "\n" . $origin);
$digest = hash_hmac("sha256", $stringtosign, $skey, false);
$signature = base64_encode($digest);
return $signature;
}
/**
* A small function for displaying the API errors. This is done via a function for compatibility issues between moodle 1.9 and moodle 2.x
* @param String $errorCode
* An error string that identifies the cause of the problem
* @throws moodle_exception
* A moodle exception is thrown if we are working with moodle 2.x. Otherways the older error() function is used
*/
private function display_error($errorCode){
if(!$errorCode)
return;
//We are in moodle 2.x
if(class_exists("moodle_exception")){
throw new moodle_exception($errorCode,'assignment_babelium');
} else {
error(get_string($errorCode,'assignment_babelium'));
}
}
private function activity_log($user_id=0, $user_name='', $action='', $query_string='', $req_method='', $req_params='', $req_hdate='', $req_hdomain='', $req_hsignature='', $resp_http_status=0, $info=''){
global $CFG;
$log_file_path = $CFG->dataroot."/babelium.log";
$calling_ip = getremoteaddr(); //getremoteaddr() is a moodle's function
$time_now = time();
$message = $time_now ."\t".$calling_ip."\t".$user_id."\t".$user_name."\t".$action."\t".$req_hdate."\t".$req_hdomain."\t".$req_hsignature."\t".$resp_http_status."\t".$query_string."\t".$req_method."\t".$req_params."\t".$info;
error_log($message."\n",3,$log_file_path);
}
}