Commit eb48ef3a authored by Piotr Baj's avatar Piotr Baj 🏡

Validate machine name with allowed characters and max length, mix format

parent a3710686
......@@ -102,11 +102,13 @@ defmodule Webapp.Hypervisors.Bhyve do
try do
case webhook_trigger(token, endpoint, payload) do
{:ok, message} -> {:ok, message}
{:error, error} ->
{:ok, message} ->
{:ok, message}
{:error, error} ->
if String.match?(error, ~r/Virtual machine (.*) doesn't exist/) do
{:ok, error}
else
else
{:error, error}
end
end
......
......@@ -11,6 +11,10 @@ defmodule Webapp.Machines.Machine do
Accounts.Team
}
# Machine name max length
# @TODO: Change to UUID once https://github.com/churchers/vm-bhyve/issues/281 will be solved.
@machine_maxlen 30
schema "machines" do
field(:uuid, Ecto.UUID, autogenerate: true)
field(:name, :string)
......@@ -55,6 +59,7 @@ defmodule Webapp.Machines.Machine do
|> cast(attrs, [:name, :template, :hypervisor_id, :plan_id, :team_id])
|> validate_required([:name, :template, :hypervisor_id, :plan_id, :team_id])
|> unique_constraint(:name, name: :machines_name_team_id_index)
|> validate_name()
|> put_change(:uuid, uuid)
|> unique_constraint(:uuid)
|> assoc_constraint(:hypervisor)
......@@ -75,4 +80,18 @@ defmodule Webapp.Machines.Machine do
machine
|> cast(attrs, [:last_status, :job_id])
end
defp validate_name(changeset) do
team_len =
Ecto.Changeset.get_change(changeset, :team_id)
|> Integer.to_string()
|> String.length()
changeset
# Machine name is prefixed with "TEAMID_"
|> validate_length(:name, max: 32 - team_len - 1)
|> validate_format(:name, ~r/^[a-zA-Z0-9_-]+$/,
message: "Namespace must only contain letters and numbers and _ -"
)
end
end
......@@ -33,7 +33,7 @@ defmodule Webapp.Machines do
def list_machines(preloads \\ [:hypervisor, :plan]) do
Machine
|> order_by(asc: :name)
|> Repo.all
|> Repo.all()
|> Repo.preload(preloads)
end
......
......@@ -14,7 +14,7 @@ defmodule Webapp.Sessions do
"""
def list_sessions(%User{} = user) do
sessions = Repo.preload(user, :sessions).sessions
Enum.filter(sessions, &(DateTime.compare(&1.expires_at,DateTime.utc_now()) == :gt))
Enum.filter(sessions, &(DateTime.compare(&1.expires_at, DateTime.utc_now()) == :gt))
end
@doc """
......
......@@ -24,11 +24,12 @@ defmodule WebappWeb.AdminChannel do
{:reply, {:ok, payload}, socket}
end
def handle_in("status", payload, socket) do
machines = String.split(socket.assigns[:machines], ",")
machines =
String.split(socket.assigns[:machines], ",")
|> Enum.map(fn id ->
machine = Machines.get_machine!(id)
%{
id: machine.id,
status_css: map_status_to_css(machine.last_status),
......
......@@ -26,7 +26,9 @@ defmodule WebappWeb.SessionController do
{:ok, user} ->
conn
|> add_session(user, params)
|> redirect(to: get_session(conn, :request_path) || team_path(:machine_path, conn, :index))
|> redirect(
to: get_session(conn, :request_path) || team_path(:machine_path, conn, :index)
)
{:error, message} ->
conn
......
......@@ -136,13 +136,19 @@ defmodule WebappWeb.MachineController do
# Errors from changeset should be displayed!
{:error, %Ecto.Changeset{} = changeset} ->
conn
|> put_flash(:error, "Couldn't connect machine #{machine.name} to network #{network.name}")
|> put_flash(
:error,
"Couldn't connect machine #{machine.name} to network #{network.name}"
)
|> redirect(to: team_path(:machine_path, conn, :show, machine))
# Errors from changeset should be displayed!
{:error, :machine, %Ecto.Changeset{} = changeset, _} ->
conn
|> put_flash(:error, "Couldn't connect machine #{machine.name} to network #{network.name}")
|> put_flash(
:error,
"Couldn't connect machine #{machine.name} to network #{network.name}"
)
|> redirect(to: team_path(:machine_path, conn, :show, machine))
{:error, :hypervisor, error, _} ->
......
......@@ -2,7 +2,6 @@ defmodule WebappWeb.Admin.MachineView do
use WebappWeb, :view
import Webapp.Machines, only: [machine_can_do?: 2]
import WebappWeb.MachineView, only: [map_status_to_css: 1, status_icon: 1]
def extract_ids(machines) do
Enum.map(machines, fn machine ->
......
......@@ -5,6 +5,5 @@ defmodule Webapp.Repo.Migrations.AddPriceToPlan do
alter table(:plans) do
add(:price, :integer)
end
end
end
......@@ -10,13 +10,14 @@ defmodule Webapp.AccountsTeamTest do
@update_team %{name: "some updated name"}
def team_fixture(attrs \\ %{}) do
{:ok, fixtures} = Accounts.register_user(%{
user_email: "fred@example.com",
user_password: "reallyHard2gue$$",
user_name: "fred",
team_name: "company",
team_namespace: "company"
})
{:ok, fixtures} =
Accounts.register_user(%{
user_email: "fred@example.com",
user_password: "reallyHard2gue$$",
user_name: "fred",
team_name: "company",
team_namespace: "company"
})
# {:ok, team} =
# attrs
......@@ -26,41 +27,41 @@ defmodule Webapp.AccountsTeamTest do
fixtures
end
test "list_teams/0 returns all teams" do
%{team: team} = team_fixture()
assert Accounts.list_teams() == [team]
end
test "get_team!/1 returns the team with given id" do
test "list_teams/0 returns all teams" do
%{team: team} = team_fixture()
assert Accounts.get_team!(team.id) == team
end
test "create_team/1 with valid data creates a team" do
assert Accounts.list_teams() == [team]
end
test "get_team!/1 returns the team with given id" do
%{team: team} = team_fixture()
assert Accounts.get_team!(team.id) == team
end
test "create_team/1 with valid data creates a team" do
%{team: team, user: user} = team_fixture()
team_params =
Map.merge(@valid_team, %{members: [%{user_id: user.id, role: "Administrator"}]})
assert {:ok, %Team{} = team} = Accounts.create_team(team_params)
assert team.name == "some name"
end
test "create_team/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Accounts.create_team(@invalid_team)
end
test "update_team/2 with valid data updates the team" do
team_params = Map.merge(@valid_team, %{members: [%{user_id: user.id, role: "Administrator"}]})
assert {:ok, %Team{} = team} = Accounts.create_team(team_params)
assert team.name == "some name"
end
test "create_team/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Accounts.create_team(@invalid_team)
end
test "update_team/2 with valid data updates the team" do
%{team: team, user: user} = team_fixture()
assert {:ok, %Team{} = team} = Accounts.update_team(team, @update_team)
assert team.name == "some updated name"
end
test "update_team/2 with invalid data returns error changeset" do
%{team: team, user: user} = team_fixture()
assert {:ok, %Team{} = team} = Accounts.update_team(team, @update_team)
assert team.name == "some updated name"
end
test "update_team/2 with invalid data returns error changeset" do
%{team: team, user: user} = team_fixture()
assert {:error, %Ecto.Changeset{}} = Accounts.update_team(team, @invalid_team)
assert team == Accounts.get_team!(team.id)
end
assert {:error, %Ecto.Changeset{}} = Accounts.update_team(team, @invalid_team)
assert team == Accounts.get_team!(team.id)
end
#
# test "delete_team/1 deletes the team" do
# team = team_fixture()
......@@ -68,8 +69,8 @@ defmodule Webapp.AccountsTeamTest do
# assert_raise Ecto.NoResultsError, fn -> Accounts.get_team!(team.id) end
# end
#
test "change_team/1 returns a team changeset" do
%{team: team, user: user} = team_fixture()
assert %Ecto.Changeset{} = Accounts.change_team(team)
end
test "change_team/1 returns a team changeset" do
%{team: team, user: user} = team_fixture()
assert %Ecto.Changeset{} = Accounts.change_team(team)
end
end
......@@ -8,7 +8,6 @@ defmodule WebappWeb.MachineControllerTest do
@update_attrs %{name: "some updated name", template: "some updated template"}
@invalid_attrs %{name: nil, template: nil}
# setup do
# conn = build_conn() |> bypass_through(WebappWeb.Router, [:browser]) |> get("/")
......
......@@ -2,15 +2,17 @@ defmodule WebappWeb.TeamContextTest do
use WebappWeb.ConnCase
import WebappWeb.AuthCase
alias Webapp.{
Accounts
}
setup %{conn: conn} do
# prepare connection
conn = conn
|> bypass_through(WebappWeb.Router, [:browser_auth])
|> get("/")
conn =
conn
|> bypass_through(WebappWeb.Router, [:browser_auth])
|> get("/")
# create user
user = add_user_confirmed("user@example.com")
......@@ -18,34 +20,39 @@ defmodule WebappWeb.TeamContextTest do
another_user = add_user_confirmed("another_user@example.com")
# create team
{:ok, team} = Accounts.create_team(%{
name: "some name",
namespace: "user-company",
members: [%{"user_id" => user.id, "role" => "Administrator"}]
})
{:ok, another_team} = Accounts.create_team(%{
name: "other name",
namespace: "other-company",
members: [%{"user_id" => another_user.id, "role" => "Administrator"}]
})
{:ok, team} =
Accounts.create_team(%{
name: "some name",
namespace: "user-company",
members: [%{"user_id" => user.id, "role" => "Administrator"}]
})
{:ok, another_team} =
Accounts.create_team(%{
name: "other name",
namespace: "other-company",
members: [%{"user_id" => another_user.id, "role" => "Administrator"}]
})
conn = conn |> add_session(user) |> send_resp(:ok, "/")
{:ok, %{conn: conn, team: team, user: user}}
end
test "first user team is assigned as current_team if namespace is not provided in url", %{conn: conn, user: user} do
test "first user team is assigned as current_team if namespace is not provided in url", %{
conn: conn,
user: user
} do
conn = get(conn, "/")
team = Accounts.user_first_team(user)
assert conn.assigns[:current_team] == team
end
test "current_team is taken form url", %{conn: conn, user: user, team: team} do
test "current_team is taken form url", %{conn: conn, user: user, team: team} do
conn = get(conn, "/user-company/machines")
# current_team is loaded without members
team = Accounts.get_team!(team.id)
assert conn.assigns[:current_team] == team
assert conn.assigns[:current_team] == team
end
test "user can not access other teams", %{conn: conn, user: user, team: team} do
......
......@@ -2,15 +2,17 @@ defmodule WebappWeb.TeamPrefixTest do
use WebappWeb.ConnCase
import WebappWeb.AuthCase
alias Webapp.{
Accounts
}
setup %{conn: conn} do
# prepare connection
conn = conn
|> bypass_through(WebappWeb.Router, [:browser_auth])
|> get("/")
conn =
conn
|> bypass_through(WebappWeb.Router, [:browser_auth])
|> get("/")
# create user
user = add_user_confirmed("user@example.com")
......@@ -18,43 +20,47 @@ defmodule WebappWeb.TeamPrefixTest do
another_user = add_user_confirmed("another_user@example.com")
# create team
{:ok, team} = Accounts.create_team(%{
name: "some name",
namespace: "user-company",
members: [%{"user_id" => user.id, "role" => "Administrator"}]
})
{:ok, another_team} = Accounts.create_team(%{
name: "other name",
namespace: "other-company",
members: [%{"user_id" => another_user.id, "role" => "Administrator"}]
})
{:ok, team} =
Accounts.create_team(%{
name: "some name",
namespace: "user-company",
members: [%{"user_id" => user.id, "role" => "Administrator"}]
})
{:ok, another_team} =
Accounts.create_team(%{
name: "other name",
namespace: "other-company",
members: [%{"user_id" => another_user.id, "role" => "Administrator"}]
})
conn = conn |> add_session(user) |> send_resp(:ok, "/")
{:ok, %{conn: conn, team: team, user: user}}
end
test "assigns user team as current_team form url", %{conn: conn, user: user, team: team} do
test "assigns user team as current_team form url", %{conn: conn, user: user, team: team} do
conn = get(conn, "/user-company/machines")
# current_team is loaded without members
team = Accounts.get_team!(team.id)
assert conn.assigns[:current_team] == team
assert conn.assigns[:current_team] == team
end
test "team namespace is removed from path_info", %{conn: conn} do
test "team namespace is removed from path_info", %{conn: conn} do
conn = get(conn, "/user-company/machines")
assert conn.path_info == ["machines"]
end
test "restricted namespaces are not removed from path_info", %{conn: conn} do
test "restricted namespaces are not removed from path_info", %{conn: conn} do
conn = get(conn, "/health")
assert conn.path_info == ["health"]
end
test "responses with not found if team not exists", %{conn: conn} do
assert_raise Phoenix.Router.NoRouteError, ~r/no route found for GET \/non-existing-company\/machines/, fn ->
get(conn, "/non-existing-company/machines")
end
assert_raise Phoenix.Router.NoRouteError,
~r/no route found for GET \/non-existing-company\/machines/,
fn ->
get(conn, "/non-existing-company/machines")
end
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment