diff --git a/lib/recycledcloud/accounts.ex b/lib/recycledcloud/accounts.ex index 94ce1ce..3cd13d6 100644 --- a/lib/recycledcloud/accounts.ex +++ b/lib/recycledcloud/accounts.ex @@ -5,7 +5,7 @@ defmodule RecycledCloud.Accounts do import Ecto.Query, warn: false alias RecycledCloud.Repo - alias RecycledCloud.Accounts.{User, UserToken, UserNotifier} + alias RecycledCloud.Accounts.{User, Key, UserToken, UserNotifier} alias RecycledCloud.LDAP ## Database getters @@ -356,4 +356,27 @@ defmodule RecycledCloud.Accounts do {:error, :user, changeset, _} -> {:error, changeset} end end + + ## Keys + + def get_key!(id), do: Repo.get!(Key, id) + + def get_keys_for(%User{} = user) do + Repo.preload(user, :keys).keys + end + + def change_user_key(user, attrs \\ %{}) do + Key.changeset(user, attrs) + end + + def add_key(%User{} = user, value, comment \\ nil) do + %Key{} + |> Key.changeset(%{value: value, comment: comment}) + |> Ecto.Changeset.put_assoc(:user, user) + |> Repo.insert() + end + + def remove_key!(%Key{} = key) do + Repo.delete!(key) + end end diff --git a/lib/recycledcloud/accounts/key.ex b/lib/recycledcloud/accounts/key.ex new file mode 100644 index 0000000..7fd2a37 --- /dev/null +++ b/lib/recycledcloud/accounts/key.ex @@ -0,0 +1,48 @@ +defmodule RecycledCloud.Accounts.Key do + use Ecto.Schema + import Ecto.Changeset + + schema "keys" do + field :comment, :string + field :value, :string + field :fingerprint, :string + belongs_to :user, RecycledCloud.Accounts.User + + timestamps() + end + + def process_ssh_key(changeset, value_field, fingerprint_field, comment_field) do + raw_value = get_field(changeset, value_field) + comment = get_field(changeset, comment_field) + + with {:is_valid, true} <- {:is_valid, changeset.valid?}, + [{pk, attrs}] <- :public_key.ssh_decode(raw_value, :public_key) do + + fingerprint = :public_key.ssh_hostkey_fingerprint(pk) |> List.to_string + changeset = put_change(changeset, fingerprint_field, fingerprint) + + # Fetch comment from key, if not provided. + embedded_comment = attrs + |> Enum.into(%{}) + |> Map.get(:comment) + + case {comment, embedded_comment} do + {nil, nil} -> changeset + {nil, new_comment} -> + put_change(changeset, comment_field, List.to_string(new_comment)) + {_, _} -> changeset + end + else + {:is_valid, false} -> changeset + _ -> add_error(changeset, value_field, "key is invalid") + end + end + + @doc false + def changeset(key, attrs) do + out = key + |> cast(attrs, [:comment, :value]) + |> validate_required([:value]) + |> process_ssh_key(:value, :fingerprint, :comment) + end +end diff --git a/lib/recycledcloud/accounts/user.ex b/lib/recycledcloud/accounts/user.ex index c9a1653..139508a 100644 --- a/lib/recycledcloud/accounts/user.ex +++ b/lib/recycledcloud/accounts/user.ex @@ -15,6 +15,8 @@ defmodule RecycledCloud.Accounts.User do field :email, :string, virtual: true field :confirmed_at, :naive_datetime + has_many :keys, Accounts.Key + timestamps() end diff --git a/lib/recycledcloud_web/controllers/user_keys_controller.ex b/lib/recycledcloud_web/controllers/user_keys_controller.ex new file mode 100644 index 0000000..ee4742a --- /dev/null +++ b/lib/recycledcloud_web/controllers/user_keys_controller.ex @@ -0,0 +1,53 @@ +defmodule RecycledCloudWeb.UserKeysController do + use RecycledCloudWeb, :controller + + alias RecycledCloud.Accounts + + def index(conn, %{"username" => username}) do + case Accounts.get_user_by_username(username) do + nil -> + conn + |> put_status(:not_found) + |> text("") + + user -> + keys = Accounts.get_keys_for(user) + |> Enum.map(fn k -> k.value end) + |> Enum.join("\n") + + body = "# Keys for #{user.username}\n" <> keys + conn |> text(body) + end + end + + def new(conn, %{"key" => key} = params) do + %{"value" => value, "comment" => comment} = key + user = conn.assigns.current_user + + case Accounts.add_key(user, value, comment) do + {:ok, _} -> + conn + |> put_flash(:info, "Key added successfully.") + |> redirect(to: Routes.user_settings_path(conn, :edit)) + + {:error, _} -> + conn + |> put_flash(:error, "Submitted key is invalid.") + |> redirect(to: Routes.user_settings_path(conn, :edit)) + end + end + + def delete(conn, %{"key_id" => id}) do + user = conn.assigns.current_user + key = Accounts.get_key!(id) + + if key.user_id == user.id do + Accounts.remove_key!(key) + + conn + |> put_flash(:info, "Key removed.") + |> redirect(to: Routes.user_settings_path(conn, :edit)) + end + + end +end diff --git a/lib/recycledcloud_web/controllers/user_settings_controller.ex b/lib/recycledcloud_web/controllers/user_settings_controller.ex index d8aa79b..e4711ba 100644 --- a/lib/recycledcloud_web/controllers/user_settings_controller.ex +++ b/lib/recycledcloud_web/controllers/user_settings_controller.ex @@ -70,5 +70,7 @@ defmodule RecycledCloudWeb.UserSettingsController do conn |> assign(:email_changeset, Accounts.change_user_email(user)) |> assign(:password_changeset, Accounts.change_user_password(user)) + |> assign(:key_changeset, Accounts.change_user_key(%Accounts.Key{})) + |> assign(:keys, Accounts.get_keys_for(user)) end end diff --git a/lib/recycledcloud_web/router.ex b/lib/recycledcloud_web/router.ex index 74b72a6..7b31bba 100644 --- a/lib/recycledcloud_web/router.ex +++ b/lib/recycledcloud_web/router.ex @@ -16,16 +16,21 @@ defmodule RecycledCloudWeb.Router do plug :accepts, ["json"] end + pipeline :plain do + plug :accepts, ["text"] + end + scope "/", RecycledCloudWeb do pipe_through :browser get "/", PageController, :index end - # Other scopes may use custom stacks. - # scope "/api", RecycledCloudWeb do - # pipe_through :api - # end + scope "/", RecycledCloudWeb do + pipe_through :plain + + get "/keys/:username", UserKeysController, :index + end ## Authentication routes @@ -48,6 +53,8 @@ defmodule RecycledCloudWeb.Router do get "/users/settings", UserSettingsController, :edit put "/users/settings", UserSettingsController, :update get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email + post "/users/settings/keys/new", UserKeysController, :new + get "/users/settings/keys/:key_id/delete", UserKeysController, :delete end scope "/", RecycledCloudWeb do diff --git a/lib/recycledcloud_web/templates/user_settings/edit.html.eex b/lib/recycledcloud_web/templates/user_settings/edit.html.eex index 554883a..4bd62b6 100644 --- a/lib/recycledcloud_web/templates/user_settings/edit.html.eex +++ b/lib/recycledcloud_web/templates/user_settings/edit.html.eex @@ -58,3 +58,46 @@ method. <%= submit "Change password" %> <% end %> + +
Oops, something went wrong! Please check the errors below.
+Comment | +Imported on | +Fingerprint | +Actions | +
---|---|---|---|
<%= key.comment %> | +<%= key.inserted_at %> | +<%= key.fingerprint %> | +<%= link "Delete", to: Routes.user_keys_path(@conn, :delete, key.id) %> | +