Commit 4ef916c651d5e73c6a94556cf0ebe8636986896f

Thomas de Grivel 2021-11-24T21:08:00

ssh keys

diff --git a/config/prod.exs b/config/prod.exs
index fb0f61d..f62d6ec 100644
--- a/config/prod.exs
+++ b/config/prod.exs
@@ -10,7 +10,8 @@ import Config
 # which you should run after static files are built and
 # before starting your production server.
 config :kmxgit, KmxgitWeb.Endpoint,
-  url: [host: "example.com", port: 80],
+  http: [ip: {127, 0, 0, 1}, port: 15008],
+  url: [host: "git.kmx.io", port: 80],
   cache_static_manifest: "priv/static/cache_manifest.json"
 
 # Do not print debug messages in production
diff --git a/lib/kmxgit/git_manager.ex b/lib/kmxgit/git_manager.ex
index 41e55b3..9cef533 100644
--- a/lib/kmxgit/git_manager.ex
+++ b/lib/kmxgit/git_manager.ex
@@ -87,14 +87,34 @@ defmodule Kmxgit.GitManager do
     end
   end
 
+  def rename(from, to) do
+    dir_from = git_dir(from)
+    dir_to = git_dir(to)
+    dir = Path.dirname(dir_to)
+    :ok = File.mkdir_p(dir)
+    {out, status} = System.cmd("mv", [dir_from, dir_to], stderr_to_stdout: true)
+    case status do
+      0 -> {:ok, out}
+      _ -> {:error, out}
+    end
+  end
+
   def create(repo) do
     dir = "#{@git_root}/#{Path.dirname(repo)}"
     name = "#{Path.basename(repo)}.git"
     :ok = File.mkdir_p(dir)
-    {out, status} = System.cmd("git", ["-C", dir, "init", "--bare", name])
+    {out, status} = System.cmd("git", ["-C", dir, "init", "--bare", name], stderr_to_stdout: true)
     case status do
       0 -> {:ok, out}
       _ -> {:error, out}
     end
   end
+
+  def delete(repo) do
+    dir = git_dir(repo)
+    case System.cmd("rm", ["-rf", dir], stderr_to_stdout: true) do
+      {"", 0} -> :ok
+      {err, _} -> {:error, err}
+    end
+  end
 end
diff --git a/lib/kmxgit/repository_manager/repository.ex b/lib/kmxgit/repository_manager/repository.ex
index a065901..e4b85a5 100644
--- a/lib/kmxgit/repository_manager/repository.ex
+++ b/lib/kmxgit/repository_manager/repository.ex
@@ -11,7 +11,7 @@ defmodule Kmxgit.RepositoryManager.Repository do
     belongs_to :organisation, Organisation
     field :slug, :string, unique: true
     belongs_to :user, User
-    many_to_many :members, User, join_through: "users_repositories", on_replace: :delete
+    many_to_many :members, User, join_through: "users_repositories", on_replace: :delete, on_delete: :delete_all
     timestamps()
   end
 
diff --git a/lib/kmxgit/user_manager/user.ex b/lib/kmxgit/user_manager/user.ex
index 69683d1..1fdff72 100644
--- a/lib/kmxgit/user_manager/user.ex
+++ b/lib/kmxgit/user_manager/user.ex
@@ -88,4 +88,17 @@ defmodule Kmxgit.UserManager.User do
   def display_name(user) do
     user.name || user.login
   end
+
+  def ssh_keys_with_env(user) do
+    (user.ssh_keys || "")
+    |> String.split("\n")
+    |> Enum.map(fn line ->
+      if Regex.match?(~r/^[ \t]*ssh-/, line) do
+        "environment=\"GIT_AUTH_ID=#{user.slug.slug}\" #{line}"
+      else
+        line
+      end
+    end)
+    |> Enum.join("\n")
+  end
 end
