diff --git a/docs/quickstart/assessment/standalone_assessment.py b/docs/quickstart/assessment/standalone_assessment.py index f8c5062..19444cb 100644 --- a/docs/quickstart/assessment/standalone_assessment.py +++ b/docs/quickstart/assessment/standalone_assessment.py @@ -22,9 +22,19 @@ host = "localhost" port = 8000 +# Public & private security keys required to access Learnosity APIs and +# data. These keys grant access to Learnosity's public demos account. +# Learnosity will provide keys for your own account. +security = { + "user_id" : "abc", + 'consumer_key': config.consumer_key, + # Change to the domain used in the browser, e.g. 127.0.0.1, learnosity.com + 'domain': host, +} + # Items API configuration parameters. items_request = { - # Unique student identifier, a UUID generated above. + # Unique student identifier, a UUID generated above. "user_id": user_id, # A reference of the Activity to retrieve from the Item bank, defining # which Items will be served in this assessment. @@ -47,56 +57,568 @@ "state": "initial" } -# Public & private security keys required to access Learnosity APIs and -# data. These keys grant access to Learnosity's public demos account. -# Learnosity will provide keys for your own account. -security = { - 'consumer_key': config.consumer_key, - # Change to the domain used in the browser, e.g. 127.0.0.1, learnosity.com - 'domain': host, -} +# Questions API configuration parameters. +questions_request = { + "id": "f0001", + "name": "Intro Activity - French 101", + "questions": [ + { + "response_id": "60005", + "type": "association", + "stimulus": "Match the cities to the parent nation.", + "stimulus_list": ["London", "Dublin", "Paris", "Sydney"], + "possible_responses": ["Australia", "France", "Ireland", "England" + ], + "validation": { + "valid_responses": [ + ["England"],["Ireland"],["France"],["Australia"] + ] + }, + "instant_feedback": True + } + ], +} + +# Author API configuration parameters. +# mode can be changed by item_list and item_edit +author_request = { + "mode": "item_edit", + "reference": "a15ac409-f6d5-42de-a491-a1e4ab03c826", + "user": { + "id" : "brianmoser", + "firstname" : "Test", + "lastname" : "Test", + "email" : "test@test.com" + }, + "config": { + "global": { + "disable_onbeforeunload": True, + "hide_tags": + [ + { + "type": "internal_category_uuid" + } + ] + }, + "item_edit": { + "item": { + "back": True, + "columns": True, + "answers": True, + "scoring": True, + "reference": { + "edit": False, + "show": False, + "prefix": "LEAR_" + }, + "save": True, + "status": False, + "dynamic_content": True, + "shared_passage": True + }, + "widget": { + "delete": False, + "edit": True + } + }, + "item_list": { + "item": { + "status": True, + "url": "http://myApp.com/items/:reference/edit" + }, + "toolbar": { + "add": True, + "browse": { + "controls": [ + { + "type": "hierarchy", + "hierarchies": [ + { + "reference": "CCSS_Math_Hierarchy", + "label": "CCSS Math" + }, + { + "reference": "CCSS_ELA_Hierarchy", + "label": "CCSS ELA" + }, + { + "reference": "Demo_Items_Hierarchy", + "label": "Demo Items" + } + ] + }, + { + "type": "tag", + "tag": { + "type": "Alignment", + "label": "def456" + } + }, + { + "type": "tag", + "tag": { + "type": "Course", + "label": "commoncore" + } + } + ] + } + }, + "filter": { + "restricted": { + "current_user": True, + "tags": { + "all": [ + { + "type": "Alignment", + "name": ["def456", "abc123"] + }, + { + "type": "Course" + } + ], + "either": [ + { + "type": "Grade", + "name": "4" + }, + { + "type": "Grade", + "name": "5" + }, + { + "type": "Subject", + "name": ["Math", "Science"] + } + ], + "none": [ + { + "type": "Grade", + "name": "6" + } + ] + } + } + } + }, + "dependencies": { + "question_editor_api": { + "init_options": {} + }, + "questions_api": { + "init_options": {} + } + }, + "widget_templates": { + "back": True, + "save": True, + "widget_types": { + "default": "questions", + "show": True + } + }, + "container": { + "height": "auto", + "fixed_footer_height": 0, + "scroll_into_view_selector": "body" + }, + "label_bundle": { + + "backButton": "Zurück", + "loadingText": "Wird geladen", + "modalClose": "Schließen", + "saveButton": "Speichern", + "duplicateButton": "Duplikat", + + + "dateTimeLocale": "en-us", + "toolTipDateTimeSeparator": "um", + "toolTipDateFormat": "DD-MM-YYYY", + "toolTipTimeFormat": "HH:MM:SS", + } + }, + } + +# Assess API configuration parameters. +assess_request = { + "items": [ + { + "content": "", + "response_ids": ["demoscience1234"], + "workflow": "", + "reference": "question-demoscience1" + }, + { + "content": "", + "response_ids": ["demoscience5678"], + "workflow": "", + "reference": "question-demoscience2" + }], + "ui_style": "horizontal", + "name": "Demo (2 questions)", + "state": "initial", + "metadata": [], + "navigation": + { + "show_next": True, + "toc": True, + "show_submit": True, + "show_save": False, + "show_prev": True, + "show_title": True, + "show_intro": True + }, + "time": + { + "max_time": 600, + "limit_type": "soft", + "show_pause": True, + "warning_time": 60, + "show_time": True + }, + "configuration": + { + "onsubmit_redirect_url": "/assessment/", + "onsave_redirect_url": "/assessment/", + "idle_timeout": True, + "questionsApiVersion": "v2" + }, + "questionsApiActivity": + { + "user_id": "abc", + "type": "submit_practice", + "state": "initial", + "id": "assessdemo", + "name": "Assess API - Demo", + "questions": [ + { + "response_id": "demoscience1234", + "type": "sortlist", + "description": "In this question, the student needs to sort the events, chronologically earliest to latest.", + "list": ["Russian Revolution", "Discovery of the Americas", "Storming of the Bastille", "Battle of Plataea", "Founding of Rome", "First Crusade"], + "instant_feedback": True, + "feedback_attempts": 2, + "validation": + { + "valid_response": [4, 3, 5, 1, 2, 0], + "valid_score": 1, + "partial_scoring": True, + "penalty_score": -1 + } + }, + { + "response_id": "demoscience5678", + "type": "highlight", + "description": "The student needs to mark one of the flowers anthers in the image.", + "img_src": "http://www.learnosity.com/static/img/flower.jpg", + "line_color": "rgb(255, 20, 0)", + "line_width": "4" + }] + }, + "type": "activity" +} + +# Reports API configuration parameters. +report_request = { + "reports" : [{ + "id": "session-detail", + "type": "session-detail-by-item", + "user_id": "906d564c-39d4-44ba-8ddc-2d44066e2ba9", + "session_id": "906d564c-39d4-44ba-8ddc-2d44066e2ba9" + }] +} + +# Question Editor API configuration parameters. +question_editor_request = { + "configuration" : { + "consumer_key": config.consumer_key, + }, + "widget_conversion": True, + "ui" : { + "search_field" : True, + }, + "layout":{ + "global_template": "edit_preview", + "mode": "advanced" + } + } # Set up Learnosity initialization data. -init = Init( +initItems = Init( 'items', security, config.consumer_secret, - request=items_request + request = items_request +) + +initQuestions = Init( + 'questions', security, config.consumer_secret, + request = questions_request +) + +initAuthor = Init( + 'author',security,config.consumer_secret, + request = author_request ) -generated_request = init.generate() + +initAssess = Init( + 'assess',security,config.consumer_secret, + request = assess_request +) + +initReports = Init( + 'reports',security,config.consumer_secret, + request = report_request +) + +initQuestionEditor = Init( + 'questions',security,config.consumer_secret, + request = question_editor_request +) + +# Generated request(initOptions) w.r.t all apis +generated_request_Items = initItems.generate() +generated_request_Questions = initQuestions.generate() +generated_request_Author = initAuthor.generate() +generated_request_Assess = initAssess.generate() +generated_request_Reports = initReports.generate() +generated_request_QuestionEditor = initQuestionEditor.generate() # - - - - - - Section 2: your web page configuration - - - - - -# # Set up the HTML page template, for serving to the built-in Python web server class LearnosityServer(BaseHTTPRequestHandler): + + def createResponse(self,response): + # Send headers and data back to the client. + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + # Send the response to the client. + self.wfile.write(response.encode("utf-8")) + def do_GET(self): + + if self.path.endswith('/'): + # Define the page HTML, as a Jinja template, with {{variables}} passed in. - template = Template(""" - - - - - -

