From 50be374b1a94231ac78eafdab432cb43c2783a83 Mon Sep 17 00:00:00 2001 From: Luiz Carlos Date: Sat, 10 Oct 2020 14:25:41 -0300 Subject: [PATCH 1/5] feat: add temporary block subject identities manager and commands --- README.md | 4 +- .../commands/get_temporarilly_blocked.ex | 32 +++ .../sign_in/schemas/application_attempt.ex | 8 + .../lib/sign_in/schemas/user_attempt.ex | 8 + .../credentials/blocklist_password_manager.ex | 2 +- .../lib/credentials/ports/verify_hash.ex | 2 +- .../lib/credentials/schemas/password.ex | 2 +- .../lib/credentials/schemas/public_key.ex | 2 +- .../client_applications.ex | 4 +- .../commands/create_identity.ex | 8 +- .../commands/get_identity.ex | 8 +- .../inputs/create_client_application.ex | 6 +- .../commands/inputs/create_user.ex | 6 +- .../commands/inputs/get_client_applicate.ex | 4 +- .../commands/inputs/get_user.ex | 4 +- .../lib/identities/manager.ex | 209 ++++++++++++++++++ .../ports/get_temporarilly_blocked.ex | 18 ++ .../schemas/client_application.ex | 18 +- .../{identity => identities}/schemas/user.ex | 21 +- .../lib/{identity => identities}/users.ex | 4 +- .../lib/permissions/commands/consent_scope.ex | 2 +- .../lib/permissions/commands/remove_scope.ex | 2 +- .../schema/client_application_scope.ex | 2 +- .../lib/permissions/schema/scope.ex | 2 +- .../lib/permissions/schema/user_scope.ex | 2 +- apps/resource_manager/lib/resource_manager.ex | 2 +- .../priv/passwords/common_passwords.txt | 2 +- ...143714_alter_user_create_blocked_until.exs | 13 ++ ...alter_application_create_blocked_until.exs | 11 + apps/resource_manager/priv/repo/seeds.exs | 2 +- .../identity/client_applications_test.exs | 6 +- .../commands/create_identity_test.exs | 6 +- .../identity/commands/get_identity_test.exs | 8 +- .../resource_manager/identity/users_test.exs | 6 +- apps/resource_manager/test/support/factory.ex | 2 +- apps/resource_manager/test/support/mocks.ex | 5 +- apps/rest_api/README.md | 6 +- .../test/controllers/fallback_test.exs | 2 +- config/config.exs | 6 +- config/test.exs | 3 + 40 files changed, 399 insertions(+), 61 deletions(-) create mode 100644 apps/authenticator/lib/sign_in/commands/get_temporarilly_blocked.ex rename apps/resource_manager/lib/{identity => identities}/client_applications.ex (79%) rename apps/resource_manager/lib/{identity => identities}/commands/create_identity.ex (92%) rename apps/resource_manager/lib/{identity => identities}/commands/get_identity.ex (88%) rename apps/resource_manager/lib/{identity => identities}/commands/inputs/create_client_application.ex (91%) rename apps/resource_manager/lib/{identity => identities}/commands/inputs/create_user.ex (89%) rename apps/resource_manager/lib/{identity => identities}/commands/inputs/get_client_applicate.ex (87%) rename apps/resource_manager/lib/{identity => identities}/commands/inputs/get_user.ex (84%) create mode 100644 apps/resource_manager/lib/identities/manager.ex create mode 100644 apps/resource_manager/lib/identities/ports/get_temporarilly_blocked.ex rename apps/resource_manager/lib/{identity => identities}/schemas/client_application.ex (85%) rename apps/resource_manager/lib/{identity => identities}/schemas/user.ex (72%) rename apps/resource_manager/lib/{identity => identities}/users.ex (82%) create mode 100644 apps/resource_manager/priv/repo/migrations/20201010143714_alter_user_create_blocked_until.exs create mode 100644 apps/resource_manager/priv/repo/migrations/20201010143726_alter_application_create_blocked_until.exs diff --git a/README.md b/README.md index a3fa9e2..15960f0 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ To get the user and application data check out the database on `localhost:8181` ```elixir # Getting all user identities # The user password will be `admin` -ResourceManager.Repo.all(ResourceManager.Identity.Schemas.User) |> ResourceManager.Repo.preload([:scopes]) +ResourceManager.Repo.all(ResourceManager.Identities.Schemas.User) |> ResourceManager.Repo.preload([:scopes]) # Getting all client application identities # Check out for the client secret -ResourceManager.Repo.all(ResourceManager.Identity.Schemas.ClientApplication) |> ResourceManager.Repo.preload([:scopes]) +ResourceManager.Repo.all(ResourceManager.Identities.Schemas.ClientApplication) |> ResourceManager.Repo.preload([:scopes]) ``` ### Making requests diff --git a/apps/authenticator/lib/sign_in/commands/get_temporarilly_blocked.ex b/apps/authenticator/lib/sign_in/commands/get_temporarilly_blocked.ex new file mode 100644 index 0000000..f958d0e --- /dev/null +++ b/apps/authenticator/lib/sign_in/commands/get_temporarilly_blocked.ex @@ -0,0 +1,32 @@ +defmodule Authenticator.SignIn.Commands.GetTemporarillyBlocked do + @moduledoc """ + Get subject authentication attempts that failed consecutively and return + it's username or client_id. + """ + + require Logger + + alias Authenticator.SignIn.{ApplicationAttempts, UserAttempts} + + # Maximum failed attempts before block + @max_attempts 5 + + # 10 minutes interval + @max_interval 60 * 10 * -1 + + @doc """ + Return the identities that failed more than #{@max_attempts} times on sign in + in #{@max_interval} seconds. + """ + @spec execute(identity_type :: :user | :application) :: {:ok, list(String.t())} + def execute(:user), do: {:ok, UserAttempts.list(get_filters())} + def execute(:application), do: {:ok, ApplicationAttempts.list(get_filters())} + + # Query filters + defp get_filters, do: [temporarilly_blocked: {@max_attempts, created_after()}] + + defp created_after do + NaiveDateTime.utc_now() + |> NaiveDateTime.add(@max_interval, :second) + end +end diff --git a/apps/authenticator/lib/sign_in/schemas/application_attempt.ex b/apps/authenticator/lib/sign_in/schemas/application_attempt.ex index cf15786..6f86ee5 100644 --- a/apps/authenticator/lib/sign_in/schemas/application_attempt.ex +++ b/apps/authenticator/lib/sign_in/schemas/application_attempt.ex @@ -47,4 +47,12 @@ defmodule Authenticator.SignIn.Schemas.ApplicationAttempt do defp custom_query(query, {:ids, ids}), do: where(query, [c], c.id in ^ids) defp custom_query(query, {:created_after, date}), do: where(query, [c], c.inserted_at > ^date) defp custom_query(query, {:created_before, date}), do: where(query, [c], c.inserted_at < ^date) + + defp custom_query(query, {:temporarilly_blocked, {max_attempts, date}}) do + query + |> where([c], c.inserted_at > ^date) + |> group_by([c], c.client_id) + |> having([c], count(c.client_id) > ^max_attempts) + |> select([c], c.client_id) + end end diff --git a/apps/authenticator/lib/sign_in/schemas/user_attempt.ex b/apps/authenticator/lib/sign_in/schemas/user_attempt.ex index 2721269..22c36b3 100644 --- a/apps/authenticator/lib/sign_in/schemas/user_attempt.ex +++ b/apps/authenticator/lib/sign_in/schemas/user_attempt.ex @@ -47,4 +47,12 @@ defmodule Authenticator.SignIn.Schemas.UserAttempt do defp custom_query(query, {:ids, ids}), do: where(query, [c], c.id in ^ids) defp custom_query(query, {:created_after, date}), do: where(query, [c], c.inserted_at > ^date) defp custom_query(query, {:created_before, date}), do: where(query, [c], c.inserted_at < ^date) + + defp custom_query(query, {:temporarilly_blocked, {max_attempts, date}}) do + query + |> where([c], c.inserted_at > ^date) + |> group_by([c], c.username) + |> having([c], count(c.username) > ^max_attempts) + |> select([c], c.username) + end end diff --git a/apps/resource_manager/lib/credentials/blocklist_password_manager.ex b/apps/resource_manager/lib/credentials/blocklist_password_manager.ex index 7852ba9..a819d2f 100644 --- a/apps/resource_manager/lib/credentials/blocklist_password_manager.ex +++ b/apps/resource_manager/lib/credentials/blocklist_password_manager.ex @@ -37,7 +37,7 @@ defmodule ResourceManager.Credentials.BlocklistPasswordManager do # coveralls-ignore-stop - @doc "Update session statuses and save on cache" + @doc "Update blocked password list on cache" @spec execute() :: {:ok, :managed} | {:error, :update_failed | :failed_to_cache} def execute, do: manage_passwords() diff --git a/apps/resource_manager/lib/credentials/ports/verify_hash.ex b/apps/resource_manager/lib/credentials/ports/verify_hash.ex index 783a12a..e37a4b3 100644 --- a/apps/resource_manager/lib/credentials/ports/verify_hash.ex +++ b/apps/resource_manager/lib/credentials/ports/verify_hash.ex @@ -3,7 +3,7 @@ defmodule ResourceManager.Credentials.Ports.VerifyHash do Port to access Authenticator verify hash command. """ - alias ResourceManager.Identity.Schemas.{ClientApplication, User} + alias ResourceManager.Identities.Schemas.{ClientApplication, User} @typedoc "All possible hash algorithms" @type algorithms :: :argon2 | :bcrypt | :pbkdf2 diff --git a/apps/resource_manager/lib/credentials/schemas/password.ex b/apps/resource_manager/lib/credentials/schemas/password.ex index 34309dd..6464e8d 100644 --- a/apps/resource_manager/lib/credentials/schemas/password.ex +++ b/apps/resource_manager/lib/credentials/schemas/password.ex @@ -12,7 +12,7 @@ defmodule ResourceManager.Credentials.Schemas.Password do import Ecto.Changeset - alias ResourceManager.Identity.Schemas.User + alias ResourceManager.Identities.Schemas.User @typedoc """ Abstract password module type. diff --git a/apps/resource_manager/lib/credentials/schemas/public_key.ex b/apps/resource_manager/lib/credentials/schemas/public_key.ex index f004ce3..9c8b4f4 100644 --- a/apps/resource_manager/lib/credentials/schemas/public_key.ex +++ b/apps/resource_manager/lib/credentials/schemas/public_key.ex @@ -7,7 +7,7 @@ defmodule ResourceManager.Credentials.Schemas.PublicKey do import Ecto.Changeset - alias ResourceManager.Identity.Schemas.ClientApplication + alias ResourceManager.Identities.Schemas.ClientApplication @typedoc """ Abstract public_key module type. diff --git a/apps/resource_manager/lib/identity/client_applications.ex b/apps/resource_manager/lib/identities/client_applications.ex similarity index 79% rename from apps/resource_manager/lib/identity/client_applications.ex rename to apps/resource_manager/lib/identities/client_applications.ex index c63f390..9e05a38 100644 --- a/apps/resource_manager/lib/identity/client_applications.ex +++ b/apps/resource_manager/lib/identities/client_applications.ex @@ -1,4 +1,4 @@ -defmodule ResourceManager.Identity.ClientApplications do +defmodule ResourceManager.Identities.ClientApplications do @moduledoc """ Client application are subject identities that are not impersonated by a person. @@ -8,5 +8,5 @@ defmodule ResourceManager.Identity.ClientApplications do What a client application can do is defined by it's scopes. """ - use ResourceManager.Domain, schema_model: ResourceManager.Identity.Schemas.ClientApplication + use ResourceManager.Domain, schema_model: ResourceManager.Identities.Schemas.ClientApplication end diff --git a/apps/resource_manager/lib/identity/commands/create_identity.ex b/apps/resource_manager/lib/identities/commands/create_identity.ex similarity index 92% rename from apps/resource_manager/lib/identity/commands/create_identity.ex rename to apps/resource_manager/lib/identities/commands/create_identity.ex index 7ed95de..bc850e9 100644 --- a/apps/resource_manager/lib/identity/commands/create_identity.ex +++ b/apps/resource_manager/lib/identities/commands/create_identity.ex @@ -1,4 +1,4 @@ -defmodule ResourceManager.Identity.Commands.CreateIdentity do +defmodule ResourceManager.Identities.Commands.CreateIdentity do @moduledoc """ Command for creating a new identity. """ @@ -8,9 +8,9 @@ defmodule ResourceManager.Identity.Commands.CreateIdentity do alias Ecto.Multi alias ResourceManager.Permissions.Commands.ConsentScope alias ResourceManager.Credentials.{Passwords, PublicKeys} - alias ResourceManager.Identity.Commands.Inputs.{CreateClientApplication, CreateUser} - alias ResourceManager.Identity.Schemas.{ClientApplication, User} - alias ResourceManager.Identity.{ClientApplications, Users} + alias ResourceManager.Identities.Commands.Inputs.{CreateClientApplication, CreateUser} + alias ResourceManager.Identities.Schemas.{ClientApplication, User} + alias ResourceManager.Identities.{ClientApplications, Users} alias ResourceManager.Repo @typedoc "All possible identities" diff --git a/apps/resource_manager/lib/identity/commands/get_identity.ex b/apps/resource_manager/lib/identities/commands/get_identity.ex similarity index 88% rename from apps/resource_manager/lib/identity/commands/get_identity.ex rename to apps/resource_manager/lib/identities/commands/get_identity.ex index b9e7142..eeae28f 100644 --- a/apps/resource_manager/lib/identity/commands/get_identity.ex +++ b/apps/resource_manager/lib/identities/commands/get_identity.ex @@ -1,13 +1,13 @@ -defmodule ResourceManager.Identity.Commands.GetIdentity do +defmodule ResourceManager.Identities.Commands.GetIdentity do @moduledoc """ Find out an identity that matches the given parameters """ require Logger - alias ResourceManager.Identity.{ClientApplications, Users} - alias ResourceManager.Identity.Commands.Inputs.{GetClientApplication, GetUser} - alias ResourceManager.Identity.Schemas.{ClientApplication, User} + alias ResourceManager.Identities.{ClientApplications, Users} + alias ResourceManager.Identities.Commands.Inputs.{GetClientApplication, GetUser} + alias ResourceManager.Identities.Schemas.{ClientApplication, User} alias ResourceManager.Repo @typedoc "All possible identities" diff --git a/apps/resource_manager/lib/identity/commands/inputs/create_client_application.ex b/apps/resource_manager/lib/identities/commands/inputs/create_client_application.ex similarity index 91% rename from apps/resource_manager/lib/identity/commands/inputs/create_client_application.ex rename to apps/resource_manager/lib/identities/commands/inputs/create_client_application.ex index c6fc4b5..4ab75b0 100644 --- a/apps/resource_manager/lib/identity/commands/inputs/create_client_application.ex +++ b/apps/resource_manager/lib/identities/commands/inputs/create_client_application.ex @@ -1,11 +1,11 @@ -defmodule ResourceManager.Identity.Commands.Inputs.CreateClientApplication do +defmodule ResourceManager.Identities.Commands.Inputs.CreateClientApplication do @moduledoc """ Input parameters for creating client applications """ use ResourceManager.Input - alias ResourceManager.Identity.Schemas.ClientApplication + alias ResourceManager.Identities.Schemas.ClientApplication @typedoc "Create client application input fields" @type t :: %__MODULE__{ @@ -23,7 +23,7 @@ defmodule ResourceManager.Identity.Commands.Inputs.CreateClientApplication do @required [:name, :public_key, :status, :protocol, :access_type] @optional [:description, :scopes] embedded_schema do - # Identity + # Identities field :name, :string field :description, :string field :status, :string, default: "active" diff --git a/apps/resource_manager/lib/identity/commands/inputs/create_user.ex b/apps/resource_manager/lib/identities/commands/inputs/create_user.ex similarity index 89% rename from apps/resource_manager/lib/identity/commands/inputs/create_user.ex rename to apps/resource_manager/lib/identities/commands/inputs/create_user.ex index 8c11247..4b2568d 100644 --- a/apps/resource_manager/lib/identity/commands/inputs/create_user.ex +++ b/apps/resource_manager/lib/identities/commands/inputs/create_user.ex @@ -1,11 +1,11 @@ -defmodule ResourceManager.Identity.Commands.Inputs.CreateUser do +defmodule ResourceManager.Identities.Commands.Inputs.CreateUser do @moduledoc """ Input parameters for creating user identity """ use ResourceManager.Input - alias ResourceManager.Identity.Schemas.User + alias ResourceManager.Identities.Schemas.User @typedoc "Create user input fields" @type t :: %__MODULE__{ @@ -19,7 +19,7 @@ defmodule ResourceManager.Identity.Commands.Inputs.CreateUser do @required [:username, :password_hash, :password_algorithm] @optional [:scopes] embedded_schema do - # Identity + # Identities field :username, :string field :status, :string, default: "active" diff --git a/apps/resource_manager/lib/identity/commands/inputs/get_client_applicate.ex b/apps/resource_manager/lib/identities/commands/inputs/get_client_applicate.ex similarity index 87% rename from apps/resource_manager/lib/identity/commands/inputs/get_client_applicate.ex rename to apps/resource_manager/lib/identities/commands/inputs/get_client_applicate.ex index b474fac..69b8374 100644 --- a/apps/resource_manager/lib/identity/commands/inputs/get_client_applicate.ex +++ b/apps/resource_manager/lib/identities/commands/inputs/get_client_applicate.ex @@ -1,11 +1,11 @@ -defmodule ResourceManager.Identity.Commands.Inputs.GetClientApplication do +defmodule ResourceManager.Identities.Commands.Inputs.GetClientApplication do @moduledoc """ Input parameters for getting user identity """ use ResourceManager.Input - alias ResourceManager.Identity.Schemas.ClientApplication + alias ResourceManager.Identities.Schemas.ClientApplication @typedoc "Get user input fields" @type t :: %__MODULE__{ diff --git a/apps/resource_manager/lib/identity/commands/inputs/get_user.ex b/apps/resource_manager/lib/identities/commands/inputs/get_user.ex similarity index 84% rename from apps/resource_manager/lib/identity/commands/inputs/get_user.ex rename to apps/resource_manager/lib/identities/commands/inputs/get_user.ex index 22645d9..0099bfe 100644 --- a/apps/resource_manager/lib/identity/commands/inputs/get_user.ex +++ b/apps/resource_manager/lib/identities/commands/inputs/get_user.ex @@ -1,11 +1,11 @@ -defmodule ResourceManager.Identity.Commands.Inputs.GetUser do +defmodule ResourceManager.Identities.Commands.Inputs.GetUser do @moduledoc """ Input parameters for getting user identity """ use ResourceManager.Input - alias ResourceManager.Identity.Schemas.User + alias ResourceManager.Identities.Schemas.User @typedoc "Get user input fields" @type t :: %__MODULE__{ diff --git a/apps/resource_manager/lib/identities/manager.ex b/apps/resource_manager/lib/identities/manager.ex new file mode 100644 index 0000000..f8cbc37 --- /dev/null +++ b/apps/resource_manager/lib/identities/manager.ex @@ -0,0 +1,209 @@ +defmodule ResouceManager.Identities.Manager do + @moduledoc """ + Genserver for dealing with identity status changes. + + This will check for temporarilly blocked identities and update it's status + when necessary. + """ + + use GenServer + + require Logger + + alias Ecto.Multi + alias ResourceManager.Identities.Ports.GetTemporarillyBlocked + alias ResourceManager.Identities.Schemas.{ClientApplication, User} + alias ResourceManager.Repo + + @typedoc "Identities manager supervisor state" + @type state :: %{ + started_at: NaiveDateTime.t(), + updated_at: NaiveDateTime.t() | nil, + scheduled_to: NaiveDateTime.t() | nil + } + + # One minute interval + @schedule_interval 60 + + # 15 minutes in seconds + @block_time 60 * 15 + + ######### + # CLIENT + ######### + + # coveralls-ignore-start + + @doc "Starts the `GenServer" + @spec start_link(args :: keyword()) :: {:ok, pid()} | :ignore | {:error, keyword()} + def start_link(args \\ []), do: GenServer.start_link(__MODULE__, args, name: __MODULE__) + + @doc "Checks #{__MODULE__} actual state" + @spec check(process_id :: pid() | __MODULE__) :: state() + def check(pid \\ __MODULE__), do: GenServer.call(pid, :check) + + # coveralls-ignore-stop + + @doc "Update identity statuses and save on cache" + @spec execute() :: {:ok, :managed} | {:error, :update_failed | :failed_to_cache} + def execute, do: manage_identities() + + # coveralls-ignore-start + + @impl true + def init(_args) do + Logger.info("Identity manager started") + + state = %{ + started_at: NaiveDateTime.utc_now(), + updated_at: nil, + scheduled_to: nil + } + + {:ok, state, {:continue, :manage}} + end + + @impl true + def handle_continue(:manage, state) do + # Scheduling next management + state = schedule_work(state) + + {:noreply, state} + end + + @impl true + def handle_call(:check, _from, state), do: {:reply, state, state} + + @impl true + def handle_info(:manage, state), do: {:noreply, state, {:continue, :manage}} + + # coveralls-ignore-stop + + ########## + # Helpers + ########## + + defp manage_identities do + Multi.new() + |> Multi.run(:get_user_identities, fn _repo, _changes -> + GetTemporarillyBlocked.execute(:user) + end) + |> Multi.run(:block_user_identities, fn _repo, %{get_user_identities: usernames} -> + block_user_identities(usernames) + end) + |> Multi.run(:unblock_user_identities, fn _repo, _changes -> + unblock_user_identities() + end) + |> Multi.run(:get_application_identities, fn _repo, _changes -> + GetTemporarillyBlocked.execute(:application) + end) + |> Multi.run(:block_application_identities, fn _, %{get_application_identities: client_ids} -> + block_application_identities(client_ids) + end) + |> Multi.run(:unblock_application_identities, fn _repo, _changes -> + unblock_application_identities() + end) + |> Repo.transaction() + |> case do + {:ok, _any} -> + Logger.info("Succeeds in managing identities") + {:ok, :managed} + + {:error, step, err, _changes} -> + Logger.error("Failed to manage identities in step #{inspect(step)}", reason: err) + {:error, err} + end + end + + defp block_user_identities(usernames) when is_list(usernames) do + [usernames: usernames, status: "active"] + |> User.query() + |> Repo.update_all(set: [status: "temporary_blocked", blocked_until: blocked_until()]) + |> case do + {count, _} when is_integer(count) -> + Logger.debug("Identities manager blocked #{inspect(count)} user identities") + {:ok, count} + + err -> + Logger.error("Identities manager failed to block user identities", error: inspect(err)) + {:error, :update_failed} + end + end + + defp unblock_user_identities do + [status: "temporarilly_blocked", blocked_before: NaiveDateTime.utc_now()] + |> User.query() + |> Repo.update_all(set: [status: "active", blocked_until: nil]) + |> case do + {count, _} when is_integer(count) -> + Logger.debug("Identities manager unblocked #{inspect(count)} user identities") + {:ok, count} + + err -> + Logger.error("Identities manager failed to unblocked user identities", error: inspect(err)) + + {:error, :update_failed} + end + end + + defp block_application_identities(client_ids) when is_list(client_ids) do + [client_ids: client_ids, status: "active"] + |> ClientApplication.query() + |> Repo.update_all(set: [status: "temporary_blocked", blocked_until: blocked_until()]) + |> case do + {count, _} when is_integer(count) -> + Logger.debug("Identities manager blocked #{inspect(count)} app identities") + {:ok, count} + + err -> + Logger.error("Identities manager failed to block app identities", error: inspect(err)) + {:error, :update_failed} + end + end + + defp unblock_application_identities do + [status: "temporarilly_blocked", blocked_before: NaiveDateTime.utc_now()] + |> ClientApplication.query() + |> Repo.update_all(set: [status: "active", blocked_until: nil]) + |> case do + {count, _} when is_integer(count) -> + Logger.debug("Identities manager unblocked #{inspect(count)} app identities") + {:ok, count} + + error -> + Logger.error("Identities manager failed to unblocked app identities", + error: inspect(error) + ) + + {:error, :update_failed} + end + end + + defp blocked_until do + NaiveDateTime.utc_now() + |> NaiveDateTime.add(@block_time, :second) + end + + # coveralls-ignore-start + + defp schedule_work(state) do + interval = schedule_interval() + date_to_schedule = schedule_to(interval) + + Process.send_after(__MODULE__, :manage, :timer.seconds(interval)) + + # Updating state + %{state | scheduled_to: date_to_schedule} + end + + defp schedule_to(interval) do + NaiveDateTime.utc_now() + |> NaiveDateTime.add(interval, :second) + |> NaiveDateTime.truncate(:second) + end + + defp schedule_interval, do: Keyword.get(config(), :schedule_interval, @schedule_interval) + defp config, do: Application.get_env(:authenticator, __MODULE__, []) + + # coveralls-ignore-stop +end diff --git a/apps/resource_manager/lib/identities/ports/get_temporarilly_blocked.ex b/apps/resource_manager/lib/identities/ports/get_temporarilly_blocked.ex new file mode 100644 index 0000000..66d3d0a --- /dev/null +++ b/apps/resource_manager/lib/identities/ports/get_temporarilly_blocked.ex @@ -0,0 +1,18 @@ +defmodule ResourceManager.Identities.Ports.GetTemporarillyBlocked do + @moduledoc """ + Port to access Authenticator get temporarilly blocked command. + """ + + @doc "Delegates to #{__MODULE__}.execute/1 command" + @callback execute(subject_type :: :user | :application) :: false + + @doc "Gets the temporarilly blocked subjects" + @spec execute(subject_type :: :user | :application) :: false + def execute(subject_type), do: implementation().execute(subject_type) + + defp implementation do + :resource_manager + |> Application.get_env(__MODULE__) + |> Keyword.get(:command) + end +end diff --git a/apps/resource_manager/lib/identity/schemas/client_application.ex b/apps/resource_manager/lib/identities/schemas/client_application.ex similarity index 85% rename from apps/resource_manager/lib/identity/schemas/client_application.ex rename to apps/resource_manager/lib/identities/schemas/client_application.ex index 8ba6558..03b026a 100644 --- a/apps/resource_manager/lib/identity/schemas/client_application.ex +++ b/apps/resource_manager/lib/identities/schemas/client_application.ex @@ -1,4 +1,4 @@ -defmodule ResourceManager.Identity.Schemas.ClientApplication do +defmodule ResourceManager.Identities.Schemas.ClientApplication do @moduledoc """ The application is a resource and a subject that makes requests through the systems. @@ -36,12 +36,13 @@ defmodule ResourceManager.Identity.Schemas.ClientApplication do @possible_grant_flows ~w(resource_owner implicit client_credentials refresh_token authorization_code) @required_fields [:name, :status, :protocol, :access_type] - @optional_fields [:grant_flows, :description, :redirect_uri] + @optional_fields [:grant_flows, :description, :redirect_uri, :blocked_until] schema "client_applications" do field :client_id, Ecto.UUID, autogenerate: true field :name, :string field :description, :string field :status, :string, default: "active" + field :blocked_until, :naive_datetime field :protocol, :string, default: "openid-connect" field :access_type, :string, default: "confidential" field :is_admin, :boolean, default: false @@ -99,4 +100,17 @@ defmodule ResourceManager.Identity.Schemas.ClientApplication do @doc false def possible_grant_flows, do: @possible_grant_flows + + ################# + # Custom filters + ################# + + defp custom_query(query, {:client_ids, client_ids}), + do: where(query, [c], c.client_id in ^client_ids) + + defp custom_query(query, {:blocked_after, date}), + do: where(query, [c], c.blocked_until > ^date) + + defp custom_query(query, {:blocked_before, date}), + do: where(query, [c], c.blocked_until < ^date) end diff --git a/apps/resource_manager/lib/identity/schemas/user.ex b/apps/resource_manager/lib/identities/schemas/user.ex similarity index 72% rename from apps/resource_manager/lib/identity/schemas/user.ex rename to apps/resource_manager/lib/identities/schemas/user.ex index a39f5b6..acc7385 100644 --- a/apps/resource_manager/lib/identity/schemas/user.ex +++ b/apps/resource_manager/lib/identities/schemas/user.ex @@ -1,4 +1,4 @@ -defmodule ResourceManager.Identity.Schemas.User do +defmodule ResourceManager.Identities.Schemas.User do @moduledoc """ The user is a resource and a subject that makes requests through the systems. @@ -28,10 +28,12 @@ defmodule ResourceManager.Identity.Schemas.User do @possible_statuses ~w(active inactive blocked temporary_blocked) @required_fields [:username, :status] + @optional_fields [:blocked_until] schema "users" do field :username, :string field :status, :string, default: "active" field :is_admin, :boolean, default: false + field :blocked_until, :naive_datetime has_one :password, Password many_to_many :scopes, Scope, join_through: "users_scopes" @@ -42,7 +44,7 @@ defmodule ResourceManager.Identity.Schemas.User do @doc false def changeset_create(params) when is_map(params) do %__MODULE__{} - |> cast(params, @required_fields) + |> cast(params, @required_fields ++ @optional_fields) |> validate_required(@required_fields) |> validate_length(:username, min: 5, max: 150) |> validate_inclusion(:status, @possible_statuses) @@ -52,7 +54,7 @@ defmodule ResourceManager.Identity.Schemas.User do @doc false def changeset_update(%__MODULE__{} = model, params) when is_map(params) do model - |> cast(params, @required_fields) + |> cast(params, @required_fields ++ @optional_fields) |> validate_length(:username, min: 5, max: 150) |> validate_inclusion(:status, @possible_statuses) |> unique_constraint(:username) @@ -60,4 +62,17 @@ defmodule ResourceManager.Identity.Schemas.User do @doc false def possible_statuses, do: @possible_statuses + + ################# + # Custom filters + ################# + + defp custom_query(query, {:usernames, usernames}), + do: where(query, [c], c.username in ^usernames) + + defp custom_query(query, {:blocked_after, date}), + do: where(query, [c], c.blocked_until > ^date) + + defp custom_query(query, {:blocked_before, date}), + do: where(query, [c], c.blocked_until < ^date) end diff --git a/apps/resource_manager/lib/identity/users.ex b/apps/resource_manager/lib/identities/users.ex similarity index 82% rename from apps/resource_manager/lib/identity/users.ex rename to apps/resource_manager/lib/identities/users.ex index 7ffd924..236bd0e 100644 --- a/apps/resource_manager/lib/identity/users.ex +++ b/apps/resource_manager/lib/identities/users.ex @@ -1,4 +1,4 @@ -defmodule ResourceManager.Identity.Users do +defmodule ResourceManager.Identities.Users do @moduledoc """ Users are subject identities that are impersonates by a person. @@ -8,5 +8,5 @@ defmodule ResourceManager.Identity.Users do What a user can do is defined by it's scopes. """ - use ResourceManager.Domain, schema_model: ResourceManager.Identity.Schemas.User + use ResourceManager.Domain, schema_model: ResourceManager.Identities.Schemas.User end diff --git a/apps/resource_manager/lib/permissions/commands/consent_scope.ex b/apps/resource_manager/lib/permissions/commands/consent_scope.ex index 1f8ae2d..b6922a4 100644 --- a/apps/resource_manager/lib/permissions/commands/consent_scope.ex +++ b/apps/resource_manager/lib/permissions/commands/consent_scope.ex @@ -6,7 +6,7 @@ defmodule ResourceManager.Permissions.Commands.ConsentScope do require Logger alias Ecto.Multi - alias ResourceManager.Identity.Schemas.{ClientApplication, User} + alias ResourceManager.Identities.Schemas.{ClientApplication, User} alias ResourceManager.Permissions.Schemas.{ClientApplicationScope, UserScope} alias ResourceManager.Repo diff --git a/apps/resource_manager/lib/permissions/commands/remove_scope.ex b/apps/resource_manager/lib/permissions/commands/remove_scope.ex index cbb194b..244ebaa 100644 --- a/apps/resource_manager/lib/permissions/commands/remove_scope.ex +++ b/apps/resource_manager/lib/permissions/commands/remove_scope.ex @@ -6,7 +6,7 @@ defmodule ResourceManager.Permissions.Commands.RemoveScope do require Logger alias Ecto.Multi - alias ResourceManager.Identity.Schemas.{ClientApplication, User} + alias ResourceManager.Identities.Schemas.{ClientApplication, User} alias ResourceManager.Permissions.Schemas.{ClientApplicationScope, UserScope} alias ResourceManager.Repo diff --git a/apps/resource_manager/lib/permissions/schema/client_application_scope.ex b/apps/resource_manager/lib/permissions/schema/client_application_scope.ex index 2c01226..0e94662 100644 --- a/apps/resource_manager/lib/permissions/schema/client_application_scope.ex +++ b/apps/resource_manager/lib/permissions/schema/client_application_scope.ex @@ -7,7 +7,7 @@ defmodule ResourceManager.Permissions.Schemas.ClientApplicationScope do import Ecto.Changeset - alias ResourceManager.Identity.Schemas.ClientApplication + alias ResourceManager.Identities.Schemas.ClientApplication alias ResourceManager.Permissions.Schemas.Scope @typedoc "Client application scope schema fields" diff --git a/apps/resource_manager/lib/permissions/schema/scope.ex b/apps/resource_manager/lib/permissions/schema/scope.ex index f04be3f..6a1e47b 100644 --- a/apps/resource_manager/lib/permissions/schema/scope.ex +++ b/apps/resource_manager/lib/permissions/schema/scope.ex @@ -7,7 +7,7 @@ defmodule ResourceManager.Permissions.Schemas.Scope do import Ecto.Changeset - alias ResourceManager.Identity.Schemas.User + alias ResourceManager.Identities.Schemas.User @typedoc "Abstract scope module type." @type t :: %__MODULE__{ diff --git a/apps/resource_manager/lib/permissions/schema/user_scope.ex b/apps/resource_manager/lib/permissions/schema/user_scope.ex index ac0c912..8bf2b0a 100644 --- a/apps/resource_manager/lib/permissions/schema/user_scope.ex +++ b/apps/resource_manager/lib/permissions/schema/user_scope.ex @@ -7,7 +7,7 @@ defmodule ResourceManager.Permissions.Schemas.UserScope do import Ecto.Changeset - alias ResourceManager.Identity.Schemas.User + alias ResourceManager.Identities.Schemas.User alias ResourceManager.Permissions.Schemas.Scope @typedoc "User scope schema fields" diff --git a/apps/resource_manager/lib/resource_manager.ex b/apps/resource_manager/lib/resource_manager.ex index 991fc01..2fbd43f 100644 --- a/apps/resource_manager/lib/resource_manager.ex +++ b/apps/resource_manager/lib/resource_manager.ex @@ -4,7 +4,7 @@ defmodule ResourceManager do """ alias ResourceManager.Credentials.Commands.PasswordIsAllowed - alias ResourceManager.Identity.Commands.{CreateIdentity, GetIdentity} + alias ResourceManager.Identities.Commands.{CreateIdentity, GetIdentity} alias ResourceManager.Permissions.Commands.{ConsentScope, RemoveScope} @doc "Delegates to #{CreateIdentity}.execute/1" diff --git a/apps/resource_manager/priv/passwords/common_passwords.txt b/apps/resource_manager/priv/passwords/common_passwords.txt index 23822b9..60027fb 100644 --- a/apps/resource_manager/priv/passwords/common_passwords.txt +++ b/apps/resource_manager/priv/passwords/common_passwords.txt @@ -537497,7 +537497,7 @@ idesof idesign idesbp idera1 -Identity +Identities identiti identi idenipol diff --git a/apps/resource_manager/priv/repo/migrations/20201010143714_alter_user_create_blocked_until.exs b/apps/resource_manager/priv/repo/migrations/20201010143714_alter_user_create_blocked_until.exs new file mode 100644 index 0000000..704f042 --- /dev/null +++ b/apps/resource_manager/priv/repo/migrations/20201010143714_alter_user_create_blocked_until.exs @@ -0,0 +1,13 @@ +defmodule ResourceManager.Repo.Migrations.AlterUserCreateBlockedUntil do + use Ecto.Migration + + def change do + alter table(:users) do + add :blocked_until, :naive_datetime + end + + drop_if_exists index(:users, [:username, :status]) + create_if_not_exists unique_index(:users, [:username]) + create_if_not_exists index(:users, [:username, :status, :blocked_until]) + end +end diff --git a/apps/resource_manager/priv/repo/migrations/20201010143726_alter_application_create_blocked_until.exs b/apps/resource_manager/priv/repo/migrations/20201010143726_alter_application_create_blocked_until.exs new file mode 100644 index 0000000..60371e5 --- /dev/null +++ b/apps/resource_manager/priv/repo/migrations/20201010143726_alter_application_create_blocked_until.exs @@ -0,0 +1,11 @@ +defmodule ResourceManager.Repo.Migrations.AlterApplicationCreateBlockedUntil do + use Ecto.Migration + + def change do + alter table(:client_applications) do + add :blocked_until, :naive_datetime + end + + create_if_not_exists index(:client_applications, [:client_id, :status, :blocked_until]) + end +end diff --git a/apps/resource_manager/priv/repo/seeds.exs b/apps/resource_manager/priv/repo/seeds.exs index 032984d..a80f6b3 100644 --- a/apps/resource_manager/priv/repo/seeds.exs +++ b/apps/resource_manager/priv/repo/seeds.exs @@ -3,7 +3,7 @@ # mix run priv/repo/seeds.exs alias ResourceManager.Credentials.Schemas.{Password, PublicKey} -alias ResourceManager.Identity.Schemas.{ClientApplication, User} +alias ResourceManager.Identities.Schemas.{ClientApplication, User} alias ResourceManager.Permissions.Schemas.{ClientApplicationScope, Scope, UserScope} alias ResourceManager.Repo diff --git a/apps/resource_manager/test/resource_manager/identity/client_applications_test.exs b/apps/resource_manager/test/resource_manager/identity/client_applications_test.exs index 879c4ac..ea47f4c 100644 --- a/apps/resource_manager/test/resource_manager/identity/client_applications_test.exs +++ b/apps/resource_manager/test/resource_manager/identity/client_applications_test.exs @@ -1,9 +1,9 @@ -defmodule ResourceManager.Identity.ClientApplicationsTest do +defmodule ResourceManager.Identities.ClientApplicationsTest do use ResourceManager.DataCase, async: true alias ResourceManager.Credentials.Ports.GenerateHashMock - alias ResourceManager.Identity.ClientApplications - alias ResourceManager.Identity.Schemas.ClientApplication + alias ResourceManager.Identities.ClientApplications + alias ResourceManager.Identities.Schemas.ClientApplication setup do {:ok, client_application: insert!(:client_application)} diff --git a/apps/resource_manager/test/resource_manager/identity/commands/create_identity_test.exs b/apps/resource_manager/test/resource_manager/identity/commands/create_identity_test.exs index 6a87bc1..2a1b691 100644 --- a/apps/resource_manager/test/resource_manager/identity/commands/create_identity_test.exs +++ b/apps/resource_manager/test/resource_manager/identity/commands/create_identity_test.exs @@ -1,9 +1,9 @@ -defmodule ResourceManager.Identity.Commands.CreateIdentityTest do +defmodule ResourceManager.Identities.Commands.CreateIdentityTest do use ResourceManager.DataCase, async: true alias ResourceManager.Credentials.Ports.GenerateHashMock - alias ResourceManager.Identity.Commands.CreateIdentity - alias ResourceManager.Identity.Schemas.{ClientApplication, User} + alias ResourceManager.Identities.Commands.CreateIdentity + alias ResourceManager.Identities.Schemas.{ClientApplication, User} alias ResourceManager.Repo setup do diff --git a/apps/resource_manager/test/resource_manager/identity/commands/get_identity_test.exs b/apps/resource_manager/test/resource_manager/identity/commands/get_identity_test.exs index c697be0..49ea59d 100644 --- a/apps/resource_manager/test/resource_manager/identity/commands/get_identity_test.exs +++ b/apps/resource_manager/test/resource_manager/identity/commands/get_identity_test.exs @@ -1,9 +1,9 @@ -defmodule ResourceManager.Identity.Commands.GetIdentityTest do +defmodule ResourceManager.Identities.Commands.GetIdentityTest do use ResourceManager.DataCase, async: true - alias ResourceManager.Identity.Commands.GetIdentity - alias ResourceManager.Identity.Commands.Inputs.{GetClientApplication, GetUser} - alias ResourceManager.Identity.Schemas.{ClientApplication, User} + alias ResourceManager.Identities.Commands.GetIdentity + alias ResourceManager.Identities.Commands.Inputs.{GetClientApplication, GetUser} + alias ResourceManager.Identities.Schemas.{ClientApplication, User} alias ResourceManager.Repo setup do diff --git a/apps/resource_manager/test/resource_manager/identity/users_test.exs b/apps/resource_manager/test/resource_manager/identity/users_test.exs index 709ca20..9d8a17b 100644 --- a/apps/resource_manager/test/resource_manager/identity/users_test.exs +++ b/apps/resource_manager/test/resource_manager/identity/users_test.exs @@ -1,8 +1,8 @@ -defmodule ResourceManager.Identity.UsersTest do +defmodule ResourceManager.Identities.UsersTest do use ResourceManager.DataCase, async: true - alias ResourceManager.Identity.Schemas.User - alias ResourceManager.Identity.Users + alias ResourceManager.Identities.Schemas.User + alias ResourceManager.Identities.Users setup do {:ok, user: insert!(:user)} diff --git a/apps/resource_manager/test/support/factory.ex b/apps/resource_manager/test/support/factory.ex index fd0bdfa..5e16ebf 100644 --- a/apps/resource_manager/test/support/factory.ex +++ b/apps/resource_manager/test/support/factory.ex @@ -2,7 +2,7 @@ defmodule ResourceManager.Factory do @moduledoc false alias ResourceManager.Credentials.Schemas.{Password, PublicKey} - alias ResourceManager.Identity.Schemas.{ClientApplication, User} + alias ResourceManager.Identities.Schemas.{ClientApplication, User} alias ResourceManager.Permissions.Schemas.{ClientApplicationScope, Scope, UserScope} alias ResourceManager.Repo diff --git a/apps/resource_manager/test/support/mocks.ex b/apps/resource_manager/test/support/mocks.ex index e1375ea..012142e 100644 --- a/apps/resource_manager/test/support/mocks.ex +++ b/apps/resource_manager/test/support/mocks.ex @@ -2,7 +2,10 @@ for module <- [ # Credential ports mocks ResourceManager.Credentials.Ports.GenerateHash, ResourceManager.Credentials.Ports.VerifyHash, - ResourceManager.Credentials.Ports.FakeVerifyHash + ResourceManager.Credentials.Ports.FakeVerifyHash, + + # Identity ports mocks + ResourceManager.Identities.Ports.GetTemporarillyBlocked ] do Mox.defmock(:"#{module}Mock", for: module) end diff --git a/apps/rest_api/README.md b/apps/rest_api/README.md index 9b78350..94891ea 100644 --- a/apps/rest_api/README.md +++ b/apps/rest_api/README.md @@ -16,7 +16,7 @@ To generate the client assertions follow the exemple bellow (in the project iex) ```elixir signer = Joken.Signer.create("RS256", %{"pem" => "YOUR_PRIVATE_KEY_HERE"}) -Authenticator.ClientAssertion.generate_and_sign!(%{"iss" => "YOUR_APP_CLIENT_ID", "aud" => "WatcherEx", "typ" => "Bearer"}, signer) +Authenticator.Sessions.Tokens.ClientAssertion.generate_and_sign!(%{"iss" => "YOUR_APP_CLIENT_ID", "aud" => "WatcherEx", "typ" => "Bearer"}, signer) ``` Then you can try out the request using: @@ -24,7 +24,7 @@ Then you can try out the request using: ```sh curl -X POST http://localhost:4000/api/v1/auth/protocol/openid-connect/token \ -H "Content-Type: application/json" \ - -d '{"username":"admin", "password":"admin", "grant_type":"password", "scope":"admin:read admin:write", "client_id": "2e455bb1-0604-4812-9756-36f7ab23b8d9", "client_assertion": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJXYXRjaGVyRXgiLCJleHAiOjE2MDEyMzgwOTMsImlhdCI6MTYwMTIzMDg5MywiaXNzIjoiMmU0NTViYjEtMDYwNC00ODEyLTk3NTYtMzZmN2FiMjNiOGQ5IiwianRpIjoiMm9zYmUwc3JrbTMyc2tvN2ZrMDAwMnAzIiwibmJmIjoxNjAxMjMwODkzLCJ0eXAiOiJCZWFyZXIifQ.SDUlLMO9kVLfxyRRJUCCVPpz2fcjUtxC1K3IJPa2NrBp7S-IUGuZx9965M09jFJOZrNzqEC9VRZb9KqlZS2T0bGUg3pk8R91oqOgnPOvXEQ8bjTKuvqIv7K7hKaAARxRTgBf-o87quUoVoZzepLzfmJdnDVXy0QoFIO7_SYe4zmq3mrrvHM5Kaypgf0JMiOZORr2kEnk0zEkPoIvqL8psTrLlaUHr-cn3l3F7eGARhHijOTXoFXTH4BFjJzsQJRKcz1cyzUQ64Y02JWeYsbfi1higF14lGnFTduuVwMpqa7Wu5xK9FhmR1mmlqqFgD6NVeiDxoDcAzhhDbQWdKuuAyqyr67uYfY5qeeudoKYyJcjvfE0c1iMLpEQAlZDK_HjoChBEORcTcvbsCD-75y2lJhqsrW0cTWoqq0YTXU3SHvdewEZto8AEaQMKHnGozQQEkeF7rOFOJF7P_LX2LV7JbtxIl8RZPvjNNF6F6VHy_DJTVoJJNbIRRm47v8fXBBej60_76XZmxG_FtgZBevVgINq_lnYf2nb_2RybxyzRxfC4pRvTh6Og8mZy5fcgYIa4Yq3eXdDVAVxrFJWrJqfjdPSuZbFDuq6VfiXOAd_bNqNHMLN_jiTtJlVJnS-gk9Ejot8X-kwG-UPDoAQZIfyBqMSXIqyL-qFfVR8dIX9Dps", "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"}' + -d '{"username":"admin", "password":"admin222", "grant_type":"password", "scope":"admin:read admin:write", "client_id": "2e455bb1-0604-4812-9756-36f7ab23b8d9", "client_assertion": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJXYXRjaGVyRXgiLCJleHAiOjE2MDIzNTY4NTMsImlhdCI6MTYwMjM0OTY1MywiaXNzIjoiMmU0NTViYjEtMDYwNC00ODEyLTk3NTYtMzZmN2FiMjNiOGQ5IiwianRpIjoiMm91YjExOWNrdDgxNHFybDRvMDAwNmwxIiwibmJmIjoxNjAyMzQ5NjUzLCJ0eXAiOiJCZWFyZXIifQ.Hp8XCLjAQXnS3LrCPVNMvDfd4TKZcDL8CaPhVqtMU_jZ08yJmz3YKIrPyHclI2GnXY-Ii-KfkMJrcWj93VisJlf514ft3fRkqGC-MRfCdDyIWDOjhwIS1-ckn35ej0PxWK2QbPnqv0wIHYqdVpXBdDnF0xaZk_KX_nb4obEx6K5soiewieWDO-PZtgqQZAHKrsKO83kiM3rPeGia1FLvMy2mY7SE7g2fsbK0_Ik_h-MtQ9lTn0dC7uUGYmAGse82MyMNLVzeW2FezVLvoFuQ9yA9VlKNtl-VhXQ5kEdpzSCSUhFYzUOgR_U09U2AI6AegsybR_MEFdGGSHfb7l_m6V44I1LPhi-0LOdOgbsfo_g_uO7zrGI9nMhlsJjwgRUy50ONDQjkzlYTYjYSR-EBqS_ulwG8_keNG93JPM662z7h2o15AfQmZ8dISi4nPR_CeWZZ0gaKunhstsGopI8a9JalsNySVdar84RP6tvxrAdhr7nS37D5FE5_gylB_dT7hJ15DeAfZ34h-jX_v2VqinlWf3DwWuEYfwKHb7afGIB-udcN-cMDI_WrC6WO_SU7OV2PquQ43MYB-79wHVqaqGmDV3UrhsfbBok7iGDs1yhsUGKrp2sTPEz41epfUrH4K81qEkRSdgDrQMyDbq9En9gtqFCJeWnYIuhqFjhS_v4", "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"}' ``` @@ -77,7 +77,7 @@ To generate the client assertions follow the exemple bellow (in the project iex) ```elixir signer = Joken.Signer.create("RS256", %{"pem" => "YOUR_PRIVATE_KEY_HERE"}) -Authenticator.ClientAssertion.generate_and_sign!(%{"iss" => "YOUR_APP_CLIENT_ID", "aud" => "WatcherEx", "typ" => "Bearer"}, signer) +Authenticator.Sessions.Tokens.ClientAssertion.generate_and_sign!(%{"iss" => "YOUR_APP_CLIENT_ID", "aud" => "WatcherEx", "typ" => "Bearer"}, signer) ``` Then you can try out the request using: diff --git a/apps/rest_api/test/controllers/fallback_test.exs b/apps/rest_api/test/controllers/fallback_test.exs index 0c1e30c..aca9e26 100644 --- a/apps/rest_api/test/controllers/fallback_test.exs +++ b/apps/rest_api/test/controllers/fallback_test.exs @@ -1,7 +1,7 @@ defmodule RestAPI.Controllers.FallbackTest do use RestAPI.ConnCase, async: true - alias ResourceManager.Identity.Commands.Inputs.CreateUser + alias ResourceManager.Identities.Commands.Inputs.CreateUser alias RestAPI.Controllers.Fallback test "handles bad request responses", %{conn: conn} do diff --git a/config/config.exs b/config/config.exs index cdfd3d0..b47ff80 100644 --- a/config/config.exs +++ b/config/config.exs @@ -16,7 +16,8 @@ config :resource_manager, ResourceManager.Application, children: [ ResourceManager.Repo, ResourceManager.Credentials.BlocklistPasswordCache, - ResourceManager.Credentials.BlocklistPasswordManager + ResourceManager.Credentials.BlocklistPasswordManager, + ResouceManager.Identities.Manager ] config :resource_manager, ResourceManager.Repo, @@ -36,6 +37,9 @@ config :resource_manager, ResourceManager.Credentials.Ports.VerifyHash, config :resource_manager, ResourceManager.Credentials.Ports.FakeVerifyHash, command: Authenticator.Crypto.Commands.FakeVerifyHash +config :resource_manager, ResourceManager.Identities.Ports.GetTemporarillyBlocked, + command: Authenticator.SignIn.Commands.GetTemporarillyBlocked + ################ # Authenticator ################ diff --git a/config/test.exs b/config/test.exs index 34bbde0..071565b 100644 --- a/config/test.exs +++ b/config/test.exs @@ -26,6 +26,9 @@ config :resource_manager, ResourceManager.Credentials.Ports.VerifyHash, config :resource_manager, ResourceManager.Credentials.Ports.FakeVerifyHash, command: ResourceManager.Credentials.Ports.FakeVerifyHashMock +config :resource_manager, ResourceManager.Identities.Ports.GetTemporarillyBlocked, + command: ResourceManager.Identities.Ports.GetTemporarillyBlockedMock + ################ # Authenticator ################ From 7cc6dac0d92222d6fb65f424b998959628642483 Mon Sep 17 00:00:00 2001 From: Luiz Carlos Date: Sat, 10 Oct 2020 14:30:25 -0300 Subject: [PATCH 2/5] chore: revert commom password changes --- .../lib/identities/commands/inputs/create_user.ex | 2 +- apps/resource_manager/priv/passwords/common_passwords.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/resource_manager/lib/identities/commands/inputs/create_user.ex b/apps/resource_manager/lib/identities/commands/inputs/create_user.ex index 4b2568d..c4f0051 100644 --- a/apps/resource_manager/lib/identities/commands/inputs/create_user.ex +++ b/apps/resource_manager/lib/identities/commands/inputs/create_user.ex @@ -19,7 +19,7 @@ defmodule ResourceManager.Identities.Commands.Inputs.CreateUser do @required [:username, :password_hash, :password_algorithm] @optional [:scopes] embedded_schema do - # Identities + # Identity field :username, :string field :status, :string, default: "active" diff --git a/apps/resource_manager/priv/passwords/common_passwords.txt b/apps/resource_manager/priv/passwords/common_passwords.txt index 60027fb..23822b9 100644 --- a/apps/resource_manager/priv/passwords/common_passwords.txt +++ b/apps/resource_manager/priv/passwords/common_passwords.txt @@ -537497,7 +537497,7 @@ idesof idesign idesbp idera1 -Identities +Identity identiti identi idenipol From fc9927704d8f074b68c4379097004c8aecbe3f77 Mon Sep 17 00:00:00 2001 From: Luiz Carlos Date: Sat, 10 Oct 2020 14:40:44 -0300 Subject: [PATCH 3/5] chore: revert commom password changes --- .../identities/commands/inputs/create_client_application.ex | 2 +- apps/resource_manager/lib/identities/manager.ex | 4 ++-- apps/resource_manager/priv/passwords/common_passwords.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/resource_manager/lib/identities/commands/inputs/create_client_application.ex b/apps/resource_manager/lib/identities/commands/inputs/create_client_application.ex index 4ab75b0..4bc7952 100644 --- a/apps/resource_manager/lib/identities/commands/inputs/create_client_application.ex +++ b/apps/resource_manager/lib/identities/commands/inputs/create_client_application.ex @@ -23,7 +23,7 @@ defmodule ResourceManager.Identities.Commands.Inputs.CreateClientApplication do @required [:name, :public_key, :status, :protocol, :access_type] @optional [:description, :scopes] embedded_schema do - # Identities + # Identity field :name, :string field :description, :string field :status, :string, default: "active" diff --git a/apps/resource_manager/lib/identities/manager.ex b/apps/resource_manager/lib/identities/manager.ex index f8cbc37..ddde7a1 100644 --- a/apps/resource_manager/lib/identities/manager.ex +++ b/apps/resource_manager/lib/identities/manager.ex @@ -131,7 +131,7 @@ defmodule ResouceManager.Identities.Manager do end defp unblock_user_identities do - [status: "temporarilly_blocked", blocked_before: NaiveDateTime.utc_now()] + [status: "temporary_blocked", blocked_before: NaiveDateTime.utc_now()] |> User.query() |> Repo.update_all(set: [status: "active", blocked_until: nil]) |> case do @@ -162,7 +162,7 @@ defmodule ResouceManager.Identities.Manager do end defp unblock_application_identities do - [status: "temporarilly_blocked", blocked_before: NaiveDateTime.utc_now()] + [status: "temporary_blocked", blocked_before: NaiveDateTime.utc_now()] |> ClientApplication.query() |> Repo.update_all(set: [status: "active", blocked_until: nil]) |> case do diff --git a/apps/resource_manager/priv/passwords/common_passwords.txt b/apps/resource_manager/priv/passwords/common_passwords.txt index 23822b9..4da732e 100644 --- a/apps/resource_manager/priv/passwords/common_passwords.txt +++ b/apps/resource_manager/priv/passwords/common_passwords.txt @@ -537497,7 +537497,7 @@ idesof idesign idesbp idera1 -Identity +Identity identiti identi idenipol From c931175a10175f26d145e1fbaaf7229af1aad8d7 Mon Sep 17 00:00:00 2001 From: Luiz Carlos Date: Sun, 11 Oct 2020 10:33:14 -0300 Subject: [PATCH 4/5] chore: add tests and mocks --- .../commands/get_temporarilly_blocked.ex | 6 +- .../get_temporarilly_blocked_test.exs | 28 +++++++++ .../lib/identities/manager.ex | 9 +-- .../blockist_password_manager_test.exs | 2 +- .../identity/manager_test.exs | 60 +++++++++++++++++++ config/config.exs | 2 +- 6 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 apps/authenticator/test/authenticator/sign_in/commands/get_temporarilly_blocked_test.exs create mode 100644 apps/resource_manager/test/resource_manager/identity/manager_test.exs diff --git a/apps/authenticator/lib/sign_in/commands/get_temporarilly_blocked.ex b/apps/authenticator/lib/sign_in/commands/get_temporarilly_blocked.ex index f958d0e..028fb2f 100644 --- a/apps/authenticator/lib/sign_in/commands/get_temporarilly_blocked.ex +++ b/apps/authenticator/lib/sign_in/commands/get_temporarilly_blocked.ex @@ -24,9 +24,5 @@ defmodule Authenticator.SignIn.Commands.GetTemporarillyBlocked do # Query filters defp get_filters, do: [temporarilly_blocked: {@max_attempts, created_after()}] - - defp created_after do - NaiveDateTime.utc_now() - |> NaiveDateTime.add(@max_interval, :second) - end + defp created_after, do: NaiveDateTime.add(NaiveDateTime.utc_now(), @max_interval, :second) end diff --git a/apps/authenticator/test/authenticator/sign_in/commands/get_temporarilly_blocked_test.exs b/apps/authenticator/test/authenticator/sign_in/commands/get_temporarilly_blocked_test.exs new file mode 100644 index 0000000..70b54bc --- /dev/null +++ b/apps/authenticator/test/authenticator/sign_in/commands/get_temporarilly_blocked_test.exs @@ -0,0 +1,28 @@ +defmodule Authenticator.SignIn.Commands.GetTemporarillyBlockedTest do + use Authenticator.DataCase, async: true + + alias Authenticator.SignIn.Commands.GetTemporarillyBlocked + + describe "#{GetTemporarillyBlocked}.execute/1" do + test "succeeds and return users to block temporarilly" do + assert [%{username: username} | _] = + Enum.map(1..15, fn _ -> + insert!(:user_sign_in_attempt, username: "myusername", was_successful: false) + end) + + assert {:ok, [^username | _]} = GetTemporarillyBlocked.execute(:user) + end + + test "succeeds and return applications to block temporarilly" do + assert [%{client_id: client_id} | _] = + Enum.map(1..15, fn _ -> + insert!(:application_sign_in_attempt, + client_id: "myclientid", + was_successful: false + ) + end) + + assert {:ok, [^client_id | _]} = GetTemporarillyBlocked.execute(:application) + end + end +end diff --git a/apps/resource_manager/lib/identities/manager.ex b/apps/resource_manager/lib/identities/manager.ex index ddde7a1..7b072e4 100644 --- a/apps/resource_manager/lib/identities/manager.ex +++ b/apps/resource_manager/lib/identities/manager.ex @@ -1,4 +1,4 @@ -defmodule ResouceManager.Identities.Manager do +defmodule ResourceManager.Identities.Manager do @moduledoc """ Genserver for dealing with identity status changes. @@ -170,11 +170,8 @@ defmodule ResouceManager.Identities.Manager do Logger.debug("Identities manager unblocked #{inspect(count)} app identities") {:ok, count} - error -> - Logger.error("Identities manager failed to unblocked app identities", - error: inspect(error) - ) - + err -> + Logger.error("Identities manager failed to unblocked app identities", error: inspect(err)) {:error, :update_failed} end end diff --git a/apps/resource_manager/test/resource_manager/credentials/blockist_password_manager_test.exs b/apps/resource_manager/test/resource_manager/credentials/blockist_password_manager_test.exs index 5bf39ce..10165a6 100644 --- a/apps/resource_manager/test/resource_manager/credentials/blockist_password_manager_test.exs +++ b/apps/resource_manager/test/resource_manager/credentials/blockist_password_manager_test.exs @@ -3,7 +3,7 @@ defmodule ResourceManager.Credentials.BlocklistPasswordManagerTest do alias ResourceManager.Credentials.{BlocklistPasswordCache, BlocklistPasswordManager} - describe "#{BlocklistPasswordManager}.execute/o" do + describe "#{BlocklistPasswordManager}.execute/0" do test "populates the cache with the passwords" do assert [] == BlocklistPasswordCache.all() assert {:ok, :managed} = BlocklistPasswordManager.execute() diff --git a/apps/resource_manager/test/resource_manager/identity/manager_test.exs b/apps/resource_manager/test/resource_manager/identity/manager_test.exs new file mode 100644 index 0000000..c1ab520 --- /dev/null +++ b/apps/resource_manager/test/resource_manager/identity/manager_test.exs @@ -0,0 +1,60 @@ +defmodule ResourceManager.Identities.ManagerTest do + use ResourceManager.DataCase, async: true + + alias ResourceManager.Identities.Manager + alias ResourceManager.Identities.Schemas.{ClientApplication, User} + alias ResourceManager.Identities.Ports.GetTemporarillyBlockedMock + + describe "#{Manager}.execute/0" do + setup do + {:ok, user: insert!(:user), app: insert!(:client_application)} + end + + test "succeeds and temporary blocks users", %{user: %{id: id, username: username}} do + expect(GetTemporarillyBlockedMock, :execute, fn :user -> {:ok, [username]} end) + expect(GetTemporarillyBlockedMock, :execute, fn :application -> {:ok, []} end) + + assert {:ok, :managed} == Manager.execute() + assert %{status: "temporary_blocked", blocked_until: %{}} = Repo.get(User, id) + end + + test "succeeds and temporary blocks applications", %{app: %{id: id, client_id: client_id}} do + expect(GetTemporarillyBlockedMock, :execute, fn :user -> {:ok, []} end) + expect(GetTemporarillyBlockedMock, :execute, fn :application -> {:ok, [client_id]} end) + + assert {:ok, :managed} == Manager.execute() + assert %{status: "temporary_blocked", blocked_until: %{}} = Repo.get(ClientApplication, id) + end + + test "succeeds and unblock users" do + user = insert!(:user, status: "temporary_blocked", blocked_until: blocked_until()) + + expect(GetTemporarillyBlockedMock, :execute, fn :user -> {:ok, []} end) + expect(GetTemporarillyBlockedMock, :execute, fn :application -> {:ok, []} end) + + assert {:ok, :managed} == Manager.execute() + assert %{status: "active", blocked_until: nil} = Repo.get(User, user.id) + end + + test "succeeds and unblock applications" do + app = + insert!( + :client_application, + status: "temporary_blocked", + blocked_until: blocked_until() + ) + + expect(GetTemporarillyBlockedMock, :execute, fn :user -> {:ok, []} end) + expect(GetTemporarillyBlockedMock, :execute, fn :application -> {:ok, [app.client_id]} end) + + assert {:ok, :managed} == Manager.execute() + assert %{status: "active", blocked_until: nil} = Repo.get(ClientApplication, app.id) + end + end + + defp blocked_until do + NaiveDateTime.utc_now() + |> NaiveDateTime.add(60 * -1, :second) + |> NaiveDateTime.truncate(:second) + end +end diff --git a/config/config.exs b/config/config.exs index b47ff80..ff3400f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -17,7 +17,7 @@ config :resource_manager, ResourceManager.Application, ResourceManager.Repo, ResourceManager.Credentials.BlocklistPasswordCache, ResourceManager.Credentials.BlocklistPasswordManager, - ResouceManager.Identities.Manager + ResourceManager.Identities.Manager ] config :resource_manager, ResourceManager.Repo, From 8b84f97c6efa825fcbc6c5a2b6d6bc0ef63da81a Mon Sep 17 00:00:00 2001 From: Luiz Carlos Date: Sun, 11 Oct 2020 10:37:56 -0300 Subject: [PATCH 5/5] chore: make credo happy aggain --- .../test/resource_manager/identity/manager_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/resource_manager/test/resource_manager/identity/manager_test.exs b/apps/resource_manager/test/resource_manager/identity/manager_test.exs index c1ab520..759b29e 100644 --- a/apps/resource_manager/test/resource_manager/identity/manager_test.exs +++ b/apps/resource_manager/test/resource_manager/identity/manager_test.exs @@ -2,8 +2,8 @@ defmodule ResourceManager.Identities.ManagerTest do use ResourceManager.DataCase, async: true alias ResourceManager.Identities.Manager - alias ResourceManager.Identities.Schemas.{ClientApplication, User} alias ResourceManager.Identities.Ports.GetTemporarillyBlockedMock + alias ResourceManager.Identities.Schemas.{ClientApplication, User} describe "#{Manager}.execute/0" do setup do