diff --git a/lib/kmxgit_web/controllers/admin/repository_controller.ex b/lib/kmxgit_web/controllers/admin/repository_controller.ex
index d621455..01e2d32 100644
--- a/lib/kmxgit_web/controllers/admin/repository_controller.ex
+++ b/lib/kmxgit_web/controllers/admin/repository_controller.ex
@@ -1,6 +1,7 @@
 defmodule KmxgitWeb.Admin.RepositoryController do
   use KmxgitWeb, :controller
 
+  alias Kmxgit.GitManager
   alias Kmxgit.RepositoryManager
   alias Kmxgit.RepositoryManager.Repository
   alias Kmxgit.Repo
@@ -92,10 +93,21 @@ defmodule KmxgitWeb.Admin.RepositoryController do
 
   def update(conn, params) do
     repo = RepositoryManager.get_repository!(params["id"])
-    case RepositoryManager.update_repository(repo, params["repository"]) do
-      {:ok, repo} ->
+    case Repo.transaction(fn ->
+          case RepositoryManager.update_repository(repo, params["repository"]) do
+            {:ok, repo1} ->
+              s = Repository.full_slug(repo)
+              s1 = Repository.full_slug(repo1)
+              if s != s1 do
+                GitManager.rename(s, s1)
+              end
+              repo1
+            {:error, changeset} -> Repo.rollback changeset
+          end
+        end) do
+      {:ok, repo1} ->
         conn
-        |> redirect(to: Routes.admin_repository_path(conn, :show, repo))
+        |> redirect(to: Routes.admin_repository_path(conn, :show, repo1))
       {:error, changeset} ->
         conn
         |> assign(:action, Routes.admin_repository__path(conn, :update, repo))
@@ -151,11 +163,24 @@ defmodule KmxgitWeb.Admin.RepositoryController do
   end
 
   def delete(conn, params) do
-    repository = RepositoryManager.get_repository(params["id"])
-    if repository do
-      {:ok, _} = RepositoryManager.delete_repository(repository)
-      conn
-      |> redirect(to: Routes.admin_repository_path(conn, :index))
+    repo = RepositoryManager.get_repository(params["id"])
+    if repo do
+      case Repo.transaction(fn ->
+            case RepositoryManager.delete_repository(repo) do
+              {:ok, _} -> GitManager.delete(Repository.full_slug(repo))
+              {:error, changeset} -> Repo.rollback(changeset)
+            end
+          end) do
+        {:ok, _} ->
+          conn
+          |> redirect(to: Routes.admin_repository_path(conn, :index))
+        {:error, changeset} ->
+          conn
+          |> assign(:action, Routes.admin_repository_path(conn, :update, repo))
+          |> assign(:changeset, changeset)
+          |> assign(:repo, repo)
+          |> render("edit.html")
+      end
     else
       not_found(conn)
     end
diff --git a/lib/kmxgit_web/controllers/page_controller.ex b/lib/kmxgit_web/controllers/page_controller.ex
index 18e791f..a338d4c 100644
--- a/lib/kmxgit_web/controllers/page_controller.ex
+++ b/lib/kmxgit_web/controllers/page_controller.ex
@@ -47,4 +47,13 @@ defmodule KmxgitWeb.PageController do
       redirect(conn, to: "/")
     end
   end
+
+  def keys(conn, params) do
+    k = UserManager.list_users
+    |> Enum.map(fn user -> User.ssh_keys_with_env(user) end)
+    |> Enum.join("\n")
+    conn
+    |> put_resp_content_type("text/text")
+    |> resp(200, k)
+  end
 end
diff --git a/lib/kmxgit_web/controllers/repository_controller.ex b/lib/kmxgit_web/controllers/repository_controller.ex
index a8473c3..e7d73a5 100644
--- a/lib/kmxgit_web/controllers/repository_controller.ex
+++ b/lib/kmxgit_web/controllers/repository_controller.ex
@@ -112,7 +112,7 @@ defmodule KmxgitWeb.RepositoryController do
     |> Enum.map(&Enum.reverse/1)
   end
   def chunk_path([first | rest], acc = [acc_first | acc_rest]) do