{{ name }}

- -
- - - - - - - """) - - # Render the page template and grab the variables needed. - response = template.render(name='Standalone Assessment Example', generated_request=generated_request) - - # Send headers and data back to the client. - self.send_response(200) - self.send_header("Content-type", "text/html") - self.end_headers() - # Send the response to the client. - self.wfile.write(response.encode("utf-8")) + template = Template(""" + + + + + +

{{ name }}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
API'sLinks
Author ApiHere
Questions ApiHere
Items ApiHere
Reports ApiHere
Question Editor ApiHere
Assess ApiHere
+ + + """) + + # Render the page template and grab the variables needed. + response = template.render(name='Standalone APIs Examples') + self.createResponse(response) + + if self.path.endswith('/itemsapi'): + # Define the page HTML, as a Jinja template, with {{variables}} passed in. + template = Template(""" + + +

{{ name }}

+ +
+ + + + + + + """) + + # Render the page template and grab the variables needed. + response = template.render(name='Standalone Items API Example', generated_request=generated_request_Items) + + self.createResponse(response) + + if self.path.endswith('/questionsapi'): + # Define the page HTML, as a Jinja template, with {{variables}} passed in. + template = Template(""" + + +

{{ name }}

+ + + + + + + + + """) + + response = template.render(name='Standalone Questions API Example', generated_request=generated_request_Questions) + self.createResponse(response) + + if self.path.endswith('/authorapi'): + # Define the page HTML, as a Jinja template, with {{variables}} passed in. + template = Template(""" + + +

