9 from http.cookiejar
import MozillaCookieJar
10 from urllib.parse
import urlparse
11 from getpass
import getpass
13 moodle_domain =
"moodle.iitb.ac.in"
20 Encapsulates the recurring logic for sending out requests to the
26 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) AppleWebKit/537.36'
27 +
' (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile'
29 'Content-Type':
'application/x-www-form-urlencoded',
35 moodle_path: str =
'/',
37 skip_cert_verify: bool =
False,
38 log_responses_to: str =
None,
40 self.
tokentoken = token
44 self.
verifyverify =
not skip_cert_verify
45 self.
url_baseurl_base =
'https://' + moodle_domain + moodle_path
50 if log_responses_to
is not None:
53 response_log_file.write(
'JSON Log:\n\n')
55 logging.getLogger(
"requests").setLevel(logging.WARNING)
56 logging.getLogger(
"urllib3").setLevel(logging.WARNING)
57 urllib3.disable_warnings()
60 def post_URL(self, url: str, data: {str: str} =
None, cookie_jar_path: str =
None):
62 Sends a POST request to a specific URL, including saving of cookies in cookie jar.
63 @param url: The url to which the request is sent. (the moodle base url is not added to the given URL)
64 @param data: The optional data is added to the POST body.
65 @param cookie_jar_path: Path to the cookies file.
66 @return: The resulting response object and the session object.
71 data_urlencoded = RequestHelper.recursive_urlencode(data)
73 session = requests.Session()
75 if cookie_jar_path
is not None:
76 session.cookies = MozillaCookieJar(cookie_jar_path)
78 if os.path.exists(cookie_jar_path):
79 session.cookies.load(ignore_discard=
True, ignore_expires=
True)
81 response = session.post(url, data=data_urlencoded, headers=self.
stdHeaderstdHeader, verify=self.
verifyverify)
83 if cookie_jar_path
is not None:
84 for cookie
in session.cookies:
85 cookie.expires = 2147483647
87 session.cookies.save(ignore_discard=
True, ignore_expires=
True)
89 return response, session
91 def get_URL(self, url: str, cookie_jar_path: str =
None):
93 Sends a GET request to a specific URL of the Moodle system, including additional cookies
94 (cookies are updated after the request)
95 @param url: The url to which the request is sent. (the moodle base url is not added to the given URL)
96 @param cookie_jar_path: The optional cookies to add to the request
97 @return: The resulting Response object.
100 session = requests.Session()
102 if cookie_jar_path
is not None:
103 session.cookies = MozillaCookieJar(cookie_jar_path)
105 if os.path.exists(cookie_jar_path):
106 session.cookies.load(ignore_discard=
True, ignore_expires=
True)
108 response = session.get(url, headers=self.
stdHeaderstdHeader, verify=self.
verifyverify)
110 if cookie_jar_path
is not None:
111 session.cookies.save(ignore_discard=
True, ignore_expires=
True)
113 return response, session
115 def post_REST(self, function: str, data: {str: str} =
None) -> object:
117 Sends a POST request to the REST endpoint of the Moodle system
118 @param function: The Web service function to be called.
119 @param data: The optional data is added to the POST body.
120 @return: The JSON response returned by the Moodle system, already
124 if self.
tokentoken
is None:
125 raise ValueError(
'The required Token is not set!')
130 response = requests.post(url, data=data_urlencoded, headers=self.
stdHeaderstdHeader, verify=self.
verifyverify)
133 if self.
log_responseslog_responses
and function
not in [
'tool_mobile_get_autologin_key']:
135 response_log_file.write(
'URL: {}\n'.format(response.url))
136 response_log_file.write(
'Function: {}\n\n'.format(function))
137 response_log_file.write(
'Data: {}\n\n'.format(data))
138 response_log_file.write(json.dumps(json_result, indent=4, ensure_ascii=
False))
139 response_log_file.write(
'\n\n\n')
144 def _get_REST_POST_URL(url_base: str, function: str) -> str:
146 Generates an URL for a REST-POST request
147 @params: The necessary parameters for a REST URL
148 @return: A formatted URL
150 url =
'%swebservice/rest/server.php?moodlewsrestformat=json&wsfunction=%s' % (url_base, function)
155 def _get_POST_DATA(function: str, token: str, data_obj: str) -> str:
157 Generates the data for a REST-POST request
158 @params: The necessary parameters for a REST URL
159 @return: A URL-encoded data string
161 data = {
'moodlewssettingfilter':
'true',
'moodlewssettingfileurl':
'true'}
163 if data_obj
is not None:
164 data.update(data_obj)
166 data.update({
'wsfunction': function,
'wstoken': token})
168 return RequestHelper.recursive_urlencode(data)
172 Sends a POST request to the login endpoint of the Moodle system to
173 obtain a token in JSON format.
174 @param data: The data is inserted into the Post-Body as arguments. This
175 should contain the login data.
176 @return: The JSON response returned by the Moodle System, already
180 response = requests.post(
181 '%slogin/token.php' % (self.
url_baseurl_base),
182 data=urllib.parse.urlencode(data),
190 def _check_response_code(response):
192 if response.status_code != 200:
194 'An Unexpected Error happened on side of the Moodle System!'
195 + (
' Status-Code: %s' % str(response.status_code))
196 + (
'\nHeader: %s' % response.headers)
197 + (
'\nResponse: %s' % response.text)
202 Query the version by looking up the change-log (/lib/upgrade.txt)
204 @return: a float number representing the newest version
205 parsed from the change-log
208 url =
'%slib/upgrade.txt' % (self.
url_baseurl_base)
209 response = requests.get(url, headers=self.
stdHeaderstdHeader, verify=self.
verifyverify)
213 changelog = str(response.text).split(
'\n')
215 for line
in changelog:
216 match = re.match(
r'^===\s*([\d\.]+)\s*===$', line)
218 version_string = match.group(1)
221 majorVersion = version_string.split(
'.')[0]
222 minorVersion = version_string[len(majorVersion) :].replace(
'.',
'')
224 version = float(majorVersion +
'.' + minorVersion)
227 def _initial_parse(self, response) -> object:
229 The first time parsing the result of a REST request.
230 It is checked for known errors.
231 @param response: The JSON response of the Moodle system
232 @return: The parsed JSON object
239 response_extracted = response.json()
241 except Exception
as error:
243 'An Unexpected Error occurred while trying'
244 +
' to parse the json response! Moodle'
245 +
' response: %s.\nError: %s' % (response.read(), error)
248 if 'error' in response_extracted:
249 error = response_extracted.get(
'error',
'')
250 errorcode = response_extracted.get(
'errorcode',
'')
251 stacktrace = response_extracted.get(
'stacktrace',
'')
252 debuginfo = response_extracted.get(
'debuginfo',
'')
253 reproductionlink = response_extracted.get(
'reproductionlink',
'')
254 raise RequestRejectedError(
255 'The Moodle System rejected the Request.'
256 + (
' Details: %s (Errorcode: %s, ' % (error, errorcode))
257 + (
'Stacktrace: %s, Debuginfo: %s, Reproductionlink: %s)' % (stacktrace, debuginfo, reproductionlink))
260 if 'exception' in response_extracted:
261 exception = response_extracted.get(
'exception',
'')
262 errorcode = response_extracted.get(
'errorcode',
'')
263 message = response_extracted.get(
'message',
'')
265 raise RequestRejectedError(
266 'The Moodle System rejected the Request.'
267 +
' Details: %s (Errorcode: %s, Message: %s)' % (exception, errorcode, message)
270 return response_extracted
274 """URL-encode a multidimensional dictionary.
275 @param data: the data to be encoded
276 @returns: the url encoded data
279 def recursion(data, base=[]):
282 for key, value
in data.items():
283 new_base = base + [key]
284 if hasattr(value,
'values'):
285 pairs += recursion(value, new_base)
288 if len(new_base) > 1:
289 first = urllib.parse.quote(new_base.pop(0))
290 rest = map(
lambda x: urllib.parse.quote(x), new_base)
291 new_pair =
'%s[%s]=%s' % (first,
']['.join(rest), urllib.parse.quote(str(value)))
293 new_pair =
'%s=%s' % (urllib.parse.quote(str(key)), urllib.parse.quote(str(value)))
294 pairs.append(new_pair)
297 return '&'.join(recursion(data))
299 def obtain_login_token(
300 username: str, password: str, moodle_domain: str, moodle_path: str =
'/', skip_cert_verify: bool =
False
303 Send the login credentials to the Moodle-System and extracts the resulting Login-Token.
304 @params: The necessary parameters to create a Token.
305 @return: The received token.
307 login_data = {
'username': username,
'password': password,
'service':
'moodle_mobile_app'}
309 obj =
RequestHelper(moodle_domain, moodle_path, skip_cert_verify=skip_cert_verify)
310 response = obj.get_login(login_data)
312 if 'token' not in response:
315 raise RuntimeError(
'Invalid response received from the Moodle System! No token was received.')
317 if 'privatetoken' not in response:
318 return response.get(
'token',
''),
None
320 return response.get(
'token',
''), response.get(
'privatetoken',
''), obj
322 def _split_moodle_uri(moodle_uri: str):
324 moodle_domain = moodle_uri.netloc
325 moodle_path = moodle_uri.path
326 if not moodle_path.endswith(
'/'):
327 moodle_path = moodle_path +
'/'
329 if moodle_path ==
'':
332 return moodle_domain, moodle_path
335 def worker(moodle_token):
340 request_helper =
RequestHelper(moodle_domain, moodle_path, moodle_token,
False)
341 return request_helper
This is Class for Creating and formatting requests made to Moodle API functions.
def _check_response_code(response)
str _get_POST_DATA(str function, str token, str data_obj)
def recursive_urlencode(data)
object get_login(self, {str:str} data)
str _get_REST_POST_URL(str url_base, str function)
object _initial_parse(self, response)
object post_REST(self, str function, {str:str} data=None)
def get_URL(self, str url, str cookie_jar_path=None)
def post_URL(self, str url, {str:str} data=None, str cookie_jar_path=None)
float get_simple_moodle_version(self)