mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
refactor(portal): Add email as separate column on auth_identities table (#7472)
Why: * Currently, when using the API, a user has no way of easily identifying what identities they are pulling back as the response only includes the `provider_identifier` which for most of our AuthProviders is an ID for the IdP and not an email address. Along with that, when adding users to an OIDC provider within Firezone, there is no check for whether or not an identity has already been added with a given email address. By creating a separate email column on the `auth_identities` table, it will be very straight forward to know whether an email address exists for a given identity, return it in an API response and allow the admin of a Firezone account to track users (Identities) by email rather than IdP identifier. Fixes #7392
This commit is contained in:
@@ -56,6 +56,8 @@ defmodule API.IdentityController do
|
||||
"provider_identifier_confirmation",
|
||||
Map.get(params, "provider_identifier")
|
||||
)
|
||||
|> maybe_put_email()
|
||||
|> maybe_put_identifier()
|
||||
|
||||
with {:ok, actor} <- Domain.Actors.fetch_actor_by_id(actor_id, subject),
|
||||
{:ok, provider} <- Auth.fetch_provider_by_id(provider_id, subject),
|
||||
@@ -140,4 +142,51 @@ defmodule API.IdentityController do
|
||||
|
||||
{:provider_check, valid?}
|
||||
end
|
||||
|
||||
defp maybe_put_email(params) do
|
||||
email =
|
||||
params["email"]
|
||||
|> to_string
|
||||
|> String.trim()
|
||||
|
||||
identifier =
|
||||
params["provider_identifier"]
|
||||
|> to_string()
|
||||
|> String.trim()
|
||||
|
||||
cond do
|
||||
Domain.Auth.valid_email?(email) ->
|
||||
params
|
||||
|
||||
Domain.Auth.valid_email?(identifier) ->
|
||||
Map.put(params, "email", identifier)
|
||||
|
||||
true ->
|
||||
params
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_put_identifier(params) do
|
||||
email =
|
||||
params["email"]
|
||||
|> to_string()
|
||||
|> String.trim()
|
||||
|
||||
identifier =
|
||||
params["provider_identifier"]
|
||||
|> to_string()
|
||||
|> String.trim()
|
||||
|
||||
cond do
|
||||
identifier != "" ->
|
||||
params
|
||||
|
||||
Domain.Auth.valid_email?(email) ->
|
||||
Map.put(params, "provider_identifier", email)
|
||||
|> Map.put("provider_identifier_confirmation", email)
|
||||
|
||||
true ->
|
||||
params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,7 +24,8 @@ defmodule API.IdentityJSON do
|
||||
id: identity.id,
|
||||
actor_id: identity.actor_id,
|
||||
provider_id: identity.provider_id,
|
||||
provider_identifier: identity.provider_identifier
|
||||
provider_identifier: identity.provider_identifier,
|
||||
email: identity.email
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,14 +13,16 @@ defmodule API.Schemas.Identity do
|
||||
id: %Schema{type: :string, description: "Identity ID"},
|
||||
actor_id: %Schema{type: :string, description: "Actor ID"},
|
||||
provider_id: %Schema{type: :string, description: "Identity Provider ID"},
|
||||
provider_identifier: %Schema{type: :string, description: "Identifier from Provider"}
|
||||
provider_identifier: %Schema{type: :string, description: "Identifier from Provider"},
|
||||
email: %Schema{type: :string, description: "Email"}
|
||||
},
|
||||
required: [:id, :actor_id, :provider_id, :provider_identifier],
|
||||
required: [:id, :actor_id, :provider_id, :provider_identifier, :email],
|
||||
example: %{
|
||||
"id" => "42a7f82f-831a-4a9d-8f17-c66c2bb6e205",
|
||||
"actor_id" => "cdfa97e6-cca1-41db-8fc7-864daedb46df",
|
||||
"provider_id" => "989f9e96-e348-47ec-ba85-869fcd7adb19",
|
||||
"provider_identifier" => "foo@bar.com"
|
||||
"provider_identifier" => "2551705710219359",
|
||||
"email" => "foo@bar.com"
|
||||
}
|
||||
})
|
||||
end
|
||||
@@ -40,7 +42,8 @@ defmodule API.Schemas.Identity do
|
||||
required: [:identity],
|
||||
example: %{
|
||||
"identity" => %{
|
||||
"provider_identifier" => "foo@bar.com"
|
||||
"provider_identifier" => "2551705710219359",
|
||||
"email" => "foo@bar.com"
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -63,7 +66,8 @@ defmodule API.Schemas.Identity do
|
||||
"id" => "42a7f82f-831a-4a9d-8f17-c66c2bb6e205",
|
||||
"actor_id" => "cdfa97e6-cca1-41db-8fc7-864daedb46df",
|
||||
"provider_id" => "989f9e96-e348-47ec-ba85-869fcd7adb19",
|
||||
"provider_identifier" => "foo@bar.com"
|
||||
"provider_identifier" => "2551705710219359",
|
||||
"email" => "foo@bar.com"
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -88,13 +92,15 @@ defmodule API.Schemas.Identity do
|
||||
"id" => "42a7f82f-831a-4a9d-8f17-c66c2bb6e205",
|
||||
"actor_id" => "8f44a02b-b8eb-406f-8202-4274bf60ebd0",
|
||||
"provider_id" => "6472d898-5b98-4c3b-b4b9-d3158c1891be",
|
||||
"provider_identifier" => "foo@bar.com"
|
||||
"provider_identifier" => "2551705710219359",
|
||||
"email" => "foo@bar.com"
|
||||
},
|
||||
%{
|
||||
"id" => "8a70eb96-e74b-4cdc-91b8-48c05ef74d4c",
|
||||
"actor_id" => "38c92cda-1ddb-45b3-9d1a-7efc375e00c1",
|
||||
"provider_id" => "04f13eed-4845-47c3-833e-fdd869fab96f",
|
||||
"provider_identifier" => "baz@bar.com"
|
||||
"provider_identifier" => "2638957392736483",
|
||||
"email" => "baz@bar.com"
|
||||
}
|
||||
],
|
||||
"metadata" => %{
|
||||
|
||||
@@ -49,7 +49,7 @@ defmodule API.IdentityControllerTest do
|
||||
assert equal_ids?(data_ids, identity_ids)
|
||||
end
|
||||
|
||||
test "lists resources with limit", %{conn: conn, account: account, actor: actor} do
|
||||
test "lists identities with limit", %{conn: conn, account: account, actor: actor} do
|
||||
identities =
|
||||
for _ <- 1..3, do: Fixtures.Auth.create_identity(%{account: account, actor: actor})
|
||||
|
||||
@@ -88,7 +88,70 @@ defmodule API.IdentityControllerTest do
|
||||
assert json_response(conn, 401) == %{"error" => %{"reason" => "Unauthorized"}}
|
||||
end
|
||||
|
||||
test "returns a single resource", %{conn: conn, account: account, actor: actor} do
|
||||
test "returns a single identity with populated email field", %{
|
||||
conn: conn,
|
||||
account: account,
|
||||
actor: actor
|
||||
} do
|
||||
identity =
|
||||
Fixtures.Auth.create_identity(%{
|
||||
account: account,
|
||||
actor: actor,
|
||||
provider_identifier: "172836495673",
|
||||
email: "foo@bar.com"
|
||||
})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> authorize_conn(actor)
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> get("/actors/#{actor.id}/identities/#{identity.id}")
|
||||
|
||||
assert json_response(conn, 200) == %{
|
||||
"data" => %{
|
||||
"id" => identity.id,
|
||||
"actor_id" => actor.id,
|
||||
"provider_id" => identity.provider_id,
|
||||
"provider_identifier" => identity.provider_identifier,
|
||||
"email" => identity.email
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
test "returns a single identity with populated email field from provider_identifier", %{
|
||||
conn: conn,
|
||||
account: account,
|
||||
actor: actor
|
||||
} do
|
||||
identity =
|
||||
Fixtures.Auth.create_identity(%{
|
||||
account: account,
|
||||
actor: actor,
|
||||
provider_identifier: "foo@bar.com"
|
||||
})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> authorize_conn(actor)
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> get("/actors/#{actor.id}/identities/#{identity.id}")
|
||||
|
||||
assert json_response(conn, 200) == %{
|
||||
"data" => %{
|
||||
"id" => identity.id,
|
||||
"actor_id" => actor.id,
|
||||
"provider_id" => identity.provider_id,
|
||||
"provider_identifier" => identity.provider_identifier,
|
||||
"email" => identity.provider_identifier
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
test "returns a single identity with empty email field", %{
|
||||
conn: conn,
|
||||
account: account,
|
||||
actor: actor
|
||||
} do
|
||||
identity = Fixtures.Auth.create_identity(%{account: account, actor: actor})
|
||||
|
||||
conn =
|
||||
@@ -102,7 +165,8 @@ defmodule API.IdentityControllerTest do
|
||||
"id" => identity.id,
|
||||
"actor_id" => actor.id,
|
||||
"provider_id" => identity.provider_id,
|
||||
"provider_identifier" => identity.provider_identifier
|
||||
"provider_identifier" => identity.provider_identifier,
|
||||
"email" => nil
|
||||
}
|
||||
}
|
||||
end
|
||||
@@ -158,7 +222,7 @@ defmodule API.IdentityControllerTest do
|
||||
assert resp == %{"error" => %{"reason" => "Not Found"}}
|
||||
end
|
||||
|
||||
test "returns error on invalid identity attrs", %{
|
||||
test "returns error on empty identity attrs", %{
|
||||
conn: conn,
|
||||
account: account,
|
||||
actor: api_actor
|
||||
@@ -189,7 +253,7 @@ defmodule API.IdentityControllerTest do
|
||||
}
|
||||
end
|
||||
|
||||
test "creates a resource with valid attrs", %{
|
||||
test "returns error on invalid identity attrs", %{
|
||||
conn: conn,
|
||||
account: account,
|
||||
actor: api_actor
|
||||
@@ -199,7 +263,37 @@ defmodule API.IdentityControllerTest do
|
||||
|
||||
actor = Fixtures.Actors.create_actor(account: account)
|
||||
|
||||
attrs = %{"provider_identifier" => "foo@local"}
|
||||
attrs = %{email: "foo"}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> authorize_conn(api_actor)
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/actors/#{actor.id}/providers/#{oidc_provider.id}/identities",
|
||||
identity: attrs
|
||||
)
|
||||
|
||||
assert resp = json_response(conn, 422)
|
||||
|
||||
assert resp == %{
|
||||
"error" => %{
|
||||
"reason" => "Unprocessable Entity",
|
||||
"validation_errors" => %{"provider_identifier" => ["can't be blank"]}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
test "creates an identity with provider_identifier attr only and is not an email address", %{
|
||||
conn: conn,
|
||||
account: account,
|
||||
actor: api_actor
|
||||
} do
|
||||
{oidc_provider, _bypass} =
|
||||
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
|
||||
|
||||
actor = Fixtures.Actors.create_actor(account: account)
|
||||
|
||||
attrs = %{"provider_identifier" => "128asdf92qrh9joqwefoiu23"}
|
||||
|
||||
conn =
|
||||
conn
|
||||
@@ -210,10 +304,116 @@ defmodule API.IdentityControllerTest do
|
||||
)
|
||||
|
||||
assert resp = json_response(conn, 201)
|
||||
|
||||
assert resp["data"]["provider_identifier"] == attrs["provider_identifier"]
|
||||
assert resp["data"]["provider_id"] == oidc_provider.id
|
||||
assert resp["data"]["actor_id"] == actor.id
|
||||
assert resp["data"]["email"] == nil
|
||||
end
|
||||
|
||||
test "creates an identity with provider_identifier attr only and is an email address", %{
|
||||
conn: conn,
|
||||
account: account,
|
||||
actor: api_actor
|
||||
} do
|
||||
{oidc_provider, _bypass} =
|
||||
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
|
||||
|
||||
actor = Fixtures.Actors.create_actor(account: account)
|
||||
|
||||
attrs = %{"provider_identifier" => "foo@localhost.local"}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> authorize_conn(api_actor)
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/actors/#{actor.id}/providers/#{oidc_provider.id}/identities",
|
||||
identity: attrs
|
||||
)
|
||||
|
||||
assert resp = json_response(conn, 201)
|
||||
assert resp["data"]["provider_identifier"] == attrs["provider_identifier"]
|
||||
assert resp["data"]["email"] == attrs["provider_identifier"]
|
||||
end
|
||||
|
||||
test "creates an identity with email attr only and populates provider_identifier", %{
|
||||
conn: conn,
|
||||
account: account,
|
||||
actor: api_actor
|
||||
} do
|
||||
{oidc_provider, _bypass} =
|
||||
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
|
||||
|
||||
actor = Fixtures.Actors.create_actor(account: account)
|
||||
|
||||
attrs = %{"email" => "foo@localhost.local"}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> authorize_conn(api_actor)
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/actors/#{actor.id}/providers/#{oidc_provider.id}/identities",
|
||||
identity: attrs
|
||||
)
|
||||
|
||||
assert resp = json_response(conn, 201)
|
||||
assert resp["data"]["provider_identifier"] == attrs["email"]
|
||||
assert resp["data"]["email"] == attrs["email"]
|
||||
end
|
||||
|
||||
test "creates an identity with provider_identifier attr and email attr being the same value",
|
||||
%{
|
||||
conn: conn,
|
||||
account: account,
|
||||
actor: api_actor
|
||||
} do
|
||||
{oidc_provider, _bypass} =
|
||||
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
|
||||
|
||||
actor = Fixtures.Actors.create_actor(account: account)
|
||||
|
||||
attrs = %{
|
||||
"provider_identifier" => "foo@localhost.local",
|
||||
"email" => "foo@localhost.local"
|
||||
}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> authorize_conn(api_actor)
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/actors/#{actor.id}/providers/#{oidc_provider.id}/identities",
|
||||
identity: attrs
|
||||
)
|
||||
|
||||
assert resp = json_response(conn, 201)
|
||||
assert resp["data"]["provider_identifier"] == attrs["provider_identifier"]
|
||||
assert resp["data"]["email"] == attrs["email"]
|
||||
end
|
||||
|
||||
test "creates an identity with provider_identifier attr and email attr being different values",
|
||||
%{
|
||||
conn: conn,
|
||||
account: account,
|
||||
actor: api_actor
|
||||
} do
|
||||
{oidc_provider, _bypass} =
|
||||
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
|
||||
|
||||
actor = Fixtures.Actors.create_actor(account: account)
|
||||
|
||||
attrs = %{
|
||||
"provider_identifier" => "foo@localhost.local",
|
||||
"email" => "bar@localhost.local"
|
||||
}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> authorize_conn(api_actor)
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/actors/#{actor.id}/providers/#{oidc_provider.id}/identities",
|
||||
identity: attrs
|
||||
)
|
||||
|
||||
assert resp = json_response(conn, 201)
|
||||
assert resp["data"]["provider_identifier"] == attrs["provider_identifier"]
|
||||
assert resp["data"]["email"] == attrs["email"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -224,7 +424,7 @@ defmodule API.IdentityControllerTest do
|
||||
assert json_response(conn, 401) == %{"error" => %{"reason" => "Unauthorized"}}
|
||||
end
|
||||
|
||||
test "deletes a resource", %{conn: conn, account: account, actor: actor} do
|
||||
test "deletes an identity", %{conn: conn, account: account, actor: actor} do
|
||||
identity = Fixtures.Auth.create_identity(%{account: account, actor: actor})
|
||||
|
||||
conn =
|
||||
@@ -238,7 +438,8 @@ defmodule API.IdentityControllerTest do
|
||||
"id" => identity.id,
|
||||
"actor_id" => actor.id,
|
||||
"provider_id" => identity.provider_id,
|
||||
"provider_identifier" => identity.provider_identifier
|
||||
"provider_identifier" => identity.provider_identifier,
|
||||
"email" => nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user