From 4ba50f9fb58a449f9e8fcd11a5b4ac968cec8650 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 1 Aug 2020 19:37:16 -0400 Subject: [PATCH] Store encrypted HTML --- lib/wiki/content/page.ex | 51 +++++++++++++----- lib/wiki/content/renderer.ex | 52 ++++++++++++++----- lib/wiki_web/controllers/page_controller.ex | 3 +- lib/wiki_web/templates/page/show.html.eex | 2 +- .../20200726020001_create_pages.exs | 5 +- 5 files changed, 81 insertions(+), 32 deletions(-) diff --git a/lib/wiki/content/page.ex b/lib/wiki/content/page.ex index 6faca84..df45b2e 100644 --- a/lib/wiki/content/page.ex +++ b/lib/wiki/content/page.ex @@ -8,6 +8,9 @@ defmodule Wiki.Content.Page do encrypted_content_tag: binary(), content: String.t() | nil, content_encryption_key: String.t() | nil, + encrypted_html: binary(), + encrypted_html_iv: binary(), + encrypted_html_tag: binary(), html: String.t() | nil, title: String.t(), user: Wiki.Accounts.User.t(), @@ -20,6 +23,9 @@ defmodule Wiki.Content.Page do field :encrypted_content_tag, :binary field :content, :string, virtual: true field :content_encryption_key, :string, virtual: true + field :encrypted_html, :binary + field :encrypted_html_iv, :binary + field :encrypted_html_tag, :binary field :html, :string, virtual: true field :title, :string @@ -55,13 +61,21 @@ defmodule Wiki.Content.Page do defp encrypt_changeset(%Ecto.Changeset{changes: %{content: _}} = changeset) do content = get_change(changeset, :content) key = get_field(changeset, :content_encryption_key) + {encrypted_content, tag, iv} = do_encrypt(content, key) + user_id = get_field(changeset, :user_id) + {html, _linked_pages} = Wiki.Content.Renderer.render(content, user_id) + {encrypted_html, html_tag, html_iv} = do_encrypt(html, key) + changeset |> put_change(:encrypted_content, encrypted_content) |> put_change(:encrypted_content_tag, tag) |> put_change(:encrypted_content_iv, iv) |> delete_change(:content) + |> put_change(:encrypted_html, encrypted_html) + |> put_change(:encrypted_html_tag, html_tag) + |> put_change(:encrypted_html_iv, html_iv) |> delete_change(:content_encryption_key) end @@ -74,29 +88,38 @@ defmodule Wiki.Content.Page do key = Base.decode16!(key, case: :lower) iv = :crypto.strong_rand_bytes(@iv_size) - {encrypted_text, tag} = - :crypto.crypto_one_time_aead(:aes_256_gcm, key, iv, text, <<>>, true) - |> IO.inspect() + {encrypted_text, tag} = :crypto.crypto_one_time_aead(:aes_256_gcm, key, iv, text, <<>>, true) {encrypted_text, tag, iv} end def decrypt_content(page) do - key = Base.decode16!(page.content_encryption_key, case: :lower) - iv = page.encrypted_content_iv - tag = page.encrypted_content_tag + key = page.content_encryption_key content = - :crypto.crypto_one_time_aead( - :aes_256_gcm, - key, - iv, + do_decrypt( page.encrypted_content, - <<>>, - tag, - false + page.encrypted_content_iv, + page.encrypted_content_tag, + key ) - %__MODULE__{page | content: content} + html = do_decrypt(page.encrypted_html, page.encrypted_html_iv, page.encrypted_html_tag, key) + + %__MODULE__{page | content: content, html: html} + end + + defp do_decrypt(encrypted_text, iv, tag, key) do + key = Base.decode16!(key, case: :lower) + + :crypto.crypto_one_time_aead( + :aes_256_gcm, + key, + iv, + encrypted_text, + <<>>, + tag, + false + ) end end diff --git a/lib/wiki/content/renderer.ex b/lib/wiki/content/renderer.ex index 0cb5465..5c4a689 100644 --- a/lib/wiki/content/renderer.ex +++ b/lib/wiki/content/renderer.ex @@ -5,37 +5,61 @@ defmodule Wiki.Content.Renderer do alias WikiWeb.Endpoint import Ecto.Query - @spec render(Page.t()) :: String.t() - def render(page) do - page.content - |> replace_page_links(page) - |> render_markdown() + @spec render(String.t(), integer()) :: {String.t(), [integer()]} + def render(content, user_id) do + {content, linked_pages} = replace_page_links(content, user_id) + + { + render_markdown(content), + Enum.uniq(linked_pages) + } end @page_link_regex ~r/\[\[.*?\]\]/ - @spec replace_page_links(content :: String.t(), root_page :: Page.t()) :: String.t() - defp replace_page_links(content, root_page) do - String.replace(content, @page_link_regex, fn match -> + @spec replace_page_links(content :: String.t(), user_id :: integer()) :: + {String.t(), [integer()]} + defp replace_page_links(content, user_id) do + results = Regex.scan(@page_link_regex, content, return: :index) + + results + |> Enum.reverse() + |> Enum.reduce({content, []}, fn [{start, length}], {str, acc} -> + # can't use String.slice here, because the indicies produced by Regex.scan are character indices, and slice operates on graphemes + match = :erlang.binary_part(str, start, length) title = String.slice(match, 2..-3) lower_title = String.downcase(title) - path = + str_before = :erlang.binary_part(str, 0, start) + + str_after = :erlang.binary_part(str, start + length, byte_size(str) - start - length) + + {path, acc} = from(p in Page) - |> where([p], p.user_id == ^root_page.user_id) + |> where([p], p.user_id == ^user_id) |> where([p], fragment("lower(?)", p.title) == ^lower_title) |> select([p], p.id) |> Repo.one() |> case do nil -> - Routes.page_path(Endpoint, :new, title: title) + { + Routes.page_path(Endpoint, :new, title: title), + acc + } linked_page_id -> - Routes.page_path(Endpoint, :show, linked_page_id) + { + Routes.page_path(Endpoint, :show, linked_page_id), + [linked_page_id | acc] + } end - # convert link to markdown, because [[title]] -> link transformation happens before markdown parsing - "[#{title}](#{path})" + page_link = "[#{title}](#{path})" + + { + str_before <> page_link <> str_after, + acc + } end) end diff --git a/lib/wiki_web/controllers/page_controller.ex b/lib/wiki_web/controllers/page_controller.ex index faf7289..1c020bf 100644 --- a/lib/wiki_web/controllers/page_controller.ex +++ b/lib/wiki_web/controllers/page_controller.ex @@ -86,8 +86,7 @@ defmodule WikiWeb.PageController do def show(conn, _params) do page = Repo.preload(conn.assigns.page, :uploads) - rendered_content = Content.Renderer.render(page) - render(conn, "show.html", page: page, content: rendered_content) + render(conn, "show.html", page: page) end def edit(conn, _params) do diff --git a/lib/wiki_web/templates/page/show.html.eex b/lib/wiki_web/templates/page/show.html.eex index 4c8e558..9b381ad 100644 --- a/lib/wiki_web/templates/page/show.html.eex +++ b/lib/wiki_web/templates/page/show.html.eex @@ -3,7 +3,7 @@ <%= link "Back", to: Routes.page_path(@conn, :index) %>
- <%= raw(@content) %> + <%= raw(@page.html) %>
<%= for upload <- @page.uploads do %> diff --git a/priv/repo/migrations/20200726020001_create_pages.exs b/priv/repo/migrations/20200726020001_create_pages.exs index 14ce625..8a4ade3 100644 --- a/priv/repo/migrations/20200726020001_create_pages.exs +++ b/priv/repo/migrations/20200726020001_create_pages.exs @@ -7,12 +7,15 @@ defmodule Wiki.Repo.Migrations.CreatePages do add :encrypted_content, :binary add :encrypted_content_iv, :binary add :encrypted_content_tag, :binary + add :encrypted_html, :binary + add :encrypted_html_iv, :binary + add :encrypted_html_tag, :binary add :user_id, references(:users, on_delete: :delete_all) timestamps() end create index(:pages, [:user_id]) - create index(:pages, ["(lower(title))"], name: :pages_lowercase_title_index) + create unique_index(:pages, ["(lower(title))"], name: :pages_lowercase_title_index) end end