Add minimal SSH key management
This commit is contained in:
parent
061a748c2a
commit
c2faafe3a3
9 changed files with 208 additions and 5 deletions
|
@ -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
|
||||
|
|
48
lib/recycledcloud/accounts/key.ex
Normal file
48
lib/recycledcloud/accounts/key.ex
Normal file
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
53
lib/recycledcloud_web/controllers/user_keys_controller.ex
Normal file
53
lib/recycledcloud_web/controllers/user_keys_controller.ex
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -58,3 +58,46 @@ method.
|
|||
<%= submit "Change password" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<h3>SSH Keys</h3>
|
||||
|
||||
<%= form_for @key_changeset, Routes.user_keys_path(@conn, :new), fn f -> %>
|
||||
<%= if @key_changeset.action do %>
|
||||
<div class="alert alert-danger">
|
||||
<p>Oops, something went wrong! Please check the errors below.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= text_input f, :comment, placeholder: "comment" %>
|
||||
<%= error_tag f, :comment %>
|
||||
|
||||
<br />
|
||||
|
||||
<%= textarea f, :value, required: true, placeholder: "value" %>
|
||||
<%= error_tag f, :value %>
|
||||
|
||||
<div>
|
||||
<%= submit "Add key" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Comment</th>
|
||||
<th>Imported on</th>
|
||||
<th>Fingerprint</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for key <- @keys do %>
|
||||
<tr>
|
||||
<td><%= key.comment %></td>
|
||||
<td><%= key.inserted_at %></td>
|
||||
<td><%= key.fingerprint %></td>
|
||||
<td><%= link "Delete", to: Routes.user_keys_path(@conn, :delete, key.id) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
15
priv/repo/migrations/20201222142443_create_keys.exs
Normal file
15
priv/repo/migrations/20201222142443_create_keys.exs
Normal file
|
@ -0,0 +1,15 @@
|
|||
defmodule RecycledCloud.Repo.Migrations.CreateKeys do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:keys) do
|
||||
add :comment, :string
|
||||
add :value, :text
|
||||
add :user_id, references(:users, on_delete: :nothing)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create index(:keys, [:user_id])
|
||||
end
|
||||
end
|
10
priv/repo/migrations/20201223114606_add_key_fingerprint.exs
Normal file
10
priv/repo/migrations/20201223114606_add_key_fingerprint.exs
Normal file
|
@ -0,0 +1,10 @@
|
|||
defmodule RecycledCloud.Repo.Migrations.AddKeyFingerprint do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:keys) do
|
||||
add :fingerprint, :string
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue