From d0feb13b2dc3ef48bf95b98fd02afcd07968cda3 Mon Sep 17 00:00:00 2001 From: Felipe Ripoll Date: Tue, 4 Sep 2018 06:06:53 -0600 Subject: [PATCH] [#65] adding list users endpoint --- lib/poa_backend/auth.ex | 8 +++++ lib/poa_backend/auth/models/user.ex | 8 +++++ lib/poa_backend/auth/rest.ex | 43 ++++++++++++++++++++++++ lib/poa_backend/auth/router.ex | 21 ++++++++++++ mix.lock | 18 +++++----- test/auth/api_test.exs | 52 +++++++++++++++++++++++++++-- test/auth/auth_test.exs | 13 ++++++++ 7 files changed, 152 insertions(+), 11 deletions(-) diff --git a/lib/poa_backend/auth.ex b/lib/poa_backend/auth.ex index e8b2be8..4b19a99 100644 --- a/lib/poa_backend/auth.ex +++ b/lib/poa_backend/auth.ex @@ -91,6 +91,14 @@ defmodule POABackend.Auth do end end + @doc """ + This function returns all the users stored in the database + """ + @spec list_users() :: [User.t] + def list_users do + Repo.all(User) + end + @doc """ Authenticates an Admin """ diff --git a/lib/poa_backend/auth/models/user.ex b/lib/poa_backend/auth/models/user.ex index c7c19b4..6a295b7 100644 --- a/lib/poa_backend/auth/models/user.ex +++ b/lib/poa_backend/auth/models/user.ex @@ -22,6 +22,14 @@ defmodule POABackend.Auth.Models.User do password: String.t, active: :boolean} + def to_map(%User{user: username, active: active, inserted_at: inserted_at}) do + %{ + user: username, + active: active, + created_at: inserted_at + } + end + def changeset(%User{} = user, params \\ %{}) do user |> cast(params, ~w(user password active)) diff --git a/lib/poa_backend/auth/rest.ex b/lib/poa_backend/auth/rest.ex index 2e55b6b..6b80936 100644 --- a/lib/poa_backend/auth/rest.ex +++ b/lib/poa_backend/auth/rest.ex @@ -99,6 +99,49 @@ defmodule POABackend.Auth.REST do {"user-name":"vhuevkMy","password":"XkBCEJmuuEzPvy8"} ``` + ## List User Endpoint + + This Endpoint is needed in order to list the users in the system. Only Admin people can do that. + + `POST /user` + + HTTP header | Values + -- | -- + content-type | application/json or application/msgpack + authorization | Basic encodeBase64(adminname + “:” + password) + + Response + + CODE | Description + -- | -- + 200 | Success: A list of users + 401 | Authentication failed + 415 | Unsupported Media Type (only application/json and application/msgpack allowed) + + Example: + + ``` + curl -i -X GET -H "Authorization: Basic YWRtaW4xOnBhc3N3b3JkMTIzNDU2Nzg=" -H "Content-Type: application/json" https://localhost:4003/user --insecure + + HTTP/1.1 200 OK + server: Cowboy + date: Mon, 03 Sep 2018 16:02:29 GMT + content-length: 153 + cache-control: max-age=0, private, must-revalidate + + [{"user":"HeeV-EmU","created_at":"2018-09-03T16:02:25.210308","active":true},{"user":"W75AcY8Z","created_at":"2018-09-03T16:02:13.763003","active":true}] + ``` + + The JSON format for a user is: + + ``` + { + "user":"Username", + "active":true, # active means it is not banned + "created_at":"2018-09-03T16:02:25.210308" + } + ``` + ## Blacklist Token Endpoint This Endpoint is used when we want to ban a single JWT Token (not the entire user) and that will convert that Token invalid. This Endpoint is only called by Admins. diff --git a/lib/poa_backend/auth/router.ex b/lib/poa_backend/auth/router.ex index fb4e2d3..80b98f3 100644 --- a/lib/poa_backend/auth/router.ex +++ b/lib/poa_backend/auth/router.ex @@ -4,6 +4,7 @@ defmodule POABackend.Auth.Router do alias POABackend.Auth alias POABackend.CustomHandler.REST + alias POABackend.Auth.Models.User import Plug.Conn @token_default_ttl {1, :hour} @@ -68,6 +69,26 @@ defmodule POABackend.Auth.Router do end end + get "/user" do + with {"authorization", "Basic " <> base64} <- List.keyfind(conn.req_headers, "authorization", 0), + {:ok, decoded64} <- Base.decode64(base64), + [admin_name, admin_password] <- String.split(decoded64, ":"), + {:ok, :valid} <- Auth.authenticate_admin(admin_name, admin_password) + do + result = + Auth.list_users() + |> Enum.map(&User.to_map(&1)) + |> Poison.encode! + + send_resp(conn, 200, result) + else + _error -> + conn + |> send_resp(401, "") + |> halt + end + end + post "/blacklist/user" do with {"authorization", "Basic " <> base64} <- List.keyfind(conn.req_headers, "authorization", 0), {:ok, decoded64} <- Base.decode64(base64), diff --git a/mix.lock b/mix.lock index 37b2fbc..7cf7475 100644 --- a/mix.lock +++ b/mix.lock @@ -12,34 +12,34 @@ "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [], [], "hexpm"}, "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [], [], "hexpm"}, - "distillery": {:hex, :distillery, "1.5.3", "b2f4fc34ec71ab4f1202a796f9290e068883b042319aa8c9aa45377ecac8597a", [], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [], [], "hexpm"}, + "distillery": {:hex, :distillery, "1.5.3", "b2f4fc34ec71ab4f1202a796f9290e068883b042319aa8c9aa45377ecac8597a", [:mix], [], "hexpm"}, + "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"}, "ecto": {:hex, :ecto, "2.1.6", "29b45f393c2ecd99f83e418ea9b0a2af6078ecb30f401481abac8a473c490f84", [], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, "ecto_mnesia": {:hex, :ecto_mnesia, "0.9.1", "5887e3bcae972d9a26494a87cbd318d401a65b4af324480073d11df654d1b235", [], [{:confex, "~> 3.3", [hex: :confex, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 2.1.6", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm"}, "elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [], [], "hexpm"}, "ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"}, "ex_aws_dynamo": {:hex, :ex_aws_dynamo, "2.0.1", "fd472900779f3bb9cd2b59bde14b130404f751c1990e1fff8b6898150dc457da", [], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.18.4", "4406b8891cecf1352f49975c6d554e62e4341ceb41b9338949077b0d4a97b949", [], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, - "excoveralls": {:hex, :excoveralls, "0.9.1", "14fd20fac51ab98d8e79615814cc9811888d2d7b28e85aa90ff2e30dcf3191d6", [], [{:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "excoveralls": {:hex, :excoveralls, "0.9.1", "14fd20fac51ab98d8e79615814cc9811888d2d7b28e85aa90ff2e30dcf3191d6", [:mix], [{:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, "gen_stage": {:hex, :gen_stage, "0.14.0", "65ae78509f85b59d360690ce3378d5096c3130a0694bab95b0c4ae66f3008fad", [], [], "hexpm"}, - "guardian": {:hex, :guardian, "1.1.0", "36c1ea356a1bac02bc120c3f91f4f0259c5aa0ee92cee0efe8def5d7401f1921", [], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}, {:uuid, ">= 1.1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"}, + "guardian": {:hex, :guardian, "1.1.0", "36c1ea356a1bac02bc120c3f91f4f0259c5aa0ee92cee0efe8def5d7401f1921", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}, {:uuid, ">= 1.1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"}, "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "jason": {:hex, :jason, "1.1.1", "d3ccb840dfb06f2f90a6d335b536dd074db748b3e7f5b11ab61d239506585eb2", [], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, - "meck": {:hex, :meck, "0.8.11", "2c39e15ec87d847da6cf69b4a1c4af3fd850ae2a272e719e0e8751a7fe54771f", [], [], "hexpm"}, + "meck": {:hex, :meck, "0.8.11", "2c39e15ec87d847da6cf69b4a1c4af3fd850ae2a272e719e0e8751a7fe54771f", [:rebar3], [], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [], [], "hexpm"}, "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [], [], "hexpm"}, - "mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, + "mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "msgpax": {:hex, :msgpax, "2.1.1", "833bc9ce6d3e073cf966fec94d3f976ca7100685b4f0efb6e30ef512f2e8a4d7", [], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [], [], "hexpm"}, - "plug": {:hex, :plug, "1.6.1", "c62fe7623d035020cf989820b38490460e6903ab7eee29e234b7586e9b6c91d6", [], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, + "plug": {:hex, :plug, "1.6.1", "c62fe7623d035020cf989820b38490460e6903ab7eee29e234b7586e9b6c91d6", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [], [], "hexpm"}, "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, - "ranch": {:hex, :ranch, "1.5.0", "f04166f456790fee2ac1aa05a02745cc75783c2bfb26d39faf6aefc9a3d3a58a", [], [], "hexpm"}, + "ranch": {:hex, :ranch, "1.5.0", "f04166f456790fee2ac1aa05a02745cc75783c2bfb26d39faf6aefc9a3d3a58a", [:rebar3], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [], [], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [], [], "hexpm"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [], [], "hexpm"}, diff --git a/test/auth/api_test.exs b/test/auth/api_test.exs index 625741d..c772b0e 100644 --- a/test/auth/api_test.exs +++ b/test/auth/api_test.exs @@ -317,6 +317,39 @@ defmodule Auth.APITest do assert {409, :nobody} == result end + test "listing all the users stored" do + url = @base_url <> "/user" + mime_type = "application/msgpack" + headers = [ + {"Content-Type", mime_type}, + {"authorization", "Basic " <> Base.encode64(@admin <> ":" <> @admin_pwd)} + ] + + {200, [initial_user] = users} = get(url, headers) + + assert length(users) == 1 # ferigis user is created at the begining of each test + assert initial_user["user"] == "ferigis" + + :ok = create_user("user2", "password2") + + {200, users} = get(url, headers) + + assert length(users) == 2 + end + + test "listing all the users stored with wrong Admin Credentials" do + url = @base_url <> "/user" + mime_type = "application/msgpack" + headers = [ + {"Content-Type", mime_type}, + {"authorization", "Basic " <> Base.encode64(@admin <> ":" <> "wrongpassword")} + ] + + result = get(url, headers) + + assert {401, :nobody} == result + end + # ---------------------------------------- # /blacklist/user Endpoint Tests # ---------------------------------------- @@ -625,8 +658,8 @@ defmodule Auth.APITest do # Internal functions # ---------------------------------------- - defp create_user do - {:ok, _user} = Auth.create_user(@user, @password) + defp create_user(user \\ @user, password \\ @password) do + {:ok, _user} = Auth.create_user(user, password) :ok end @@ -645,4 +678,19 @@ defmodule Auth.APITest do {response.status_code, body} end + defp get(url, headers) do + options = [ssl: [{:versions, [:'tlsv1.2']}], recv_timeout: 500] + {:ok, response} = HTTPoison.get(url, headers, options) + + body = case response.body do + "" -> + :nobody + _ -> + {:ok, body} = Poison.decode(response.body) + body + end + + {response.status_code, body} + end + end diff --git a/test/auth/auth_test.exs b/test/auth/auth_test.exs index a3a6c49..1734ac1 100644 --- a/test/auth/auth_test.exs +++ b/test/auth/auth_test.exs @@ -95,6 +95,19 @@ defmodule Auth.AuthTest do refute user_name == user_name2 end + test "get all users" do + assert [] == Auth.list_users() + + {:ok, user1} = Auth.create_user("user_name1", "password1") + {:ok, user2} = Auth.create_user("user_name2", "password2") + + users = Auth.list_users() + + assert length(users) == 2 + assert Enum.member?(users, user1) + assert Enum.member?(users, user2) + end + # ---------------------------------------- # Token Tests # ----------------------------------------