-    if Regex.match?(~r/_/, first) do
+    if Regex.match?(~r/^_/, first) do
       chunk_path(rest, [[first] | acc])
     else
       chunk_path(rest, [[first | acc_first] | acc_rest])
@@ -262,7 +262,18 @@ defmodule KmxgitWeb.RepositoryController do
     if repo do
       org = repo.organisation
       if org && Enum.find(org.users, &(&1.id == current_user.id)) || repo.user_id == current_user.id do
-        case RepositoryManager.update_repository(repo, params["repository"]) do
+        case Repo.transaction(fn ->
+              case RepositoryManager.update_repository(repo, params["repository"]) do
+                {:ok, repo1} ->
+                  s = Repository.full_slug(repo)
+                  s1 = Repository.full_slug(repo1)
+                  if s != s1 do
+                    GitManager.rename(s, s1)
+                  end
+                  repo1
+                {:error, changeset} -> Repo.rollback(changeset)
+              end
+            end) do
           {:ok, repo} ->
             conn
             |> redirect(to: Routes.repository_path(conn, :show, params["owner"], Repository.splat(repo)))
@@ -382,9 +393,19 @@ defmodule KmxgitWeb.RepositoryController do
     if repo do
       org = repo.organisation
       if org && Enum.find(org.users, &(&1.id == current_user.id)) || repo.user_id == current_user.id do
-        {:ok, _} = RepositoryManager.delete_repository(repo)
-        conn
-        |> redirect(to: Routes.slug_path(conn, :show, params["owner"]))
+        case Repo.transaction(fn ->
+              case RepositoryManager.delete_repository(repo) do
+                {:ok, _} -> :ok
+                {:error, changeset} -> Repo.rollback changeset
+              end
+            end) do
+          {:ok, _} ->
+            conn
+            |> redirect(to: Routes.slug_path(conn, :show, params["owner"]))
+          {:error, changeset} ->
+            conn
+            |> redirect(to: Routes.slug_path(conn, :edit, params["owner"]))
+        end
       else
         not_found(conn)
       end
diff --git a/lib/kmxgit_web/router.ex b/lib/kmxgit_web/router.ex
index 67243cb..133fc0f 100644
--- a/lib/kmxgit_web/router.ex
+++ b/lib/kmxgit_web/router.ex
@@ -33,7 +33,8 @@ defmodule KmxgitWeb.Router do
   scope "/", KmxgitWeb do
     pipe_through [:browser, :auth]
 
-    get "/",            PageController, :index
+    get  "/",           PageController, :index
+    get  "/_keys",      PageController, :keys
     get  "/_new_admin", PageController, :new_admin
     post "/_new_admin", PageController, :new_admin_post
 
diff --git a/lib/kmxgit_web/templates/admin/user/form.html.heex b/lib/kmxgit_web/templates/admin/user/form.html.heex
index eec8af5..9687c73 100644
--- a/lib/kmxgit_web/templates/admin/user/form.html.heex
+++ b/lib/kmxgit_web/templates/admin/user/form.html.heex
@@ -6,6 +6,12 @@
     <%= error_tag f, :name %>
   </div>
 
+  <div class="mb-3">
+    <%= label f, :email, class: "form-label" %>
+    <%= text_input f, :email, class: "form-control" %>
+    <%= error_tag f, :email %>
+  </div>
+
   <%= inputs_for f, :slug, fn ff -> %>
     <div class="mb-3">
       <%= label ff, :slug, gettext("Login"), class: "form-label" %>
@@ -15,9 +21,15 @@
   <% end %>
 
   <div class="mb-3">
