From 0d4dfd5bb2a62ed2e783278bea9e6da470d68d26 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 17 Jul 2020 13:52:46 +0100 Subject: [PATCH 001/166] improve clarity in create_apikey in seeds.exs --- priv/repo/seeds.exs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index be260fc2..a9e0174b 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -39,15 +39,15 @@ defmodule Auth.Seeds do "name" => "system admin key", "description" => "Created by /priv/repo/seeds.exs during setup.", # the default host in %Plug.Conn - "url" => "www.example.com" + "url" => "localhost:4000" } |> AuthWeb.ApikeyController.make_apikey(person.id) |> Auth.Apikey.create_apikey() api_key = key.client_id <> "/" <> key.client_secret # Set the AUTH_API_KEY to a valid value that is in the DB: - System.put_env("AUTH_API_KEY", api_key) - IO.inspect(System.get_env("AUTH_API_KEY"), label: "AUTH_API_KEY") + IO.puts("Remember to set the AUTH_API_KEY environment variable:") + IO.puts("export AUTH_API_KEY=#{api_key}") IO.puts("- - - - - - - - - - - - - - - - - - - - - - ") key end From a351bfc9a5c7112fd4e7ac6ef2b77e495de50e90 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 17 Jul 2020 14:12:43 +0100 Subject: [PATCH 002/166] remember to export the AUTH_API_KEY in seeds.exs so its present during test lifecyle #54 --- priv/repo/seeds.exs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index a9e0174b..27bbee06 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -45,6 +45,8 @@ defmodule Auth.Seeds do |> Auth.Apikey.create_apikey() api_key = key.client_id <> "/" <> key.client_secret + # set the AUTH_API_KEY during test run: + System.put_env("AUTH_API_KEY", api_key) # Set the AUTH_API_KEY to a valid value that is in the DB: IO.puts("Remember to set the AUTH_API_KEY environment variable:") IO.puts("export AUTH_API_KEY=#{api_key}") From c2f3d19aaa0e748548a1585ecc7a9dee7c847f1c Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 17 Jul 2020 14:13:51 +0100 Subject: [PATCH 003/166] if an apikey belongs to SuperAdmin dont check the URL as its the System Key --- lib/auth_web/controllers/auth_controller.ex | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/auth_web/controllers/auth_controller.ex b/lib/auth_web/controllers/auth_controller.ex index e0aa83fc..3c5b3248 100644 --- a/lib/auth_web/controllers/auth_controller.ex +++ b/lib/auth_web/controllers/auth_controller.ex @@ -417,7 +417,6 @@ defmodule AuthWeb.AuthController do def get_client_secret(client_id, state) do person_id = AuthWeb.ApikeyController.decode_decrypt(client_id) - # decode_decrypt fails with state 0 if person_id == 0 do 0 @@ -425,7 +424,13 @@ defmodule AuthWeb.AuthController do apikeys = Auth.Apikey.list_apikeys_for_person(person_id) Enum.filter(apikeys, fn k -> - k.client_id == client_id and state =~ k.url + # if the API Key belongs to Super Admin, don't check URL as it's the "setup key": + if person_id == 1 do + k.client_id == client_id + else + # check url matches the state for all other keys: + k.client_id == client_id and state =~ k.url + end end) |> List.first() |> Map.get(:client_secret) From 0498e98f296cc9fc8e052acd9d0ca92ad64c8873 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 24 Jul 2020 15:25:07 +0100 Subject: [PATCH 004/166] add test for get_client_secret/2 --- .../controllers/auth_controller_test.exs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/auth_web/controllers/auth_controller_test.exs b/test/auth_web/controllers/auth_controller_test.exs index 1925d4d5..c3de1577 100644 --- a/test/auth_web/controllers/auth_controller_test.exs +++ b/test/auth_web/controllers/auth_controller_test.exs @@ -49,6 +49,23 @@ defmodule AuthWeb.AuthControllerTest do assert conn.resp_body =~ "state=http://localhost/admin" end + test "get_client_secret(client_id, state) gets the secret for the given client_id" do + + person = Auth.Person.create_person(%{ + email: "alex@gmail.com", + auth_provider: "email" + }) + + {:ok, key} = %{"name" => "test key", "url" => "example.com"} + |> AuthWeb.ApikeyController.make_apikey(person.id) + |> Auth.Apikey.create_apikey() + + state = "https://www.example.com/profile?auth_client_id=#{key.client_id}" + secret = AuthWeb.AuthController.get_client_secret(key.client_id, state) + + assert secret == key.client_secret + end + test "github_handler/2 github auth callback", %{conn: conn} do baseurl = AuthPlug.Helpers.get_baseurl_from_conn(conn) From 9cf0ef5628c1a32fa39e2d91c796e2856a03ee51 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 18 Jul 2020 11:34:51 +0100 Subject: [PATCH 005/166] reduce noise in tests by checking for Mix.env() == :test --- priv/repo/seeds.exs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 27bbee06..d2c05421 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -26,10 +26,12 @@ defmodule Auth.Seeds do person -> person end - - IO.inspect(person.id, label: "seeds.exs person.id") - IO.puts("- - - - - - - - - - - - - - - - - - - - - - ") - + if(Mix.env() == :test) do + # don't print noise during tests + else + IO.inspect(person.id, label: "seeds.exs person.id") + IO.puts("- - - - - - - - - - - - - - - - - - - - - - ") + end person end @@ -47,10 +49,14 @@ defmodule Auth.Seeds do api_key = key.client_id <> "/" <> key.client_secret # set the AUTH_API_KEY during test run: System.put_env("AUTH_API_KEY", api_key) - # Set the AUTH_API_KEY to a valid value that is in the DB: - IO.puts("Remember to set the AUTH_API_KEY environment variable:") - IO.puts("export AUTH_API_KEY=#{api_key}") - IO.puts("- - - - - - - - - - - - - - - - - - - - - - ") + + if(Mix.env() == :test) do + # don't print noise during tests + else + IO.puts("Remember to set the AUTH_API_KEY environment variable:") + IO.puts("export AUTH_API_KEY=#{api_key}") + IO.puts("- - - - - - - - - - - - - - - - - - - - - - ") + end key end end From 090aa6673bd91b7c921ef8850adadbe0c35010e2 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 22 Jul 2020 16:36:58 +0100 Subject: [PATCH 006/166] add notes on RBAC #81 --- role-based-access-control.md | 83 ++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 role-based-access-control.md diff --git a/role-based-access-control.md b/role-based-access-control.md new file mode 100644 index 00000000..26014365 --- /dev/null +++ b/role-based-access-control.md @@ -0,0 +1,83 @@ +# Role Based Access Control (RBAC) + +_Understand_ the fundamentals of Role Based Access Control (RBAC) + +## Why? + +RBAC lets you easily manage roles and permissions in any application +and see at a glance exactly permissions a person has in the system. +It reduces complexity over traditional +Access Control List (ACL) based permissions systems. + + +## What? + +The purpose of RBAC is to provide a framework +for application administrators and developers +to manage the permissions assigned to the people using the App(s). + +Each role granted just enough flexibility and permissions +to perform the tasks required for their job, +this helps enforce the +[principal of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) + +The RBAC methodology is based on a set of three principal rules +that govern access to systems: + +1. **Role Assignment**: +Each transaction or operation can only be carried out +if the person has assumed the appropriate role. +An operation is defined as any action taken +with respect to a system or network object that is protected by RBAC. +Roles may be assigned by a separate party +or selected by the person attempting to perform the action. + +2. **Role Authorization**: +The purpose of role authorization +is to ensure that people can only assume a role +for which they have been given the appropriate authorization. +When a person assumes a role, +they must do so with authorization from an administrator. + +3. **Transaction Authorization**: +An operation can only be completed +if the person attempting to complete the transaction +possesses the appropriate role. + + +## Who? + +Anyone who is interested in developing secure multi-user applications +should learn about RBAC. + + +## _How_? + +Let's create the Database Schemas (Tables) to store our RBAC data, +starting with **`Roles`**: + +``` +mix phx.gen.html Ctx Role roles name:string desc:string person_id:references:people +``` + + +``` +mix phx.gen.html Ctx Permission permissions name:string desc:string person_id:references:people +``` + +Next create the **`many-to-many`** relationship between roles and permissions. + +``` +mix ecto.gen.migration create_role_permissions +``` + +Now create the **`many-to-many`** relationship between people and roles. + + + + +## Recommended Reading + ++ https://en.wikipedia.org/wiki/Role-based_access_control ++ https://www.sumologic.com/glossary/role-based-access-control ++ https://medium.com/@adriennedomingus/role-based-access-control-rbac-permissions-vs-roles-55f1f0051468 From 46a2813da840bf7435f9497b3d74db08d2c69bd9 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 22 Jul 2020 18:35:52 +0100 Subject: [PATCH 007/166] add ping to mix.exs see: https://github.com/dwyl/ping --- mix.exs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mix.exs b/mix.exs index f7be88e4..8ca755aa 100644 --- a/mix.exs +++ b/mix.exs @@ -69,6 +69,9 @@ defmodule Auth.Mixfile do # Base58 Encodeing: https://github.com/dwyl/base58 {:B58, "~> 1.0", hex: :b58}, + # Ping to Wake Heroku Instance: https://github.com/dwyl/ping + {:ping, "~> 1.0.1"}, + # Check test coverage {:excoveralls, "~> 0.12.3", only: :test}, From 46c45c7966080440225a103e39fdf5e5c429e55f Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 22 Jul 2020 18:41:36 +0100 Subject: [PATCH 008/166] add RBAC schema instructions [WiP] #27 / #31 --- README.md | 3 +-- role-based-access-control.md | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c0b83831..555b2d9c 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,6 @@ And for sending emails you will need the `SECRET_KEY_BASE` and `EMAIL_APP_URL` defined. - ### 4. Create and migrate your database: > Ensure that PostgreSQL is running @@ -190,7 +189,7 @@ mix ecto.setup mix phoenix.server ``` -> It may take a couple of minutes to compile the app the first time. ⏳ +> It may take a minute to compile the app the first time. ⏳ Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. diff --git a/role-based-access-control.md b/role-based-access-control.md index 26014365..66ceb558 100644 --- a/role-based-access-control.md +++ b/role-based-access-control.md @@ -53,6 +53,14 @@ should learn about RBAC. ## _How_? +Before creating any roles, +you will need to have a baseline schema including people +as people will be referenced by roles. + +If you don't already have these schemas/tables, +see: https://github.com/dwyl/app-mvp-phoenix#create-schemas + + Let's create the Database Schemas (Tables) to store our RBAC data, starting with **`Roles`**: @@ -60,7 +68,7 @@ starting with **`Roles`**: mix phx.gen.html Ctx Role roles name:string desc:string person_id:references:people ``` - +Next create the permissions schema: ``` mix phx.gen.html Ctx Permission permissions name:string desc:string person_id:references:people ``` @@ -71,7 +79,13 @@ Next create the **`many-to-many`** relationship between roles and permissions. mix ecto.gen.migration create_role_permissions ``` -Now create the **`many-to-many`** relationship between people and roles. + + +Now create the **`many-to-many`** relationship between people and roles: + +``` +mix ecto.gen.migration create_people_roles +``` From 2f26ed1b970ad1d0b925c42de462388e2ffe7697 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 22 Jul 2020 18:55:01 +0100 Subject: [PATCH 009/166] add /ping to wake Heroku app see: https://github.com/dwyl/ping --- README.md | 1 + lib/auth_web/controllers/ping_controller.ex | 8 ++++++++ lib/auth_web/router.ex | 3 +++ mix.lock | 1 + test/auth_web/controllers/ping_controller_test.exs | 10 ++++++++++ 5 files changed, 23 insertions(+) create mode 100644 lib/auth_web/controllers/ping_controller.ex create mode 100644 test/auth_web/controllers/ping_controller_test.exs diff --git a/README.md b/README.md index 555b2d9c..5b66a9b9 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ you can setup in ***5 minutes***. +![wake-sleeping-heroku-app](https://dwylauth.herokuapp.com/ping) diff --git a/lib/auth_web/controllers/ping_controller.ex b/lib/auth_web/controllers/ping_controller.ex new file mode 100644 index 00000000..b3f59710 --- /dev/null +++ b/lib/auth_web/controllers/ping_controller.ex @@ -0,0 +1,8 @@ +defmodule AuthWeb.PingController do + use AuthWeb, :controller + + # see: github.com/dwyl/ping + def ping(conn, params) do + Ping.render_pixel(conn, params) + end +end \ No newline at end of file diff --git a/lib/auth_web/router.ex b/lib/auth_web/router.ex index 864c19b8..8fb5f35c 100644 --- a/lib/auth_web/router.ex +++ b/lib/auth_web/router.ex @@ -24,6 +24,9 @@ defmodule AuthWeb.Router do # get "/auth/password/new", AuthController, :password_input post "/auth/password/create", AuthController, :password_create post "/auth/password/verify", AuthController, :password_prompt + + # https://github.com/dwyl/ping + get "/ping", PingController, :ping end pipeline :auth do diff --git a/mix.lock b/mix.lock index 2c92a781..737d4801 100644 --- a/mix.lock +++ b/mix.lock @@ -45,6 +45,7 @@ "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.2", "38d94c30df5e2ef11000697a4fbe2b38d0fbf79239d492ff1be87bbc33bc3a84", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "a3dec3d28ddb5476c96a7c8a38ea8437923408bc88da43e5c45d97037b396280"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, + "ping": {:hex, :ping, "1.0.1", "1f44c7b7151af18627edbd7946a376935871f285fc9c11e9f16ddb1555ea65b5", [:mix], [{:plug, "~> 1.10.1", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c1421bebcb6601e7fb1158700ca99eb2dc0976b45fe2a26023db5a9318aadfa6"}, "plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"}, "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"}, "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, diff --git a/test/auth_web/controllers/ping_controller_test.exs b/test/auth_web/controllers/ping_controller_test.exs new file mode 100644 index 00000000..d6e3192a --- /dev/null +++ b/test/auth_web/controllers/ping_controller_test.exs @@ -0,0 +1,10 @@ +defmodule AuthWeb.PingControllerTest do + use AuthWeb.ConnCase + + test "GET /ping (GIF) renders 1x1 pixel", %{conn: conn} do + conn = get(conn, Routes.ping_path(conn, :ping)) + assert conn.status == 200 + assert conn.state == :sent + assert conn.resp_body =~ <<71, 73, 70, 56, 57>> + end +end \ No newline at end of file From 0114fc8ae98f48736350b7e0d200d5f54552d4bc Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 22 Jul 2020 18:59:54 +0100 Subject: [PATCH 010/166] create roles schema and resources #27 / #31 --- lib/auth/ctx.ex | 104 ++++++++++++++++++ lib/auth/ctx/role.ex | 19 ++++ lib/auth_web/controllers/role_controller.ex | 62 +++++++++++ lib/auth_web/router.ex | 2 + lib/auth_web/templates/role/edit.html.eex | 5 + lib/auth_web/templates/role/form.html.eex | 19 ++++ lib/auth_web/templates/role/index.html.eex | 28 +++++ lib/auth_web/templates/role/new.html.eex | 5 + lib/auth_web/templates/role/show.html.eex | 18 +++ lib/auth_web/views/role_view.ex | 3 + .../20200722175850_create_roles.exs | 15 +++ test/auth/ctx_test.exs | 66 +++++++++++ .../controllers/role_controller_test.exs | 88 +++++++++++++++ 13 files changed, 434 insertions(+) create mode 100644 lib/auth/ctx.ex create mode 100644 lib/auth/ctx/role.ex create mode 100644 lib/auth_web/controllers/role_controller.ex create mode 100644 lib/auth_web/templates/role/edit.html.eex create mode 100644 lib/auth_web/templates/role/form.html.eex create mode 100644 lib/auth_web/templates/role/index.html.eex create mode 100644 lib/auth_web/templates/role/new.html.eex create mode 100644 lib/auth_web/templates/role/show.html.eex create mode 100644 lib/auth_web/views/role_view.ex create mode 100644 priv/repo/migrations/20200722175850_create_roles.exs create mode 100644 test/auth/ctx_test.exs create mode 100644 test/auth_web/controllers/role_controller_test.exs diff --git a/lib/auth/ctx.ex b/lib/auth/ctx.ex new file mode 100644 index 00000000..2a07041c --- /dev/null +++ b/lib/auth/ctx.ex @@ -0,0 +1,104 @@ +defmodule Auth.Ctx do + @moduledoc """ + The Ctx context. + """ + + import Ecto.Query, warn: false + alias Auth.Repo + + alias Auth.Ctx.Role + + @doc """ + Returns the list of roles. + + ## Examples + + iex> list_roles() + [%Role{}, ...] + + """ + def list_roles do + Repo.all(Role) + end + + @doc """ + Gets a single role. + + Raises `Ecto.NoResultsError` if the Role does not exist. + + ## Examples + + iex> get_role!(123) + %Role{} + + iex> get_role!(456) + ** (Ecto.NoResultsError) + + """ + def get_role!(id), do: Repo.get!(Role, id) + + @doc """ + Creates a role. + + ## Examples + + iex> create_role(%{field: value}) + {:ok, %Role{}} + + iex> create_role(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_role(attrs \\ %{}) do + %Role{} + |> Role.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a role. + + ## Examples + + iex> update_role(role, %{field: new_value}) + {:ok, %Role{}} + + iex> update_role(role, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_role(%Role{} = role, attrs) do + role + |> Role.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a role. + + ## Examples + + iex> delete_role(role) + {:ok, %Role{}} + + iex> delete_role(role) + {:error, %Ecto.Changeset{}} + + """ + def delete_role(%Role{} = role) do + Repo.delete(role) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking role changes. + + ## Examples + + iex> change_role(role) + %Ecto.Changeset{data: %Role{}} + + """ + def change_role(%Role{} = role, attrs \\ %{}) do + Role.changeset(role, attrs) + end +end diff --git a/lib/auth/ctx/role.ex b/lib/auth/ctx/role.ex new file mode 100644 index 00000000..e6cd02b0 --- /dev/null +++ b/lib/auth/ctx/role.ex @@ -0,0 +1,19 @@ +defmodule Auth.Ctx.Role do + use Ecto.Schema + import Ecto.Changeset + + schema "roles" do + field :desc, :string + field :name, :string + field :person_id, :id + + timestamps() + end + + @doc false + def changeset(role, attrs) do + role + |> cast(attrs, [:name, :desc]) + |> validate_required([:name, :desc]) + end +end diff --git a/lib/auth_web/controllers/role_controller.ex b/lib/auth_web/controllers/role_controller.ex new file mode 100644 index 00000000..e25c3394 --- /dev/null +++ b/lib/auth_web/controllers/role_controller.ex @@ -0,0 +1,62 @@ +defmodule AuthWeb.RoleController do + use AuthWeb, :controller + + alias Auth.Ctx + alias Auth.Ctx.Role + + def index(conn, _params) do + roles = Ctx.list_roles() + render(conn, "index.html", roles: roles) + end + + def new(conn, _params) do + changeset = Ctx.change_role(%Role{}) + render(conn, "new.html", changeset: changeset) + end + + def create(conn, %{"role" => role_params}) do + case Ctx.create_role(role_params) do + {:ok, role} -> + conn + |> put_flash(:info, "Role created successfully.") + |> redirect(to: Routes.role_path(conn, :show, role)) + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, "new.html", changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + role = Ctx.get_role!(id) + render(conn, "show.html", role: role) + end + + def edit(conn, %{"id" => id}) do + role = Ctx.get_role!(id) + changeset = Ctx.change_role(role) + render(conn, "edit.html", role: role, changeset: changeset) + end + + def update(conn, %{"id" => id, "role" => role_params}) do + role = Ctx.get_role!(id) + + case Ctx.update_role(role, role_params) do + {:ok, role} -> + conn + |> put_flash(:info, "Role updated successfully.") + |> redirect(to: Routes.role_path(conn, :show, role)) + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, "edit.html", role: role, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + role = Ctx.get_role!(id) + {:ok, _role} = Ctx.delete_role(role) + + conn + |> put_flash(:info, "Role deleted successfully.") + |> redirect(to: Routes.role_path(conn, :index)) + end +end diff --git a/lib/auth_web/router.ex b/lib/auth_web/router.ex index 8fb5f35c..298652a2 100644 --- a/lib/auth_web/router.ex +++ b/lib/auth_web/router.ex @@ -38,6 +38,8 @@ defmodule AuthWeb.Router do pipe_through :auth get "/profile", AuthController, :admin + resources "/roles", RoleController + resources "/settings/apikeys", ApikeyController end diff --git a/lib/auth_web/templates/role/edit.html.eex b/lib/auth_web/templates/role/edit.html.eex new file mode 100644 index 00000000..f5527cf7 --- /dev/null +++ b/lib/auth_web/templates/role/edit.html.eex @@ -0,0 +1,5 @@ +

Edit Role

+ +<%= render "form.html", Map.put(assigns, :action, Routes.role_path(@conn, :update, @role)) %> + +<%= link "Back", to: Routes.role_path(@conn, :index) %> diff --git a/lib/auth_web/templates/role/form.html.eex b/lib/auth_web/templates/role/form.html.eex new file mode 100644 index 00000000..1339ceb1 --- /dev/null +++ b/lib/auth_web/templates/role/form.html.eex @@ -0,0 +1,19 @@ +<%= form_for @changeset, @action, fn f -> %> + <%= if @changeset.action do %> +
+

Oops, something went wrong! Please check the errors below.

+
+ <% end %> + + <%= label f, :name %> + <%= text_input f, :name %> + <%= error_tag f, :name %> + + <%= label f, :desc %> + <%= text_input f, :desc %> + <%= error_tag f, :desc %> + +
+ <%= submit "Save" %> +
+<% end %> diff --git a/lib/auth_web/templates/role/index.html.eex b/lib/auth_web/templates/role/index.html.eex new file mode 100644 index 00000000..4920d942 --- /dev/null +++ b/lib/auth_web/templates/role/index.html.eex @@ -0,0 +1,28 @@ +

Listing Roles

+ + + + + + + + + + + +<%= for role <- @roles do %> + + + + + + +<% end %> + +
NameDesc
<%= role.name %><%= role.desc %> + <%= link "Show", to: Routes.role_path(@conn, :show, role) %> + <%= link "Edit", to: Routes.role_path(@conn, :edit, role) %> + <%= link "Delete", to: Routes.role_path(@conn, :delete, role), method: :delete, data: [confirm: "Are you sure?"] %> +
+ +<%= link "New Role", to: Routes.role_path(@conn, :new) %> diff --git a/lib/auth_web/templates/role/new.html.eex b/lib/auth_web/templates/role/new.html.eex new file mode 100644 index 00000000..79e26f37 --- /dev/null +++ b/lib/auth_web/templates/role/new.html.eex @@ -0,0 +1,5 @@ +

New Role

+ +<%= render "form.html", Map.put(assigns, :action, Routes.role_path(@conn, :create)) %> + +<%= link "Back", to: Routes.role_path(@conn, :index) %> diff --git a/lib/auth_web/templates/role/show.html.eex b/lib/auth_web/templates/role/show.html.eex new file mode 100644 index 00000000..98f82c6e --- /dev/null +++ b/lib/auth_web/templates/role/show.html.eex @@ -0,0 +1,18 @@ +

Show Role

+ +
    + +
  • + Name: + <%= @role.name %> +
  • + +
  • + Desc: + <%= @role.desc %> +
  • + +
+ +<%= link "Edit", to: Routes.role_path(@conn, :edit, @role) %> +<%= link "Back", to: Routes.role_path(@conn, :index) %> diff --git a/lib/auth_web/views/role_view.ex b/lib/auth_web/views/role_view.ex new file mode 100644 index 00000000..1aff1f86 --- /dev/null +++ b/lib/auth_web/views/role_view.ex @@ -0,0 +1,3 @@ +defmodule AuthWeb.RoleView do + use AuthWeb, :view +end diff --git a/priv/repo/migrations/20200722175850_create_roles.exs b/priv/repo/migrations/20200722175850_create_roles.exs new file mode 100644 index 00000000..c393b64d --- /dev/null +++ b/priv/repo/migrations/20200722175850_create_roles.exs @@ -0,0 +1,15 @@ +defmodule Auth.Repo.Migrations.CreateRoles do + use Ecto.Migration + + def change do + create table(:roles) do + add :name, :string + add :desc, :string + add :person_id, references(:people, on_delete: :nothing) + + timestamps() + end + + create index(:roles, [:person_id]) + end +end diff --git a/test/auth/ctx_test.exs b/test/auth/ctx_test.exs new file mode 100644 index 00000000..b09034f3 --- /dev/null +++ b/test/auth/ctx_test.exs @@ -0,0 +1,66 @@ +defmodule Auth.CtxTest do + use Auth.DataCase + + alias Auth.Ctx + + describe "roles" do + alias Auth.Ctx.Role + + @valid_attrs %{desc: "some desc", name: "some name"} + @update_attrs %{desc: "some updated desc", name: "some updated name"} + @invalid_attrs %{desc: nil, name: nil} + + def role_fixture(attrs \\ %{}) do + {:ok, role} = + attrs + |> Enum.into(@valid_attrs) + |> Ctx.create_role() + + role + end + + test "list_roles/0 returns all roles" do + role = role_fixture() + assert Ctx.list_roles() == [role] + end + + test "get_role!/1 returns the role with given id" do + role = role_fixture() + assert Ctx.get_role!(role.id) == role + end + + test "create_role/1 with valid data creates a role" do + assert {:ok, %Role{} = role} = Ctx.create_role(@valid_attrs) + assert role.desc == "some desc" + assert role.name == "some name" + end + + test "create_role/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Ctx.create_role(@invalid_attrs) + end + + test "update_role/2 with valid data updates the role" do + role = role_fixture() + assert {:ok, %Role{} = role} = Ctx.update_role(role, @update_attrs) + assert role.desc == "some updated desc" + assert role.name == "some updated name" + end + + test "update_role/2 with invalid data returns error changeset" do + role = role_fixture() + assert {:error, %Ecto.Changeset{}} = Ctx.update_role(role, @invalid_attrs) + assert role == Ctx.get_role!(role.id) + end + + test "delete_role/1 deletes the role" do + role = role_fixture() + assert {:ok, %Role{}} = Ctx.delete_role(role) + assert_raise Ecto.NoResultsError, fn -> Ctx.get_role!(role.id) end + end + + test "change_role/1 returns a role changeset" do + role = role_fixture() + assert %Ecto.Changeset{} = Ctx.change_role(role) + end + end +end diff --git a/test/auth_web/controllers/role_controller_test.exs b/test/auth_web/controllers/role_controller_test.exs new file mode 100644 index 00000000..97fe5a18 --- /dev/null +++ b/test/auth_web/controllers/role_controller_test.exs @@ -0,0 +1,88 @@ +defmodule AuthWeb.RoleControllerTest do + use AuthWeb.ConnCase + + alias Auth.Ctx + + @create_attrs %{desc: "some desc", name: "some name"} + @update_attrs %{desc: "some updated desc", name: "some updated name"} + @invalid_attrs %{desc: nil, name: nil} + + def fixture(:role) do + {:ok, role} = Ctx.create_role(@create_attrs) + role + end + + describe "index" do + test "lists all roles", %{conn: conn} do + conn = get(conn, Routes.role_path(conn, :index)) + assert html_response(conn, 200) =~ "Listing Roles" + end + end + + describe "new role" do + test "renders form", %{conn: conn} do + conn = get(conn, Routes.role_path(conn, :new)) + assert html_response(conn, 200) =~ "New Role" + end + end + + describe "create role" do + test "redirects to show when data is valid", %{conn: conn} do + conn = post(conn, Routes.role_path(conn, :create), role: @create_attrs) + + assert %{id: id} = redirected_params(conn) + assert redirected_to(conn) == Routes.role_path(conn, :show, id) + + conn = get(conn, Routes.role_path(conn, :show, id)) + assert html_response(conn, 200) =~ "Show Role" + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, Routes.role_path(conn, :create), role: @invalid_attrs) + assert html_response(conn, 200) =~ "New Role" + end + end + + describe "edit role" do + setup [:create_role] + + test "renders form for editing chosen role", %{conn: conn, role: role} do + conn = get(conn, Routes.role_path(conn, :edit, role)) + assert html_response(conn, 200) =~ "Edit Role" + end + end + + describe "update role" do + setup [:create_role] + + test "redirects when data is valid", %{conn: conn, role: role} do + conn = put(conn, Routes.role_path(conn, :update, role), role: @update_attrs) + assert redirected_to(conn) == Routes.role_path(conn, :show, role) + + conn = get(conn, Routes.role_path(conn, :show, role)) + assert html_response(conn, 200) =~ "some updated desc" + end + + test "renders errors when data is invalid", %{conn: conn, role: role} do + conn = put(conn, Routes.role_path(conn, :update, role), role: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Role" + end + end + + describe "delete role" do + setup [:create_role] + + test "deletes chosen role", %{conn: conn, role: role} do + conn = delete(conn, Routes.role_path(conn, :delete, role)) + assert redirected_to(conn) == Routes.role_path(conn, :index) + assert_error_sent 404, fn -> + get(conn, Routes.role_path(conn, :show, role)) + end + end + end + + defp create_role(_) do + role = fixture(:role) + %{role: role} + end +end From 18be2a034141c73309c5e7bc36b9ee2ab92a5f10 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 22 Jul 2020 19:01:11 +0100 Subject: [PATCH 011/166] create permissions schema and resources #27 / #31 --- lib/auth/ctx.ex | 96 +++++++++++++++++++ lib/auth/ctx/permission.ex | 19 ++++ .../controllers/permission_controller.ex | 62 ++++++++++++ lib/auth_web/router.ex | 2 +- .../templates/permission/edit.html.eex | 5 + .../templates/permission/form.html.eex | 19 ++++ .../templates/permission/index.html.eex | 28 ++++++ .../templates/permission/new.html.eex | 5 + .../templates/permission/show.html.eex | 18 ++++ lib/auth_web/views/permission_view.ex | 3 + .../20200722180019_create_permissions.exs | 15 +++ test/auth/ctx_test.exs | 61 ++++++++++++ .../permission_controller_test.exs | 88 +++++++++++++++++ 13 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 lib/auth/ctx/permission.ex create mode 100644 lib/auth_web/controllers/permission_controller.ex create mode 100644 lib/auth_web/templates/permission/edit.html.eex create mode 100644 lib/auth_web/templates/permission/form.html.eex create mode 100644 lib/auth_web/templates/permission/index.html.eex create mode 100644 lib/auth_web/templates/permission/new.html.eex create mode 100644 lib/auth_web/templates/permission/show.html.eex create mode 100644 lib/auth_web/views/permission_view.ex create mode 100644 priv/repo/migrations/20200722180019_create_permissions.exs create mode 100644 test/auth_web/controllers/permission_controller_test.exs diff --git a/lib/auth/ctx.ex b/lib/auth/ctx.ex index 2a07041c..d914f3db 100644 --- a/lib/auth/ctx.ex +++ b/lib/auth/ctx.ex @@ -101,4 +101,100 @@ defmodule Auth.Ctx do def change_role(%Role{} = role, attrs \\ %{}) do Role.changeset(role, attrs) end + + alias Auth.Ctx.Permission + + @doc """ + Returns the list of permissions. + + ## Examples + + iex> list_permissions() + [%Permission{}, ...] + + """ + def list_permissions do + Repo.all(Permission) + end + + @doc """ + Gets a single permission. + + Raises `Ecto.NoResultsError` if the Permission does not exist. + + ## Examples + + iex> get_permission!(123) + %Permission{} + + iex> get_permission!(456) + ** (Ecto.NoResultsError) + + """ + def get_permission!(id), do: Repo.get!(Permission, id) + + @doc """ + Creates a permission. + + ## Examples + + iex> create_permission(%{field: value}) + {:ok, %Permission{}} + + iex> create_permission(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_permission(attrs \\ %{}) do + %Permission{} + |> Permission.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a permission. + + ## Examples + + iex> update_permission(permission, %{field: new_value}) + {:ok, %Permission{}} + + iex> update_permission(permission, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_permission(%Permission{} = permission, attrs) do + permission + |> Permission.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a permission. + + ## Examples + + iex> delete_permission(permission) + {:ok, %Permission{}} + + iex> delete_permission(permission) + {:error, %Ecto.Changeset{}} + + """ + def delete_permission(%Permission{} = permission) do + Repo.delete(permission) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking permission changes. + + ## Examples + + iex> change_permission(permission) + %Ecto.Changeset{data: %Permission{}} + + """ + def change_permission(%Permission{} = permission, attrs \\ %{}) do + Permission.changeset(permission, attrs) + end end diff --git a/lib/auth/ctx/permission.ex b/lib/auth/ctx/permission.ex new file mode 100644 index 00000000..c321e880 --- /dev/null +++ b/lib/auth/ctx/permission.ex @@ -0,0 +1,19 @@ +defmodule Auth.Ctx.Permission do + use Ecto.Schema + import Ecto.Changeset + + schema "permissions" do + field :desc, :string + field :name, :string + field :person_id, :id + + timestamps() + end + + @doc false + def changeset(permission, attrs) do + permission + |> cast(attrs, [:name, :desc]) + |> validate_required([:name, :desc]) + end +end diff --git a/lib/auth_web/controllers/permission_controller.ex b/lib/auth_web/controllers/permission_controller.ex new file mode 100644 index 00000000..2bc52e54 --- /dev/null +++ b/lib/auth_web/controllers/permission_controller.ex @@ -0,0 +1,62 @@ +defmodule AuthWeb.PermissionController do + use AuthWeb, :controller + + alias Auth.Ctx + alias Auth.Ctx.Permission + + def index(conn, _params) do + permissions = Ctx.list_permissions() + render(conn, "index.html", permissions: permissions) + end + + def new(conn, _params) do + changeset = Ctx.change_permission(%Permission{}) + render(conn, "new.html", changeset: changeset) + end + + def create(conn, %{"permission" => permission_params}) do + case Ctx.create_permission(permission_params) do + {:ok, permission} -> + conn + |> put_flash(:info, "Permission created successfully.") + |> redirect(to: Routes.permission_path(conn, :show, permission)) + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, "new.html", changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + permission = Ctx.get_permission!(id) + render(conn, "show.html", permission: permission) + end + + def edit(conn, %{"id" => id}) do + permission = Ctx.get_permission!(id) + changeset = Ctx.change_permission(permission) + render(conn, "edit.html", permission: permission, changeset: changeset) + end + + def update(conn, %{"id" => id, "permission" => permission_params}) do + permission = Ctx.get_permission!(id) + + case Ctx.update_permission(permission, permission_params) do + {:ok, permission} -> + conn + |> put_flash(:info, "Permission updated successfully.") + |> redirect(to: Routes.permission_path(conn, :show, permission)) + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, "edit.html", permission: permission, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + permission = Ctx.get_permission!(id) + {:ok, _permission} = Ctx.delete_permission(permission) + + conn + |> put_flash(:info, "Permission deleted successfully.") + |> redirect(to: Routes.permission_path(conn, :index)) + end +end diff --git a/lib/auth_web/router.ex b/lib/auth_web/router.ex index 298652a2..e16949c8 100644 --- a/lib/auth_web/router.ex +++ b/lib/auth_web/router.ex @@ -39,7 +39,7 @@ defmodule AuthWeb.Router do get "/profile", AuthController, :admin resources "/roles", RoleController - + resources "/permissions", PermissionController resources "/settings/apikeys", ApikeyController end diff --git a/lib/auth_web/templates/permission/edit.html.eex b/lib/auth_web/templates/permission/edit.html.eex new file mode 100644 index 00000000..1794189c --- /dev/null +++ b/lib/auth_web/templates/permission/edit.html.eex @@ -0,0 +1,5 @@ +

Edit Permission

+ +<%= render "form.html", Map.put(assigns, :action, Routes.permission_path(@conn, :update, @permission)) %> + +<%= link "Back", to: Routes.permission_path(@conn, :index) %> diff --git a/lib/auth_web/templates/permission/form.html.eex b/lib/auth_web/templates/permission/form.html.eex new file mode 100644 index 00000000..1339ceb1 --- /dev/null +++ b/lib/auth_web/templates/permission/form.html.eex @@ -0,0 +1,19 @@ +<%= form_for @changeset, @action, fn f -> %> + <%= if @changeset.action do %> +
+

Oops, something went wrong! Please check the errors below.

+
+ <% end %> + + <%= label f, :name %> + <%= text_input f, :name %> + <%= error_tag f, :name %> + + <%= label f, :desc %> + <%= text_input f, :desc %> + <%= error_tag f, :desc %> + +
+ <%= submit "Save" %> +
+<% end %> diff --git a/lib/auth_web/templates/permission/index.html.eex b/lib/auth_web/templates/permission/index.html.eex new file mode 100644 index 00000000..b4277070 --- /dev/null +++ b/lib/auth_web/templates/permission/index.html.eex @@ -0,0 +1,28 @@ +

Listing Permissions

+ + + + + + + + + + + +<%= for permission <- @permissions do %> + + + + + + +<% end %> + +
NameDesc
<%= permission.name %><%= permission.desc %> + <%= link "Show", to: Routes.permission_path(@conn, :show, permission) %> + <%= link "Edit", to: Routes.permission_path(@conn, :edit, permission) %> + <%= link "Delete", to: Routes.permission_path(@conn, :delete, permission), method: :delete, data: [confirm: "Are you sure?"] %> +
+ +<%= link "New Permission", to: Routes.permission_path(@conn, :new) %> diff --git a/lib/auth_web/templates/permission/new.html.eex b/lib/auth_web/templates/permission/new.html.eex new file mode 100644 index 00000000..736492b0 --- /dev/null +++ b/lib/auth_web/templates/permission/new.html.eex @@ -0,0 +1,5 @@ +

New Permission

+ +<%= render "form.html", Map.put(assigns, :action, Routes.permission_path(@conn, :create)) %> + +<%= link "Back", to: Routes.permission_path(@conn, :index) %> diff --git a/lib/auth_web/templates/permission/show.html.eex b/lib/auth_web/templates/permission/show.html.eex new file mode 100644 index 00000000..cb78e3a1 --- /dev/null +++ b/lib/auth_web/templates/permission/show.html.eex @@ -0,0 +1,18 @@ +

Show Permission

+ +
    + +
  • + Name: + <%= @permission.name %> +
  • + +
  • + Desc: + <%= @permission.desc %> +
  • + +
+ +<%= link "Edit", to: Routes.permission_path(@conn, :edit, @permission) %> +<%= link "Back", to: Routes.permission_path(@conn, :index) %> diff --git a/lib/auth_web/views/permission_view.ex b/lib/auth_web/views/permission_view.ex new file mode 100644 index 00000000..52313083 --- /dev/null +++ b/lib/auth_web/views/permission_view.ex @@ -0,0 +1,3 @@ +defmodule AuthWeb.PermissionView do + use AuthWeb, :view +end diff --git a/priv/repo/migrations/20200722180019_create_permissions.exs b/priv/repo/migrations/20200722180019_create_permissions.exs new file mode 100644 index 00000000..7c47cbad --- /dev/null +++ b/priv/repo/migrations/20200722180019_create_permissions.exs @@ -0,0 +1,15 @@ +defmodule Auth.Repo.Migrations.CreatePermissions do + use Ecto.Migration + + def change do + create table(:permissions) do + add :name, :string + add :desc, :string + add :person_id, references(:people, on_delete: :nothing) + + timestamps() + end + + create index(:permissions, [:person_id]) + end +end diff --git a/test/auth/ctx_test.exs b/test/auth/ctx_test.exs index b09034f3..ef89d285 100644 --- a/test/auth/ctx_test.exs +++ b/test/auth/ctx_test.exs @@ -63,4 +63,65 @@ defmodule Auth.CtxTest do assert %Ecto.Changeset{} = Ctx.change_role(role) end end + + describe "permissions" do + alias Auth.Ctx.Permission + + @valid_attrs %{desc: "some desc", name: "some name"} + @update_attrs %{desc: "some updated desc", name: "some updated name"} + @invalid_attrs %{desc: nil, name: nil} + + def permission_fixture(attrs \\ %{}) do + {:ok, permission} = + attrs + |> Enum.into(@valid_attrs) + |> Ctx.create_permission() + + permission + end + + test "list_permissions/0 returns all permissions" do + permission = permission_fixture() + assert Ctx.list_permissions() == [permission] + end + + test "get_permission!/1 returns the permission with given id" do + permission = permission_fixture() + assert Ctx.get_permission!(permission.id) == permission + end + + test "create_permission/1 with valid data creates a permission" do + assert {:ok, %Permission{} = permission} = Ctx.create_permission(@valid_attrs) + assert permission.desc == "some desc" + assert permission.name == "some name" + end + + test "create_permission/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Ctx.create_permission(@invalid_attrs) + end + + test "update_permission/2 with valid data updates the permission" do + permission = permission_fixture() + assert {:ok, %Permission{} = permission} = Ctx.update_permission(permission, @update_attrs) + assert permission.desc == "some updated desc" + assert permission.name == "some updated name" + end + + test "update_permission/2 with invalid data returns error changeset" do + permission = permission_fixture() + assert {:error, %Ecto.Changeset{}} = Ctx.update_permission(permission, @invalid_attrs) + assert permission == Ctx.get_permission!(permission.id) + end + + test "delete_permission/1 deletes the permission" do + permission = permission_fixture() + assert {:ok, %Permission{}} = Ctx.delete_permission(permission) + assert_raise Ecto.NoResultsError, fn -> Ctx.get_permission!(permission.id) end + end + + test "change_permission/1 returns a permission changeset" do + permission = permission_fixture() + assert %Ecto.Changeset{} = Ctx.change_permission(permission) + end + end end diff --git a/test/auth_web/controllers/permission_controller_test.exs b/test/auth_web/controllers/permission_controller_test.exs new file mode 100644 index 00000000..a087b14a --- /dev/null +++ b/test/auth_web/controllers/permission_controller_test.exs @@ -0,0 +1,88 @@ +defmodule AuthWeb.PermissionControllerTest do + use AuthWeb.ConnCase + + alias Auth.Ctx + + @create_attrs %{desc: "some desc", name: "some name"} + @update_attrs %{desc: "some updated desc", name: "some updated name"} + @invalid_attrs %{desc: nil, name: nil} + + def fixture(:permission) do + {:ok, permission} = Ctx.create_permission(@create_attrs) + permission + end + + describe "index" do + test "lists all permissions", %{conn: conn} do + conn = get(conn, Routes.permission_path(conn, :index)) + assert html_response(conn, 200) =~ "Listing Permissions" + end + end + + describe "new permission" do + test "renders form", %{conn: conn} do + conn = get(conn, Routes.permission_path(conn, :new)) + assert html_response(conn, 200) =~ "New Permission" + end + end + + describe "create permission" do + test "redirects to show when data is valid", %{conn: conn} do + conn = post(conn, Routes.permission_path(conn, :create), permission: @create_attrs) + + assert %{id: id} = redirected_params(conn) + assert redirected_to(conn) == Routes.permission_path(conn, :show, id) + + conn = get(conn, Routes.permission_path(conn, :show, id)) + assert html_response(conn, 200) =~ "Show Permission" + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, Routes.permission_path(conn, :create), permission: @invalid_attrs) + assert html_response(conn, 200) =~ "New Permission" + end + end + + describe "edit permission" do + setup [:create_permission] + + test "renders form for editing chosen permission", %{conn: conn, permission: permission} do + conn = get(conn, Routes.permission_path(conn, :edit, permission)) + assert html_response(conn, 200) =~ "Edit Permission" + end + end + + describe "update permission" do + setup [:create_permission] + + test "redirects when data is valid", %{conn: conn, permission: permission} do + conn = put(conn, Routes.permission_path(conn, :update, permission), permission: @update_attrs) + assert redirected_to(conn) == Routes.permission_path(conn, :show, permission) + + conn = get(conn, Routes.permission_path(conn, :show, permission)) + assert html_response(conn, 200) =~ "some updated desc" + end + + test "renders errors when data is invalid", %{conn: conn, permission: permission} do + conn = put(conn, Routes.permission_path(conn, :update, permission), permission: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Permission" + end + end + + describe "delete permission" do + setup [:create_permission] + + test "deletes chosen permission", %{conn: conn, permission: permission} do + conn = delete(conn, Routes.permission_path(conn, :delete, permission)) + assert redirected_to(conn) == Routes.permission_path(conn, :index) + assert_error_sent 404, fn -> + get(conn, Routes.permission_path(conn, :show, permission)) + end + end + end + + defp create_permission(_) do + permission = fixture(:permission) + %{permission: permission} + end +end From e961a173365837edd15c0ce4b15c8f4d59a07b6d Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 23 Jul 2020 11:57:38 +0100 Subject: [PATCH 012/166] add AuthTest.admin_login/1 helper function to login as admin https://github.com/dwyl/auth/issues/27#issuecomment-662895801 --- test/test_helper.exs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/test_helper.exs b/test/test_helper.exs index 26689cc8..c4d0d2a0 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,2 +1,17 @@ ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(Auth.Repo, :manual) + + +defmodule AuthTest do + @moduledoc """ + Test helper functions :-) + """ + @admin_email System.get_env("ADMIN_EMAIL") + @doc """ + add a valid JWT/session to the conn for routes that require auth as "SuperAdmin" + """ + def admin_login(conn) do + person = Auth.Person.get_person_by_email(@admin_email) + AuthPlug.create_jwt_session(conn, person) + end +end \ No newline at end of file From a202d73ea3e777cf63e1bf3ad74aa061534818a4 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 23 Jul 2020 11:58:56 +0100 Subject: [PATCH 013/166] import AuthTest helper function in conn_case.ex so helper functions are available in all tests #27 --- test/support/conn_case.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index f453f883..07e74d03 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -22,6 +22,9 @@ defmodule AuthWeb.ConnCase do # Import conveniences for testing with connections import Plug.Conn import Phoenix.ConnTest + # AuthTest is defined in test_helpers.exs + # as per https://stackoverflow.com/a/58902158/1148249 + import AuthTest alias AuthWeb.Router.Helpers, as: Routes # The default endpoint for testing From 39ff5c1ac3f5b3fbf7cff45c3448c51804fd2a88 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 23 Jul 2020 12:07:51 +0100 Subject: [PATCH 014/166] add admin_login/1 to all Role routes for RBAC #27 / #31 --- .../controllers/role_controller_test.exs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test/auth_web/controllers/role_controller_test.exs b/test/auth_web/controllers/role_controller_test.exs index 97fe5a18..f18c9b56 100644 --- a/test/auth_web/controllers/role_controller_test.exs +++ b/test/auth_web/controllers/role_controller_test.exs @@ -14,21 +14,22 @@ defmodule AuthWeb.RoleControllerTest do describe "index" do test "lists all roles", %{conn: conn} do - conn = get(conn, Routes.role_path(conn, :index)) + conn = admin_login(conn) |> get(Routes.role_path(conn, :index)) assert html_response(conn, 200) =~ "Listing Roles" end end describe "new role" do test "renders form", %{conn: conn} do - conn = get(conn, Routes.role_path(conn, :new)) + conn = admin_login(conn) |> get(Routes.role_path(conn, :new)) assert html_response(conn, 200) =~ "New Role" end end describe "create role" do test "redirects to show when data is valid", %{conn: conn} do - conn = post(conn, Routes.role_path(conn, :create), role: @create_attrs) + conn = admin_login(conn) + |> post(Routes.role_path(conn, :create), role: @create_attrs) assert %{id: id} = redirected_params(conn) assert redirected_to(conn) == Routes.role_path(conn, :show, id) @@ -38,7 +39,8 @@ defmodule AuthWeb.RoleControllerTest do end test "renders errors when data is invalid", %{conn: conn} do - conn = post(conn, Routes.role_path(conn, :create), role: @invalid_attrs) + conn = admin_login(conn) + |> post(Routes.role_path(conn, :create), role: @invalid_attrs) assert html_response(conn, 200) =~ "New Role" end end @@ -47,7 +49,8 @@ defmodule AuthWeb.RoleControllerTest do setup [:create_role] test "renders form for editing chosen role", %{conn: conn, role: role} do - conn = get(conn, Routes.role_path(conn, :edit, role)) + conn = admin_login(conn) + |> get(Routes.role_path(conn, :edit, role)) assert html_response(conn, 200) =~ "Edit Role" end end @@ -56,7 +59,8 @@ defmodule AuthWeb.RoleControllerTest do setup [:create_role] test "redirects when data is valid", %{conn: conn, role: role} do - conn = put(conn, Routes.role_path(conn, :update, role), role: @update_attrs) + conn = admin_login(conn) + |> put(Routes.role_path(conn, :update, role), role: @update_attrs) assert redirected_to(conn) == Routes.role_path(conn, :show, role) conn = get(conn, Routes.role_path(conn, :show, role)) @@ -64,7 +68,8 @@ defmodule AuthWeb.RoleControllerTest do end test "renders errors when data is invalid", %{conn: conn, role: role} do - conn = put(conn, Routes.role_path(conn, :update, role), role: @invalid_attrs) + conn = admin_login(conn) + |> put(Routes.role_path(conn, :update, role), role: @invalid_attrs) assert html_response(conn, 200) =~ "Edit Role" end end @@ -73,7 +78,8 @@ defmodule AuthWeb.RoleControllerTest do setup [:create_role] test "deletes chosen role", %{conn: conn, role: role} do - conn = delete(conn, Routes.role_path(conn, :delete, role)) + conn = admin_login(conn) + |> delete(Routes.role_path(conn, :delete, role)) assert redirected_to(conn) == Routes.role_path(conn, :index) assert_error_sent 404, fn -> get(conn, Routes.role_path(conn, :show, role)) From f468b4d3637adf4726b358e0c57b1ceada72cbd3 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 23 Jul 2020 12:37:49 +0100 Subject: [PATCH 015/166] add admin_login/1 to all permissions tests #27 / #31 --- role-based-access-control.md | 15 +++++++----- .../controllers/apikey_controller_test.exs | 12 ++++------ .../permission_controller_test.exs | 23 ++++++++++++------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/role-based-access-control.md b/role-based-access-control.md index 66ceb558..8a909a49 100644 --- a/role-based-access-control.md +++ b/role-based-access-control.md @@ -1,13 +1,16 @@ -# Role Based Access Control (RBAC) +# Role Based Access Control (RBAC) _Understand_ the fundamentals of Role Based Access Control (RBAC) +so that you can easily control who has access to what in your App. ## Why? RBAC lets you easily manage roles and permissions in any application -and see at a glance exactly permissions a person has in the system. +and see at a glance exactly what permissions a person has. It reduces complexity over traditional -Access Control List (ACL) based permissions systems. +Access Control List (ACL) based permissions systems +and helps everyone building and maintaining the app +to focus on security. ## What? @@ -53,9 +56,9 @@ should learn about RBAC. ## _How_? -Before creating any roles, -you will need to have a baseline schema including people -as people will be referenced by roles. +_Before_ creating any roles, +you will need to have a baseline schema including **`people`** +as **`person.id`** will be referenced by roles. If you don't already have these schemas/tables, see: https://github.com/dwyl/app-mvp-phoenix#create-schemas diff --git a/test/auth_web/controllers/apikey_controller_test.exs b/test/auth_web/controllers/apikey_controller_test.exs index 2f4d26d6..38c73115 100644 --- a/test/auth_web/controllers/apikey_controller_test.exs +++ b/test/auth_web/controllers/apikey_controller_test.exs @@ -74,8 +74,7 @@ defmodule AuthWeb.ApikeyControllerTest do # describe "index" do test "lists all apikeys", %{conn: conn} do - person = Auth.Person.get_person_by_email(@email) - conn = AuthPlug.create_jwt_session(conn, %{email: @email, id: person.id}) + conn = admin_login(conn) |> get(Routes.apikey_path(conn, :index)) assert html_response(conn, 200) =~ "Auth API Keys" @@ -84,9 +83,7 @@ defmodule AuthWeb.ApikeyControllerTest do describe "new apikey" do test "renders form", %{conn: conn} do - person = Auth.Person.get_person_by_email(@email) - - conn = AuthPlug.create_jwt_session(conn, %{email: @email, id: person.id}) + conn = admin_login(conn) |> get(Routes.apikey_path(conn, :new)) assert html_response(conn, 200) =~ "New Apikey" @@ -95,8 +92,7 @@ defmodule AuthWeb.ApikeyControllerTest do describe "create apikey" do test "redirects to show when data is valid", %{conn: conn} do - person = Auth.Person.get_person_by_email(@email) - conn = AuthPlug.create_jwt_session(conn, person) + conn = admin_login(conn) |> post(Routes.apikey_path(conn, :create), apikey: @create_attrs) assert %{id: id} = redirected_params(conn) @@ -118,7 +114,7 @@ defmodule AuthWeb.ApikeyControllerTest do describe "edit apikey" do test "renders form for editing chosen apikey", %{conn: conn} do person = Auth.Person.get_person_by_email(@email) - conn = AuthPlug.create_jwt_session(conn, person) + conn = admin_login(conn) {:ok, key} = %{"name" => "test key", "url" => "http://localhost:4000"} diff --git a/test/auth_web/controllers/permission_controller_test.exs b/test/auth_web/controllers/permission_controller_test.exs index a087b14a..47e00bb9 100644 --- a/test/auth_web/controllers/permission_controller_test.exs +++ b/test/auth_web/controllers/permission_controller_test.exs @@ -14,21 +14,23 @@ defmodule AuthWeb.PermissionControllerTest do describe "index" do test "lists all permissions", %{conn: conn} do - conn = get(conn, Routes.permission_path(conn, :index)) + conn = admin_login(conn) |> get(Routes.permission_path(conn, :index)) assert html_response(conn, 200) =~ "Listing Permissions" end end describe "new permission" do test "renders form", %{conn: conn} do - conn = get(conn, Routes.permission_path(conn, :new)) + conn = admin_login(conn) |> get(Routes.permission_path(conn, :new)) assert html_response(conn, 200) =~ "New Permission" end end describe "create permission" do test "redirects to show when data is valid", %{conn: conn} do - conn = post(conn, Routes.permission_path(conn, :create), permission: @create_attrs) + + conn = admin_login(conn) + |> post(Routes.permission_path(conn, :create), permission: @create_attrs) assert %{id: id} = redirected_params(conn) assert redirected_to(conn) == Routes.permission_path(conn, :show, id) @@ -38,7 +40,8 @@ defmodule AuthWeb.PermissionControllerTest do end test "renders errors when data is invalid", %{conn: conn} do - conn = post(conn, Routes.permission_path(conn, :create), permission: @invalid_attrs) + conn = admin_login(conn) + |> post(Routes.permission_path(conn, :create), permission: @invalid_attrs) assert html_response(conn, 200) =~ "New Permission" end end @@ -47,7 +50,8 @@ defmodule AuthWeb.PermissionControllerTest do setup [:create_permission] test "renders form for editing chosen permission", %{conn: conn, permission: permission} do - conn = get(conn, Routes.permission_path(conn, :edit, permission)) + conn = admin_login(conn) + |> get(Routes.permission_path(conn, :edit, permission)) assert html_response(conn, 200) =~ "Edit Permission" end end @@ -56,7 +60,8 @@ defmodule AuthWeb.PermissionControllerTest do setup [:create_permission] test "redirects when data is valid", %{conn: conn, permission: permission} do - conn = put(conn, Routes.permission_path(conn, :update, permission), permission: @update_attrs) + conn = admin_login(conn) + |> put(Routes.permission_path(conn, :update, permission), permission: @update_attrs) assert redirected_to(conn) == Routes.permission_path(conn, :show, permission) conn = get(conn, Routes.permission_path(conn, :show, permission)) @@ -64,7 +69,8 @@ defmodule AuthWeb.PermissionControllerTest do end test "renders errors when data is invalid", %{conn: conn, permission: permission} do - conn = put(conn, Routes.permission_path(conn, :update, permission), permission: @invalid_attrs) + conn = admin_login(conn) + |> put(Routes.permission_path(conn, :update, permission), permission: @invalid_attrs) assert html_response(conn, 200) =~ "Edit Permission" end end @@ -73,7 +79,8 @@ defmodule AuthWeb.PermissionControllerTest do setup [:create_permission] test "deletes chosen permission", %{conn: conn, permission: permission} do - conn = delete(conn, Routes.permission_path(conn, :delete, permission)) + conn = admin_login(conn) + |> delete(Routes.permission_path(conn, :delete, permission)) assert redirected_to(conn) == Routes.permission_path(conn, :index) assert_error_sent 404, fn -> get(conn, Routes.permission_path(conn, :show, permission)) From 2a3c361e87cbe4fadbd6beda2eef989299c48a53 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 23 Jul 2020 13:48:55 +0100 Subject: [PATCH 016/166] add table listing 7 default roles to rbac doc #82 --- role-based-access-control.md | 49 ++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/role-based-access-control.md b/role-based-access-control.md index 8a909a49..a064e4e9 100644 --- a/role-based-access-control.md +++ b/role-based-access-control.md @@ -22,7 +22,7 @@ to manage the permissions assigned to the people using the App(s). Each role granted just enough flexibility and permissions to perform the tasks required for their job, this helps enforce the -[principal of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) +[principal of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege). The RBAC methodology is based on a set of three principal rules that govern access to systems: @@ -40,7 +40,7 @@ The purpose of role authorization is to ensure that people can only assume a role for which they have been given the appropriate authorization. When a person assumes a role, -they must do so with authorization from an administrator. +they must do so with authorization from an admin. 3. **Transaction Authorization**: An operation can only be completed @@ -48,6 +48,35 @@ if the person attempting to complete the transaction possesses the appropriate role. +### Default Roles + +We have defined the following 7 `default` roles based on our experience/research +into RBAC systems of several of the most popular applications +including both "enterprise" (closed source) and popular open source CRM/CMS apps. + +| **`id`** | **`name`** | **`desc`** | `person_id` | +| -------- | ---------- | ---------- | ----------- | +| `1` | superadmin | Can **`CREATE`** new roles. Can **`CREATE`**, **`UPDATE`** and **`DELETE`** Any content. Can **`PURGE`** deleted items. Can "ban" any user including people with "Admin" Role. | 1 | +| `2` | admin | Can **create** new roles and **assign** existing roles. Can **`CREATE`**, **`UPDATE`** and **`DELETE`** any content. Can "ban" any user except people with "admin" Role. Can see deleted content and un-delete it. Cannot _purge_ deleted. This guarantees audit-trail. | 1 | +| `3` | editor | Can **`CREATE`** and **`UPDATE`** _Any_ content. Can **"`DELETE`"** content. Cannot _see_ deleted content. | 1 | +| `4` | creator | Can **`CREATE`** content. Can **`UPDATE`** their _own_ content. Can **`DELETE`** their _own_ content. | 1 | +| `5` | commenter | Can **`COMMENT`** on content that has commenting enabled. | 1 | +| `6` | subscriber | Can **`SUBSCRIBE`** to receive updates (e.g: newsletter), but has either not verified their account or has made negative comments and is therefore _not_ allowed to comment. | 1 | +| `7` | banned | Can login and see their past content. Cannot create any new content. Can see the _reason_ for their banning (_which the Admin has to write when performing the "ban user" action. usually linked to a specific action the person performed like a particularly unacceptable comment._) | 1 | + +The first 3 roles closely matches WordPress: +https://wordpress.org/support/article/roles-and-capabilities +We have renamed "author" to "creator" to emphasize the creative part +and the fact that we will allow for various types of content not just "posts". +We have added a "**commenter** role as an "upgrade" to **subscriber**, +to indicate that the person has the ability to _comment_ on content. +Finally, we have added the concept of a "**banned**" role +that still allows the person to login and view their _own_ content, +but they have no other privileges. + + + + ## Who? Anyone who is interested in developing secure multi-user applications @@ -64,7 +93,10 @@ If you don't already have these schemas/tables, see: https://github.com/dwyl/app-mvp-phoenix#create-schemas -Let's create the Database Schemas (Tables) to store our RBAC data, +### Create `Roles` and `Permissions` Schemas + +Let's create the Database Schemas (Tables) +to store our RBAC data, starting with **`Roles`**: ``` @@ -76,14 +108,20 @@ Next create the permissions schema: mix phx.gen.html Ctx Permission permissions name:string desc:string person_id:references:people ``` -Next create the **`many-to-many`** relationship between roles and permissions. +We placed the roles and permissions resources in an **`auth`** pipeline +because we only want people with **`superadmin`** role to access them. + + +### Create Associations + +Next create the **`many-to-many`** relationship +between roles and permissions. ``` mix ecto.gen.migration create_role_permissions ``` - Now create the **`many-to-many`** relationship between people and roles: ``` @@ -93,6 +131,7 @@ mix ecto.gen.migration create_people_roles + ## Recommended Reading + https://en.wikipedia.org/wiki/Role-based_access_control From 2b7a66d69d83ab640bbc97c57943ca092e49547c Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 23 Jul 2020 15:51:01 +0100 Subject: [PATCH 017/166] mix ecto.gen.migration create_role_permissions #27 /#31 --- ...20200723143204_create_role_permissions.exs | 14 ++++++ role-based-access-control.md | 47 ++++++++++++++----- 2 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 priv/repo/migrations/20200723143204_create_role_permissions.exs diff --git a/priv/repo/migrations/20200723143204_create_role_permissions.exs b/priv/repo/migrations/20200723143204_create_role_permissions.exs new file mode 100644 index 00000000..30e23849 --- /dev/null +++ b/priv/repo/migrations/20200723143204_create_role_permissions.exs @@ -0,0 +1,14 @@ +defmodule Auth.Repo.Migrations.CreateRolePermissions do + use Ecto.Migration + + def change do + create table(:role_permissions) do + add :role_id, references(:roles) + add :permission_id, references(:permissions) + + timestamps() + end + + create unique_index(:role_permissionss, [:role_id, :permission_id]) + end +end diff --git a/role-based-access-control.md b/role-based-access-control.md index a064e4e9..c8b9d50c 100644 --- a/role-based-access-control.md +++ b/role-based-access-control.md @@ -65,9 +65,10 @@ including both "enterprise" (closed source) and popular open source CRM/CMS apps | `7` | banned | Can login and see their past content. Cannot create any new content. Can see the _reason_ for their banning (_which the Admin has to write when performing the "ban user" action. usually linked to a specific action the person performed like a particularly unacceptable comment._) | 1 | The first 3 roles closely matches WordPress: -https://wordpress.org/support/article/roles-and-capabilities -We have renamed "author" to "creator" to emphasize the creative part -and the fact that we will allow for various types of content not just "posts". +https://wordpress.org/support/article/roles-and-capabilities
+We have renamed "author" to "creator" to emphasize that creating content +is more than just "authoring" text. +There will be various types of content not just "posts". We have added a "**commenter** role as an "upgrade" to **subscriber**, to indicate that the person has the ability to _comment_ on content. Finally, we have added the concept of a "**banned**" role @@ -75,11 +76,10 @@ that still allows the person to login and view their _own_ content, but they have no other privileges. - - ## Who? -Anyone who is interested in developing secure multi-user applications +Anyone who is interested in developing and _maintaining_ +secure multi-person applications should learn about RBAC. @@ -108,11 +108,14 @@ Next create the permissions schema: mix phx.gen.html Ctx Permission permissions name:string desc:string person_id:references:people ``` -We placed the roles and permissions resources in an **`auth`** pipeline +We placed the roles and permissions resources in an **`:auth`** pipeline because we only want people with **`superadmin`** role to access them. +See: +[`/lib/auth_web/router.ex#L41-L43`](https://github.com/dwyl/auth/blob/2a3c361e87cbe4fadbd6beda2eef989299c48a53/lib/auth_web/router.ex#L41-L42) -### Create Associations + +### Create Roles<->Permissions Associations Next create the **`many-to-many`** relationship between roles and permissions. @@ -121,8 +124,31 @@ between roles and permissions. mix ecto.gen.migration create_role_permissions ``` +Open the file that was just created, e.g: +[`priv/repo/migrations/20200723143204_create_role_permissions.exs`]() + +And replace the contents with: +```elixir +defmodule Auth.Repo.Migrations.CreateRolePermissions do + use Ecto.Migration + + def change do + create table(:role_permissions) do + add :role_id, references(:roles) + add :permission_id, references(:permissions) + + timestamps() + end + + create unique_index(:role_permissionss, [:role_id, :permission_id]) + end +end +``` + + -Now create the **`many-to-many`** relationship between people and roles: +Now create the **`many-to-many`** relationship +between **`people`** and **`roles`**: ``` mix ecto.gen.migration create_people_roles @@ -130,10 +156,9 @@ mix ecto.gen.migration create_people_roles - - ## Recommended Reading + https://en.wikipedia.org/wiki/Role-based_access_control + https://www.sumologic.com/glossary/role-based-access-control + https://medium.com/@adriennedomingus/role-based-access-control-rbac-permissions-vs-roles-55f1f0051468 ++ https://digitalguardian.com/blog/what-role-based-access-control-rbac-examples-benefits-and-more \ No newline at end of file From b6e4a5fa74b14298de3920ea0ae4832844691328 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 23 Jul 2020 16:55:34 +0100 Subject: [PATCH 018/166] mix ecto.gen.migration create_people_roles for rbac #27 / #31 --- ...20200723143204_create_role_permissions.exs | 6 +-- .../20200723154847_create_people_roles.exs | 14 ++++++ role-based-access-control.md | 46 ++++++++++++++----- 3 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 priv/repo/migrations/20200723154847_create_people_roles.exs diff --git a/priv/repo/migrations/20200723143204_create_role_permissions.exs b/priv/repo/migrations/20200723143204_create_role_permissions.exs index 30e23849..8068670c 100644 --- a/priv/repo/migrations/20200723143204_create_role_permissions.exs +++ b/priv/repo/migrations/20200723143204_create_role_permissions.exs @@ -3,12 +3,12 @@ defmodule Auth.Repo.Migrations.CreateRolePermissions do def change do create table(:role_permissions) do - add :role_id, references(:roles) - add :permission_id, references(:permissions) + add :role_id, references(:roles, on_delete: :nothing) + add :permission_id, references(:permissions, on_delete: :nothing) timestamps() end - create unique_index(:role_permissionss, [:role_id, :permission_id]) + create unique_index(:role_permissions, [:role_id, :permission_id]) end end diff --git a/priv/repo/migrations/20200723154847_create_people_roles.exs b/priv/repo/migrations/20200723154847_create_people_roles.exs new file mode 100644 index 00000000..df56d4a9 --- /dev/null +++ b/priv/repo/migrations/20200723154847_create_people_roles.exs @@ -0,0 +1,14 @@ +defmodule Auth.Repo.Migrations.CreatePeopleRoles do + use Ecto.Migration + + def change do + create table(:people_roles) do + add :person_id, references(:people, on_delete: :nothing) + add :role_id, references(:roles, on_delete: :nothing) + + timestamps() + end + + create unique_index(:people_roles, [:person_id, :role_id]) + end +end diff --git a/role-based-access-control.md b/role-based-access-control.md index c8b9d50c..8ad6000d 100644 --- a/role-based-access-control.md +++ b/role-based-access-control.md @@ -12,6 +12,13 @@ Access Control List (ACL) based permissions systems and helps everyone building and maintaining the app to focus on security. +## _Who_? + +This document is relevant to anyone +that is interested in developing and _maintaining_ +secure multi-person applications +should learn about RBAC. + ## What? @@ -76,13 +83,6 @@ that still allows the person to login and view their _own_ content, but they have no other privileges. -## Who? - -Anyone who is interested in developing and _maintaining_ -secure multi-person applications -should learn about RBAC. - - ## _How_? _Before_ creating any roles, @@ -134,18 +134,18 @@ defmodule Auth.Repo.Migrations.CreateRolePermissions do def change do create table(:role_permissions) do - add :role_id, references(:roles) - add :permission_id, references(:permissions) + add :role_id, references(:roles, on_delete: :nothing) + add :permission_id, references(:permissions, on_delete: :nothing) timestamps() end - create unique_index(:role_permissionss, [:role_id, :permission_id]) + create unique_index(:role_permissions, [:role_id, :permission_id]) end end ``` - +### Create People<->Roles Associations Now create the **`many-to-many`** relationship between **`people`** and **`roles`**: @@ -154,6 +154,30 @@ between **`people`** and **`roles`**: mix ecto.gen.migration create_people_roles ``` +Open the migration file that was just created, e.g: +[`/Users/n/code/auth/priv/repo/migrations/20200723154847_create_people_roles.exs`]() + + +Replace the contents of the file with the following code: + +```elixir +defmodule Auth.Repo.Migrations.CreatePeopleRoles do + use Ecto.Migration + + def change do + create table(:people_roles) do + add :person_id, references(:people, on_delete: :nothing) + add :role_id, references(:roles, on_delete: :nothing) + + timestamps() + end + + create unique_index(:people_roles, [:person_id, :role_id]) + end +end +``` + + ## Recommended Reading From eab02919fe95c337c751f55f3048e72c126bc4f6 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 24 Jul 2020 16:12:52 +0100 Subject: [PATCH 019/166] add "granter" (person who grants a permission/role) to association tables #31 --- .../migrations/20200723143204_create_role_permissions.exs | 1 + priv/repo/migrations/20200723154847_create_people_roles.exs | 1 + role-based-access-control.md | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/priv/repo/migrations/20200723143204_create_role_permissions.exs b/priv/repo/migrations/20200723143204_create_role_permissions.exs index 8068670c..2651ee17 100644 --- a/priv/repo/migrations/20200723143204_create_role_permissions.exs +++ b/priv/repo/migrations/20200723143204_create_role_permissions.exs @@ -5,6 +5,7 @@ defmodule Auth.Repo.Migrations.CreateRolePermissions do create table(:role_permissions) do add :role_id, references(:roles, on_delete: :nothing) add :permission_id, references(:permissions, on_delete: :nothing) + add :granter, references(:people, on_delete: :nothing) timestamps() end diff --git a/priv/repo/migrations/20200723154847_create_people_roles.exs b/priv/repo/migrations/20200723154847_create_people_roles.exs index df56d4a9..8d69b222 100644 --- a/priv/repo/migrations/20200723154847_create_people_roles.exs +++ b/priv/repo/migrations/20200723154847_create_people_roles.exs @@ -5,6 +5,7 @@ defmodule Auth.Repo.Migrations.CreatePeopleRoles do create table(:people_roles) do add :person_id, references(:people, on_delete: :nothing) add :role_id, references(:roles, on_delete: :nothing) + add :granter, references(:people, on_delete: :nothing) timestamps() end diff --git a/role-based-access-control.md b/role-based-access-control.md index 8ad6000d..0e846215 100644 --- a/role-based-access-control.md +++ b/role-based-access-control.md @@ -136,6 +136,7 @@ defmodule Auth.Repo.Migrations.CreateRolePermissions do create table(:role_permissions) do add :role_id, references(:roles, on_delete: :nothing) add :permission_id, references(:permissions, on_delete: :nothing) + add :granter, references(:people, on_delete: :nothing) timestamps() end @@ -168,6 +169,7 @@ defmodule Auth.Repo.Migrations.CreatePeopleRoles do create table(:people_roles) do add :person_id, references(:people, on_delete: :nothing) add :role_id, references(:roles, on_delete: :nothing) + add :granter, references(:people, on_delete: :nothing) timestamps() end @@ -177,6 +179,8 @@ defmodule Auth.Repo.Migrations.CreatePeopleRoles do end ``` +This + From f03c050b798fa942ddc44ce072e52ab8a3db89e6 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 24 Jul 2020 22:59:39 +0100 Subject: [PATCH 020/166] write AUTH_API_KEY to .env file during setup (seeds.exs) fixes #87 --- priv/repo/seeds.exs | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index d2c05421..dabc37ff 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -40,7 +40,6 @@ defmodule Auth.Seeds do %{ "name" => "system admin key", "description" => "Created by /priv/repo/seeds.exs during setup.", - # the default host in %Plug.Conn "url" => "localhost:4000" } |> AuthWeb.ApikeyController.make_apikey(person.id) @@ -48,18 +47,38 @@ defmodule Auth.Seeds do api_key = key.client_id <> "/" <> key.client_secret # set the AUTH_API_KEY during test run: - System.put_env("AUTH_API_KEY", api_key) + write_env("AUTH_API_KEY", api_key) + end - if(Mix.env() == :test) do - # don't print noise during tests - else - IO.puts("Remember to set the AUTH_API_KEY environment variable:") - IO.puts("export AUTH_API_KEY=#{api_key}") - IO.puts("- - - - - - - - - - - - - - - - - - - - - - ") - end - key + # write the key:value pair to project .env file + def write_env(key, value) do + path = File.cwd! <> "/.env" + {:ok, data} = File.read(path) + + lines = String.split(data, "\n") + |> Enum.filter(fn line -> + not String.contains?(line, key) + end) + str = "export #{key}=#{value}" # |> IO.inspect + vars = lines ++ [str] + content = Enum.join(vars, "\n") + File.write!(path, content) |> File.close() + env(vars) + end + + # export all the environment variables during app excution/tests + def env(vars) do + Enum.map(vars, fn line -> + parts = line + |> String.replace("export ", "") + |> String.replace("'", "") + |> String.split("=") + + System.put_env(List.first(parts), List.last(parts)) + end) end end + Auth.Seeds.create_admin() |> Auth.Seeds.create_apikey_for_admin() From b60522a15b76dda172eb3cd4e35527c31c1bc2fd Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 24 Jul 2020 23:08:42 +0100 Subject: [PATCH 021/166] add debug IO.inspect in write_env/2 #87 https://travis-ci.org/github/dwyl/auth/builds/711606595#L391 --- priv/repo/seeds.exs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index dabc37ff..0ba4218a 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -52,7 +52,9 @@ defmodule Auth.Seeds do # write the key:value pair to project .env file def write_env(key, value) do + IO.inspect(File.cwd!) path = File.cwd! <> "/.env" + IO.inspect(path, label: "path") {:ok, data} = File.read(path) lines = String.split(data, "\n") From 1762d43ad2ce4fcc55914dc66aaab13f44c1d706 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 24 Jul 2020 23:12:22 +0100 Subject: [PATCH 022/166] obviously the .env file doesnt exist on Travis-CI #87 https://travis-ci.org/github/dwyl/auth/builds/711608673#L386 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4170a96a..ef74634f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ services: env: - MIX_ENV=test before_script: + - echo "export MIX_ENV=test" > .env - mix ecto.setup script: - mix do deps.get, coveralls.json From 5ec8568edd50c2d3398caa4c6f5e86276eeea687 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 24 Jul 2020 23:35:35 +0100 Subject: [PATCH 023/166] debugging why tests are failing #87 https://travis-ci.org/github/dwyl/auth/builds/711609239#L458 --- priv/repo/seeds.exs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 0ba4218a..d442c433 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -56,6 +56,7 @@ defmodule Auth.Seeds do path = File.cwd! <> "/.env" IO.inspect(path, label: "path") {:ok, data} = File.read(path) + # IO.inspect(data) lines = String.split(data, "\n") |> Enum.filter(fn line -> @@ -76,6 +77,7 @@ defmodule Auth.Seeds do |> String.replace("'", "") |> String.split("=") + IO.inspect(List.last(parts), label: List.first(parts)) System.put_env(List.first(parts), List.last(parts)) end) end From 8f192b12f4efa92872d388d908862c6ebcb678c9 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 25 Jul 2020 11:35:08 +0100 Subject: [PATCH 024/166] move Role related helper functions out of Ctx #86 --- .travis.yml | 1 + lib/auth/ctx.ex | 96 -------------- lib/auth/ctx/role.ex | 19 --- lib/auth/role.ex | 119 ++++++++++++++++++ lib/auth_web/controllers/role_controller.ex | 23 ++-- priv/repo/create_default_roles.exs | 4 + priv/repo/seeds.exs | 4 +- .../controllers/role_controller_test.exs | 4 +- 8 files changed, 139 insertions(+), 131 deletions(-) delete mode 100644 lib/auth/ctx/role.ex create mode 100644 lib/auth/role.ex create mode 100644 priv/repo/create_default_roles.exs diff --git a/.travis.yml b/.travis.yml index ef74634f..48d8f989 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ services: env: - MIX_ENV=test before_script: + # create .env file on Travis-CI: - echo "export MIX_ENV=test" > .env - mix ecto.setup script: diff --git a/lib/auth/ctx.ex b/lib/auth/ctx.ex index d914f3db..e69adb00 100644 --- a/lib/auth/ctx.ex +++ b/lib/auth/ctx.ex @@ -6,102 +6,6 @@ defmodule Auth.Ctx do import Ecto.Query, warn: false alias Auth.Repo - alias Auth.Ctx.Role - - @doc """ - Returns the list of roles. - - ## Examples - - iex> list_roles() - [%Role{}, ...] - - """ - def list_roles do - Repo.all(Role) - end - - @doc """ - Gets a single role. - - Raises `Ecto.NoResultsError` if the Role does not exist. - - ## Examples - - iex> get_role!(123) - %Role{} - - iex> get_role!(456) - ** (Ecto.NoResultsError) - - """ - def get_role!(id), do: Repo.get!(Role, id) - - @doc """ - Creates a role. - - ## Examples - - iex> create_role(%{field: value}) - {:ok, %Role{}} - - iex> create_role(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def create_role(attrs \\ %{}) do - %Role{} - |> Role.changeset(attrs) - |> Repo.insert() - end - - @doc """ - Updates a role. - - ## Examples - - iex> update_role(role, %{field: new_value}) - {:ok, %Role{}} - - iex> update_role(role, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def update_role(%Role{} = role, attrs) do - role - |> Role.changeset(attrs) - |> Repo.update() - end - - @doc """ - Deletes a role. - - ## Examples - - iex> delete_role(role) - {:ok, %Role{}} - - iex> delete_role(role) - {:error, %Ecto.Changeset{}} - - """ - def delete_role(%Role{} = role) do - Repo.delete(role) - end - - @doc """ - Returns an `%Ecto.Changeset{}` for tracking role changes. - - ## Examples - - iex> change_role(role) - %Ecto.Changeset{data: %Role{}} - - """ - def change_role(%Role{} = role, attrs \\ %{}) do - Role.changeset(role, attrs) - end - alias Auth.Ctx.Permission @doc """ diff --git a/lib/auth/ctx/role.ex b/lib/auth/ctx/role.ex deleted file mode 100644 index e6cd02b0..00000000 --- a/lib/auth/ctx/role.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule Auth.Ctx.Role do - use Ecto.Schema - import Ecto.Changeset - - schema "roles" do - field :desc, :string - field :name, :string - field :person_id, :id - - timestamps() - end - - @doc false - def changeset(role, attrs) do - role - |> cast(attrs, [:name, :desc]) - |> validate_required([:name, :desc]) - end -end diff --git a/lib/auth/role.ex b/lib/auth/role.ex new file mode 100644 index 00000000..f2661a2c --- /dev/null +++ b/lib/auth/role.ex @@ -0,0 +1,119 @@ +defmodule Auth.Role do + use Ecto.Schema + import Ecto.Changeset + import Ecto.Query, warn: false + alias Auth.Repo + # https://stackoverflow.com/a/47501059/1148249 + alias __MODULE__ + + schema "roles" do + field :desc, :string + field :name, :string + field :person_id, :id + + timestamps() + end + + @doc false + def changeset(role, attrs) do + role + |> cast(attrs, [:name, :desc]) + |> validate_required([:name, :desc]) + end + + +@doc """ + Returns the list of roles. + + ## Examples + + iex> list_roles() + [%Role{}, ...] + + """ + def list_roles do + Repo.all(__MODULE__) + end + + @doc """ + Gets a single role. + + Raises `Ecto.NoResultsError` if the Role does not exist. + + ## Examples + + iex> get_role!(123) + %Role{} + + iex> get_role!(456) + ** (Ecto.NoResultsError) + + """ + def get_role!(id), do: Repo.get!(__MODULE__, id) + + @doc """ + Creates a role. + + ## Examples + + iex> create_role(%{field: value}) + {:ok, %Role{}} + + iex> create_role(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_role(attrs \\ %{}) do + %Role{} + |> Role.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a role. + + ## Examples + + iex> update_role(role, %{field: new_value}) + {:ok, %Role{}} + + iex> update_role(role, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_role(%Role{} = role, attrs) do + role + |> Role.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a role. + + ## Examples + + iex> delete_role(role) + {:ok, %Role{}} + + iex> delete_role(role) + {:error, %Ecto.Changeset{}} + + """ + def delete_role(%Role{} = role) do + Repo.delete(role) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking role changes. + + ## Examples + + iex> change_role(role) + %Ecto.Changeset{data: %Role{}} + + """ + def change_role(%Role{} = role, attrs \\ %{}) do + Role.changeset(role, attrs) + end + +end diff --git a/lib/auth_web/controllers/role_controller.ex b/lib/auth_web/controllers/role_controller.ex index e25c3394..3ce9ec11 100644 --- a/lib/auth_web/controllers/role_controller.ex +++ b/lib/auth_web/controllers/role_controller.ex @@ -1,21 +1,20 @@ defmodule AuthWeb.RoleController do use AuthWeb, :controller - alias Auth.Ctx - alias Auth.Ctx.Role + alias Auth.Role def index(conn, _params) do - roles = Ctx.list_roles() + roles = Role.list_roles() render(conn, "index.html", roles: roles) end def new(conn, _params) do - changeset = Ctx.change_role(%Role{}) + changeset = Role.change_role(%Role{}) render(conn, "new.html", changeset: changeset) end def create(conn, %{"role" => role_params}) do - case Ctx.create_role(role_params) do + case Role.create_role(role_params) do {:ok, role} -> conn |> put_flash(:info, "Role created successfully.") @@ -27,20 +26,20 @@ defmodule AuthWeb.RoleController do end def show(conn, %{"id" => id}) do - role = Ctx.get_role!(id) + role = Role.get_role!(id) render(conn, "show.html", role: role) end def edit(conn, %{"id" => id}) do - role = Ctx.get_role!(id) - changeset = Ctx.change_role(role) + role = Role.get_role!(id) + changeset = Role.change_role(role) render(conn, "edit.html", role: role, changeset: changeset) end def update(conn, %{"id" => id, "role" => role_params}) do - role = Ctx.get_role!(id) + role = Role.get_role!(id) - case Ctx.update_role(role, role_params) do + case Role.update_role(role, role_params) do {:ok, role} -> conn |> put_flash(:info, "Role updated successfully.") @@ -52,8 +51,8 @@ defmodule AuthWeb.RoleController do end def delete(conn, %{"id" => id}) do - role = Ctx.get_role!(id) - {:ok, _role} = Ctx.delete_role(role) + role = Role.get_role!(id) + {:ok, _role} = Role.delete_role(role) conn |> put_flash(:info, "Role deleted successfully.") diff --git a/priv/repo/create_default_roles.exs b/priv/repo/create_default_roles.exs new file mode 100644 index 00000000..3c4e11cd --- /dev/null +++ b/priv/repo/create_default_roles.exs @@ -0,0 +1,4 @@ +# scripts for creating default roles and permissions +defmodule Setup.CreateDefaultRoles do + +end \ No newline at end of file diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index d442c433..55898e91 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -46,13 +46,13 @@ defmodule Auth.Seeds do |> Auth.Apikey.create_apikey() api_key = key.client_id <> "/" <> key.client_secret - # set the AUTH_API_KEY during test run: + # set the AUTH_API_KEY environment variable during test run: write_env("AUTH_API_KEY", api_key) end # write the key:value pair to project .env file def write_env(key, value) do - IO.inspect(File.cwd!) + IO.inspect(File.cwd!, label: "CWD") path = File.cwd! <> "/.env" IO.inspect(path, label: "path") {:ok, data} = File.read(path) diff --git a/test/auth_web/controllers/role_controller_test.exs b/test/auth_web/controllers/role_controller_test.exs index f18c9b56..f7562279 100644 --- a/test/auth_web/controllers/role_controller_test.exs +++ b/test/auth_web/controllers/role_controller_test.exs @@ -1,14 +1,14 @@ defmodule AuthWeb.RoleControllerTest do use AuthWeb.ConnCase - alias Auth.Ctx + alias Auth.Role @create_attrs %{desc: "some desc", name: "some name"} @update_attrs %{desc: "some updated desc", name: "some updated name"} @invalid_attrs %{desc: nil, name: nil} def fixture(:role) do - {:ok, role} = Ctx.create_role(@create_attrs) + {:ok, role} = Role.create_role(@create_attrs) role end From 73770272256df054d3819901e644b2ea0873ccf5 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 25 Jul 2020 13:10:19 +0100 Subject: [PATCH 025/166] rename auth/ctx_test.exs to auth/roles_test.exs for #87 --- test/auth/{ctx_test.exs => role_test.exs} | 27 ++++++++++++----------- 1 file changed, 14 insertions(+), 13 deletions(-) rename test/auth/{ctx_test.exs => role_test.exs} (83%) diff --git a/test/auth/ctx_test.exs b/test/auth/role_test.exs similarity index 83% rename from test/auth/ctx_test.exs rename to test/auth/role_test.exs index ef89d285..dc7c344c 100644 --- a/test/auth/ctx_test.exs +++ b/test/auth/role_test.exs @@ -1,10 +1,10 @@ -defmodule Auth.CtxTest do +defmodule Auth.RoleTest do use Auth.DataCase alias Auth.Ctx describe "roles" do - alias Auth.Ctx.Role + alias Auth.Role @valid_attrs %{desc: "some desc", name: "some name"} @update_attrs %{desc: "some updated desc", name: "some updated name"} @@ -14,53 +14,54 @@ defmodule Auth.CtxTest do {:ok, role} = attrs |> Enum.into(@valid_attrs) - |> Ctx.create_role() + |> Role.create_role() role end test "list_roles/0 returns all roles" do role = role_fixture() - assert Ctx.list_roles() == [role] + assert Role.list_roles() == [role] end test "get_role!/1 returns the role with given id" do role = role_fixture() - assert Ctx.get_role!(role.id) == role + assert Role.get_role!(role.id) == role end test "create_role/1 with valid data creates a role" do - assert {:ok, %Role{} = role} = Ctx.create_role(@valid_attrs) + assert {:ok, %Role{} = role} = Role.create_role(@valid_attrs) assert role.desc == "some desc" assert role.name == "some name" end test "create_role/1 with invalid data returns error changeset" do - assert {:error, %Ecto.Changeset{}} = Ctx.create_role(@invalid_attrs) + assert {:error, %Ecto.Changeset{}} = Role.create_role(@invalid_attrs) end test "update_role/2 with valid data updates the role" do role = role_fixture() - assert {:ok, %Role{} = role} = Ctx.update_role(role, @update_attrs) + assert {:ok, %Role{} = role} = Role.update_role(role, @update_attrs) assert role.desc == "some updated desc" assert role.name == "some updated name" end test "update_role/2 with invalid data returns error changeset" do role = role_fixture() - assert {:error, %Ecto.Changeset{}} = Ctx.update_role(role, @invalid_attrs) - assert role == Ctx.get_role!(role.id) + assert {:error, %Ecto.Changeset{}} = + Role.update_role(role, @invalid_attrs) + assert role == Role.get_role!(role.id) end test "delete_role/1 deletes the role" do role = role_fixture() - assert {:ok, %Role{}} = Ctx.delete_role(role) - assert_raise Ecto.NoResultsError, fn -> Ctx.get_role!(role.id) end + assert {:ok, %Role{}} = Role.delete_role(role) + assert_raise Ecto.NoResultsError, fn -> Role.get_role!(role.id) end end test "change_role/1 returns a role changeset" do role = role_fixture() - assert %Ecto.Changeset{} = Ctx.change_role(role) + assert %Ecto.Changeset{} = Role.change_role(role) end end From ef4261d09a702c4003cd84f30dabe630b47922d2 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 25 Jul 2020 13:27:48 +0100 Subject: [PATCH 026/166] remove ctx from project #87 --- lib/auth/ctx/permission.ex | 19 ----------- lib/auth/{ctx.ex => permission.ex} | 30 ++++++++++++----- .../controllers/permission_controller.ex | 23 +++++++------ test/auth/role_test.exs | 32 +++++++++++-------- .../permission_controller_test.exs | 4 +-- 5 files changed, 53 insertions(+), 55 deletions(-) delete mode 100644 lib/auth/ctx/permission.ex rename lib/auth/{ctx.ex => permission.ex} (78%) diff --git a/lib/auth/ctx/permission.ex b/lib/auth/ctx/permission.ex deleted file mode 100644 index c321e880..00000000 --- a/lib/auth/ctx/permission.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule Auth.Ctx.Permission do - use Ecto.Schema - import Ecto.Changeset - - schema "permissions" do - field :desc, :string - field :name, :string - field :person_id, :id - - timestamps() - end - - @doc false - def changeset(permission, attrs) do - permission - |> cast(attrs, [:name, :desc]) - |> validate_required([:name, :desc]) - end -end diff --git a/lib/auth/ctx.ex b/lib/auth/permission.ex similarity index 78% rename from lib/auth/ctx.ex rename to lib/auth/permission.ex index e69adb00..6af06870 100644 --- a/lib/auth/ctx.ex +++ b/lib/auth/permission.ex @@ -1,12 +1,25 @@ -defmodule Auth.Ctx do - @moduledoc """ - The Ctx context. - """ - +defmodule Auth.Permission do + use Ecto.Schema + import Ecto.Changeset import Ecto.Query, warn: false alias Auth.Repo + # alias the Struct so we can use it below + alias Auth.Permission + + schema "permissions" do + field :desc, :string + field :name, :string + field :person_id, :id + + timestamps() + end - alias Auth.Ctx.Permission + @doc false + def changeset(permission, attrs) do + permission + |> cast(attrs, [:name, :desc]) + |> validate_required([:name, :desc]) + end @doc """ Returns the list of permissions. @@ -18,7 +31,7 @@ defmodule Auth.Ctx do """ def list_permissions do - Repo.all(Permission) + Repo.all(__MODULE__) end @doc """ @@ -35,7 +48,7 @@ defmodule Auth.Ctx do ** (Ecto.NoResultsError) """ - def get_permission!(id), do: Repo.get!(Permission, id) + def get_permission!(id), do: Repo.get!(__MODULE__, id) @doc """ Creates a permission. @@ -101,4 +114,5 @@ defmodule Auth.Ctx do def change_permission(%Permission{} = permission, attrs \\ %{}) do Permission.changeset(permission, attrs) end + end diff --git a/lib/auth_web/controllers/permission_controller.ex b/lib/auth_web/controllers/permission_controller.ex index 2bc52e54..5b10f276 100644 --- a/lib/auth_web/controllers/permission_controller.ex +++ b/lib/auth_web/controllers/permission_controller.ex @@ -1,21 +1,20 @@ defmodule AuthWeb.PermissionController do use AuthWeb, :controller - alias Auth.Ctx - alias Auth.Ctx.Permission + alias Auth.Permission def index(conn, _params) do - permissions = Ctx.list_permissions() + permissions = Permission.list_permissions() render(conn, "index.html", permissions: permissions) end def new(conn, _params) do - changeset = Ctx.change_permission(%Permission{}) + changeset = Permission.change_permission(%Permission{}) render(conn, "new.html", changeset: changeset) end def create(conn, %{"permission" => permission_params}) do - case Ctx.create_permission(permission_params) do + case Permission.create_permission(permission_params) do {:ok, permission} -> conn |> put_flash(:info, "Permission created successfully.") @@ -27,20 +26,20 @@ defmodule AuthWeb.PermissionController do end def show(conn, %{"id" => id}) do - permission = Ctx.get_permission!(id) + permission = Permission.get_permission!(id) render(conn, "show.html", permission: permission) end def edit(conn, %{"id" => id}) do - permission = Ctx.get_permission!(id) - changeset = Ctx.change_permission(permission) + permission = Permission.get_permission!(id) + changeset = Permission.change_permission(permission) render(conn, "edit.html", permission: permission, changeset: changeset) end def update(conn, %{"id" => id, "permission" => permission_params}) do - permission = Ctx.get_permission!(id) + permission = Permission.get_permission!(id) - case Ctx.update_permission(permission, permission_params) do + case Permission.update_permission(permission, permission_params) do {:ok, permission} -> conn |> put_flash(:info, "Permission updated successfully.") @@ -52,8 +51,8 @@ defmodule AuthWeb.PermissionController do end def delete(conn, %{"id" => id}) do - permission = Ctx.get_permission!(id) - {:ok, _permission} = Ctx.delete_permission(permission) + permission = Permission.get_permission!(id) + {:ok, _permission} = Permission.delete_permission(permission) conn |> put_flash(:info, "Permission deleted successfully.") diff --git a/test/auth/role_test.exs b/test/auth/role_test.exs index dc7c344c..54b7fca4 100644 --- a/test/auth/role_test.exs +++ b/test/auth/role_test.exs @@ -1,8 +1,6 @@ defmodule Auth.RoleTest do use Auth.DataCase - alias Auth.Ctx - describe "roles" do alias Auth.Role @@ -66,7 +64,7 @@ defmodule Auth.RoleTest do end describe "permissions" do - alias Auth.Ctx.Permission + alias Auth.Permission @valid_attrs %{desc: "some desc", name: "some name"} @update_attrs %{desc: "some updated desc", name: "some updated name"} @@ -76,53 +74,59 @@ defmodule Auth.RoleTest do {:ok, permission} = attrs |> Enum.into(@valid_attrs) - |> Ctx.create_permission() + |> Permission.create_permission() permission end test "list_permissions/0 returns all permissions" do permission = permission_fixture() - assert Ctx.list_permissions() == [permission] + assert Permission.list_permissions() == [permission] end test "get_permission!/1 returns the permission with given id" do permission = permission_fixture() - assert Ctx.get_permission!(permission.id) == permission + assert Permission.get_permission!(permission.id) == permission end test "create_permission/1 with valid data creates a permission" do - assert {:ok, %Permission{} = permission} = Ctx.create_permission(@valid_attrs) + assert {:ok, %Permission{} = permission} = + Permission.create_permission(@valid_attrs) assert permission.desc == "some desc" assert permission.name == "some name" end test "create_permission/1 with invalid data returns error changeset" do - assert {:error, %Ecto.Changeset{}} = Ctx.create_permission(@invalid_attrs) + assert {:error, %Ecto.Changeset{}} = + Permission.create_permission(@invalid_attrs) end test "update_permission/2 with valid data updates the permission" do permission = permission_fixture() - assert {:ok, %Permission{} = permission} = Ctx.update_permission(permission, @update_attrs) + assert {:ok, %Permission{} = permission} = + Permission.update_permission(permission, @update_attrs) assert permission.desc == "some updated desc" assert permission.name == "some updated name" end test "update_permission/2 with invalid data returns error changeset" do permission = permission_fixture() - assert {:error, %Ecto.Changeset{}} = Ctx.update_permission(permission, @invalid_attrs) - assert permission == Ctx.get_permission!(permission.id) + assert {:error, %Ecto.Changeset{}} = + Permission.update_permission(permission, @invalid_attrs) + assert permission == Permission.get_permission!(permission.id) end test "delete_permission/1 deletes the permission" do permission = permission_fixture() - assert {:ok, %Permission{}} = Ctx.delete_permission(permission) - assert_raise Ecto.NoResultsError, fn -> Ctx.get_permission!(permission.id) end + assert {:ok, %Permission{}} = Permission.delete_permission(permission) + assert_raise Ecto.NoResultsError, fn -> + Permission.get_permission!(permission.id) + end end test "change_permission/1 returns a permission changeset" do permission = permission_fixture() - assert %Ecto.Changeset{} = Ctx.change_permission(permission) + assert %Ecto.Changeset{} = Permission.change_permission(permission) end end end diff --git a/test/auth_web/controllers/permission_controller_test.exs b/test/auth_web/controllers/permission_controller_test.exs index 47e00bb9..2971e785 100644 --- a/test/auth_web/controllers/permission_controller_test.exs +++ b/test/auth_web/controllers/permission_controller_test.exs @@ -1,14 +1,14 @@ defmodule AuthWeb.PermissionControllerTest do use AuthWeb.ConnCase - alias Auth.Ctx + alias Auth.Permission @create_attrs %{desc: "some desc", name: "some name"} @update_attrs %{desc: "some updated desc", name: "some updated name"} @invalid_attrs %{desc: nil, name: nil} def fixture(:permission) do - {:ok, permission} = Ctx.create_permission(@create_attrs) + {:ok, permission} = Permission.create_permission(@create_attrs) permission end From 723ae8337388e174faa13bf5d95e438c04877a95 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 25 Jul 2020 15:53:54 +0100 Subject: [PATCH 027/166] create script and default_roles.json (version controllable data file) for #86 --- lib/auth/role.ex | 2 +- priv/repo/create_default_roles.exs | 4 ---- priv/repo/default_roles.json | 12 +++++++++++ priv/repo/seeds.exs | 32 ++++++++++++++++++++++++++---- role-based-access-control.md | 26 +++++++++++++++++++++--- 5 files changed, 64 insertions(+), 12 deletions(-) delete mode 100644 priv/repo/create_default_roles.exs create mode 100644 priv/repo/default_roles.json diff --git a/lib/auth/role.ex b/lib/auth/role.ex index f2661a2c..8508c657 100644 --- a/lib/auth/role.ex +++ b/lib/auth/role.ex @@ -17,7 +17,7 @@ defmodule Auth.Role do @doc false def changeset(role, attrs) do role - |> cast(attrs, [:name, :desc]) + |> cast(attrs, [:name, :desc, :person_id]) |> validate_required([:name, :desc]) end diff --git a/priv/repo/create_default_roles.exs b/priv/repo/create_default_roles.exs deleted file mode 100644 index 3c4e11cd..00000000 --- a/priv/repo/create_default_roles.exs +++ /dev/null @@ -1,4 +0,0 @@ -# scripts for creating default roles and permissions -defmodule Setup.CreateDefaultRoles do - -end \ No newline at end of file diff --git a/priv/repo/default_roles.json b/priv/repo/default_roles.json new file mode 100644 index 00000000..1216f98e --- /dev/null +++ b/priv/repo/default_roles.json @@ -0,0 +1,12 @@ +[ + { + "name": "superadmin", + "desc": "With great power comes great responsibility", + "person_id": "1" + }, + { + "name": "admin", + "desc": "Can perform all system administration tasks", + "person_id": "1" + } +] \ No newline at end of file diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 55898e91..c71744b0 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -52,9 +52,9 @@ defmodule Auth.Seeds do # write the key:value pair to project .env file def write_env(key, value) do - IO.inspect(File.cwd!, label: "CWD") + # IO.inspect(File.cwd!, label: "cwd") path = File.cwd! <> "/.env" - IO.inspect(path, label: "path") + IO.inspect(path, label: ".env file path") {:ok, data} = File.read(path) # IO.inspect(data) @@ -77,12 +77,36 @@ defmodule Auth.Seeds do |> String.replace("'", "") |> String.split("=") - IO.inspect(List.last(parts), label: List.first(parts)) + # IO.inspect(List.last(parts), label: List.first(parts)) System.put_env(List.first(parts), List.last(parts)) end) end end - Auth.Seeds.create_admin() |> Auth.Seeds.create_apikey_for_admin() + + +# scripts for creating default roles and permissions +defmodule SetupRoles do + alias Auth.Role + + def get_json(filepath) do + # IO.inspect(filepath, label: "filepath") + path = File.cwd! <> filepath + # IO.inspect(path, label: "path") + {:ok, data} = File.read(path) + json = Jason.decode!(data) + # IO.inspect(json) + json + end + + def create_default_roles() do + json = get_json("/priv/repo/default_roles.json") + Enum.each(json, fn role -> + Role.create_role(role) + end) + end +end + +SetupRoles.create_default_roles() \ No newline at end of file diff --git a/role-based-access-control.md b/role-based-access-control.md index 0e846215..e883efcf 100644 --- a/role-based-access-control.md +++ b/role-based-access-control.md @@ -93,6 +93,7 @@ If you don't already have these schemas/tables, see: https://github.com/dwyl/app-mvp-phoenix#create-schemas + ### Create `Roles` and `Permissions` Schemas Let's create the Database Schemas (Tables) @@ -125,7 +126,7 @@ mix ecto.gen.migration create_role_permissions ``` Open the file that was just created, e.g: -[`priv/repo/migrations/20200723143204_create_role_permissions.exs`]() +[`priv/repo/migrations/20200723143204_create_role_permissions.exs`](https://github.com/dwyl/auth/blob/ef4261d09a702c4003cd84f30dabe630b47922d2/priv/repo/migrations/20200723143204_create_role_permissions.exs) And replace the contents with: ```elixir @@ -156,7 +157,7 @@ mix ecto.gen.migration create_people_roles ``` Open the migration file that was just created, e.g: -[`/Users/n/code/auth/priv/repo/migrations/20200723154847_create_people_roles.exs`]() +[`/Users/n/code/auth/priv/repo/migrations/20200723154847_create_people_roles.exs`](https://github.com/dwyl/auth/blob/ef4261d09a702c4003cd84f30dabe630b47922d2/priv/repo/migrations/20200723154847_create_people_roles.exs) Replace the contents of the file with the following code: @@ -179,7 +180,26 @@ defmodule Auth.Repo.Migrations.CreatePeopleRoles do end ``` -This +This is all we need in terms of database tables for now. +Run: +``` +mix ecto.migrate +``` +To create the tables. + +The Entity Relationship Diagram (ERD) should now look like this: + +[![auth-erd-with-roles-permissions](https://user-images.githubusercontent.com/194400/88439166-5c2e0e00-ce02-11ea-93ce-11c3a721cb18.png "Schema Diagram - Click to Enlarge")](https://user-images.githubusercontent.com/194400/88439166-5c2e0e00-ce02-11ea-93ce-11c3a721cb18.png) + +Next we need to create a script +that inserts the default roles and permissions +during the setup of the Auth App. + +### Setup Default Roles & Permissions + + + + From 7a8d967000174f813e4f978ba6d57f89342affe1 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 25 Jul 2020 19:16:38 +0100 Subject: [PATCH 028/166] add all default roles to default_roles.json #86 --- priv/repo/default_roles.json | 25 +++++++++++++++++++++++++ role-based-access-control.md | 21 ++++++++++++++++++--- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/priv/repo/default_roles.json b/priv/repo/default_roles.json index 1216f98e..71d36bd4 100644 --- a/priv/repo/default_roles.json +++ b/priv/repo/default_roles.json @@ -8,5 +8,30 @@ "name": "admin", "desc": "Can perform all system administration tasks", "person_id": "1" + }, + { + "name": "moderator", + "desc": "Can view and neutrally moderate any content. Can ban rule-breakers. Cannot delete.", + "person_id": "1" + }, + { + "name": "creator", + "desc": "Can create any content. Can edit and delete their own content.", + "person_id": "1" + }, + { + "name": "commenter", + "desc": "Can comment on content where commenting is available.", + "person_id": "1" + }, + { + "name": "subscriber", + "desc": "Subscribes for updates e.g. newsletter or content from a specific person. Cannot comment until verified.", + "person_id": "1" + }, + { + "name": "banned", + "desc": "Can still login to see their content but cannot perform any other action.", + "person_id": "1" } ] \ No newline at end of file diff --git a/role-based-access-control.md b/role-based-access-control.md index e883efcf..9772c77a 100644 --- a/role-based-access-control.md +++ b/role-based-access-control.md @@ -65,15 +65,30 @@ including both "enterprise" (closed source) and popular open source CRM/CMS apps | -------- | ---------- | ---------- | ----------- | | `1` | superadmin | Can **`CREATE`** new roles. Can **`CREATE`**, **`UPDATE`** and **`DELETE`** Any content. Can **`PURGE`** deleted items. Can "ban" any user including people with "Admin" Role. | 1 | | `2` | admin | Can **create** new roles and **assign** existing roles. Can **`CREATE`**, **`UPDATE`** and **`DELETE`** any content. Can "ban" any user except people with "admin" Role. Can see deleted content and un-delete it. Cannot _purge_ deleted. This guarantees audit-trail. | 1 | -| `3` | editor | Can **`CREATE`** and **`UPDATE`** _Any_ content. Can **"`DELETE`"** content. Cannot _see_ deleted content. | 1 | +| `3` | moderator | Can neutrally moderate _any_ content. Can _ban_ rule-breaking `people`. Cannot **"`DELETE`"** content. | 1 | | `4` | creator | Can **`CREATE`** content. Can **`UPDATE`** their _own_ content. Can **`DELETE`** their _own_ content. | 1 | | `5` | commenter | Can **`COMMENT`** on content that has commenting enabled. | 1 | | `6` | subscriber | Can **`SUBSCRIBE`** to receive updates (e.g: newsletter), but has either not verified their account or has made negative comments and is therefore _not_ allowed to comment. | 1 | | `7` | banned | Can login and see their past content. Cannot create any new content. Can see the _reason_ for their banning (_which the Admin has to write when performing the "ban user" action. usually linked to a specific action the person performed like a particularly unacceptable comment._) | 1 | -The first 3 roles closely matches WordPress: +These roles are loosely inspired by WordPress: https://wordpress.org/support/article/roles-and-capabilities
-We have renamed "author" to "creator" to emphasize that creating content +The **`superadmin`** and **`admin`** roles make sense: +The person who can perform "system administration" tasks like updating the schemas/code, +this is the **`superadmin`** or "**owner**" of the application. +Typically there is only ***one*** **`superadmin`**, +this person is ultimately responsible for _everything_. +The people who are responsible for _maintaining_ the site/app +including the `content` and `people` +are given the **`admin`** role; +they can see everything that is going on. + +The **`admin`** role should +We have renamed "editor" to "moderator" +because we feel this role is more _relevant_ in a multi-content setting +see: https://en.wikipedia.org/wiki/Moderator +We have also renamed "author" to "creator" +to emphasize that creating content is more than just "authoring" text. There will be various types of content not just "posts". We have added a "**commenter** role as an "upgrade" to **subscriber**, From 3c1c7204aa84148846ec2ea8716f1cabf20846e0 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 25 Jul 2020 21:13:02 +0100 Subject: [PATCH 029/166] update Role.list_roles() to reflect that we have more roles (default roles) #86 --- test/auth/role_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/auth/role_test.exs b/test/auth/role_test.exs index 54b7fca4..6764f9db 100644 --- a/test/auth/role_test.exs +++ b/test/auth/role_test.exs @@ -19,7 +19,7 @@ defmodule Auth.RoleTest do test "list_roles/0 returns all roles" do role = role_fixture() - assert Role.list_roles() == [role] + assert Role.list_roles() |> List.last() == role end test "get_role!/1 returns the role with given id" do From 510489c581fd621ec45d4af1d20336556689694c Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sun, 26 Jul 2020 09:46:44 +0100 Subject: [PATCH 030/166] add basic permissions to default_roles.json #86 --- priv/repo/default_roles.json | 28 +++++++++++++++++++++------- priv/repo/seeds.exs | 2 ++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/priv/repo/default_roles.json b/priv/repo/default_roles.json index 71d36bd4..e5f3b8ad 100644 --- a/priv/repo/default_roles.json +++ b/priv/repo/default_roles.json @@ -2,36 +2,50 @@ { "name": "superadmin", "desc": "With great power comes great responsibility", - "person_id": "1" + "person_id": "1", + "id": "1", + "permissions": "grant_admin_role" }, { "name": "admin", "desc": "Can perform all system administration tasks", - "person_id": "1" + "person_id": "1", + "id": "2", + "permissions": "manage_people, grant_non_admin_role" }, { "name": "moderator", "desc": "Can view and neutrally moderate any content. Can ban rule-breakers. Cannot delete.", - "person_id": "1" + "person_id": "1", + "id": "3", + "permissions": "edit_any_content, lock_content, ban_rule_breaking_people, view_deleted" }, { "name": "creator", "desc": "Can create any content. Can edit and delete their own content.", - "person_id": "1" + "person_id": "1", + "id": "4", + "permissions": "create_content, edit_own_content, delete_own_content" }, { "name": "commenter", "desc": "Can comment on content where commenting is available.", - "person_id": "1" + "person_id": "1", + "id": "5", + "permissions": "comment" }, { "name": "subscriber", "desc": "Subscribes for updates e.g. newsletter or content from a specific person. Cannot comment until verified.", - "person_id": "1" + "person_id": "1", + "id": "6", + "permissions": "subscribe, give_feedback" }, { "name": "banned", "desc": "Can still login to see their content but cannot perform any other action.", - "person_id": "1" + "person_id": "1", + "id": "7", + "permissions": "view_content, view_profile, delete_own_content, delete_own_profile" } ] \ No newline at end of file diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index c71744b0..72259d8d 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -107,6 +107,8 @@ defmodule SetupRoles do Role.create_role(role) end) end + + end SetupRoles.create_default_roles() \ No newline at end of file From c1f9d3b0e102c04d1e82b509a9550f237232bbc1 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Mon, 27 Jul 2020 15:31:13 +0100 Subject: [PATCH 031/166] refine basic permissions in default_roles.json #86 --- priv/repo/default_roles.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/priv/repo/default_roles.json b/priv/repo/default_roles.json index e5f3b8ad..0bbcadae 100644 --- a/priv/repo/default_roles.json +++ b/priv/repo/default_roles.json @@ -18,21 +18,21 @@ "desc": "Can view and neutrally moderate any content. Can ban rule-breakers. Cannot delete.", "person_id": "1", "id": "3", - "permissions": "edit_any_content, lock_content, ban_rule_breaking_people, view_deleted" + "permissions": "edit_any_content, lock_content, unpublish_content, ban_rule_breaking_people, view_deleted" }, { "name": "creator", "desc": "Can create any content. Can edit and delete their own content.", "person_id": "1", "id": "4", - "permissions": "create_content, edit_own_content, delete_own_content" + "permissions": "create_content, upload_images, edit_own_content, delete_own_content, invite_people" }, { "name": "commenter", "desc": "Can comment on content where commenting is available.", "person_id": "1", "id": "5", - "permissions": "comment" + "permissions": "comment, flag_comments, flag_content" }, { "name": "subscriber", From b27a21abca63499173636e2528feacf7ee979b02 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 14 Aug 2020 09:20:16 +0100 Subject: [PATCH 032/166] fix failing tests by adding ecto associations to Auth.Person functions #89 --- lib/auth/person.ex | 6 +++++- lib/auth/role.ex | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/auth/person.ex b/lib/auth/person.ex index 04878b54..4db327f8 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -20,6 +20,7 @@ defmodule Auth.Person do field :status, :id field :tag, :id field :key_id, :integer + many_to_many :roles, Auth.Role, join_through: "people_roles" has_many :statuses, Auth.Status # has_many :sessions, Auth.Session, on_delete: :delete_all @@ -29,7 +30,7 @@ defmodule Auth.Person do @doc """ Default attributes validation for Person """ - def changeset(person, attrs) do + def changeset(person, attrs, roles \\ []) do person |> cast(attrs, [ :id, @@ -49,6 +50,7 @@ defmodule Auth.Person do |> validate_required([:email]) |> put_email_hash() |> put_pass_hash() + |> put_assoc(:roles, roles) end def create_person(person) do @@ -196,6 +198,7 @@ defmodule Auth.Person do def get_person_by_id(id) do __MODULE__ |> Repo.get_by(id: id) + |> Repo.preload(:roles) end defp put_pass_hash(changeset) do @@ -214,6 +217,7 @@ defmodule Auth.Person do def get_person_by_email(email) do __MODULE__ |> Repo.get_by(email_hash: email) + |> Repo.preload(:roles) end @doc """ diff --git a/lib/auth/role.ex b/lib/auth/role.ex index 8508c657..6c564c99 100644 --- a/lib/auth/role.ex +++ b/lib/auth/role.ex @@ -10,6 +10,7 @@ defmodule Auth.Role do field :desc, :string field :name, :string field :person_id, :id + many_to_many :people, Auth.Person, join_through: "people_roles" timestamps() end @@ -116,4 +117,19 @@ defmodule Auth.Role do Role.changeset(role, attrs) end + + @doc """ + grants the default "subscriber" (6) role to the person + """ + # def set_default_role(person) do + + # end + + @doc """ + grants a role to the given person + """ + # def grant_role(person, role, granter) do + + # end + end From 259e23954dc4c0eb84f159c5e78924c01579171c Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 20 Aug 2020 10:53:13 +0100 Subject: [PATCH 033/166] create lib/auth/people_roles.ex Auth.PeopleRoles schema + functions for #90 --- lib/auth/people_roles.ex | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 lib/auth/people_roles.ex diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex new file mode 100644 index 00000000..49ee05cc --- /dev/null +++ b/lib/auth/people_roles.ex @@ -0,0 +1,39 @@ +defmodule Auth.PeopleRoles do + use Ecto.Schema + import Ecto.Changeset + alias Auth.Repo + alias __MODULE__ + + schema "people_roles" do + belongs_to :person, Auth.Person + belongs_to :role, Auth.Role + field :granter_id, :integer + + timestamps() + end + + + @doc """ + grant_role/3 grants a role to the given person + the conn must have conn.assigns.person to check for admin in order to grant the role. + """ + def grant_role(conn, grantee_id, role_id) do + granter = conn.assigns.person + # IO.inspect(granter, label: "granter") + # confirm that the granter is either superadmin (conn.assigns.person.id == 1) + # or has an "admin" role (1 || 2) + if granter.id == 1 do + %PeopleRoles{} + |> cast(%{granter_id: granter.id}, [:granter_id]) + |> put_assoc(:person, Auth.Person.get_person_by_id(grantee_id)) + |> put_assoc(:role, Auth.Role.get_role!(role_id)) + |> Repo.insert() + + conn + else + AuthWeb.AuthController.unauthorized(conn) + end + + end + +end \ No newline at end of file From 506d365c490ac25d855059fe12d9ebaec4feb9a2 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 20 Aug 2020 11:11:24 +0100 Subject: [PATCH 034/166] add grant_role/3 function + test to grant roles to people #90 |> #27 #31 #82 --- lib/auth/people_roles.ex | 1 + lib/auth/person.ex | 9 ++++++++- lib/auth/role.ex | 15 ++++----------- .../20200723143204_create_role_permissions.exs | 2 +- .../20200723154847_create_people_roles.exs | 2 +- priv/repo/seeds.exs | 13 ++++++++++++- test/auth/role_test.exs | 17 +++++++++++++++++ .../controllers/role_controller_test.exs | 14 ++++++++++++++ 8 files changed, 58 insertions(+), 15 deletions(-) diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex index 49ee05cc..bffd49d4 100644 --- a/lib/auth/people_roles.ex +++ b/lib/auth/people_roles.ex @@ -2,6 +2,7 @@ defmodule Auth.PeopleRoles do use Ecto.Schema import Ecto.Changeset alias Auth.Repo + # https://stackoverflow.com/a/47501059/1148249 alias __MODULE__ schema "people_roles" do diff --git a/lib/auth/person.ex b/lib/auth/person.ex index 4db327f8..b4dedcdd 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -20,7 +20,9 @@ defmodule Auth.Person do field :status, :id field :tag, :id field :key_id, :integer - many_to_many :roles, Auth.Role, join_through: "people_roles" + # many_to_many :roles, Auth.Role, join_through: "people_roles" + # has_many :roles, through: [:people_roles, :role] + many_to_many :roles, Auth.Role, join_through: Auth.PeopleRoles has_many :statuses, Auth.Status # has_many :sessions, Auth.Session, on_delete: :delete_all @@ -31,6 +33,9 @@ defmodule Auth.Person do Default attributes validation for Person """ def changeset(person, attrs, roles \\ []) do + # IO.inspect(person, label: "changeset > person") + # IO.inspect(attrs, label: "changeset > attrs") + # IO.inspect(roles, label: "changeset > roles") person |> cast(attrs, [ :id, @@ -199,6 +204,7 @@ defmodule Auth.Person do __MODULE__ |> Repo.get_by(id: id) |> Repo.preload(:roles) + |> Repo.preload(:statuses) end defp put_pass_hash(changeset) do @@ -218,6 +224,7 @@ defmodule Auth.Person do __MODULE__ |> Repo.get_by(email_hash: email) |> Repo.preload(:roles) + |> Repo.preload(:statuses) end @doc """ diff --git a/lib/auth/role.ex b/lib/auth/role.ex index 6c564c99..fbac1f8c 100644 --- a/lib/auth/role.ex +++ b/lib/auth/role.ex @@ -10,7 +10,7 @@ defmodule Auth.Role do field :desc, :string field :name, :string field :person_id, :id - many_to_many :people, Auth.Person, join_through: "people_roles" + # many_to_many :roles, Auth.Role, join_through: Auth.PeopleRoles timestamps() end @@ -118,18 +118,11 @@ defmodule Auth.Role do end - @doc """ - grants the default "subscriber" (6) role to the person - """ + # @doc """ + # grants the default "subscriber" (6) role to the person + # """ # def set_default_role(person) do # end - @doc """ - grants a role to the given person - """ - # def grant_role(person, role, granter) do - - # end - end diff --git a/priv/repo/migrations/20200723143204_create_role_permissions.exs b/priv/repo/migrations/20200723143204_create_role_permissions.exs index 2651ee17..e607b969 100644 --- a/priv/repo/migrations/20200723143204_create_role_permissions.exs +++ b/priv/repo/migrations/20200723143204_create_role_permissions.exs @@ -5,7 +5,7 @@ defmodule Auth.Repo.Migrations.CreateRolePermissions do create table(:role_permissions) do add :role_id, references(:roles, on_delete: :nothing) add :permission_id, references(:permissions, on_delete: :nothing) - add :granter, references(:people, on_delete: :nothing) + add :granter_id, references(:people, on_delete: :nothing) timestamps() end diff --git a/priv/repo/migrations/20200723154847_create_people_roles.exs b/priv/repo/migrations/20200723154847_create_people_roles.exs index 8d69b222..f5938f41 100644 --- a/priv/repo/migrations/20200723154847_create_people_roles.exs +++ b/priv/repo/migrations/20200723154847_create_people_roles.exs @@ -5,7 +5,7 @@ defmodule Auth.Repo.Migrations.CreatePeopleRoles do create table(:people_roles) do add :person_id, references(:people, on_delete: :nothing) add :role_id, references(:roles, on_delete: :nothing) - add :granter, references(:people, on_delete: :nothing) + add :granter_id, references(:people, on_delete: :nothing) timestamps() end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 72259d8d..fd72ccf0 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -32,6 +32,7 @@ defmodule Auth.Seeds do IO.inspect(person.id, label: "seeds.exs person.id") IO.puts("- - - - - - - - - - - - - - - - - - - - - - ") end + person end @@ -47,7 +48,12 @@ defmodule Auth.Seeds do api_key = key.client_id <> "/" <> key.client_secret # set the AUTH_API_KEY environment variable during test run: - write_env("AUTH_API_KEY", api_key) + if(Mix.env() == :test) do + System.put_env("AUTH_API_KEY", api_key) + else + # update the AUTH_API_KEY in the .env file: + write_env("AUTH_API_KEY", api_key) + end end # write the key:value pair to project .env file @@ -105,9 +111,14 @@ defmodule SetupRoles do json = get_json("/priv/repo/default_roles.json") Enum.each(json, fn role -> Role.create_role(role) + # |> IO.inspect() end) end + def assign_superadmin_role() do + + end + end diff --git a/test/auth/role_test.exs b/test/auth/role_test.exs index 6764f9db..9a425a3d 100644 --- a/test/auth/role_test.exs +++ b/test/auth/role_test.exs @@ -1,5 +1,6 @@ defmodule Auth.RoleTest do use Auth.DataCase + # use AuthWeb.ConnCase describe "roles" do alias Auth.Role @@ -129,4 +130,20 @@ defmodule Auth.RoleTest do assert %Ecto.Changeset{} = Permission.change_permission(permission) end end + + + # create a new person and confirm they were asigned a default role of "subscriber" + + + + # describe "grant role" do + + + + + # # test "change_permission/1 returns a permission changeset" do + # # permission = permission_fixture() + # # assert %Ecto.Changeset{} = Permission.change_permission(permission) + # # end + # end end diff --git a/test/auth_web/controllers/role_controller_test.exs b/test/auth_web/controllers/role_controller_test.exs index f7562279..35fe8147 100644 --- a/test/auth_web/controllers/role_controller_test.exs +++ b/test/auth_web/controllers/role_controller_test.exs @@ -91,4 +91,18 @@ defmodule AuthWeb.RoleControllerTest do role = fixture(:role) %{role: role} end + + + test "grant_role/3 happy path", %{conn: conn} do + # login as superadmin + conn = AuthTest.admin_login(conn) + # create a new person + alex = %{email: "alex_grant_role@gmail.com", auth_provider: "email"} + grantee = Auth.Person.create_person(alex) + role_id = 4 + Auth.PeopleRoles.grant_role(conn, grantee.id, role_id) + person_with_role = Auth.Person.get_person_by_id(grantee.id) + role = List.first(person_with_role.roles) + assert role_id == role.id + end end From 1d6a2592b884671bb3583345de1d6d5560825486 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 20 Aug 2020 11:47:20 +0100 Subject: [PATCH 035/166] create test/auth/people_roles_test.exs to test both happy path and 401 for #90 & #27 #31 #82 --- test/auth/people_roles_test.exs | 27 +++++++++++++++++++ .../controllers/role_controller_test.exs | 15 +---------- 2 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 test/auth/people_roles_test.exs diff --git a/test/auth/people_roles_test.exs b/test/auth/people_roles_test.exs new file mode 100644 index 00000000..b47b713d --- /dev/null +++ b/test/auth/people_roles_test.exs @@ -0,0 +1,27 @@ +defmodule AuthWeb.PeopleRolesTest do + use AuthWeb.ConnCase + + test "grant_role/3 happy path", %{conn: conn} do + # login as superadmin + conn = AuthTest.admin_login(conn) + # create a new person + alex = %{email: "alex_grant_role@gmail.com", auth_provider: "email"} + grantee = Auth.Person.create_person(alex) + role_id = 4 + Auth.PeopleRoles.grant_role(conn, grantee.id, role_id) + person_with_role = Auth.Person.get_person_by_id(grantee.id) + role = List.first(person_with_role.roles) + assert role_id == role.id + end + + test "attempt to grant_role/3 without admin should 401", %{conn: conn} do + alex = %{email: "alex_grant_role_fail@gmail.com", auth_provider: "email"} + grantee = Auth.Person.create_person(alex) + conn = assign(conn, :person, grantee) # + role_id = 4 + conn = Auth.PeopleRoles.grant_role(conn, grantee.id, role_id) + + assert conn.status == 401 + end + +end \ No newline at end of file diff --git a/test/auth_web/controllers/role_controller_test.exs b/test/auth_web/controllers/role_controller_test.exs index 35fe8147..c6ad8b30 100644 --- a/test/auth_web/controllers/role_controller_test.exs +++ b/test/auth_web/controllers/role_controller_test.exs @@ -91,18 +91,5 @@ defmodule AuthWeb.RoleControllerTest do role = fixture(:role) %{role: role} end - - - test "grant_role/3 happy path", %{conn: conn} do - # login as superadmin - conn = AuthTest.admin_login(conn) - # create a new person - alex = %{email: "alex_grant_role@gmail.com", auth_provider: "email"} - grantee = Auth.Person.create_person(alex) - role_id = 4 - Auth.PeopleRoles.grant_role(conn, grantee.id, role_id) - person_with_role = Auth.Person.get_person_by_id(grantee.id) - role = List.first(person_with_role.roles) - assert role_id == role.id - end + end From 812e1926e83a82eed50a3e639d8815d7cbeca20d Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 20 Aug 2020 11:49:00 +0100 Subject: [PATCH 036/166] run "mix format" to keep SourceLevel happy https://github.com/dwyl/auth/pull/85#issuecomment-677507944 --- lib/auth/people_roles.ex | 5 +-- lib/auth/permission.ex | 1 - lib/auth/role.ex | 7 +--- lib/auth_web/controllers/auth_controller.ex | 2 +- lib/auth_web/controllers/ping_controller.ex | 2 +- mix.exs | 2 +- ...20200723143204_create_role_permissions.exs | 4 +- .../20200723154847_create_people_roles.exs | 4 +- priv/repo/seeds.exs | 38 ++++++++++--------- test/auth/people_roles_test.exs | 5 +-- test/auth/role_test.exs | 32 +++++++--------- .../controllers/apikey_controller_test.exs | 17 +++++---- .../controllers/auth_controller_test.exs | 30 ++++++++------- .../permission_controller_test.exs | 37 +++++++++++------- .../controllers/ping_controller_test.exs | 2 +- .../controllers/role_controller_test.exs | 37 +++++++++++------- test/support/conn_case.ex | 8 ++-- test/test_helper.exs | 3 +- 18 files changed, 128 insertions(+), 108 deletions(-) diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex index bffd49d4..2bacfd38 100644 --- a/lib/auth/people_roles.ex +++ b/lib/auth/people_roles.ex @@ -13,7 +13,6 @@ defmodule Auth.PeopleRoles do timestamps() end - @doc """ grant_role/3 grants a role to the given person the conn must have conn.assigns.person to check for admin in order to grant the role. @@ -34,7 +33,5 @@ defmodule Auth.PeopleRoles do else AuthWeb.AuthController.unauthorized(conn) end - end - -end \ No newline at end of file +end diff --git a/lib/auth/permission.ex b/lib/auth/permission.ex index 6af06870..a26b031d 100644 --- a/lib/auth/permission.ex +++ b/lib/auth/permission.ex @@ -114,5 +114,4 @@ defmodule Auth.Permission do def change_permission(%Permission{} = permission, attrs \\ %{}) do Permission.changeset(permission, attrs) end - end diff --git a/lib/auth/role.ex b/lib/auth/role.ex index fbac1f8c..887577dc 100644 --- a/lib/auth/role.ex +++ b/lib/auth/role.ex @@ -22,8 +22,7 @@ defmodule Auth.Role do |> validate_required([:name, :desc]) end - -@doc """ + @doc """ Returns the list of roles. ## Examples @@ -117,12 +116,10 @@ defmodule Auth.Role do Role.changeset(role, attrs) end - # @doc """ # grants the default "subscriber" (6) role to the person # """ # def set_default_role(person) do - - # end + # end end diff --git a/lib/auth_web/controllers/auth_controller.ex b/lib/auth_web/controllers/auth_controller.ex index 3c5b3248..be83887e 100644 --- a/lib/auth_web/controllers/auth_controller.ex +++ b/lib/auth_web/controllers/auth_controller.ex @@ -426,7 +426,7 @@ defmodule AuthWeb.AuthController do Enum.filter(apikeys, fn k -> # if the API Key belongs to Super Admin, don't check URL as it's the "setup key": if person_id == 1 do - k.client_id == client_id + k.client_id == client_id else # check url matches the state for all other keys: k.client_id == client_id and state =~ k.url diff --git a/lib/auth_web/controllers/ping_controller.ex b/lib/auth_web/controllers/ping_controller.ex index b3f59710..dc6a207f 100644 --- a/lib/auth_web/controllers/ping_controller.ex +++ b/lib/auth_web/controllers/ping_controller.ex @@ -5,4 +5,4 @@ defmodule AuthWeb.PingController do def ping(conn, params) do Ping.render_pixel(conn, params) end -end \ No newline at end of file +end diff --git a/mix.exs b/mix.exs index 8ca755aa..3e32b768 100644 --- a/mix.exs +++ b/mix.exs @@ -73,7 +73,7 @@ defmodule Auth.Mixfile do {:ping, "~> 1.0.1"}, # Check test coverage - {:excoveralls, "~> 0.12.3", only: :test}, + {:excoveralls, "~> 0.12.3", only: :test}, #  Property based tests: github.com/dwyl/learn-property-based-testing {:stream_data, "~> 0.4.3", only: :test}, diff --git a/priv/repo/migrations/20200723143204_create_role_permissions.exs b/priv/repo/migrations/20200723143204_create_role_permissions.exs index e607b969..55bde3af 100644 --- a/priv/repo/migrations/20200723143204_create_role_permissions.exs +++ b/priv/repo/migrations/20200723143204_create_role_permissions.exs @@ -6,10 +6,10 @@ defmodule Auth.Repo.Migrations.CreateRolePermissions do add :role_id, references(:roles, on_delete: :nothing) add :permission_id, references(:permissions, on_delete: :nothing) add :granter_id, references(:people, on_delete: :nothing) - + timestamps() end - + create unique_index(:role_permissions, [:role_id, :permission_id]) end end diff --git a/priv/repo/migrations/20200723154847_create_people_roles.exs b/priv/repo/migrations/20200723154847_create_people_roles.exs index f5938f41..eb7cd985 100644 --- a/priv/repo/migrations/20200723154847_create_people_roles.exs +++ b/priv/repo/migrations/20200723154847_create_people_roles.exs @@ -6,10 +6,10 @@ defmodule Auth.Repo.Migrations.CreatePeopleRoles do add :person_id, references(:people, on_delete: :nothing) add :role_id, references(:roles, on_delete: :nothing) add :granter_id, references(:people, on_delete: :nothing) - + timestamps() end - + create unique_index(:people_roles, [:person_id, :role_id]) end end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index fd72ccf0..862adb00 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -26,13 +26,14 @@ defmodule Auth.Seeds do person -> person end + if(Mix.env() == :test) do # don't print noise during tests else IO.inspect(person.id, label: "seeds.exs person.id") IO.puts("- - - - - - - - - - - - - - - - - - - - - - ") end - + person end @@ -59,16 +60,19 @@ defmodule Auth.Seeds do # write the key:value pair to project .env file def write_env(key, value) do # IO.inspect(File.cwd!, label: "cwd") - path = File.cwd! <> "/.env" + path = File.cwd!() <> "/.env" IO.inspect(path, label: ".env file path") {:ok, data} = File.read(path) # IO.inspect(data) - lines = String.split(data, "\n") - |> Enum.filter(fn line -> - not String.contains?(line, key) - end) - str = "export #{key}=#{value}" # |> IO.inspect + lines = + String.split(data, "\n") + |> Enum.filter(fn line -> + not String.contains?(line, key) + end) + + # |> IO.inspect + str = "export #{key}=#{value}" vars = lines ++ [str] content = Enum.join(vars, "\n") File.write!(path, content) |> File.close() @@ -78,10 +82,11 @@ defmodule Auth.Seeds do # export all the environment variables during app excution/tests def env(vars) do Enum.map(vars, fn line -> - parts = line - |> String.replace("export ", "") - |> String.replace("'", "") - |> String.split("=") + parts = + line + |> String.replace("export ", "") + |> String.replace("'", "") + |> String.split("=") # IO.inspect(List.last(parts), label: List.first(parts)) System.put_env(List.first(parts), List.last(parts)) @@ -92,14 +97,13 @@ end Auth.Seeds.create_admin() |> Auth.Seeds.create_apikey_for_admin() - # scripts for creating default roles and permissions defmodule SetupRoles do alias Auth.Role def get_json(filepath) do # IO.inspect(filepath, label: "filepath") - path = File.cwd! <> filepath + path = File.cwd!() <> filepath # IO.inspect(path, label: "path") {:ok, data} = File.read(path) json = Jason.decode!(data) @@ -109,17 +113,15 @@ defmodule SetupRoles do def create_default_roles() do json = get_json("/priv/repo/default_roles.json") - Enum.each(json, fn role -> + + Enum.each(json, fn role -> Role.create_role(role) # |> IO.inspect() end) end def assign_superadmin_role() do - end - - end -SetupRoles.create_default_roles() \ No newline at end of file +SetupRoles.create_default_roles() diff --git a/test/auth/people_roles_test.exs b/test/auth/people_roles_test.exs index b47b713d..461217a1 100644 --- a/test/auth/people_roles_test.exs +++ b/test/auth/people_roles_test.exs @@ -17,11 +17,10 @@ defmodule AuthWeb.PeopleRolesTest do test "attempt to grant_role/3 without admin should 401", %{conn: conn} do alex = %{email: "alex_grant_role_fail@gmail.com", auth_provider: "email"} grantee = Auth.Person.create_person(alex) - conn = assign(conn, :person, grantee) # + conn = assign(conn, :person, grantee) role_id = 4 conn = Auth.PeopleRoles.grant_role(conn, grantee.id, role_id) assert conn.status == 401 end - -end \ No newline at end of file +end diff --git a/test/auth/role_test.exs b/test/auth/role_test.exs index 9a425a3d..9769d67a 100644 --- a/test/auth/role_test.exs +++ b/test/auth/role_test.exs @@ -47,8 +47,7 @@ defmodule Auth.RoleTest do test "update_role/2 with invalid data returns error changeset" do role = role_fixture() - assert {:error, %Ecto.Changeset{}} = - Role.update_role(role, @invalid_attrs) + assert {:error, %Ecto.Changeset{}} = Role.update_role(role, @invalid_attrs) assert role == Role.get_role!(role.id) end @@ -91,37 +90,40 @@ defmodule Auth.RoleTest do end test "create_permission/1 with valid data creates a permission" do - assert {:ok, %Permission{} = permission} = - Permission.create_permission(@valid_attrs) + assert {:ok, %Permission{} = permission} = Permission.create_permission(@valid_attrs) assert permission.desc == "some desc" assert permission.name == "some name" end test "create_permission/1 with invalid data returns error changeset" do - assert {:error, %Ecto.Changeset{}} = - Permission.create_permission(@invalid_attrs) + assert {:error, %Ecto.Changeset{}} = Permission.create_permission(@invalid_attrs) end test "update_permission/2 with valid data updates the permission" do permission = permission_fixture() - assert {:ok, %Permission{} = permission} = - Permission.update_permission(permission, @update_attrs) + + assert {:ok, %Permission{} = permission} = + Permission.update_permission(permission, @update_attrs) + assert permission.desc == "some updated desc" assert permission.name == "some updated name" end test "update_permission/2 with invalid data returns error changeset" do permission = permission_fixture() - assert {:error, %Ecto.Changeset{}} = - Permission.update_permission(permission, @invalid_attrs) + + assert {:error, %Ecto.Changeset{}} = + Permission.update_permission(permission, @invalid_attrs) + assert permission == Permission.get_permission!(permission.id) end test "delete_permission/1 deletes the permission" do permission = permission_fixture() assert {:ok, %Permission{}} = Permission.delete_permission(permission) - assert_raise Ecto.NoResultsError, fn -> - Permission.get_permission!(permission.id) + + assert_raise Ecto.NoResultsError, fn -> + Permission.get_permission!(permission.id) end end @@ -131,16 +133,10 @@ defmodule Auth.RoleTest do end end - # create a new person and confirm they were asigned a default role of "subscriber" - - # describe "grant role" do - - - # # test "change_permission/1 returns a permission changeset" do # # permission = permission_fixture() # # assert %Ecto.Changeset{} = Permission.change_permission(permission) diff --git a/test/auth_web/controllers/apikey_controller_test.exs b/test/auth_web/controllers/apikey_controller_test.exs index 38c73115..5e19411d 100644 --- a/test/auth_web/controllers/apikey_controller_test.exs +++ b/test/auth_web/controllers/apikey_controller_test.exs @@ -74,8 +74,9 @@ defmodule AuthWeb.ApikeyControllerTest do # describe "index" do test "lists all apikeys", %{conn: conn} do - conn = admin_login(conn) - |> get(Routes.apikey_path(conn, :index)) + conn = + admin_login(conn) + |> get(Routes.apikey_path(conn, :index)) assert html_response(conn, 200) =~ "Auth API Keys" end @@ -83,8 +84,9 @@ defmodule AuthWeb.ApikeyControllerTest do describe "new apikey" do test "renders form", %{conn: conn} do - conn = admin_login(conn) - |> get(Routes.apikey_path(conn, :new)) + conn = + admin_login(conn) + |> get(Routes.apikey_path(conn, :new)) assert html_response(conn, 200) =~ "New Apikey" end @@ -92,8 +94,9 @@ defmodule AuthWeb.ApikeyControllerTest do describe "create apikey" do test "redirects to show when data is valid", %{conn: conn} do - conn = admin_login(conn) - |> post(Routes.apikey_path(conn, :create), apikey: @create_attrs) + conn = + admin_login(conn) + |> post(Routes.apikey_path(conn, :create), apikey: @create_attrs) assert %{id: id} = redirected_params(conn) assert redirected_to(conn) == Routes.apikey_path(conn, :show, id) @@ -134,7 +137,7 @@ defmodule AuthWeb.ApikeyControllerTest do auth_provider: "email" }) - conn = AuthPlug.create_jwt_session(conn, wrong_person) + conn = AuthPlug.create_jwt_session(conn, wrong_person) {:ok, key} = %{"name" => "test key", "url" => "http://localhost:4000"} diff --git a/test/auth_web/controllers/auth_controller_test.exs b/test/auth_web/controllers/auth_controller_test.exs index c3de1577..74ff3c82 100644 --- a/test/auth_web/controllers/auth_controller_test.exs +++ b/test/auth_web/controllers/auth_controller_test.exs @@ -22,8 +22,10 @@ defmodule AuthWeb.AuthControllerTest do } person = Auth.Person.create_person(data) - conn = AuthPlug.create_jwt_session(conn, Map.merge(data, %{id: person.id})) - |> get("/profile", %{}) + + conn = + AuthPlug.create_jwt_session(conn, Map.merge(data, %{id: person.id})) + |> get("/profile", %{}) assert html_response(conn, 200) =~ "Google account" end @@ -50,19 +52,20 @@ defmodule AuthWeb.AuthControllerTest do end test "get_client_secret(client_id, state) gets the secret for the given client_id" do + person = + Auth.Person.create_person(%{ + email: "alex@gmail.com", + auth_provider: "email" + }) - person = Auth.Person.create_person(%{ - email: "alex@gmail.com", - auth_provider: "email" - }) - - {:ok, key} = %{"name" => "test key", "url" => "example.com"} - |> AuthWeb.ApikeyController.make_apikey(person.id) - |> Auth.Apikey.create_apikey() + {:ok, key} = + %{"name" => "test key", "url" => "example.com"} + |> AuthWeb.ApikeyController.make_apikey(person.id) + |> Auth.Apikey.create_apikey() state = "https://www.example.com/profile?auth_client_id=#{key.client_id}" secret = AuthWeb.AuthController.get_client_secret(key.client_id, state) - + assert secret == key.client_secret end @@ -126,8 +129,9 @@ defmodule AuthWeb.AuthControllerTest do person = Auth.Person.upsert_person(data) - conn = AuthPlug.create_jwt_session(conn, person) - |> get("/auth/google/callback", %{"code" => "234", "state" => nil}) + conn = + AuthPlug.create_jwt_session(conn, person) + |> get("/auth/google/callback", %{"code" => "234", "state" => nil}) assert html_response(conn, 200) =~ "Google account" end diff --git a/test/auth_web/controllers/permission_controller_test.exs b/test/auth_web/controllers/permission_controller_test.exs index 2971e785..0c5d1fb5 100644 --- a/test/auth_web/controllers/permission_controller_test.exs +++ b/test/auth_web/controllers/permission_controller_test.exs @@ -28,9 +28,9 @@ defmodule AuthWeb.PermissionControllerTest do describe "create permission" do test "redirects to show when data is valid", %{conn: conn} do - - conn = admin_login(conn) - |> post(Routes.permission_path(conn, :create), permission: @create_attrs) + conn = + admin_login(conn) + |> post(Routes.permission_path(conn, :create), permission: @create_attrs) assert %{id: id} = redirected_params(conn) assert redirected_to(conn) == Routes.permission_path(conn, :show, id) @@ -40,8 +40,10 @@ defmodule AuthWeb.PermissionControllerTest do end test "renders errors when data is invalid", %{conn: conn} do - conn = admin_login(conn) - |> post(Routes.permission_path(conn, :create), permission: @invalid_attrs) + conn = + admin_login(conn) + |> post(Routes.permission_path(conn, :create), permission: @invalid_attrs) + assert html_response(conn, 200) =~ "New Permission" end end @@ -50,8 +52,10 @@ defmodule AuthWeb.PermissionControllerTest do setup [:create_permission] test "renders form for editing chosen permission", %{conn: conn, permission: permission} do - conn = admin_login(conn) - |> get(Routes.permission_path(conn, :edit, permission)) + conn = + admin_login(conn) + |> get(Routes.permission_path(conn, :edit, permission)) + assert html_response(conn, 200) =~ "Edit Permission" end end @@ -60,8 +64,10 @@ defmodule AuthWeb.PermissionControllerTest do setup [:create_permission] test "redirects when data is valid", %{conn: conn, permission: permission} do - conn = admin_login(conn) - |> put(Routes.permission_path(conn, :update, permission), permission: @update_attrs) + conn = + admin_login(conn) + |> put(Routes.permission_path(conn, :update, permission), permission: @update_attrs) + assert redirected_to(conn) == Routes.permission_path(conn, :show, permission) conn = get(conn, Routes.permission_path(conn, :show, permission)) @@ -69,8 +75,10 @@ defmodule AuthWeb.PermissionControllerTest do end test "renders errors when data is invalid", %{conn: conn, permission: permission} do - conn = admin_login(conn) - |> put(Routes.permission_path(conn, :update, permission), permission: @invalid_attrs) + conn = + admin_login(conn) + |> put(Routes.permission_path(conn, :update, permission), permission: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Permission" end end @@ -79,9 +87,12 @@ defmodule AuthWeb.PermissionControllerTest do setup [:create_permission] test "deletes chosen permission", %{conn: conn, permission: permission} do - conn = admin_login(conn) - |> delete(Routes.permission_path(conn, :delete, permission)) + conn = + admin_login(conn) + |> delete(Routes.permission_path(conn, :delete, permission)) + assert redirected_to(conn) == Routes.permission_path(conn, :index) + assert_error_sent 404, fn -> get(conn, Routes.permission_path(conn, :show, permission)) end diff --git a/test/auth_web/controllers/ping_controller_test.exs b/test/auth_web/controllers/ping_controller_test.exs index d6e3192a..cc536c58 100644 --- a/test/auth_web/controllers/ping_controller_test.exs +++ b/test/auth_web/controllers/ping_controller_test.exs @@ -7,4 +7,4 @@ defmodule AuthWeb.PingControllerTest do assert conn.state == :sent assert conn.resp_body =~ <<71, 73, 70, 56, 57>> end -end \ No newline at end of file +end diff --git a/test/auth_web/controllers/role_controller_test.exs b/test/auth_web/controllers/role_controller_test.exs index c6ad8b30..5bb16972 100644 --- a/test/auth_web/controllers/role_controller_test.exs +++ b/test/auth_web/controllers/role_controller_test.exs @@ -28,8 +28,9 @@ defmodule AuthWeb.RoleControllerTest do describe "create role" do test "redirects to show when data is valid", %{conn: conn} do - conn = admin_login(conn) - |> post(Routes.role_path(conn, :create), role: @create_attrs) + conn = + admin_login(conn) + |> post(Routes.role_path(conn, :create), role: @create_attrs) assert %{id: id} = redirected_params(conn) assert redirected_to(conn) == Routes.role_path(conn, :show, id) @@ -39,8 +40,10 @@ defmodule AuthWeb.RoleControllerTest do end test "renders errors when data is invalid", %{conn: conn} do - conn = admin_login(conn) - |> post(Routes.role_path(conn, :create), role: @invalid_attrs) + conn = + admin_login(conn) + |> post(Routes.role_path(conn, :create), role: @invalid_attrs) + assert html_response(conn, 200) =~ "New Role" end end @@ -49,8 +52,10 @@ defmodule AuthWeb.RoleControllerTest do setup [:create_role] test "renders form for editing chosen role", %{conn: conn, role: role} do - conn = admin_login(conn) - |> get(Routes.role_path(conn, :edit, role)) + conn = + admin_login(conn) + |> get(Routes.role_path(conn, :edit, role)) + assert html_response(conn, 200) =~ "Edit Role" end end @@ -59,8 +64,10 @@ defmodule AuthWeb.RoleControllerTest do setup [:create_role] test "redirects when data is valid", %{conn: conn, role: role} do - conn = admin_login(conn) - |> put(Routes.role_path(conn, :update, role), role: @update_attrs) + conn = + admin_login(conn) + |> put(Routes.role_path(conn, :update, role), role: @update_attrs) + assert redirected_to(conn) == Routes.role_path(conn, :show, role) conn = get(conn, Routes.role_path(conn, :show, role)) @@ -68,8 +75,10 @@ defmodule AuthWeb.RoleControllerTest do end test "renders errors when data is invalid", %{conn: conn, role: role} do - conn = admin_login(conn) - |> put(Routes.role_path(conn, :update, role), role: @invalid_attrs) + conn = + admin_login(conn) + |> put(Routes.role_path(conn, :update, role), role: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Role" end end @@ -78,9 +87,12 @@ defmodule AuthWeb.RoleControllerTest do setup [:create_role] test "deletes chosen role", %{conn: conn, role: role} do - conn = admin_login(conn) - |> delete(Routes.role_path(conn, :delete, role)) + conn = + admin_login(conn) + |> delete(Routes.role_path(conn, :delete, role)) + assert redirected_to(conn) == Routes.role_path(conn, :index) + assert_error_sent 404, fn -> get(conn, Routes.role_path(conn, :show, role)) end @@ -91,5 +103,4 @@ defmodule AuthWeb.RoleControllerTest do role = fixture(:role) %{role: role} end - end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 07e74d03..24e3bc1f 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -24,7 +24,7 @@ defmodule AuthWeb.ConnCase do import Phoenix.ConnTest # AuthTest is defined in test_helpers.exs # as per https://stackoverflow.com/a/58902158/1148249 - import AuthTest + import AuthTest alias AuthWeb.Router.Helpers, as: Routes # The default endpoint for testing @@ -39,8 +39,10 @@ defmodule AuthWeb.ConnCase do Ecto.Adapters.SQL.Sandbox.mode(Auth.Repo, {:shared, self()}) end - conn = Phoenix.ConnTest.build_conn() - |> Phoenix.ConnTest.init_test_session(%{}) + conn = + Phoenix.ConnTest.build_conn() + |> Phoenix.ConnTest.init_test_session(%{}) + # invoke Plug.Test.init_test_session/2 to setup the test session # before attempting to set a JWT. see: # https://github.com/dwyl/auth/issues/83#issuecomment-660052222 diff --git a/test/test_helper.exs b/test/test_helper.exs index c4d0d2a0..f0a5fa15 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,7 +1,6 @@ ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(Auth.Repo, :manual) - defmodule AuthTest do @moduledoc """ Test helper functions :-) @@ -14,4 +13,4 @@ defmodule AuthTest do person = Auth.Person.get_person_by_email(@admin_email) AuthPlug.create_jwt_session(conn, person) end -end \ No newline at end of file +end From 5a0d8bfa88ecd7b9548ad48aa3298b3de1b8a22e Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 20 Aug 2020 12:01:30 +0100 Subject: [PATCH 037/166] add @moduledoc tag comment to all newly created files and some old ones for https://github.com/dwyl/auth/pull/85/files#r473848716 --- lib/auth/apikey.ex | 3 +++ lib/auth/login_log.ex | 3 +++ lib/auth/people_roles.ex | 3 +++ lib/auth/permission.ex | 3 +++ lib/auth/person.ex | 3 +++ lib/auth/role.ex | 3 +++ lib/auth_web/controllers/apikey_controller.ex | 3 +++ lib/auth_web/controllers/auth_controller.ex | 3 +++ lib/auth_web/router.ex | 3 +++ mix.exs | 3 +++ 10 files changed, 30 insertions(+) diff --git a/lib/auth/apikey.ex b/lib/auth/apikey.ex index c45d6c6d..4c087636 100644 --- a/lib/auth/apikey.ex +++ b/lib/auth/apikey.ex @@ -1,4 +1,7 @@ defmodule Auth.Apikey do + @moduledoc """ + Defines apikeys schema and CRUD functions + """ use Ecto.Schema import Ecto.Query, warn: false import Ecto.Changeset diff --git a/lib/auth/login_log.ex b/lib/auth/login_log.ex index cf9eca1a..bc13bb13 100644 --- a/lib/auth/login_log.ex +++ b/lib/auth/login_log.ex @@ -1,4 +1,7 @@ defmodule Auth.LoginLog do + @moduledoc """ + Defines login_logs schema and CRUD functions + """ use Ecto.Schema import Ecto.Changeset alias Auth.Repo diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex index 2bacfd38..ef1141e3 100644 --- a/lib/auth/people_roles.ex +++ b/lib/auth/people_roles.ex @@ -1,4 +1,7 @@ defmodule Auth.PeopleRoles do + @moduledoc """ + Defines people_roles schema and fuction to grant roles to a person. + """ use Ecto.Schema import Ecto.Changeset alias Auth.Repo diff --git a/lib/auth/permission.ex b/lib/auth/permission.ex index a26b031d..9ef99ef7 100644 --- a/lib/auth/permission.ex +++ b/lib/auth/permission.ex @@ -1,4 +1,7 @@ defmodule Auth.Permission do + @moduledoc """ + Defines Rermission schema and CRUD functions + """ use Ecto.Schema import Ecto.Changeset import Ecto.Query, warn: false diff --git a/lib/auth/person.ex b/lib/auth/person.ex index b4dedcdd..002f5bba 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -1,4 +1,7 @@ defmodule Auth.Person do + @moduledoc """ + Defines Person schema and CRUD functions + """ use Ecto.Schema import Ecto.Changeset alias Auth.Repo diff --git a/lib/auth/role.ex b/lib/auth/role.ex index 887577dc..5f08b82a 100644 --- a/lib/auth/role.ex +++ b/lib/auth/role.ex @@ -1,4 +1,7 @@ defmodule Auth.Role do + @moduledoc """ + Defines Role schema and CRUD functions + """ use Ecto.Schema import Ecto.Changeset import Ecto.Query, warn: false diff --git a/lib/auth_web/controllers/apikey_controller.ex b/lib/auth_web/controllers/apikey_controller.ex index efb9eeb0..dc3972fb 100644 --- a/lib/auth_web/controllers/apikey_controller.ex +++ b/lib/auth_web/controllers/apikey_controller.ex @@ -1,4 +1,7 @@ defmodule AuthWeb.ApikeyController do + @moduledoc """ + Defines API Key controller functions + """ use AuthWeb, :controller alias Auth.Apikey diff --git a/lib/auth_web/controllers/auth_controller.ex b/lib/auth_web/controllers/auth_controller.ex index be83887e..50eedbd1 100644 --- a/lib/auth_web/controllers/auth_controller.ex +++ b/lib/auth_web/controllers/auth_controller.ex @@ -1,4 +1,7 @@ defmodule AuthWeb.AuthController do + @moduledoc """ + Defines AuthController and all functions for authenticaiton + """ use AuthWeb, :controller alias Auth.Person diff --git a/lib/auth_web/router.ex b/lib/auth_web/router.ex index e16949c8..7e798df6 100644 --- a/lib/auth_web/router.ex +++ b/lib/auth_web/router.ex @@ -1,4 +1,7 @@ defmodule AuthWeb.Router do + @moduledoc """ + Defines Web Application Router pipelines and routes + """ use AuthWeb, :router pipeline :browser do diff --git a/mix.exs b/mix.exs index 3e32b768..aef11f5c 100644 --- a/mix.exs +++ b/mix.exs @@ -1,4 +1,7 @@ defmodule Auth.Mixfile do + @moduledoc """ + Defines the Mix Project, Dependencies and Scripts + """ use Mix.Project def project do From 3ce9bab87ad4fad01209625a477b19364c0500bc Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 20 Aug 2020 19:33:21 +0100 Subject: [PATCH 038/166] create and use Auth.PeopleRoles.list_people_roles/0 function to list all people_roles data #27 #31 #82 #90 --- lib/auth/people_roles.ex | 12 +++++++++++- lib/auth/permission.ex | 2 +- lib/auth/role.ex | 4 ++-- test/auth/people_roles_test.exs | 11 ++++++++--- test/support/conn_case.ex | 4 +--- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex index ef1141e3..95529893 100644 --- a/lib/auth/people_roles.ex +++ b/lib/auth/people_roles.ex @@ -4,6 +4,7 @@ defmodule Auth.PeopleRoles do """ use Ecto.Schema import Ecto.Changeset + import Ecto.Query alias Auth.Repo # https://stackoverflow.com/a/47501059/1148249 alias __MODULE__ @@ -16,6 +17,15 @@ defmodule Auth.PeopleRoles do timestamps() end + @doc """ + Returns the list of people_roles. + """ + def list_people_roles do + Repo.all(from pr in __MODULE__, preload: [:person, :role]) + end + + + @doc """ grant_role/3 grants a role to the given person the conn must have conn.assigns.person to check for admin in order to grant the role. @@ -23,7 +33,7 @@ defmodule Auth.PeopleRoles do def grant_role(conn, grantee_id, role_id) do granter = conn.assigns.person # IO.inspect(granter, label: "granter") - # confirm that the granter is either superadmin (conn.assigns.person.id == 1) + # confirm that the granter is either superadmin (conn.assigns.person.id == 1) # or has an "admin" role (1 || 2) if granter.id == 1 do %PeopleRoles{} diff --git a/lib/auth/permission.ex b/lib/auth/permission.ex index 9ef99ef7..0b01d219 100644 --- a/lib/auth/permission.ex +++ b/lib/auth/permission.ex @@ -1,6 +1,6 @@ defmodule Auth.Permission do @moduledoc """ - Defines Rermission schema and CRUD functions + Defines permissions schema and CRUD functions """ use Ecto.Schema import Ecto.Changeset diff --git a/lib/auth/role.ex b/lib/auth/role.ex index 5f08b82a..228eaad4 100644 --- a/lib/auth/role.ex +++ b/lib/auth/role.ex @@ -1,13 +1,13 @@ defmodule Auth.Role do @moduledoc """ - Defines Role schema and CRUD functions + Defines roles schema and CRUD functions """ use Ecto.Schema import Ecto.Changeset import Ecto.Query, warn: false alias Auth.Repo # https://stackoverflow.com/a/47501059/1148249 - alias __MODULE__ + alias __MODULE__ schema "roles" do field :desc, :string diff --git a/test/auth/people_roles_test.exs b/test/auth/people_roles_test.exs index 461217a1..06dc1e48 100644 --- a/test/auth/people_roles_test.exs +++ b/test/auth/people_roles_test.exs @@ -4,14 +4,20 @@ defmodule AuthWeb.PeopleRolesTest do test "grant_role/3 happy path", %{conn: conn} do # login as superadmin conn = AuthTest.admin_login(conn) - # create a new person + # create a new person alex = %{email: "alex_grant_role@gmail.com", auth_provider: "email"} grantee = Auth.Person.create_person(alex) role_id = 4 Auth.PeopleRoles.grant_role(conn, grantee.id, role_id) - person_with_role = Auth.Person.get_person_by_id(grantee.id) + person_with_role = Auth.Person.get_person_by_id(grantee.id) # |> IO.inspect() role = List.first(person_with_role.roles) assert role_id == role.id + + # check the latest people_roles record: + pr = List.last(Auth.PeopleRoles.list_people_roles()) + assert pr.granter_id == 1 + assert pr.person_id == grantee.id + end test "attempt to grant_role/3 without admin should 401", %{conn: conn} do @@ -20,7 +26,6 @@ defmodule AuthWeb.PeopleRolesTest do conn = assign(conn, :person, grantee) role_id = 4 conn = Auth.PeopleRoles.grant_role(conn, grantee.id, role_id) - assert conn.status == 401 end end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 24e3bc1f..83f42d82 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -39,9 +39,7 @@ defmodule AuthWeb.ConnCase do Ecto.Adapters.SQL.Sandbox.mode(Auth.Repo, {:shared, self()}) end - conn = - Phoenix.ConnTest.build_conn() - |> Phoenix.ConnTest.init_test_session(%{}) + conn = Phoenix.ConnTest.init_test_session(Phoenix.ConnTest.build_conn, %{}) # invoke Plug.Test.init_test_session/2 to setup the test session # before attempting to set a JWT. see: From 198b3fb7c912259cc5c2077fe66860477f8beb16 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 20 Aug 2020 19:50:46 +0100 Subject: [PATCH 039/166] simplify Auth.PeopleRoles.insert/3 to not use conn as discussed with @th0mas in https://github.com/dwyl/auth/pull/85#discussion_r473914644 --- lib/auth/people_roles.ex | 27 ++++++------------- lib/auth/role.ex | 2 +- lib/auth_web/controllers/role_controller.ex | 16 +++++++++++ test/auth/people_roles_test.exs | 30 ++++++++++----------- test/support/conn_case.ex | 2 +- 5 files changed, 40 insertions(+), 37 deletions(-) diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex index 95529893..e7f0d989 100644 --- a/lib/auth/people_roles.ex +++ b/lib/auth/people_roles.ex @@ -18,33 +18,22 @@ defmodule Auth.PeopleRoles do end @doc """ - Returns the list of people_roles. + Returns the list of people_roles with all people data. + This is useful for displaying the data in a admin overview table. """ def list_people_roles do Repo.all(from pr in __MODULE__, preload: [:person, :role]) end - - @doc """ grant_role/3 grants a role to the given person the conn must have conn.assigns.person to check for admin in order to grant the role. """ - def grant_role(conn, grantee_id, role_id) do - granter = conn.assigns.person - # IO.inspect(granter, label: "granter") - # confirm that the granter is either superadmin (conn.assigns.person.id == 1) - # or has an "admin" role (1 || 2) - if granter.id == 1 do - %PeopleRoles{} - |> cast(%{granter_id: granter.id}, [:granter_id]) - |> put_assoc(:person, Auth.Person.get_person_by_id(grantee_id)) - |> put_assoc(:role, Auth.Role.get_role!(role_id)) - |> Repo.insert() - - conn - else - AuthWeb.AuthController.unauthorized(conn) - end + def insert(granter_id, grantee_id, role_id) do + %PeopleRoles{} + |> cast(%{granter_id: granter_id}, [:granter_id]) + |> put_assoc(:person, Auth.Person.get_person_by_id(grantee_id)) + |> put_assoc(:role, Auth.Role.get_role!(role_id)) + |> Repo.insert() end end diff --git a/lib/auth/role.ex b/lib/auth/role.ex index 228eaad4..2e1fba9a 100644 --- a/lib/auth/role.ex +++ b/lib/auth/role.ex @@ -7,7 +7,7 @@ defmodule Auth.Role do import Ecto.Query, warn: false alias Auth.Repo # https://stackoverflow.com/a/47501059/1148249 - alias __MODULE__ + alias __MODULE__ schema "roles" do field :desc, :string diff --git a/lib/auth_web/controllers/role_controller.ex b/lib/auth_web/controllers/role_controller.ex index 3ce9ec11..b42f27b4 100644 --- a/lib/auth_web/controllers/role_controller.ex +++ b/lib/auth_web/controllers/role_controller.ex @@ -58,4 +58,20 @@ defmodule AuthWeb.RoleController do |> put_flash(:info, "Role deleted successfully.") |> redirect(to: Routes.role_path(conn, :index)) end + + @doc """ + grant_role/3 grants a role to the given person + the conn must have conn.assigns.person to check for admin in order to grant the role. + grantee_id should be a valid person.id (the person you want to grant the role to) and + role_id a valid role.id + """ + # def grant_role(conn, grantee_id, role_id) do + # # confirm that the granter is either superadmin (conn.assigns.person.id == 1) + # # or has an "admin" role (1 || 2) + # if granter.id == 1 do + # conn + # else + # AuthWeb.AuthController.unauthorized(conn) + # end + # end end diff --git a/test/auth/people_roles_test.exs b/test/auth/people_roles_test.exs index 06dc1e48..bd5e54eb 100644 --- a/test/auth/people_roles_test.exs +++ b/test/auth/people_roles_test.exs @@ -1,15 +1,14 @@ defmodule AuthWeb.PeopleRolesTest do - use AuthWeb.ConnCase + use Auth.DataCase - test "grant_role/3 happy path", %{conn: conn} do - # login as superadmin - conn = AuthTest.admin_login(conn) - # create a new person + test "Auth.PeopleRoles.insert/3 happy path" do + # create a new person: alex = %{email: "alex_grant_role@gmail.com", auth_provider: "email"} grantee = Auth.Person.create_person(alex) role_id = 4 - Auth.PeopleRoles.grant_role(conn, grantee.id, role_id) - person_with_role = Auth.Person.get_person_by_id(grantee.id) # |> IO.inspect() + # grant the "creator" role (id: 4) to the new person: + Auth.PeopleRoles.insert(1, grantee.id, role_id) + person_with_role = Auth.Person.get_person_by_id(grantee.id) role = List.first(person_with_role.roles) assert role_id == role.id @@ -17,15 +16,14 @@ defmodule AuthWeb.PeopleRolesTest do pr = List.last(Auth.PeopleRoles.list_people_roles()) assert pr.granter_id == 1 assert pr.person_id == grantee.id - end - test "attempt to grant_role/3 without admin should 401", %{conn: conn} do - alex = %{email: "alex_grant_role_fail@gmail.com", auth_provider: "email"} - grantee = Auth.Person.create_person(alex) - conn = assign(conn, :person, grantee) - role_id = 4 - conn = Auth.PeopleRoles.grant_role(conn, grantee.id, role_id) - assert conn.status == 401 - end + # test "attempt to grant_role/3 without admin should 401", %{conn: conn} do + # alex = %{email: "alex_grant_role_fail@gmail.com", auth_provider: "email"} + # grantee = Auth.Person.create_person(alex) + # conn = assign(conn, :person, grantee) + # role_id = 4 + # conn = Auth.PeopleRoles.insert(conn, grantee.id, role_id) + # assert conn.status == 401 + # end end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 83f42d82..63bf95df 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -39,7 +39,7 @@ defmodule AuthWeb.ConnCase do Ecto.Adapters.SQL.Sandbox.mode(Auth.Repo, {:shared, self()}) end - conn = Phoenix.ConnTest.init_test_session(Phoenix.ConnTest.build_conn, %{}) + conn = Phoenix.ConnTest.init_test_session(Phoenix.ConnTest.build_conn(), %{}) # invoke Plug.Test.init_test_session/2 to setup the test session # before attempting to set a JWT. see: From d3fa7de6e93a82d077b2e959b31f52ac95f42950 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 21 Aug 2020 09:14:47 +0100 Subject: [PATCH 040/166] update @doc comment for PeopleRoles.insert/3 https://github.com/dwyl/auth/pull/85#discussion_r473914644 --- lib/auth/people_roles.ex | 8 +++++--- lib/auth_web/controllers/role_controller.ex | 5 +++++ test/auth/people_roles_test.exs | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex index e7f0d989..8bd083aa 100644 --- a/lib/auth/people_roles.ex +++ b/lib/auth/people_roles.ex @@ -18,7 +18,7 @@ defmodule Auth.PeopleRoles do end @doc """ - Returns the list of people_roles with all people data. + list_people_roles/0 returns the list of people_roles with all people data. This is useful for displaying the data in a admin overview table. """ def list_people_roles do @@ -26,8 +26,10 @@ defmodule Auth.PeopleRoles do end @doc """ - grant_role/3 grants a role to the given person - the conn must have conn.assigns.person to check for admin in order to grant the role. + insert/3 grants a role to the given person + granter_id is the id of the person (admin) granting the role + grantee_id is the person.id of the person being granted the role + role_id is the role.id (int, e.g: 4) of th role being granted. """ def insert(granter_id, grantee_id, role_id) do %PeopleRoles{} diff --git a/lib/auth_web/controllers/role_controller.ex b/lib/auth_web/controllers/role_controller.ex index b42f27b4..7e99f2d8 100644 --- a/lib/auth_web/controllers/role_controller.ex +++ b/lib/auth_web/controllers/role_controller.ex @@ -5,6 +5,7 @@ defmodule AuthWeb.RoleController do def index(conn, _params) do roles = Role.list_roles() + IO.inspect(conn.assigns.person, label: "conn.assigns.person") render(conn, "index.html", roles: roles) end @@ -68,10 +69,14 @@ defmodule AuthWeb.RoleController do # def grant_role(conn, grantee_id, role_id) do # # confirm that the granter is either superadmin (conn.assigns.person.id == 1) # # or has an "admin" role (1 || 2) + # granter = conn.assigns.person + # if granter.id == 1 do # conn # else # AuthWeb.AuthController.unauthorized(conn) # end # end + + end diff --git a/test/auth/people_roles_test.exs b/test/auth/people_roles_test.exs index bd5e54eb..9149f8fe 100644 --- a/test/auth/people_roles_test.exs +++ b/test/auth/people_roles_test.exs @@ -8,7 +8,7 @@ defmodule AuthWeb.PeopleRolesTest do role_id = 4 # grant the "creator" role (id: 4) to the new person: Auth.PeopleRoles.insert(1, grantee.id, role_id) - person_with_role = Auth.Person.get_person_by_id(grantee.id) + person_with_role = Auth.Person.get_person_by_id(grantee.id) |> IO.inspect() role = List.first(person_with_role.roles) assert role_id == role.id From 9dfc9decc825a65be509e1a973076110fc87cdfc Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 21 Aug 2020 22:30:37 +0100 Subject: [PATCH 041/166] use RBAC v0.2.0 to transform roles from List of Maps/Structs to String for #91 --- lib/auth/person.ex | 15 ++++-- lib/auth_web/controllers/auth_controller.ex | 6 ++- mix.exs | 5 +- mix.lock | 1 + priv/repo/seeds.exs | 4 +- test/auth/people_roles_test.exs | 13 +++-- .../controllers/apikey_controller_test.exs | 47 ++++++++++--------- .../controllers/auth_controller_test.exs | 7 +-- test/test_helper.exs | 8 +++- 9 files changed, 63 insertions(+), 43 deletions(-) diff --git a/lib/auth/person.ex b/lib/auth/person.ex index 002f5bba..c2dce850 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -35,7 +35,7 @@ defmodule Auth.Person do @doc """ Default attributes validation for Person """ - def changeset(person, attrs, roles \\ []) do + def changeset(person, attrs) do # IO.inspect(person, label: "changeset > person") # IO.inspect(attrs, label: "changeset > attrs") # IO.inspect(roles, label: "changeset > roles") @@ -58,7 +58,6 @@ defmodule Auth.Person do |> validate_required([:email]) |> put_email_hash() |> put_pass_hash() - |> put_assoc(:roles, roles) end def create_person(person) do @@ -66,6 +65,7 @@ defmodule Auth.Person do %Person{} |> changeset(person) |> put_email_status_verified() + |> put_assoc(:roles, [ Auth.Role.get_role!(6) ]) case get_person_by_email(person.changes.email) do nil -> @@ -164,8 +164,10 @@ defmodule Auth.Person do end def create_google_person(profile) do - transform_google_profile_data_to_person(profile) + person = transform_google_profile_data_to_person(profile) |> upsert_person() + + Map.replace!(person, :roles, RBAC.transform_role_list_to_string(person.roles)) end # @doc """ @@ -200,7 +202,9 @@ defmodule Auth.Person do def verify_person_by_id(id) do person = get_person_by_id(id) - %{email: person.email, status: get_status_verified()} |> upsert_person() + %{email: person.email, status: get_status_verified()} + |> upsert_person() + end def get_person_by_id(id) do @@ -246,7 +250,8 @@ defmodule Auth.Person do changeset(%Person{id: ep.id}, merged) |> Repo.update() - person + # ensure that the preloads are returned: + get_person_by_email(person.email) end end diff --git a/lib/auth_web/controllers/auth_controller.ex b/lib/auth_web/controllers/auth_controller.ex index 50eedbd1..b0c288b5 100644 --- a/lib/auth_web/controllers/auth_controller.ex +++ b/lib/auth_web/controllers/auth_controller.ex @@ -340,6 +340,9 @@ defmodule AuthWeb.AuthController do if changeset.valid? do person = Auth.Person.upsert_person(%{email: email, password: p["password"]}) + # replace %Auth.Role{} struct with string github.com/dwyl/rbac/issues/4 + person = Map.replace!(person, :roles, + RBAC.transform_role_list_to_string(person.roles)) redirect_or_render(conn, person, p["state"]) else conn @@ -447,7 +450,8 @@ defmodule AuthWeb.AuthController do id: person.id, picture: person.picture, status: person.status, - email: person.email + email: person.email, + roles: RBAC.transform_role_list_to_string(person.roles) } jwt = AuthPlug.Token.generate_jwt!(data, client_secret) diff --git a/mix.exs b/mix.exs index aef11f5c..bfeb7cf2 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,4 @@ defmodule Auth.Mixfile do - @moduledoc """ - Defines the Mix Project, Dependencies and Scripts - """ use Mix.Project def project do @@ -66,6 +63,8 @@ defmodule Auth.Mixfile do {:elixir_auth_google, "~> 1.3.0"}, # https://github.com/dwyl/auth_plug {:auth_plug, "1.2.0"}, + # https://github.com/dwyl/rbac + {:rbac, "~> 0.2.0"}, # Field Validation and Encryption: github.com/dwyl/fields {:fields, "~> 2.6.0"}, diff --git a/mix.lock b/mix.lock index 737d4801..3d5f2dd0 100644 --- a/mix.lock +++ b/mix.lock @@ -52,6 +52,7 @@ "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, "postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, + "rbac": {:hex, :rbac, "0.2.0", "ed77a2bcad9e8bc4879d887a288a216be85641f06cecd0d2a84eaadc97c2ade8", [:mix], [], "hexpm", "78f37cffcff1675957c2c510d2052e5e9a99053ec205e3c695609dcc9cd306bd"}, "sobelow": {:hex, :sobelow, "0.10.2", "00e91208046d3b434f9f08779fe0ca7c6d6595b7fa33b289e792dffa6dde8081", [:mix], [], "hexpm", "e30fc994330cf6f485c1c4f2fb7c4b2d403557d0e101c6e5329fd17a58e55a7e"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "stream_data": {:hex, :stream_data, "0.4.3", "62aafd870caff0849a5057a7ec270fad0eb86889f4d433b937d996de99e3db25", [:mix], [], "hexpm", "7dafd5a801f0bc897f74fcd414651632b77ca367a7ae4568778191fc3bf3a19a"}, diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 862adb00..a8c39ebd 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -119,9 +119,7 @@ defmodule SetupRoles do # |> IO.inspect() end) end - - def assign_superadmin_role() do - end end SetupRoles.create_default_roles() +Auth.PeopleRoles.insert(1, 1, 1) diff --git a/test/auth/people_roles_test.exs b/test/auth/people_roles_test.exs index 9149f8fe..e1f110f7 100644 --- a/test/auth/people_roles_test.exs +++ b/test/auth/people_roles_test.exs @@ -8,12 +8,14 @@ defmodule AuthWeb.PeopleRolesTest do role_id = 4 # grant the "creator" role (id: 4) to the new person: Auth.PeopleRoles.insert(1, grantee.id, role_id) - person_with_role = Auth.Person.get_person_by_id(grantee.id) |> IO.inspect() - role = List.first(person_with_role.roles) - assert role_id == role.id + person_with_role = Auth.Person.get_person_by_id(grantee.id) # |> IO.inspect() + roles = RBAC.transform_role_list_to_string(person_with_role.roles) + assert roles =~ Integer.to_string(role_id) # check the latest people_roles record: - pr = List.last(Auth.PeopleRoles.list_people_roles()) + list = Auth.PeopleRoles.list_people_roles() + # IO.inspect(list, label: "list") + pr = List.last(list) assert pr.granter_id == 1 assert pr.person_id == grantee.id end @@ -26,4 +28,7 @@ defmodule AuthWeb.PeopleRolesTest do # conn = Auth.PeopleRoles.insert(conn, grantee.id, role_id) # assert conn.status == 401 # end + # test "get list of roles" do + # Auth.Role.list_roles() |> IO.inspect() + # end end diff --git a/test/auth_web/controllers/apikey_controller_test.exs b/test/auth_web/controllers/apikey_controller_test.exs index 5e19411d..6005a1f4 100644 --- a/test/auth_web/controllers/apikey_controller_test.exs +++ b/test/auth_web/controllers/apikey_controller_test.exs @@ -131,13 +131,13 @@ defmodule AuthWeb.ApikeyControllerTest do test "attempt to edit a key I don't own > should 404", %{conn: conn} do person = Auth.Person.get_person_by_email(@email) - wrong_person = - Auth.Person.create_person(%{ - email: "wrong@gmail.com", - auth_provider: "email" - }) - - conn = AuthPlug.create_jwt_session(conn, wrong_person) + wrong_person_data = %{ + email: "wronger@gmail.com", + auth_provider: "email", + id: 42 + } + Auth.Person.create_person(wrong_person_data) + conn = AuthPlug.create_jwt_session(conn, wrong_person_data) {:ok, key} = %{"name" => "test key", "url" => "http://localhost:4000"} @@ -152,7 +152,7 @@ defmodule AuthWeb.ApikeyControllerTest do describe "update apikey" do test "redirects when data is valid", %{conn: conn} do person = Auth.Person.get_person_by_email(@email) - conn = AuthPlug.create_jwt_session(conn, person) + conn = AuthPlug.create_jwt_session(conn, %{id: person.id}) {:ok, key} = %{"name" => "test key", "url" => "http://localhost:4000"} @@ -168,7 +168,7 @@ defmodule AuthWeb.ApikeyControllerTest do test "renders errors when data is invalid", %{conn: conn} do person = Auth.Person.get_person_by_email(@email) - conn = AuthPlug.create_jwt_session(conn, person) + conn = admin_login(conn) {:ok, key} = %{"name" => "test key", "url" => "http://localhost:4000"} @@ -182,13 +182,13 @@ defmodule AuthWeb.ApikeyControllerTest do test "attempt to UPDATE a key I don't own > should 404", %{conn: conn} do person = Auth.Person.get_person_by_email(@email) # create session with wrong person: - wrong_person = - Auth.Person.create_person(%{ - email: "wronger@gmail.com", - auth_provider: "email" - }) - - conn = AuthPlug.create_jwt_session(conn, wrong_person) + wrong_person_data = %{ + email: "wronger@gmail.com", + auth_provider: "email", + id: 42 + } + Auth.Person.create_person(wrong_person_data) + conn = AuthPlug.create_jwt_session(conn, wrong_person_data) {:ok, key} = %{"name" => "test key", "url" => "http://localhost:4000", "person_id" => person.id} @@ -203,7 +203,7 @@ defmodule AuthWeb.ApikeyControllerTest do describe "delete apikey" do test "deletes chosen apikey", %{conn: conn} do person = Auth.Person.get_person_by_email(@email) - conn = AuthPlug.create_jwt_session(conn, person) + conn = admin_login(conn) {:ok, key} = %{"name" => "test key", "url" => "http://localhost:4000"} @@ -219,13 +219,14 @@ defmodule AuthWeb.ApikeyControllerTest do end test "cannot delete a key belonging to someone else! 404", %{conn: conn} do - wrong_person = - Auth.Person.create_person(%{ - email: "wrongin@gmail.com", - auth_provider: "email" - }) - conn = AuthPlug.create_jwt_session(conn, wrong_person) + wrong_person_data = %{ + email: "wronger@gmail.com", + auth_provider: "email", + id: 42 + } + Auth.Person.create_person(wrong_person_data) + conn = AuthPlug.create_jwt_session(conn, wrong_person_data) person = Auth.Person.get_person_by_email(@email) {:ok, key} = diff --git a/test/auth_web/controllers/auth_controller_test.exs b/test/auth_web/controllers/auth_controller_test.exs index 74ff3c82..73effbcb 100644 --- a/test/auth_web/controllers/auth_controller_test.exs +++ b/test/auth_web/controllers/auth_controller_test.exs @@ -127,11 +127,11 @@ defmodule AuthWeb.AuthControllerTest do auth_provider: "google" } - person = Auth.Person.upsert_person(data) + Auth.Person.upsert_person(data) conn = - AuthPlug.create_jwt_session(conn, person) - |> get("/auth/google/callback", %{"code" => "234", "state" => nil}) + AuthPlug.create_jwt_session(conn, data) + |> get("/auth/google/callback", %{"code" => "234", "state" => nil}) assert html_response(conn, 200) =~ "Google account" end @@ -320,6 +320,7 @@ defmodule AuthWeb.AuthControllerTest do link = AuthWeb.AuthController.make_verify_link(conn, person, state) link = "/auth/verify" <> List.last(String.split(link, "/auth/verify")) + conn = get(conn, link, %{}) assert html_response(conn, 302) =~ "redirected" end diff --git a/test/test_helper.exs b/test/test_helper.exs index f0a5fa15..57aeb927 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -11,6 +11,12 @@ defmodule AuthTest do """ def admin_login(conn) do person = Auth.Person.get_person_by_email(@admin_email) - AuthPlug.create_jwt_session(conn, person) + data = %{ + id: person.id, + email: person.email, + auth_provider: person.auth_provider + } + # IO.inspect(person, label: "person") + AuthPlug.create_jwt_session(conn, data) end end From aed44b8b8a1a372f5b6a468420bc2366be87d555 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 21 Aug 2020 22:34:52 +0100 Subject: [PATCH 042/166] mix format --- lib/auth/person.ex | 9 +++++---- lib/auth_web/controllers/auth_controller.ex | 3 +-- lib/auth_web/controllers/role_controller.ex | 4 +--- test/auth/people_roles_test.exs | 3 ++- test/auth_web/controllers/apikey_controller_test.exs | 4 +++- test/auth_web/controllers/auth_controller_test.exs | 4 ++-- test/test_helper.exs | 2 ++ 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/auth/person.ex b/lib/auth/person.ex index c2dce850..d0a11036 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -65,7 +65,7 @@ defmodule Auth.Person do %Person{} |> changeset(person) |> put_email_status_verified() - |> put_assoc(:roles, [ Auth.Role.get_role!(6) ]) + |> put_assoc(:roles, [Auth.Role.get_role!(6)]) case get_person_by_email(person.changes.email) do nil -> @@ -164,8 +164,9 @@ defmodule Auth.Person do end def create_google_person(profile) do - person = transform_google_profile_data_to_person(profile) - |> upsert_person() + person = + transform_google_profile_data_to_person(profile) + |> upsert_person() Map.replace!(person, :roles, RBAC.transform_role_list_to_string(person.roles)) end @@ -202,9 +203,9 @@ defmodule Auth.Person do def verify_person_by_id(id) do person = get_person_by_id(id) + %{email: person.email, status: get_status_verified()} |> upsert_person() - end def get_person_by_id(id) do diff --git a/lib/auth_web/controllers/auth_controller.ex b/lib/auth_web/controllers/auth_controller.ex index b0c288b5..30b605d8 100644 --- a/lib/auth_web/controllers/auth_controller.ex +++ b/lib/auth_web/controllers/auth_controller.ex @@ -341,8 +341,7 @@ defmodule AuthWeb.AuthController do if changeset.valid? do person = Auth.Person.upsert_person(%{email: email, password: p["password"]}) # replace %Auth.Role{} struct with string github.com/dwyl/rbac/issues/4 - person = Map.replace!(person, :roles, - RBAC.transform_role_list_to_string(person.roles)) + person = Map.replace!(person, :roles, RBAC.transform_role_list_to_string(person.roles)) redirect_or_render(conn, person, p["state"]) else conn diff --git a/lib/auth_web/controllers/role_controller.ex b/lib/auth_web/controllers/role_controller.ex index 7e99f2d8..1e0b8399 100644 --- a/lib/auth_web/controllers/role_controller.ex +++ b/lib/auth_web/controllers/role_controller.ex @@ -5,7 +5,7 @@ defmodule AuthWeb.RoleController do def index(conn, _params) do roles = Role.list_roles() - IO.inspect(conn.assigns.person, label: "conn.assigns.person") + # IO.inspect(conn.assigns.person, label: "conn.assigns.person") render(conn, "index.html", roles: roles) end @@ -77,6 +77,4 @@ defmodule AuthWeb.RoleController do # AuthWeb.AuthController.unauthorized(conn) # end # end - - end diff --git a/test/auth/people_roles_test.exs b/test/auth/people_roles_test.exs index e1f110f7..1f07557d 100644 --- a/test/auth/people_roles_test.exs +++ b/test/auth/people_roles_test.exs @@ -8,7 +8,8 @@ defmodule AuthWeb.PeopleRolesTest do role_id = 4 # grant the "creator" role (id: 4) to the new person: Auth.PeopleRoles.insert(1, grantee.id, role_id) - person_with_role = Auth.Person.get_person_by_id(grantee.id) # |> IO.inspect() + # |> IO.inspect() + person_with_role = Auth.Person.get_person_by_id(grantee.id) roles = RBAC.transform_role_list_to_string(person_with_role.roles) assert roles =~ Integer.to_string(role_id) diff --git a/test/auth_web/controllers/apikey_controller_test.exs b/test/auth_web/controllers/apikey_controller_test.exs index 6005a1f4..1d717704 100644 --- a/test/auth_web/controllers/apikey_controller_test.exs +++ b/test/auth_web/controllers/apikey_controller_test.exs @@ -136,6 +136,7 @@ defmodule AuthWeb.ApikeyControllerTest do auth_provider: "email", id: 42 } + Auth.Person.create_person(wrong_person_data) conn = AuthPlug.create_jwt_session(conn, wrong_person_data) @@ -187,6 +188,7 @@ defmodule AuthWeb.ApikeyControllerTest do auth_provider: "email", id: 42 } + Auth.Person.create_person(wrong_person_data) conn = AuthPlug.create_jwt_session(conn, wrong_person_data) @@ -219,12 +221,12 @@ defmodule AuthWeb.ApikeyControllerTest do end test "cannot delete a key belonging to someone else! 404", %{conn: conn} do - wrong_person_data = %{ email: "wronger@gmail.com", auth_provider: "email", id: 42 } + Auth.Person.create_person(wrong_person_data) conn = AuthPlug.create_jwt_session(conn, wrong_person_data) person = Auth.Person.get_person_by_email(@email) diff --git a/test/auth_web/controllers/auth_controller_test.exs b/test/auth_web/controllers/auth_controller_test.exs index 73effbcb..592410e8 100644 --- a/test/auth_web/controllers/auth_controller_test.exs +++ b/test/auth_web/controllers/auth_controller_test.exs @@ -130,8 +130,8 @@ defmodule AuthWeb.AuthControllerTest do Auth.Person.upsert_person(data) conn = - AuthPlug.create_jwt_session(conn, data) - |> get("/auth/google/callback", %{"code" => "234", "state" => nil}) + AuthPlug.create_jwt_session(conn, data) + |> get("/auth/google/callback", %{"code" => "234", "state" => nil}) assert html_response(conn, 200) =~ "Google account" end diff --git a/test/test_helper.exs b/test/test_helper.exs index 57aeb927..368e8cf5 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -11,11 +11,13 @@ defmodule AuthTest do """ def admin_login(conn) do person = Auth.Person.get_person_by_email(@admin_email) + data = %{ id: person.id, email: person.email, auth_provider: person.auth_provider } + # IO.inspect(person, label: "person") AuthPlug.create_jwt_session(conn, data) end From 89acf448be3afb3d8f1071d3e190d616b558a252 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 21 Aug 2020 23:03:07 +0100 Subject: [PATCH 043/166] apply credo fixes --- lib/auth/person.ex | 4 +--- test/auth/people_roles_test.exs | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/auth/person.ex b/lib/auth/person.ex index d0a11036..d5838468 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -203,9 +203,7 @@ defmodule Auth.Person do def verify_person_by_id(id) do person = get_person_by_id(id) - - %{email: person.email, status: get_status_verified()} - |> upsert_person() + upsert_person(%{email: person.email, status: get_status_verified()}) end def get_person_by_id(id) do diff --git a/test/auth/people_roles_test.exs b/test/auth/people_roles_test.exs index 1f07557d..07844470 100644 --- a/test/auth/people_roles_test.exs +++ b/test/auth/people_roles_test.exs @@ -8,7 +8,6 @@ defmodule AuthWeb.PeopleRolesTest do role_id = 4 # grant the "creator" role (id: 4) to the new person: Auth.PeopleRoles.insert(1, grantee.id, role_id) - # |> IO.inspect() person_with_role = Auth.Person.get_person_by_id(grantee.id) roles = RBAC.transform_role_list_to_string(person_with_role.roles) assert roles =~ Integer.to_string(role_id) From f0a7782d2778ec2d7382d959569d6fc3074e94a6 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 22 Aug 2020 00:31:25 +0100 Subject: [PATCH 044/166] add :revoked field to people_roles migration and schema for #92 --- lib/auth/people_roles.ex | 1 + lib/auth/person.ex | 2 ++ priv/repo/migrations/20200723154847_create_people_roles.exs | 1 + 3 files changed, 4 insertions(+) diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex index 8bd083aa..ab7f4ee2 100644 --- a/lib/auth/people_roles.ex +++ b/lib/auth/people_roles.ex @@ -13,6 +13,7 @@ defmodule Auth.PeopleRoles do belongs_to :person, Auth.Person belongs_to :role, Auth.Role field :granter_id, :integer + field :revoked, :naive_datetime timestamps() end diff --git a/lib/auth/person.ex b/lib/auth/person.ex index d5838468..bc3a3e2f 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -65,7 +65,9 @@ defmodule Auth.Person do %Person{} |> changeset(person) |> put_email_status_verified() + # assign default role of "subscriber": |> put_assoc(:roles, [Auth.Role.get_role!(6)]) + # other roles can be assigned in the UI case get_person_by_email(person.changes.email) do nil -> diff --git a/priv/repo/migrations/20200723154847_create_people_roles.exs b/priv/repo/migrations/20200723154847_create_people_roles.exs index eb7cd985..df5abe8c 100644 --- a/priv/repo/migrations/20200723154847_create_people_roles.exs +++ b/priv/repo/migrations/20200723154847_create_people_roles.exs @@ -6,6 +6,7 @@ defmodule Auth.Repo.Migrations.CreatePeopleRoles do add :person_id, references(:people, on_delete: :nothing) add :role_id, references(:roles, on_delete: :nothing) add :granter_id, references(:people, on_delete: :nothing) + add :revoked, :naive_datetime timestamps() end From 3218d5c3f68ca29333834d51a0698d7dfe138ac7 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 22 Aug 2020 01:25:31 +0100 Subject: [PATCH 045/166] use RBAC v.0.3.0 containing check for :revoked roles #92 --- lib/auth/people_roles.ex | 16 ++++++++++++++++ lib/auth/person.ex | 6 +----- mix.exs | 2 +- mix.lock | 2 +- .../20200723154847_create_people_roles.exs | 1 + test/auth/people_roles_test.exs | 14 ++------------ .../controllers/role_controller_test.exs | 12 ++++++++++++ 7 files changed, 34 insertions(+), 19 deletions(-) diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex index ab7f4ee2..f7fdf229 100644 --- a/lib/auth/people_roles.ex +++ b/lib/auth/people_roles.ex @@ -14,6 +14,7 @@ defmodule Auth.PeopleRoles do belongs_to :role, Auth.Role field :granter_id, :integer field :revoked, :naive_datetime + field :revoker_id, :integer timestamps() end @@ -39,4 +40,19 @@ defmodule Auth.PeopleRoles do |> put_assoc(:role, Auth.Role.get_role!(role_id)) |> Repo.insert() end + + @doc """ + revoke/3 grants a role to the given person + granter_id is the id of the person (admin) granting the role + grantee_id is the person.id of the person being granted the role + role_id is the role.id (int, e.g: 4) of th role being granted. + """ + def revoke(granter_id, grantee_id, role_id) do + %PeopleRoles{} + |> cast(%{granter_id: granter_id}, [:granter_id]) + |> put_assoc(:person, Auth.Person.get_person_by_id(grantee_id)) + |> put_assoc(:role, Auth.Role.get_role!(role_id)) + |> Repo.insert() + end + end diff --git a/lib/auth/person.ex b/lib/auth/person.ex index bc3a3e2f..f86bb341 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -246,11 +246,7 @@ defmodule Auth.Person do # existing person ep -> merged = Map.merge(AuthPlug.Helpers.strip_struct_metadata(ep), person) - - {:ok, person} = - changeset(%Person{id: ep.id}, merged) - |> Repo.update() - + {:ok, person} = Repo.update(changeset(%Person{id: ep.id}, merged)) # ensure that the preloads are returned: get_person_by_email(person.email) end diff --git a/mix.exs b/mix.exs index bfeb7cf2..b771494c 100644 --- a/mix.exs +++ b/mix.exs @@ -64,7 +64,7 @@ defmodule Auth.Mixfile do # https://github.com/dwyl/auth_plug {:auth_plug, "1.2.0"}, # https://github.com/dwyl/rbac - {:rbac, "~> 0.2.0"}, + {:rbac, "~> 0.3.0"}, # Field Validation and Encryption: github.com/dwyl/fields {:fields, "~> 2.6.0"}, diff --git a/mix.lock b/mix.lock index 3d5f2dd0..e000f255 100644 --- a/mix.lock +++ b/mix.lock @@ -52,7 +52,7 @@ "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, "postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, - "rbac": {:hex, :rbac, "0.2.0", "ed77a2bcad9e8bc4879d887a288a216be85641f06cecd0d2a84eaadc97c2ade8", [:mix], [], "hexpm", "78f37cffcff1675957c2c510d2052e5e9a99053ec205e3c695609dcc9cd306bd"}, + "rbac": {:hex, :rbac, "0.3.0", "81ef1f898da2da61ed3b054822b98915005b1b07ed1b7d32071d41cd3ce93d84", [:mix], [], "hexpm", "ccba5f1bd58c351e58596ca3fbfd1e49391e4e6aedfac0ae08f54baea43a66dc"}, "sobelow": {:hex, :sobelow, "0.10.2", "00e91208046d3b434f9f08779fe0ca7c6d6595b7fa33b289e792dffa6dde8081", [:mix], [], "hexpm", "e30fc994330cf6f485c1c4f2fb7c4b2d403557d0e101c6e5329fd17a58e55a7e"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "stream_data": {:hex, :stream_data, "0.4.3", "62aafd870caff0849a5057a7ec270fad0eb86889f4d433b937d996de99e3db25", [:mix], [], "hexpm", "7dafd5a801f0bc897f74fcd414651632b77ca367a7ae4568778191fc3bf3a19a"}, diff --git a/priv/repo/migrations/20200723154847_create_people_roles.exs b/priv/repo/migrations/20200723154847_create_people_roles.exs index df5abe8c..ea14dc14 100644 --- a/priv/repo/migrations/20200723154847_create_people_roles.exs +++ b/priv/repo/migrations/20200723154847_create_people_roles.exs @@ -7,6 +7,7 @@ defmodule Auth.Repo.Migrations.CreatePeopleRoles do add :role_id, references(:roles, on_delete: :nothing) add :granter_id, references(:people, on_delete: :nothing) add :revoked, :naive_datetime + add :revoker_id, references(:people, on_delete: :nothing) timestamps() end diff --git a/test/auth/people_roles_test.exs b/test/auth/people_roles_test.exs index 07844470..2eefdb6f 100644 --- a/test/auth/people_roles_test.exs +++ b/test/auth/people_roles_test.exs @@ -13,22 +13,12 @@ defmodule AuthWeb.PeopleRolesTest do assert roles =~ Integer.to_string(role_id) # check the latest people_roles record: - list = Auth.PeopleRoles.list_people_roles() + list = Auth.PeopleRoles.list_people_roles() |> IO.inspect() # IO.inspect(list, label: "list") pr = List.last(list) assert pr.granter_id == 1 assert pr.person_id == grantee.id end - # test "attempt to grant_role/3 without admin should 401", %{conn: conn} do - # alex = %{email: "alex_grant_role_fail@gmail.com", auth_provider: "email"} - # grantee = Auth.Person.create_person(alex) - # conn = assign(conn, :person, grantee) - # role_id = 4 - # conn = Auth.PeopleRoles.insert(conn, grantee.id, role_id) - # assert conn.status == 401 - # end - # test "get list of roles" do - # Auth.Role.list_roles() |> IO.inspect() - # end + # test "Auth.PeopleRoles.revoke/3 " end diff --git a/test/auth_web/controllers/role_controller_test.exs b/test/auth_web/controllers/role_controller_test.exs index 5bb16972..2541416b 100644 --- a/test/auth_web/controllers/role_controller_test.exs +++ b/test/auth_web/controllers/role_controller_test.exs @@ -103,4 +103,16 @@ defmodule AuthWeb.RoleControllerTest do role = fixture(:role) %{role: role} end + + # test "attempt to grant_role/3 without admin should 401", %{conn: conn} do + # alex = %{email: "alex_grant_role_fail@gmail.com", auth_provider: "email"} + # grantee = Auth.Person.create_person(alex) + # conn = assign(conn, :person, grantee) + # role_id = 4 + # conn = Auth.PeopleRoles.insert(conn, grantee.id, role_id) + # assert conn.status == 401 + # end + # test "get list of roles" do + # Auth.Role.list_roles() |> IO.inspect() + # end end From 361648db54359ee72a0cd771dc8fe851f0c41919 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 22 Aug 2020 01:54:18 +0100 Subject: [PATCH 046/166] create Auth.PeopleRoles.revoke/3 + tests for #92 --- lib/auth/people_roles.ex | 36 ++++++++++++++----- .../20200723154847_create_people_roles.exs | 3 +- priv/repo/seeds.exs | 1 + test/auth/people_roles_test.exs | 34 ++++++++++++++++-- 4 files changed, 62 insertions(+), 12 deletions(-) diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex index f7fdf229..a4f0f032 100644 --- a/lib/auth/people_roles.ex +++ b/lib/auth/people_roles.ex @@ -13,7 +13,7 @@ defmodule Auth.PeopleRoles do belongs_to :person, Auth.Person belongs_to :role, Auth.Role field :granter_id, :integer - field :revoked, :naive_datetime + field :revoked, :utc_datetime field :revoker_id, :integer timestamps() @@ -27,6 +27,19 @@ defmodule Auth.PeopleRoles do Repo.all(from pr in __MODULE__, preload: [:person, :role]) end + @doc """ + get_record/0 returns the record where the person was granted a role. + """ + def get_record(person_id, role_id) do + # Repo.all(from pr in __MODULE__, preload: [:person, :role]) + from(pr in __MODULE__, + where: pr.person_id == ^person_id + and pr.role_id == ^role_id, + preload: [:person, :role] + ) + |> Repo.one() + end + @doc """ insert/3 grants a role to the given person granter_id is the id of the person (admin) granting the role @@ -43,16 +56,23 @@ defmodule Auth.PeopleRoles do @doc """ revoke/3 grants a role to the given person - granter_id is the id of the person (admin) granting the role + revoker_id is the id of the person (admin) granting the role grantee_id is the person.id of the person being granted the role role_id is the role.id (int, e.g: 4) of th role being granted. """ - def revoke(granter_id, grantee_id, role_id) do - %PeopleRoles{} - |> cast(%{granter_id: granter_id}, [:granter_id]) - |> put_assoc(:person, Auth.Person.get_person_by_id(grantee_id)) - |> put_assoc(:role, Auth.Role.get_role!(role_id)) - |> Repo.insert() + def revoke(revoker_id, person_id, role_id) do + # get the people_role record that needs to be updated (revoked) + record = get_record(person_id, role_id) + + record + # |> Map.delete(:__meta__) + |> cast( + %{revoker_id: revoker_id, revoked: DateTime.utc_now}, + [:revoker_id, :revoked] + ) + # |> put_assoc(:person, Auth.Person.get_person_by_id(person_id)) + # |> put_assoc(:role, Auth.Role.get_role!(role_id)) + |> Repo.update() end end diff --git a/priv/repo/migrations/20200723154847_create_people_roles.exs b/priv/repo/migrations/20200723154847_create_people_roles.exs index ea14dc14..f71062b8 100644 --- a/priv/repo/migrations/20200723154847_create_people_roles.exs +++ b/priv/repo/migrations/20200723154847_create_people_roles.exs @@ -6,7 +6,8 @@ defmodule Auth.Repo.Migrations.CreatePeopleRoles do add :person_id, references(:people, on_delete: :nothing) add :role_id, references(:roles, on_delete: :nothing) add :granter_id, references(:people, on_delete: :nothing) - add :revoked, :naive_datetime + # elixirforum.com/t/difference-between-utc-datetime-and-naive-datetime/12551 + add :revoked, :utc_datetime add :revoker_id, references(:people, on_delete: :nothing) timestamps() diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index a8c39ebd..41152667 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -122,4 +122,5 @@ defmodule SetupRoles do end SetupRoles.create_default_roles() +# grant superadmin role to app owner: Auth.PeopleRoles.insert(1, 1, 1) diff --git a/test/auth/people_roles_test.exs b/test/auth/people_roles_test.exs index 2eefdb6f..7fc7c15c 100644 --- a/test/auth/people_roles_test.exs +++ b/test/auth/people_roles_test.exs @@ -8,17 +8,45 @@ defmodule AuthWeb.PeopleRolesTest do role_id = 4 # grant the "creator" role (id: 4) to the new person: Auth.PeopleRoles.insert(1, grantee.id, role_id) + + # confirm people_roles record exists: + record = Auth.PeopleRoles.get_record(grantee.id, role_id) + assert record.person_id == grantee.id + assert record.role_id == role_id + assert record.granter_id == 1 + + # confirm person record has roles preloaded: person_with_role = Auth.Person.get_person_by_id(grantee.id) roles = RBAC.transform_role_list_to_string(person_with_role.roles) assert roles =~ Integer.to_string(role_id) # check the latest people_roles record: - list = Auth.PeopleRoles.list_people_roles() |> IO.inspect() - # IO.inspect(list, label: "list") + list = Auth.PeopleRoles.list_people_roles() pr = List.last(list) assert pr.granter_id == 1 assert pr.person_id == grantee.id end - # test "Auth.PeopleRoles.revoke/3 " + test "Auth.PeopleRoles.revoke/3 revokes a role" do + # create a new person: + alex = %{email: "alex_revoke@gmail.com", auth_provider: "email"} + grantee = Auth.Person.create_person(alex) + role_id = 3 + # grant the role to the new person: + Auth.PeopleRoles.insert(1, grantee.id, role_id) + + # confirm people_roles record exists: + record = Auth.PeopleRoles.get_record(grantee.id, role_id) + assert record.person_id == grantee.id + assert record.role_id == role_id + assert record.granter_id == 1 + + # revoke the role! + Auth.PeopleRoles.revoke(1, grantee.id, role_id) + + # confirm people_roles record was updated: + record = Auth.PeopleRoles.get_record(grantee.id, role_id) + assert record.revoker_id == 1 + assert not is_nil(record.revoked) + end end From d27444ba16cbd79bdf878d114bead6d8c34ca6f1 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 22 Aug 2020 01:54:53 +0100 Subject: [PATCH 047/166] mix format --- lib/auth/people_roles.ex | 12 ++++++------ lib/auth/person.ex | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex index a4f0f032..72ec8c33 100644 --- a/lib/auth/people_roles.ex +++ b/lib/auth/people_roles.ex @@ -33,10 +33,11 @@ defmodule Auth.PeopleRoles do def get_record(person_id, role_id) do # Repo.all(from pr in __MODULE__, preload: [:person, :role]) from(pr in __MODULE__, - where: pr.person_id == ^person_id - and pr.role_id == ^role_id, + where: + pr.person_id == ^person_id and + pr.role_id == ^role_id, preload: [:person, :role] - ) + ) |> Repo.one() end @@ -57,7 +58,7 @@ defmodule Auth.PeopleRoles do @doc """ revoke/3 grants a role to the given person revoker_id is the id of the person (admin) granting the role - grantee_id is the person.id of the person being granted the role + person_id is the person.id of the person being granted the role role_id is the role.id (int, e.g: 4) of th role being granted. """ def revoke(revoker_id, person_id, role_id) do @@ -67,12 +68,11 @@ defmodule Auth.PeopleRoles do record # |> Map.delete(:__meta__) |> cast( - %{revoker_id: revoker_id, revoked: DateTime.utc_now}, + %{revoker_id: revoker_id, revoked: DateTime.utc_now()}, [:revoker_id, :revoked] ) # |> put_assoc(:person, Auth.Person.get_person_by_id(person_id)) # |> put_assoc(:role, Auth.Role.get_role!(role_id)) |> Repo.update() end - end diff --git a/lib/auth/person.ex b/lib/auth/person.ex index f86bb341..95d1ac32 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -67,7 +67,8 @@ defmodule Auth.Person do |> put_email_status_verified() # assign default role of "subscriber": |> put_assoc(:roles, [Auth.Role.get_role!(6)]) - # other roles can be assigned in the UI + + # other roles can be assigned in the UI case get_person_by_email(person.changes.email) do nil -> From b652d34a23251c26b44b0c5653157579019d2d0b Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 22 Aug 2020 01:59:15 +0100 Subject: [PATCH 048/166] tidy up revoke/3 function for #92 --- lib/auth/people_roles.ex | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex index 72ec8c33..8f57a261 100644 --- a/lib/auth/people_roles.ex +++ b/lib/auth/people_roles.ex @@ -31,14 +31,12 @@ defmodule Auth.PeopleRoles do get_record/0 returns the record where the person was granted a role. """ def get_record(person_id, role_id) do - # Repo.all(from pr in __MODULE__, preload: [:person, :role]) - from(pr in __MODULE__, - where: - pr.person_id == ^person_id and - pr.role_id == ^role_id, - preload: [:person, :role] + Repo.one( + from(pr in __MODULE__, + where: pr.person_id == ^person_id and pr.role_id == ^role_id, + preload: [:person, :role] + ) ) - |> Repo.one() end @doc """ @@ -63,16 +61,11 @@ defmodule Auth.PeopleRoles do """ def revoke(revoker_id, person_id, role_id) do # get the people_role record that needs to be updated (revoked) - record = get_record(person_id, role_id) - - record - # |> Map.delete(:__meta__) + get_record(person_id, role_id) |> cast( %{revoker_id: revoker_id, revoked: DateTime.utc_now()}, [:revoker_id, :revoked] ) - # |> put_assoc(:person, Auth.Person.get_person_by_id(person_id)) - # |> put_assoc(:role, Auth.Role.get_role!(role_id)) |> Repo.update() end end From 0f4596675e23664e57d0bc354ed14162945a4e1f Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 22 Aug 2020 02:01:14 +0100 Subject: [PATCH 049/166] inline function calls to keep @sourcelevel-bot happy https://github.com/dwyl/auth/pull/85#pullrequestreview-472831613 --- lib/auth/person.ex | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/auth/person.ex b/lib/auth/person.ex index 95d1ac32..647fde2e 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -124,7 +124,7 @@ defmodule Auth.Person do end def create_github_person(profile) do - transform_github_profile_data_to_person(profile) |> upsert_person() + upsert_person(transform_github_profile_data_to_person(profile)) end @doc """ @@ -167,10 +167,7 @@ defmodule Auth.Person do end def create_google_person(profile) do - person = - transform_google_profile_data_to_person(profile) - |> upsert_person() - + person = upsert_person(transform_google_profile_data_to_person(profile)) Map.replace!(person, :roles, RBAC.transform_role_list_to_string(person.roles)) end From 7310d6088e5f20c3ab7d04fe46f60cf10acc53fb Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 22 Aug 2020 23:56:29 +0100 Subject: [PATCH 050/166] create list_people/0 function for #93 --- lib/auth/person.ex | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/auth/person.ex b/lib/auth/person.ex index 647fde2e..e60470ea 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -4,6 +4,7 @@ defmodule Auth.Person do """ use Ecto.Schema import Ecto.Changeset + import Ecto.Query alias Auth.Repo # https://stackoverflow.com/a/47501059/1148249 alias __MODULE__ @@ -262,4 +263,45 @@ defmodule Auth.Person do 0 end end + + + @doc """ + `list_people/0` lists all people in the system. + Used for displaying the table of authenticated people. + """ + def list_people do + + Repo.all( + from(pr in __MODULE__, preload: [:roles, :statuses] + ) + ) + + # query = """ + # SELECT DISTINCT ON (s.status_id, s.person_id) s.id, s.message_id, + # s.updated_at, s.template, st.text as status, s.person_id + # FROM sent s + # JOIN status as st on s.status_id = st.id + # WHERE s.message_id IS NOT NULL + # """ + # {:ok, result} = Repo.query(query) + + # # create List of Maps from the result.rows: + # Enum.map(result.rows, fn([id, mid, iat, t, s, pid]) -> + # # e = Fields.AES.decrypt(e) + # # e = case e !== :error and e =~ "@" do + # # true -> e |> String.split("@") |> List.first + # # false -> e + # # end + # %{ + # id: id, + # message_id: mid, + # updated_at: NaiveDateTime.truncate(iat, :second), + # template: t, + # status: s, + # person_id: pid, + # email: "" + # } + # end) + # |> Enum.sort(&(&1.id > &2.id)) + end end From ffca1fa4a34e1040c3e40a862d5a27143877219f Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sat, 22 Aug 2020 23:57:15 +0100 Subject: [PATCH 051/166] create /people controller, view and templtes for #93 --- lib/auth_web/controllers/people_controller.ex | 14 ++++++++++++++ lib/auth_web/router.ex | 1 + lib/auth_web/templates/people/index.html.eex | 16 ++++++++++++++++ lib/auth_web/views/people_view.ex | 3 +++ 4 files changed, 34 insertions(+) create mode 100644 lib/auth_web/controllers/people_controller.ex create mode 100644 lib/auth_web/templates/people/index.html.eex create mode 100644 lib/auth_web/views/people_view.ex diff --git a/lib/auth_web/controllers/people_controller.ex b/lib/auth_web/controllers/people_controller.ex new file mode 100644 index 00000000..2db8abb4 --- /dev/null +++ b/lib/auth_web/controllers/people_controller.ex @@ -0,0 +1,14 @@ +defmodule AuthWeb.PeopleController do + @moduledoc """ + Defines People controller functions + """ + use AuthWeb, :controller + + @doc """ + `index/2` lists all the people who have authenticated with the auth app. + """ + def index(conn, _params) do + conn + |> render(:index) + end +end diff --git a/lib/auth_web/router.ex b/lib/auth_web/router.ex index 7e798df6..d14f728f 100644 --- a/lib/auth_web/router.ex +++ b/lib/auth_web/router.ex @@ -40,6 +40,7 @@ defmodule AuthWeb.Router do pipe_through :browser pipe_through :auth + get "/people", PeopleController, :index get "/profile", AuthController, :admin resources "/roles", RoleController resources "/permissions", PermissionController diff --git a/lib/auth_web/templates/people/index.html.eex b/lib/auth_web/templates/people/index.html.eex new file mode 100644 index 00000000..3b055518 --- /dev/null +++ b/lib/auth_web/templates/people/index.html.eex @@ -0,0 +1,16 @@ +
+

Welcome <%= @conn.assigns.person.givenName %>! +

+ +

You are signed in + with your <%= String.capitalize(@conn.assigns.person.auth_provider) %> account. +

+ +
+ + Manage API Keys + +

diff --git a/lib/auth_web/views/people_view.ex b/lib/auth_web/views/people_view.ex new file mode 100644 index 00000000..d6cc6d72 --- /dev/null +++ b/lib/auth_web/views/people_view.ex @@ -0,0 +1,3 @@ +defmodule AuthWeb.PeopleView do + use AuthWeb, :view +end From f4aeb673abda6ff614ba3393cad8fcd41c68419d Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sun, 23 Aug 2020 22:13:39 +0100 Subject: [PATCH 052/166] add status_string to /people table for #93 --- lib/auth/person.ex | 9 +-- lib/auth/status.ex | 4 ++ lib/auth_web/controllers/people_controller.ex | 12 +++- lib/auth_web/templates/people/index.html.eex | 62 +++++++++++++++---- lib/auth_web/views/people_view.ex | 22 +++++++ 5 files changed, 88 insertions(+), 21 deletions(-) diff --git a/lib/auth/person.ex b/lib/auth/person.ex index e60470ea..b4944b7b 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -163,7 +163,8 @@ defmodule Auth.Person do Map.merge(profile, %{ familyName: profile.family_name, givenName: profile.given_name, - auth_provider: "google" + auth_provider: "google", + status: 1 }) end @@ -270,11 +271,7 @@ defmodule Auth.Person do Used for displaying the table of authenticated people. """ def list_people do - - Repo.all( - from(pr in __MODULE__, preload: [:roles, :statuses] - ) - ) + Repo.all(from(pr in __MODULE__, preload: [:roles, :statuses])) # query = """ # SELECT DISTINCT ON (s.status_id, s.person_id) s.id, s.message_id, diff --git a/lib/auth/status.ex b/lib/auth/status.ex index 9228d4dd..fe3f4ceb 100644 --- a/lib/auth/status.ex +++ b/lib/auth/status.ex @@ -38,4 +38,8 @@ defmodule Auth.Status do status end end + + def list_statuses do + Repo.all(__MODULE__) + end end diff --git a/lib/auth_web/controllers/people_controller.ex b/lib/auth_web/controllers/people_controller.ex index 2db8abb4..7eabcaf0 100644 --- a/lib/auth_web/controllers/people_controller.ex +++ b/lib/auth_web/controllers/people_controller.ex @@ -8,7 +8,15 @@ defmodule AuthWeb.PeopleController do `index/2` lists all the people who have authenticated with the auth app. """ def index(conn, _params) do - conn - |> render(:index) + if conn.assigns.person.id == 1 do + render(conn, :index, + people: Auth.Person.list_people(), + roles: Auth.Role.list_roles(), + statuses: Auth.Status.list_statuses() + ) + # Note: this can easily be refactored to save on DB queries. #HelpWanted + else + AuthWeb.AuthController.unauthorized(conn) + end end end diff --git a/lib/auth_web/templates/people/index.html.eex b/lib/auth_web/templates/people/index.html.eex index 3b055518..b9de221a 100644 --- a/lib/auth_web/templates/people/index.html.eex +++ b/lib/auth_web/templates/people/index.html.eex @@ -1,16 +1,52 @@
-

Welcome <%= @conn.assigns.person.givenName %>! -

- -

You are signed in - with your <%= String.capitalize(@conn.assigns.person.auth_provider) %> account. -

+

People Authenticated with Auth

-
- - Manage API Keys - + + + + + + + + + + + + + + + <%= for {p, idx} <- Enum.with_index(@people) do %> + + + + + + + + + + <% end %> + +
IDPicNameStatusTimeEmailAuth Provider
<%= p.id %><%= p.givenName %><%= status_string(p.status, @statuses) %><%= p.updated_at %><%= p.email %><%= String.capitalize(p.auth_provider) %>
+ + + + + + + + + \ No newline at end of file diff --git a/lib/auth_web/views/people_view.ex b/lib/auth_web/views/people_view.ex index d6cc6d72..0267e55e 100644 --- a/lib/auth_web/views/people_view.ex +++ b/lib/auth_web/views/people_view.ex @@ -1,3 +1,25 @@ defmodule AuthWeb.PeopleView do use AuthWeb, :view + + def status_string(status_id, statuses) do + if status_id != nil do + status = Enum.filter(statuses, fn s -> + s.id == status_id + end) |> Enum.at(0) + status.text + else + "none" + end + end + + def role_string(role_id, roles) do + if role_id != nil do + role = Enum.filter(roles, fn r -> + r.id == role_id + end) |> Enum.at(0) + role.name + else + "none" + end + end end From 0b1848d1f29fb19aaa3e3a7576b89b9a3a812736 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sun, 23 Aug 2020 23:16:19 +0100 Subject: [PATCH 053/166] display roles in /people table #93 --- lib/auth_web/templates/people/index.html.eex | 5 +++++ lib/auth_web/views/people_view.ex | 23 ++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/auth_web/templates/people/index.html.eex b/lib/auth_web/templates/people/index.html.eex index b9de221a..5d391c48 100644 --- a/lib/auth_web/templates/people/index.html.eex +++ b/lib/auth_web/templates/people/index.html.eex @@ -12,6 +12,7 @@ Email Auth Provider + Roles @@ -25,6 +26,9 @@ <%= p.updated_at %> <%= p.email %> <%= String.capitalize(p.auth_provider) %> + <%= role_string(p.roles) %> + + <% end %> @@ -33,6 +37,7 @@ \ No newline at end of file diff --git a/lib/auth_web/views/people_view.ex b/lib/auth_web/views/people_view.ex index ae56b018..7be52ceb 100644 --- a/lib/auth_web/views/people_view.ex +++ b/lib/auth_web/views/people_view.ex @@ -1,6 +1,9 @@ defmodule AuthWeb.PeopleView do use AuthWeb, :view + @doc """ + status_string/2 returns a string of status + """ def status_string(status_id, statuses) do if status_id != nil do status = Enum.filter(statuses, fn s -> @@ -12,6 +15,9 @@ defmodule AuthWeb.PeopleView do end end + @doc """ + role_string/1 returns a string of all the role names + """ def role_string(person_roles) do Enum.map_join(person_roles, " ", fn r -> r.name From 7ac38b3634158e2bcd4fb123adffd69caa22376c Mon Sep 17 00:00:00 2001 From: nelsonic Date: Mon, 24 Aug 2020 21:58:07 +0100 Subject: [PATCH 057/166] [WiP] create revoke.html.eex and /roles/revoke/:id + handler for #94 --- lib/auth/people_roles.ex | 18 +++++++++++++++--- lib/auth_web/controllers/role_controller.ex | 10 +++++----- lib/auth_web/router.ex | 3 ++- lib/auth_web/templates/people/profile.html.eex | 6 +++--- lib/auth_web/templates/role/revoke.html.eex | 11 ++++++++++- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/lib/auth/people_roles.ex b/lib/auth/people_roles.ex index 19edb1b4..1815f147 100644 --- a/lib/auth/people_roles.ex +++ b/lib/auth/people_roles.ex @@ -28,7 +28,7 @@ defmodule Auth.PeopleRoles do end @doc """ - get_record/0 returns the record where the person was granted a role. + get_record/2 returns the record where the person was granted a role. """ def get_record(person_id, role_id) do Repo.one( @@ -39,6 +39,18 @@ defmodule Auth.PeopleRoles do ) end + @doc """ + get_by_id!/1 returns the record with the given people_roles.id. + """ + def get_by_id(id) do + Repo.one( + from(pr in __MODULE__, + where: pr.id == ^id, + preload: [:person, :role] + ) + ) + end + @doc """ get_roles_for_person/1 returns the list of roles for a given person.id """ @@ -72,9 +84,9 @@ defmodule Auth.PeopleRoles do person_id is the person.id of the person being granted the role role_id is the role.id (int, e.g: 4) of th role being granted. """ - def revoke(revoker_id, person_id, role_id) do + def revoke(revoker_id, people_roles_id) do # get the people_role record that needs to be updated (revoked) - get_record(person_id, role_id) + get_by_id(people_roles_id) |> cast( %{revoker_id: revoker_id, revoked: DateTime.utc_now()}, [:revoker_id, :revoked] diff --git a/lib/auth_web/controllers/role_controller.ex b/lib/auth_web/controllers/role_controller.ex index d7ccbb61..66f2ab47 100644 --- a/lib/auth_web/controllers/role_controller.ex +++ b/lib/auth_web/controllers/role_controller.ex @@ -87,14 +87,14 @@ defmodule AuthWeb.RoleController do # confirm that the granter is either superadmin (conn.assigns.person.id == 1) # or has an "admin" role (1 || 2) if conn.assigns.person.id == 1 do - pr = Auth.PeopleRoles.get_record( - Map.get(params, "person_id"), Map.get(params, "role_id") - ) + people_roles_id = Map.get(params, "people_roles_id") + pr = Auth.PeopleRoles.get_by_id(people_roles_id) IO.inspect(pr, label: "pr") if conn.method == "GET" do - render(conn, "revoke.html", role: pr) + render(conn, "revoke.html", role: pr, people_roles_id: people_roles_id) else - + Auth.PeopleRoles.revoke(conn.assigns.person.id, people_roles_id) + redirect(conn, to: Routes.people_path(conn, :show, pr.person_id)) end else AuthWeb.AuthController.unauthorized(conn) diff --git a/lib/auth_web/router.ex b/lib/auth_web/router.ex index bad3fc22..905f794f 100644 --- a/lib/auth_web/router.ex +++ b/lib/auth_web/router.ex @@ -44,7 +44,8 @@ defmodule AuthWeb.Router do get "/people/:person_id", PeopleController, :show get "/profile", AuthController, :admin resources "/roles", RoleController - get "/roles/revoke/:role_id/:person_id", RoleController, :revoke + get "/roles/revoke/:people_roles_id", RoleController, :revoke + post "/roles/revoke/:people_roles_id", RoleController, :revoke resources "/permissions", PermissionController resources "/settings/apikeys", ApikeyController end diff --git a/lib/auth_web/templates/people/profile.html.eex b/lib/auth_web/templates/people/profile.html.eex index 9204f45b..fde1bba0 100644 --- a/lib/auth_web/templates/people/profile.html.eex +++ b/lib/auth_web/templates/people/profile.html.eex @@ -12,7 +12,7 @@
Last login:
<%= @person.updated_at %>
-

Role Admin

+

Roles

@@ -21,7 +21,7 @@ - + @@ -41,7 +41,7 @@ <%= if Map.has_key?(r, :revoked) and not is_nil(r.revoked) do %> <%= r.revoked %> <% else %> - Revoke Role diff --git a/lib/auth_web/templates/role/revoke.html.eex b/lib/auth_web/templates/role/revoke.html.eex index 884f1196..ac3a0282 100644 --- a/lib/auth_web/templates/role/revoke.html.eex +++ b/lib/auth_web/templates/role/revoke.html.eex @@ -1,5 +1,14 @@
-

Are Your Sure You Want to Revoke this Role?

+

Are your sure you want + to revoke the + <%= @role.role.name %> role + from <%= @role.person.givenName %> + ?

+ + <%= form_for :revoke, Routes.role_path(AuthWeb.Endpoint, :revoke, @people_roles_id), fn f -> %> + <%= submit "Revoke", class: "pointer bn bg-red white f2 pa2 mt1 shadow-hover bg-animate" %> + <% end %> +
+<%= form_tag("/roles/grant/", method: "get", class: "w-30 tc center mt2") do %> + + + <%= submit "Grant Role", class: "pointer bn bg-green white f2 pa2 mt1 shadow-hover bg-animate pa3 br1" %> +<% end %> + \ No newline at end of file diff --git a/lib/auth_web/views/people_view.ex b/lib/auth_web/views/people_view.ex index a2799729..ef3aa43a 100644 --- a/lib/auth_web/views/people_view.ex +++ b/lib/auth_web/views/people_view.ex @@ -25,9 +25,9 @@ defmodule AuthWeb.PeopleView do end @doc """ - upcaseFirst/2 captalises the first character of a string. - stackoverflow.com/questions/58672621/elixir-upcase-only-first-letter - got: (FunctionClauseError) no function clause matching in String.capitalize/2 + capitalize/1 captalises the first character of a string. + checks for nil values to avoid seeing the following error: + (FunctionClauseError) no function clause matching in String.capitalize/2 """ def capitalize(str) do if is_nil(str) do diff --git a/test/auth_web/controllers/people_controller_test.exs b/test/auth_web/controllers/people_controller_test.exs index ed6aaf00..c2a998fa 100644 --- a/test/auth_web/controllers/people_controller_test.exs +++ b/test/auth_web/controllers/people_controller_test.exs @@ -3,9 +3,7 @@ defmodule AuthWeb.PeopleControllerTest do # @email System.get_env("ADMIN_EMAIL") test "GET /people displays list of people", %{conn: conn} do - conn = admin_login(conn) - |> get("/people") - + conn = get(admin_login(conn)"/people") assert html_response(conn, 200) =~ "People Authenticated" end From a22dd7b2c35fe40cc3dcaa7b91846d7775752ddb Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 26 Aug 2020 08:22:11 +0100 Subject: [PATCH 060/166] add test for POST /roles/grant for https://github.com/dwyl/auth/issues/94#issuecomment-680125158 --- lib/auth_web/controllers/role_controller.ex | 20 ++++------ lib/auth_web/templates/role/revoke.html.eex | 2 +- .../controllers/people_controller_test.exs | 2 +- .../controllers/role_controller_test.exs | 38 +++++++++++++------ 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/lib/auth_web/controllers/role_controller.ex b/lib/auth_web/controllers/role_controller.ex index 30e8be7a..5947c60c 100644 --- a/lib/auth_web/controllers/role_controller.ex +++ b/lib/auth_web/controllers/role_controller.ex @@ -70,18 +70,14 @@ defmodule AuthWeb.RoleController do # confirm that the granter is either superadmin (conn.assigns.person.id == 1) # or has an "admin" role (1 || 2) granter_id = conn.assigns.person.id - IO.inspect(conn, label: "conn") - IO.inspect(params, label: "params") - role_id = Map.get(params, "role_id") - person_id = Map.get(params, "person_id") - Auth.PeopleRoles.insert(granter_id, person_id, role_id) - # redirect(conn, to: Routes.people_path(conn, :index)) - redirect(conn, to: Routes.people_path(conn, :show, person_id)) - # if granter.id == 1 do - # conn - # else - # AuthWeb.AuthController.unauthorized(conn) - # end + if granter_id == 1 do + role_id = Map.get(params, "role_id") + person_id = Map.get(params, "person_id") + Auth.PeopleRoles.insert(granter_id, person_id, role_id) + redirect(conn, to: Routes.people_path(conn, :show, person_id)) + else + AuthWeb.AuthController.unauthorized(conn) + end end @doc """ diff --git a/lib/auth_web/templates/role/revoke.html.eex b/lib/auth_web/templates/role/revoke.html.eex index ac3a0282..0d2895af 100644 --- a/lib/auth_web/templates/role/revoke.html.eex +++ b/lib/auth_web/templates/role/revoke.html.eex @@ -5,7 +5,7 @@ from <%= @role.person.givenName %> ?

- <%= form_for :revoke, Routes.role_path(AuthWeb.Endpoint, :revoke, @people_roles_id), fn f -> %> + <%= form_for :revoke, Routes.role_path(AuthWeb.Endpoint, :revoke, @people_roles_id), fn _f -> %> <%= submit "Revoke", class: "pointer bn bg-red white f2 pa2 mt1 shadow-hover bg-animate" %> <% end %> diff --git a/test/auth_web/controllers/people_controller_test.exs b/test/auth_web/controllers/people_controller_test.exs index c2a998fa..86198cf1 100644 --- a/test/auth_web/controllers/people_controller_test.exs +++ b/test/auth_web/controllers/people_controller_test.exs @@ -3,7 +3,7 @@ defmodule AuthWeb.PeopleControllerTest do # @email System.get_env("ADMIN_EMAIL") test "GET /people displays list of people", %{conn: conn} do - conn = get(admin_login(conn)"/people") + conn = get(admin_login(conn), "/people") assert html_response(conn, 200) =~ "People Authenticated" end diff --git a/test/auth_web/controllers/role_controller_test.exs b/test/auth_web/controllers/role_controller_test.exs index aa78bc41..96742b38 100644 --- a/test/auth_web/controllers/role_controller_test.exs +++ b/test/auth_web/controllers/role_controller_test.exs @@ -104,17 +104,33 @@ defmodule AuthWeb.RoleControllerTest do %{role: role} end - # test "attempt to grant_role/3 without admin should 401", %{conn: conn} do - # alex = %{email: "alex_grant_role_fail@gmail.com", auth_provider: "email"} - # grantee = Auth.Person.create_person(alex) - # conn = assign(conn, :person, grantee) - # role_id = 4 - # conn = Auth.PeopleRoles.insert(conn, grantee.id, role_id) - # assert conn.status == 401 - # end - # test "get list of roles" do - # Auth.Role.list_roles() |> IO.inspect() - # end + + + test "POST /roles/grant without admin should 401", %{conn: conn} do + alex = %{email: "alex_grant_role_fail@gmail.com", auth_provider: "email"} + grantee = Auth.Person.create_person(alex) + conn = assign(conn, :person, grantee) + conn = AuthWeb.RoleController.grant(conn, %{"role_id" => 5, "person_id" => grantee.id}) + assert conn.status == 401 + end + + test "POST /roles/grant should create people_roles entry", %{conn: conn} do + alex = %{email: "alex_grant_success@gmail.com", auth_provider: "email"} + grantee = Auth.Person.create_person(alex) + + + conn = get(admin_login(conn), Routes.role_path(conn, :grant, + %{"role_id" => 5, "person_id" => grantee.id})) + + # the grant/2 controller handler redirects back to /person/:id + assert html_response(conn, 302) =~ "redirected" + + # check that the record was created: + pr = Auth.PeopleRoles.get_record(grantee.id, 5) + assert pr.person_id == grantee.id + assert pr.role_id == 5 + assert pr.granter_id == 1 + end test "GET /roles/revoke/:people_roles_id displays confirm prompt", %{conn: conn} do conn = admin_login(conn) From 4568c7d1213a0403a6aed35162182229b93cbfb7 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 26 Aug 2020 08:26:40 +0100 Subject: [PATCH 061/166] mix format --- lib/auth/person.ex | 1 - lib/auth_web/controllers/people_controller.ex | 3 +++ lib/auth_web/controllers/role_controller.ex | 2 ++ lib/auth_web/views/people_view.ex | 13 ++++++++----- .../auth_web/controllers/people_controller_test.exs | 1 - test/auth_web/controllers/role_controller_test.exs | 10 +++++----- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/auth/person.ex b/lib/auth/person.ex index 505a7c05..2de30ee4 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -264,7 +264,6 @@ defmodule Auth.Person do end end - @doc """ `list_people/0` lists all people in the system. Used for displaying the table of authenticated people. diff --git a/lib/auth_web/controllers/people_controller.ex b/lib/auth_web/controllers/people_controller.ex index bddc5fa2..12511509 100644 --- a/lib/auth_web/controllers/people_controller.ex +++ b/lib/auth_web/controllers/people_controller.ex @@ -15,6 +15,7 @@ defmodule AuthWeb.PeopleController do roles: Auth.Role.list_roles(), statuses: Auth.Status.list_statuses() ) + # Note: this can easily be refactored to save on DB queries. #HelpWanted else AuthWeb.AuthController.unauthorized(conn) @@ -31,12 +32,14 @@ defmodule AuthWeb.PeopleController do roles = Auth.PeopleRoles.get_roles_for_person(person.id) IO.inspect(roles, label: "roles") all_roles = Auth.Role.list_roles() + render(conn, :profile, person: person, roles: roles, statuses: Auth.Status.list_statuses(), all_roles: all_roles ) + # Note: this can easily be refactored to save on DB queries. #HelpWanted else AuthWeb.AuthController.unauthorized(conn) diff --git a/lib/auth_web/controllers/role_controller.ex b/lib/auth_web/controllers/role_controller.ex index 5947c60c..0271f048 100644 --- a/lib/auth_web/controllers/role_controller.ex +++ b/lib/auth_web/controllers/role_controller.ex @@ -70,6 +70,7 @@ defmodule AuthWeb.RoleController do # confirm that the granter is either superadmin (conn.assigns.person.id == 1) # or has an "admin" role (1 || 2) granter_id = conn.assigns.person.id + if granter_id == 1 do role_id = Map.get(params, "role_id") person_id = Map.get(params, "person_id") @@ -89,6 +90,7 @@ defmodule AuthWeb.RoleController do if conn.assigns.person.id == 1 do people_roles_id = Map.get(params, "people_roles_id") pr = Auth.PeopleRoles.get_by_id(people_roles_id) + if conn.method == "GET" do render(conn, "revoke.html", role: pr, people_roles_id: people_roles_id) else diff --git a/lib/auth_web/views/people_view.ex b/lib/auth_web/views/people_view.ex index ef3aa43a..7a56592b 100644 --- a/lib/auth_web/views/people_view.ex +++ b/lib/auth_web/views/people_view.ex @@ -4,11 +4,14 @@ defmodule AuthWeb.PeopleView do @doc """ status_string/2 returns a string of status """ - def status_string(status_id, statuses) do + def status_string(status_id, statuses) do if status_id != nil do - status = Enum.filter(statuses, fn s -> - s.id == status_id - end) |> Enum.at(0) + status = + Enum.filter(statuses, fn s -> + s.id == status_id + end) + |> Enum.at(0) + status.text else "none" @@ -18,7 +21,7 @@ defmodule AuthWeb.PeopleView do @doc """ role_string/1 returns a string of all the role names """ - def role_string(person_roles) do + def role_string(person_roles) do Enum.map_join(person_roles, " ", fn r -> r.name end) diff --git a/test/auth_web/controllers/people_controller_test.exs b/test/auth_web/controllers/people_controller_test.exs index 86198cf1..95e27a1f 100644 --- a/test/auth_web/controllers/people_controller_test.exs +++ b/test/auth_web/controllers/people_controller_test.exs @@ -12,7 +12,6 @@ defmodule AuthWeb.PeopleControllerTest do assert conn.status == 302 end - test "Attempt to GET /people without admin role should 401", %{conn: conn} do wrong_person_data = %{ email: "not_admin@gmail.com", diff --git a/test/auth_web/controllers/role_controller_test.exs b/test/auth_web/controllers/role_controller_test.exs index 96742b38..a037b301 100644 --- a/test/auth_web/controllers/role_controller_test.exs +++ b/test/auth_web/controllers/role_controller_test.exs @@ -104,8 +104,6 @@ defmodule AuthWeb.RoleControllerTest do %{role: role} end - - test "POST /roles/grant without admin should 401", %{conn: conn} do alex = %{email: "alex_grant_role_fail@gmail.com", auth_provider: "email"} grantee = Auth.Person.create_person(alex) @@ -118,9 +116,11 @@ defmodule AuthWeb.RoleControllerTest do alex = %{email: "alex_grant_success@gmail.com", auth_provider: "email"} grantee = Auth.Person.create_person(alex) - - conn = get(admin_login(conn), Routes.role_path(conn, :grant, - %{"role_id" => 5, "person_id" => grantee.id})) + conn = + get( + admin_login(conn), + Routes.role_path(conn, :grant, %{"role_id" => 5, "person_id" => grantee.id}) + ) # the grant/2 controller handler redirects back to /person/:id assert html_response(conn, 302) =~ "redirected" From 3df1c93fcf524da71ab3950165af9fd4e6656ba1 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 26 Aug 2020 08:32:16 +0100 Subject: [PATCH 062/166] remove pipeline from simple PeopleView.status_string/2 function https://github.com/dwyl/auth/pull/85#pullrequestreview-47522326 --- lib/auth_web/views/people_view.ex | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/auth_web/views/people_view.ex b/lib/auth_web/views/people_view.ex index 7a56592b..d0fdec05 100644 --- a/lib/auth_web/views/people_view.ex +++ b/lib/auth_web/views/people_view.ex @@ -6,12 +6,7 @@ defmodule AuthWeb.PeopleView do """ def status_string(status_id, statuses) do if status_id != nil do - status = - Enum.filter(statuses, fn s -> - s.id == status_id - end) - |> Enum.at(0) - + status = Enum.at(Enum.filter(statuses, fn s -> s.id == status_id end), 0) status.text else "none" From 2eacfeb2b95d11d84247cba0738cc753ac811b06 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 26 Aug 2020 08:33:47 +0100 Subject: [PATCH 063/166] tidy up PeopleController.show/2 handler https://github.com/dwyl/auth/pull/85#pullrequestreview-475217260 --- lib/auth_web/controllers/people_controller.ex | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/auth_web/controllers/people_controller.ex b/lib/auth_web/controllers/people_controller.ex index 12511509..21fd53ab 100644 --- a/lib/auth_web/controllers/people_controller.ex +++ b/lib/auth_web/controllers/people_controller.ex @@ -29,15 +29,11 @@ defmodule AuthWeb.PeopleController do # should be visible to superadmin and people with "admin" role if conn.assigns.person.id == 1 do person = Auth.Person.get_person_by_id(Map.get(params, "person_id")) - roles = Auth.PeopleRoles.get_roles_for_person(person.id) - IO.inspect(roles, label: "roles") - all_roles = Auth.Role.list_roles() - render(conn, :profile, person: person, - roles: roles, + roles: Auth.PeopleRoles.get_roles_for_person(person.id), statuses: Auth.Status.list_statuses(), - all_roles: all_roles + all_roles: Auth.Role.list_roles() ) # Note: this can easily be refactored to save on DB queries. #HelpWanted From 46e4a8a5838d507a1540c8e9779415c8bf4233f3 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 27 Aug 2020 23:43:20 +0100 Subject: [PATCH 064/166] use latest version of elixir-auth-github see: https://github.com/dwyl/elixir-auth-github/issues/46 --- lib/auth/person.ex | 5 ++++- mix.exs | 2 +- mix.lock | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/auth/person.ex b/lib/auth/person.ex index 2de30ee4..7271c923 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -116,6 +116,8 @@ defmodule Auth.Person do } """ def transform_github_profile_data_to_person(profile) do + + IO.inspect(profile, label: "profile") Map.merge(profile, %{ username: profile.login, givenName: profile.name, @@ -270,7 +272,8 @@ defmodule Auth.Person do """ def list_people do Repo.all(from(pr in __MODULE__, preload: [:roles, :statuses])) - + # keeping this query commented here for now in case I decide to use it + # instead of having to call PeopleView.status_string/2 # query = """ # SELECT DISTINCT ON (s.status_id, s.person_id) s.id, s.message_id, # s.updated_at, s.template, st.text as status, s.person_id diff --git a/mix.exs b/mix.exs index b771494c..f0dd52ea 100644 --- a/mix.exs +++ b/mix.exs @@ -58,7 +58,7 @@ defmodule Auth.Mixfile do # Auth: # https://github.com/dwyl/elixir-auth-github - {:elixir_auth_github, "~> 1.3.0"}, + {:elixir_auth_github, "~> 1.4.0"}, # https://github.com/dwyl/elixir-auth-google {:elixir_auth_google, "~> 1.3.0"}, # https://github.com/dwyl/auth_plug diff --git a/mix.lock b/mix.lock index e000f255..222db618 100644 --- a/mix.lock +++ b/mix.lock @@ -15,7 +15,7 @@ "earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"}, "ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"}, "ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"}, - "elixir_auth_github": {:hex, :elixir_auth_github, "1.3.0", "1885de6fb7c3994e4a614864c76032f59761d5b3d2dce6c839f93d72f46e375a", [:mix], [{:httpoison, "~> 1.7.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 4.0.1", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "4ae8bc9c9cd1a7a087ffe1e6dfda55137cf3ca9b82cd30e18e9dc7b79ba3f253"}, + "elixir_auth_github": {:hex, :elixir_auth_github, "1.4.0", "fa6b4f52812201380213d49bbe09d8706dc97eacf9962c9e8d34d95aec3ad33c", [:mix], [{:httpoison, "~> 1.7.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 4.0.1", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "7c6ec4e8c73cef5c810a70e308505f67b56805a72b6fe9841ea81e8d24b91c7e"}, "elixir_auth_google": {:hex, :elixir_auth_google, "1.3.0", "b3a7843ccc004888f2dd1478b3ca5a102751138657ee2fd68fdbbf43e44fb138", [:mix], [{:httpoison, "~> 1.7.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 4.0.1", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "d2fb766ca4f6fcbf8f1f4128dcf2a35c7cd0c702ab62e3110458acd8038d0267"}, "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, From 7abbd6f69e183e368cb0f49fd22047b67e2d89f0 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 27 Aug 2020 23:49:04 +0100 Subject: [PATCH 065/166] remove stray IO.inspect https://github.com/dwyl/auth/pull/85#pullrequestreview-477133431 --- lib/auth/person.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/auth/person.ex b/lib/auth/person.ex index 7271c923..f2111a7c 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -116,8 +116,6 @@ defmodule Auth.Person do } """ def transform_github_profile_data_to_person(profile) do - - IO.inspect(profile, label: "profile") Map.merge(profile, %{ username: profile.login, givenName: profile.name, From 5389ea48ced23d27448011f7dfbda62ba4283ffd Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sun, 30 Aug 2020 07:22:56 +0100 Subject: [PATCH 066/166] mix phx.gen.html Ctx App apps #95 --- lib/auth/ctx.ex | 104 ++++++++++++++++++ lib/auth/ctx/app.ex | 23 ++++ lib/auth_web/controllers/app_controller.ex | 62 +++++++++++ lib/auth_web/templates/app/edit.html.eex | 5 + lib/auth_web/templates/app/form.html.eex | 27 +++++ lib/auth_web/templates/app/index.html.eex | 32 ++++++ lib/auth_web/templates/app/new.html.eex | 5 + lib/auth_web/templates/app/show.html.eex | 28 +++++ lib/auth_web/views/app_view.ex | 3 + .../migrations/20200830061923_create_apps.exs | 21 ++++ test/auth/ctx_test.exs | 70 ++++++++++++ .../controllers/app_controller_test.exs | 88 +++++++++++++++ 12 files changed, 468 insertions(+) create mode 100644 lib/auth/ctx.ex create mode 100644 lib/auth/ctx/app.ex create mode 100644 lib/auth_web/controllers/app_controller.ex create mode 100644 lib/auth_web/templates/app/edit.html.eex create mode 100644 lib/auth_web/templates/app/form.html.eex create mode 100644 lib/auth_web/templates/app/index.html.eex create mode 100644 lib/auth_web/templates/app/new.html.eex create mode 100644 lib/auth_web/templates/app/show.html.eex create mode 100644 lib/auth_web/views/app_view.ex create mode 100644 priv/repo/migrations/20200830061923_create_apps.exs create mode 100644 test/auth/ctx_test.exs create mode 100644 test/auth_web/controllers/app_controller_test.exs diff --git a/lib/auth/ctx.ex b/lib/auth/ctx.ex new file mode 100644 index 00000000..2844f7d1 --- /dev/null +++ b/lib/auth/ctx.ex @@ -0,0 +1,104 @@ +defmodule Auth.Ctx do + @moduledoc """ + The Ctx context. + """ + + import Ecto.Query, warn: false + alias Auth.Repo + + alias Auth.Ctx.App + + @doc """ + Returns the list of apps. + + ## Examples + + iex> list_apps() + [%App{}, ...] + + """ + def list_apps do + Repo.all(App) + end + + @doc """ + Gets a single app. + + Raises `Ecto.NoResultsError` if the App does not exist. + + ## Examples + + iex> get_app!(123) + %App{} + + iex> get_app!(456) + ** (Ecto.NoResultsError) + + """ + def get_app!(id), do: Repo.get!(App, id) + + @doc """ + Creates a app. + + ## Examples + + iex> create_app(%{field: value}) + {:ok, %App{}} + + iex> create_app(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_app(attrs \\ %{}) do + %App{} + |> App.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a app. + + ## Examples + + iex> update_app(app, %{field: new_value}) + {:ok, %App{}} + + iex> update_app(app, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_app(%App{} = app, attrs) do + app + |> App.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a app. + + ## Examples + + iex> delete_app(app) + {:ok, %App{}} + + iex> delete_app(app) + {:error, %Ecto.Changeset{}} + + """ + def delete_app(%App{} = app) do + Repo.delete(app) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking app changes. + + ## Examples + + iex> change_app(app) + %Ecto.Changeset{data: %App{}} + + """ + def change_app(%App{} = app, attrs \\ %{}) do + App.changeset(app, attrs) + end +end diff --git a/lib/auth/ctx/app.ex b/lib/auth/ctx/app.ex new file mode 100644 index 00000000..084ca5bb --- /dev/null +++ b/lib/auth/ctx/app.ex @@ -0,0 +1,23 @@ +defmodule Auth.Ctx.App do + use Ecto.Schema + import Ecto.Changeset + + schema "apps" do + field :description, :binary + field :end, :naive_datetime + field :name, :binary + field :url, :binary + field :person_id, :id + field :status, :id + field :apikey_id, :id + + timestamps() + end + + @doc false + def changeset(app, attrs) do + app + |> cast(attrs, [:name, :description, :url, :end]) + |> validate_required([:name, :description, :url, :end]) + end +end diff --git a/lib/auth_web/controllers/app_controller.ex b/lib/auth_web/controllers/app_controller.ex new file mode 100644 index 00000000..585c915a --- /dev/null +++ b/lib/auth_web/controllers/app_controller.ex @@ -0,0 +1,62 @@ +defmodule AuthWeb.AppController do + use AuthWeb, :controller + + alias Auth.Ctx + alias Auth.Ctx.App + + def index(conn, _params) do + apps = Ctx.list_apps() + render(conn, "index.html", apps: apps) + end + + def new(conn, _params) do + changeset = Ctx.change_app(%App{}) + render(conn, "new.html", changeset: changeset) + end + + def create(conn, %{"app" => app_params}) do + case Ctx.create_app(app_params) do + {:ok, app} -> + conn + |> put_flash(:info, "App created successfully.") + |> redirect(to: Routes.app_path(conn, :show, app)) + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, "new.html", changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + app = Ctx.get_app!(id) + render(conn, "show.html", app: app) + end + + def edit(conn, %{"id" => id}) do + app = Ctx.get_app!(id) + changeset = Ctx.change_app(app) + render(conn, "edit.html", app: app, changeset: changeset) + end + + def update(conn, %{"id" => id, "app" => app_params}) do + app = Ctx.get_app!(id) + + case Ctx.update_app(app, app_params) do + {:ok, app} -> + conn + |> put_flash(:info, "App updated successfully.") + |> redirect(to: Routes.app_path(conn, :show, app)) + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, "edit.html", app: app, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + app = Ctx.get_app!(id) + {:ok, _app} = Ctx.delete_app(app) + + conn + |> put_flash(:info, "App deleted successfully.") + |> redirect(to: Routes.app_path(conn, :index)) + end +end diff --git a/lib/auth_web/templates/app/edit.html.eex b/lib/auth_web/templates/app/edit.html.eex new file mode 100644 index 00000000..28e61bad --- /dev/null +++ b/lib/auth_web/templates/app/edit.html.eex @@ -0,0 +1,5 @@ +

Edit App

+ +<%= render "form.html", Map.put(assigns, :action, Routes.app_path(@conn, :update, @app)) %> + +<%= link "Back", to: Routes.app_path(@conn, :index) %> diff --git a/lib/auth_web/templates/app/form.html.eex b/lib/auth_web/templates/app/form.html.eex new file mode 100644 index 00000000..7a669661 --- /dev/null +++ b/lib/auth_web/templates/app/form.html.eex @@ -0,0 +1,27 @@ +<%= form_for @changeset, @action, fn f -> %> + <%= if @changeset.action do %> +
+

Oops, something went wrong! Please check the errors below.

+
+ <% end %> + + <%= label f, :name %> + <%= text_input f, :name %> + <%= error_tag f, :name %> + + <%= label f, :description %> + <%= text_input f, :description %> + <%= error_tag f, :description %> + + <%= label f, :url %> + <%= text_input f, :url %> + <%= error_tag f, :url %> + + <%= label f, :end %> + <%= datetime_select f, :end %> + <%= error_tag f, :end %> + +
+ <%= submit "Save" %> +
+<% end %> diff --git a/lib/auth_web/templates/app/index.html.eex b/lib/auth_web/templates/app/index.html.eex new file mode 100644 index 00000000..948b2925 --- /dev/null +++ b/lib/auth_web/templates/app/index.html.eex @@ -0,0 +1,32 @@ +

Listing Apps

+ +
Role Granted Granted byRevoke?Revocation
+ + + + + + + + + + + +<%= for app <- @apps do %> + + + + + + + + +<% end %> + +
NameDescriptionUrlEnd
<%= app.name %><%= app.description %><%= app.url %><%= app.end %> + <%= link "Show", to: Routes.app_path(@conn, :show, app) %> + <%= link "Edit", to: Routes.app_path(@conn, :edit, app) %> + <%= link "Delete", to: Routes.app_path(@conn, :delete, app), method: :delete, data: [confirm: "Are you sure?"] %> +
+ +<%= link "New App", to: Routes.app_path(@conn, :new) %> diff --git a/lib/auth_web/templates/app/new.html.eex b/lib/auth_web/templates/app/new.html.eex new file mode 100644 index 00000000..517173bc --- /dev/null +++ b/lib/auth_web/templates/app/new.html.eex @@ -0,0 +1,5 @@ +

New App

+ +<%= render "form.html", Map.put(assigns, :action, Routes.app_path(@conn, :create)) %> + +<%= link "Back", to: Routes.app_path(@conn, :index) %> diff --git a/lib/auth_web/templates/app/show.html.eex b/lib/auth_web/templates/app/show.html.eex new file mode 100644 index 00000000..f64d8a1c --- /dev/null +++ b/lib/auth_web/templates/app/show.html.eex @@ -0,0 +1,28 @@ +

Show App

+ +
    + +
  • + Name: + <%= @app.name %> +
  • + +
  • + Description: + <%= @app.description %> +
  • + +
  • + Url: + <%= @app.url %> +
  • + +
  • + End: + <%= @app.end %> +
  • + +
+ +<%= link "Edit", to: Routes.app_path(@conn, :edit, @app) %> +<%= link "Back", to: Routes.app_path(@conn, :index) %> diff --git a/lib/auth_web/views/app_view.ex b/lib/auth_web/views/app_view.ex new file mode 100644 index 00000000..b65eb8a7 --- /dev/null +++ b/lib/auth_web/views/app_view.ex @@ -0,0 +1,3 @@ +defmodule AuthWeb.AppView do + use AuthWeb, :view +end diff --git a/priv/repo/migrations/20200830061923_create_apps.exs b/priv/repo/migrations/20200830061923_create_apps.exs new file mode 100644 index 00000000..e5c6ccc3 --- /dev/null +++ b/priv/repo/migrations/20200830061923_create_apps.exs @@ -0,0 +1,21 @@ +defmodule Auth.Repo.Migrations.CreateApps do + use Ecto.Migration + + def change do + create table(:apps) do + add :name, :binary + add :description, :binary + add :url, :binary + add :end, :naive_datetime + add :person_id, references(:people, on_delete: :nothing) + add :status, references(:status, on_delete: :nothing) + add :apikey_id, references(:api, on_delete: :nothing) + + timestamps() + end + + create index(:apps, [:person_id]) + create index(:apps, [:status]) + create index(:apps, [:apikey_id]) + end +end diff --git a/test/auth/ctx_test.exs b/test/auth/ctx_test.exs new file mode 100644 index 00000000..6b2a76af --- /dev/null +++ b/test/auth/ctx_test.exs @@ -0,0 +1,70 @@ +defmodule Auth.CtxTest do + use Auth.DataCase + + alias Auth.Ctx + + describe "apps" do + alias Auth.Ctx.App + + @valid_attrs %{description: "some description", end: ~N[2010-04-17 14:00:00], name: "some name", url: "some url"} + @update_attrs %{description: "some updated description", end: ~N[2011-05-18 15:01:01], name: "some updated name", url: "some updated url"} + @invalid_attrs %{description: nil, end: nil, name: nil, url: nil} + + def app_fixture(attrs \\ %{}) do + {:ok, app} = + attrs + |> Enum.into(@valid_attrs) + |> Ctx.create_app() + + app + end + + test "list_apps/0 returns all apps" do + app = app_fixture() + assert Ctx.list_apps() == [app] + end + + test "get_app!/1 returns the app with given id" do + app = app_fixture() + assert Ctx.get_app!(app.id) == app + end + + test "create_app/1 with valid data creates a app" do + assert {:ok, %App{} = app} = Ctx.create_app(@valid_attrs) + assert app.description == "some description" + assert app.end == ~N[2010-04-17 14:00:00] + assert app.name == "some name" + assert app.url == "some url" + end + + test "create_app/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Ctx.create_app(@invalid_attrs) + end + + test "update_app/2 with valid data updates the app" do + app = app_fixture() + assert {:ok, %App{} = app} = Ctx.update_app(app, @update_attrs) + assert app.description == "some updated description" + assert app.end == ~N[2011-05-18 15:01:01] + assert app.name == "some updated name" + assert app.url == "some updated url" + end + + test "update_app/2 with invalid data returns error changeset" do + app = app_fixture() + assert {:error, %Ecto.Changeset{}} = Ctx.update_app(app, @invalid_attrs) + assert app == Ctx.get_app!(app.id) + end + + test "delete_app/1 deletes the app" do + app = app_fixture() + assert {:ok, %App{}} = Ctx.delete_app(app) + assert_raise Ecto.NoResultsError, fn -> Ctx.get_app!(app.id) end + end + + test "change_app/1 returns a app changeset" do + app = app_fixture() + assert %Ecto.Changeset{} = Ctx.change_app(app) + end + end +end diff --git a/test/auth_web/controllers/app_controller_test.exs b/test/auth_web/controllers/app_controller_test.exs new file mode 100644 index 00000000..2da664e5 --- /dev/null +++ b/test/auth_web/controllers/app_controller_test.exs @@ -0,0 +1,88 @@ +defmodule AuthWeb.AppControllerTest do + use AuthWeb.ConnCase + + alias Auth.Ctx + + @create_attrs %{description: "some description", end: ~N[2010-04-17 14:00:00], name: "some name", url: "some url"} + @update_attrs %{description: "some updated description", end: ~N[2011-05-18 15:01:01], name: "some updated name", url: "some updated url"} + @invalid_attrs %{description: nil, end: nil, name: nil, url: nil} + + def fixture(:app) do + {:ok, app} = Ctx.create_app(@create_attrs) + app + end + + describe "index" do + test "lists all apps", %{conn: conn} do + conn = get(conn, Routes.app_path(conn, :index)) + assert html_response(conn, 200) =~ "Listing Apps" + end + end + + describe "new app" do + test "renders form", %{conn: conn} do + conn = get(conn, Routes.app_path(conn, :new)) + assert html_response(conn, 200) =~ "New App" + end + end + + describe "create app" do + test "redirects to show when data is valid", %{conn: conn} do + conn = post(conn, Routes.app_path(conn, :create), app: @create_attrs) + + assert %{id: id} = redirected_params(conn) + assert redirected_to(conn) == Routes.app_path(conn, :show, id) + + conn = get(conn, Routes.app_path(conn, :show, id)) + assert html_response(conn, 200) =~ "Show App" + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, Routes.app_path(conn, :create), app: @invalid_attrs) + assert html_response(conn, 200) =~ "New App" + end + end + + describe "edit app" do + setup [:create_app] + + test "renders form for editing chosen app", %{conn: conn, app: app} do + conn = get(conn, Routes.app_path(conn, :edit, app)) + assert html_response(conn, 200) =~ "Edit App" + end + end + + describe "update app" do + setup [:create_app] + + test "redirects when data is valid", %{conn: conn, app: app} do + conn = put(conn, Routes.app_path(conn, :update, app), app: @update_attrs) + assert redirected_to(conn) == Routes.app_path(conn, :show, app) + + conn = get(conn, Routes.app_path(conn, :show, app)) + assert html_response(conn, 200) + end + + test "renders errors when data is invalid", %{conn: conn, app: app} do + conn = put(conn, Routes.app_path(conn, :update, app), app: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit App" + end + end + + describe "delete app" do + setup [:create_app] + + test "deletes chosen app", %{conn: conn, app: app} do + conn = delete(conn, Routes.app_path(conn, :delete, app)) + assert redirected_to(conn) == Routes.app_path(conn, :index) + assert_error_sent 404, fn -> + get(conn, Routes.app_path(conn, :show, app)) + end + end + end + + defp create_app(_) do + app = fixture(:app) + %{app: app} + end +end From f90ef84e33a1c35c6c4ffcd58d661590bc7e5966 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Sun, 30 Aug 2020 07:39:40 +0100 Subject: [PATCH 067/166] remvoe Ctx and fix CreateApps migration #95 --- lib/auth/{ctx.ex => app.ex} | 28 ++++++++++++++++--- lib/auth/ctx/app.ex | 23 --------------- lib/auth_web/controllers/app_controller.ex | 24 ++++++++-------- lib/auth_web/router.ex | 1 + .../migrations/20200830061923_create_apps.exs | 2 +- .../controllers/app_controller_test.exs | 5 ++-- 6 files changed, 40 insertions(+), 43 deletions(-) rename lib/auth/{ctx.ex => app.ex} (72%) delete mode 100644 lib/auth/ctx/app.ex diff --git a/lib/auth/ctx.ex b/lib/auth/app.ex similarity index 72% rename from lib/auth/ctx.ex rename to lib/auth/app.ex index 2844f7d1..ae489fc7 100644 --- a/lib/auth/ctx.ex +++ b/lib/auth/app.ex @@ -1,12 +1,32 @@ -defmodule Auth.Ctx do +defmodule Auth.App do @moduledoc """ - The Ctx context. + Schema and helper functions for creating/managing Apps. """ - + use Ecto.Schema + import Ecto.Changeset import Ecto.Query, warn: false alias Auth.Repo + # https://stackoverflow.com/a/47501059/1148249 + alias __MODULE__ + + schema "apps" do + field :description, :binary + field :end, :naive_datetime + field :name, :binary + field :url, :binary + field :person_id, :id + field :status, :id + field :apikey_id, :id + + timestamps() + end - alias Auth.Ctx.App + @doc false + def changeset(app, attrs) do + app + |> cast(attrs, [:name, :description, :url, :end]) + |> validate_required([:name, :url]) + end @doc """ Returns the list of apps. diff --git a/lib/auth/ctx/app.ex b/lib/auth/ctx/app.ex deleted file mode 100644 index 084ca5bb..00000000 --- a/lib/auth/ctx/app.ex +++ /dev/null @@ -1,23 +0,0 @@ -defmodule Auth.Ctx.App do - use Ecto.Schema - import Ecto.Changeset - - schema "apps" do - field :description, :binary - field :end, :naive_datetime - field :name, :binary - field :url, :binary - field :person_id, :id - field :status, :id - field :apikey_id, :id - - timestamps() - end - - @doc false - def changeset(app, attrs) do - app - |> cast(attrs, [:name, :description, :url, :end]) - |> validate_required([:name, :description, :url, :end]) - end -end diff --git a/lib/auth_web/controllers/app_controller.ex b/lib/auth_web/controllers/app_controller.ex index 585c915a..4f0cbbc6 100644 --- a/lib/auth_web/controllers/app_controller.ex +++ b/lib/auth_web/controllers/app_controller.ex @@ -1,21 +1,19 @@ defmodule AuthWeb.AppController do use AuthWeb, :controller - - alias Auth.Ctx - alias Auth.Ctx.App + alias Auth.App def index(conn, _params) do - apps = Ctx.list_apps() + apps = App.list_apps() render(conn, "index.html", apps: apps) end def new(conn, _params) do - changeset = Ctx.change_app(%App{}) + changeset = App.change_app(%App{}) render(conn, "new.html", changeset: changeset) end def create(conn, %{"app" => app_params}) do - case Ctx.create_app(app_params) do + case App.create_app(app_params) do {:ok, app} -> conn |> put_flash(:info, "App created successfully.") @@ -27,20 +25,20 @@ defmodule AuthWeb.AppController do end def show(conn, %{"id" => id}) do - app = Ctx.get_app!(id) + app = App.get_app!(id) render(conn, "show.html", app: app) end def edit(conn, %{"id" => id}) do - app = Ctx.get_app!(id) - changeset = Ctx.change_app(app) + app = App.get_app!(id) + changeset = App.change_app(app) render(conn, "edit.html", app: app, changeset: changeset) end def update(conn, %{"id" => id, "app" => app_params}) do - app = Ctx.get_app!(id) + app = App.get_app!(id) - case Ctx.update_app(app, app_params) do + case App.update_app(app, app_params) do {:ok, app} -> conn |> put_flash(:info, "App updated successfully.") @@ -52,8 +50,8 @@ defmodule AuthWeb.AppController do end def delete(conn, %{"id" => id}) do - app = Ctx.get_app!(id) - {:ok, _app} = Ctx.delete_app(app) + app = App.get_app!(id) + {:ok, _app} = App.delete_app(app) conn |> put_flash(:info, "App deleted successfully.") diff --git a/lib/auth_web/router.ex b/lib/auth_web/router.ex index ab405c97..94328a55 100644 --- a/lib/auth_web/router.ex +++ b/lib/auth_web/router.ex @@ -50,6 +50,7 @@ defmodule AuthWeb.Router do resources "/roles", RoleController resources "/permissions", PermissionController + resources "/apps", AppController resources "/settings/apikeys", ApikeyController end diff --git a/priv/repo/migrations/20200830061923_create_apps.exs b/priv/repo/migrations/20200830061923_create_apps.exs index e5c6ccc3..27757cf9 100644 --- a/priv/repo/migrations/20200830061923_create_apps.exs +++ b/priv/repo/migrations/20200830061923_create_apps.exs @@ -9,7 +9,7 @@ defmodule Auth.Repo.Migrations.CreateApps do add :end, :naive_datetime add :person_id, references(:people, on_delete: :nothing) add :status, references(:status, on_delete: :nothing) - add :apikey_id, references(:api, on_delete: :nothing) + add :apikey_id, references(:apikeys, on_delete: :nothing) timestamps() end diff --git a/test/auth_web/controllers/app_controller_test.exs b/test/auth_web/controllers/app_controller_test.exs index 2da664e5..7af06ebb 100644 --- a/test/auth_web/controllers/app_controller_test.exs +++ b/test/auth_web/controllers/app_controller_test.exs @@ -1,19 +1,20 @@ defmodule AuthWeb.AppControllerTest do use AuthWeb.ConnCase - alias Auth.Ctx + alias Auth.App @create_attrs %{description: "some description", end: ~N[2010-04-17 14:00:00], name: "some name", url: "some url"} @update_attrs %{description: "some updated description", end: ~N[2011-05-18 15:01:01], name: "some updated name", url: "some updated url"} @invalid_attrs %{description: nil, end: nil, name: nil, url: nil} def fixture(:app) do - {:ok, app} = Ctx.create_app(@create_attrs) + {:ok, app} = App.create_app(@create_attrs) app end describe "index" do test "lists all apps", %{conn: conn} do + conn = admin_login(conn) conn = get(conn, Routes.app_path(conn, :index)) assert html_response(conn, 200) =~ "Listing Apps" end From 01b2b87a838faf0b028c77f71696e580c7a510c3 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Mon, 31 Aug 2020 07:48:07 +0100 Subject: [PATCH 068/166] add admin_login(conn) to all AppController tests #95 --- test/auth_web/controllers/app_controller_test.exs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/auth_web/controllers/app_controller_test.exs b/test/auth_web/controllers/app_controller_test.exs index 7af06ebb..4d9aaf38 100644 --- a/test/auth_web/controllers/app_controller_test.exs +++ b/test/auth_web/controllers/app_controller_test.exs @@ -22,6 +22,7 @@ defmodule AuthWeb.AppControllerTest do describe "new app" do test "renders form", %{conn: conn} do + conn = admin_login(conn) conn = get(conn, Routes.app_path(conn, :new)) assert html_response(conn, 200) =~ "New App" end @@ -29,6 +30,7 @@ defmodule AuthWeb.AppControllerTest do describe "create app" do test "redirects to show when data is valid", %{conn: conn} do + conn = admin_login(conn) conn = post(conn, Routes.app_path(conn, :create), app: @create_attrs) assert %{id: id} = redirected_params(conn) @@ -39,6 +41,7 @@ defmodule AuthWeb.AppControllerTest do end test "renders errors when data is invalid", %{conn: conn} do + conn = admin_login(conn) conn = post(conn, Routes.app_path(conn, :create), app: @invalid_attrs) assert html_response(conn, 200) =~ "New App" end @@ -48,6 +51,7 @@ defmodule AuthWeb.AppControllerTest do setup [:create_app] test "renders form for editing chosen app", %{conn: conn, app: app} do + conn = admin_login(conn) conn = get(conn, Routes.app_path(conn, :edit, app)) assert html_response(conn, 200) =~ "Edit App" end @@ -57,6 +61,7 @@ defmodule AuthWeb.AppControllerTest do setup [:create_app] test "redirects when data is valid", %{conn: conn, app: app} do + conn = admin_login(conn) conn = put(conn, Routes.app_path(conn, :update, app), app: @update_attrs) assert redirected_to(conn) == Routes.app_path(conn, :show, app) @@ -65,6 +70,7 @@ defmodule AuthWeb.AppControllerTest do end test "renders errors when data is invalid", %{conn: conn, app: app} do + conn = admin_login(conn) conn = put(conn, Routes.app_path(conn, :update, app), app: @invalid_attrs) assert html_response(conn, 200) =~ "Edit App" end @@ -74,6 +80,7 @@ defmodule AuthWeb.AppControllerTest do setup [:create_app] test "deletes chosen app", %{conn: conn, app: app} do + conn = admin_login(conn) conn = delete(conn, Routes.app_path(conn, :delete, app)) assert redirected_to(conn) == Routes.app_path(conn, :index) assert_error_sent 404, fn -> From 52c082822fee76cea4dba5da3672022b5256b6df Mon Sep 17 00:00:00 2001 From: nelsonic Date: Mon, 31 Aug 2020 07:50:44 +0100 Subject: [PATCH 069/166] rename Ctx to App in app_test.exs #95 --- test/auth/{ctx_test.exs => app_test.exs} | 28 +++++++++++------------- 1 file changed, 13 insertions(+), 15 deletions(-) rename test/auth/{ctx_test.exs => app_test.exs} (71%) diff --git a/test/auth/ctx_test.exs b/test/auth/app_test.exs similarity index 71% rename from test/auth/ctx_test.exs rename to test/auth/app_test.exs index 6b2a76af..13b5b017 100644 --- a/test/auth/ctx_test.exs +++ b/test/auth/app_test.exs @@ -1,10 +1,8 @@ -defmodule Auth.CtxTest do +defmodule Auth.AppTest do use Auth.DataCase - alias Auth.Ctx - describe "apps" do - alias Auth.Ctx.App + alias Auth.App @valid_attrs %{description: "some description", end: ~N[2010-04-17 14:00:00], name: "some name", url: "some url"} @update_attrs %{description: "some updated description", end: ~N[2011-05-18 15:01:01], name: "some updated name", url: "some updated url"} @@ -14,23 +12,23 @@ defmodule Auth.CtxTest do {:ok, app} = attrs |> Enum.into(@valid_attrs) - |> Ctx.create_app() + |> App.create_app() app end test "list_apps/0 returns all apps" do app = app_fixture() - assert Ctx.list_apps() == [app] + assert App.list_apps() == [app] end test "get_app!/1 returns the app with given id" do app = app_fixture() - assert Ctx.get_app!(app.id) == app + assert App.get_app!(app.id) == app end test "create_app/1 with valid data creates a app" do - assert {:ok, %App{} = app} = Ctx.create_app(@valid_attrs) + assert {:ok, %App{} = app} = App.create_app(@valid_attrs) assert app.description == "some description" assert app.end == ~N[2010-04-17 14:00:00] assert app.name == "some name" @@ -38,12 +36,12 @@ defmodule Auth.CtxTest do end test "create_app/1 with invalid data returns error changeset" do - assert {:error, %Ecto.Changeset{}} = Ctx.create_app(@invalid_attrs) + assert {:error, %Ecto.Changeset{}} = App.create_app(@invalid_attrs) end test "update_app/2 with valid data updates the app" do app = app_fixture() - assert {:ok, %App{} = app} = Ctx.update_app(app, @update_attrs) + assert {:ok, %App{} = app} = App.update_app(app, @update_attrs) assert app.description == "some updated description" assert app.end == ~N[2011-05-18 15:01:01] assert app.name == "some updated name" @@ -52,19 +50,19 @@ defmodule Auth.CtxTest do test "update_app/2 with invalid data returns error changeset" do app = app_fixture() - assert {:error, %Ecto.Changeset{}} = Ctx.update_app(app, @invalid_attrs) - assert app == Ctx.get_app!(app.id) + assert {:error, %Ecto.Changeset{}} = App.update_app(app, @invalid_attrs) + assert app == App.get_app!(app.id) end test "delete_app/1 deletes the app" do app = app_fixture() - assert {:ok, %App{}} = Ctx.delete_app(app) - assert_raise Ecto.NoResultsError, fn -> Ctx.get_app!(app.id) end + assert {:ok, %App{}} = App.delete_app(app) + assert_raise Ecto.NoResultsError, fn -> App.get_app!(app.id) end end test "change_app/1 returns a app changeset" do app = app_fixture() - assert %Ecto.Changeset{} = Ctx.change_app(app) + assert %Ecto.Changeset{} = App.change_app(app) end end end From 3100d2802c037c0510f41f36b32a1cf51765e55f Mon Sep 17 00:00:00 2001 From: nelsonic Date: Mon, 31 Aug 2020 08:21:54 +0100 Subject: [PATCH 070/166] update html forms / tables to be more visually appealing #95 --- lib/auth_web/templates/app/form.html.eex | 31 +++++++++++++---------- lib/auth_web/templates/app/index.html.eex | 30 +++++++++++++--------- lib/auth_web/templates/app/new.html.eex | 14 +++++++--- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/lib/auth_web/templates/app/form.html.eex b/lib/auth_web/templates/app/form.html.eex index 7a669661..ae40a158 100644 --- a/lib/auth_web/templates/app/form.html.eex +++ b/lib/auth_web/templates/app/form.html.eex @@ -5,23 +5,26 @@ <% end %> - <%= label f, :name %> - <%= text_input f, :name %> + <%= label f, :name%> + <%= text_input f, :name, class: "db w-100 mt2 pa2 ba b--dark-grey", + placeholder: "A distinctive name"%> <%= error_tag f, :name %> - - <%= label f, :description %> - <%= text_input f, :description %> +
+ <%= label f, :description, class: "mt3" %> + <%= textarea f, :description, class: "db w-100 mt2 pa2 ba b--black", + placeholder: "Be as descriptive as possible." %> <%= error_tag f, :description %> - - <%= label f, :url %> - <%= text_input f, :url %> +
+ <%= label f, :url, class: "pt3" %> + <%= text_input f, :url, class: "db w-100 mt2 pa2 ba b--dark-grey", + placeholder: "The URL of your app e.g: http://localhost:4000 or dwyl.com" + %> <%= error_tag f, :url %> - <%= label f, :end %> - <%= datetime_select f, :end %> - <%= error_tag f, :end %> - -
- <%= submit "Save" %> +
+ <%= submit "Save", + class: "pointer br2 ba b--dark-green bg-green white pa3 ml1 mv1 f4 + shadow-hover bg-animate hover-bg-dark-green border-box no-underline" + %>
<% end %> diff --git a/lib/auth_web/templates/app/index.html.eex b/lib/auth_web/templates/app/index.html.eex index 948b2925..454dd2a1 100644 --- a/lib/auth_web/templates/app/index.html.eex +++ b/lib/auth_web/templates/app/index.html.eex @@ -1,14 +1,11 @@ -

Listing Apps

+

Apps

- - - - - - - - - +
NameDescriptionUrlEnd
+ + + + + @@ -17,8 +14,6 @@ - -
NameDescriptionUrl
<%= app.name %> <%= app.description %> <%= app.url %><%= app.end %> <%= link "Show", to: Routes.app_path(@conn, :show, app) %> <%= link "Edit", to: Routes.app_path(@conn, :edit, app) %> @@ -30,3 +25,14 @@
<%= link "New App", to: Routes.app_path(@conn, :new) %> + + \ No newline at end of file diff --git a/lib/auth_web/templates/app/new.html.eex b/lib/auth_web/templates/app/new.html.eex index 517173bc..5e8872b4 100644 --- a/lib/auth_web/templates/app/new.html.eex +++ b/lib/auth_web/templates/app/new.html.eex @@ -1,5 +1,13 @@ -

New App

+
+

New App

-<%= render "form.html", Map.put(assigns, :action, Routes.app_path(@conn, :create)) %> + <%= render "form.html", Map.put(assigns, :action, Routes.app_path(@conn, :create)) %> -<%= link "Back", to: Routes.app_path(@conn, :index) %> + <%= link "< Back", to: Routes.app_path(@conn, :index), + class: "fl pointer br2 ba b--orange bg-gold white pa3 f4 mt3 + shadow-hover bg-animate hover-bg-orange border-box no-underline", + data: + [confirm: "Are you sure you want to disguard this form? + (The data cannot be recovered!)"] + %> +
\ No newline at end of file From fdea23d5a4a642e9ef6ece366f195a12811a525c Mon Sep 17 00:00:00 2001 From: nelsonic Date: Tue, 1 Sep 2020 15:58:22 +0100 Subject: [PATCH 071/166] update copy in test assertion "Listing Apps" > "Apps" #95 --- lib/auth_web/templates/app/edit.html.eex | 10 +++- lib/auth_web/templates/app/index.html.eex | 23 ++++++-- lib/auth_web/templates/app/show.html.eex | 53 ++++++++++++------- .../controllers/app_controller_test.exs | 2 +- 4 files changed, 62 insertions(+), 26 deletions(-) diff --git a/lib/auth_web/templates/app/edit.html.eex b/lib/auth_web/templates/app/edit.html.eex index 28e61bad..03fb00e9 100644 --- a/lib/auth_web/templates/app/edit.html.eex +++ b/lib/auth_web/templates/app/edit.html.eex @@ -1,5 +1,13 @@ +

Edit App

<%= render "form.html", Map.put(assigns, :action, Routes.app_path(@conn, :update, @app)) %> -<%= link "Back", to: Routes.app_path(@conn, :index) %> + + <%= link "< Back", to: Routes.app_path(@conn, :index), + class: "fl pointer br2 ba b--orange bg-gold white pa3 f4 mt3 + shadow-hover bg-animate hover-bg-orange border-box no-underline", + data: + [confirm: "Any changes made will not be saved."] + %> +
\ No newline at end of file diff --git a/lib/auth_web/templates/app/index.html.eex b/lib/auth_web/templates/app/index.html.eex index 454dd2a1..3fd50ba5 100644 --- a/lib/auth_web/templates/app/index.html.eex +++ b/lib/auth_web/templates/app/index.html.eex @@ -15,10 +15,25 @@ <%= app.description %> <%= app.url %> - <%= link "Show", to: Routes.app_path(@conn, :show, app) %> - <%= link "Edit", to: Routes.app_path(@conn, :edit, app) %> - <%= link "Delete", to: Routes.app_path(@conn, :delete, app), method: :delete, data: [confirm: "Are you sure?"] %> - + <%= link "View", to: Routes.app_path(@conn, :show, app), + class: "pointer br2 ba b--dark-blue bg-blue white pa3 ml1 mv1 f4 + bg-animate hover-bg-dark-blue border-box no-underline" + %> + + <%= link "Edit", to: Routes.app_path(@conn, :edit, app), + class: "pointer br2 ba b--orange bg-gold white pa3 ml1 mv1 f4 + bg-animate hover-bg-orange border-box no-underline" + %> + + <%= link "Delete", to: Routes.app_path(@conn, :delete, app), + method: :delete, + class: "pointer br2 ba b--dark-red bg-red white pa3 ml1 mv1 f4 + bg-animate hover-bg-dark-red border-box no-underline", + data: + [confirm: "Are you sure you want to delete this App? + (This cannot be undone!)"] %> + + <% end %> diff --git a/lib/auth_web/templates/app/show.html.eex b/lib/auth_web/templates/app/show.html.eex index f64d8a1c..1cda03f3 100644 --- a/lib/auth_web/templates/app/show.html.eex +++ b/lib/auth_web/templates/app/show.html.eex @@ -1,28 +1,41 @@ -

Show App

+
+

Your App

-
    -
  • - Name: - <%= @app.name %> -
  • -
  • +

    + App Name: + + <%= @app.name %> + +

    + +

    Description: - <%= @app.description %> -

  • + + <%= @app.description %> + +

    -
  • - Url: - <%= @app.url %> -
  • +

    + URL: + + <%= @app.url %> + +

    -
  • - End: - <%= @app.end %> -
  • -
+ + <%= link "< Back", to: Routes.app_path(@conn, :index), + class: "pointer br2 ba b--orange bg-gold white pa3 ml1 mv1 f4 + shadow-hover bg-animate hover-bg-orange border-box no-underline" + %> + + + <%= link "Edit", to: Routes.app_path(@conn, :edit, @app), + class: "pointer br2 ba b--dark-green bg-green white pa3 ml1 mv1 f4 + shadow-hover bg-animate hover-bg-dark-green border-box no-underline" + %> + -<%= link "Edit", to: Routes.app_path(@conn, :edit, @app) %> -<%= link "Back", to: Routes.app_path(@conn, :index) %> +
diff --git a/test/auth_web/controllers/app_controller_test.exs b/test/auth_web/controllers/app_controller_test.exs index 4d9aaf38..fd104950 100644 --- a/test/auth_web/controllers/app_controller_test.exs +++ b/test/auth_web/controllers/app_controller_test.exs @@ -16,7 +16,7 @@ defmodule AuthWeb.AppControllerTest do test "lists all apps", %{conn: conn} do conn = admin_login(conn) conn = get(conn, Routes.app_path(conn, :index)) - assert html_response(conn, 200) =~ "Listing Apps" + assert html_response(conn, 200) =~ "Apps" end end From 45b7dbeae578c21b8dcd7ba4d19e166fd9033b71 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Tue, 1 Sep 2020 16:09:07 +0100 Subject: [PATCH 072/166] fix failing test (assertion) "Show App" > "App" #95 --- test/auth_web/controllers/app_controller_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/auth_web/controllers/app_controller_test.exs b/test/auth_web/controllers/app_controller_test.exs index fd104950..58f25f24 100644 --- a/test/auth_web/controllers/app_controller_test.exs +++ b/test/auth_web/controllers/app_controller_test.exs @@ -37,7 +37,7 @@ defmodule AuthWeb.AppControllerTest do assert redirected_to(conn) == Routes.app_path(conn, :show, id) conn = get(conn, Routes.app_path(conn, :show, id)) - assert html_response(conn, 200) =~ "Show App" + assert html_response(conn, 200) =~ "App" end test "renders errors when data is invalid", %{conn: conn} do From ecc6043dd1baf33c6d86e0c630ed657d577eb35f Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 2 Sep 2020 08:27:11 +0100 Subject: [PATCH 073/166] create apps before apikeys for #97 --- lib/auth/apikey.ex | 9 +++++---- lib/auth/app.ex | 3 ++- ...23_create_apps.exs => 20200424141937_create_apps.exs} | 4 ++-- priv/repo/migrations/20200424141938_create_apikeys.exs | 7 ++++--- 4 files changed, 13 insertions(+), 10 deletions(-) rename priv/repo/migrations/{20200830061923_create_apps.exs => 20200424141937_create_apps.exs} (81%) diff --git a/lib/auth/apikey.ex b/lib/auth/apikey.ex index 4c087636..20b32bab 100644 --- a/lib/auth/apikey.ex +++ b/lib/auth/apikey.ex @@ -12,11 +12,12 @@ defmodule Auth.Apikey do schema "apikeys" do field :client_secret, :binary field :client_id, :binary - field :description, :string - field :name, :string - field :url, :binary + # field :description, :string + # field :name, :string + # field :url, :binary field :person_id, :id field :status, :id + belongs_to :app, Auth.App timestamps() end @@ -24,7 +25,7 @@ defmodule Auth.Apikey do @doc false def changeset(apikey, attrs) do apikey - |> cast(attrs, [:client_id, :client_secret, :name, :description, :url, :person_id]) + |> cast(attrs, [:client_id, :client_secret, :person_id]) |> validate_required([:client_secret]) end diff --git a/lib/auth/app.ex b/lib/auth/app.ex index ae489fc7..b9ab7b86 100644 --- a/lib/auth/app.ex +++ b/lib/auth/app.ex @@ -16,7 +16,8 @@ defmodule Auth.App do field :url, :binary field :person_id, :id field :status, :id - field :apikey_id, :id + # field :apikey_id, :id + # has_many :apikeys, Auth.Status timestamps() end diff --git a/priv/repo/migrations/20200830061923_create_apps.exs b/priv/repo/migrations/20200424141937_create_apps.exs similarity index 81% rename from priv/repo/migrations/20200830061923_create_apps.exs rename to priv/repo/migrations/20200424141937_create_apps.exs index 27757cf9..fad28f20 100644 --- a/priv/repo/migrations/20200830061923_create_apps.exs +++ b/priv/repo/migrations/20200424141937_create_apps.exs @@ -9,13 +9,13 @@ defmodule Auth.Repo.Migrations.CreateApps do add :end, :naive_datetime add :person_id, references(:people, on_delete: :nothing) add :status, references(:status, on_delete: :nothing) - add :apikey_id, references(:apikeys, on_delete: :nothing) + # add :apikey_id, references(:apikeys, on_delete: :nothing) timestamps() end create index(:apps, [:person_id]) create index(:apps, [:status]) - create index(:apps, [:apikey_id]) + # create index(:apps, [:apikey_id]) end end diff --git a/priv/repo/migrations/20200424141938_create_apikeys.exs b/priv/repo/migrations/20200424141938_create_apikeys.exs index d96c53f1..dededfa5 100644 --- a/priv/repo/migrations/20200424141938_create_apikeys.exs +++ b/priv/repo/migrations/20200424141938_create_apikeys.exs @@ -5,9 +5,10 @@ defmodule Auth.Repo.Migrations.CreateApikeys do create table(:apikeys) do add :client_id, :binary add :client_secret, :binary - add :name, :string - add :description, :text - add :url, :binary + # add :name, :string + # add :description, :text + # add :url, :binary + add :app_id, references(:apps, on_delete: :nothing) add :person_id, references(:people, on_delete: :nothing) add :status, references(:status, on_delete: :nothing) From a4d5e5802f15b3166de198af854ba15b16e7ca50 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 2 Sep 2020 09:09:32 +0100 Subject: [PATCH 074/166] fix 1/4 failing auth tests #97 --- lib/auth/apikey.ex | 9 +++++++-- lib/auth_web/controllers/auth_controller.ex | 2 +- test/auth_web/controllers/auth_controller_test.exs | 13 +++++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/auth/apikey.ex b/lib/auth/apikey.ex index 20b32bab..307ab315 100644 --- a/lib/auth/apikey.ex +++ b/lib/auth/apikey.ex @@ -25,8 +25,9 @@ defmodule Auth.Apikey do @doc false def changeset(apikey, attrs) do apikey - |> cast(attrs, [:client_id, :client_secret, :person_id]) + |> cast(attrs, [:client_id, :client_secret, :person_id, :app_id]) |> validate_required([:client_secret]) + # |> put_assoc(:app, attrs.app_id) end def change_apikey(%Apikey{} = apikey) do @@ -47,6 +48,7 @@ defmodule Auth.Apikey do ) Repo.all(query) + |> Repo.preload(:app) end @doc """ @@ -63,7 +65,10 @@ defmodule Auth.Apikey do ** (Ecto.NoResultsError) """ - def get_apikey!(id), do: Repo.get!(__MODULE__, id) + def get_apikey!(id) do + Repo.get!(__MODULE__, id) + |> Repo.preload(:apps) + end @doc """ Updates a apikey. diff --git a/lib/auth_web/controllers/auth_controller.ex b/lib/auth_web/controllers/auth_controller.ex index 30b605d8..6f48538c 100644 --- a/lib/auth_web/controllers/auth_controller.ex +++ b/lib/auth_web/controllers/auth_controller.ex @@ -434,7 +434,7 @@ defmodule AuthWeb.AuthController do k.client_id == client_id else # check url matches the state for all other keys: - k.client_id == client_id and state =~ k.url + k.client_id == client_id and state =~ k.app.url end end) |> List.first() diff --git a/test/auth_web/controllers/auth_controller_test.exs b/test/auth_web/controllers/auth_controller_test.exs index 592410e8..61bfe329 100644 --- a/test/auth_web/controllers/auth_controller_test.exs +++ b/test/auth_web/controllers/auth_controller_test.exs @@ -58,10 +58,15 @@ defmodule AuthWeb.AuthControllerTest do auth_provider: "email" }) - {:ok, key} = - %{"name" => "test key", "url" => "example.com"} - |> AuthWeb.ApikeyController.make_apikey(person.id) - |> Auth.Apikey.create_apikey() + app_data = %{ + "name" => "example key", + "url" => "https://www.example.com", + "person_id" => person.id + } + {:ok, app} = Auth.App.create_app(app_data) + apikey_params = %{"app_id" => app.id} + key = AuthWeb.ApikeyController.make_apikey(apikey_params, person.id) + {:ok, key} = Auth.Apikey.create_apikey(key) state = "https://www.example.com/profile?auth_client_id=#{key.client_id}" secret = AuthWeb.AuthController.get_client_secret(key.client_id, state) From b7188d0f152f86be5339d0f66721e99b01d0be47 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Wed, 2 Sep 2020 10:37:59 +0100 Subject: [PATCH 075/166] update seeds and tests to create App before API Key #97 --- lib/auth/apikey.ex | 6 +++--- lib/auth/app.ex | 4 +++- lib/auth_web/templates/apikey/index.html.eex | 6 ------ lib/auth_web/templates/apikey/show.html.eex | 21 ------------------- priv/repo/seeds.exs | 10 ++++++--- .../controllers/apikey_controller_test.exs | 9 ++++---- 6 files changed, 17 insertions(+), 39 deletions(-) diff --git a/lib/auth/apikey.ex b/lib/auth/apikey.ex index 307ab315..b4692648 100644 --- a/lib/auth/apikey.ex +++ b/lib/auth/apikey.ex @@ -25,9 +25,9 @@ defmodule Auth.Apikey do @doc false def changeset(apikey, attrs) do apikey - |> cast(attrs, [:client_id, :client_secret, :person_id, :app_id]) + |> cast(attrs, [:client_id, :client_secret, :person_id]) |> validate_required([:client_secret]) - # |> put_assoc(:app, attrs.app_id) + |> put_assoc(:app, Map.get(attrs, "app")) end def change_apikey(%Apikey{} = apikey) do @@ -67,7 +67,7 @@ defmodule Auth.Apikey do """ def get_apikey!(id) do Repo.get!(__MODULE__, id) - |> Repo.preload(:apps) + |> Repo.preload(:app) end @doc """ diff --git a/lib/auth/app.ex b/lib/auth/app.ex index b9ab7b86..c5ffb36d 100644 --- a/lib/auth/app.ex +++ b/lib/auth/app.ex @@ -17,7 +17,7 @@ defmodule Auth.App do field :person_id, :id field :status, :id # field :apikey_id, :id - # has_many :apikeys, Auth.Status + has_many :apikeys, Auth.Apikey timestamps() end @@ -27,6 +27,8 @@ defmodule Auth.App do app |> cast(attrs, [:name, :description, :url, :end]) |> validate_required([:name, :url]) + + end @doc """ diff --git a/lib/auth_web/templates/apikey/index.html.eex b/lib/auth_web/templates/apikey/index.html.eex index 5b9450b8..0fd7724b 100644 --- a/lib/auth_web/templates/apikey/index.html.eex +++ b/lib/auth_web/templates/apikey/index.html.eex @@ -6,9 +6,6 @@ AUTH_API_KEY - Name - Description - Url @@ -17,9 +14,6 @@ <%= apikey.client_id %>/<%= apikey.client_secret %> - <%= apikey.name %> - <%= apikey.description %> - <%= apikey.url %> <%= link "View", to: Routes.apikey_path(@conn, :show, apikey), diff --git a/lib/auth_web/templates/apikey/show.html.eex b/lib/auth_web/templates/apikey/show.html.eex index 96740c0d..b8c1377d 100644 --- a/lib/auth_web/templates/apikey/show.html.eex +++ b/lib/auth_web/templates/apikey/show.html.eex @@ -25,27 +25,6 @@

-

- Key Name: - - <%= @apikey.name %> - -

- -

- Description: - - <%= @apikey.description %> - -

- -

- URL: - - <%= @apikey.url %> - -

- <%= link "< Back", to: Routes.apikey_path(@conn, :index), diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 41152667..51a89c3a 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -38,15 +38,19 @@ defmodule Auth.Seeds do end def create_apikey_for_admin(person) do - {:ok, key} = + {:ok, app} = %{ - "name" => "system admin key", + "name" => "default system app", "description" => "Created by /priv/repo/seeds.exs during setup.", "url" => "localhost:4000" } - |> AuthWeb.ApikeyController.make_apikey(person.id) + |> Auth.App.create_app() + + {:ok, key} = AuthWeb.ApikeyController.make_apikey(%{"app" => app}, person.id) |> Auth.Apikey.create_apikey() + # IO.inspect(key, label: "key") + api_key = key.client_id <> "/" <> key.client_secret # set the AUTH_API_KEY environment variable during test run: if(Mix.env() == :test) do diff --git a/test/auth_web/controllers/apikey_controller_test.exs b/test/auth_web/controllers/apikey_controller_test.exs index 1d717704..64464346 100644 --- a/test/auth_web/controllers/apikey_controller_test.exs +++ b/test/auth_web/controllers/apikey_controller_test.exs @@ -5,7 +5,7 @@ defmodule AuthWeb.ApikeyControllerTest do # alias Auth.Apikey # alias AuthWeb.ApikeyController, as: Ctrl @email System.get_env("ADMIN_EMAIL") - @create_attrs %{description: "some description", name: "some name", url: "some url"} + @create_attrs %{description: "some description", name: "some name", url: "localhost"} @update_attrs %{ client_secret: "updated client sec", description: "some updated desc", @@ -94,9 +94,11 @@ defmodule AuthWeb.ApikeyControllerTest do describe "create apikey" do test "redirects to show when data is valid", %{conn: conn} do + {:ok, app} = Auth.App.create_app(@create_attrs) + conn = admin_login(conn) - |> post(Routes.apikey_path(conn, :create), apikey: @create_attrs) + |> post(Routes.apikey_path(conn, :create), apikey: %{"app" => app}) assert %{id: id} = redirected_params(conn) assert redirected_to(conn) == Routes.apikey_path(conn, :show, id) @@ -162,9 +164,6 @@ defmodule AuthWeb.ApikeyControllerTest do conn = put(conn, Routes.apikey_path(conn, :update, key.id), apikey: @update_attrs) assert redirected_to(conn) == Routes.apikey_path(conn, :show, key) - - conn = get(conn, Routes.apikey_path(conn, :show, key)) - assert html_response(conn, 200) =~ "some updated desc" end test "renders errors when data is invalid", %{conn: conn} do From 87c52d6da8191fcccda3526a84eb21ee4288621c Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 3 Sep 2020 08:12:09 +0100 Subject: [PATCH 076/166] fix failing tests for Apps #97 --- test/auth/app_test.exs | 3 ++- test/auth_web/controllers/auth_controller_test.exs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/auth/app_test.exs b/test/auth/app_test.exs index 13b5b017..040c8ee7 100644 --- a/test/auth/app_test.exs +++ b/test/auth/app_test.exs @@ -19,7 +19,8 @@ defmodule Auth.AppTest do test "list_apps/0 returns all apps" do app = app_fixture() - assert App.list_apps() == [app] + a = List.first(Enum.filter(App.list_apps(), fn(a) -> a.id == app.id end)) + assert a.id == app.id end test "get_app!/1 returns the app with given id" do diff --git a/test/auth_web/controllers/auth_controller_test.exs b/test/auth_web/controllers/auth_controller_test.exs index 61bfe329..fbdd131c 100644 --- a/test/auth_web/controllers/auth_controller_test.exs +++ b/test/auth_web/controllers/auth_controller_test.exs @@ -64,7 +64,7 @@ defmodule AuthWeb.AuthControllerTest do "person_id" => person.id } {:ok, app} = Auth.App.create_app(app_data) - apikey_params = %{"app_id" => app.id} + apikey_params = %{"app" => app} key = AuthWeb.ApikeyController.make_apikey(apikey_params, person.id) {:ok, key} = Auth.Apikey.create_apikey(key) From 5d52ed3a0e1f4afbfbcafb53a716cbe809854210 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Tue, 8 Sep 2020 13:08:49 +0100 Subject: [PATCH 077/166] [WiP:TestsFailing] automatically create API key when new app is inserted see: https://github.com/dwyl/auth/issues/97#issuecomment-688821757 --- lib/auth/app.ex | 16 ++++++++++--- lib/auth_web/controllers/app_controller.ex | 1 + lib/auth_web/templates/app/show.html.eex | 28 ++++++++++++++++++---- priv/repo/seeds.exs | 3 ++- test/auth/apikey_test.exs | 4 ++-- test/auth/app_test.exs | 5 ++-- 6 files changed, 45 insertions(+), 12 deletions(-) diff --git a/lib/auth/app.ex b/lib/auth/app.ex index c5ffb36d..02c0c1ff 100644 --- a/lib/auth/app.ex +++ b/lib/auth/app.ex @@ -25,7 +25,7 @@ defmodule Auth.App do @doc false def changeset(app, attrs) do app - |> cast(attrs, [:name, :description, :url, :end]) + |> cast(attrs, [:name, :description, :url, :end, :person_id]) |> validate_required([:name, :url]) @@ -58,7 +58,10 @@ defmodule Auth.App do ** (Ecto.NoResultsError) """ - def get_app!(id), do: Repo.get!(App, id) + def get_app!(id) do + Repo.get!(App, id) + |> Repo.preload(:apikeys) + end @doc """ Creates a app. @@ -73,9 +76,16 @@ defmodule Auth.App do """ def create_app(attrs \\ %{}) do - %App{} + {:ok, app} = %App{} |> App.changeset(attrs) |> Repo.insert() + + # Create API Key for App https://github.com/dwyl/auth/issues/97 + AuthWeb.ApikeyController.make_apikey(%{"app" => app}, app.person_id) + |> Auth.Apikey.create_apikey() + + # return the App with the API Key preloaded: + {:ok, get_app!(app.id)} end @doc """ diff --git a/lib/auth_web/controllers/app_controller.ex b/lib/auth_web/controllers/app_controller.ex index 4f0cbbc6..896b2c31 100644 --- a/lib/auth_web/controllers/app_controller.ex +++ b/lib/auth_web/controllers/app_controller.ex @@ -26,6 +26,7 @@ defmodule AuthWeb.AppController do def show(conn, %{"id" => id}) do app = App.get_app!(id) + IO.inspect(app, label: "app 29") render(conn, "show.html", app: app) end diff --git a/lib/auth_web/templates/app/show.html.eex b/lib/auth_web/templates/app/show.html.eex index 1cda03f3..6c97ca60 100644 --- a/lib/auth_web/templates/app/show.html.eex +++ b/lib/auth_web/templates/app/show.html.eex @@ -1,10 +1,10 @@
-

Your App

+

Your App

- App Name: + Name: <%= @app.name %> @@ -24,16 +24,36 @@

+ <%= for apikey <- @app.apikeys do %> +

AUTH_API_KEY: +

+ <%= apikey.client_id %>/<%= apikey.client_secret %> +
+

+ +

Export it as an environment variable: +

+ export AUTH_API_KEY=<%= apikey.client_id %>/<%= apikey.client_secret %> +
+

+ <% end %> + + + <%= link "< Back", to: Routes.app_path(@conn, :index), - class: "pointer br2 ba b--orange bg-gold white pa3 ml1 mv1 f4 + class: "fl pointer br2 ba b--orange bg-gold white pa3 ml1 mv1 f4 shadow-hover bg-animate hover-bg-orange border-box no-underline" %> <%= link "Edit", to: Routes.app_path(@conn, :edit, @app), - class: "pointer br2 ba b--dark-green bg-green white pa3 ml1 mv1 f4 + class: "fr pointer br2 ba b--dark-green bg-green white pa3 ml1 mv1 f4 shadow-hover bg-animate hover-bg-dark-green border-box no-underline" %> diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 51a89c3a..4b2a4627 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -42,7 +42,8 @@ defmodule Auth.Seeds do %{ "name" => "default system app", "description" => "Created by /priv/repo/seeds.exs during setup.", - "url" => "localhost:4000" + "url" => "localhost:4000", + "person_id" => person.id } |> Auth.App.create_app() diff --git a/test/auth/apikey_test.exs b/test/auth/apikey_test.exs index 3e262bf8..12d617a5 100644 --- a/test/auth/apikey_test.exs +++ b/test/auth/apikey_test.exs @@ -8,7 +8,7 @@ defmodule Auth.ApikeyTest do person = Auth.Person.get_person_by_email(@email) keys = Auth.Apikey.list_apikeys_for_person(person.id) - assert length(keys) == 1 + assert length(keys) > 1 # Insert Two API keys: params = %{ @@ -27,6 +27,6 @@ defmodule Auth.ApikeyTest do |> Auth.Apikey.create_apikey() keys = Auth.Apikey.list_apikeys_for_person(person.id) - assert length(keys) == 3 + assert length(keys) > 3 end end diff --git a/test/auth/app_test.exs b/test/auth/app_test.exs index 040c8ee7..c885dec7 100644 --- a/test/auth/app_test.exs +++ b/test/auth/app_test.exs @@ -24,8 +24,9 @@ defmodule Auth.AppTest do end test "get_app!/1 returns the app with given id" do - app = app_fixture() - assert App.get_app!(app.id) == app + app = app_fixture(%{person_id: 1}) + a = App.get_app!(app.id) + assert a.id == app.id end test "create_app/1 with valid data creates a app" do From 22f5d5049a5f7b3db4bc8388ef2c3094bbd32168 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Tue, 8 Sep 2020 15:19:41 +0100 Subject: [PATCH 078/166] create statuses.json for #101 --- priv/repo/statuses.json | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 priv/repo/statuses.json diff --git a/priv/repo/statuses.json b/priv/repo/statuses.json new file mode 100644 index 00000000..b03758db --- /dev/null +++ b/priv/repo/statuses.json @@ -0,0 +1,57 @@ +[ + { + "text": "unverified", + "id": "1", + "desc": "People are considered unverified until they confirm their email address" + }, + { + "text": "verified", + "id": "2", + "desc": "People are verified once they confirm their email address" + }, + { + "text": "uncategorized", + "id": "3", + "desc": "All items are uncategorized when they are first created. (Yes, American spelling)" + }, + { + "text": "active", + "id": "4", + "desc": "An App, Item or Person can be active; this is the default state for an App" + }, + { + "text": "flagged", + "id": "5", + "desc": "A flagged App, Item or Person requires admin attention" + }, + { + "text": "deleted", + "id": "6", + "desc": "Soft-deleted items that no longer appear in UI but are kept for audit trail purposes" + }, + { + "text": "pending", + "id": "7", + "desc": "When an email or item is ready to be started/sent is still pending" + }, + { + "text": "sent", + "id": "8", + "desc": "An email that has been sent but not yet opened" + }, + { + "text": "opened", + "id": "9", + "desc": "When an email is opened by the recipient" + }, + { + "text": "bounce_transient", + "id": "10", + "desc": "Temporary email bounce e.g. because inbox is full" + }, + { + "text": "bounce_permanent", + "id": "11", + "desc": "Permanent email bounce e.g. when inbox doesn't exist" + } +] \ No newline at end of file From 2aa97f74e24b4121e8944403e1488d24fcce424f Mon Sep 17 00:00:00 2001 From: nelsonic Date: Tue, 8 Sep 2020 18:14:13 +0100 Subject: [PATCH 079/166] create insert_statuses/0 for #101 --- lib/auth/person.ex | 2 +- lib/auth/status.ex | 13 ++++++----- .../20191113100912_create_status.exs | 1 + priv/repo/seeds.exs | 22 ++++++++++++++----- priv/repo/statuses.json | 16 +++++++------- test/auth/status_test.exs | 6 ++--- 6 files changed, 37 insertions(+), 23 deletions(-) diff --git a/lib/auth/person.ex b/lib/auth/person.ex index f2111a7c..a38830fa 100644 --- a/lib/auth/person.ex +++ b/lib/auth/person.ex @@ -188,7 +188,7 @@ defmodule Auth.Person do end def get_status_verified do - status = Auth.Status.upsert_status("verified") + status = Auth.Status.upsert_status(%{"text" => "verified"}) status.id end diff --git a/lib/auth/status.ex b/lib/auth/status.ex index fe3f4ceb..94471188 100644 --- a/lib/auth/status.ex +++ b/lib/auth/status.ex @@ -7,6 +7,7 @@ defmodule Auth.Status do schema "status" do field :text, :string + field :desc, :string belongs_to :person, Auth.Person timestamps() @@ -15,24 +16,26 @@ defmodule Auth.Status do @doc false def changeset(status, attrs) do status - |> cast(attrs, [:text]) + |> cast(attrs, [:text, :desc]) |> validate_required([:text]) end - def create_status(text, person) do + def create_status(attrs, person) do %Status{} - |> changeset(%{text: text}) + |> changeset(attrs) |> put_assoc(:person, person) |> Repo.insert!() end - def upsert_status(text) do + def upsert_status(attrs) do + text = Map.get(attrs, "text") + |> IO.inspect(label: "text") case Auth.Repo.get_by(__MODULE__, text: text) do # create status nil -> email = System.get_env("ADMIN_EMAIL") person = Auth.Person.get_person_by_email(email) - create_status(text, person) + create_status(attrs, person) status -> status diff --git a/priv/repo/migrations/20191113100912_create_status.exs b/priv/repo/migrations/20191113100912_create_status.exs index 6d19f2c7..df640170 100644 --- a/priv/repo/migrations/20191113100912_create_status.exs +++ b/priv/repo/migrations/20191113100912_create_status.exs @@ -4,6 +4,7 @@ defmodule Auth.Repo.Migrations.CreateStatus do def change do create table(:status) do add :text, :string + add :desc, :string timestamps() end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 4b2a4627..398cfaa7 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -10,7 +10,7 @@ defmodule Auth.Seeds do alias Auth.{Person, Repo, Status} # put_assoc - import Ecto.Changeset + # import Ecto.Changeset def create_admin do email = System.get_env("ADMIN_EMAIL") @@ -20,9 +20,10 @@ defmodule Auth.Seeds do nil -> %Person{} |> Person.changeset(%{email: email}) - |> put_assoc(:statuses, [%Status{text: "verified"}]) + # |> put_assoc(:statuses, [%Status{text: "verified"}]) |> Repo.insert!() + person -> person end @@ -103,8 +104,8 @@ Auth.Seeds.create_admin() |> Auth.Seeds.create_apikey_for_admin() # scripts for creating default roles and permissions -defmodule SetupRoles do - alias Auth.Role +defmodule SeedData do + alias Auth.{Role, Status} def get_json(filepath) do # IO.inspect(filepath, label: "filepath") @@ -116,7 +117,7 @@ defmodule SetupRoles do json end - def create_default_roles() do + def create_default_roles do json = get_json("/priv/repo/default_roles.json") Enum.each(json, fn role -> @@ -124,8 +125,17 @@ defmodule SetupRoles do # |> IO.inspect() end) end + + def insert_statuses do + json = get_json("/priv/repo/statuses.json") + Enum.each(json, fn s -> + Status.upsert_status(s) + end) + + end end -SetupRoles.create_default_roles() +SeedData.create_default_roles() +SeedData.insert_statuses() # grant superadmin role to app owner: Auth.PeopleRoles.insert(1, 1, 1) diff --git a/priv/repo/statuses.json b/priv/repo/statuses.json index b03758db..0427ab2c 100644 --- a/priv/repo/statuses.json +++ b/priv/repo/statuses.json @@ -1,23 +1,23 @@ [ { - "text": "unverified", + "text": "verified", "id": "1", - "desc": "People are considered unverified until they confirm their email address" + "desc": "People are verified once they confirm their email address" }, { - "text": "verified", + "text": "uncategorized", "id": "2", - "desc": "People are verified once they confirm their email address" + "desc": "All items are uncategorized when they are first created. (Yes, US spelling)" }, { - "text": "uncategorized", + "text": "active", "id": "3", - "desc": "All items are uncategorized when they are first created. (Yes, American spelling)" + "desc": "An App, Item or Person can be active; this is the default state for an App" }, { - "text": "active", + "text": "done", "id": "4", - "desc": "An App, Item or Person can be active; this is the default state for an App" + "desc": "Items marked as done are complete" }, { "text": "flagged", diff --git a/test/auth/status_test.exs b/test/auth/status_test.exs index 76addb66..a71018d7 100644 --- a/test/auth/status_test.exs +++ b/test/auth/status_test.exs @@ -3,10 +3,10 @@ defmodule Auth.StatusTest do alias Auth.{Status} test "upsert_status/1 inserts or updates a status record" do - status = Status.upsert_status("verified") + status = Status.upsert_status(%{"text" => "verified"}) assert status.id == 1 - new_status = Status.upsert_status("amaze") - assert new_status.id == 2 + new_status = Status.upsert_status(%{"text" => "amaze"}) + assert new_status.id == 12 end end From 205c828a8d109051f4066ef465fbcad6dbc55373 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Tue, 8 Sep 2020 19:13:01 +0100 Subject: [PATCH 080/166] rework create_app/1 to use case statement for errors #97 --- lib/auth/app.ex | 22 ++++++++++--------- lib/auth_web/controllers/app_controller.ex | 2 +- .../controllers/app_controller_test.exs | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/auth/app.ex b/lib/auth/app.ex index 02c0c1ff..c450045e 100644 --- a/lib/auth/app.ex +++ b/lib/auth/app.ex @@ -76,16 +76,18 @@ defmodule Auth.App do """ def create_app(attrs \\ %{}) do - {:ok, app} = %App{} - |> App.changeset(attrs) - |> Repo.insert() - - # Create API Key for App https://github.com/dwyl/auth/issues/97 - AuthWeb.ApikeyController.make_apikey(%{"app" => app}, app.person_id) - |> Auth.Apikey.create_apikey() - - # return the App with the API Key preloaded: - {:ok, get_app!(app.id)} + case %App{} |> App.changeset(attrs) |> Repo.insert() do + {:ok, app} -> + # Create API Key for App https://github.com/dwyl/auth/issues/97 + AuthWeb.ApikeyController.make_apikey(%{"app" => app}, app.person_id) + |> Auth.Apikey.create_apikey() + + # return the App with the API Key preloaded: + {:ok, get_app!(app.id)} + + {:error, err} -> + {:error, err} + end end @doc """ diff --git a/lib/auth_web/controllers/app_controller.ex b/lib/auth_web/controllers/app_controller.ex index 896b2c31..86c543b5 100644 --- a/lib/auth_web/controllers/app_controller.ex +++ b/lib/auth_web/controllers/app_controller.ex @@ -26,7 +26,7 @@ defmodule AuthWeb.AppController do def show(conn, %{"id" => id}) do app = App.get_app!(id) - IO.inspect(app, label: "app 29") + # restrict viewership to owner||admin https://github.com/dwyl/auth/issues/99 render(conn, "show.html", app: app) end diff --git a/test/auth_web/controllers/app_controller_test.exs b/test/auth_web/controllers/app_controller_test.exs index 58f25f24..a1817e74 100644 --- a/test/auth_web/controllers/app_controller_test.exs +++ b/test/auth_web/controllers/app_controller_test.exs @@ -5,7 +5,7 @@ defmodule AuthWeb.AppControllerTest do @create_attrs %{description: "some description", end: ~N[2010-04-17 14:00:00], name: "some name", url: "some url"} @update_attrs %{description: "some updated description", end: ~N[2011-05-18 15:01:01], name: "some updated name", url: "some updated url"} - @invalid_attrs %{description: nil, end: nil, name: nil, url: nil} + @invalid_attrs %{description: nil, end: nil, name: nil, url: nil, person_id: nil} def fixture(:app) do {:ok, app} = App.create_app(@create_attrs) From 2da212d7cf2f6ca463219c010a6bbbb4d403493a Mon Sep 17 00:00:00 2001 From: nelsonic Date: Tue, 8 Sep 2020 19:13:18 +0100 Subject: [PATCH 081/166] mix format --- lib/auth/app.ex | 2 -- lib/auth/status.ex | 6 ++++-- lib/auth_web/controllers/app_controller.ex | 2 +- lib/auth_web/controllers/people_controller.ex | 1 + priv/repo/seeds.exs | 6 +++--- test/auth/app_test.exs | 16 +++++++++++++--- .../auth_web/controllers/app_controller_test.exs | 15 +++++++++++++-- .../controllers/auth_controller_test.exs | 1 + 8 files changed, 36 insertions(+), 13 deletions(-) diff --git a/lib/auth/app.ex b/lib/auth/app.ex index c450045e..7c919366 100644 --- a/lib/auth/app.ex +++ b/lib/auth/app.ex @@ -27,8 +27,6 @@ defmodule Auth.App do app |> cast(attrs, [:name, :description, :url, :end, :person_id]) |> validate_required([:name, :url]) - - end @doc """ diff --git a/lib/auth/status.ex b/lib/auth/status.ex index 94471188..9f11f069 100644 --- a/lib/auth/status.ex +++ b/lib/auth/status.ex @@ -28,8 +28,10 @@ defmodule Auth.Status do end def upsert_status(attrs) do - text = Map.get(attrs, "text") - |> IO.inspect(label: "text") + text = + Map.get(attrs, "text") + |> IO.inspect(label: "text") + case Auth.Repo.get_by(__MODULE__, text: text) do # create status nil -> diff --git a/lib/auth_web/controllers/app_controller.ex b/lib/auth_web/controllers/app_controller.ex index 86c543b5..720c41af 100644 --- a/lib/auth_web/controllers/app_controller.ex +++ b/lib/auth_web/controllers/app_controller.ex @@ -26,7 +26,7 @@ defmodule AuthWeb.AppController do def show(conn, %{"id" => id}) do app = App.get_app!(id) - # restrict viewership to owner||admin https://github.com/dwyl/auth/issues/99 + #  restrict viewership to owner||admin https://github.com/dwyl/auth/issues/99 render(conn, "show.html", app: app) end diff --git a/lib/auth_web/controllers/people_controller.ex b/lib/auth_web/controllers/people_controller.ex index 21fd53ab..1a7dc42e 100644 --- a/lib/auth_web/controllers/people_controller.ex +++ b/lib/auth_web/controllers/people_controller.ex @@ -29,6 +29,7 @@ defmodule AuthWeb.PeopleController do # should be visible to superadmin and people with "admin" role if conn.assigns.person.id == 1 do person = Auth.Person.get_person_by_id(Map.get(params, "person_id")) + render(conn, :profile, person: person, roles: Auth.PeopleRoles.get_roles_for_person(person.id), diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 398cfaa7..1408ab28 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -23,7 +23,6 @@ defmodule Auth.Seeds do # |> put_assoc(:statuses, [%Status{text: "verified"}]) |> Repo.insert!() - person -> person end @@ -48,7 +47,8 @@ defmodule Auth.Seeds do } |> Auth.App.create_app() - {:ok, key} = AuthWeb.ApikeyController.make_apikey(%{"app" => app}, person.id) + {:ok, key} = + AuthWeb.ApikeyController.make_apikey(%{"app" => app}, person.id) |> Auth.Apikey.create_apikey() # IO.inspect(key, label: "key") @@ -128,10 +128,10 @@ defmodule SeedData do def insert_statuses do json = get_json("/priv/repo/statuses.json") + Enum.each(json, fn s -> Status.upsert_status(s) end) - end end diff --git a/test/auth/app_test.exs b/test/auth/app_test.exs index c885dec7..0082351c 100644 --- a/test/auth/app_test.exs +++ b/test/auth/app_test.exs @@ -4,8 +4,18 @@ defmodule Auth.AppTest do describe "apps" do alias Auth.App - @valid_attrs %{description: "some description", end: ~N[2010-04-17 14:00:00], name: "some name", url: "some url"} - @update_attrs %{description: "some updated description", end: ~N[2011-05-18 15:01:01], name: "some updated name", url: "some updated url"} + @valid_attrs %{ + description: "some description", + end: ~N[2010-04-17 14:00:00], + name: "some name", + url: "some url" + } + @update_attrs %{ + description: "some updated description", + end: ~N[2011-05-18 15:01:01], + name: "some updated name", + url: "some updated url" + } @invalid_attrs %{description: nil, end: nil, name: nil, url: nil} def app_fixture(attrs \\ %{}) do @@ -19,7 +29,7 @@ defmodule Auth.AppTest do test "list_apps/0 returns all apps" do app = app_fixture() - a = List.first(Enum.filter(App.list_apps(), fn(a) -> a.id == app.id end)) + a = List.first(Enum.filter(App.list_apps(), fn a -> a.id == app.id end)) assert a.id == app.id end diff --git a/test/auth_web/controllers/app_controller_test.exs b/test/auth_web/controllers/app_controller_test.exs index a1817e74..b60de7d8 100644 --- a/test/auth_web/controllers/app_controller_test.exs +++ b/test/auth_web/controllers/app_controller_test.exs @@ -3,8 +3,18 @@ defmodule AuthWeb.AppControllerTest do alias Auth.App - @create_attrs %{description: "some description", end: ~N[2010-04-17 14:00:00], name: "some name", url: "some url"} - @update_attrs %{description: "some updated description", end: ~N[2011-05-18 15:01:01], name: "some updated name", url: "some updated url"} + @create_attrs %{ + description: "some description", + end: ~N[2010-04-17 14:00:00], + name: "some name", + url: "some url" + } + @update_attrs %{ + description: "some updated description", + end: ~N[2011-05-18 15:01:01], + name: "some updated name", + url: "some updated url" + } @invalid_attrs %{description: nil, end: nil, name: nil, url: nil, person_id: nil} def fixture(:app) do @@ -83,6 +93,7 @@ defmodule AuthWeb.AppControllerTest do conn = admin_login(conn) conn = delete(conn, Routes.app_path(conn, :delete, app)) assert redirected_to(conn) == Routes.app_path(conn, :index) + assert_error_sent 404, fn -> get(conn, Routes.app_path(conn, :show, app)) end diff --git a/test/auth_web/controllers/auth_controller_test.exs b/test/auth_web/controllers/auth_controller_test.exs index fbdd131c..2c86abfc 100644 --- a/test/auth_web/controllers/auth_controller_test.exs +++ b/test/auth_web/controllers/auth_controller_test.exs @@ -63,6 +63,7 @@ defmodule AuthWeb.AuthControllerTest do "url" => "https://www.example.com", "person_id" => person.id } + {:ok, app} = Auth.App.create_app(app_data) apikey_params = %{"app" => app} key = AuthWeb.ApikeyController.make_apikey(apikey_params, person.id) From 0ae675b346a91a2f08c53b46679993bfacba50da Mon Sep 17 00:00:00 2001 From: nelsonic Date: Tue, 8 Sep 2020 22:37:32 +0100 Subject: [PATCH 082/166] fix failing tests #102 --- lib/auth/apikey.ex | 2 +- lib/auth/app.ex | 29 +++++++++++++++---- lib/auth/status.ex | 6 +--- lib/auth_web/controllers/app_controller.ex | 3 ++ lib/auth_web/templates/app/form.html.eex | 6 ++-- lib/auth_web/templates/app/index.html.eex | 2 +- lib/auth_web/templates/app/show.html.eex | 2 +- .../migrations/20200424141937_create_apps.exs | 2 +- priv/repo/seeds.exs | 21 +++++++------- test/auth/apikey_test.exs | 4 +-- test/auth/app_test.exs | 17 ++++++----- .../controllers/app_controller_test.exs | 12 ++++---- .../controllers/auth_controller_test.exs | 3 +- 13 files changed, 66 insertions(+), 43 deletions(-) diff --git a/lib/auth/apikey.ex b/lib/auth/apikey.ex index b4692648..33ea3af9 100644 --- a/lib/auth/apikey.ex +++ b/lib/auth/apikey.ex @@ -101,6 +101,6 @@ defmodule Auth.Apikey do """ def delete_apikey(%Apikey{} = apikey) do - Repo.delete(apikey) + Repo.delete(apikey) |> IO.inspect(label: "delete_apikey:104") end end diff --git a/lib/auth/app.ex b/lib/auth/app.ex index 7c919366..a64584de 100644 --- a/lib/auth/app.ex +++ b/lib/auth/app.ex @@ -10,7 +10,7 @@ defmodule Auth.App do alias __MODULE__ schema "apps" do - field :description, :binary + field :desc, :binary field :end, :naive_datetime field :name, :binary field :url, :binary @@ -25,7 +25,7 @@ defmodule Auth.App do @doc false def changeset(app, attrs) do app - |> cast(attrs, [:name, :description, :url, :end, :person_id]) + |> cast(attrs, [:name, :desc, :url, :end, :person_id, :status]) |> validate_required([:name, :url]) end @@ -39,7 +39,10 @@ defmodule Auth.App do """ def list_apps do - Repo.all(App) + App + |> where([a], a.status != 6) + |> Repo.all() + end @doc """ @@ -57,8 +60,18 @@ defmodule Auth.App do """ def get_app!(id) do - Repo.get!(App, id) + # IO.inspect(id, label: "get_app!/1 id:60") + # Repo.get!(App, id, where: :status != 6) + # Repo.get!(App, id, where: :status not in [6]) + # |> Repo.preload(:apikeys) + # |> IO.inspect(label: "get_app!:63") + App + |> where([a], a.id == ^id and a.status != 6) + # |> select([:id, :name, :url, :desc]) + |> Repo.one() |> Repo.preload(:apikeys) + # |> IO.inspect(label: "app:69") + end @doc """ @@ -74,6 +87,8 @@ defmodule Auth.App do """ def create_app(attrs \\ %{}) do + # IO.inspect(attrs, label: "attrs:87") + # attrs = Map.merge(attrs, %{status: 3}) # active case %App{} |> App.changeset(attrs) |> Repo.insert() do {:ok, app} -> # Create API Key for App https://github.com/dwyl/auth/issues/97 @@ -119,7 +134,11 @@ defmodule Auth.App do """ def delete_app(%App{} = app) do - Repo.delete(app) + # IO.inspect(app, label: "app:131") + # Repo.delete(app) + # |> IO.inspect(label: "delete") + update_app(app, %{status: 6}) + # |> IO.inspect(label: "delete:135") end @doc """ diff --git a/lib/auth/status.ex b/lib/auth/status.ex index 9f11f069..9bd4a9c2 100644 --- a/lib/auth/status.ex +++ b/lib/auth/status.ex @@ -28,11 +28,7 @@ defmodule Auth.Status do end def upsert_status(attrs) do - text = - Map.get(attrs, "text") - |> IO.inspect(label: "text") - - case Auth.Repo.get_by(__MODULE__, text: text) do + case Auth.Repo.get_by(__MODULE__, text: Map.get(attrs, "text")) do # create status nil -> email = System.get_env("ADMIN_EMAIL") diff --git a/lib/auth_web/controllers/app_controller.ex b/lib/auth_web/controllers/app_controller.ex index 720c41af..703d6868 100644 --- a/lib/auth_web/controllers/app_controller.ex +++ b/lib/auth_web/controllers/app_controller.ex @@ -13,8 +13,10 @@ defmodule AuthWeb.AppController do end def create(conn, %{"app" => app_params}) do + # IO.inspect(app_params, label: "app_params:16") case App.create_app(app_params) do {:ok, app} -> + # IO.inspect(app, label: "app:19") conn |> put_flash(:info, "App created successfully.") |> redirect(to: Routes.app_path(conn, :show, app)) @@ -31,6 +33,7 @@ defmodule AuthWeb.AppController do end def edit(conn, %{"id" => id}) do + # IO.inspect(id, label: "edit id:36") app = App.get_app!(id) changeset = App.change_app(app) render(conn, "edit.html", app: app, changeset: changeset) diff --git a/lib/auth_web/templates/app/form.html.eex b/lib/auth_web/templates/app/form.html.eex index ae40a158..76b51489 100644 --- a/lib/auth_web/templates/app/form.html.eex +++ b/lib/auth_web/templates/app/form.html.eex @@ -10,10 +10,10 @@ placeholder: "A distinctive name"%> <%= error_tag f, :name %>
- <%= label f, :description, class: "mt3" %> - <%= textarea f, :description, class: "db w-100 mt2 pa2 ba b--black", + <%= label f, :desc, class: "mt3" %> + <%= textarea f, :desc, class: "db w-100 mt2 pa2 ba b--black", placeholder: "Be as descriptive as possible." %> - <%= error_tag f, :description %> + <%= error_tag f, :desc %>
<%= label f, :url, class: "pt3" %> <%= text_input f, :url, class: "db w-100 mt2 pa2 ba b--dark-grey", diff --git a/lib/auth_web/templates/app/index.html.eex b/lib/auth_web/templates/app/index.html.eex index 3fd50ba5..d574cdc6 100644 --- a/lib/auth_web/templates/app/index.html.eex +++ b/lib/auth_web/templates/app/index.html.eex @@ -12,7 +12,7 @@ <%= for app <- @apps do %> <%= app.name %> - <%= app.description %> + <%= app.desc %> <%= app.url %> <%= link "View", to: Routes.app_path(@conn, :show, app), diff --git a/lib/auth_web/templates/app/show.html.eex b/lib/auth_web/templates/app/show.html.eex index 6c97ca60..fc5eac3b 100644 --- a/lib/auth_web/templates/app/show.html.eex +++ b/lib/auth_web/templates/app/show.html.eex @@ -13,7 +13,7 @@

Description: - <%= @app.description %> + <%= @app.desc %>

diff --git a/priv/repo/migrations/20200424141937_create_apps.exs b/priv/repo/migrations/20200424141937_create_apps.exs index fad28f20..9e907bf8 100644 --- a/priv/repo/migrations/20200424141937_create_apps.exs +++ b/priv/repo/migrations/20200424141937_create_apps.exs @@ -4,7 +4,7 @@ defmodule Auth.Repo.Migrations.CreateApps do def change do create table(:apps) do add :name, :binary - add :description, :binary + add :desc, :binary add :url, :binary add :end, :naive_datetime add :person_id, references(:people, on_delete: :nothing) diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 1408ab28..48ae48a9 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -41,17 +41,16 @@ defmodule Auth.Seeds do {:ok, app} = %{ "name" => "default system app", - "description" => "Created by /priv/repo/seeds.exs during setup.", + "desc" => "Created by /priv/repo/seeds.exs during setup.", "url" => "localhost:4000", - "person_id" => person.id + "person_id" => person.id, + "status" => 3 } |> Auth.App.create_app() - {:ok, key} = - AuthWeb.ApikeyController.make_apikey(%{"app" => app}, person.id) - |> Auth.Apikey.create_apikey() - - # IO.inspect(key, label: "key") + # API Key is automatically created by create_app/1 + # https://github.com/dwyl/auth/issues/97 + key = List.first(app.apikeys) api_key = key.client_id <> "/" <> key.client_secret # set the AUTH_API_KEY environment variable during test run: @@ -100,9 +99,6 @@ defmodule Auth.Seeds do end end -Auth.Seeds.create_admin() -|> Auth.Seeds.create_apikey_for_admin() - # scripts for creating default roles and permissions defmodule SeedData do alias Auth.{Role, Status} @@ -135,7 +131,10 @@ defmodule SeedData do end end -SeedData.create_default_roles() SeedData.insert_statuses() +Auth.Seeds.create_admin() +|> Auth.Seeds.create_apikey_for_admin() + +SeedData.create_default_roles() # grant superadmin role to app owner: Auth.PeopleRoles.insert(1, 1, 1) diff --git a/test/auth/apikey_test.exs b/test/auth/apikey_test.exs index 12d617a5..3e262bf8 100644 --- a/test/auth/apikey_test.exs +++ b/test/auth/apikey_test.exs @@ -8,7 +8,7 @@ defmodule Auth.ApikeyTest do person = Auth.Person.get_person_by_email(@email) keys = Auth.Apikey.list_apikeys_for_person(person.id) - assert length(keys) > 1 + assert length(keys) == 1 # Insert Two API keys: params = %{ @@ -27,6 +27,6 @@ defmodule Auth.ApikeyTest do |> Auth.Apikey.create_apikey() keys = Auth.Apikey.list_apikeys_for_person(person.id) - assert length(keys) > 3 + assert length(keys) == 3 end end diff --git a/test/auth/app_test.exs b/test/auth/app_test.exs index 0082351c..b5ff61e1 100644 --- a/test/auth/app_test.exs +++ b/test/auth/app_test.exs @@ -5,16 +5,18 @@ defmodule Auth.AppTest do alias Auth.App @valid_attrs %{ - description: "some description", + desc: "some description", end: ~N[2010-04-17 14:00:00], name: "some name", - url: "some url" + url: "some url", + status: 3 } @update_attrs %{ - description: "some updated description", + desc: "some updated description", end: ~N[2011-05-18 15:01:01], name: "some updated name", - url: "some updated url" + url: "some updated url", + status: 3 } @invalid_attrs %{description: nil, end: nil, name: nil, url: nil} @@ -41,7 +43,7 @@ defmodule Auth.AppTest do test "create_app/1 with valid data creates a app" do assert {:ok, %App{} = app} = App.create_app(@valid_attrs) - assert app.description == "some description" + assert app.desc == "some description" assert app.end == ~N[2010-04-17 14:00:00] assert app.name == "some name" assert app.url == "some url" @@ -54,7 +56,7 @@ defmodule Auth.AppTest do test "update_app/2 with valid data updates the app" do app = app_fixture() assert {:ok, %App{} = app} = App.update_app(app, @update_attrs) - assert app.description == "some updated description" + assert app.desc == "some updated description" assert app.end == ~N[2011-05-18 15:01:01] assert app.name == "some updated name" assert app.url == "some updated url" @@ -69,7 +71,8 @@ defmodule Auth.AppTest do test "delete_app/1 deletes the app" do app = app_fixture() assert {:ok, %App{}} = App.delete_app(app) - assert_raise Ecto.NoResultsError, fn -> App.get_app!(app.id) end + app = App.get_app!(app.id) + assert is_nil(app) end test "change_app/1 returns a app changeset" do diff --git a/test/auth_web/controllers/app_controller_test.exs b/test/auth_web/controllers/app_controller_test.exs index b60de7d8..ed31e260 100644 --- a/test/auth_web/controllers/app_controller_test.exs +++ b/test/auth_web/controllers/app_controller_test.exs @@ -4,16 +4,18 @@ defmodule AuthWeb.AppControllerTest do alias Auth.App @create_attrs %{ - description: "some description", + desc: "some description", end: ~N[2010-04-17 14:00:00], name: "some name", - url: "some url" + url: "some url", + status: 3 } @update_attrs %{ - description: "some updated description", + desc: "some updated description", end: ~N[2011-05-18 15:01:01], name: "some updated name", - url: "some updated url" + url: "some updated url", + status: 3 } @invalid_attrs %{description: nil, end: nil, name: nil, url: nil, person_id: nil} @@ -94,7 +96,7 @@ defmodule AuthWeb.AppControllerTest do conn = delete(conn, Routes.app_path(conn, :delete, app)) assert redirected_to(conn) == Routes.app_path(conn, :index) - assert_error_sent 404, fn -> + assert_error_sent 500, fn -> get(conn, Routes.app_path(conn, :show, app)) end end diff --git a/test/auth_web/controllers/auth_controller_test.exs b/test/auth_web/controllers/auth_controller_test.exs index 2c86abfc..b6f1eb29 100644 --- a/test/auth_web/controllers/auth_controller_test.exs +++ b/test/auth_web/controllers/auth_controller_test.exs @@ -61,7 +61,8 @@ defmodule AuthWeb.AuthControllerTest do app_data = %{ "name" => "example key", "url" => "https://www.example.com", - "person_id" => person.id + "person_id" => person.id, + "status" => 3 } {:ok, app} = Auth.App.create_app(app_data) From e07952ec89b7100430a922523461d8643ffd98ad Mon Sep 17 00:00:00 2001 From: nelsonic Date: Tue, 8 Sep 2020 22:37:44 +0100 Subject: [PATCH 083/166] mix format --- lib/auth/app.ex | 3 +-- priv/repo/seeds.exs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/auth/app.ex b/lib/auth/app.ex index a64584de..47050ae3 100644 --- a/lib/auth/app.ex +++ b/lib/auth/app.ex @@ -42,7 +42,6 @@ defmodule Auth.App do App |> where([a], a.status != 6) |> Repo.all() - end @doc """ @@ -70,8 +69,8 @@ defmodule Auth.App do # |> select([:id, :name, :url, :desc]) |> Repo.one() |> Repo.preload(:apikeys) - # |> IO.inspect(label: "app:69") + # |> IO.inspect(label: "app:69") end @doc """ diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 48ae48a9..a11c9bef 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -132,6 +132,7 @@ defmodule SeedData do end SeedData.insert_statuses() + Auth.Seeds.create_admin() |> Auth.Seeds.create_apikey_for_admin() From 36fd51e6d9e11be92cde64751b50900f239237f2 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Tue, 8 Sep 2020 22:41:14 +0100 Subject: [PATCH 084/166] remove IO.inspect() https://github.com/dwyl/auth/pull/85#pullrequestreview-484510440 --- lib/auth/apikey.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/auth/apikey.ex b/lib/auth/apikey.ex index 33ea3af9..b4692648 100644 --- a/lib/auth/apikey.ex +++ b/lib/auth/apikey.ex @@ -101,6 +101,6 @@ defmodule Auth.Apikey do """ def delete_apikey(%Apikey{} = apikey) do - Repo.delete(apikey) |> IO.inspect(label: "delete_apikey:104") + Repo.delete(apikey) end end From 978504f89b46d203dbcb86a98a6db0d3fa7bc903 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Tue, 8 Sep 2020 22:59:23 +0100 Subject: [PATCH 085/166] add Top Nav for /apps /people & /roles https://github.com/dwyl/auth/issues/103#issuecomment-689159011 --- lib/auth_web/templates/layout/nav.html.eex | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/auth_web/templates/layout/nav.html.eex b/lib/auth_web/templates/layout/nav.html.eex index dce5099e..8a8165a3 100644 --- a/lib/auth_web/templates/layout/nav.html.eex +++ b/lib/auth_web/templates/layout/nav.html.eex @@ -12,22 +12,22 @@ dwyl logo
- + From 78cb2d8ce90af73e7303b03ab4936239a4d75b34 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Tue, 8 Sep 2020 23:08:20 +0100 Subject: [PATCH 086/166] make "New App" button more obvious: https://github.com/dwyl/auth/issues/103#issuecomment-689162296 --- lib/auth_web/templates/app/index.html.eex | 7 ++++++- lib/auth_web/templates/auth/welcome.html.eex | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/auth_web/templates/app/index.html.eex b/lib/auth_web/templates/app/index.html.eex index d574cdc6..570131a1 100644 --- a/lib/auth_web/templates/app/index.html.eex +++ b/lib/auth_web/templates/app/index.html.eex @@ -39,7 +39,12 @@ -<%= link "New App", to: Routes.app_path(@conn, :new) %> + + <%= link "New App", to: Routes.app_path(@conn, :new), + class: "pointer br2 ba b--dark-green bg-green white pa3 ml1 mv1 f3 + shadow-hover bg-animate hover-bg-dark-green border-box no-underline" %> + + -
diff --git a/lib/auth_web/templates/apikey/new.html.eex b/lib/auth_web/templates/apikey/new.html.eex deleted file mode 100644 index 76ed1f6e..00000000 --- a/lib/auth_web/templates/apikey/new.html.eex +++ /dev/null @@ -1,15 +0,0 @@ -
-

New Apikey

- - <%= render "form.html", Map.put(assigns, :action, - Routes.apikey_path(@conn, :create)) %> - -
- <%= link "< Back", to: Routes.apikey_path(@conn, :index), - class: "pointer br2 ba b--orange bg-gold white pa3 ml1 mv1 f4 mt3 - shadow-hover bg-animate hover-bg-orange border-box no-underline", - data: - [confirm: "Are you sure you want to disguard this form? - (The data cannot be recovered!)"] - %> -
diff --git a/lib/auth_web/templates/apikey/show.html.eex b/lib/auth_web/templates/apikey/show.html.eex deleted file mode 100644 index b8c1377d..00000000 --- a/lib/auth_web/templates/apikey/show.html.eex +++ /dev/null @@ -1,42 +0,0 @@ -
-

Your AUTH_API_KEY

- -

- To securely access your data from outside the dwyl app, - you will need to use an API Key. Keep this key safe. - Anyone who has the key can access - all of your data. -

- -

-

- <%= @apikey.client_id %>/<%= @apikey.client_secret %> -
-

- -

Export it as an environment variable: -

- export AUTH_API_KEY=<%= @apikey.client_id %>/<%= @apikey.client_secret %> -
-

- - - - - <%= link "< Back", to: Routes.apikey_path(@conn, :index), - class: "pointer br2 ba b--orange bg-gold white pa3 ml1 mv1 f4 - shadow-hover bg-animate hover-bg-orange border-box no-underline" - %> - - - <%= link "Edit", to: Routes.apikey_path(@conn, :edit, @apikey), - class: "pointer br2 ba b--dark-green bg-green white pa3 ml1 mv1 f4 - shadow-hover bg-animate hover-bg-dark-green border-box no-underline" - %> - - -
diff --git a/lib/auth_web/templates/app/show.html.eex b/lib/auth_web/templates/app/show.html.eex index e2594405..6a15ee2a 100644 --- a/lib/auth_web/templates/app/show.html.eex +++ b/lib/auth_web/templates/app/show.html.eex @@ -25,7 +25,7 @@

<%= for apikey <- @app.apikeys do %> -

AUTH_API_KEY: +

API KEY:

@@ -47,10 +47,11 @@ <%= link "< All Apps", to: Routes.app_path(@conn, :index), - class: "fl pointer br2 ba b--orange bg-gold white pa3 ml1 mv1 f4 - shadow-hover bg-animate hover-bg-orange border-box no-underline" + class: "fl pointer br2 ba b--dark-blue bg-blue white pa3 ml1 mv1 f4 + shadow-hover bg-animate hover-bg-dark-blue border-box no-underline" %> + <%= link "Edit", to: Routes.app_path(@conn, :edit, @app), class: "fr pointer br2 ba b--dark-green bg-green white pa3 ml1 mv1 f4 @@ -58,4 +59,11 @@ %> + + <%= link "Reset API Key", to: Routes.app_path(@conn, :resetapikey, @app), + class: "fl ml5 pointer br2 ba b--orange bg-gold white pa3 ml1 mv1 f4 + shadow-hover bg-animate hover-bg-orange border-box no-underline" + %> + +
From 13cca0e289805a246eb9d168093729962bbb1bd8 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 10 Sep 2020 08:22:15 +0100 Subject: [PATCH 099/166] remove lib/auth_web/views/apikey_view.ex as no longer needed #107 --- lib/auth_web/views/apikey_view.ex | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 lib/auth_web/views/apikey_view.ex diff --git a/lib/auth_web/views/apikey_view.ex b/lib/auth_web/views/apikey_view.ex deleted file mode 100644 index c135c170..00000000 --- a/lib/auth_web/views/apikey_view.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule AuthWeb.ApikeyView do - use AuthWeb, :view -end From 259149d324d3751772f2992dee98f7030cdd854f Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 10 Sep 2020 09:24:34 +0100 Subject: [PATCH 100/166] all tests passing again after removing API Key UI #107 --- lib/auth/apikey.ex | 55 +++++- lib/auth/app.ex | 4 +- lib/auth_web/controllers/apikey_controller.ex | 181 +++++++----------- lib/auth_web/controllers/app_controller.ex | 25 +++ lib/auth_web/controllers/auth_controller.ex | 10 +- lib/auth_web/router.ex | 1 + test/auth/apikey_test.exs | 77 ++++++-- .../controllers/apikey_controller_test.exs | 54 +----- .../controllers/auth_controller_test.exs | 40 ++-- 9 files changed, 233 insertions(+), 214 deletions(-) diff --git a/lib/auth/apikey.ex b/lib/auth/apikey.ex index ab51fcf4..b9f039cf 100644 --- a/lib/auth/apikey.ex +++ b/lib/auth/apikey.ex @@ -19,19 +19,62 @@ defmodule Auth.Apikey do timestamps() end + + @doc """ + `encrypt_encode/1` does exactly what it's name suggests, + AES Encrypts a string of plaintext and then Base58 encodes it. + We encode it using Base58 so it's human-friendly (readable). + """ + def encrypt_encode(plaintext) do + Fields.AES.encrypt(plaintext) |> Base58.encode() + end + + @doc """ + `create_api_key/1` uses the `encrypt_encode/1` to create an API Key + that is just two strings joined with a forwardslash ("/"). + This allows us to use a *single* environment variable. + """ + def create_api_key(person_id) do + encrypt_encode(person_id) <> "/" <> encrypt_encode(person_id) + end + + @doc """ + `decode_decrypt/1` accepts a `key` and attempts to Base58.decode + followed by AES.decrypt it. If decode or decrypt fails, return 0 (zero). + """ + def decode_decrypt(key) do + try do + key |> Base58.decode() |> Fields.AES.decrypt() |> String.to_integer() + rescue + ArgumentError -> + 0 + end + end + + def decrypt_api_key(key) do + key |> String.split("/") |> List.first() |> decode_decrypt() + end + + @doc false def changeset(apikey, attrs) do apikey |> cast(attrs, [:client_id, :client_secret, :person_id, :status]) - |> validate_required([:client_secret]) |> put_assoc(:app, Map.get(attrs, "app")) end - def change_apikey(%Apikey{} = apikey) do - Apikey.changeset(apikey, %{}) - end - - def create_apikey(attrs \\ %{}) do + # def change_apikey(%Apikey{} = apikey) do + # Apikey.changeset(apikey, %{}) + # end + + def create_apikey(app) do + attrs = %{ + "client_secret" => encrypt_encode(app.person_id), + "client_id" => encrypt_encode(app.person_id), + "person_id" => app.person_id, + "status" => 3, + "app" => app + } %Apikey{} |> Apikey.changeset(attrs) |> Repo.insert() diff --git a/lib/auth/app.ex b/lib/auth/app.ex index cffe4be9..4b599721 100644 --- a/lib/auth/app.ex +++ b/lib/auth/app.ex @@ -80,9 +80,7 @@ defmodule Auth.App do case %App{} |> App.changeset(attrs) |> Repo.insert() do {:ok, app} -> # Create API Key for App https://github.com/dwyl/auth/issues/97 - %{"app" => app, "status" => 3} - |> AuthWeb.ApikeyController.make_apikey(app.person_id) - |> Auth.Apikey.create_apikey() + Auth.Apikey.create_apikey(app) # return the App with the API Key preloaded: {:ok, get_app!(app.id)} diff --git a/lib/auth_web/controllers/apikey_controller.ex b/lib/auth_web/controllers/apikey_controller.ex index dc3972fb..4b1f6502 100644 --- a/lib/auth_web/controllers/apikey_controller.ex +++ b/lib/auth_web/controllers/apikey_controller.ex @@ -5,111 +5,78 @@ defmodule AuthWeb.ApikeyController do use AuthWeb, :controller alias Auth.Apikey - def index(conn, _params) do - person_id = conn.assigns.person.id - apikeys = Apikey.list_apikeys_for_person(person_id) - render(conn, "index.html", apikeys: apikeys) - end - - def new(conn, _params) do - changeset = Apikey.change_apikey(%Apikey{}) - render(conn, "new.html", changeset: changeset) - end - - def encrypt_encode(plaintext) do - Fields.AES.encrypt(plaintext) |> Base58.encode() - end - - def create_api_key(person_id) do - encrypt_encode(person_id) <> "/" <> encrypt_encode(person_id) - end - - @doc """ - `decode_decrypt/1` accepts a `key` and attempts to Base58.decode - followed by AES.decrypt it. If decode or decrypt fails, return 0 (zero). - """ - def decode_decrypt(key) do - try do - key |> Base58.decode() |> Fields.AES.decrypt() |> String.to_integer() - rescue - ArgumentError -> - 0 - end - end - - def decrypt_api_key(key) do - key |> String.split("/") |> List.first() |> decode_decrypt() - end - - def make_apikey(apikey_params, person_id) do - Map.merge(apikey_params, %{ - "client_secret" => encrypt_encode(person_id), - "client_id" => encrypt_encode(person_id), - "person_id" => person_id - }) - end - - def create(conn, %{"apikey" => apikey_params}) do - {:ok, apikey} = - apikey_params - |> make_apikey(conn.assigns.person.id) - |> Apikey.create_apikey() - - conn - |> put_flash(:info, "Apikey created successfully.") - |> redirect(to: Routes.apikey_path(conn, :show, apikey)) - end - - def show(conn, %{"id" => id}) do - apikey = Apikey.get_apikey!(id) - # combined = apikey.client_id <> "/" <> apikey.client_secret - render(conn, "show.html", apikey: apikey) - end - - def edit(conn, %{"id" => id}) do - apikey = Auth.Apikey.get_apikey!(id) - - if apikey.person_id == conn.assigns.person.id do - changeset = Auth.Apikey.change_apikey(apikey) - render(conn, "edit.html", apikey: apikey, changeset: changeset) - else - AuthWeb.AuthController.not_found(conn, "API KEY " <> id <> " not found.") - end - end - - @doc """ - `update/2` updates a given API Key. Checks if the person attempting - to update the key is the "owner" of the key before updating. - """ - def update(conn, %{"id" => id, "apikey" => apikey_params}) do - apikey = Apikey.get_apikey!(id) - # check that the person attempting to update the key owns it! - if apikey.person_id == conn.assigns.person.id do - case Apikey.update_apikey(apikey, apikey_params) do - {:ok, apikey} -> - conn - |> put_flash(:info, "Apikey updated successfully.") - |> redirect(to: Routes.apikey_path(conn, :show, apikey)) - - {:error, %Ecto.Changeset{} = changeset} -> - render(conn, "edit.html", apikey: apikey, changeset: changeset) - end - else - AuthWeb.AuthController.not_found(conn, "API KEY " <> id <> " not found.") - end - end - - def delete(conn, %{"id" => id}) do - apikey = Apikey.get_apikey!(id) - # check that the person attempting to delete the key owns it! - if apikey.person_id == conn.assigns.person.id do - {:ok, _apikey} = Apikey.delete_apikey(apikey) - - conn - |> put_flash(:info, "Apikey deleted successfully.") - |> redirect(to: Routes.apikey_path(conn, :index)) - else - AuthWeb.AuthController.not_found(conn, "API KEY " <> id <> " not found.") - end - end + # def index(conn, _params) do + # person_id = conn.assigns.person.id + # apikeys = Apikey.list_apikeys_for_person(person_id) + # render(conn, "index.html", apikeys: apikeys) + # end + + # def new(conn, _params) do + # changeset = Apikey.change_apikey(%Apikey{}) + # render(conn, "new.html", changeset: changeset) + # end + + # def create(conn, %{"apikey" => apikey_params}) do + # {:ok, apikey} = + # apikey_params + # |> make_apikey(conn.assigns.person.id) + # |> Apikey.create_apikey() + + # conn + # |> put_flash(:info, "Apikey created successfully.") + # |> redirect(to: Routes.apikey_path(conn, :show, apikey)) + # end + + # def show(conn, %{"id" => id}) do + # apikey = Apikey.get_apikey!(id) + # # combined = apikey.client_id <> "/" <> apikey.client_secret + # render(conn, "show.html", apikey: apikey) + # end + + # def edit(conn, %{"id" => id}) do + # apikey = Auth.Apikey.get_apikey!(id) + + # if apikey.person_id == conn.assigns.person.id do + # changeset = Auth.Apikey.change_apikey(apikey) + # render(conn, "edit.html", apikey: apikey, changeset: changeset) + # else + # AuthWeb.AuthController.not_found(conn, "API KEY " <> id <> " not found.") + # end + # end + + # @doc """ + # `update/2` updates a given API Key. Checks if the person attempting + # to update the key is the "owner" of the key before updating. + # """ + # def update(conn, %{"id" => id, "apikey" => apikey_params}) do + # apikey = Apikey.get_apikey!(id) + # # check that the person attempting to update the key owns it! + # if apikey.person_id == conn.assigns.person.id do + # case Apikey.update_apikey(apikey, apikey_params) do + # {:ok, apikey} -> + # conn + # |> put_flash(:info, "Apikey updated successfully.") + # |> redirect(to: Routes.apikey_path(conn, :show, apikey)) + + # {:error, %Ecto.Changeset{} = changeset} -> + # render(conn, "edit.html", apikey: apikey, changeset: changeset) + # end + # else + # AuthWeb.AuthController.not_found(conn, "API KEY " <> id <> " not found.") + # end + # end + + # def delete(conn, %{"id" => id}) do + # apikey = Apikey.get_apikey!(id) + # # check that the person attempting to delete the key owns it! + # if apikey.person_id == conn.assigns.person.id do + # {:ok, _apikey} = Apikey.delete_apikey(apikey) + + # conn + # |> put_flash(:info, "Apikey deleted successfully.") + # |> redirect(to: Routes.apikey_path(conn, :index)) + # else + # AuthWeb.AuthController.not_found(conn, "API KEY " <> id <> " not found.") + # end + # end end diff --git a/lib/auth_web/controllers/app_controller.ex b/lib/auth_web/controllers/app_controller.ex index 912daecb..3d166d7b 100644 --- a/lib/auth_web/controllers/app_controller.ex +++ b/lib/auth_web/controllers/app_controller.ex @@ -65,4 +65,29 @@ defmodule AuthWeb.AppController do |> put_flash(:info, "App deleted successfully.") |> redirect(to: Routes.app_path(conn, :index)) end + + @doc """ + Reset the API Key in case of suspected compromise. + + """ + def resetapikey(conn, %{"id" => id}) do + IO.inspect(id, label: "id:74") + app = App.get_app!(id) + IO.inspect(app, label: "app:76") + + Enum.each(app.apikeys, fn k -> + IO.inspect(k, label: "apikey:78") + if k.status == 3 do + # soft delete the apikey + Auth.Apikey.update_apikey(Map.delete(k, :app), %{status: 6}) + end + end) + + + + + # get the app again and render it: + + render(conn, "show.html", app: app) + end end diff --git a/lib/auth_web/controllers/auth_controller.ex b/lib/auth_web/controllers/auth_controller.ex index c3b764f9..4e92621f 100644 --- a/lib/auth_web/controllers/auth_controller.ex +++ b/lib/auth_web/controllers/auth_controller.ex @@ -297,7 +297,7 @@ defmodule AuthWeb.AuthController do changeset: Auth.Person.password_new_changeset(%{email: email}), # so we can redirect after creatig a password state: state, - email: AuthWeb.ApikeyController.encrypt_encode(email) + email: Auth.Apikey.encrypt_encode(email) ) end @@ -312,7 +312,7 @@ defmodule AuthWeb.AuthController do def make_verify_link(conn, person, state) do AuthPlug.Helpers.get_baseurl_from_conn(conn) <> "/auth/verify?id=" <> - AuthWeb.ApikeyController.encrypt_encode(person.id) <> + Auth.Apikey.encrypt_encode(person.id) <> "&referer=" <> state end @@ -322,7 +322,7 @@ defmodule AuthWeb.AuthController do # |> render("password_create.html", # changeset: Auth.Person.password_new_changeset(%{email: params["email"]}), # state: params["state"], # so we can redirect after creatig a password - # email: AuthWeb.ApikeyController.encrypt_encode(params["email"]) + # email: AuthWeb.Apikey.encrypt_encode(params["email"]) # ) # end @@ -394,7 +394,7 @@ defmodule AuthWeb.AuthController do end def verify_email(conn, params) do - id = AuthWeb.ApikeyController.decode_decrypt(params["id"]) + id = Auth.Apikey.decode_decrypt(params["id"]) person = Auth.Person.verify_person_by_id(id) redirect_or_render(conn, person, params["referer"]) end @@ -421,7 +421,7 @@ defmodule AuthWeb.AuthController do end def get_client_secret(client_id, state) do - person_id = AuthWeb.ApikeyController.decode_decrypt(client_id) + person_id = Auth.Apikey.decode_decrypt(client_id) # decode_decrypt fails with state 0 if person_id == 0 do 0 diff --git a/lib/auth_web/router.ex b/lib/auth_web/router.ex index ea8b9b52..50df91e2 100644 --- a/lib/auth_web/router.ex +++ b/lib/auth_web/router.ex @@ -50,6 +50,7 @@ defmodule AuthWeb.Router do resources "/roles", RoleController resources "/permissions", PermissionController + get "/apps/:id/resetapikey", AppController, :resetapikey resources "/apps", AppController # resources "/settings/apikeys", ApikeyController end diff --git a/test/auth/apikey_test.exs b/test/auth/apikey_test.exs index 3e262bf8..c4361a80 100644 --- a/test/auth/apikey_test.exs +++ b/test/auth/apikey_test.exs @@ -1,32 +1,79 @@ defmodule Auth.ApikeyTest do - # use Auth.DataCase - use AuthWeb.ConnCase + use Auth.DataCase + # use AuthWeb.ConnCase + use ExUnitProperties # alias Auth.Apikey @email System.get_env("ADMIN_EMAIL") + describe "Create an AUTH_API_KEY for a given person_id" do + test "encrypt_encode/1 returns a base58 we can decrypt" do + person_id = 1 + key = Auth.Apikey.encrypt_encode(person_id) + + decrypted = + key + |> Base58.decode() + |> Fields.AES.decrypt() + |> String.to_integer() + + assert decrypted == person_id + end + + test "decode_decrypt/1 reverses the operation of encrypt_encode/1" do + person_id = 4_869_234_521 + key = Auth.Apikey.encrypt_encode(person_id) + id = Auth.Apikey.decode_decrypt(key) + assert person_id == id + end + + test "create_api_key/1 creates an AUTH_API_KEY" do + person_id = 123_456_789 + key = Auth.Apikey.create_api_key(person_id) + assert key =~ "/" + # parts = String.split(key, "/") + # assert decode_decrypt(List.first(parts)) == person_id + end + + test "decrypt_api_key/1 decrypts an AUTH_API_KEY" do + person_id = 1234 + key = Auth.Apikey.create_api_key(person_id) + decrypted = Auth.Apikey.decrypt_api_key(key) + assert decrypted == person_id + end + + test "decode_decrypt/1 with invalid client_id" do + valid_key = Auth.Apikey.encrypt_encode(1) + person_id = Auth.Apikey.decode_decrypt(valid_key) + assert person_id == 1 + + invalid_key = String.slice(valid_key, 0..-2) + error = Auth.Apikey.decode_decrypt(invalid_key) + assert error == 0 + end + + property "Check a batch of int values can be decoded decode_decrypt/1" do + check all(int <- integer()) do + assert Auth.Apikey.decode_decrypt( + Auth.Apikey.encrypt_encode(int) + ) == int + end + end + end + test "list_apikeys_for_person/1 returns all apikeys person" do person = Auth.Person.get_person_by_email(@email) keys = Auth.Apikey.list_apikeys_for_person(person.id) assert length(keys) == 1 - # Insert Two API keys: - params = %{ - # "description" => "test key", + # Insert Another App (And API Key): + Auth.App.create_app(%{ "name" => "My Amazing Key", "url" => "http://localhost:400", - "person_id" => person.id, - "client_secret" => AuthWeb.ApikeyController.encrypt_encode(person.id) - } - - Auth.Apikey.create_apikey(params) - - Map.merge(params, %{ - "client_secret" => AuthWeb.ApikeyController.encrypt_encode(person.id) + "person_id" => person.id }) - |> Auth.Apikey.create_apikey() keys = Auth.Apikey.list_apikeys_for_person(person.id) - assert length(keys) == 3 + assert length(keys) == 2 end end diff --git a/test/auth_web/controllers/apikey_controller_test.exs b/test/auth_web/controllers/apikey_controller_test.exs index ddbdd1a0..6b43e185 100644 --- a/test/auth_web/controllers/apikey_controller_test.exs +++ b/test/auth_web/controllers/apikey_controller_test.exs @@ -1,6 +1,5 @@ defmodule AuthWeb.ApikeyControllerTest do use AuthWeb.ConnCase - use ExUnitProperties # @email System.get_env("ADMIN_EMAIL") # @create_attrs %{ @@ -18,58 +17,7 @@ defmodule AuthWeb.ApikeyControllerTest do # } # @invalid_attrs %{client_secret: nil, description: nil, key_id: nil, name: nil, url: nil} - describe "Create an AUTH_API_KEY for a given person_id" do - test "encrypt_encode/1 returns a base58 we can decrypt" do - person_id = 1 - key = AuthWeb.ApikeyController.encrypt_encode(person_id) - - decrypted = - key - |> Base58.decode() - |> Fields.AES.decrypt() - |> String.to_integer() - - assert decrypted == person_id - end - - test "decode_decrypt/1 reverses the operation of encrypt_encode/1" do - person_id = 4_869_234_521 - key = AuthWeb.ApikeyController.encrypt_encode(person_id) - id = AuthWeb.ApikeyController.decode_decrypt(key) - assert person_id == id - end - - test "create_api_key/1 creates an AUTH_API_KEY" do - person_id = 123_456_789 - key = AuthWeb.ApikeyController.create_api_key(person_id) - assert key =~ "/" - end - - test "decrypt_api_key/1 decrypts an AUTH_API_KEY" do - person_id = 1234 - key = AuthWeb.ApikeyController.create_api_key(person_id) - decrypted = AuthWeb.ApikeyController.decrypt_api_key(key) - assert decrypted == person_id - end - - test "decode_decrypt/1 with invalid client_id" do - valid_key = AuthWeb.ApikeyController.encrypt_encode(1) - person_id = AuthWeb.ApikeyController.decode_decrypt(valid_key) - assert person_id == 1 - - invalid_key = String.slice(valid_key, 0..-2) - error = AuthWeb.ApikeyController.decode_decrypt(invalid_key) - assert error == 0 - end - - property "Check a batch of int values can be decoded decode_decrypt/1" do - check all(int <- integer()) do - assert AuthWeb.ApikeyController.decode_decrypt( - AuthWeb.ApikeyController.encrypt_encode(int) - ) == int - end - end - end + # describe "index" do # test "lists all apikeys", %{conn: conn} do diff --git a/test/auth_web/controllers/auth_controller_test.exs b/test/auth_web/controllers/auth_controller_test.exs index b6f1eb29..e63c43d5 100644 --- a/test/auth_web/controllers/auth_controller_test.exs +++ b/test/auth_web/controllers/auth_controller_test.exs @@ -2,6 +2,13 @@ defmodule AuthWeb.AuthControllerTest do use AuthWeb.ConnCase # @email System.get_env("ADMIN_EMAIL") + @app_data %{ + "name" => "example key", + "url" => "https://www.example.com", + "person_id" => 1, + "status" => 3 + } + test "GET /", %{conn: conn} do conn = get(conn, "/") assert html_response(conn, 200) =~ "Sign in" @@ -52,23 +59,8 @@ defmodule AuthWeb.AuthControllerTest do end test "get_client_secret(client_id, state) gets the secret for the given client_id" do - person = - Auth.Person.create_person(%{ - email: "alex@gmail.com", - auth_provider: "email" - }) - - app_data = %{ - "name" => "example key", - "url" => "https://www.example.com", - "person_id" => person.id, - "status" => 3 - } - - {:ok, app} = Auth.App.create_app(app_data) - apikey_params = %{"app" => app} - key = AuthWeb.ApikeyController.make_apikey(apikey_params, person.id) - {:ok, key} = Auth.Apikey.create_apikey(key) + {:ok, app} = Auth.App.create_app(@app_data) + key = List.first(app.apikeys) state = "https://www.example.com/profile?auth_client_id=#{key.client_id}" secret = AuthWeb.AuthController.get_client_secret(key.client_id, state) @@ -109,10 +101,8 @@ defmodule AuthWeb.AuthControllerTest do test "google_handler/2 show welcome page", %{conn: conn} do # Google Auth Mock makes the state https://www.example.com # so we need to create a new API_KEY with that url: - {:ok, key} = - %{"name" => "example key", "url" => "https://www.example.com"} - |> AuthWeb.ApikeyController.make_apikey(1) - |> Auth.Apikey.create_apikey() + {:ok, app} = Auth.App.create_app(@app_data) + key = List.first(app.apikeys) conn = get(conn, "/auth/google/callback", %{ @@ -295,7 +285,7 @@ defmodule AuthWeb.AuthControllerTest do params = %{ "person" => %{ - "email" => AuthWeb.ApikeyController.encrypt_encode("anabela@mail.com"), + "email" => Auth.Apikey.encrypt_encode("anabela@mail.com"), "password" => "thiswillbehashed" } } @@ -307,7 +297,7 @@ defmodule AuthWeb.AuthControllerTest do test "password_create/2 display form when password not valid", %{conn: conn} do params = %{ "person" => %{ - "email" => AuthWeb.ApikeyController.encrypt_encode("anabela@mail.com"), + "email" => Auth.Apikey.encrypt_encode("anabela@mail.com"), "password" => "short" } } @@ -348,7 +338,7 @@ defmodule AuthWeb.AuthControllerTest do params = %{ "person" => %{ - "email" => AuthWeb.ApikeyController.encrypt_encode(data.email), + "email" => Auth.Apikey.encrypt_encode(data.email), "password" => "thiswillbehashed", "state" => state } @@ -374,7 +364,7 @@ defmodule AuthWeb.AuthControllerTest do params = %{ "person" => %{ - "email" => AuthWeb.ApikeyController.encrypt_encode(data.email), + "email" => Auth.Apikey.encrypt_encode(data.email), "password" => "fail", "state" => state } From b0ff09fc337fd32fa3c341f07409d6ea8f3deac9 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 10 Sep 2020 09:25:36 +0100 Subject: [PATCH 101/166] =?UTF-8?q?=F0=9F=94=AA=20DELETE=20apikey=5Fcontro?= =?UTF-8?q?ller.ex=20and=20apikey=5Fcontroller=5Ftest.exs=20#107?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/auth_web/controllers/apikey_controller.ex | 82 -------- .../controllers/apikey_controller_test.exs | 181 ------------------ 2 files changed, 263 deletions(-) delete mode 100644 lib/auth_web/controllers/apikey_controller.ex delete mode 100644 test/auth_web/controllers/apikey_controller_test.exs diff --git a/lib/auth_web/controllers/apikey_controller.ex b/lib/auth_web/controllers/apikey_controller.ex deleted file mode 100644 index 4b1f6502..00000000 --- a/lib/auth_web/controllers/apikey_controller.ex +++ /dev/null @@ -1,82 +0,0 @@ -defmodule AuthWeb.ApikeyController do - @moduledoc """ - Defines API Key controller functions - """ - use AuthWeb, :controller - alias Auth.Apikey - - # def index(conn, _params) do - # person_id = conn.assigns.person.id - # apikeys = Apikey.list_apikeys_for_person(person_id) - # render(conn, "index.html", apikeys: apikeys) - # end - - # def new(conn, _params) do - # changeset = Apikey.change_apikey(%Apikey{}) - # render(conn, "new.html", changeset: changeset) - # end - - # def create(conn, %{"apikey" => apikey_params}) do - # {:ok, apikey} = - # apikey_params - # |> make_apikey(conn.assigns.person.id) - # |> Apikey.create_apikey() - - # conn - # |> put_flash(:info, "Apikey created successfully.") - # |> redirect(to: Routes.apikey_path(conn, :show, apikey)) - # end - - # def show(conn, %{"id" => id}) do - # apikey = Apikey.get_apikey!(id) - # # combined = apikey.client_id <> "/" <> apikey.client_secret - # render(conn, "show.html", apikey: apikey) - # end - - # def edit(conn, %{"id" => id}) do - # apikey = Auth.Apikey.get_apikey!(id) - - # if apikey.person_id == conn.assigns.person.id do - # changeset = Auth.Apikey.change_apikey(apikey) - # render(conn, "edit.html", apikey: apikey, changeset: changeset) - # else - # AuthWeb.AuthController.not_found(conn, "API KEY " <> id <> " not found.") - # end - # end - - # @doc """ - # `update/2` updates a given API Key. Checks if the person attempting - # to update the key is the "owner" of the key before updating. - # """ - # def update(conn, %{"id" => id, "apikey" => apikey_params}) do - # apikey = Apikey.get_apikey!(id) - # # check that the person attempting to update the key owns it! - # if apikey.person_id == conn.assigns.person.id do - # case Apikey.update_apikey(apikey, apikey_params) do - # {:ok, apikey} -> - # conn - # |> put_flash(:info, "Apikey updated successfully.") - # |> redirect(to: Routes.apikey_path(conn, :show, apikey)) - - # {:error, %Ecto.Changeset{} = changeset} -> - # render(conn, "edit.html", apikey: apikey, changeset: changeset) - # end - # else - # AuthWeb.AuthController.not_found(conn, "API KEY " <> id <> " not found.") - # end - # end - - # def delete(conn, %{"id" => id}) do - # apikey = Apikey.get_apikey!(id) - # # check that the person attempting to delete the key owns it! - # if apikey.person_id == conn.assigns.person.id do - # {:ok, _apikey} = Apikey.delete_apikey(apikey) - - # conn - # |> put_flash(:info, "Apikey deleted successfully.") - # |> redirect(to: Routes.apikey_path(conn, :index)) - # else - # AuthWeb.AuthController.not_found(conn, "API KEY " <> id <> " not found.") - # end - # end -end diff --git a/test/auth_web/controllers/apikey_controller_test.exs b/test/auth_web/controllers/apikey_controller_test.exs deleted file mode 100644 index 6b43e185..00000000 --- a/test/auth_web/controllers/apikey_controller_test.exs +++ /dev/null @@ -1,181 +0,0 @@ -defmodule AuthWeb.ApikeyControllerTest do - use AuthWeb.ConnCase - - # @email System.get_env("ADMIN_EMAIL") - # @create_attrs %{ - # description: "some description", - # name: "some name", url: "localhost", - # status: 3, - # person_id: 1 - # } - # @update_attrs %{ - # client_secret: "updated client sec", - # description: "some updated desc", - # name: "updated name", - # url: "surl", - # status: 3 - # } - # @invalid_attrs %{client_secret: nil, description: nil, key_id: nil, name: nil, url: nil} - - - - # describe "index" do - # test "lists all apikeys", %{conn: conn} do - # conn = admin_login(conn) - # conn = get(conn, Routes.apikey_path(conn, :index)) - - # assert html_response(conn, 200) =~ "Auth API Keys" - # end - # end - - # describe "new apikey" do - # test "renders form", %{conn: conn} do - # conn = admin_login(conn) - # conn = get(conn, Routes.apikey_path(conn, :new)) - - # assert html_response(conn, 200) =~ "New Apikey" - # end - # end - - # describe "create apikey" do - # test "redirects to show when data is valid", %{conn: conn} do - # {:ok, app} = Auth.App.create_app(@create_attrs) - # IO.inspect(app, label: "app") - # conn = admin_login(conn) - # params = %{"app" => app} - # conn = post(conn, Routes.apikey_path(conn, :create), apikey: params) - - # assert %{id: id} = redirected_params(conn) - # IO.inspect(id, label: "id") - # assert redirected_to(conn) == Routes.apikey_path(conn, :show, id) - - # conn = get(conn, Routes.apikey_path(conn, :show, id)) - # IO.inspect(conn, label: "conn") - # # assert html_response(conn, 200) =~ "Your AUTH_API_KEY" - # end - # end - - # describe "edit apikey" do - # test "renders form for editing chosen apikey", %{conn: conn} do - # person = Auth.Person.get_person_by_email(@email) - # conn = admin_login(conn) - - # {:ok, key} = - # %{"name" => "test key", "url" => "http://localhost:4000"} - # |> AuthWeb.ApikeyController.make_apikey(person.id) - # |> Auth.Apikey.create_apikey() - - # conn = get(conn, Routes.apikey_path(conn, :edit, key.id)) - # assert html_response(conn, 200) =~ "Edit Apikey" - # end - - # test "attempt to edit a key I don't own > should 404", %{conn: conn} do - # person = Auth.Person.get_person_by_email(@email) - - # wrong_person_data = %{ - # email: "wronger@gmail.com", - # auth_provider: "email", - # id: 42 - # } - - # Auth.Person.create_person(wrong_person_data) - # conn = AuthPlug.create_jwt_session(conn, wrong_person_data) - - # {:ok, key} = - # %{"name" => "test key", "url" => "http://localhost:4000"} - # |> AuthWeb.ApikeyController.make_apikey(person.id) - # |> Auth.Apikey.create_apikey() - - # conn = get(conn, Routes.apikey_path(conn, :edit, key.id)) - # assert html_response(conn, 404) =~ "not found" - # end - # end - - # describe "update apikey" do - # test "redirects when data is valid", %{conn: conn} do - # person = Auth.Person.get_person_by_email(@email) - # conn = AuthPlug.create_jwt_session(conn, %{id: person.id}) - - # {:ok, key} = - # %{"name" => "test key", "url" => "http://localhost:4000"} - # |> AuthWeb.ApikeyController.make_apikey(person.id) - # |> Auth.Apikey.create_apikey() - - # conn = put(conn, Routes.apikey_path(conn, :update, key.id), apikey: @update_attrs) - # assert redirected_to(conn) == Routes.apikey_path(conn, :show, key) - # end - - # test "renders errors when data is invalid", %{conn: conn} do - # person = Auth.Person.get_person_by_email(@email) - # conn = admin_login(conn) - - # {:ok, key} = - # %{"name" => "test key", "url" => "http://localhost:4000"} - # |> AuthWeb.ApikeyController.make_apikey(person.id) - # |> Auth.Apikey.create_apikey() - - # conn = put(conn, Routes.apikey_path(conn, :update, key), apikey: @invalid_attrs) - # assert html_response(conn, 200) =~ "Edit Apikey" - # end - - # test "attempt to UPDATE a key I don't own > should 404", %{conn: conn} do - # person = Auth.Person.get_person_by_email(@email) - # # create session with wrong person: - # wrong_person_data = %{ - # email: "wronger@gmail.com", - # auth_provider: "email", - # id: 42 - # } - - # Auth.Person.create_person(wrong_person_data) - # conn = AuthPlug.create_jwt_session(conn, wrong_person_data) - - # {:ok, key} = - # %{"name" => "test key", "url" => "http://localhost:4000", "person_id" => person.id} - # |> AuthWeb.ApikeyController.make_apikey(person.id) - # |> Auth.Apikey.create_apikey() - - # conn = put(conn, Routes.apikey_path(conn, :update, key.id), apikey: @update_attrs) - # assert html_response(conn, 404) =~ "not found" - # end - # end - - # describe "delete apikey" do - # test "deletes chosen apikey", %{conn: conn} do - # person = Auth.Person.get_person_by_email(@email) - # conn = admin_login(conn) - - # {:ok, key} = - # %{"name" => "test key", "url" => "http://localhost:4000"} - # |> AuthWeb.ApikeyController.make_apikey(person.id) - # |> Auth.Apikey.create_apikey() - - # conn = delete(conn, Routes.apikey_path(conn, :delete, key)) - # assert redirected_to(conn) == Routes.apikey_path(conn, :index) - - # assert_error_sent 404, fn -> - # get(conn, Routes.apikey_path(conn, :show, key)) - # end - # end - - # test "cannot delete a key belonging to someone else! 404", %{conn: conn} do - # wrong_person_data = %{ - # email: "wronger@gmail.com", - # auth_provider: "email", - # id: 42 - # } - - # Auth.Person.create_person(wrong_person_data) - # conn = AuthPlug.create_jwt_session(conn, wrong_person_data) - # person = Auth.Person.get_person_by_email(@email) - - # {:ok, key} = - # %{"name" => "test key", "url" => "http://localhost:4000"} - # |> AuthWeb.ApikeyController.make_apikey(person.id) - # |> Auth.Apikey.create_apikey() - - # conn = delete(conn, Routes.apikey_path(conn, :delete, key)) - # assert html_response(conn, 404) =~ "not found" - # end - # end -end From f59cdf98e67dc72dae0c4a2c80dd35b64e47664a Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 10 Sep 2020 09:59:46 +0100 Subject: [PATCH 102/166] all tests passing again after removing API Key UI #107 --- lib/auth/apikey.ex | 46 ------------------- lib/auth_web/controllers/app_controller.ex | 15 +++--- lib/auth_web/templates/app/show.html.eex | 2 + .../controllers/app_controller_test.exs | 10 ++++ 4 files changed, 18 insertions(+), 55 deletions(-) diff --git a/lib/auth/apikey.ex b/lib/auth/apikey.ex index b9f039cf..75d180b7 100644 --- a/lib/auth/apikey.ex +++ b/lib/auth/apikey.ex @@ -55,18 +55,12 @@ defmodule Auth.Apikey do key |> String.split("/") |> List.first() |> decode_decrypt() end - - @doc false def changeset(apikey, attrs) do apikey |> cast(attrs, [:client_id, :client_secret, :person_id, :status]) |> put_assoc(:app, Map.get(attrs, "app")) end - # def change_apikey(%Apikey{} = apikey) do - # Apikey.changeset(apikey, %{}) - # end - def create_apikey(app) do attrs = %{ "client_secret" => encrypt_encode(app.person_id), @@ -89,28 +83,6 @@ defmodule Auth.Apikey do |> Repo.preload(:app) end - @doc """ - Gets a single apikey. - - Raises `Ecto.NoResultsError` if the Apikey does not exist. - - ## Examples - - iex> get_apikey!(123) - %Apikey{} - - iex> get_apikey!(456) - ** (Ecto.NoResultsError) - - """ - def get_apikey!(id) do - __MODULE__ - # |> Repo.get!(id) - |> where([a], a.id == ^id and a.status != 6) - |> Repo.one() - |> Repo.preload(:app) - end - @doc """ Updates a apikey. @@ -128,22 +100,4 @@ defmodule Auth.Apikey do |> changeset(attrs) |> Repo.update() end - - @doc """ - Deletes a apikey. - - ## Examples - - iex> delete_apikey(apikey) - {:ok, %Apikey{}} - - iex> delete_apikey(apikey) - {:error, %Ecto.Changeset{}} - - """ - def delete_apikey(%Apikey{} = apikey) do - # Repo.delete(apikey) - # "soft delete" for autiting purposes: - update_apikey(apikey, %{status: 6}) - end end diff --git a/lib/auth_web/controllers/app_controller.ex b/lib/auth_web/controllers/app_controller.ex index 3d166d7b..373f6a43 100644 --- a/lib/auth_web/controllers/app_controller.ex +++ b/lib/auth_web/controllers/app_controller.ex @@ -68,26 +68,23 @@ defmodule AuthWeb.AppController do @doc """ Reset the API Key in case of suspected compromise. - """ def resetapikey(conn, %{"id" => id}) do - IO.inspect(id, label: "id:74") app = App.get_app!(id) - IO.inspect(app, label: "app:76") Enum.each(app.apikeys, fn k -> - IO.inspect(k, label: "apikey:78") if k.status == 3 do - # soft delete the apikey + # retire the apikey Auth.Apikey.update_apikey(Map.delete(k, :app), %{status: 6}) end end) - - + # Create New API Key: + Auth.Apikey.create_apikey(app) # get the app again and render it: - - render(conn, "show.html", app: app) + conn + |> put_flash(:info, "Your API Key has been successfully reset") + |> render("show.html", app: App.get_app!(id)) end end diff --git a/lib/auth_web/templates/app/show.html.eex b/lib/auth_web/templates/app/show.html.eex index 6a15ee2a..4c73ffca 100644 --- a/lib/auth_web/templates/app/show.html.eex +++ b/lib/auth_web/templates/app/show.html.eex @@ -25,6 +25,7 @@

<%= for apikey <- @app.apikeys do %> + <%= if apikey.status != 6 do %>

API KEY:

Date: Thu, 10 Sep 2020 10:00:53 +0100 Subject: [PATCH 103/166] preload :apikeys in where macro Co-authored-by: Tom Haines --- lib/auth/app.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/auth/app.ex b/lib/auth/app.ex index b3a03401..ddf40418 100644 --- a/lib/auth/app.ex +++ b/lib/auth/app.ex @@ -59,7 +59,9 @@ defmodule Auth.App do """ def get_app!(id) do App - |> where([a], a.id == ^id and a.status != 6) + |> where([a], a.id == ^id and a.status != 6, + preload: [:apikeys] + ) |> Repo.one() |> Repo.preload(:apikeys) end From 3859c9fbd4b48e56eec16f54a9c0b37073854ae0 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 10 Sep 2020 10:08:13 +0100 Subject: [PATCH 104/166] move environment variable lookup to module attribute in upsert_status/1 https://github.com/dwyl/auth/pull/85#discussion_r485536867 --- lib/auth/status.ex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/auth/status.ex b/lib/auth/status.ex index 9bd4a9c2..c0e11886 100644 --- a/lib/auth/status.ex +++ b/lib/auth/status.ex @@ -4,6 +4,7 @@ defmodule Auth.Status do alias Auth.Repo # https://stackoverflow.com/a/47501059/1148249 alias __MODULE__ + @admin_email System.get_env("ADMIN_EMAIL") schema "status" do field :text, :string @@ -31,9 +32,7 @@ defmodule Auth.Status do case Auth.Repo.get_by(__MODULE__, text: Map.get(attrs, "text")) do # create status nil -> - email = System.get_env("ADMIN_EMAIL") - person = Auth.Person.get_person_by_email(email) - create_status(attrs, person) + create_status(attrs, Auth.Person.get_person_by_email(@admin_email)) status -> status From 3c4e5a2d66a9a18c7e68b2c7eba75f1e1ee46d84 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 10 Sep 2020 10:16:48 +0100 Subject: [PATCH 105/166] check for API Keys that are "deleted" in get_client_secret/2 #107 + #106 --- lib/auth/app.ex | 4 +--- lib/auth_web/controllers/auth_controller.ex | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/auth/app.ex b/lib/auth/app.ex index 6ea7e8c9..4b599721 100644 --- a/lib/auth/app.ex +++ b/lib/auth/app.ex @@ -59,9 +59,7 @@ defmodule Auth.App do """ def get_app!(id) do App - |> where([a], a.id == ^id and a.status != 6, - preload: [:apikeys] - ) + |> where([a], a.id == ^id and a.status != 6) |> Repo.one() |> Repo.preload(:apikeys) end diff --git a/lib/auth_web/controllers/auth_controller.ex b/lib/auth_web/controllers/auth_controller.ex index 4e92621f..bc42dfc6 100644 --- a/lib/auth_web/controllers/auth_controller.ex +++ b/lib/auth_web/controllers/auth_controller.ex @@ -434,7 +434,7 @@ defmodule AuthWeb.AuthController do k.client_id == client_id else # check url matches the state for all other keys: - k.client_id == client_id and state =~ k.app.url + k.client_id == client_id and state =~ k.app.url and k.status != 6 end end) |> List.first() From 3190d72ffcedb4d41bb9ae8053fc566b4eb994f5 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 10 Sep 2020 11:03:50 +0100 Subject: [PATCH 106/166] Restrict access to apps on all CRUD routes #99 --- lib/auth/app.ex | 6 ++ lib/auth_web/controllers/app_controller.ex | 84 ++++++++++++------- .../controllers/app_controller_test.exs | 40 ++++++++- test/test_helper.exs | 16 ++++ 4 files changed, 114 insertions(+), 32 deletions(-) diff --git a/lib/auth/app.ex b/lib/auth/app.ex index 4b599721..bfb67147 100644 --- a/lib/auth/app.ex +++ b/lib/auth/app.ex @@ -43,6 +43,12 @@ defmodule Auth.App do |> Repo.all() end + def list_apps(person_id) do + App + |> where([a], a.status != 6 and a.person_id == ^person_id) + |> Repo.all() + end + @doc """ Gets a single app. diff --git a/lib/auth_web/controllers/app_controller.ex b/lib/auth_web/controllers/app_controller.ex index 373f6a43..99e81b1b 100644 --- a/lib/auth_web/controllers/app_controller.ex +++ b/lib/auth_web/controllers/app_controller.ex @@ -3,7 +3,11 @@ defmodule AuthWeb.AppController do alias Auth.App def index(conn, _params) do - apps = App.list_apps() + apps = if conn.assigns.person.id == 1 do + App.list_apps() + else + App.list_apps(conn.assigns.person.id) + end render(conn, "index.html", apps: apps) end @@ -33,37 +37,54 @@ defmodule AuthWeb.AppController do def show(conn, %{"id" => id}) do app = App.get_app!(id) #  restrict viewership to owner||admin https://github.com/dwyl/auth/issues/99 - render(conn, "show.html", app: app) + if conn.assigns.person.id != app.person_id || conn.assigns.person.id !== 1 do + AuthWeb.AuthController.not_found(conn, "can't touch this.") + else + render(conn, "show.html", app: app) + end end def edit(conn, %{"id" => id}) do # IO.inspect(id, label: "edit id:36") app = App.get_app!(id) - changeset = App.change_app(app) - render(conn, "edit.html", app: app, changeset: changeset) + #  restrict viewership to owner||admin https://github.com/dwyl/auth/issues/99 + if conn.assigns.person.id != app.person_id || conn.assigns.person.id !== 1 do + AuthWeb.AuthController.not_found(conn, "can't touch this.") + else + changeset = App.change_app(app) + render(conn, "edit.html", app: app, changeset: changeset) + end end def update(conn, %{"id" => id, "app" => app_params}) do app = App.get_app!(id) + #  restrict viewership to owner||admin https://github.com/dwyl/auth/issues/99 + if conn.assigns.person.id != app.person_id || conn.assigns.person.id !== 1 do + AuthWeb.AuthController.not_found(conn, "can't touch this.") + else + case App.update_app(app, app_params) do + {:ok, app} -> + conn + |> put_flash(:info, "App updated successfully.") + |> redirect(to: Routes.app_path(conn, :show, app)) - case App.update_app(app, app_params) do - {:ok, app} -> - conn - |> put_flash(:info, "App updated successfully.") - |> redirect(to: Routes.app_path(conn, :show, app)) - - {:error, %Ecto.Changeset{} = changeset} -> - render(conn, "edit.html", app: app, changeset: changeset) + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, "edit.html", app: app, changeset: changeset) + end end end def delete(conn, %{"id" => id}) do app = App.get_app!(id) - {:ok, _app} = App.delete_app(app) + if conn.assigns.person.id != app.person_id || conn.assigns.person.id !== 1 do + AuthWeb.AuthController.not_found(conn, "can't touch this.") + else + {:ok, _app} = App.delete_app(app) - conn - |> put_flash(:info, "App deleted successfully.") - |> redirect(to: Routes.app_path(conn, :index)) + conn + |> put_flash(:info, "App deleted successfully.") + |> redirect(to: Routes.app_path(conn, :index)) + end end @doc """ @@ -71,20 +92,25 @@ defmodule AuthWeb.AppController do """ def resetapikey(conn, %{"id" => id}) do app = App.get_app!(id) + if conn.assigns.person.id != app.person_id || conn.assigns.person.id !== 1 do + AuthWeb.AuthController.not_found(conn, "can't touch this.") + else + Enum.each(app.apikeys, fn k -> + if k.status == 3 do + # retire the apikey + Auth.Apikey.update_apikey(Map.delete(k, :app), %{status: 6}) + end + end) - Enum.each(app.apikeys, fn k -> - if k.status == 3 do - # retire the apikey - Auth.Apikey.update_apikey(Map.delete(k, :app), %{status: 6}) - end - end) - - # Create New API Key: - Auth.Apikey.create_apikey(app) + # Create New API Key: + Auth.Apikey.create_apikey(app) - # get the app again and render it: - conn - |> put_flash(:info, "Your API Key has been successfully reset") - |> render("show.html", app: App.get_app!(id)) + # get the app again and render it: + conn + |> put_flash(:info, "Your API Key has been successfully reset") + |> render("show.html", app: App.get_app!(id)) + end end + + end diff --git a/test/auth_web/controllers/app_controller_test.exs b/test/auth_web/controllers/app_controller_test.exs index ca7e659c..0a0e856b 100644 --- a/test/auth_web/controllers/app_controller_test.exs +++ b/test/auth_web/controllers/app_controller_test.exs @@ -8,14 +8,14 @@ defmodule AuthWeb.AppControllerTest do end: ~N[2010-04-17 14:00:00], name: "some name", url: "some url", - status: 3 + status: 3, + person_id: 1 } @update_attrs %{ desc: "some updated description", end: ~N[2011-05-18 15:01:01], name: "some updated name", - url: "some updated url", - status: 3 + url: "some updated url" } @invalid_attrs %{description: nil, end: nil, name: nil, url: nil, person_id: nil} @@ -59,6 +59,16 @@ defmodule AuthWeb.AppControllerTest do end end + describe "show app" do + setup [:create_app] + + test "attempt to VIEW app you don't own > 404", %{conn: conn, app: app} do + conn = non_admin_login(conn) + conn = get(conn, Routes.app_path(conn, :show, app)) + assert html_response(conn, 404) =~ "can't touch this." + end + end + describe "edit app" do setup [:create_app] @@ -67,6 +77,12 @@ defmodule AuthWeb.AppControllerTest do conn = get(conn, Routes.app_path(conn, :edit, app)) assert html_response(conn, 200) =~ "Edit App" end + + test "attempt to EDIT app you don't own > 404", %{conn: conn, app: app} do + conn = non_admin_login(conn) + conn = get(conn, Routes.app_path(conn, :edit, app)) + assert html_response(conn, 404) =~ "can't touch this." + end end describe "update app" do @@ -86,6 +102,12 @@ defmodule AuthWeb.AppControllerTest do conn = put(conn, Routes.app_path(conn, :update, app), app: @invalid_attrs) assert html_response(conn, 200) =~ "Edit App" end + + test "attempt UPDATE app you don't own > 404", %{conn: conn, app: app} do + conn = non_admin_login(conn) + conn = get(conn, Routes.app_path(conn, :update, app)) + assert html_response(conn, 404) =~ "can't touch this." + end end describe "delete app" do @@ -100,6 +122,12 @@ defmodule AuthWeb.AppControllerTest do get(conn, Routes.app_path(conn, :show, app)) end end + + test "attempt DELETE app you don't own > 404", %{conn: conn, app: app} do + conn = non_admin_login(conn) + conn = get(conn, Routes.app_path(conn, :delete, app)) + assert html_response(conn, 404) =~ "can't touch this." + end end defp create_app(_) do @@ -115,5 +143,11 @@ defmodule AuthWeb.AppControllerTest do conn = get(conn, Routes.app_path(conn, :resetapikey, app)) assert html_response(conn, 200) =~ "successfully reset" end + + test "attempt reset apikey you don't own > 404", %{conn: conn, app: app} do + conn = non_admin_login(conn) + conn = get(conn, Routes.app_path(conn, :resetapikey, app)) + assert html_response(conn, 404) =~ "can't touch this." + end end end diff --git a/test/test_helper.exs b/test/test_helper.exs index 368e8cf5..1f9a98e9 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -21,4 +21,20 @@ defmodule AuthTest do # IO.inspect(person, label: "person") AuthPlug.create_jwt_session(conn, data) end + + def non_admin_login(conn) do + person = Auth.Person.upsert_person(%{ + email: "alex@gmail.com", + auth_provider: "email", + password: "thiswillbehashed" + }) + + data = %{ + id: person.id, + email: person.email, + auth_provider: person.auth_provider + } + + AuthPlug.create_jwt_session(conn, data) + end end From f45a850a3d8b112ee32fad9d07fa4b1c3406143c Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 10 Sep 2020 12:00:45 +0100 Subject: [PATCH 107/166] add test for "deleted" API Key #106 --- .../controllers/app_controller_test.exs | 10 ++++++++++ .../controllers/auth_controller_test.exs | 17 +++++++++++++++-- test/test_helper.exs | 8 ++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/test/auth_web/controllers/app_controller_test.exs b/test/auth_web/controllers/app_controller_test.exs index 0a0e856b..8a2f4152 100644 --- a/test/auth_web/controllers/app_controller_test.exs +++ b/test/auth_web/controllers/app_controller_test.exs @@ -25,11 +25,21 @@ defmodule AuthWeb.AppControllerTest do end describe "index" do + setup [:create_app] + test "lists all apps", %{conn: conn} do conn = admin_login(conn) conn = get(conn, Routes.app_path(conn, :index)) assert html_response(conn, 200) =~ "Apps" end + + test "non-admin cannot see admin apps", %{conn: conn, app: app} do + conn = non_admin_login(conn) + conn = get(conn, Routes.app_path(conn, :index)) + assert html_response(conn, 200) =~ "Apps" + # the non-admin cannot see the app created in setup: + assert not String.contains?(conn.resp_body, app.name) + end end describe "new app" do diff --git a/test/auth_web/controllers/auth_controller_test.exs b/test/auth_web/controllers/auth_controller_test.exs index e63c43d5..3c0ab1c4 100644 --- a/test/auth_web/controllers/auth_controller_test.exs +++ b/test/auth_web/controllers/auth_controller_test.exs @@ -61,13 +61,26 @@ defmodule AuthWeb.AuthControllerTest do test "get_client_secret(client_id, state) gets the secret for the given client_id" do {:ok, app} = Auth.App.create_app(@app_data) key = List.first(app.apikeys) - state = "https://www.example.com/profile?auth_client_id=#{key.client_id}" secret = AuthWeb.AuthController.get_client_secret(key.client_id, state) - assert secret == key.client_secret end + test "get_client_secret(client_id, state) for 'deleted' apikey (non-admin)" do + person = non_admin_person() + {:ok, app} = Auth.App.create_app(Map.merge(@app_data, %{"person_id" => person.id})) + key = List.first(app.apikeys) + Auth.Apikey.update_apikey(Map.delete(key, :app), %{status: 6}) + state = "https://www.example.com/profile?auth_client_id=#{key.client_id}" + # Note: not sure what to assert here ... ¯\_(ツ)_/¯ + # The API Key is "deleted" so it won't be found in the lookup + try do + AuthWeb.AuthController.get_client_secret(key.client_id, state) + rescue + e in BadMapError -> assert e == %BadMapError{term: nil} + end + end + test "github_handler/2 github auth callback", %{conn: conn} do baseurl = AuthPlug.Helpers.get_baseurl_from_conn(conn) diff --git a/test/test_helper.exs b/test/test_helper.exs index 1f9a98e9..7a2ee5c7 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -22,12 +22,16 @@ defmodule AuthTest do AuthPlug.create_jwt_session(conn, data) end - def non_admin_login(conn) do - person = Auth.Person.upsert_person(%{ + def non_admin_person() do + Auth.Person.upsert_person(%{ email: "alex@gmail.com", auth_provider: "email", password: "thiswillbehashed" }) + end + + def non_admin_login(conn) do + person = non_admin_person() data = %{ id: person.id, From 8ec766965f9d6c9470fb77658fcb18c6b1bb97e6 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 10 Sep 2020 12:06:48 +0100 Subject: [PATCH 108/166] update test methods to put/delete instead of get for #99; --- test/auth_web/controllers/app_controller_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/auth_web/controllers/app_controller_test.exs b/test/auth_web/controllers/app_controller_test.exs index 8a2f4152..74462b10 100644 --- a/test/auth_web/controllers/app_controller_test.exs +++ b/test/auth_web/controllers/app_controller_test.exs @@ -115,7 +115,7 @@ defmodule AuthWeb.AppControllerTest do test "attempt UPDATE app you don't own > 404", %{conn: conn, app: app} do conn = non_admin_login(conn) - conn = get(conn, Routes.app_path(conn, :update, app)) + conn = put(conn, Routes.app_path(conn, :update, app), app: @update_attrs) assert html_response(conn, 404) =~ "can't touch this." end end @@ -135,7 +135,7 @@ defmodule AuthWeb.AppControllerTest do test "attempt DELETE app you don't own > 404", %{conn: conn, app: app} do conn = non_admin_login(conn) - conn = get(conn, Routes.app_path(conn, :delete, app)) + conn = delete(conn, Routes.app_path(conn, :delete, app)) assert html_response(conn, 404) =~ "can't touch this." end end From 5a21c74592e545f249f517dba013a3fee3181e78 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 10 Sep 2020 12:11:30 +0100 Subject: [PATCH 109/166] address code issues noted by @sourcelevel-bot in https://github.com/dwyl/auth/pull/85#pullrequestreview-485821477 for #104 --- lib/auth/apikey.ex | 4 ++-- lib/auth_web/controllers/app_controller.ex | 26 +++++++++++++--------- priv/repo/seeds.exs | 2 ++ test/auth/apikey_test.exs | 4 +--- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/auth/apikey.ex b/lib/auth/apikey.ex index 75d180b7..60cd77c0 100644 --- a/lib/auth/apikey.ex +++ b/lib/auth/apikey.ex @@ -19,14 +19,13 @@ defmodule Auth.Apikey do timestamps() end - @doc """ `encrypt_encode/1` does exactly what it's name suggests, AES Encrypts a string of plaintext and then Base58 encodes it. We encode it using Base58 so it's human-friendly (readable). """ def encrypt_encode(plaintext) do - Fields.AES.encrypt(plaintext) |> Base58.encode() + plaintext |> Fields.AES.encrypt() |> Base58.encode() end @doc """ @@ -69,6 +68,7 @@ defmodule Auth.Apikey do "status" => 3, "app" => app } + %Apikey{} |> Apikey.changeset(attrs) |> Repo.insert() diff --git a/lib/auth_web/controllers/app_controller.ex b/lib/auth_web/controllers/app_controller.ex index 99e81b1b..2239a8f8 100644 --- a/lib/auth_web/controllers/app_controller.ex +++ b/lib/auth_web/controllers/app_controller.ex @@ -3,11 +3,13 @@ defmodule AuthWeb.AppController do alias Auth.App def index(conn, _params) do - apps = if conn.assigns.person.id == 1 do - App.list_apps() - else - App.list_apps(conn.assigns.person.id) - end + apps = + if conn.assigns.person.id == 1 do + App.list_apps() + else + App.list_apps(conn.assigns.person.id) + end + render(conn, "index.html", apps: apps) end @@ -18,10 +20,12 @@ defmodule AuthWeb.AppController do def create(conn, %{"app" => app_params}) do # IO.inspect(app_params, label: "app_params:16") - attrs = Map.merge(app_params, %{ - "person_id" => conn.assigns.person.id, - "status" => 3 - }) + attrs = + Map.merge(app_params, %{ + "person_id" => conn.assigns.person.id, + "status" => 3 + }) + case App.create_app(attrs) do {:ok, app} -> # IO.inspect(app, label: "app:23") @@ -76,6 +80,7 @@ defmodule AuthWeb.AppController do def delete(conn, %{"id" => id}) do app = App.get_app!(id) + if conn.assigns.person.id != app.person_id || conn.assigns.person.id !== 1 do AuthWeb.AuthController.not_found(conn, "can't touch this.") else @@ -92,6 +97,7 @@ defmodule AuthWeb.AppController do """ def resetapikey(conn, %{"id" => id}) do app = App.get_app!(id) + if conn.assigns.person.id != app.person_id || conn.assigns.person.id !== 1 do AuthWeb.AuthController.not_found(conn, "can't touch this.") else @@ -111,6 +117,4 @@ defmodule AuthWeb.AppController do |> render("show.html", app: App.get_app!(id)) end end - - end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index ee2bd031..2946e7a4 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -112,6 +112,7 @@ defmodule SeedData do def create_default_roles do json = get_json("/priv/repo/default_roles.json") + Enum.each(json, fn role -> Role.create_role(role) end) @@ -119,6 +120,7 @@ defmodule SeedData do def insert_statuses do json = get_json("/priv/repo/statuses.json") + Enum.each(json, fn s -> Status.upsert_status(s) end) diff --git a/test/auth/apikey_test.exs b/test/auth/apikey_test.exs index c4361a80..bcab3c53 100644 --- a/test/auth/apikey_test.exs +++ b/test/auth/apikey_test.exs @@ -53,9 +53,7 @@ defmodule Auth.ApikeyTest do property "Check a batch of int values can be decoded decode_decrypt/1" do check all(int <- integer()) do - assert Auth.Apikey.decode_decrypt( - Auth.Apikey.encrypt_encode(int) - ) == int + assert Auth.Apikey.decode_decrypt(Auth.Apikey.encrypt_encode(int)) == int end end end From cb45d3805e12bf962a088fc22402832d7aa07614 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Thu, 10 Sep 2020 12:38:29 +0100 Subject: [PATCH 110/166] update faulty logic for restricted apps resources #99 --- lib/auth_web/controllers/app_controller.ex | 26 ++++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/auth_web/controllers/app_controller.ex b/lib/auth_web/controllers/app_controller.ex index 2239a8f8..ec09ed22 100644 --- a/lib/auth_web/controllers/app_controller.ex +++ b/lib/auth_web/controllers/app_controller.ex @@ -40,11 +40,13 @@ defmodule AuthWeb.AppController do def show(conn, %{"id" => id}) do app = App.get_app!(id) + # IO.inspect(app, label: "app:43") + # IO.inspect(conn.assigns.person, label: "conn.assigns.person:44") #  restrict viewership to owner||admin https://github.com/dwyl/auth/issues/99 - if conn.assigns.person.id != app.person_id || conn.assigns.person.id !== 1 do - AuthWeb.AuthController.not_found(conn, "can't touch this.") - else + if conn.assigns.person.id == app.person_id || conn.assigns.person.id == 1 do render(conn, "show.html", app: app) + else + AuthWeb.AuthController.not_found(conn, "can't touch this.") end end @@ -52,20 +54,18 @@ defmodule AuthWeb.AppController do # IO.inspect(id, label: "edit id:36") app = App.get_app!(id) #  restrict viewership to owner||admin https://github.com/dwyl/auth/issues/99 - if conn.assigns.person.id != app.person_id || conn.assigns.person.id !== 1 do - AuthWeb.AuthController.not_found(conn, "can't touch this.") - else + if conn.assigns.person.id == app.person_id || conn.assigns.person.id == 1 do changeset = App.change_app(app) render(conn, "edit.html", app: app, changeset: changeset) + else + AuthWeb.AuthController.not_found(conn, "can't touch this.") end end def update(conn, %{"id" => id, "app" => app_params}) do app = App.get_app!(id) #  restrict viewership to owner||admin https://github.com/dwyl/auth/issues/99 - if conn.assigns.person.id != app.person_id || conn.assigns.person.id !== 1 do - AuthWeb.AuthController.not_found(conn, "can't touch this.") - else + if conn.assigns.person.id == app.person_id || conn.assigns.person.id == 1 do case App.update_app(app, app_params) do {:ok, app} -> conn @@ -75,20 +75,22 @@ defmodule AuthWeb.AppController do {:error, %Ecto.Changeset{} = changeset} -> render(conn, "edit.html", app: app, changeset: changeset) end + else + AuthWeb.AuthController.not_found(conn, "can't touch this.") end end def delete(conn, %{"id" => id}) do app = App.get_app!(id) - if conn.assigns.person.id != app.person_id || conn.assigns.person.id !== 1 do - AuthWeb.AuthController.not_found(conn, "can't touch this.") - else + if conn.assigns.person.id == app.person_id || conn.assigns.person.id == 1 do {:ok, _app} = App.delete_app(app) conn |> put_flash(:info, "App deleted successfully.") |> redirect(to: Routes.app_path(conn, :index)) + else + AuthWeb.AuthController.not_found(conn, "can't touch this.") end end From bebb9398fe55454622720b0190066f01a7464b11 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 11 Sep 2020 13:14:52 +0100 Subject: [PATCH 111/166] add :app_id field to :roles schema. tests still pass #108 --- lib/auth/role.ex | 1 + lib/auth_web/controllers/app_controller.ex | 7 +++---- priv/repo/migrations/20200722175850_create_roles.exs | 2 ++ priv/repo/seeds.exs | 11 +++++------ 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/auth/role.ex b/lib/auth/role.ex index 951f1369..71d2c04b 100644 --- a/lib/auth/role.ex +++ b/lib/auth/role.ex @@ -13,6 +13,7 @@ defmodule Auth.Role do field :desc, :string field :name, :string field :person_id, :id + field :app_id, :id # many_to_many :roles, Auth.Role, join_through: Auth.PeopleRoles timestamps() diff --git a/lib/auth_web/controllers/app_controller.ex b/lib/auth_web/controllers/app_controller.ex index ec09ed22..5a066474 100644 --- a/lib/auth_web/controllers/app_controller.ex +++ b/lib/auth_web/controllers/app_controller.ex @@ -5,6 +5,7 @@ defmodule AuthWeb.AppController do def index(conn, _params) do apps = if conn.assigns.person.id == 1 do + # IO.inspect(conn.assigns.person, label: "conn.assigns.person") App.list_apps() else App.list_apps(conn.assigns.person.id) @@ -40,8 +41,6 @@ defmodule AuthWeb.AppController do def show(conn, %{"id" => id}) do app = App.get_app!(id) - # IO.inspect(app, label: "app:43") - # IO.inspect(conn.assigns.person, label: "conn.assigns.person:44") #  restrict viewership to owner||admin https://github.com/dwyl/auth/issues/99 if conn.assigns.person.id == app.person_id || conn.assigns.person.id == 1 do render(conn, "show.html", app: app) @@ -53,7 +52,7 @@ defmodule AuthWeb.AppController do def edit(conn, %{"id" => id}) do # IO.inspect(id, label: "edit id:36") app = App.get_app!(id) - #  restrict viewership to owner||admin https://github.com/dwyl/auth/issues/99 + #  restrict editing to owner||admin https://github.com/dwyl/auth/issues/99 if conn.assigns.person.id == app.person_id || conn.assigns.person.id == 1 do changeset = App.change_app(app) render(conn, "edit.html", app: app, changeset: changeset) @@ -64,7 +63,7 @@ defmodule AuthWeb.AppController do def update(conn, %{"id" => id, "app" => app_params}) do app = App.get_app!(id) - #  restrict viewership to owner||admin https://github.com/dwyl/auth/issues/99 + #  restrict updating to owner||admin https://github.com/dwyl/auth/issues/99 if conn.assigns.person.id == app.person_id || conn.assigns.person.id == 1 do case App.update_app(app, app_params) do {:ok, app} -> diff --git a/priv/repo/migrations/20200722175850_create_roles.exs b/priv/repo/migrations/20200722175850_create_roles.exs index c393b64d..1586d0be 100644 --- a/priv/repo/migrations/20200722175850_create_roles.exs +++ b/priv/repo/migrations/20200722175850_create_roles.exs @@ -6,10 +6,12 @@ defmodule Auth.Repo.Migrations.CreateRoles do add :name, :string add :desc, :string add :person_id, references(:people, on_delete: :nothing) + add :app_id, references(:apps, on_delete: :nothing) timestamps() end create index(:roles, [:person_id]) + create index(:roles, [:app_id]) end end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 2946e7a4..7fd72a56 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -111,17 +111,13 @@ defmodule SeedData do end def create_default_roles do - json = get_json("/priv/repo/default_roles.json") - - Enum.each(json, fn role -> + Enum.each(get_json("/priv/repo/default_roles.json"), fn role -> Role.create_role(role) end) end def insert_statuses do - json = get_json("/priv/repo/statuses.json") - - Enum.each(json, fn s -> + Enum.each(get_json("/priv/repo/statuses.json"), fn s -> Status.upsert_status(s) end) end @@ -132,6 +128,9 @@ SeedData.insert_statuses() Auth.Seeds.create_admin() |> Auth.Seeds.create_apikey_for_admin() +# Update status of Admin to "Verified" +Auth.Person.verify_person_by_id(1) + SeedData.create_default_roles() # grant superadmin role to app owner: Auth.PeopleRoles.insert(1, 1, 1) From 930ad6eb21f97ce90c80ae1d9d664a4b0165b0e8 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 11 Sep 2020 15:48:09 +0100 Subject: [PATCH 112/166] add app