From 13164e68889fa686d34be1a55f9feab069f231db Mon Sep 17 00:00:00 2001
From: Paulo Valente <16843419+polvalente@users.noreply.github.com>
Date: Fri, 4 Apr 2025 15:47:52 -0300
Subject: [PATCH 1/7] graded cell example
---
.gitignore | 1 +
.../lib/grading_server/answer_store.ex | 7 +-
grading_server/lib/grading_server/answers.ex | 2 +-
grading_server/priv/answers.yml | 40 +----------
modules/grading_client/config/config.exs | 23 ++++++
.../lib/grading_client/application.ex | 12 ++++
.../lib/grading_client/grading_cell.ex | 72 +++++++++++++++++++
modules/grading_client/mix.exs | 5 +-
modules/grading_client/mix.lock | 26 +++++++
9 files changed, 142 insertions(+), 46 deletions(-)
create mode 100644 .gitignore
create mode 100644 modules/grading_client/config/config.exs
create mode 100644 modules/grading_client/lib/grading_client/application.ex
create mode 100644 modules/grading_client/lib/grading_client/grading_cell.ex
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e43b0f9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.DS_Store
diff --git a/grading_server/lib/grading_server/answer_store.ex b/grading_server/lib/grading_server/answer_store.ex
index 2551463..7d36802 100644
--- a/grading_server/lib/grading_server/answer_store.ex
+++ b/grading_server/lib/grading_server/answer_store.ex
@@ -24,7 +24,7 @@ defmodule GradingServer.AnswerStore do
{:ok, state}
end
- defp key(module_id, question_id), do: "module_#{module_id}-#{question_id}}"
+ defp key(module_id, question_id), do: "#{module_id}-#{question_id}"
@impl true
def handle_call({:fetch, module_id, question_id}, _from, _) do
@@ -63,12 +63,9 @@ defmodule GradingServer.AnswerStore do
end
defp map_module({module_name, answers}) do
- [_, module_id] = String.split(module_name, "module_")
- module_id = String.to_integer(module_id)
-
Enum.map(answers, fn data ->
%Answer{
- module_id: module_id,
+ module_id: module_name,
answer: data["answer"],
help_text: data["help_text"],
question_id: data["question_id"]
diff --git a/grading_server/lib/grading_server/answers.ex b/grading_server/lib/grading_server/answers.ex
index e041da3..76e8909 100644
--- a/grading_server/lib/grading_server/answers.ex
+++ b/grading_server/lib/grading_server/answers.ex
@@ -16,7 +16,7 @@ defmodule GradingServer.Answers do
{:incorrect, "Question not found"}
%{answer: correct_answer, help_text: help_text} ->
- if String.trim(answer) == String.trim(correct_answer) do
+ if String.trim(inspect(answer)) == String.trim(correct_answer) do
:correct
else
{:incorrect, help_text}
diff --git a/grading_server/priv/answers.yml b/grading_server/priv/answers.yml
index 9817b88..42ba497 100644
--- a/grading_server/priv/answers.yml
+++ b/grading_server/priv/answers.yml
@@ -3,42 +3,4 @@
module_1:
- question_id: 1
help_text: "This is helpful text that can be used."
- answer: "Not a real answer, but an example."
-
-module_2:
- - question_id: 1
- help_text: ""
- answer: ""
- - question_id: 2
- help_text: ""
- answer: ""
-
-module_3:
- - question_id: 1
- help_text: ""
- answer: ""
-
-module_5:
- - question_id: 1
- help_text: ""
- answer: ""
-
-module_6:
- - question_id: 1
- help_text: ""
- answer: ""
-
-module_7:
- - question_id: 1
- help_text: ""
- answer: ""
-
-module_8:
- - question_id: 1
- help_text: ""
- answer: ""
-
-module_9:
- - question_id: 1
- help_text: ""
- answer: ""
+ answer: ":f"
diff --git a/modules/grading_client/config/config.exs b/modules/grading_client/config/config.exs
new file mode 100644
index 0000000..d9b0ad7
--- /dev/null
+++ b/modules/grading_client/config/config.exs
@@ -0,0 +1,23 @@
+import Config
+
+config :grading_server, answer_store_file: "answers.yml"
+
+# Configures the endpoint
+config :grading_server, GradingServerWeb.Endpoint,
+ server: false,
+ url: [host: "localhost"],
+ render_errors: [view: GradingServerWeb.ErrorView, accepts: ~w(json), layout: false],
+ pubsub_server: GradingServer.PubSub,
+ live_view: [signing_salt: "HVVkYc2t"]
+
+# Configures Elixir's Logger
+config :logger, :console,
+ format: "$time $metadata[$level] $message\n",
+ metadata: [:request_id]
+
+# Use Jason for JSON parsing in Phoenix
+config :phoenix, :json_library, Jason
+
+config :simple_token_authentication,
+ # CHANGE ME IF IN USE
+ token: "my-token"
diff --git a/modules/grading_client/lib/grading_client/application.ex b/modules/grading_client/lib/grading_client/application.ex
new file mode 100644
index 0000000..cb91405
--- /dev/null
+++ b/modules/grading_client/lib/grading_client/application.ex
@@ -0,0 +1,12 @@
+defmodule GradingClient.Application do
+ use Application
+
+ def start(_type, _args) do
+ Kino.SmartCell.register(GradingClient.GradingCell)
+
+ children = []
+
+ opts = [strategy: :one_for_one, name: GradingClient.Supervisor]
+ Supervisor.start_link(children, opts)
+ end
+end
diff --git a/modules/grading_client/lib/grading_client/grading_cell.ex b/modules/grading_client/lib/grading_client/grading_cell.ex
new file mode 100644
index 0000000..93186b4
--- /dev/null
+++ b/modules/grading_client/lib/grading_client/grading_cell.ex
@@ -0,0 +1,72 @@
+defmodule GradingClient.GradingCell do
+ use Kino.JS
+ use Kino.JS.Live
+ use Kino.SmartCell, name: "Graded"
+
+ @impl true
+ def init(attrs, ctx) do
+ source = attrs["source"] || ""
+
+ {:ok, assign(ctx, source: source), editor: [source: source, language: "elixir"]}
+ end
+
+ @impl true
+ def handle_connect(ctx) do
+ {:ok, %{}, ctx}
+ end
+
+ @impl true
+ def handle_editor_change(source, ctx) do
+ {:ok, assign(ctx, source: source)}
+ end
+
+ @impl true
+ def to_attrs(ctx) do
+ %{"source" => ctx.assigns.source}
+ end
+
+ @impl true
+ def to_source(attrs) do
+ try do
+ source = Code.string_to_quoted!(attrs["source"])
+
+ ast =
+ quote do
+ result = unquote(source)
+
+ GradingServer.Answers.check(module_id, question_id, result)
+ end
+
+ Kino.SmartCell.quoted_to_string(ast)
+ rescue
+ error ->
+ IO.inspect(error)
+ attrs["source"]
+ end
+ end
+
+ asset "main.js" do
+ """
+ export function init(ctx, payload) {
+ ctx.importCSS("main.css");
+
+ root.innerHTML = `
+
+ Graded Cell
+
+ `;
+ }
+ """
+ end
+
+ asset "main.css" do
+ """
+ .app {
+ padding: 8px 16px;
+ border: solid 1px #cad5e0;
+ border-radius: 0.5rem 0.5rem 0 0;
+ border-bottom: none;
+ }
+ """
+ end
+end
diff --git a/modules/grading_client/mix.exs b/modules/grading_client/mix.exs
index 88f6bb5..7c520e9 100644
--- a/modules/grading_client/mix.exs
+++ b/modules/grading_client/mix.exs
@@ -14,6 +14,7 @@ defmodule GradingClient.MixProject do
# Run "mix help compile.app" to learn about applications.
def application do
[
+ mod: {GradingClient.Application, []},
extra_applications: [:logger]
]
end
@@ -21,8 +22,10 @@ defmodule GradingClient.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
+ {:grading_server, path: "#{__DIR__}/../../grading_server"},
{:httpoison, "~> 2.1"},
- {:jason, "~> 1.4"}
+ {:jason, "~> 1.4"},
+ {:kino, "~> 0.10"}
]
end
end
diff --git a/modules/grading_client/mix.lock b/modules/grading_client/mix.lock
index c8939aa..a2575ba 100644
--- a/modules/grading_client/mix.lock
+++ b/modules/grading_client/mix.lock
@@ -1,15 +1,41 @@
%{
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
+ "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"},
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
+ "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"},
+ "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
+ "cowlib": {:hex, :cowlib, "2.14.0", "623791c56c1cc9df54a71a9c55147a401549917f00a2e48a6ae12b812c586ced", [:make, :rebar3], [], "hexpm", "0af652d1550c8411c3b58eed7a035a7fb088c0b86aff6bc504b0bc3b7f791aa2"},
"credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
+ "fss": {:hex, :fss, "0.1.1", "9db2344dbbb5d555ce442ac7c2f82dd975b605b50d169314a20f08ed21e08642", [:mix], [], "hexpm", "78ad5955c7919c3764065b21144913df7515d52e228c09427a004afe9c1a16b0"},
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
"httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
+ "kino": {:hex, :kino, "0.15.3", "c99e21fc3e5d89513120295b91efc3efd18f7c1fb83875edced9d06ada13a2c0", [:mix], [{:fss, "~> 0.1.0", [hex: :fss, repo: "hexpm", optional: false]}, {:nx, "~> 0.1", [hex: :nx, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:table, "~> 0.1.2", [hex: :table, repo: "hexpm", optional: false]}], "hexpm", "11f62457ce6ac97ad377db9fcde168361fcf0de7db2a47b6f570607dc7897753"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
+ "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
+ "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"},
+ "phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"},
+ "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"},
+ "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.9", "4dc5e535832733df68df22f9de168b11c0c74bca65b27b088a10ac36dfb75d04", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1dccb04ec8544340e01608e108f32724458d0ac4b07e551406b3b920c40ba2e5"},
+ "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
+ "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
+ "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
+ "plug": {:hex, :plug, "1.17.0", "a0832e7af4ae0f4819e0c08dd2e7482364937aea6a8a997a679f2cbb7e026b2e", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6692046652a69a00a5a21d0b7e11fcf401064839d59d6b8787f23af55b1e6bc"},
+ "plug_cowboy": {:hex, :plug_cowboy, "2.7.3", "1304d36752e8bdde213cea59ef424ca932910a91a07ef9f3874be709c4ddb94b", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "77c95524b2aa5364b247fa17089029e73b951ebc1adeef429361eab0bb55819d"},
+ "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
+ "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
+ "simple_token_authentication": {:hex, :simple_token_authentication, "0.7.0", "5621af0367ee37d71b90d0d9346fdde097ac1daab18c070252e59db620de2e3e", [:mix], [{:plug, ">= 1.3.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "6100de9482b04603b22ecba0b4f93beab1827fdf9dfda6f0dab59830f6815357"},
+ "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
+ "table": {:hex, :table, "0.1.2", "87ad1125f5b70c5dea0307aa633194083eb5182ec537efc94e96af08937e14a8", [:mix], [], "hexpm", "7e99bc7efef806315c7e65640724bf165c3061cdc5d854060f74468367065029"},
+ "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
+ "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
+ "telemetry_poller": {:hex, :telemetry_poller, "1.2.0", "ba82e333215aed9dd2096f93bd1d13ae89d249f82760fcada0850ba33bac154b", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7216e21a6c326eb9aa44328028c34e9fd348fb53667ca837be59d0aa2a0156e8"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
+ "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
+ "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"},
}
From a5caf26b493d02b7ca80a5c67d25ee362f62ae48 Mon Sep 17 00:00:00 2001
From: Paulo Valente <16843419+polvalente@users.noreply.github.com>
Date: Fri, 4 Apr 2025 21:20:14 -0300
Subject: [PATCH 2/7] feat: self-sufficient grading client
---
modules/grading_client/config/config.exs | 23 --------
modules/grading_client/lib/grading_client.ex | 25 +--------
.../lib/grading_client/answer.ex | 11 ++++
.../lib/grading_client/answers.ex | 56 +++++++++++++++++++
.../lib/grading_client/application.ex | 9 ++-
.../lib/grading_client/grading_cell.ex | 4 +-
modules/grading_client/mix.exs | 3 -
modules/grading_client/priv/answers.exs | 24 ++++++++
8 files changed, 102 insertions(+), 53 deletions(-)
delete mode 100644 modules/grading_client/config/config.exs
create mode 100644 modules/grading_client/lib/grading_client/answer.ex
create mode 100644 modules/grading_client/lib/grading_client/answers.ex
create mode 100644 modules/grading_client/priv/answers.exs
diff --git a/modules/grading_client/config/config.exs b/modules/grading_client/config/config.exs
deleted file mode 100644
index d9b0ad7..0000000
--- a/modules/grading_client/config/config.exs
+++ /dev/null
@@ -1,23 +0,0 @@
-import Config
-
-config :grading_server, answer_store_file: "answers.yml"
-
-# Configures the endpoint
-config :grading_server, GradingServerWeb.Endpoint,
- server: false,
- url: [host: "localhost"],
- render_errors: [view: GradingServerWeb.ErrorView, accepts: ~w(json), layout: false],
- pubsub_server: GradingServer.PubSub,
- live_view: [signing_salt: "HVVkYc2t"]
-
-# Configures Elixir's Logger
-config :logger, :console,
- format: "$time $metadata[$level] $message\n",
- metadata: [:request_id]
-
-# Use Jason for JSON parsing in Phoenix
-config :phoenix, :json_library, Jason
-
-config :simple_token_authentication,
- # CHANGE ME IF IN USE
- token: "my-token"
diff --git a/modules/grading_client/lib/grading_client.ex b/modules/grading_client/lib/grading_client.ex
index c3de88d..dc05b59 100644
--- a/modules/grading_client/lib/grading_client.ex
+++ b/modules/grading_client/lib/grading_client.ex
@@ -7,29 +7,8 @@ defmodule GradingClient do
Checks the answer to a question.
"""
- @spec check_answer(any() | String.t(), integer(), integer()) :: :ok | {:error, String.t()}
- def check_answer(answer, module_id, question_id) when not is_binary(answer) do
- check_answer(module_id, question_id, "#{inspect(answer)}")
- end
-
+ @spec check_answer(any(), any(), any()) :: :correct | {:incorrect, String.t() | nil}
def check_answer(answer, module_id, question_id) do
- # TODO: Make this configurable
- url = "http://localhost:4000/api/answers/check"
-
- headers = [
- {"Content-Type", "application/json"}
- ]
-
- json = Jason.encode!(%{module_id: module_id, question_id: question_id, answer: answer})
-
- %{body: body, status_code: 200} = HTTPoison.post!(url, json, headers)
-
- %{"correct" => is_correct, "help_text" => help_text} = Jason.decode!(body)
-
- if is_correct do
- :ok
- else
- {:error, help_text}
- end
+ GradingClient.Answers.check(module_id, question_id, answer)
end
end
diff --git a/modules/grading_client/lib/grading_client/answer.ex b/modules/grading_client/lib/grading_client/answer.ex
new file mode 100644
index 0000000..57723cf
--- /dev/null
+++ b/modules/grading_client/lib/grading_client/answer.ex
@@ -0,0 +1,11 @@
+defmodule GradingClient.Answer do
+ @enforce_keys [:question_id, :module_id, :answer, :help_text]
+ defstruct [:question_id, :module_id, :answer, :help_text]
+
+ @type t :: %__MODULE__{
+ module_id: term(),
+ question_id: term(),
+ answer: term(),
+ help_text: term()
+ }
+end
diff --git a/modules/grading_client/lib/grading_client/answers.ex b/modules/grading_client/lib/grading_client/answers.ex
new file mode 100644
index 0000000..37a3987
--- /dev/null
+++ b/modules/grading_client/lib/grading_client/answers.ex
@@ -0,0 +1,56 @@
+defmodule GradingClient.Answers do
+ @moduledoc """
+ This module is responsible for checking if an answer is correct.
+ It uses the `AnswerStore` to fetch the answer and compares if it is correct or not.
+ """
+ use GenServer
+
+ alias GradingClient.Answer
+
+ @table :answer_store
+
+ def start_link(opts) do
+ GenServer.start_link(__MODULE__, opts, name: __MODULE__)
+ end
+
+ @impl true
+ def init(opts) do
+ :ets.new(@table, [:set, :named_table])
+
+ filename = opts[:filename]
+
+ {answers, _} = Code.eval_file(filename)
+
+ Enum.each(answers, fn answer ->
+ :ets.insert(@table, {{answer.module_id, answer.question_id}, answer})
+ end)
+
+ {:ok, nil}
+ end
+
+ @doc """
+ Checks if the given answer is correct.
+ """
+ @spec check(integer(), integer(), String.t()) :: :correct | {:incorrect, String.t()}
+ def check(module_id, question_id, answer) do
+ GenServer.call(__MODULE__, {:check, module_id, question_id, answer})
+ end
+
+ @impl true
+ def handle_call({:check, module_id, question_id, answer}, _from, state) do
+ result =
+ case :ets.lookup(@table, {module_id, question_id}) do
+ [] ->
+ {:incorrect, "Question not found"}
+
+ [{_id, %Answer{answer: correct_answer, help_text: help_text}}] ->
+ if answer == correct_answer do
+ :correct
+ else
+ {:incorrect, help_text}
+ end
+ end
+
+ {:reply, result, state}
+ end
+end
diff --git a/modules/grading_client/lib/grading_client/application.ex b/modules/grading_client/lib/grading_client/application.ex
index cb91405..c7567ba 100644
--- a/modules/grading_client/lib/grading_client/application.ex
+++ b/modules/grading_client/lib/grading_client/application.ex
@@ -2,9 +2,14 @@ defmodule GradingClient.Application do
use Application
def start(_type, _args) do
- Kino.SmartCell.register(GradingClient.GradingCell)
+ Kino.SmartCell.register(GradingClient.GradedCell)
- children = []
+ default_filename = Path.join(:code.priv_dir(:grading_client), "answers.exs")
+
+ children = [
+ {GradingClient.Answers,
+ filename: Application.get_env(:grading_client, :answers_file, default_filename)}
+ ]
opts = [strategy: :one_for_one, name: GradingClient.Supervisor]
Supervisor.start_link(children, opts)
diff --git a/modules/grading_client/lib/grading_client/grading_cell.ex b/modules/grading_client/lib/grading_client/grading_cell.ex
index 93186b4..1685fc5 100644
--- a/modules/grading_client/lib/grading_client/grading_cell.ex
+++ b/modules/grading_client/lib/grading_client/grading_cell.ex
@@ -1,7 +1,7 @@
-defmodule GradingClient.GradingCell do
+defmodule GradingClient.GradedCell do
use Kino.JS
use Kino.JS.Live
- use Kino.SmartCell, name: "Graded"
+ use Kino.SmartCell, name: "Graded Cell"
@impl true
def init(attrs, ctx) do
diff --git a/modules/grading_client/mix.exs b/modules/grading_client/mix.exs
index 7c520e9..9154457 100644
--- a/modules/grading_client/mix.exs
+++ b/modules/grading_client/mix.exs
@@ -22,9 +22,6 @@ defmodule GradingClient.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
- {:grading_server, path: "#{__DIR__}/../../grading_server"},
- {:httpoison, "~> 2.1"},
- {:jason, "~> 1.4"},
{:kino, "~> 0.10"}
]
end
diff --git a/modules/grading_client/priv/answers.exs b/modules/grading_client/priv/answers.exs
new file mode 100644
index 0000000..c91cfcd
--- /dev/null
+++ b/modules/grading_client/priv/answers.exs
@@ -0,0 +1,24 @@
+alias GradingClient.Answer
+
+to_answers = fn module_name, answers ->
+ Enum.map(answers, fn data ->
+ %Answer{
+ module_id: module_name,
+ question_id: data.question_id,
+ answer: data.answer,
+ help_text: data[:help_text]
+ }
+ end)
+end
+
+owasp_questions = [
+ %{
+ question_id: 1,
+ answer: "A",
+ help_text: "A"
+ }
+]
+
+List.flatten([
+ to_answers.(OWASP, owasp_questions)
+])
From 19e47196759c881fa9e4791bd81d326682a04662 Mon Sep 17 00:00:00 2001
From: Paulo Valente <16843419+polvalente@users.noreply.github.com>
Date: Fri, 4 Apr 2025 22:54:36 -0300
Subject: [PATCH 3/7] feat: self-evaluation and updates for owasp module
---
modules/2-owasp.livemd | 138 ++++++++++++++----
modules/grading_client/config/config.exs | 1 +
modules/grading_client/lib/grading_client.ex | 2 +-
.../lib/grading_client/answers.ex | 26 +++-
.../lib/grading_client/graded_cell.ex | 116 +++++++++++++++
.../lib/grading_client/grading_cell.ex | 72 ---------
modules/grading_client/mix.exs | 4 +-
modules/grading_client/mix.lock | 15 +-
modules/grading_client/priv/answers.exs | 9 +-
.../test/grading_client_test.exs | 4 -
10 files changed, 269 insertions(+), 118 deletions(-)
create mode 100644 modules/grading_client/config/config.exs
create mode 100644 modules/grading_client/lib/grading_client/graded_cell.ex
delete mode 100644 modules/grading_client/lib/grading_client/grading_cell.ex
delete mode 100644 modules/grading_client/test/grading_client_test.exs
diff --git a/modules/2-owasp.livemd b/modules/2-owasp.livemd
index 9accc72..9f4d04b 100644
--- a/modules/2-owasp.livemd
+++ b/modules/2-owasp.livemd
@@ -1,14 +1,14 @@
+
+
# ESCT: Part 2 - OWASP
```elixir
-Mix.install([
- {:grading_client, path: "#{__DIR__}/grading_client"},
- :bcrypt_elixir,
- :httpoison,
- {:absinthe, "~> 1.7.0"},
- {:phoenix, "~> 1.0"},
- {:plug, "~> 1.3.2"}
-])
+Mix.install(
+ [
+ {:grading_client, path: "#{__DIR__}/grading_client"}
+ ],
+ config_path: "#{__DIR__}/grading_client/config/config.exs"
+)
md5_hash = :crypto.hash(:md5, "users_password")
bcrypt_salted_hash = Bcrypt.hash_pwd_salt("users_password")
@@ -103,27 +103,56 @@ Notable CWEs included are CWE-259: Use of Hard-coded Password, CWE-327: Broken o
_Please uncomment the function call that you believe is correct._
+
+
```elixir
-defmodule PasswordCompare do
- def option_one(password, md5_hash) do
- case :crypto.hash(:md5, password) == md5_hash do
- true -> :entry_granted_op1
- false -> :entry_denied_op1
+module_id = Kino.Input.select("Module", [{OWASP, "OWASP"}])
+question_id = Kino.Input.number("Question ID")
+Kino.render(Kino.Layout.grid([module_id, question_id], columns: 2))
+nil
+
+module_id = Kino.Input.read(module_id)
+question_id = Kino.Input.read(question_id)
+
+result =
+ defmodule PasswordCompare do
+ def option_one(password, md5_hash) do
+ case :crypto.hash(:md5, password) == md5_hash do
+ true -> :entry_granted_op1
+ false -> :entry_denied_op1
+ end
end
- end
- def option_two(password, bcrypt_salted_hash) do
- case Bcrypt.verify_pass(password, bcrypt_salted_hash) do
- true -> :entry_granted_op2
- false -> :entry_denied_op2
+ def option_two(password, bcrypt_salted_hash) do
+ case Bcrypt.verify_pass(password, bcrypt_salted_hash) do
+ true -> :entry_granted_op2
+ false -> :entry_denied_op2
+ end
end
end
+
+case GradingClient.check_answer(module_id, question_id, result) do
+ :correct ->
+ IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
+
+ {:incorrect, help_text} when is_binary(help_text) ->
+ IO.puts([IO.ANSI.red(), "Incorrect: ", IO.ANSI.reset(), help_text])
+
+ _ ->
+ IO.puts([IO.ANSI.red(), "Incorrect.", IO.ANSI.reset()])
end
+```
-# DO NOT CHANGE CODE ABOVE THIS LINE =========================
+
-# PasswordCompare.option_one("users_password", md5_hash)
-# PasswordCompare.option_two("users_password", bcrypt_salted_hash)
+```
+Incorrect: Research MD5 Rainbow Tables
+```
+
+
+
+```
+:ok
```
@@ -244,19 +273,58 @@ Notable CWE included is CWE-1104: Use of Unmaintained Third-Party Components
### QUIZ
-**Which of the outdated components currently installed is vulnerable?**
+**Which of the outdated components listed below is vulnerable?**
_Please change the atom below to the name of the vulnerable package installed in this Livebook AND update the afflicted package._
-_HINT: Installed dependencies can be found at the very top, it was the very first cell you ran._
+_HINT: Check the changelogs for each dependency._
+
+
```elixir
-# CHANGE ME
-vulnerable_dependency = :vulnerable_dependency
+module_id = Kino.Input.select("Module", [{OWASP, "OWASP"}])
+question_id = Kino.Input.number("Question ID")
+Kino.render(Kino.Layout.grid([module_id, question_id], columns: 2))
+nil
+
+module_id = Kino.Input.read(module_id)
+question_id = Kino.Input.read(question_id)
+
+result =
+ (
+ answer =
+ Kino.Input.select("Answer",
+ ecto: "Ecto v2.2.2",
+ nx: "Nx v0.5.0",
+ plug: "Plug v1.3.2"
+ )
+
+ Kino.render(answer)
+ Kino.Input.read(answer)
+ )
+
+case GradingClient.check_answer(module_id, question_id, result) do
+ :correct ->
+ IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
+
+ {:incorrect, help_text} when is_binary(help_text) ->
+ IO.puts([IO.ANSI.red(), "Incorrect: ", IO.ANSI.reset(), help_text])
+
+ _ ->
+ IO.puts([IO.ANSI.red(), "Incorrect.", IO.ANSI.reset()])
+end
+```
+
+
-# DO NOT CHANGE CODE BELOW THIS LINE ============================
-Application.spec(vulnerable_dependency)[:vsn] |> List.to_string() |> IO.puts()
-IO.puts(vulnerable_dependency)
+```
+Incorrect: Check the changelog for the next minor or major release.
+```
+
+
+
+```
+:ok
```
@@ -389,4 +457,18 @@ case HTTPoison.get(user_inputted_url) do
end
```
+
+
+```
+This is the IP belonging to your Livebook instance:
+179.241.241.114
+
+```
+
+
+
+```
+:ok
+```
+
[**<- Previous Module: Introduction**](./1-introduction.livemd) || [**Next Module: Secure SDLC Concepts ->**](./3-ssdlc.livemd)
diff --git a/modules/grading_client/config/config.exs b/modules/grading_client/config/config.exs
new file mode 100644
index 0000000..becde76
--- /dev/null
+++ b/modules/grading_client/config/config.exs
@@ -0,0 +1 @@
+import Config
diff --git a/modules/grading_client/lib/grading_client.ex b/modules/grading_client/lib/grading_client.ex
index dc05b59..83efe42 100644
--- a/modules/grading_client/lib/grading_client.ex
+++ b/modules/grading_client/lib/grading_client.ex
@@ -8,7 +8,7 @@ defmodule GradingClient do
"""
@spec check_answer(any(), any(), any()) :: :correct | {:incorrect, String.t() | nil}
- def check_answer(answer, module_id, question_id) do
+ def check_answer(module_id, question_id, answer) do
GradingClient.Answers.check(module_id, question_id, answer)
end
end
diff --git a/modules/grading_client/lib/grading_client/answers.ex b/modules/grading_client/lib/grading_client/answers.ex
index 37a3987..d5f80e7 100644
--- a/modules/grading_client/lib/grading_client/answers.ex
+++ b/modules/grading_client/lib/grading_client/answers.ex
@@ -21,11 +21,13 @@ defmodule GradingClient.Answers do
{answers, _} = Code.eval_file(filename)
- Enum.each(answers, fn answer ->
- :ets.insert(@table, {{answer.module_id, answer.question_id}, answer})
- end)
+ modules =
+ MapSet.new(answers, fn answer ->
+ :ets.insert(@table, {{answer.module_id, answer.question_id}, answer})
+ answer.module_id
+ end)
- {:ok, nil}
+ {:ok, %{modules: modules}}
end
@doc """
@@ -36,6 +38,19 @@ defmodule GradingClient.Answers do
GenServer.call(__MODULE__, {:check, module_id, question_id, answer})
end
+ @doc """
+ Returns the list of modules.
+ """
+ @spec get_modules() :: [atom()]
+ def get_modules() do
+ GenServer.call(__MODULE__, :get_modules)
+ end
+
+ @impl true
+ def handle_call(:get_modules, _from, state) do
+ {:reply, state.modules, state}
+ end
+
@impl true
def handle_call({:check, module_id, question_id, answer}, _from, state) do
result =
@@ -44,6 +59,9 @@ defmodule GradingClient.Answers do
{:incorrect, "Question not found"}
[{_id, %Answer{answer: correct_answer, help_text: help_text}}] ->
+ dbg(answer)
+ dbg(correct_answer)
+
if answer == correct_answer do
:correct
else
diff --git a/modules/grading_client/lib/grading_client/graded_cell.ex b/modules/grading_client/lib/grading_client/graded_cell.ex
new file mode 100644
index 0000000..67363f1
--- /dev/null
+++ b/modules/grading_client/lib/grading_client/graded_cell.ex
@@ -0,0 +1,116 @@
+defmodule GradingClient.GradedCell do
+ use Kino.JS
+ use Kino.JS.Live
+ use Kino.SmartCell, name: "Graded Cell"
+
+ @impl true
+ def init(attrs, ctx) do
+ source = attrs["source"] || ""
+
+ {:ok, assign(ctx, source: source, module_id: nil, question_id: nil),
+ editor: [source: source, language: "elixir"]}
+ end
+
+ @impl true
+ def handle_connect(ctx) do
+ {:ok, %{}, ctx}
+ end
+
+ @impl true
+ def handle_editor_change(source, ctx) do
+ {:ok, assign(ctx, source: source)}
+ end
+
+ @impl true
+ def to_attrs(ctx) do
+ %{
+ "source" => ctx.assigns.source,
+ "module_id" => ctx.assigns.module_id,
+ "question_id" => ctx.assigns.question_id
+ }
+ end
+
+ @impl true
+ def to_source(attrs) do
+ dbg(attrs)
+
+ options = Enum.map(GradingClient.Answers.get_modules(), &{&1, inspect(&1)})
+
+ inputs =
+ quote do
+ module_id = Kino.Input.select("Module", unquote(options))
+ question_id = Kino.Input.number("Question ID")
+
+ Kino.render(Kino.Layout.grid([module_id, question_id], columns: 2))
+ nil
+ end
+
+ source_ast =
+ try do
+ source = Code.string_to_quoted!(attrs["source"])
+
+ quote do
+ module_id = Kino.Input.read(module_id)
+ question_id = Kino.Input.read(question_id)
+
+ result = unquote(source)
+
+ case GradingClient.check_answer(module_id, question_id, result) do
+ :correct ->
+ IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
+
+ {:incorrect, help_text} when is_binary(help_text) ->
+ IO.puts([IO.ANSI.red(), "Incorrect: ", IO.ANSI.reset(), help_text])
+
+ _ ->
+ IO.puts([IO.ANSI.red(), "Incorrect.", IO.ANSI.reset()])
+ end
+ end
+ rescue
+ error ->
+ IO.inspect(error)
+ {:<<>>, [delimiter: ~s["""]], [attrs["source"] <> "\n"]}
+ end
+
+ [
+ Kino.SmartCell.quoted_to_string(inputs),
+ Kino.SmartCell.quoted_to_string(source_ast)
+ ]
+ end
+
+ @impl true
+ def handle_connect(ctx) do
+ {:ok, %{}, ctx}
+ end
+
+ asset "main.js" do
+ """
+ export function init(ctx, payload) {
+ ctx.importCSS("main.css");
+
+ root.innerHTML = `
+
+ `;
+ }
+ """
+ end
+
+ asset "main.css" do
+ """
+ .app {
+ padding: 8px 16px;
+ border: solid 1px #cad5e0;
+ border-radius: 0.5rem 0.5rem 0 0;
+ border-bottom: none;
+ }
+
+ .header {
+ display: grid;
+ grid-template-columns: 3fr 1fr 1fr;
+ gap: 8px;
+ }
+ """
+ end
+end
diff --git a/modules/grading_client/lib/grading_client/grading_cell.ex b/modules/grading_client/lib/grading_client/grading_cell.ex
deleted file mode 100644
index 1685fc5..0000000
--- a/modules/grading_client/lib/grading_client/grading_cell.ex
+++ /dev/null
@@ -1,72 +0,0 @@
-defmodule GradingClient.GradedCell do
- use Kino.JS
- use Kino.JS.Live
- use Kino.SmartCell, name: "Graded Cell"
-
- @impl true
- def init(attrs, ctx) do
- source = attrs["source"] || ""
-
- {:ok, assign(ctx, source: source), editor: [source: source, language: "elixir"]}
- end
-
- @impl true
- def handle_connect(ctx) do
- {:ok, %{}, ctx}
- end
-
- @impl true
- def handle_editor_change(source, ctx) do
- {:ok, assign(ctx, source: source)}
- end
-
- @impl true
- def to_attrs(ctx) do
- %{"source" => ctx.assigns.source}
- end
-
- @impl true
- def to_source(attrs) do
- try do
- source = Code.string_to_quoted!(attrs["source"])
-
- ast =
- quote do
- result = unquote(source)
-
- GradingServer.Answers.check(module_id, question_id, result)
- end
-
- Kino.SmartCell.quoted_to_string(ast)
- rescue
- error ->
- IO.inspect(error)
- attrs["source"]
- end
- end
-
- asset "main.js" do
- """
- export function init(ctx, payload) {
- ctx.importCSS("main.css");
-
- root.innerHTML = `
-
- Graded Cell
-
- `;
- }
- """
- end
-
- asset "main.css" do
- """
- .app {
- padding: 8px 16px;
- border: solid 1px #cad5e0;
- border-radius: 0.5rem 0.5rem 0 0;
- border-bottom: none;
- }
- """
- end
-end
diff --git a/modules/grading_client/mix.exs b/modules/grading_client/mix.exs
index 9154457..b658b86 100644
--- a/modules/grading_client/mix.exs
+++ b/modules/grading_client/mix.exs
@@ -22,7 +22,9 @@ defmodule GradingClient.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
- {:kino, "~> 0.10"}
+ {:kino, "~> 0.10"},
+ {:bcrypt_elixir, "~> 3.2"},
+ {:httpoison, "~> 2.2"}
]
end
end
diff --git a/modules/grading_client/mix.lock b/modules/grading_client/mix.lock
index a2575ba..36afecb 100644
--- a/modules/grading_client/mix.lock
+++ b/modules/grading_client/mix.lock
@@ -1,22 +1,25 @@
%{
+ "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.2.1", "e361261a0401d82dadc1ab7b969f91d250bf7577283e933fe8c5b72f8f5b3c46", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "81170177d5c2e280d12141a0b9d9e299bf731535e2d959982bdcd4cfe3c82865"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"},
- "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
+ "certifi": {:hex, :certifi, "2.14.0", "ed3bef654e69cde5e6c022df8070a579a79e8ba2368a00acf3d75b82d9aceeed", [:rebar3], [], "hexpm", "ea59d87ef89da429b8e905264fdec3419f84f2215bb3d81e07a18aac919026c3"},
+ "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
"cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.14.0", "623791c56c1cc9df54a71a9c55147a401549917f00a2e48a6ae12b812c586ced", [:make, :rebar3], [], "hexpm", "0af652d1550c8411c3b58eed7a035a7fb088c0b86aff6bc504b0bc3b7f791aa2"},
"credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"},
+ "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"fss": {:hex, :fss, "0.1.1", "9db2344dbbb5d555ce442ac7c2f82dd975b605b50d169314a20f08ed21e08642", [:mix], [], "hexpm", "78ad5955c7919c3764065b21144913df7515d52e228c09427a004afe9c1a16b0"},
- "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
- "httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"},
+ "hackney": {:hex, :hackney, "1.23.0", "55cc09077112bcb4a69e54be46ed9bc55537763a96cd4a80a221663a7eafd767", [:rebar3], [{:certifi, "~> 2.14.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "6cd1c04cd15c81e5a493f167b226a15f0938a84fc8f0736ebe4ddcab65c0b44e"},
+ "httpoison": {:hex, :httpoison, "2.2.2", "15420e9e5bbb505b931b2f589dc8be0c3b21e2a91a2c6ba882d99bf8f3ad499d", [:mix], [{:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "de7ac49fe2ffd89219972fdf39b268582f6f7f68d8cd29b4482dacca1ce82324"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"kino": {:hex, :kino, "0.15.3", "c99e21fc3e5d89513120295b91efc3efd18f7c1fb83875edced9d06ada13a2c0", [:mix], [{:fss, "~> 0.1.0", [hex: :fss, repo: "hexpm", optional: false]}, {:nx, "~> 0.1", [hex: :nx, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:table, "~> 0.1.2", [hex: :table, repo: "hexpm", optional: false]}], "hexpm", "11f62457ce6ac97ad377db9fcde168361fcf0de7db2a47b6f570607dc7897753"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
- "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
- "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
+ "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
+ "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"},
"phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"},
@@ -30,7 +33,7 @@
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
"simple_token_authentication": {:hex, :simple_token_authentication, "0.7.0", "5621af0367ee37d71b90d0d9346fdde097ac1daab18c070252e59db620de2e3e", [:mix], [{:plug, ">= 1.3.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "6100de9482b04603b22ecba0b4f93beab1827fdf9dfda6f0dab59830f6815357"},
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
- "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
+ "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"table": {:hex, :table, "0.1.2", "87ad1125f5b70c5dea0307aa633194083eb5182ec537efc94e96af08937e14a8", [:mix], [], "hexpm", "7e99bc7efef806315c7e65640724bf165c3061cdc5d854060f74468367065029"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
diff --git a/modules/grading_client/priv/answers.exs b/modules/grading_client/priv/answers.exs
index c91cfcd..bcd5eb8 100644
--- a/modules/grading_client/priv/answers.exs
+++ b/modules/grading_client/priv/answers.exs
@@ -14,8 +14,13 @@ end
owasp_questions = [
%{
question_id: 1,
- answer: "A",
- help_text: "A"
+ answer: :entry_granted_op2,
+ help_text: "Research MD5 Rainbow Tables"
+ },
+ %{
+ question_id: 2,
+ answer: :plug,
+ help_text: "Check the changelog for the next minor or major release of each option."
}
]
diff --git a/modules/grading_client/test/grading_client_test.exs b/modules/grading_client/test/grading_client_test.exs
deleted file mode 100644
index 759392e..0000000
--- a/modules/grading_client/test/grading_client_test.exs
+++ /dev/null
@@ -1,4 +0,0 @@
-defmodule GradingClientTest do
- use ExUnit.Case
- doctest GradingClient
-end
From 23ff44bd9c6318f27b255033834ac0b9bd270a68 Mon Sep 17 00:00:00 2001
From: Paulo Valente <16843419+polvalente@users.noreply.github.com>
Date: Fri, 4 Apr 2025 23:00:04 -0300
Subject: [PATCH 4/7] chore: revert grading server
---
.../lib/grading_server/answer_store.ex | 7 +++-
grading_server/lib/grading_server/answers.ex | 2 +-
grading_server/priv/answers.yml | 40 ++++++++++++++++++-
3 files changed, 45 insertions(+), 4 deletions(-)
diff --git a/grading_server/lib/grading_server/answer_store.ex b/grading_server/lib/grading_server/answer_store.ex
index 7d36802..2551463 100644
--- a/grading_server/lib/grading_server/answer_store.ex
+++ b/grading_server/lib/grading_server/answer_store.ex
@@ -24,7 +24,7 @@ defmodule GradingServer.AnswerStore do
{:ok, state}
end
- defp key(module_id, question_id), do: "#{module_id}-#{question_id}"
+ defp key(module_id, question_id), do: "module_#{module_id}-#{question_id}}"
@impl true
def handle_call({:fetch, module_id, question_id}, _from, _) do
@@ -63,9 +63,12 @@ defmodule GradingServer.AnswerStore do
end
defp map_module({module_name, answers}) do
+ [_, module_id] = String.split(module_name, "module_")
+ module_id = String.to_integer(module_id)
+
Enum.map(answers, fn data ->
%Answer{
- module_id: module_name,
+ module_id: module_id,
answer: data["answer"],
help_text: data["help_text"],
question_id: data["question_id"]
diff --git a/grading_server/lib/grading_server/answers.ex b/grading_server/lib/grading_server/answers.ex
index 76e8909..e041da3 100644
--- a/grading_server/lib/grading_server/answers.ex
+++ b/grading_server/lib/grading_server/answers.ex
@@ -16,7 +16,7 @@ defmodule GradingServer.Answers do
{:incorrect, "Question not found"}
%{answer: correct_answer, help_text: help_text} ->
- if String.trim(inspect(answer)) == String.trim(correct_answer) do
+ if String.trim(answer) == String.trim(correct_answer) do
:correct
else
{:incorrect, help_text}
diff --git a/grading_server/priv/answers.yml b/grading_server/priv/answers.yml
index 42ba497..9817b88 100644
--- a/grading_server/priv/answers.yml
+++ b/grading_server/priv/answers.yml
@@ -3,4 +3,42 @@
module_1:
- question_id: 1
help_text: "This is helpful text that can be used."
- answer: ":f"
+ answer: "Not a real answer, but an example."
+
+module_2:
+ - question_id: 1
+ help_text: ""
+ answer: ""
+ - question_id: 2
+ help_text: ""
+ answer: ""
+
+module_3:
+ - question_id: 1
+ help_text: ""
+ answer: ""
+
+module_5:
+ - question_id: 1
+ help_text: ""
+ answer: ""
+
+module_6:
+ - question_id: 1
+ help_text: ""
+ answer: ""
+
+module_7:
+ - question_id: 1
+ help_text: ""
+ answer: ""
+
+module_8:
+ - question_id: 1
+ help_text: ""
+ answer: ""
+
+module_9:
+ - question_id: 1
+ help_text: ""
+ answer: ""
From 19d56d3097acdaea4b4c4ca948aad8ba0b4d6b66 Mon Sep 17 00:00:00 2001
From: Paulo Valente <16843419+polvalente@users.noreply.github.com>
Date: Fri, 4 Apr 2025 23:11:05 -0300
Subject: [PATCH 5/7] fix: use comment instead of kino inputs
---
modules/2-owasp.livemd | 98 ++++++++-----------
.../lib/grading_client/graded_cell.ex | 54 +++++-----
2 files changed, 69 insertions(+), 83 deletions(-)
diff --git a/modules/2-owasp.livemd b/modules/2-owasp.livemd
index 9f4d04b..02fa5de 100644
--- a/modules/2-owasp.livemd
+++ b/modules/2-owasp.livemd
@@ -1,5 +1,3 @@
-
-
# ESCT: Part 2 - OWASP
```elixir
@@ -103,17 +101,9 @@ Notable CWEs included are CWE-259: Use of Hard-coded Password, CWE-327: Broken o
_Please uncomment the function call that you believe is correct._
-
+
```elixir
-module_id = Kino.Input.select("Module", [{OWASP, "OWASP"}])
-question_id = Kino.Input.number("Question ID")
-Kino.render(Kino.Layout.grid([module_id, question_id], columns: 2))
-nil
-
-module_id = Kino.Input.read(module_id)
-question_id = Kino.Input.read(question_id)
-
result =
defmodule PasswordCompare do
def option_one(password, md5_hash) do
@@ -131,6 +121,25 @@ result =
end
end
+[module_id, question_id] =
+ "#OWASP:1\ndefmodule PasswordCompare do\n def option_one(password, md5_hash) do\n case :crypto.hash(:md5, password) == md5_hash do\n true -> :entry_granted_op1\n false -> :entry_denied_op1\n end\n end\n\n def option_two(password, bcrypt_salted_hash) do\n case Bcrypt.verify_pass(password, bcrypt_salted_hash) do\n true -> :entry_granted_op2\n false -> :entry_denied_op2\n end\n end\nend\n\n# DO NOT CHANGE CODE ABOVE THIS LINE =========================\n\n# PasswordCompare.option_one(\"users_password\", md5_hash)\n# PasswordCompare.option_two(\"users_password\", bcrypt_salted_hash)"
+ |> String.split("\n", parts: 2)
+ |> hd()
+ |> String.trim_leading("#")
+ |> String.split(":", parts: 2)
+
+module_id =
+ case %{"OWASP" => OWASP}[String.trim(module_id)] do
+ nil -> raise "invalid module id: #{module_id}"
+ module_id -> module_id
+ end
+
+question_id =
+ case Integer.parse(String.trim(question_id)) do
+ {id, ""} -> id
+ _ -> raise "invalid question id: #{question_id}"
+ end
+
case GradingClient.check_answer(module_id, question_id, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
@@ -143,18 +152,6 @@ case GradingClient.check_answer(module_id, question_id, result) do
end
```
-
-
-```
-Incorrect: Research MD5 Rainbow Tables
-```
-
-
-
-```
-:ok
-```
-
## Injection
@@ -279,17 +276,9 @@ _Please change the atom below to the name of the vulnerable package installed in
_HINT: Check the changelogs for each dependency._
-
+
```elixir
-module_id = Kino.Input.select("Module", [{OWASP, "OWASP"}])
-question_id = Kino.Input.number("Question ID")
-Kino.render(Kino.Layout.grid([module_id, question_id], columns: 2))
-nil
-
-module_id = Kino.Input.read(module_id)
-question_id = Kino.Input.read(question_id)
-
result =
(
answer =
@@ -303,6 +292,25 @@ result =
Kino.Input.read(answer)
)
+[module_id, question_id] =
+ "#OWASP:2\nanswer = \n Kino.Input.select(\"Answer\", [\n {:ecto, \"Ecto v2.2.2\"},\n {:nx, \"Nx v0.5.0\"},\n {:plug, \"Plug v1.3.2\"}\n ])\n\nKino.render(answer)\n\nKino.Input.read(answer)"
+ |> String.split("\n", parts: 2)
+ |> hd()
+ |> String.trim_leading("#")
+ |> String.split(":", parts: 2)
+
+module_id =
+ case %{"OWASP" => OWASP}[String.trim(module_id)] do
+ nil -> raise "invalid module id: #{module_id}"
+ module_id -> module_id
+ end
+
+question_id =
+ case Integer.parse(String.trim(question_id)) do
+ {id, ""} -> id
+ _ -> raise "invalid question id: #{question_id}"
+ end
+
case GradingClient.check_answer(module_id, question_id, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
@@ -315,18 +323,6 @@ case GradingClient.check_answer(module_id, question_id, result) do
end
```
-
-
-```
-Incorrect: Check the changelog for the next minor or major release.
-```
-
-
-
-```
-:ok
-```
-
## Identification and Authentication Failures
@@ -457,18 +453,4 @@ case HTTPoison.get(user_inputted_url) do
end
```
-
-
-```
-This is the IP belonging to your Livebook instance:
-179.241.241.114
-
-```
-
-
-
-```
-:ok
-```
-
[**<- Previous Module: Introduction**](./1-introduction.livemd) || [**Next Module: Secure SDLC Concepts ->**](./3-ssdlc.livemd)
diff --git a/modules/grading_client/lib/grading_client/graded_cell.ex b/modules/grading_client/lib/grading_client/graded_cell.ex
index 67363f1..18f1761 100644
--- a/modules/grading_client/lib/grading_client/graded_cell.ex
+++ b/modules/grading_client/lib/grading_client/graded_cell.ex
@@ -11,11 +11,6 @@ defmodule GradingClient.GradedCell do
editor: [source: source, language: "elixir"]}
end
- @impl true
- def handle_connect(ctx) do
- {:ok, %{}, ctx}
- end
-
@impl true
def handle_editor_change(source, ctx) do
{:ok, assign(ctx, source: source)}
@@ -32,29 +27,41 @@ defmodule GradingClient.GradedCell do
@impl true
def to_source(attrs) do
- dbg(attrs)
-
- options = Enum.map(GradingClient.Answers.get_modules(), &{&1, inspect(&1)})
-
- inputs =
- quote do
- module_id = Kino.Input.select("Module", unquote(options))
- question_id = Kino.Input.number("Question ID")
-
- Kino.render(Kino.Layout.grid([module_id, question_id], columns: 2))
- nil
- end
+ modules = Map.new(GradingClient.Answers.get_modules(), &{inspect(&1), &1})
source_ast =
try do
- source = Code.string_to_quoted!(attrs["source"])
+ source_attr = attrs["source"]
+ source = Code.string_to_quoted!(source_attr)
quote do
- module_id = Kino.Input.read(module_id)
- question_id = Kino.Input.read(question_id)
-
result = unquote(source)
+ [module_id, question_id] =
+ unquote(source_attr)
+ |> String.split("\n", parts: 2)
+ |> hd()
+ |> String.trim_leading("#")
+ |> String.split(":", parts: 2)
+
+ module_id =
+ case unquote(Macro.escape(modules))[String.trim(module_id)] do
+ nil ->
+ raise "invalid module id: #{module_id}"
+
+ module_id ->
+ module_id
+ end
+
+ question_id =
+ case Integer.parse(String.trim(question_id)) do
+ {id, ""} ->
+ id
+
+ _ ->
+ raise "invalid question id: #{question_id}"
+ end
+
case GradingClient.check_answer(module_id, question_id, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
@@ -72,10 +79,7 @@ defmodule GradingClient.GradedCell do
{:<<>>, [delimiter: ~s["""]], [attrs["source"] <> "\n"]}
end
- [
- Kino.SmartCell.quoted_to_string(inputs),
- Kino.SmartCell.quoted_to_string(source_ast)
- ]
+ Kino.SmartCell.quoted_to_string(source_ast)
end
@impl true
From 1096c2c264c6a48aba4c3ddacdd2296cec688115 Mon Sep 17 00:00:00 2001
From: Paulo Valente <16843419+polvalente@users.noreply.github.com>
Date: Fri, 4 Apr 2025 23:19:43 -0300
Subject: [PATCH 6/7] chore: cleanup stray changes
---
.../lib/grading_client/graded_cell.ex | 17 +++--------------
1 file changed, 3 insertions(+), 14 deletions(-)
diff --git a/modules/grading_client/lib/grading_client/graded_cell.ex b/modules/grading_client/lib/grading_client/graded_cell.ex
index 18f1761..d4e7355 100644
--- a/modules/grading_client/lib/grading_client/graded_cell.ex
+++ b/modules/grading_client/lib/grading_client/graded_cell.ex
@@ -7,8 +7,7 @@ defmodule GradingClient.GradedCell do
def init(attrs, ctx) do
source = attrs["source"] || ""
- {:ok, assign(ctx, source: source, module_id: nil, question_id: nil),
- editor: [source: source, language: "elixir"]}
+ {:ok, assign(ctx, source: source), editor: [source: source, language: "elixir"]}
end
@impl true
@@ -18,11 +17,7 @@ defmodule GradingClient.GradedCell do
@impl true
def to_attrs(ctx) do
- %{
- "source" => ctx.assigns.source,
- "module_id" => ctx.assigns.module_id,
- "question_id" => ctx.assigns.question_id
- }
+ %{"source" => ctx.assigns.source}
end
@impl true
@@ -93,7 +88,7 @@ defmodule GradingClient.GradedCell do
ctx.importCSS("main.css");
root.innerHTML = `
-