-    <%= label f, :email, class: "form-label" %>
-    <%= text_input f, :email, class: "form-control" %>
-    <%= error_tag f, :email %>
+    <%= label f, :description, class: "form-label" %>
+    <%= textarea f, :description, class: "form-control" %>
+    <%= error_tag f, :description %>
+  </div>
+
+  <div class="mb-3">
+    <%= label f, :ssh_keys, gettext("SSH keys"), class: "form-label" %>
+    <%= textarea f, :ssh_keys, class: "form-control" %>
+    <%= error_tag f, :ssh_keys %>
   </div>
 
   <div class="mb-3">
diff --git a/lib/kmxgit_web/templates/admin/user/show.html.heex b/lib/kmxgit_web/templates/admin/user/show.html.heex
index f056547..8f81b6c 100644
--- a/lib/kmxgit_web/templates/admin/user/show.html.heex
+++ b/lib/kmxgit_web/templates/admin/user/show.html.heex
@@ -44,6 +44,14 @@
       <th><%= gettext "Deploy only" %></th>
       <td><%= @user.deploy_only %></td>
     </tr>
+    <tr>
+      <th><%= gettext "SSH keys" %></th>
+      <td class="scroll-x">
+        <pre class="ssh_keys">
+          <%= @user.ssh_keys %>
+        </pre>
+      </td>
+    </tr>
   </table>
 
   <%= link gettext("Delete user"),
diff --git a/lib/kmxgit_web/templates/organisation/show.html.heex b/lib/kmxgit_web/templates/organisation/show.html.heex
index 6288719..a70a157 100644
--- a/lib/kmxgit_web/templates/organisation/show.html.heex
+++ b/lib/kmxgit_web/templates/organisation/show.html.heex
@@ -18,9 +18,13 @@
     <div class="col col-12 col-md-7">
       <hr/>
       <h2><%= gettext "Repositories" %></h2>
-      <%= for repo <- @org.owned_repositories do %>
-        <%= link Repository.full_slug(repo), to: Routes.repository_path(@conn, :show, @org.slug.slug, Repository.splat(repo)) %>
-      <% end %>
+      <ul>
+        <%= for repo <- @org.owned_repositories do %>
+          <li>
+            <%= link Repository.full_slug(repo), to: Routes.repository_path(@conn, :show, @org.slug.slug, Repository.splat(repo)) %>
+          </li>
+        <% end %>
+      </ul>  
     </div>
     <div class="col col-12 col-md-4">
       <hr/>
diff --git a/lib/kmxgit_web/templates/repository/show.html.heex b/lib/kmxgit_web/templates/repository/show.html.heex
index 95854a5..c719d25 100644
--- a/lib/kmxgit_web/templates/repository/show.html.heex
+++ b/lib/kmxgit_web/templates/repository/show.html.heex
@@ -32,6 +32,8 @@
                   <%= link file.name, to: file.url %>
                 <% "tree" -> %>
                   <%= link "#{file.name}/", to: file.url %>
+                <% _ -> %>
+                  <%= "#{file.type} #{file.name}" %>
               <% end %>
             </li>
           <% end %>
diff --git a/lib/kmxgit_web/templates/user/show.html.heex b/lib/kmxgit_web/templates/user/show.html.heex
index 0f92445..bdc8ba7 100644
--- a/lib/kmxgit_web/templates/user/show.html.heex
+++ b/lib/kmxgit_web/templates/user/show.html.heex
@@ -17,9 +17,13 @@
     <div class="col col-12 col-md-7">
       <hr/>
       <h2><%= gettext "Repositories" %></h2>
-      <%= for repo <- @user.owned_repositories do %>
-        <%= link Repository.full_slug(repo), to: Routes.repository_path(@conn, :show, @user.slug.slug, Repository.splat(repo)) %>
-      <% end %>
+      <ul>
+        <%= for repo <- @user.owned_repositories do %>
+          <li>
+            <%= link Repository.full_slug(repo), to: Routes.repository_path(@conn, :show, @user.slug.slug, Repository.splat(repo)) %>
+          </li>
+        <% end %>
+      </ul>
     </div>
     <div class="col col-12 col-md-4">
       <hr/>