{{ name }}

+ +
+ + + + + + + """) + + response = template.render(name='Standalone Author API Example', generated_request=generated_request_Author) + self.createResponse(response) + + if self.path.endswith('/assessapi'): + # Define the page HTML, as a Jinja template, with {{variables}} passed in. + template = Template(""" + + + + + +

{{ name }}

+ +
+ + + + + + + """) + + response = template.render(name='Standalone Assess API Example', generated_request=generated_request_Assess) + self.createResponse(response) + + if self.path.endswith('/reportsapi'): + # Define the page HTML, as a Jinja template, with {{variables}} passed in. + template = Template(""" + + +

{{ name }}

+ + + + + + + + + """) + + response = template.render(name='Standalone Reports API Example', generated_request=generated_request_Reports) + self.createResponse(response) + + if self.path.endswith('/questioneditorapi'): + # Define the page HTML, as a Jinja template, with {{variables}} passed in. + template = Template(""" + + +

{{ name }}

+ +
+ + + + + + + """) + + response = template.render(name='Standalone Question Editor API Example', generated_request=generated_request_QuestionEditor) + self.createResponse(response) def main(): web_server = HTTPServer((host, port), LearnosityServer) diff --git a/learnosity_sdk/request/init.py b/learnosity_sdk/request/init.py index 7f0bc4f..9a11067 100644 --- a/learnosity_sdk/request/init.py +++ b/learnosity_sdk/request/init.py @@ -1,6 +1,7 @@ import datetime import hashlib +import hmac import json import platform from learnosity_sdk._version import __version__ @@ -132,9 +133,6 @@ def generate_signature(self): if key in self.security: vals.append(self.security[key]) - # Add the secret. - vals.append(self.secret) - # Add the request if necessary if self.sign_request_data and self.request_string is not None: vals.append(self.request_string) @@ -239,7 +237,9 @@ def set_service_options(self): def hash_list(self, l): "Hash a list by concatenating values with an underscore" - return hashlib.sha256("_".join(l).encode('utf-8')).hexdigest() + concatValues = "_".join(l) + signature = hmac.new(bytes(str(self.secret),'utf_8'), msg = bytes(str(concatValues) , 'utf-8'), digestmod = hashlib.sha256).hexdigest() + return '$02$' + signature def add_telemetry_data(self): if self.__telemetry_enabled: diff --git a/tests/unit/test_init.py b/tests/unit/test_init.py index bcffed6..09603bd 100644 --- a/tests/unit/test_init.py +++ b/tests/unit/test_init.py @@ -42,36 +42,36 @@ ] }, None, - '03f4869659eeaca81077785135d5157874f4800e57752bf507891bf39c4d4a90', + '$02$8de51b7601f606a7f32665541026580d09616028dde9a929ce81cf2e88f56eb8', ), ServiceTestSpec( "data", True, None, {"limit": 100}, "get", - 'e1eae0b86148df69173cb3b824275ea73c9c93967f7d17d6957fcdd299c8a4fe', + '$02$e19c8a62fba81ef6baf2731e2ab0512feaf573ca5ca5929c2ee9a77303d2e197', ), ServiceTestSpec( "assess", True, {"user_id": "$ANONYMIZED_USER_ID"}, {"foo": "bar"}, None, - '03f4869659eeaca81077785135d5157874f4800e57752bf507891bf39c4d4a90', + '$02$8de51b7601f606a7f32665541026580d09616028dde9a929ce81cf2e88f56eb8', ), ServiceTestSpec( # string "items", True, {"user_id": "$ANONYMIZED_USER_ID"}, '{ "user_id" : "$ANONYMIZED_USER_ID", "activity_id": "8E9859C2-CBCF-427B-A478-B8FFC5222DEB", "session_id": "E637AC08-7BF1-48AF-B264-0F40D5BF8898", "rendering_type": "assess", "items": [ "item_1" ] }', None, - '584e9c7cae8530e92b258b3ac4361e58484a5e604f0b17d0acd8d7298cb8230a', + '$02$57bfc14e7d1c66d1f370546120dda2195b3ad8ad866c5fcd818c4051389f6df2', ), ServiceTestSpec( # Dict "items", True, {"user_id": "$ANONYMIZED_USER_ID"}, { "user_id" : "$ANONYMIZED_USER_ID", "activity_id": "8E9859C2-CBCF-427B-A478-B8FFC5222DEB", "session_id": "E637AC08-7BF1-48AF-B264-0F40D5BF8898", "rendering_type": "assess", "items": [ "item_1" ] }, None, - '584e9c7cae8530e92b258b3ac4361e58484a5e604f0b17d0acd8d7298cb8230a', + '$02$57bfc14e7d1c66d1f370546120dda2195b3ad8ad866c5fcd818c4051389f6df2', ), ServiceTestSpec( "events", True, None, {"users": [ "$ANONYMIZED_USER_ID_1", "$ANONYMIZED_USER_ID_2", "$ANONYMIZED_USER_ID_3", "$ANONYMIZED_USER_ID_4" ] }, None, - '20739eed410d54a135e8cb3745628834886ab315bfc01693ce9acc0d14dc98bf' + '$02$5c3160dbb9ab4d01774b5c2fc3b01a35ce4f9709c84571c27dfe333d1ca9d349' ), ]