diff --git a/ChangeLog.md b/ChangeLog.md index c3b0917..61a6fb6 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v0.9.2] - 2023-06-23 +### Security +- Upgraded signature to match the security standard. ## [v0.9.1] - 2019-12-23 ### Added diff --git a/LearnosityDemo/LearnosityDemo.sln b/LearnosityDemo/LearnosityDemo.sln new file mode 100644 index 0000000..16ff9fd --- /dev/null +++ b/LearnosityDemo/LearnosityDemo.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 25.0.1704.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LearnosityDemo", "LearnosityDemo.csproj", "{27F96458-915A-4C09-91CF-C72F2144559F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {27F96458-915A-4C09-91CF-C72F2144559F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27F96458-915A-4C09-91CF-C72F2144559F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27F96458-915A-4C09-91CF-C72F2144559F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27F96458-915A-4C09-91CF-C72F2144559F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1FD8BD7C-CC74-4222-AE18-82F15D9DF2CE} + EndGlobalSection +EndGlobal diff --git a/LearnosityDemo/Pages/AssessAPIDemo.cshtml b/LearnosityDemo/Pages/AssessAPIDemo.cshtml new file mode 100644 index 0000000..2d6cc71 --- /dev/null +++ b/LearnosityDemo/Pages/AssessAPIDemo.cshtml @@ -0,0 +1,26 @@ +@page +@model LearnosityDemo.Pages.AssessAPIDemoModel +@{ + ViewData["Title"] = "Learnosity Example: Standalone Assessment"; + ViewData["TopJS"] = ""; + +} + +
+ + diff --git a/LearnosityDemo/Pages/AssessAPIDemo.cshtml.cs b/LearnosityDemo/Pages/AssessAPIDemo.cshtml.cs new file mode 100644 index 0000000..b11a371 --- /dev/null +++ b/LearnosityDemo/Pages/AssessAPIDemo.cshtml.cs @@ -0,0 +1,414 @@ +using System; +using System.Text; +using LearnositySDK; +using LearnositySDK.Examples; +using LearnositySDK.Request; +using LearnositySDK.Utils; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Newtonsoft.Json; + +namespace LearnosityDemo.Pages +{ + public class AssessAPIDemoModel : PageModel + { + public void OnGet(string mode) + { + string uuid = Uuid.generate(); + string courseId = "mycourse"; + string questionsApiActivityJson = AssessAPIDemoModel.questionsApiActivity(uuid, courseId); + JsonObject questionsApiActivity = JsonObjectFactory.fromString(questionsApiActivityJson); + + string service = "assess"; + + JsonObject security = new JsonObject(); + security.set("consumer_key", Credentials.ConsumerKey); + security.set("user_id", "$ANONYMIZED_USER_ID"); + security.set("domain", Credentials.Domain); + + string secret = Credentials.ConsumerSecret; + + JsonObject request = new JsonObject(); + request.set("name", "Demo Activity (8 questions)"); + request.set("state", "initial"); + request.set("items", AssessAPIDemoModel.items(uuid)); + request.set("questionsApiActivity", questionsApiActivity); + + Init init = new Init(service, security, secret, request); + ViewData["InitJSON"] = init.generate(); + } + + private static JsonObject items(string uuid) + { + JsonObject items = new JsonObject(true); + + for (int i = 3; i <= 10; i++) + { + JsonObject responseIDs = new JsonObject(true); + responseIDs.set(uuid + "_Demo" + i.ToString()); + JsonObject item = new JsonObject(); + item.set("reference", "Demo" + i.ToString()); + item.set("content", ""); + item.set("workflow", new JsonObject(true)); + item.set("response_ids", responseIDs); + item.set("feature_ids", new JsonObject(true)); + items.set(item); + } + + return items; + } + + private static string questionsApiActivity(string uuid, string courseId) + { + return string.Format(@"{{ + ""type"": ""submit_practice"", + ""state"": ""initial"", + ""id"": ""assessdemo"", + ""name"": ""Assess API - Demo"", + ""course_id"": ""{1}"", + ""questions"": [ + {{ + ""type"": ""orderlist"", + ""list"": [ + ""cat"", + ""horse"", + ""pig"", + ""elephant"", + ""mouse"" + ], + ""stimulus"": ""

Arrange these animals from smallest to largest

"", + ""ui_style"": ""button"", + ""validation"": {{ + ""show_partial_ui"": true, + ""partial_scoring"": true, + ""valid_score"": 1, + ""penalty_score"": 0, + ""valid_response"": [ + 4, + 0, + 2, + 1, + 3 + ], + ""pairwise"": false + }}, + ""instant_feedback"": true, + ""response_id"": ""{0}_Demo3"", + ""metadata"": {{ + ""sheet_reference"": ""Demo3"", + ""widget_reference"": ""Demo3"" + }} + }}, + {{ + ""type"": ""clozeassociation"", + ""template"": ""

The United States of America was founded in {{{{response}}}}.

"", + ""possible_responses"": [ + ""1676"", + ""1776"", + ""1876"" + ], + ""feedback_attempts"": 2, + ""instant_feedback"": true, + ""validation"": {{ + ""show_partial_ui"": true, + ""partial_scoring"": true, + ""valid_score"": 1, + ""penalty_score"": 0, + ""valid_responses"": [ + [ + ""1776"" + ] + ] + }}, + ""response_id"": ""{0}_Demo4"", + ""metadata"": {{ + ""sheet_reference"": ""Demo4"", + ""widget_reference"": ""Demo4"" + }} + }}, + {{ + ""type"": ""clozetext"", + ""template"": ""

What is the sum of \\\\(785 \\\\times 89\\\\)

{{{{response}}}}"", + ""is_math"": true, + ""validation"": {{ + ""show_partial_ui"": true, + ""partial_scoring"": true, + ""valid_score"": 1, + ""penalty_score"": 0, + ""valid_responses"": [ + [ + ""69865"" + ] + ] + }}, + ""instant_feedback"": true, + ""response_id"": ""{0}_Demo5"", + ""metadata"": {{ + ""sheet_reference"": ""Demo5"", + ""widget_reference"": ""Demo5"" + }} + }}, + {{ + ""type"": ""numberline"", + ""points"": [ + ""5/5"", + ""1/4"", + ""2/4"", + ""7/8"" + ], + ""is_math"": true, + ""labels"": {{ + ""points"": ""0,1,2,3,4"", + ""show_min"": true, + ""show_max"": true + }}, + ""line"": {{ + ""min"": 0, + ""max"": 4, + ""left_arrow"": true, + ""right_arrow"": true + }}, + ""stimulus"": ""

Drag the points onto the numberline.

"", + ""ticks"": {{ + ""distance"": "".25"", + ""show"": true + }}, + ""validation"": {{ + ""partial_scoring"": ""true"", + ""show_partial_ui"": ""true"", + ""valid_score"": ""1"", + ""penalty_score"": ""0"", + ""threshold"": ""0"", + ""valid_responses"": [ + {{ + ""point"": ""5/5"", + ""position"": ""1"" + }}, + {{ + ""point"": ""1/4"", + ""position"": "".25"" + }}, + {{ + ""point"": ""2/4"", + ""position"": ""2"" + }}, + {{ + ""point"": ""7/8"", + ""position"": ""3.5"" + }} + ] + }}, + ""instant_feedback"": true, + ""snap_to_ticks"": true, + ""response_id"": ""{0}_Demo6"", + ""metadata"": {{ + ""sheet_reference"": ""Demo6"", + ""widget_reference"": ""Demo6"" + }} + }}, + {{ + ""type"": ""tokenhighlight"", + ""template"": ""

He was told not to laugh in class.

"", + ""tokenization"": ""word"", + ""validation"": {{ + ""show_partial_ui"": true, + ""partial_scoring"": true, + ""valid_score"": 1, + ""penalty_score"": 0, + ""valid_responses"": [ + 5 + ] + }}, + ""stimulus"": ""

Highlight the verb in the sentence below.

"", + ""instant_feedback"": true, + ""response_id"": ""{0}_Demo7"", + ""metadata"": {{ + ""sheet_reference"": ""Demo7"", + ""widget_reference"": ""Demo7"" + }} + }}, + {{ + ""type"": ""mcq"", + ""options"": [ + {{ + ""value"": ""0"", + ""label"": ""Berlin"" + }}, + {{ + ""value"": ""1"", + ""label"": ""Paris"" + }}, + {{ + ""value"": ""2"", + ""label"": ""London"" + }}, + {{ + ""value"": ""3"", + ""label"": ""Madrid"" + }} + ], + ""stimulus"": ""What\'s the capital of France?"", + ""stimulus_review"": ""Something Else"", + ""ui_style"": {{ + ""type"": ""block"", + ""choice_label"": ""upper-alpha"" + }}, + ""valid_responses"": [ + {{ + ""value"": ""1"", + ""score"": 1 + }} + ], + ""instant_feedback"": true, + ""response_id"": ""{0}_Demo8"", + ""metadata"": {{ + ""sheet_reference"": ""Demo8"", + ""widget_reference"": ""Demo8"" + }} + }}, + {{ + ""type"": ""clozedropdown"", + ""template"": ""“It’s all clear,’ he {{{{response}}}}. “Have you the chisel and the bags? Great Scott! Jump, Archie, jump, and I’ll swing for it!’ Sherlock {{{{response}}}} had sprung out and seized the {{{{response}}}} by the collar. The other dived down the hole, and I heard the sound of {{{{response}}}} cloth as Jones clutched at his skirts. The light flashed upon the barrel of a revolver, but Holmes’ {{{{response}}}} came down on the man’s wrist, and the pistol {{{{response}}}} upon the stone floor."", + ""possible_responses"": [ + [ + ""whispered"", + ""sprinted"", + ""joked"" + ], + [ + ""Homes"", + ""holmes"", + ""Holmes"" + ], + [ + ""acquaintance"", + ""intruder"", + ""shopkeeper"" + ], + [ + ""burning"", + ""departing"", + ""rending"", + ""broken"" + ], + [ + ""revolver"", + ""hunting crop"" + ], + [ + ""rattled"", + ""clinked"", + ""spilt"" + ] + ], + ""stimulus"": ""

Fill in the blanks.

"", + ""validation"": {{ + ""show_partial_ui"": true, + ""partial_scoring"": true, + ""valid_score"": 1, + ""penalty_score"": 0, + ""valid_responses"": [ + [ + ""whispered"" + ], + [ + ""Holmes"" + ], + [ + ""intruder"" + ], + [ + ""rending"" + ], + [ + ""hunting crop"" + ], + [ + ""clinked"" + ] + ] + }}, + ""instant_feedback"": true, + ""response_id"": ""{0}_Demo9"", + ""metadata"": {{ + ""sheet_reference"": ""Demo9"", + ""widget_reference"": ""Demo9"" + }} + }}, + {{ + ""type"": ""graphplotting"", + ""axis_x"": {{ + ""ticks_distance"": 1, + ""draw_labels"": true + }}, + ""axis_y"": {{ + ""ticks_distance"": 1, + ""draw_labels"": true + }}, + ""canvas"": {{ + ""x_min"": 0, + ""x_max"": 10.2, + ""y_min"": -0.5, + ""y_max"": 10.2, + ""snap_to"": ""grid"" + }}, + ""grid"": {{ + ""x_distance"": 1, + ""y_distance"": 1 + }}, + ""toolbar"": {{ + ""tools"": [ + ""point"", + ""move"" + ], + ""default_tool"": ""point"" + }}, + ""ui_style"": {{ + ""margin"": ""10px"" + }}, + ""stimulus"": ""

Plot the following points \\\\((2,5), (4,8), (8,1)\\\\).

"", + ""is_math"": true, + ""validation"": {{ + ""valid_score"": ""1"", + ""penalty_score"": ""0"", + ""valid_responses"": [ + [ + {{ + ""id"": ""lrn_1"", + ""type"": ""point"", + ""coords"": {{ + ""x"": 2, + ""y"": 5 + }} + }}, + {{ + ""id"": ""lrn_2"", + ""type"": ""point"", + ""coords"": {{ + ""x"": 4, + ""y"": 8 + }} + }}, + {{ + ""id"": ""lrn_3"", + ""type"": ""point"", + ""coords"": {{ + ""x"": 8, + ""y"": 1 + }} + }} + ] + ] + }}, + ""instant_feedback"": true, + ""response_id"": ""{0}_Demo10"", + ""metadata"": {{ + ""sheet_reference"": ""Demo10"", + ""widget_reference"": ""Demo10"" + }} + }} + ] + }}", uuid, courseId); + } + } +} diff --git a/LearnosityDemo/Pages/AuthorAPIDemo.cshtml b/LearnosityDemo/Pages/AuthorAPIDemo.cshtml new file mode 100644 index 0000000..878f4dd --- /dev/null +++ b/LearnosityDemo/Pages/AuthorAPIDemo.cshtml @@ -0,0 +1,23 @@ +@page +@model LearnosityDemo.Pages.AuthorAPIDemoModel +@{ + ViewData["Title"] = "Learnosity Example: Standalone Assessment"; + ViewData["TopJS"] = ""; + + } + +
+ + diff --git a/LearnosityDemo/Pages/AuthorAPIDemo.cshtml.cs b/LearnosityDemo/Pages/AuthorAPIDemo.cshtml.cs new file mode 100644 index 0000000..41df008 --- /dev/null +++ b/LearnosityDemo/Pages/AuthorAPIDemo.cshtml.cs @@ -0,0 +1,117 @@ +using System; +using System.Text; +using LearnositySDK.Request; +using LearnositySDK.Utils; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Newtonsoft.Json; + +namespace LearnosityDemo.Pages +{ + public class AuthorAPIDemoModel : PageModel + { + public void OnGet(string mode) + { + // Author API contains 2 modes + // item_edit mode and item_list mode + // the below example generate initOptions for item_edit mode + // more information about item_list mode can be found in initializeItemList() function + Init init = (mode == "item_list") ? initializeItemList() : initializeItemEdit(); + ViewData["InitJSON"] = init.generate(); + } + + private static Init initializeItemEdit() + { + string service = "author"; + + JsonObject security = new JsonObject(); + security.set("consumer_key", LearnositySDK.Credentials.ConsumerKey); + security.set("domain", LearnositySDK.Credentials.Domain); + + string secret = LearnositySDK.Credentials.ConsumerSecret; + + JsonObject request = new JsonObject(); + request.set("mode", "item_edit"); + request.set("reference", Uuid.generate()); + + JsonObject config = new JsonObject(); + JsonObject config_item_edit = new JsonObject(); + JsonObject config_item_edit_widget = new JsonObject(); + config_item_edit_widget.set("delete", true); + config_item_edit_widget.set("edit", true); + config_item_edit.set("widget", config_item_edit_widget); + JsonObject config_item_edit_item = new JsonObject(); + JsonObject config_item_edit_item_tags = new JsonObject(); + JsonObject config_item_edit_item_tags_includesTagOnEdit = new JsonObject(true); + JsonObject config_item_edit_item_tags_includesTagOnEdit_tag = new JsonObject(); + config_item_edit_item_tags_includesTagOnEdit_tag.set("type", "course"); + config_item_edit_item_tags_includesTagOnEdit_tag.set("name", "commoncore"); + config_item_edit_item_tags_includesTagOnEdit.set(config_item_edit_item_tags_includesTagOnEdit_tag); + config_item_edit_item_tags.set("include_tags_on_edit", config_item_edit_item_tags_includesTagOnEdit); + config_item_edit_item.set("tags", config_item_edit_item_tags); + config_item_edit.set("item", config_item_edit_item); + config.set("item_edit", config_item_edit); + + JsonObject config_questionEditorInitOptions = new JsonObject(); + JsonObject config_questionEditorInitOptions_ui = new JsonObject(); + config_questionEditorInitOptions_ui.set("question_tiles", false); + config_questionEditorInitOptions_ui.set("documentation_link", false); + config_questionEditorInitOptions_ui.set("change_button", true); + config_questionEditorInitOptions_ui.set("source_button", false); + config_questionEditorInitOptions_ui.set("fixed_preview", true); + config_questionEditorInitOptions_ui.set("advanced_group", false); + config_questionEditorInitOptions_ui.set("search_field", false); + config_questionEditorInitOptions.set("ui", config_questionEditorInitOptions_ui); + config.set("question_editor_init_options", config_questionEditorInitOptions); + + request.set("config", config); + + JsonObject user = new JsonObject(); + user.set("id", "brianmoser"); + user.set("firstname", "Test"); + user.set("lastname", "Test"); + user.set("email", "test@test.com"); + request.set("user", user); + + return new Init(service, security, secret, request); + } + + private static Init initializeItemList() + { + string service = "author"; + + JsonObject security = new JsonObject(); + security.set("consumer_key", LearnositySDK.Credentials.ConsumerKey); + security.set("domain", LearnositySDK.Credentials.Domain); + + string secret = LearnositySDK.Credentials.ConsumerSecret; + + JsonObject request = new JsonObject(); + request.set("mode", "item_list"); + + JsonObject config = new JsonObject(); + JsonObject config_item_list = new JsonObject(); + JsonObject config_item_list_toolbar = new JsonObject(); + config_item_list_toolbar.set("add", true); + config_item_list.set("toolbar", config_item_list_toolbar); + config.set("item_list", config_item_list); + request.set("config", config); + + JsonObject tags = new JsonObject(true); + JsonObject tag = new JsonObject(); + tag.set("type", "course"); + tag.set("name", "commoncore"); + tags.set(tag); + request.set("tags", tags); + + JsonObject user = new JsonObject(); + user.set("id", "brianmoser"); + user.set("firstname", "Test"); + user.set("lastname", "Test"); + user.set("email", "test@test.com"); + request.set("user", user); + + return new Init(service, security, secret, request); + } + } +} diff --git a/LearnosityDemo/Pages/Index.cshtml b/LearnosityDemo/Pages/Index.cshtml index b78b99c..2906fb1 100644 --- a/LearnosityDemo/Pages/Index.cshtml +++ b/LearnosityDemo/Pages/Index.cshtml @@ -8,6 +8,11 @@

Learnosity Quick Start Guide


Click to load:

+

Click to load:

+

Click to load:

+

Click to load:

+

Click to load:

+

Click to load:



For instructions, see the tutorial for this project in the README file.

diff --git a/LearnosityDemo/Pages/ItemsAPIDemo.cshtml b/LearnosityDemo/Pages/ItemsAPIDemo.cshtml index 0679949..10e5d7e 100644 --- a/LearnosityDemo/Pages/ItemsAPIDemo.cshtml +++ b/LearnosityDemo/Pages/ItemsAPIDemo.cshtml @@ -2,12 +2,11 @@ @model LearnosityDemo.Pages.ItemsAPIDemoModel @{ ViewData["Title"] = "Learnosity Example: Standalone Assessment"; - ViewData["TopJS"] = ""; + ViewData["TopJS"] = ""; }
- "; + +} + +
+ + diff --git a/LearnosityDemo/Pages/QuestioneditorAPIDemo.cshtml.cs b/LearnosityDemo/Pages/QuestioneditorAPIDemo.cshtml.cs new file mode 100644 index 0000000..ef716a6 --- /dev/null +++ b/LearnosityDemo/Pages/QuestioneditorAPIDemo.cshtml.cs @@ -0,0 +1,47 @@ +using System; +using System.Text; +using LearnositySDK; +using LearnositySDK.Examples; +using LearnositySDK.Request; +using LearnositySDK.Utils; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Newtonsoft.Json; + +namespace LearnosityDemo.Pages +{ + public class QuestioneditorAPIDemoModel : PageModel + { + public void OnGet(string mode) + { + JsonObject request = new JsonObject(); + JsonObject consumer_key = new JsonObject(); + consumer_key.set("consumer_key", LearnositySDK.Credentials.ConsumerKey); + request.set("configuration", consumer_key); + request.set("widget_conversion", true); + + JsonObject ui = new JsonObject(); + ui.set("search_field", true); + + JsonObject layout = new JsonObject(); + layout.set("global_template", "edit_preview"); + layout.set("mode", "advanced"); + ui.set("layout", layout); + request.set("ui", ui); + string service = "questions"; + + JsonObject security = new JsonObject(); + security.set("consumer_key", LearnositySDK.Credentials.ConsumerKey); + security.set("user_id", "abc"); + security.set("domain", LearnositySDK.Credentials.Domain); + + string secret = LearnositySDK.Credentials.ConsumerSecret; + + Init init = new Init(service, security, secret, request); + + ViewData["InitJSON"] = init.generate(); + } + + } +} diff --git a/LearnosityDemo/Pages/QuestionsAPIDemo.cshtml b/LearnosityDemo/Pages/QuestionsAPIDemo.cshtml new file mode 100644 index 0000000..322b627 --- /dev/null +++ b/LearnosityDemo/Pages/QuestionsAPIDemo.cshtml @@ -0,0 +1,27 @@ +@page +@model LearnosityDemo.Pages.QuestionsAPIDemoModel +@{ + ViewData["Title"] = "Learnosity Example: Standalone Assessment"; + ViewData["TopJS"] = ""; +} + + + + + diff --git a/LearnosityDemo/Pages/QuestionsAPIDemo.cshtml.cs b/LearnosityDemo/Pages/QuestionsAPIDemo.cshtml.cs new file mode 100644 index 0000000..63a24c1 --- /dev/null +++ b/LearnosityDemo/Pages/QuestionsAPIDemo.cshtml.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using LearnositySDK.Request; +using LearnositySDK.Utils; +using Newtonsoft.Json; +using Microsoft.AspNetCore.DataProtection; + + +namespace LearnosityDemo.Pages +{ + public class QuestionsAPIDemoModel : PageModel + { + public void OnGet() + { + + // prepare all the params + string service = "questions"; + string uuid = Uuid.generate(); + string courseId = "mycourse"; + JsonObject security = new JsonObject(); + security.set("consumer_key", LearnositySDK.Credentials.ConsumerKey); + security.set("user_id", "abc"); + security.set("domain", LearnositySDK.Credentials.Domain); + string secret = LearnositySDK.Credentials.ConsumerSecret; + JsonObject request = JsonObjectFactory.fromString(QuestionsAPIDemoModel.requestJson(uuid, courseId)); + + // Instantiate Init class + Init init = new Init(service, security, secret, request); + + // Call the generate() method to retrieve a JavaScript object + ViewData["InitJSON"] = init.generate(); + } + + private static string requestJson(string uuid, string courseId) + { + return string.Format(@"{{ + ""type"": ""local_practice"", + ""sigver"": ""v2"", + ""state"": ""initial"", + ""id"": ""questionsapi-demo"", + ""name"": ""Questions API Demo"", + ""course_id"": ""{1}"", + ""questions"": [ + {{ + ""type"": ""association"", + ""response_id"": ""60001"", + ""stimulus"": ""Match the cities to the parent nation."", + ""stimulus_list"": [""London"", ""Dublin"", ""Paris"", ""Sydney""], + ""possible_responses"": [""Australia"", ""France"", ""Ireland"", ""England""], + ""instant_feedback"" : true, + ""validation"": {{ + ""valid_responses"" : [ + [""England""],[""Ireland""],[""France""],[""Australia""] + ] + }} + }}, + + ] + }}", uuid, courseId); + } + } +} diff --git a/LearnosityDemo/Pages/ReportsAPIDemo.cshtml b/LearnosityDemo/Pages/ReportsAPIDemo.cshtml new file mode 100644 index 0000000..b321b3c --- /dev/null +++ b/LearnosityDemo/Pages/ReportsAPIDemo.cshtml @@ -0,0 +1,23 @@ +@page +@model LearnosityDemo.Pages.ReportsAPIDemoModel +@{ + ViewData["Title"] = "Learnosity Example: Standalone Assessment"; + ViewData["TopJS"] = ""; + + } + + + + diff --git a/LearnosityDemo/Pages/ReportsAPIDemo.cshtml.cs b/LearnosityDemo/Pages/ReportsAPIDemo.cshtml.cs new file mode 100644 index 0000000..be72717 --- /dev/null +++ b/LearnosityDemo/Pages/ReportsAPIDemo.cshtml.cs @@ -0,0 +1,41 @@ +using System; +using System.Text; +using LearnositySDK; +using LearnositySDK.Request; +using LearnositySDK.Utils; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Newtonsoft.Json; +using static System.Net.Mime.MediaTypeNames; + +namespace LearnosityDemo.Pages +{ + public class ReportsAPIDemoModel : PageModel + { + public void OnGet(string mode) + { + string service = "reports"; + + JsonObject security = new JsonObject(); + security.set("consumer_key", Credentials.ConsumerKey); + security.set("domain", Credentials.Domain); + + string secret = Credentials.ConsumerSecret; + + JsonObject report = new JsonObject(); + report.set("id", "session-detail"); + report.set("type", "session-detail-by-item"); + report.set("user_id", "906d564c-39d4-44ba-8ddc-2d44066e2ba9"); + report.set("session_id", "906d564c-39d4-44ba-8ddc-2d44066e2ba9"); + + JsonObject reports = new JsonObject(true); + reports.set(report); + + JsonObject request = new JsonObject(); + request.set("reports", reports); + + Init init = new Init(service, security, secret, request); + ViewData["initJSON"] = init.generate(); + } + } +} diff --git a/LearnosityDemo/Pages/Shared/_Layout.cshtml b/LearnosityDemo/Pages/Shared/_Layout.cshtml index 5f90469..e4d79c0 100644 --- a/LearnosityDemo/Pages/Shared/_Layout.cshtml +++ b/LearnosityDemo/Pages/Shared/_Layout.cshtml @@ -25,6 +25,21 @@ + + + + + diff --git a/LearnosityDemo/Properties/launchSettings.json b/LearnosityDemo/Properties/launchSettings.json index afa59ec..22c4049 100644 --- a/LearnosityDemo/Properties/launchSettings.json +++ b/LearnosityDemo/Properties/launchSettings.json @@ -1,4 +1,4 @@ -{ +{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, @@ -24,4 +24,4 @@ } } } -} +} \ No newline at end of file diff --git a/LearnositySDK.sln b/LearnositySDK.sln index 27523be..3534785 100644 --- a/LearnositySDK.sln +++ b/LearnositySDK.sln @@ -25,13 +25,13 @@ Global {DC1B295A-F21E-4A3C-A4DA-76130D281546}.Release|Any CPU.ActiveCfg = Release|Any CPU {DC1B295A-F21E-4A3C-A4DA-76130D281546}.Release|Any CPU.Build.0 = Release|Any CPU {29D6AB6C-C34D-4FAE-A433-E59C0B990DB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29D6AB6C-C34D-4FAE-A433-E59C0B990DB6}.Debug|Any CPU.Build.0 = Debug|Any CPU {29D6AB6C-C34D-4FAE-A433-E59C0B990DB6}.Release|Any CPU.ActiveCfg = Release|Any CPU {29D6AB6C-C34D-4FAE-A433-E59C0B990DB6}.Release|Any CPU.Build.0 = Release|Any CPU + {29D6AB6C-C34D-4FAE-A433-E59C0B990DB6}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC7DF4B1-1A89-40CA-99B1-E059D54EA373}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EC7DF4B1-1A89-40CA-99B1-E059D54EA373}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC7DF4B1-1A89-40CA-99B1-E059D54EA373}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC7DF4B1-1A89-40CA-99B1-E059D54EA373}.Release|Any CPU.Build.0 = Release|Any CPU + {EC7DF4B1-1A89-40CA-99B1-E059D54EA373}.Debug|Any CPU.Build.0 = Debug|Any CPU {48234B2A-1BD6-477F-9DCA-F7F1A1FD15B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {48234B2A-1BD6-477F-9DCA-F7F1A1FD15B2}.Debug|Any CPU.Build.0 = Debug|Any CPU {48234B2A-1BD6-477F-9DCA-F7F1A1FD15B2}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/LearnositySDK/LearnositySDK.csproj b/LearnositySDK/LearnositySDK.csproj index fd822af..6823e67 100644 --- a/LearnositySDK/LearnositySDK.csproj +++ b/LearnositySDK/LearnositySDK.csproj @@ -5,14 +5,14 @@ LearnositySDK LearnositySDK LearnositySDK - 0.9.1 + 0.9.2 Learnosity Learnosity SDK for ASP.NET / C# Telemetry data (basic information about the execution environment) is now added to the request objects being signed which is later read and logged internally by our APIs when the request is received. This allows us to better support our various SDKs and does not send any additional network requests. More information can be found in README.md. - Copyright Learnosity 2017 (c) + Copyright Learnosity 2023 (c) learnosity sdk asp .net - 0.9.1 + 0.9.2 https://github.com/Learnosity/learnosity-sdk-asp.net/blob/master/LICENSE.md https://github.com/Learnosity/learnosity-sdk-asp.net https://github.com/Learnosity/learnosity-sdk-asp.net diff --git a/LearnositySDK/Request/Init.cs b/LearnositySDK/Request/Init.cs index c2498fb..997d2b2 100644 --- a/LearnositySDK/Request/Init.cs +++ b/LearnositySDK/Request/Init.cs @@ -4,6 +4,7 @@ using LearnositySDK.Utils; using System.Web; using System.Runtime.InteropServices; +using System.Drawing; namespace LearnositySDK.Request { @@ -52,6 +53,11 @@ public class Init /// private string requestString; + /// + /// the `prehashString` string is concatination of signaturelist (Array) , required for hashing. + /// + private string prehashString; + /// /// An optional value used to define what type of request is being made. This is only required for certain requests made to the Data API (http://docs.learnosity.com/dataapi/) /// @@ -152,7 +158,7 @@ private void Initialize(string service, JsonObject securityPacket, string secret this.signRequestData = true; this.validSecurityKeys = new string[5] { "consumer_key", "domain", "timestamp", "expires", "user_id" }; this.validServices = new string[7] { "assess", "author", "data", "events", "items", "questions", "reports" }; - this.algorithm = "sha256"; + this.algorithm = "hmac-sha256"; if (this.requestPacket == null) { @@ -208,8 +214,6 @@ public string generateSignature() } } - signatureList.Add(this.secret); - if (this.signRequestData && !Tools.empty(this.requestString)) { signatureList.Add(this.requestString); @@ -220,7 +224,9 @@ public string generateSignature() signatureList.Add(this.action); } - return this.hashValue(signatureList.ToArray()); + this.prehashString = Tools.implode("_", signatureList.ToArray()); + + return this.hashValue(this.prehashString, this.secret); } /// @@ -362,15 +368,15 @@ private JsonObject generateQuestions(JsonObject output) } /// - /// Hash an array value + /// Returns a hash value /// - /// An array to hash + /// String to be hashed + /// String used for encryption /// The hashed string - private string hashValue(string[] value) + private string hashValue(string prehash, string secret ) { - string implode = Tools.implode("_", value); - string hash = Tools.hash(this.algorithm, implode); - return hash; + string hash = Tools.hash(this.algorithm, prehash, secret); + return "$02$" + hash; } /// @@ -406,23 +412,13 @@ private void setServiceOptions() JsonObject questionsApiActivity = new JsonObject(); - List signatureList = new List(); - - signatureList.Add(this.securityPacket.getString("consumer_key")); - signatureList.Add(domain); - signatureList.Add(this.securityPacket.getString("timestamp")); - if (Tools.array_key_exists("expires", this.securityPacket)) { - signatureList.Add(this.securityPacket.getString("expires")); questionsApiActivity.set("expires", this.securityPacket.getString("expires")); questionsApi.remove("expires"); } - signatureList.Add(this.securityPacket.getString("user_id")); - signatureList.Add(this.secret); - - string signature = this.hashValue(signatureList.ToArray()); + string signature = generateSignature(); questionsApiActivity.set("consumer_key", this.securityPacket.getString("consumer_key")); questionsApiActivity.set("timestamp", this.securityPacket.getString("timestamp")); @@ -468,7 +464,7 @@ private void setServiceOptions() for (int i = 0; i < users.Length; i++) { string user_id = users[i]; - hashedUsers.set(user_id, Tools.hash(this.algorithm, user_id + this.secret)); + hashedUsers.set(user_id, Tools.hash(this.algorithm, this.prehashString, user_id + this.secret)); } this.requestPacket.set("users", hashedUsers); } diff --git a/LearnositySDK/Utils/CryptoUtil.cs b/LearnositySDK/Utils/CryptoUtil.cs index 9b83e99..a4999f7 100755 --- a/LearnositySDK/Utils/CryptoUtil.cs +++ b/LearnositySDK/Utils/CryptoUtil.cs @@ -1,4 +1,6 @@ -using System.Security.Cryptography; +using System; +using System.Net.Sockets; +using System.Security.Cryptography; using System.Text; namespace LearnositySDK.Utils @@ -20,6 +22,21 @@ public static string sha256(string s) return hex(hashBytes); } + /// + /// Compute hmac256 hash for string encoded as UTF8 + /// + /// String to be hashed + ///String used for encryption + /// 64-character hex string + public static string hmacsha256(string prehash, string secret) + { + byte[] keyByte = Encoding.UTF8.GetBytes(secret); + byte[] messageBytes = Encoding.UTF8.GetBytes(prehash); + var hmacsha256 = new HMACSHA256(keyByte); + byte[] hashmessage = hmacsha256.ComputeHash(messageBytes); + return hex(hashmessage); + } + /// /// Convert an array of bytes to a string of hex digits /// diff --git a/LearnositySDK/Utils/Tools.cs b/LearnositySDK/Utils/Tools.cs index a5a04e5..5526367 100755 --- a/LearnositySDK/Utils/Tools.cs +++ b/LearnositySDK/Utils/Tools.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Security.Cryptography; + namespace LearnositySDK.Utils { public class Tools @@ -138,22 +139,37 @@ public static string sha256(string str) return CryptoUtil.sha256(str); } + /// + /// Returns HMAC256 hash + /// + /// String to be hashed + ///String used for encryption + /// + public static string hmacsha256(string prehash, string secret) + { + return CryptoUtil.hmacsha256(prehash, secret); + } + /// /// Returns hash /// - /// Possible algorithms: md5, sha256 (default), sha1 - /// + /// Possible algorithms: md5, sha256 (default), sha1, hmac256 + /// String to be hashed + /// String used for encryption /// - public static string hash(string algorithm, string message) + public static string hash(string algorithm, string prehash, string secret) { string str = ""; switch (algorithm) { + case "hmac-sha256": + str = Tools.hmacsha256(prehash, secret); + break; case "sha256": // fall through default: - str = Tools.sha256(message); + str = Tools.sha256(secret); break; } diff --git a/LearnositySDKIntegrationTests/LearnositySDKIntegrationTests.cs b/LearnositySDKIntegrationTests/LearnositySDKIntegrationTests.cs index ac28403..caf54c7 100644 --- a/LearnositySDKIntegrationTests/LearnositySDKIntegrationTests.cs +++ b/LearnositySDKIntegrationTests/LearnositySDKIntegrationTests.cs @@ -83,7 +83,7 @@ public void InitGeneratesExactSameSignature() // Assert signature is still the same Assert.Equal( - "e1eae0b86148df69173cb3b824275ea73c9c93967f7d17d6957fcdd299c8a4fe", + "$02$e19c8a62fba81ef6baf2731e2ab0512feaf573ca5ca5929c2ee9a77303d2e197", init.generateSignature() ); diff --git a/LearnositySDKUnitTests/InitTests.cs b/LearnositySDKUnitTests/InitTests.cs index e22be66..53661b8 100644 --- a/LearnositySDKUnitTests/InitTests.cs +++ b/LearnositySDKUnitTests/InitTests.cs @@ -48,11 +48,11 @@ public void testGenerateSignatureStringRequests() private Dictionary getInitTestCases() { return new Dictionary { - { "assess", "03f4869659eeaca81077785135d5157874f4800e57752bf507891bf39c4d4a90" }, - { "author", "108b985a4db36ef03905572943a514fc02ed7cc6b700926183df7babc2cd1c96" }, - { "items", "dd6bf2a5fd28c9935acef5e2918a1069269154414c19ab346d476c363a7a964c" }, - { "questions", "03f4869659eeaca81077785135d5157874f4800e57752bf507891bf39c4d4a90" }, - { "reports", "91085beccf57bf0df77c89df94d1055e631b36bc11941e61460b445b4ed774bc" }, + { "assess", "$02$8de51b7601f606a7f32665541026580d09616028dde9a929ce81cf2e88f56eb8" }, + { "author", "$02$ca2769c4be77037cf22e0f7a2291fe48c470ac6db2f45520a259907370eff861" }, + { "items", "$02$78ef0334e708829bd3c92decc040f91c8433b07ba32cc9198705a18c36c2ea54" }, + { "questions", "$02$8de51b7601f606a7f32665541026580d09616028dde9a929ce81cf2e88f56eb8" }, + { "reports", "$02$8e0069e7aa8058b47509f35be236c53fa1a878c64b12589fd42f48b568f6ac84" }, }; } @@ -60,7 +60,7 @@ private Dictionary getInitTestCases() { public void testGenerateJsonDataApiGetRequest() { Init init = TestRequest.getTestRequestFor("data", "get").getJsonInit(); - string expectedSignature = "e1eae0b86148df69173cb3b824275ea73c9c93967f7d17d6957fcdd299c8a4fe"; + string expectedSignature = "%2402%24e19c8a62fba81ef6baf2731e2ab0512feaf573ca5ca5929c2ee9a77303d2e197"; Assert.AreEqual( @@ -73,7 +73,7 @@ public void testGenerateJsonDataApiGetRequest() public void testGenerateJsonDataApiPostRequest() { Init init = TestRequest.getTestRequestFor("data", "post").getJsonInit(); - string expectedSignature = "18e5416041a13f95681f747222ca7bdaaebde057f4f222083881cd0ad6282c38"; + string expectedSignature = "%2402%249d1971fb9ac51482f7e73dcf87fc029d4a3dfffa05314f71af9d89fb3c2bcf16"; Assert.AreEqual( "security=%7b%22consumer_key%22%3a%22yis0TYCu7U9V4o7M%22%2c%22domain%22%3a%22localhost%22%2c%22timestamp%22%3a%2220140626-0528%22%2c%22signature%22%3a%22" + expectedSignature + "%22%7d&request=%7b%22limit%22%3a100%7d&action=post", diff --git a/docs/images/image-quickstart-index.png b/docs/images/image-quickstart-index.png new file mode 100644 index 0000000..eab1329 Binary files /dev/null and b/docs/images/image-quickstart-index.png differ diff --git a/readme.md b/readme.md index 8625b7f..e3ef139 100755 --- a/readme.md +++ b/readme.md @@ -109,7 +109,20 @@ From this point on, we'll assume that your web server is available at this local http://localhost:5000/ -The page will load. This is a basic example of an assessment loaded into a web page with Learnosity's assessment player. You can interact with this demo assessment to try out the various Question types. +You can now access the APIs using the following URL [click here](http://localhost:5000) + + + +Following are the routes to access our APIs. + +* Author API : http://localhost:5000/AuthorAPIDemo +* Questions API : http://localhost:5000/QuestionsAPIDemo +* Items API : http://localhost:5000/ItemsAPIDemo +* Reports API : http://localhost:5000/ReportsAPIDemo +* Assess API : http://localhost:5000/AssessAPIDemo +* Question Editor API : http://localhost:5000/QuestioneditorAPIDemo + +Open these pages with your web browser. These are all basic examples of Learnosity's integration. You can interact with these demo pages to try out the various APIs. The Items API example is a basic example of an assessment loaded into a web page with Learnosity's assessment player. You can interact with this demo assessment to try out the various Question types. @@ -193,7 +206,7 @@ This example uses plain HTML in a Razor template. The following example HTML tem @model LearnosityDemo.Pages.ItemsAPIDemoModel @{ ViewData["Title"] = "Learnosity Example: Standalone Assessment"; - ViewData["TopJS"] = ""; + ViewData["TopJS"] = ""; } @@ -208,7 +221,7 @@ This example uses plain HTML in a Razor template. The following example HTML tem The important parts to be aware of in this HTML are: * A div with `id="learnosity_assess"`. This is where the Learnosity assessment player will be rendered to deliver the assessment. -* The `` tag, which includes Learnosity's Items API on the page and makes the global `LearnosityItems` object available. It then puts the data into a variable called `TopJS`, which will be referenced in [_Layout.cshtml](LearnosityDemo/Pages/Shared/_Layout.cshtml). Note: the version specified as `v2021.2.LTS` will retrieve that specific [Long Term Support (LTS) version](https://help.learnosity.com/hc/en-us/articles/360001268538-Release-Cadence-and-Version-Lifecycle). In production, you should always pin to a specific LTS version to ensure version compatibility. +* The `` tag, which includes Learnosity's Items API on the page and makes the global `LearnosityItems` object available. It then puts the data into a variable called `TopJS`, which will be referenced in [_Layout.cshtml](LearnosityDemo/Pages/Shared/_Layout.cshtml). Note: The version specified as `latest-lts` will retrieve the latest version supported. To know more about switching to a specific LTS version, visit our [Long Term Support (LTS) page](https://help.learnosity.com/hc/en-us/articles/360001268538-Release-Cadence-and-Version-Lifecycle). In production, you should always pin to a specific LTS version to ensure version compatibility. * The call to `LearnosityItems.init()`, which initiates Items API to inject the assessment player into the page. * The variable `InitJSON` dynamically sends the contents of our init options to JavaScript, so it can be passed to `init()`.