Complete Actors, Devices and Groups UIs (#1885)

This will be done once the remaining UI code is covered with tests.
This commit is contained in:
Andrew Dryga
2023-09-01 23:35:52 -06:00
committed by GitHub
parent 3bb4857f5f
commit e290f26298
211 changed files with 12970 additions and 5065 deletions

View File

@@ -85,6 +85,7 @@ defmodule API.Device.Channel do
connected_gateway_ids = Map.get(attrs, "connected_gateway_ids", [])
with {:ok, resource} <- Resources.fetch_resource_by_id(resource_id, socket.assigns.subject),
# TODO:
# :ok = Resource.authorize(resource, socket.assigns.subject),
{:ok, [_ | _] = gateways} <-
Gateways.list_connected_gateways_for_resource(resource),

View File

@@ -1,29 +1,27 @@
defmodule API.Device.ChannelTest do
use API.ChannelCase
alias Domain.{AccountsFixtures, ActorsFixtures, AuthFixtures, ResourcesFixtures}
alias Domain.{ConfigFixtures, DevicesFixtures, RelaysFixtures, GatewaysFixtures}
setup do
account = AccountsFixtures.create_account()
ConfigFixtures.upsert_configuration(account: account, devices_upstream_dns: ["1.1.1.1"])
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(actor: actor, account: account)
subject = AuthFixtures.create_subject(identity)
device = DevicesFixtures.create_device(subject: subject)
gateway = GatewaysFixtures.create_gateway(account: account)
account = Fixtures.Accounts.create_account()
Fixtures.Config.upsert_configuration(account: account, devices_upstream_dns: ["1.1.1.1"])
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(actor: actor, account: account)
subject = Fixtures.Auth.create_subject(identity: identity)
device = Fixtures.Devices.create_device(subject: subject)
gateway = Fixtures.Gateways.create_gateway(account: account)
dns_resource =
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: gateway.group_id}]
connections: [%{gateway_group_id: gateway.group_id}]
)
cidr_resource =
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
type: :cidr,
address: "192.168.1.1/28",
account: account,
gateway_groups: [%{gateway_group_id: gateway.group_id}]
connections: [%{gateway_group_id: gateway.group_id}]
)
expires_at = DateTime.utc_now() |> DateTime.add(30, :second)
@@ -138,7 +136,7 @@ defmodule API.Device.ChannelTest do
dns_resource: resource,
socket: socket
} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
:ok = Domain.Gateways.connect_gateway(gateway)
ref = push(socket, "prepare_connection", %{"resource_id" => resource.id})
@@ -152,9 +150,9 @@ defmodule API.Device.ChannelTest do
socket: socket
} do
# Online Relay
global_relay_group = RelaysFixtures.create_global_group()
global_relay = RelaysFixtures.create_relay(group: global_relay_group, ipv6: nil)
relay = RelaysFixtures.create_relay(account: account)
global_relay_group = Fixtures.Relays.create_global_group()
global_relay = Fixtures.Relays.create_relay(group: global_relay_group, ipv6: nil)
relay = Fixtures.Relays.create_relay(account: account)
stamp_secret = Ecto.UUID.generate()
:ok = Domain.Relays.connect_relay(relay, stamp_secret)
@@ -247,7 +245,7 @@ defmodule API.Device.ChannelTest do
dns_resource: resource,
socket: socket
} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
:ok = Domain.Gateways.connect_gateway(gateway)
attrs = %{
@@ -334,7 +332,7 @@ defmodule API.Device.ChannelTest do
dns_resource: resource,
socket: socket
} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
:ok = Domain.Gateways.connect_gateway(gateway)
attrs = %{

View File

@@ -3,7 +3,6 @@ defmodule API.Device.SocketTest do
import API.Device.Socket, only: [id: 1]
alias API.Device.Socket
alias Domain.Auth
alias Domain.{AuthFixtures, DevicesFixtures}
@connect_info %{
user_agent: "iOS/12.7 (iPhone) connlib/0.1.1",
@@ -22,7 +21,7 @@ defmodule API.Device.SocketTest do
end
test "creates a new device" do
subject = AuthFixtures.create_subject()
subject = Fixtures.Auth.create_subject()
{:ok, token} = Auth.create_session_token_from_subject(subject)
attrs = connect_attrs(token: token)
@@ -38,8 +37,8 @@ defmodule API.Device.SocketTest do
end
test "updates existing device" do
subject = AuthFixtures.create_subject()
existing_device = DevicesFixtures.create_device(subject: subject)
subject = Fixtures.Auth.create_subject()
existing_device = Fixtures.Devices.create_device(subject: subject)
{:ok, token} = Auth.create_session_token_from_subject(subject)
attrs = connect_attrs(token: token, external_id: existing_device.external_id)
@@ -52,7 +51,7 @@ defmodule API.Device.SocketTest do
describe "id/1" do
test "creates a channel for a device" do
device = DevicesFixtures.create_device()
device = Fixtures.Devices.create_device()
socket = socket(API.Device.Socket, "", %{device: device})
assert id(socket) == "device:#{device.id}"
@@ -68,7 +67,7 @@ defmodule API.Device.SocketTest do
end
defp connect_attrs(attrs) do
DevicesFixtures.device_attrs()
Fixtures.Devices.device_attrs()
|> Map.take(~w[external_id public_key]a)
|> Map.merge(Enum.into(attrs, %{}))
|> Enum.into(%{}, fn {k, v} -> {to_string(k), v} end)

View File

@@ -1,20 +1,18 @@
defmodule API.Gateway.ChannelTest do
use API.ChannelCase
alias Domain.{AccountsFixtures, ActorsFixtures, AuthFixtures, ResourcesFixtures}
alias Domain.{DevicesFixtures, RelaysFixtures, GatewaysFixtures}
setup do
account = AccountsFixtures.create_account()
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(actor: actor, account: account)
subject = AuthFixtures.create_subject(identity)
device = DevicesFixtures.create_device(subject: subject)
gateway = GatewaysFixtures.create_gateway(account: account)
account = Fixtures.Accounts.create_account()
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(actor: actor, account: account)
subject = Fixtures.Auth.create_subject(identity: identity)
device = Fixtures.Devices.create_device(subject: subject)
gateway = Fixtures.Gateways.create_gateway(account: account)
resource =
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: gateway.group_id}]
connections: [%{gateway_group_id: gateway.group_id}]
)
{:ok, _, socket} =
@@ -22,7 +20,7 @@ defmodule API.Gateway.ChannelTest do
|> socket("gateway:#{gateway.id}", %{gateway: gateway})
|> subscribe_and_join(API.Gateway.Channel, "gateway")
relay = RelaysFixtures.create_relay(account: account)
relay = Fixtures.Relays.create_relay(account: account)
%{
account: account,

View File

@@ -3,7 +3,6 @@ defmodule API.Gateway.SocketTest do
import API.Gateway.Socket, except: [connect: 3]
alias API.Gateway.Socket
alias Domain.Gateways
alias Domain.GatewaysFixtures
@connlib_version "0.1.1"
@@ -19,7 +18,7 @@ defmodule API.Gateway.SocketTest do
end
test "creates a new gateway" do
token = GatewaysFixtures.create_token()
token = Fixtures.Gateways.create_token()
encrypted_secret = Gateways.encode_token!(token)
attrs = connect_attrs(token: encrypted_secret)
@@ -35,8 +34,8 @@ defmodule API.Gateway.SocketTest do
end
test "updates existing gateway" do
token = GatewaysFixtures.create_token()
existing_gateway = GatewaysFixtures.create_gateway(token: token)
token = Fixtures.Gateways.create_token()
existing_gateway = Fixtures.Gateways.create_gateway(token: token)
encrypted_secret = Gateways.encode_token!(token)
attrs = connect_attrs(token: encrypted_secret, external_id: existing_gateway.external_id)
@@ -54,7 +53,7 @@ defmodule API.Gateway.SocketTest do
describe "id/1" do
test "creates a channel for a gateway" do
gateway = GatewaysFixtures.create_gateway()
gateway = Fixtures.Gateways.create_gateway()
socket = socket(API.Gateway.Socket, "", %{gateway: gateway})
assert id(socket) == "gateway:#{gateway.id}"
@@ -62,7 +61,7 @@ defmodule API.Gateway.SocketTest do
end
defp connect_attrs(attrs) do
GatewaysFixtures.gateway_attrs()
Fixtures.Gateways.gateway_attrs()
|> Map.take(~w[external_id public_key]a)
|> Map.merge(Enum.into(attrs, %{}))
|> Enum.into(%{}, fn {k, v} -> {to_string(k), v} end)

View File

@@ -1,9 +1,8 @@
defmodule API.Relay.ChannelTest do
use API.ChannelCase
alias Domain.RelaysFixtures
setup do
relay = RelaysFixtures.create_relay()
relay = Fixtures.Relays.create_relay()
stamp_secret = Domain.Crypto.rand_string()
@@ -24,8 +23,8 @@ defmodule API.Relay.ChannelTest do
end
test "tracks presence after join of an global relay" do
group = RelaysFixtures.create_global_group()
relay = RelaysFixtures.create_relay(group: group)
group = Fixtures.Relays.create_global_group()
relay = Fixtures.Relays.create_relay(group: group)
stamp_secret = Domain.Crypto.rand_string()

View File

@@ -3,7 +3,6 @@ defmodule API.Relay.SocketTest do
import API.Relay.Socket, except: [connect: 3]
alias API.Relay.Socket
alias Domain.Relays
alias Domain.RelaysFixtures
@connlib_version "0.1.1"
@@ -19,7 +18,7 @@ defmodule API.Relay.SocketTest do
end
test "creates a new relay" do
token = RelaysFixtures.create_token()
token = Fixtures.Relays.create_token()
encrypted_secret = Relays.encode_token!(token)
attrs = connect_attrs(token: encrypted_secret)
@@ -35,8 +34,8 @@ defmodule API.Relay.SocketTest do
end
test "updates existing relay" do
token = RelaysFixtures.create_token()
existing_relay = RelaysFixtures.create_relay(token: token)
token = Fixtures.Relays.create_token()
existing_relay = Fixtures.Relays.create_relay(token: token)
encrypted_secret = Relays.encode_token!(token)
attrs = connect_attrs(token: encrypted_secret, ipv4: existing_relay.ipv4)
@@ -54,7 +53,7 @@ defmodule API.Relay.SocketTest do
describe "id/1" do
test "creates a channel for a relay" do
relay = RelaysFixtures.create_relay()
relay = Fixtures.Relays.create_relay()
socket = socket(API.Relay.Socket, "", %{relay: relay})
assert id(socket) == "relay:#{relay.id}"
@@ -62,7 +61,7 @@ defmodule API.Relay.SocketTest do
end
defp connect_attrs(attrs) do
RelaysFixtures.relay_attrs()
Fixtures.Relays.relay_attrs()
|> Map.take(~w[ipv4 ipv6]a)
|> Map.merge(Enum.into(attrs, %{}))
|> Enum.into(%{}, fn {k, v} -> {to_string(k), v} end)

View File

@@ -14,6 +14,7 @@ defmodule API.ChannelCase do
import Phoenix.ChannelTest
import API.ChannelCase
alias Domain.Repo
alias Domain.Fixtures
# The default endpoint for testing
@endpoint API.Endpoint

View File

@@ -50,7 +50,7 @@ defmodule Domain.Accounts do
end
def create_account(attrs) do
Account.Changeset.create_changeset(attrs)
Account.Changeset.create(attrs)
|> Repo.insert()
end

View File

@@ -5,6 +5,32 @@ defmodule Domain.Accounts.Account do
field :name, :string
field :slug, :string
# We mention all schemas here to leverage Ecto compile-time reference checks,
# because later we will have to shard data by account_id.
has_many :actors, Domain.Actors.Actor, where: [deleted_at: nil]
has_many :actor_group_memberships, Domain.Actors.Membership, where: [deleted_at: nil]
has_many :actor_groups, Domain.Actors.Group, where: [deleted_at: nil]
has_many :auth_providers, Domain.Auth.Provider, where: [deleted_at: nil]
has_many :auth_identities, Domain.Auth.Identity, where: [deleted_at: nil]
has_many :network_addresses, Domain.Network.Address, where: [deleted_at: nil]
has_many :policies, Domain.Policies.Policy, where: [deleted_at: nil]
has_many :resources, Domain.Resources.Resource, where: [deleted_at: nil]
has_many :resource_connections, Domain.Resources.Connection, where: [deleted_at: nil]
has_many :devices, Domain.Devices.Device, where: [deleted_at: nil]
has_many :gateways, Domain.Gateways.Gateway, where: [deleted_at: nil]
has_many :gateway_groups, Domain.Gateways.Group, where: [deleted_at: nil]
has_many :gateway_tokens, Domain.Gateways.Token, where: [deleted_at: nil]
has_many :relays, Domain.Relays.Relay, where: [deleted_at: nil]
has_many :relay_groups, Domain.Relays.Group, where: [deleted_at: nil]
has_many :relay_tokens, Domain.Relays.Token, where: [deleted_at: nil]
timestamps()
end
end

View File

@@ -2,28 +2,23 @@ defmodule Domain.Accounts.Account.Changeset do
use Domain, :changeset
alias Domain.Accounts.Account
def changeset(account, attrs) do
account
def create(attrs) do
%Account{}
|> cast(attrs, [:name, :slug])
|> changeset()
end
defp changeset(changeset) do
changeset
|> validate_required([:name])
|> validate_name()
|> trim_change(:name)
|> validate_length(:name, min: 3, max: 64)
|> prepare_changes(fn changeset -> put_slug_default(changeset) end)
|> downcase_slug()
|> validate_slug()
|> unique_constraint(:slug, name: :accounts_slug_index)
end
def create_changeset(attrs) do
%Account{}
|> changeset(attrs)
end
defp validate_name(changeset) do
changeset
|> validate_length(:name, min: 3, max: 64)
end
defp put_slug_default(changeset) do
changeset
|> put_default_value(:slug, &Domain.Accounts.generate_unique_slug/0)

View File

@@ -1,117 +1,12 @@
defmodule Domain.Actors do
alias Domain.{Repo, Auth, Validator}
alias Domain.Actors.Membership
alias Web.Devices
alias Domain.{Repo, Validator}
alias Domain.{Accounts, Auth, Devices}
alias Domain.Actors.{Authorizer, Actor, Group}
require Ecto.Query
def fetch_group_by_id(id, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()),
true <- Validator.valid_uuid?(id) do
Group.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
else
false -> {:error, :not_found}
other -> other
end
end
def list_groups(%Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Group.Query.all()
|> Authorizer.for_subject(subject)
|> Repo.list()
end
end
def new_group(attrs \\ %{}) do
change_group(%Group{}, attrs)
end
def upsert_provider_groups(%Auth.Provider{} = provider, attrs_by_provider_identifier) do
attrs_by_provider_identifier
|> Enum.reduce(Ecto.Multi.new(), fn {provider_identifier, attrs}, multi ->
Ecto.Multi.insert(
multi,
{:group, provider_identifier},
Group.Changeset.create_changeset(provider, provider_identifier, attrs),
conflict_target: Group.Changeset.upsert_conflict_target(),
on_conflict: Group.Changeset.upsert_on_conflict(),
returning: true
)
end)
|> Repo.transaction()
# Ecto.Multi.new()
# |> Ecto.Multi.insert(:actor, Actor.Changeset.create_changeset(provider, attrs))
# |> Ecto.Multi.run(:identity, fn _repo, %{actor: actor} ->
# Auth.create_identity(actor, provider, provider_identifier)
# end)
# |> Repo.transaction()
|> case do
{:ok, %{group: group}} ->
{:ok, group}
{:error, _step, changeset, _effects_so_far} ->
{:error, changeset}
end
end
def group_synced?(%Group{provider_id: nil}), do: false
def group_synced?(%Group{}), do: true
def create_group(attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
subject.account
|> Group.Changeset.create_changeset(attrs)
|> Repo.insert()
end
end
def change_group(group, attrs \\ %{})
def change_group(%Group{provider_id: nil} = group, attrs) do
group
|> Repo.preload(:memberships)
|> Group.Changeset.update_changeset(attrs)
end
def change_group(%Group{}, _attrs) do
raise ArgumentError, "can't change synced groups"
end
def update_group(%Group{provider_id: nil} = group, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
group
|> Repo.preload(:memberships)
|> Group.Changeset.update_changeset(attrs)
|> Repo.update()
end
end
def update_group(%Group{}, _attrs, %Auth.Subject{}) do
{:error, :synced_group}
end
def delete_group(%Group{provider_id: nil} = group, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Group.Query.by_id(group.id)
|> Authorizer.for_subject(subject)
|> Group.Query.by_account_id(subject.account.id)
|> Repo.fetch_and_update(with: &Group.Changeset.delete_changeset/1)
end
end
def delete_group(%Group{}, %Auth.Subject{}) do
{:error, :synced_group}
end
def fetch_count_by_type(type, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Actor.Query.by_type(type)
|> Authorizer.for_subject(subject)
|> Repo.aggregate(:count)
end
end
# Groups
def fetch_groups_count_grouped_by_provider_id(%Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
@@ -129,11 +24,136 @@ defmodule Domain.Actors do
end
end
def fetch_actor_by_id(id, %Auth.Subject{} = subject, opts \\ []) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
def fetch_group_by_id(id, %Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()),
true <- Validator.valid_uuid?(id) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Group.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
|> case do
{:ok, group} -> {:ok, Repo.preload(group, preload)}
{:error, reason} -> {:error, reason}
end
else
false -> {:error, :not_found}
other -> other
end
end
def list_groups(%Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
{:ok, groups} =
Group.Query.all()
|> Authorizer.for_subject(subject)
|> Repo.list()
{:ok, Repo.preload(groups, preload)}
end
end
def peek_group_actors(groups, limit, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
ids = groups |> Enum.map(& &1.id) |> Enum.uniq()
Group.Query.by_id({:in, ids})
|> Group.Query.preload_few_actors_for_each_group(limit)
|> Repo.peek(groups)
end
end
def peek_actor_groups(actors, limit, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
ids = actors |> Enum.map(& &1.id) |> Enum.uniq()
Actor.Query.by_id({:in, ids})
|> Actor.Query.preload_few_groups_for_each_actor(limit)
|> Repo.peek(actors)
end
end
def sync_provider_groups_multi(%Auth.Provider{} = provider, attrs_list) do
Group.Sync.sync_provider_groups_multi(provider, attrs_list)
end
def sync_provider_memberships_multi(multi, %Auth.Provider{} = provider, tuples) do
Membership.Sync.sync_provider_memberships_multi(multi, provider, tuples)
end
def new_group(attrs \\ %{}) do
change_group(%Group{}, attrs)
end
def create_group(attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
subject.account
|> Group.Changeset.create(attrs, subject)
|> Repo.insert()
end
end
def change_group(group, attrs \\ %{})
def change_group(%Group{provider_id: nil} = group, attrs) do
group
|> Repo.preload(:memberships)
|> Group.Changeset.update(attrs)
end
def change_group(%Group{}, _attrs) do
raise ArgumentError, "can't change synced groups"
end
def update_group(%Group{provider_id: nil} = group, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
group
|> Repo.preload(:memberships)
|> Group.Changeset.update(attrs)
|> Repo.update()
end
end
def update_group(%Group{}, _attrs, %Auth.Subject{}) do
{:error, :synced_group}
end
def delete_group(%Group{provider_id: nil} = group, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Group.Query.by_id(group.id)
|> Authorizer.for_subject(subject)
|> Group.Query.by_account_id(subject.account.id)
|> Repo.fetch_and_update(with: &Group.Changeset.delete/1)
end
end
def delete_group(%Group{}, %Auth.Subject{}) do
{:error, :synced_group}
end
def group_synced?(%Group{provider_id: nil}), do: false
def group_synced?(%Group{}), do: true
def group_deleted?(%Group{deleted_at: nil}), do: false
def group_deleted?(%Group{}), do: true
# Actors
def fetch_actors_count_by_type(type, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Actor.Query.by_type(type)
|> Authorizer.for_subject(subject)
|> Repo.aggregate(:count)
end
end
def fetch_actor_by_id(id, %Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()),
true <- Validator.valid_uuid?(id) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Actor.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
@@ -163,76 +183,52 @@ defmodule Domain.Actors do
def list_actors(%Auth.Subject{} = subject, opts \\ []) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
{hydrate, _opts} = Keyword.pop(opts, :hydrate, [])
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
{:ok, actors} =
Actor.Query.all()
|> Authorizer.for_subject(subject)
|> hydrate_fields(hydrate)
|> Repo.list()
{:ok, Repo.preload(actors, preload)}
end
end
defp hydrate_fields(queryable, []), do: queryable
def new_actor(attrs \\ %{memberships: []}) do
Actor.Changeset.create(attrs)
end
def create_actor(
%Auth.Provider{} = provider,
provider_identifier,
attrs,
%Auth.Subject{} = subject
) do
def create_actor(%Accounts.Account{} = account, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()),
:ok <- Auth.ensure_has_access_to(subject, provider),
changeset = Actor.Changeset.create_changeset(provider.account_id, attrs),
{:ok, data} <- Ecto.Changeset.apply_action(changeset, :validate) do
granted_permissions = Auth.fetch_type_permissions!(data.type)
if MapSet.subset?(granted_permissions, subject.permissions) do
create_actor(provider, provider_identifier, attrs)
else
missing_permissions =
MapSet.difference(granted_permissions, subject.permissions)
|> MapSet.to_list()
{:error, {:unauthorized, privilege_escalation: missing_permissions}}
end
:ok <- Accounts.ensure_has_access_to(subject, account) do
Actor.Changeset.create(account.id, attrs, subject)
|> Repo.insert()
end
end
def create_actor(%Auth.Provider{} = provider, provider_identifier, attrs) do
{provider_attrs, attrs} = Map.pop(attrs, "provider", %{})
Ecto.Multi.new()
|> Ecto.Multi.insert(:actor, Actor.Changeset.create_changeset(provider.account_id, attrs))
|> Ecto.Multi.run(:identity, fn _repo, %{actor: actor} ->
Auth.create_identity(actor, provider, provider_identifier, provider_attrs)
end)
|> Repo.transaction()
|> case do
{:ok, %{actor: actor, identity: identity}} ->
{:ok, %{actor | identities: [identity]}}
{:error, _step, changeset, _effects_so_far} ->
{:error, changeset}
end
def create_actor(%Accounts.Account{} = account, attrs) do
Actor.Changeset.create(account.id, attrs)
|> Repo.insert()
end
def change_actor_type(%Actor{} = actor, type, %Auth.Subject{} = subject) do
def change_actor(%Actor{} = actor, attrs \\ %{}) do
Actor.Changeset.update(actor, attrs)
end
def update_actor(%Actor{} = actor, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Actor.Query.by_id(actor.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(
with: fn actor ->
changeset = Actor.Changeset.set_actor_type(actor, type)
actor = Repo.preload(actor, :memberships)
changeset = Actor.Changeset.update(actor, attrs, subject)
cond do
changeset.data.type != :admin ->
changeset.data.type != :account_admin_user ->
changeset
changeset.changes.type == :admin ->
Map.get(changeset.changes, :type) == :account_admin_user ->
changeset
other_enabled_admins_exist?(actor) ->
@@ -252,7 +248,7 @@ defmodule Domain.Actors do
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(
with: fn actor ->
if other_enabled_admins_exist?(actor) do
if actor.type != :account_admin_user or other_enabled_admins_exist?(actor) do
Actor.Changeset.disable_actor(actor)
else
:cant_disable_the_last_admin
@@ -276,7 +272,10 @@ defmodule Domain.Actors do
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(
with: fn actor ->
if other_enabled_admins_exist?(actor) do
if actor.type != :account_admin_user or other_enabled_admins_exist?(actor) do
:ok = Auth.delete_actor_identities(actor)
:ok = Devices.delete_actor_devices(actor)
Actor.Changeset.delete_actor(actor)
else
:cant_delete_the_last_admin
@@ -286,6 +285,16 @@ defmodule Domain.Actors do
end
end
# TODO: when actor is synced we should not allow changing the name
def actor_synced?(%Actor{last_synced_at: nil}), do: false
def actor_synced?(%Actor{}), do: true
def actor_deleted?(%Actor{deleted_at: nil}), do: false
def actor_deleted?(%Actor{}), do: true
def actor_disabled?(%Actor{disabled_at: nil}), do: false
def actor_disabled?(%Actor{}), do: true
defp other_enabled_admins_exist?(%Actor{
type: :account_admin_user,
account_id: account_id,

View File

@@ -7,12 +7,13 @@ defmodule Domain.Actors.Actor do
field :name, :string
has_many :identities, Domain.Auth.Identity, where: [deleted_at: nil]
has_many :devices, Domain.Devices.Device, where: [deleted_at: nil]
has_many :memberships, Domain.Actors.Membership, on_replace: :delete
has_many :groups, through: [:memberships, :group]
belongs_to :account, Domain.Accounts.Account
has_many :memberships, Domain.Actors.Membership, on_replace: :delete
has_many :groups, through: [:memberships, :group], where: [deleted_at: nil]
field :last_synced_at, :utc_datetime_usec
field :disabled_at, :utc_datetime_usec
field :deleted_at, :utc_datetime_usec
timestamps()

View File

@@ -1,41 +1,84 @@
defmodule Domain.Actors.Actor.Changeset do
use Domain, :changeset
alias Domain.Auth
alias Domain.Actors
alias Domain.Actors.Actor
def changeset(actor, attrs) do
actor
|> cast(attrs, ~w[type name]a)
|> validate_required(~w[type name]a)
|> validate_length(:name, min: 1, max: 255)
def keys, do: ~w[type name]a
def keys(%Actor{last_synced_at: nil}), do: ~w[type name]a
def keys(%Actor{}), do: ~w[type]a
def create(account_id, attrs, %Auth.Subject{} = subject) do
create(account_id, attrs)
|> validate_granted_permissions(subject)
end
def create_changeset(account_id, attrs) do
%Actors.Actor{}
|> changeset(attrs)
def create(account_id, attrs) do
create(attrs)
|> put_change(:account_id, account_id)
|> cast_assoc(:memberships,
with: &Actors.Membership.Changeset.changeset(account_id, &1, &2)
)
end
def set_actor_type(actor, type) do
def create(attrs) do
keys = keys()
%Actors.Actor{memberships: []}
|> cast(attrs, keys)
|> validate_required(keys)
|> changeset()
end
def update(%Actor{} = actor, attrs, %Auth.Subject{} = subject) do
update(actor, attrs)
|> validate_granted_permissions(subject)
end
def update(%Actor{} = actor, attrs) do
keys = keys(actor)
actor
|> change()
|> put_change(:type, type)
|> cast(attrs, keys)
|> validate_required(keys)
|> cast_assoc(:memberships,
with: &Actors.Membership.Changeset.changeset(actor.account_id, &1, &2)
)
|> changeset()
end
def disable_actor(actor) do
def changeset(changeset) do
changeset
# Actor name can be very long in case IdP syncs something crazy long to us,
# we still don't wait to fail for that silently
|> validate_length(:name, max: 512)
end
def disable_actor(%Actor{} = actor) do
actor
|> change()
|> put_default_value(:disabled_at, DateTime.utc_now())
end
def enable_actor(actor) do
def enable_actor(%Actor{} = actor) do
actor
|> change()
|> put_default_value(:disabled_at, nil)
|> put_change(:disabled_at, nil)
end
def delete_actor(actor) do
def delete_actor(%Actor{} = actor) do
actor
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())
end
defp validate_granted_permissions(changeset, subject) do
validate_change(changeset, :type, fn :type, granted_actor_type ->
if Auth.can_grant_role?(subject, granted_actor_type) do
[]
else
[{:type, "does not have permissions to grant this actor type"}]
end
end)
end
end

View File

@@ -8,6 +8,10 @@ defmodule Domain.Actors.Actor.Query do
def by_id(queryable \\ all(), id)
def by_id(queryable, {:in, ids}) do
where(queryable, [actors: actors], actors.id in ^ids)
end
def by_id(queryable, {:not, id}) do
where(queryable, [actors: actors], actors.id != ^id)
end
@@ -28,13 +32,58 @@ defmodule Domain.Actors.Actor.Query do
where(queryable, [actors: actors], is_nil(actors.disabled_at))
end
def preload_few_groups_for_each_actor(queryable \\ all(), limit) do
queryable
|> with_joined_memberships(limit)
|> with_joined_groups()
|> with_joined_group_counts()
|> select([actors: actors, groups: groups, group_counts: group_counts], %{
id: actors.id,
count: group_counts.count,
item: groups
})
end
def with_joined_memberships(queryable, limit) do
subquery =
Domain.Actors.Membership.Query.all()
|> where([memberships: memberships], memberships.actor_id == parent_as(:actors).id)
# we need second join to exclude soft deleted actors before applying a limit
|> join(:inner, [memberships: memberships], groups in ^Domain.Actors.Group.Query.all(),
on: groups.id == memberships.group_id
)
|> select([memberships: memberships], memberships.group_id)
|> limit(^limit)
join(queryable, :cross_lateral, [actors: actors], memberships in subquery(subquery),
as: :memberships
)
end
def with_joined_group_counts(queryable) do
subquery =
Domain.Actors.Membership.Query.count_groups_by_actor_id()
|> where([memberships: memberships], memberships.actor_id == parent_as(:actors).id)
join(queryable, :cross_lateral, [actors: actors], group_counts in subquery(subquery),
as: :group_counts
)
end
def with_joined_groups(queryable \\ all()) do
join(queryable, :left, [memberships: memberships], groups in ^Domain.Actors.Group.Query.all(),
on: groups.id == memberships.group_id,
as: :groups
)
end
def lock(queryable \\ all()) do
lock(queryable, "FOR UPDATE")
end
def with_assoc(queryable \\ all(), assoc) do
def with_assoc(queryable \\ all(), qual \\ :left, assoc) do
with_named_binding(queryable, assoc, fn query, binding ->
join(query, :left, [actors: actors], a in assoc(actors, ^binding), as: ^binding)
join(query, qual, [actors: actors], a in assoc(actors, ^binding), as: ^binding)
end)
end
end

View File

@@ -5,11 +5,18 @@ defmodule Domain.Actors.Group do
field :name, :string
# Those fields will be set for groups we synced from IdP's
belongs_to :provider, Domain.Auth.Provider
belongs_to :provider, Domain.Auth.Provider, where: [deleted_at: nil]
field :provider_identifier, :string
has_many :policies, Domain.Policies.Policy,
foreign_key: :actor_group_id,
where: [deleted_at: nil]
has_many :memberships, Domain.Actors.Membership, on_replace: :delete
has_many :actors, through: [:memberships, :actor], where: [deleted_at: nil]
has_many :actors, through: [:memberships, :actor]
field :created_by, Ecto.Enum, values: ~w[identity provider]a
belongs_to :created_by_identity, Domain.Auth.Identity
belongs_to :account, Domain.Accounts.Account

View File

@@ -3,54 +3,55 @@ defmodule Domain.Actors.Group.Changeset do
alias Domain.{Auth, Accounts}
alias Domain.Actors
@fields ~w[name]a
def upsert_conflict_target do
{:unsafe_fragment,
"(account_id, provider_id, provider_identifier) " <>
"WHERE deleted_at IS NULL AND provider_id IS NOT NULL AND provider_identifier IS NOT NULL"}
end
# We do not update the `name` field because we allow to manually override it in the UI
# for usability reasons when the provider uses group names that can make people confused
def upsert_on_conflict, do: {:replace, (@fields -- ~w[name]a) ++ ~w[updated_at]a}
def upsert_on_conflict, do: {:replace, ~w[name updated_at]a}
def create_changeset(%Accounts.Account{} = account, attrs) do
%Actors.Group{account_id: account.id}
|> changeset(attrs)
|> validate_length(:name, min: 1, max: 64)
|> cast_assoc(:memberships,
with: &Actors.Membership.Changeset.group_changeset(account.id, &1, &2)
)
end
def create_changeset(%Auth.Provider{} = provider, provider_identifier, attrs) do
def create(%Accounts.Account{} = account, attrs, %Auth.Subject{} = subject) do
%Actors.Group{}
|> changeset(attrs)
|> cast(attrs, ~w[name]a)
|> validate_required(~w[name]a)
|> changeset()
|> put_change(:account_id, account.id)
|> cast_assoc(:memberships,
with: &Actors.Membership.Changeset.changeset(account.id, &1, &2)
)
|> put_change(:created_by, :identity)
|> put_change(:created_by_identity_id, subject.identity.id)
end
def create(%Auth.Provider{} = provider, attrs) do
%Actors.Group{}
|> cast(attrs, ~w[name provider_identifier]a)
|> validate_required(~w[name provider_identifier]a)
|> changeset()
|> put_change(:provider_id, provider.id)
|> put_change(:provider_identifier, provider_identifier)
|> cast_assoc(:memberships,
with: &Actors.Membership.Changeset.group_changeset(provider.account_id, &1, &2)
)
|> put_change(:account_id, provider.account_id)
|> put_change(:created_by, :provider)
end
def update_changeset(%Actors.Group{} = group, attrs) do
changeset(group, attrs)
|> validate_length(:name, min: 1, max: 64)
|> cast_assoc(:memberships,
with: &Actors.Membership.Changeset.group_changeset(group.account_id, &1, &2)
)
end
defp changeset(group, attrs) do
def update(%Actors.Group{} = group, attrs) do
group
|> cast(attrs, @fields)
|> validate_required(@fields)
|> cast(attrs, ~w[name]a)
|> validate_required(~w[name]a)
|> changeset()
|> cast_assoc(:memberships,
with: &Actors.Membership.Changeset.changeset(group.account_id, &1, &2)
)
end
defp changeset(changeset) do
changeset
|> trim_change(:name)
|> validate_length(:name, min: 1, max: 64)
|> unique_constraint(:name, name: :actor_groups_account_id_name_index)
end
def delete_changeset(%Actors.Group{} = group) do
def delete(%Actors.Group{} = group) do
group
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())

View File

@@ -6,7 +6,13 @@ defmodule Domain.Actors.Group.Query do
|> where([groups: groups], is_nil(groups.deleted_at))
end
def by_id(queryable \\ all(), id) do
def by_id(queryable \\ all(), id)
def by_id(queryable, {:in, ids}) do
where(queryable, [groups: groups], groups.id in ^ids)
end
def by_id(queryable, id) do
where(queryable, [groups: groups], groups.id == ^id)
end
@@ -18,7 +24,13 @@ defmodule Domain.Actors.Group.Query do
where(queryable, [groups: groups], groups.provider_id == ^provider_id)
end
def by_provider_identifier(queryable \\ all(), provider_identifier) do
def by_provider_identifier(queryable \\ all(), provider_identifier)
def by_provider_identifier(queryable, {:in, provider_identifiers}) do
where(queryable, [groups: groups], groups.provider_identifier in ^provider_identifiers)
end
def by_provider_identifier(queryable, provider_identifier) do
where(queryable, [groups: groups], groups.provider_identifier == ^provider_identifier)
end
@@ -32,6 +44,57 @@ defmodule Domain.Actors.Group.Query do
})
end
def preload_few_actors_for_each_group(queryable \\ all(), limit) do
queryable
|> with_joined_memberships(limit)
|> with_joined_actors()
|> with_joined_actor_counts()
|> select([groups: groups, actors: actors, actor_counts: actor_counts], %{
id: groups.id,
count: actor_counts.count,
item: actors
})
end
def with_joined_memberships(queryable) do
join(queryable, :left, [groups: groups], memberships in assoc(groups, :memberships),
as: :memberships
)
end
def with_joined_memberships(queryable, limit) do
subquery =
Domain.Actors.Membership.Query.all()
|> where([memberships: memberships], memberships.group_id == parent_as(:groups).id)
# we need second join to exclude soft deleted actors before applying a limit
|> join(:inner, [memberships: memberships], actors in ^Domain.Actors.Actor.Query.all(),
on: actors.id == memberships.actor_id
)
|> select([memberships: memberships], memberships.actor_id)
|> limit(^limit)
join(queryable, :cross_lateral, [groups: groups], memberships in subquery(subquery),
as: :memberships
)
end
def with_joined_actor_counts(queryable) do
subquery =
Domain.Actors.Membership.Query.count_actors_by_group_id()
|> where([memberships: memberships], memberships.group_id == parent_as(:groups).id)
join(queryable, :cross_lateral, [groups: groups], actor_counts in subquery(subquery),
as: :actor_counts
)
end
def with_joined_actors(queryable \\ all()) do
join(queryable, :left, [memberships: memberships], actors in ^Domain.Actors.Actor.Query.all(),
on: actors.id == memberships.actor_id,
as: :actors
)
end
def lock(queryable \\ all()) do
lock(queryable, "FOR UPDATE")
end

View File

@@ -0,0 +1,82 @@
defmodule Domain.Actors.Group.Sync do
alias Domain.Auth
alias Domain.Actors.Group
def sync_provider_groups_multi(%Auth.Provider{} = provider, attrs_list) do
now = DateTime.utc_now()
attrs_by_provider_identifier =
for attrs <- attrs_list, into: %{} do
{Map.fetch!(attrs, "provider_identifier"), attrs}
end
Ecto.Multi.new()
|> Ecto.Multi.all(:groups, fn _effects_so_far ->
fetch_and_lock_provider_groups_query(provider)
end)
|> Ecto.Multi.run(:plan_groups, fn _repo, %{groups: groups} ->
plan_groups_update(groups, attrs_by_provider_identifier)
end)
|> Ecto.Multi.update_all(
:delete_groups,
fn %{plan_groups: {_upsert, delete}} ->
delete_groups_query(provider, delete)
end,
set: [deleted_at: now]
)
|> Ecto.Multi.run(:upsert_groups, fn repo, %{plan_groups: {upsert, _delete}} ->
upsert_groups(repo, provider, attrs_by_provider_identifier, upsert)
end)
end
defp fetch_and_lock_provider_groups_query(provider) do
Group.Query.by_account_id(provider.account_id)
|> Group.Query.by_provider_id(provider.id)
|> Group.Query.lock()
end
defp plan_groups_update(groups, attrs_by_provider_identifier) do
{update, delete} =
Enum.reduce(groups, {[], []}, fn group, {update, delete} ->
if Map.has_key?(attrs_by_provider_identifier, group.provider_identifier) do
{[group.provider_identifier] ++ update, delete}
else
{update, [group.provider_identifier] ++ delete}
end
end)
insert = Map.keys(attrs_by_provider_identifier) -- (update ++ delete)
{:ok, {update ++ insert, delete}}
end
defp delete_groups_query(provider, provider_identifiers_to_delete) do
Group.Query.by_account_id(provider.account_id)
|> Group.Query.by_provider_id(provider.id)
|> Group.Query.by_provider_identifier({:in, provider_identifiers_to_delete})
end
defp upsert_groups(repo, provider, attrs_by_provider_identifier, provider_identifiers_to_upsert) do
provider_identifiers_to_upsert
|> Enum.reduce_while({:ok, []}, fn provider_identifier, {:ok, acc} ->
attrs = Map.get(attrs_by_provider_identifier, provider_identifier)
case upsert_group(repo, provider, attrs) do
{:ok, group} ->
{:cont, {:ok, [group | acc]}}
{:error, changeset} ->
{:halt, {:error, changeset}}
end
end)
end
defp upsert_group(repo, provider, attrs) do
Group.Changeset.create(provider, attrs)
|> repo.insert(
conflict_target: Group.Changeset.upsert_conflict_target(),
on_conflict: Group.Changeset.upsert_on_conflict(),
returning: true
)
end
end

View File

@@ -3,8 +3,8 @@ defmodule Domain.Actors.Membership do
@primary_key false
schema "actor_group_memberships" do
belongs_to :group, Domain.Actors.Group, primary_key: true
belongs_to :actor, Domain.Actors.Actor, primary_key: true
belongs_to :group, Domain.Actors.Group, primary_key: true, where: [deleted_at: nil]
belongs_to :actor, Domain.Actors.Actor, primary_key: true, where: [deleted_at: nil]
belongs_to :account, Domain.Accounts.Account
end

View File

@@ -1,15 +1,13 @@
defmodule Domain.Actors.Membership.Changeset do
use Domain, :changeset
def group_changeset(account_id, connection, attrs) do
connection
|> cast(attrs, ~w[actor_id]a)
|> validate_required(~w[actor_id]a)
|> changeset(account_id)
end
def upsert_conflict_target, do: [:group_id, :actor_id]
def upsert_on_conflict, do: :nothing
defp changeset(changeset, account_id) do
changeset
def changeset(account_id, membership, attrs) do
membership
|> cast(attrs, ~w[actor_id group_id]a)
|> validate_required_one_of(~w[actor_id group_id]a)
|> assoc_constraint(:actor)
|> assoc_constraint(:group)
|> assoc_constraint(:account)

View File

@@ -1,19 +1,82 @@
defmodule Domain.Actors.Membership.Query do
use Domain, :query
alias Domain.Actors.{Actor, Group, Membership}
def all do
from(memberships in Domain.Actors.Membership, as: :memberships)
from(memberships in Membership, as: :memberships)
end
def by_actor_id(queryable \\ all(), actor_id) do
where(queryable, [memberships: memberships], memberships.actor_id == ^actor_id)
end
def by_group_id(queryable \\ all(), group_id) do
def by_group_id(queryable \\ all(), group_id)
def by_group_id(queryable, {:in, group_ids}) do
where(queryable, [memberships: memberships], memberships.group_id in ^group_ids)
end
def by_group_id(queryable, group_id) do
where(queryable, [memberships: memberships], memberships.group_id == ^group_id)
end
def by_group_id_and_actor_id(queryable \\ all(), {:in, tuples}) do
queryable = where(queryable, [], false)
Enum.reduce(tuples, queryable, fn {group_id, actor_id}, queryable ->
or_where(
queryable,
[memberships: memberships],
memberships.group_id == ^group_id and memberships.actor_id == ^actor_id
)
end)
end
def by_account_id(queryable \\ all(), account_id) do
where(queryable, [memberships: memberships], memberships.account_id == ^account_id)
end
def by_group_provider_id(queryable \\ all(), provider_id) do
queryable
|> with_joined_groups()
|> where([groups: groups], groups.provider_id == ^provider_id)
end
def count_actors_by_group_id(queryable \\ all()) do
queryable
|> group_by([memberships: memberships], memberships.group_id)
|> with_joined_actors()
|> select([memberships: memberships, actors: actors], %{
group_id: memberships.group_id,
count: count(actors.id)
})
end
def count_groups_by_actor_id(queryable \\ all()) do
queryable
|> group_by([memberships: memberships], memberships.actor_id)
|> with_joined_groups()
|> select([memberships: memberships, groups: groups], %{
actor_id: memberships.actor_id,
count: count(groups.id)
})
end
def with_joined_actors(queryable \\ all()) do
join(queryable, :inner, [memberships: memberships], actors in ^Actor.Query.all(),
on: actors.id == memberships.actor_id,
as: :actors
)
end
def with_joined_groups(queryable \\ all()) do
join(queryable, :inner, [memberships: memberships], groups in ^Group.Query.all(),
on: groups.id == memberships.group_id,
as: :groups
)
end
def lock(queryable \\ all()) do
lock(queryable, "FOR UPDATE")
end
end

View File

@@ -0,0 +1,113 @@
defmodule Domain.Actors.Membership.Sync do
alias Domain.Auth
alias Domain.Actors.Membership
def sync_provider_memberships_multi(multi, %Auth.Provider{} = provider, tuples) do
multi
|> Ecto.Multi.all(:memberships, fn _effects_so_far ->
fetch_and_lock_provider_memberships_query(provider)
end)
|> Ecto.Multi.run(
:plan_memberships,
fn _repo,
%{
identities: identities,
insert_identities: insert_identities,
groups: groups,
upsert_groups: upsert_groups,
memberships: memberships
} ->
plan_memberships_update(
tuples,
identities,
insert_identities,
groups,
upsert_groups,
memberships
)
end
)
|> Ecto.Multi.delete_all(:delete_memberships, fn %{plan_memberships: {_upsert, delete}} ->
delete_memberships_query(delete)
end)
|> Ecto.Multi.run(:upsert_memberships, fn repo, %{plan_memberships: {upsert, _delete}} ->
upsert_memberships(repo, provider, upsert)
end)
end
defp fetch_and_lock_provider_memberships_query(provider) do
Membership.Query.by_account_id(provider.account_id)
|> Membership.Query.by_group_provider_id(provider.id)
|> Membership.Query.lock()
end
defp plan_memberships_update(
tuples,
identities,
insert_identities,
groups,
upsert_groups,
memberships
) do
identity_by_provider_identifier =
for identity <- identities ++ insert_identities, into: %{} do
{identity.provider_identifier, identity}
end
group_by_provider_identifier =
for group <- groups ++ upsert_groups, into: %{} do
{group.provider_identifier, group}
end
tuples =
Enum.map(tuples, fn {group_provider_identifier, actor_provider_identifier} ->
{Map.fetch!(group_by_provider_identifier, group_provider_identifier).id,
Map.fetch!(identity_by_provider_identifier, actor_provider_identifier).actor_id}
end)
{upsert, delete} =
Enum.reduce(
memberships,
{tuples, []},
fn membership, {upsert, delete} ->
tuple = {membership.group_id, membership.actor_id}
if tuple in tuples do
{upsert -- [tuple], delete}
else
{upsert -- [tuple], [{membership.group_id, membership.actor_id}] ++ delete}
end
end
)
{:ok, {upsert, delete}}
end
defp delete_memberships_query(provider_identifiers_to_delete) do
Membership.Query.by_group_id_and_actor_id({:in, provider_identifiers_to_delete})
end
defp upsert_memberships(repo, provider, provider_identifiers_to_upsert) do
provider_identifiers_to_upsert
|> Enum.reduce_while({:ok, []}, fn {group_id, actor_id}, {:ok, acc} ->
attrs = %{group_id: group_id, actor_id: actor_id}
case upsert_membership(repo, provider, attrs) do
{:ok, membership} ->
{:cont, {:ok, [membership | acc]}}
{:error, changeset} ->
{:halt, {:error, changeset}}
end
end)
end
defp upsert_membership(repo, provider, attrs) do
Membership.Changeset.changeset(provider.account_id, %Membership{}, attrs)
|> repo.insert(
conflict_target: Membership.Changeset.upsert_conflict_target(),
on_conflict: Membership.Changeset.upsert_on_conflict(),
returning: true
)
end
end

View File

@@ -10,6 +10,8 @@ defmodule Domain.Auth do
account_user: 24 * 7
}
@max_session_duration_hours @default_session_duration_hours
def start_link(opts) do
Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
end
@@ -49,6 +51,28 @@ defmodule Domain.Auth do
end
end
@doc """
This functions allows to fetch singleton providers like `email` or `token`.
"""
def fetch_active_provider_by_adapter(adapter, %Subject{} = subject, opts \\ [])
when adapter in [:email, :token, :userpass] do
with :ok <- ensure_has_permissions(subject, Authorizer.manage_providers_permission()) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Provider.Query.by_adapter(adapter)
|> Provider.Query.not_disabled()
|> Authorizer.for_subject(Provider, subject)
|> Repo.fetch()
|> case do
{:ok, provider} ->
{:ok, Repo.preload(provider, preload)}
{:error, reason} ->
{:error, reason}
end
end
end
def fetch_provider_by_id(id) do
if Validator.valid_uuid?(id) do
Provider.Query.by_id(id)
@@ -104,8 +128,28 @@ defmodule Domain.Auth do
|> Repo.list()
end
def list_providers_pending_token_refresh_by_adapter(adapter) do
datetime_filter = DateTime.utc_now() |> DateTime.add(1, :hour)
Provider.Query.by_adapter(adapter)
|> Provider.Query.by_provisioner(:custom)
|> Provider.Query.token_expires_at({:lt, datetime_filter})
|> Provider.Query.not_disabled()
|> Repo.list()
end
def list_providers_pending_sync_by_adapter(adapter) do
datetime_filter = DateTime.utc_now() |> DateTime.add(-10, :minute)
Provider.Query.by_adapter(adapter)
|> Provider.Query.by_provisioner(:custom)
|> Provider.Query.last_synced_at({:lt, datetime_filter})
|> Provider.Query.not_disabled()
|> Repo.list()
end
def new_provider(%Accounts.Account{} = account, attrs \\ %{}) do
Provider.Changeset.create_changeset(account, attrs)
Provider.Changeset.create(account, attrs)
|> Adapters.provider_changeset()
end
@@ -113,7 +157,7 @@ defmodule Domain.Auth do
with :ok <- ensure_has_permissions(subject, Authorizer.manage_providers_permission()),
:ok <- Accounts.ensure_has_access_to(subject, account),
changeset =
Provider.Changeset.create_changeset(account, attrs, subject)
Provider.Changeset.create(account, attrs, subject)
|> Adapters.provider_changeset(),
{:ok, provider} <- Repo.insert(changeset) do
Adapters.ensure_provisioned(provider)
@@ -122,7 +166,7 @@ defmodule Domain.Auth do
def create_provider(%Accounts.Account{} = account, attrs) do
changeset =
Provider.Changeset.create_changeset(account, attrs)
Provider.Changeset.create(account, attrs)
|> Adapters.provider_changeset()
with {:ok, provider} <- Repo.insert(changeset) do
@@ -131,7 +175,7 @@ defmodule Domain.Auth do
end
def change_provider(%Provider{} = provider, attrs \\ %{}) do
Provider.Changeset.update_changeset(provider, attrs)
Provider.Changeset.update(provider, attrs)
|> Adapters.provider_changeset()
end
@@ -141,7 +185,7 @@ defmodule Domain.Auth do
|> Authorizer.for_subject(Provider, subject)
|> Repo.fetch_and_update(
with: fn provider ->
Provider.Changeset.update_changeset(provider, attrs)
Provider.Changeset.update(provider, attrs)
|> Adapters.provider_changeset()
end
)
@@ -198,6 +242,7 @@ defmodule Domain.Auth do
defp other_active_providers_exist?(%Provider{id: id, account_id: account_id}) do
Provider.Query.by_id({:not, id})
|> Provider.Query.by_adapter({:not_in, [:token]})
|> Provider.Query.not_disabled()
|> Provider.Query.by_account_id(account_id)
|> Provider.Query.lock()
@@ -210,6 +255,20 @@ defmodule Domain.Auth do
# Identities
def fetch_identity_by_id(id, %Subject{} = subject) do
with :ok <- ensure_has_permissions(subject, Authorizer.manage_identities_permission()) do
Identity.Query.by_id(id)
|> Authorizer.for_subject(Identity, subject)
|> Repo.fetch()
end
end
def fetch_active_identity_by_id(id) do
Identity.Query.by_id(id)
|> Identity.Query.not_disabled()
|> Repo.fetch()
end
def fetch_identity_by_id(id) do
Identity.Query.by_id(id)
|> Repo.fetch()
@@ -236,14 +295,13 @@ defmodule Domain.Auth do
end
end
def upsert_identity(
%Actors.Actor{} = actor,
%Provider{} = provider,
provider_identifier,
provider_attrs \\ %{}
) do
Identity.Changeset.create_identity(actor, provider, provider_identifier)
|> Adapters.identity_changeset(provider, provider_attrs)
def sync_provider_identities_multi(%Provider{} = provider, attrs_list) do
Identity.Sync.sync_provider_identities_multi(provider, attrs_list)
end
def upsert_identity(%Actors.Actor{} = actor, %Provider{} = provider, attrs) do
Identity.Changeset.create_identity(actor, provider, attrs)
|> Adapters.identity_changeset(provider)
|> Repo.insert(
conflict_target:
{:unsafe_fragment,
@@ -260,23 +318,29 @@ defmodule Domain.Auth do
)
end
def new_identity(%Actors.Actor{} = actor, %Provider{} = provider, attrs \\ %{}) do
Identity.Changeset.create_identity(actor, provider, attrs)
|> Adapters.identity_changeset(provider)
end
def create_identity(
%Actors.Actor{} = actor,
%Provider{} = provider,
provider_identifier,
provider_attrs \\ %{}
attrs,
%Subject{} = subject
) do
Identity.Changeset.create_identity(actor, provider, provider_identifier)
|> Adapters.identity_changeset(provider, provider_attrs)
with :ok <- ensure_has_permissions(subject, Authorizer.manage_identities_permission()) do
create_identity(actor, provider, attrs)
end
end
def create_identity(%Actors.Actor{} = actor, %Provider{} = provider, attrs) do
Identity.Changeset.create_identity(actor, provider, attrs)
|> Adapters.identity_changeset(provider)
|> Repo.insert()
end
def replace_identity(
%Identity{} = identity,
provider_identifier,
provider_attrs \\ %{},
%Subject{} = subject
) do
def replace_identity(%Identity{} = identity, attrs, %Subject{} = subject) do
required_permissions =
{:one_of,
[
@@ -294,13 +358,8 @@ defmodule Domain.Auth do
|> Repo.fetch()
end)
|> Ecto.Multi.insert(:new_identity, fn %{identity: identity} ->
Identity.Changeset.create_identity(
identity.actor,
identity.provider,
provider_identifier,
subject
)
|> Adapters.identity_changeset(identity.provider, provider_attrs)
Identity.Changeset.create_identity(identity.actor, identity.provider, attrs, subject)
|> Adapters.identity_changeset(identity.provider)
end)
|> Ecto.Multi.update(:deleted_identity, fn %{identity: identity} ->
Identity.Changeset.delete_identity(identity)
@@ -316,6 +375,10 @@ defmodule Domain.Auth do
end
end
def delete_identity(%Identity{created_by: :provider}, %Subject{}) do
{:error, :cant_delete_synced_identity}
end
def delete_identity(%Identity{} = identity, %Subject{} = subject) do
required_permissions =
{:one_of,
@@ -331,11 +394,26 @@ defmodule Domain.Auth do
end
end
def delete_actor_identities(%Actors.Actor{} = actor) do
{_count, nil} =
Identity.Query.by_actor_id(actor.id)
|> Repo.update_all(set: [deleted_at: DateTime.utc_now(), provider_state: %{}])
:ok
end
def identity_disabled?(%{disabled_at: nil}), do: false
def identity_disabled?(_identity), do: true
def identity_deleted?(%{deleted_at: nil}), do: false
def identity_deleted?(_identity), do: true
# Sign Up / In / Off
def sign_in(%Provider{} = provider, id_or_provider_identifier, secret, user_agent, remote_ip) do
identity_queryable =
Identity.Query.by_provider_id(provider.id)
Identity.Query.not_disabled()
|> Identity.Query.by_provider_id(provider.id)
|> Identity.Query.by_id_or_provider_identifier(id_or_provider_identifier)
with {:ok, identity} <- Repo.fetch(identity_queryable),
@@ -359,8 +437,7 @@ defmodule Domain.Auth do
end
def sign_in(token, user_agent, remote_ip) when is_binary(token) do
with {:ok, identity, expires_at} <-
verify_token(token, user_agent, remote_ip) do
with {:ok, identity, expires_at} <- verify_token(token, user_agent, remote_ip) do
{:ok, build_subject(identity, expires_at, user_agent, remote_ip)}
else
{:error, :not_found} -> {:error, :unauthorized}
@@ -398,8 +475,15 @@ defmodule Domain.Auth do
end
defp build_subject_expires_at(%Actors.Actor{} = actor, expires_at) do
now = DateTime.utc_now()
default_session_duration_hours = Map.fetch!(@default_session_duration_hours, actor.type)
expires_at || DateTime.utc_now() |> DateTime.add(default_session_duration_hours, :hour)
expires_at = expires_at || DateTime.add(now, default_session_duration_hours, :hour)
max_session_duration_hours = Map.fetch!(@max_session_duration_hours, actor.type)
max_expires_at = DateTime.add(now, max_session_duration_hours, :hour)
Enum.min([expires_at, max_expires_at], DateTime)
end
# Session
@@ -482,7 +566,7 @@ defmodule Domain.Auth do
_user_agent,
_remote_ip
) do
with {:ok, identity} <- fetch_identity_by_id(identity_id),
with {:ok, identity} <- fetch_active_identity_by_id(identity_id),
{:ok, provider} <- fetch_active_provider_by_id(identity.provider_id),
{:ok, identity, expires_at} <-
Adapters.verify_secret(provider, identity, secret) do
@@ -500,7 +584,7 @@ defmodule Domain.Auth do
_user_agent,
_remote_ip
) do
with {:ok, identity} <- fetch_identity_by_id(identity_id),
with {:ok, identity} <- fetch_active_identity_by_id(identity_id),
{:ok, expires_at} <- fetch_session_token_expires_at(token) do
{:ok, identity, expires_at}
end
@@ -512,7 +596,7 @@ defmodule Domain.Auth do
user_agent,
remote_ip
) do
with {:ok, identity} <- fetch_identity_by_id(identity_id),
with {:ok, identity} <- fetch_active_identity_by_id(identity_id),
true <- context_payload == session_context_payload(remote_ip, user_agent),
{:ok, expires_at} <- fetch_session_token_expires_at(token) do
{:ok, identity, expires_at}
@@ -531,7 +615,7 @@ defmodule Domain.Auth do
end
defp access_token_payload(%Identity{} = identity) do
{:identity, identity.id, identity.provider_virtual_state.secret, :ignore}
{:identity, identity.id, identity.provider_virtual_state.changes.secret, :ignore}
end
defp fetch_config! do
@@ -588,4 +672,9 @@ defmodule Domain.Auth do
missing_permissions -> {:error, {:unauthorized, missing_permissions: missing_permissions}}
end
end
def can_grant_role?(%Subject{} = subject, granted_role) do
granted_permissions = fetch_type_permissions!(granted_role)
MapSet.subset?(granted_permissions, subject.permissions)
end
end

View File

@@ -36,9 +36,8 @@ defmodule Domain.Auth.Adapters do
fetch_adapter!(adapter).capabilities()
end
def identity_changeset(%Ecto.Changeset{} = changeset, %Provider{} = provider, provider_attrs) do
def identity_changeset(%Ecto.Changeset{} = changeset, %Provider{} = provider) do
adapter = fetch_provider_adapter!(provider)
changeset = Ecto.Changeset.put_change(changeset, :provider_virtual_state, provider_attrs)
%Ecto.Changeset{} = adapter.identity_changeset(provider, changeset)
end

View File

@@ -72,4 +72,8 @@ defmodule Domain.Auth.Adapters.GoogleWorkspace do
def verify_and_upsert_identity(%Actors.Actor{} = actor, %Provider{} = provider, payload) do
OpenIDConnect.verify_and_upsert_identity(actor, provider, payload)
end
def refresh_access_token(%Provider{} = provider) do
OpenIDConnect.refresh_access_token(provider)
end
end

View File

@@ -1,9 +1,101 @@
defmodule Domain.Auth.Adapters.GoogleWorkspace.Jobs do
use Domain.Jobs.Recurrent, otp_app: :domain
alias Domain.{Auth, Actors}
alias Domain.Auth.Adapters.GoogleWorkspace
require Logger
every minutes(5), :refresh_access_tokens do
Logger.debug("Refreshing tokens")
:ok
with {:ok, providers} <-
Domain.Auth.list_providers_pending_token_refresh_by_adapter(:google_workspace) do
Enum.each(providers, fn provider ->
Logger.debug("Refreshing tokens for #{inspect(provider)}")
GoogleWorkspace.refresh_access_token(provider)
end)
end
end
every minutes(3), :sync_directory do
with {:ok, providers} <- Domain.Auth.list_providers_pending_sync_by_adapter(:google_workspace) do
Logger.debug("Syncing #{length(providers)} providers")
providers
|> Enum.chunk_every(5)
|> Enum.each(fn providers ->
Enum.map(providers, fn provider ->
Logger.debug("Syncing provider", provider_id: provider.id)
access_token = provider.adapter_state[:access_token]
with {:ok, users} <- GoogleWorkspace.APIClient.list_users(access_token),
{:ok, organization_units} <-
GoogleWorkspace.APIClient.list_organization_units(access_token),
{:ok, groups} <- GoogleWorkspace.APIClient.list_groups(access_token),
{:ok, tuples} <-
list_membership_tuples(access_token, groups) do
identities_attrs =
Enum.map(users, fn user ->
%{
"provider_identifier" => user["id"],
"actor" => %{
"type" => :account_user,
"name" => user["name"]["fullName"]
}
}
end)
actor_groups_attrs =
Enum.map(groups, fn group ->
%{
"name" => "Group:" <> group["name"],
"provider_identifier" => "G:" <> group["id"]
}
end) ++
Enum.map(organization_units, fn organization_unit ->
%{
"name" => "OrgUnit:" <> organization_unit["name"],
"provider_identifier" => "OU:" <> organization_unit["orgUnitId"]
}
end)
tuples =
Enum.flat_map(users, fn user ->
organization_unit =
Enum.find(organization_units, fn organization_unit ->
organization_unit["orgUnitPath"] == user["orgUnitPath"]
end)
[{"OU:" <> organization_unit["orgUnitId"], user["id"]}]
end) ++ tuples
Ecto.Multi.new()
|> Ecto.Multi.append(Auth.sync_provider_identities_multi(provider, identities_attrs))
|> Ecto.Multi.append(Actors.sync_provider_groups_multi(provider, actor_groups_attrs))
|> Actors.sync_provider_memberships_multi(provider, tuples)
|> Domain.Repo.transaction()
Logger.debug("Finished syncing provider", provider_id: provider.id)
else
{:error, reason} ->
Logger.error("Failed syncing provider",
provider_id: provider.id,
reason: inspect(reason)
)
end
end)
end)
end
end
defp list_membership_tuples(access_token, groups) do
Enum.reduce_while(groups, {:ok, []}, fn group, {:ok, tuples} ->
case GoogleWorkspace.APIClient.list_group_members(access_token, group["id"]) do
{:ok, members} ->
tuples = Enum.map(members, &{"G:" <> group["id"], &1["id"]}) ++ tuples
{:cont, {:ok, tuples}}
{:error, reason} ->
{:halt, {:error, reason}}
end
end)
end
end

View File

@@ -87,16 +87,28 @@ defmodule Domain.Auth.Adapters.OpenIDConnect do
@impl true
def verify_and_update_identity(%Provider{} = provider, {redirect_uri, code_verifier, code}) do
provider
|> sync_identity(%{
token_params = %{
grant_type: "authorization_code",
redirect_uri: redirect_uri,
code: code,
code_verifier: code_verifier
})
|> case do
{:ok, identity, expires_at} -> {:ok, identity, expires_at}
{:error, :not_found} -> {:error, :not_found}
}
with {:ok, provider_identifier, identity_state} <-
fetch_state(provider, token_params) do
Identity.Query.not_disabled()
|> Identity.Query.by_provider_id(provider.id)
|> Identity.Query.by_provider_identifier(provider_identifier)
|> Repo.fetch_and_update(
with: fn identity ->
Identity.Changeset.update_identity_provider_state(identity, identity_state)
end
)
|> case do
{:ok, identity} -> {:ok, identity, identity_state.expires_at}
{:error, reason} -> {:error, reason}
end
else
{:error, :expired_token} -> {:error, :expired}
{:error, :invalid_token} -> {:error, :invalid}
{:error, :internal_error} -> {:error, :internal_error}
@@ -116,31 +128,41 @@ defmodule Domain.Auth.Adapters.OpenIDConnect do
}
with {:ok, provider_identifier, identity_state} <-
fetch_identity_state(provider, token_params) do
Domain.Auth.upsert_identity(actor, provider, provider_identifier, identity_state)
fetch_state(provider, token_params) do
Domain.Auth.upsert_identity(actor, provider, %{
provider_identifier: provider_identifier,
provider_virtual_state: identity_state
})
end
end
def refresh_token(%Identity{} = identity) do
identity = Repo.preload(identity, :provider)
sync_identity(identity.provider, %{
def refresh_access_token(%Provider{} = provider) do
token_params = %{
grant_type: "refresh_token",
refresh_token: identity.provider_state["refresh_token"]
})
refresh_token: provider.adapter_state["refresh_token"]
}
with {:ok, _provider_identifier, adapter_state} <-
fetch_state(provider, token_params) do
Provider.Query.by_id(provider.id)
|> Repo.fetch_and_update(
with: fn provider ->
Provider.Changeset.update(provider, %{adapter_state: adapter_state})
end
)
else
{:error, :expired_token} -> {:error, :expired}
{:error, :invalid_token} -> {:error, :invalid}
{:error, :internal_error} -> {:error, :internal_error}
end
end
defp fetch_identity_state(%Provider{} = provider, token_params) do
defp fetch_state(%Provider{} = provider, token_params) do
config = config_for_provider(provider)
with {:ok, tokens} <- OpenIDConnect.fetch_tokens(config, token_params),
{:ok, claims} <- OpenIDConnect.verify(config, tokens["id_token"]),
{:ok, userinfo} <- OpenIDConnect.fetch_userinfo(config, tokens["access_token"]) do
# TODO: sync groups
# TODO: refresh the access token so it doesn't expire
# TODO: first admin user token that configured provider should used for periodic syncs
# TODO: active status for relays, gateways in list functions
# TODO: JIT provisioning
expires_at =
cond do
not is_nil(tokens["expires_in"]) ->
@@ -180,23 +202,6 @@ defmodule Domain.Auth.Adapters.OpenIDConnect do
end
end
defp sync_identity(%Provider{} = provider, token_params) do
with {:ok, provider_identifier, identity_state} <-
fetch_identity_state(provider, token_params) do
Identity.Query.by_provider_id(provider.id)
|> Identity.Query.by_provider_identifier(provider_identifier)
|> Repo.fetch_and_update(
with: fn identity ->
Identity.Changeset.update_identity_provider_state(identity, identity_state)
end
)
|> case do
{:ok, identity} -> {:ok, identity, identity_state.expires_at}
{:error, reason} -> {:error, reason}
end
end
end
defp config_for_provider(%Provider{} = provider) do
Ecto.embedded_load(Settings, provider.adapter_config, :json)
|> Map.from_struct()

View File

@@ -1,16 +1,11 @@
defmodule Domain.Auth.Adapters.OpenIDConnect.Settings.Changeset do
use Domain, :changeset
alias Domain.Auth.Adapters.OpenIDConnect.Settings
@fields ~w[scope
response_type
client_id client_secret
discovery_document_uri]a
def create_changeset(attrs) do
changeset(%Settings{}, attrs)
end
def changeset(struct, attrs) do
struct
|> cast(attrs, @fields)

View File

@@ -60,15 +60,17 @@ defmodule Domain.Auth.Adapters.Token do
%{valid?: true} = nested_changeset ->
expires_at = Ecto.Changeset.fetch_change!(nested_changeset, :expires_at)
nested_changeset = Ecto.Changeset.put_change(nested_changeset, :secret, secret)
{changeset, _original_type} =
changeset
|> Ecto.Changeset.put_change(:provider_state, %{
"expires_at" => DateTime.to_iso8601(expires_at),
"secret_hash" => secret_hash
})
|> Domain.Changeset.inject_embedded_changeset(:provider_virtual_state, nested_changeset)
changeset
|> Ecto.Changeset.put_change(:provider_state, %{
"expires_at" => DateTime.to_iso8601(expires_at),
"secret_hash" => secret_hash
})
|> Ecto.Changeset.put_change(:provider_virtual_state, %{
secret: secret
})
end
end

View File

@@ -3,6 +3,7 @@ defmodule Domain.Auth.Adapters.Token.State do
@primary_key false
embedded_schema do
field :secret, :string, virtual: true, redact: true
field :secret_hash, :string, redact: true
field :expires_at, :utc_datetime_usec
end

View File

@@ -1,13 +1,8 @@
defmodule Domain.Auth.Adapters.Token.State.Changeset do
use Domain, :changeset
alias Domain.Auth.Adapters.Token.State
@fields ~w[expires_at]a
def create_changeset(attrs) do
changeset(%State{}, attrs)
end
def changeset(struct, attrs) do
struct
|> cast(attrs, @fields)

View File

@@ -56,11 +56,19 @@ defmodule Domain.Auth.Adapters.UserPass do
changeset
%{valid?: true} = nested_changeset ->
nested_changeset =
nested_changeset
|> Domain.Validator.redact_field(:password)
|> Domain.Validator.redact_field(:password_confirmation)
password_hash = Ecto.Changeset.fetch_change!(nested_changeset, :password_hash)
{changeset, _original_type} =
changeset
|> Ecto.Changeset.put_change(:provider_state, %{"password_hash" => password_hash})
|> Domain.Changeset.inject_embedded_changeset(:provider_virtual_state, nested_changeset)
changeset
|> Ecto.Changeset.put_change(:provider_state, %{"password_hash" => password_hash})
|> Ecto.Changeset.put_change(:provider_virtual_state, %{})
end
end

View File

@@ -2,15 +2,11 @@ defmodule Domain.Auth.Adapters.UserPass.Password.Changeset do
use Domain, :changeset
alias Domain.Auth.Adapters.UserPass.Password
@fields ~w[password]a
@fields ~w[password password_confirmation]a
@min_password_length 12
@max_password_length 72
def create_changeset(attrs) do
changeset(%Password{}, attrs)
end
def changeset(struct, attrs) do
def changeset(%Password{} = struct, attrs) do
struct
|> cast(attrs, @fields)
|> validate_required(@fields)
@@ -28,8 +24,6 @@ defmodule Domain.Auth.Adapters.UserPass.Password.Changeset do
# |> validate_no_sequential_characters(:password)
# |> validate_no_public_context(:password)
|> put_hash(:password, to: :password_hash)
|> redact_field(:password)
|> redact_field(:password_confirmation)
|> validate_required([:password_hash])
end
end

View File

@@ -40,6 +40,7 @@ defmodule Domain.Auth.Authorizer do
def list_permissions_for_role(:account_admin_user) do
[
manage_providers_permission(),
manage_own_identities_permission(),
manage_identities_permission()
]
end

View File

@@ -18,6 +18,9 @@ defmodule Domain.Auth.Identity do
field :created_by, Ecto.Enum, values: ~w[system provider identity]a
belongs_to :created_by_identity, Domain.Auth.Identity
has_many :devices, Domain.Devices.Device, where: [deleted_at: nil]
field :deleted_at, :utc_datetime_usec
timestamps(updated_at: false)
end
end

View File

@@ -3,9 +3,14 @@ defmodule Domain.Auth.Identity.Changeset do
alias Domain.Actors
alias Domain.Auth.{Subject, Identity, Provider}
def create_identity(actor, provider, provider_identifier, %Subject{} = subject) do
def create_identity(
%Actors.Actor{} = actor,
%Provider{} = provider,
attrs,
%Subject{} = subject
) do
actor
|> create_identity(provider, provider_identifier)
|> create_identity(provider, attrs)
|> put_change(:created_by, :identity)
|> put_change(:created_by_identity_id, subject.identity.id)
end
@@ -13,19 +18,42 @@ defmodule Domain.Auth.Identity.Changeset do
def create_identity(
%Actors.Actor{account_id: account_id} = actor,
%Provider{account_id: account_id} = provider,
provider_identifier
attrs
) do
%Identity{}
|> change()
|> cast(attrs, ~w[provider_identifier provider_virtual_state]a)
|> validate_required(~w[provider_identifier]a)
|> put_change(:actor_id, actor.id)
|> put_change(:provider_id, provider.id)
|> put_change(:account_id, account_id)
|> put_change(:provider_identifier, provider_identifier)
|> put_change(:created_by, :system)
|> changeset()
end
def create_identity_and_actor(
%Provider{account_id: account_id} = provider,
attrs
) do
%Identity{}
|> cast(attrs, ~w[provider_identifier provider_virtual_state]a)
|> validate_required(~w[provider_identifier]a)
|> cast_assoc(:actor,
with: fn _actor, attrs ->
Actors.Actor.Changeset.create(account_id, attrs)
|> put_change(:last_synced_at, DateTime.utc_now())
end
)
|> put_change(:provider_id, provider.id)
|> put_change(:account_id, account_id)
|> put_change(:created_by, :provider)
|> changeset()
end
def changeset(changeset) do
changeset
|> unique_constraint(:provider_identifier,
name: :auth_identities_account_id_provider_id_provider_identifier_idx
)
|> validate_required(:provider_identifier)
|> put_change(:created_by, :system)
end
def update_identity_provider_state(identity_or_changeset, %{} = state, virtual_state \\ %{}) do

View File

@@ -4,9 +4,6 @@ defmodule Domain.Auth.Identity.Query do
def all do
from(identities in Domain.Auth.Identity, as: :identities)
|> where([identities: identities], is_nil(identities.deleted_at))
|> join(:inner, [identities: identities], actors in assoc(identities, :actor), as: :actors)
|> where([actors: actors], is_nil(actors.deleted_at))
|> where([actors: actors], is_nil(actors.disabled_at))
end
def by_id(queryable \\ all(), id)
@@ -38,7 +35,17 @@ defmodule Domain.Auth.Identity.Query do
where(queryable, [identities: identities], identities.adapter == ^adapter)
end
def by_provider_identifier(queryable \\ all(), provider_identifier) do
def by_provider_identifier(queryable \\ all(), provider_identifier)
def by_provider_identifier(queryable, {:in, provider_identifiers}) do
where(
queryable,
[identities: identities],
identities.provider_identifier in ^provider_identifiers
)
end
def by_provider_identifier(queryable, provider_identifier) do
where(
queryable,
[identities: identities],
@@ -59,6 +66,13 @@ defmodule Domain.Auth.Identity.Query do
end
end
def not_disabled(queryable \\ all()) do
queryable
|> join(:inner, [identities: identities], actors in assoc(identities, :actor), as: :actors)
|> where([actors: actors], is_nil(actors.deleted_at))
|> where([actors: actors], is_nil(actors.disabled_at))
end
def lock(queryable \\ all()) do
lock(queryable, "FOR UPDATE")
end

View File

@@ -0,0 +1,78 @@
defmodule Domain.Auth.Identity.Sync do
alias Domain.Auth.{Identity, Provider}
def sync_provider_identities_multi(%Provider{} = provider, attrs_list) do
now = DateTime.utc_now()
attrs_by_provider_identifier =
for attrs <- attrs_list, into: %{} do
{Map.fetch!(attrs, "provider_identifier"), attrs}
end
provider_identifiers = Map.keys(attrs_by_provider_identifier)
Ecto.Multi.new()
|> Ecto.Multi.all(:identities, fn _effects_so_far ->
fetch_and_lock_provider_identities_query(provider)
end)
|> Ecto.Multi.run(:plan_identities, fn _repo, %{identities: identities} ->
plan_identities_update(identities, provider_identifiers)
end)
|> Ecto.Multi.update_all(
:delete_identities,
fn %{plan_identities: {_insert, delete}} ->
delete_identities_query(provider, delete)
end,
set: [deleted_at: now]
)
|> Ecto.Multi.run(:insert_identities, fn repo, %{plan_identities: {insert, _delete}} ->
upsert_identities(repo, provider, attrs_by_provider_identifier, insert)
end)
end
defp fetch_and_lock_provider_identities_query(provider) do
Identity.Query.by_account_id(provider.account_id)
|> Identity.Query.by_provider_id(provider.id)
|> Identity.Query.lock()
end
defp plan_identities_update(identities, provider_identifiers) do
{insert, delete} =
Enum.reduce(identities, {provider_identifiers, []}, fn identity, {insert, delete} ->
if identity.provider_identifier in provider_identifiers do
{insert -- [identity.provider_identifier], delete}
else
{insert -- [identity.provider_identifier], [identity.provider_identifier] ++ delete}
end
end)
{:ok, {insert, delete}}
end
defp delete_identities_query(provider, provider_identifiers_to_delete) do
Identity.Query.by_account_id(provider.account_id)
|> Identity.Query.by_provider_id(provider.id)
|> Identity.Query.by_provider_identifier({:in, provider_identifiers_to_delete})
end
defp upsert_identities(
repo,
provider,
attrs_by_provider_identifier,
provider_identifiers_to_insert
) do
provider_identifiers_to_insert
|> Enum.reduce_while({:ok, []}, fn provider_identifier, {:ok, acc} ->
attrs = Map.get(attrs_by_provider_identifier, provider_identifier)
changeset = Identity.Changeset.create_identity_and_actor(provider, attrs)
case repo.insert(changeset) do
{:ok, identity} ->
{:cont, {:ok, [identity | acc]}}
{:error, changeset} ->
{:halt, {:error, changeset}}
end
end)
end
end

View File

@@ -11,7 +11,8 @@ defmodule Domain.Auth.Provider do
belongs_to :account, Domain.Accounts.Account
has_many :groups, Domain.Actors.Group, where: [deleted_at: nil]
has_many :actor_groups, Domain.Actors.Group, where: [deleted_at: nil]
has_many :identities, Domain.Auth.Identity, where: [deleted_at: nil]
field :created_by, Ecto.Enum, values: ~w[system identity]a
belongs_to :created_by_identity, Domain.Auth.Identity

View File

@@ -7,14 +7,14 @@ defmodule Domain.Auth.Provider.Changeset do
@update_fields ~w[name adapter_config adapter_state provisioner disabled_at deleted_at]a
@required_fields ~w[name adapter adapter_config provisioner]a
def create_changeset(account, attrs, %Subject{} = subject) do
def create(account, attrs, %Subject{} = subject) do
account
|> create_changeset(attrs)
|> create(attrs)
|> put_change(:created_by, :identity)
|> put_change(:created_by_identity_id, subject.identity.id)
end
def create_changeset(%Accounts.Account{} = account, attrs) do
def create(%Accounts.Account{} = account, attrs) do
%Provider{}
|> cast(attrs, @create_fields)
|> put_change(:account_id, account.id)
@@ -22,7 +22,7 @@ defmodule Domain.Auth.Provider.Changeset do
|> put_change(:created_by, :system)
end
def update_changeset(%Provider{} = provider, attrs) do
def update(%Provider{} = provider, attrs) do
provider
|> cast(attrs, @update_fields)
|> changeset()

View File

@@ -16,6 +16,36 @@ defmodule Domain.Auth.Provider.Query do
where(queryable, [provider: provider], provider.id == ^id)
end
def by_adapter(queryable \\ all(), adapter)
def by_adapter(queryable, {:not_in, adapters}) do
where(queryable, [provider: provider], provider.adapter not in ^adapters)
end
def by_adapter(queryable, adapter) do
where(queryable, [provider: provider], provider.adapter == ^adapter)
end
def last_synced_at(queryable \\ all(), {:lt, datetime}) do
where(
queryable,
[provider: provider],
provider.last_synced_at < ^datetime or is_nil(provider.last_synced_at)
)
end
def token_expires_at(queryable \\ all(), {:lt, datetime}) do
where(
queryable,
[provider: provider],
fragment("(?->>'expires_at')::timestamp < ?", provider.adapter_state, ^datetime)
)
end
def by_provisioner(queryable \\ all(), provisioner) do
where(queryable, [provider: provider], provider.provisioner == ^provisioner)
end
def by_account_id(queryable \\ all(), account_id) do
where(queryable, [provider: provider], provider.account_id == ^account_id)
end

View File

@@ -27,8 +27,6 @@ defmodule Domain.Devices do
end
def fetch_device_by_id(id, %Auth.Subject{} = subject, opts \\ []) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
required_permissions =
{:one_of,
[
@@ -38,12 +36,22 @@ defmodule Domain.Devices do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
true <- Validator.valid_uuid?(id) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
Device.Query.by_id(id)
|> Authorizer.for_subject(subject)
|> Repo.fetch()
|> case do
{:ok, device} -> {:ok, Repo.preload(device, preload)}
{:error, reason} -> {:error, reason}
{:ok, device} ->
device =
device
|> Repo.preload(preload)
|> preload_online_status()
{:ok, device}
{:error, reason} ->
{:error, reason}
end
else
false -> {:error, :not_found}
@@ -57,6 +65,7 @@ defmodule Domain.Devices do
Device.Query.by_id(id)
|> Repo.one!()
|> Repo.preload(preload)
|> preload_online_status()
end
def list_devices(%Auth.Subject{} = subject, opts \\ []) do
@@ -75,6 +84,10 @@ defmodule Domain.Devices do
|> Authorizer.for_subject(subject)
|> Repo.list()
devices =
devices
|> preload_online_statuses()
{:ok, Repo.preload(devices, preload)}
end
end
@@ -93,22 +106,45 @@ defmodule Domain.Devices do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
true <- Validator.valid_uuid?(actor_id) do
Device.Query.by_actor_id(actor_id)
|> Authorizer.for_subject(subject)
|> Repo.list()
{:ok, devices} =
Device.Query.by_actor_id(actor_id)
|> Authorizer.for_subject(subject)
|> Repo.list()
devices =
devices
|> preload_online_statuses()
{:ok, devices}
else
false -> {:error, :not_found}
other -> other
end
end
# TODO: this is ugly!
defp preload_online_status(device) do
connected_devices = Presence.list("devices:#{device.id}")
%{device | online?: Map.has_key?(connected_devices, device.id)}
end
defp preload_online_statuses([]), do: []
defp preload_online_statuses([device | _] = devices) do
connected_devices = Presence.list("devices:#{device.account_id}")
Enum.map(devices, fn device ->
%{device | online?: Map.has_key?(connected_devices, device.id)}
end)
end
def change_device(%Device{} = device, attrs \\ %{}) do
Device.Changeset.update_changeset(device, attrs)
Device.Changeset.update(device, attrs)
end
def upsert_device(attrs \\ %{}, %Auth.Subject{identity: %Auth.Identity{} = identity} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_own_devices_permission()) do
changeset = Device.Changeset.upsert_changeset(identity, subject.context, attrs)
changeset = Device.Changeset.upsert(identity, subject.context, attrs)
Ecto.Multi.new()
|> Ecto.Multi.insert(:device, changeset,
@@ -120,7 +156,7 @@ defmodule Domain.Devices do
|> resolve_address_multi(:ipv6)
|> Ecto.Multi.update(:device_with_address, fn
%{device: %Device{} = device, ipv4: ipv4, ipv6: ipv6} ->
Device.Changeset.finalize_upsert_changeset(device, ipv4, ipv6)
Device.Changeset.finalize_upsert(device, ipv4, ipv6)
end)
|> Repo.transaction()
|> case do
@@ -144,7 +180,14 @@ defmodule Domain.Devices do
with :ok <- authorize_actor_device_management(device.actor_id, subject) do
Device.Query.by_id(device.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(with: &Device.Changeset.update_changeset(&1, attrs))
|> Repo.fetch_and_update(with: &Device.Changeset.update(&1, attrs))
|> case do
{:ok, device} ->
{:ok, preload_online_status(device)}
{:error, reason} ->
{:error, reason}
end
end
end
@@ -152,10 +195,18 @@ defmodule Domain.Devices do
with :ok <- authorize_actor_device_management(device.actor_id, subject) do
Device.Query.by_id(device.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(with: &Device.Changeset.delete_changeset/1)
|> Repo.fetch_and_update(with: &Device.Changeset.delete/1)
end
end
def delete_actor_devices(%Actors.Actor{} = actor) do
{_count, nil} =
Device.Query.by_actor_id(actor.id)
|> Repo.update_all(set: [deleted_at: DateTime.utc_now()])
:ok
end
def authorize_actor_device_management(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do
authorize_actor_device_management(actor.id, subject)
end
@@ -169,13 +220,13 @@ defmodule Domain.Devices do
end
def connect_device(%Device{} = device) do
Phoenix.PubSub.subscribe(Domain.PubSub, "actor:#{device.actor_id}")
{:ok, _} =
Presence.track(self(), "devices:#{device.account_id}", device.id, %{
online_at: System.system_time(:second)
})
{:ok, _} = Presence.track(self(), "actor_devices:#{device.actor_id}", device.id, %{})
:ok
end

View File

@@ -16,6 +16,8 @@ defmodule Domain.Devices.Device do
field :last_seen_version, :string
field :last_seen_at, :utc_datetime_usec
field :online?, :boolean, virtual: true
belongs_to :account, Domain.Accounts.Account
belongs_to :actor, Domain.Actors.Actor
belongs_to :identity, Domain.Auth.Identity

View File

@@ -19,7 +19,7 @@ defmodule Domain.Devices.Device.Changeset do
def upsert_on_conflict, do: {:replace, @conflict_replace_fields}
def upsert_changeset(%Auth.Identity{} = identity, %Auth.Context{} = context, attrs) do
def upsert(%Auth.Identity{} = identity, %Auth.Context{} = context, attrs) do
%Devices.Device{}
|> cast(attrs, @upsert_fields)
|> put_default_value(:name, &generate_name/0)
@@ -38,7 +38,7 @@ defmodule Domain.Devices.Device.Changeset do
|> put_device_version()
end
def finalize_upsert_changeset(%Devices.Device{} = device, ipv4, ipv6) do
def finalize_upsert(%Devices.Device{} = device, ipv4, ipv6) do
device
|> change()
|> put_change(:ipv4, ipv4)
@@ -47,14 +47,14 @@ defmodule Domain.Devices.Device.Changeset do
|> unique_constraint(:ipv6, name: :devices_account_id_ipv6_index)
end
def update_changeset(%Devices.Device{} = device, attrs) do
def update(%Devices.Device{} = device, attrs) do
device
|> cast(attrs, @update_fields)
|> changeset()
|> validate_required(@required_fields)
|> changeset()
end
def delete_changeset(%Devices.Device{} = device) do
def delete(%Devices.Device{} = device) do
device
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())
@@ -68,6 +68,7 @@ defmodule Domain.Devices.Device.Changeset do
|> unique_constraint([:actor_id, :name])
|> unique_constraint([:actor_id, :public_key])
|> unique_constraint(:external_id)
|> unique_constraint(:name, name: :devices_account_id_actor_id_name_index)
end
defp put_device_version(changeset) do

View File

@@ -102,7 +102,7 @@ defmodule Domain.Gateways do
def create_group(attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_gateways_permission()) do
subject.account
|> Group.Changeset.create_changeset(attrs, subject)
|> Group.Changeset.create(attrs, subject)
|> Repo.insert()
end
end
@@ -110,14 +110,14 @@ defmodule Domain.Gateways do
def change_group(%Group{} = group, attrs \\ %{}) do
group
|> Repo.preload(:account)
|> Group.Changeset.update_changeset(attrs)
|> Group.Changeset.update(attrs)
end
def update_group(%Group{} = group, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_gateways_permission()) do
group
|> Repo.preload(:account)
|> Group.Changeset.update_changeset(attrs)
|> Group.Changeset.update(attrs)
|> Repo.update()
end
end
@@ -132,12 +132,12 @@ defmodule Domain.Gateways do
Token.Query.by_group_id(group.id)
|> Repo.all()
|> Enum.each(fn token ->
Token.Changeset.delete_changeset(token)
Token.Changeset.delete(token)
|> Repo.update!()
end)
group
|> Group.Changeset.delete_changeset()
|> Group.Changeset.delete()
end
)
end
@@ -149,7 +149,7 @@ defmodule Domain.Gateways do
|> Repo.fetch_and_update(
with: fn token ->
if Domain.Crypto.equal?(secret, token.hash) do
Token.Changeset.use_changeset(token)
Token.Changeset.use(token)
else
:not_found
end
@@ -270,11 +270,11 @@ defmodule Domain.Gateways do
end
def change_gateway(%Gateway{} = gateway, attrs \\ %{}) do
Gateway.Changeset.update_changeset(gateway, attrs)
Gateway.Changeset.update(gateway, attrs)
end
def upsert_gateway(%Token{} = token, attrs) do
changeset = Gateway.Changeset.upsert_changeset(token, attrs)
changeset = Gateway.Changeset.upsert(token, attrs)
Ecto.Multi.new()
|> Ecto.Multi.insert(:gateway, changeset,
@@ -286,7 +286,7 @@ defmodule Domain.Gateways do
|> resolve_address_multi(:ipv6)
|> Ecto.Multi.update(:gateway_with_address, fn
%{gateway: %Gateway{} = gateway, ipv4: ipv4, ipv6: ipv6} ->
Gateway.Changeset.finalize_upsert_changeset(gateway, ipv4, ipv6)
Gateway.Changeset.finalize_upsert(gateway, ipv4, ipv6)
end)
|> Repo.transaction()
|> case do
@@ -309,7 +309,7 @@ defmodule Domain.Gateways do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_gateways_permission()) do
Gateway.Query.by_id(gateway.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(with: &Gateway.Changeset.update_changeset(&1, attrs))
|> Repo.fetch_and_update(with: &Gateway.Changeset.update(&1, attrs))
|> case do
{:ok, gateway} ->
{:ok, preload_online_status(gateway)}
@@ -324,7 +324,7 @@ defmodule Domain.Gateways do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_gateways_permission()) do
Gateway.Query.by_id(gateway.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(with: &Gateway.Changeset.delete_changeset/1)
|> Repo.fetch_and_update(with: &Gateway.Changeset.delete/1)
|> case do
{:ok, gateway} ->
{:ok, preload_online_status(gateway)}

View File

@@ -9,7 +9,8 @@ defmodule Domain.Gateways.Authorizer do
def list_permissions_for_role(:account_admin_user) do
[
manage_gateways_permission()
manage_gateways_permission(),
connect_gateways_permission()
]
end

View File

@@ -20,7 +20,7 @@ defmodule Domain.Gateways.Gateway.Changeset do
def upsert_on_conflict, do: {:replace, @conflict_replace_fields}
def upsert_changeset(%Gateways.Token{} = token, attrs) do
def upsert(%Gateways.Token{} = token, attrs) do
%Gateways.Gateway{}
|> cast(attrs, @upsert_fields)
|> put_default_value(:name_suffix, fn -> Domain.Crypto.rand_string(5) end)
@@ -40,21 +40,21 @@ defmodule Domain.Gateways.Gateway.Changeset do
|> assoc_constraint(:token)
end
def finalize_upsert_changeset(%Gateways.Gateway{} = gateway, ipv4, ipv6) do
def finalize_upsert(%Gateways.Gateway{} = gateway, ipv4, ipv6) do
gateway
|> change()
|> put_change(:ipv4, ipv4)
|> put_change(:ipv6, ipv6)
end
def update_changeset(%Gateways.Gateway{} = gateway, attrs) do
def update(%Gateways.Gateway{} = gateway, attrs) do
gateway
|> cast(attrs, @update_fields)
|> changeset()
|> validate_required(@required_fields)
end
def delete_changeset(%Gateways.Gateway{} = gateway) do
def delete(%Gateways.Gateway{} = gateway) do
gateway
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())

View File

@@ -5,7 +5,7 @@ defmodule Domain.Gateways.Group.Changeset do
@fields ~w[name_prefix tags]a
def create_changeset(%Accounts.Account{} = account, attrs, %Auth.Subject{} = subject) do
def create(%Accounts.Account{} = account, attrs, %Auth.Subject{} = subject) do
%Gateways.Group{account: account}
|> changeset(attrs)
|> put_change(:account_id, account.id)
@@ -13,13 +13,13 @@ defmodule Domain.Gateways.Group.Changeset do
|> put_change(:created_by_identity_id, subject.identity.id)
|> cast_assoc(:tokens,
with: fn _token, _attrs ->
Gateways.Token.Changeset.create_changeset(account, subject)
Gateways.Token.Changeset.create(account, subject)
end,
required: true
)
end
def update_changeset(%Gateways.Group{} = group, attrs) do
def update(%Gateways.Group{} = group, attrs) do
changeset(group, attrs)
end
@@ -28,6 +28,7 @@ defmodule Domain.Gateways.Group.Changeset do
|> cast(attrs, @fields)
|> trim_change(:name_prefix)
|> put_default_value(:name_prefix, &Domain.NameGenerator.generate/0)
|> validate_required(@fields)
|> validate_length(:name_prefix, min: 1, max: 64)
|> validate_length(:tags, min: 0, max: 128)
|> validate_no_duplicates(:tags)
@@ -38,11 +39,10 @@ defmodule Domain.Gateways.Group.Changeset do
[]
end
end)
|> validate_required(@fields)
|> unique_constraint(:name_prefix, name: :gateway_groups_account_id_name_prefix_index)
end
def delete_changeset(%Gateways.Group{} = group) do
def delete(%Gateways.Group{} = group) do
group
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())

View File

@@ -4,7 +4,7 @@ defmodule Domain.Gateways.Token.Changeset do
alias Domain.Accounts
alias Domain.Gateways
def create_changeset(%Accounts.Account{} = account, %Auth.Subject{} = subject) do
def create(%Accounts.Account{} = account, %Auth.Subject{} = subject) do
%Gateways.Token{}
|> change()
|> put_change(:account_id, account.id)
@@ -16,15 +16,15 @@ defmodule Domain.Gateways.Token.Changeset do
|> put_change(:created_by_identity_id, subject.identity.id)
end
def use_changeset(%Gateways.Token{} = token) do
def use(%Gateways.Token{} = token) do
# TODO: While we don't have token rotation implemented, the tokens are all multi-use
# delete_changeset(token)
# delete(token)
token
|> change()
end
def delete_changeset(%Gateways.Token{} = token) do
def delete(%Gateways.Token{} = token) do
token
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())

View File

@@ -156,6 +156,12 @@ defmodule Domain.Jobs.Executors.Global do
end
defp execute_handler(module, function, config) do
Logger.metadata(
job_runner: __MODULE__,
job_execution_id: Ecto.UUID.generate(),
job_callback: "#{module}.#{function}/1"
)
_ = apply(module, function, [config])
:ok
end

View File

@@ -22,7 +22,7 @@ defmodule Domain.Network do
address =
Address.Query.next_available_address(account_id, cidr, offset)
|> Domain.Repo.one!()
|> Address.Changeset.create_changeset(account_id)
|> Address.Changeset.create(account_id)
|> Repo.insert!()
address.address

View File

@@ -2,7 +2,7 @@ defmodule Domain.Network.Address.Changeset do
use Domain, :changeset
alias Domain.Network.Address
def create_changeset(address, account_id) do
def create(address, account_id) do
%Address{}
|> change()
|> put_change(:address, address)

View File

@@ -53,7 +53,7 @@ defmodule Domain.Policies do
{:one_of, [Authorizer.manage_policies_permission()]}
with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
Policy.Changeset.create_changeset(attrs, subject)
Policy.Changeset.create(attrs, subject)
|> Repo.insert()
end
end
@@ -64,7 +64,7 @@ defmodule Domain.Policies do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
:ok <- ensure_has_access_to(subject, policy) do
Policy.Changeset.update_changeset(policy, attrs)
Policy.Changeset.update(policy, attrs)
|> Repo.update()
end
end
@@ -76,7 +76,7 @@ defmodule Domain.Policies do
with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
Policy.Query.by_id(policy.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(with: &Policy.Changeset.delete_changeset/1)
|> Repo.fetch_and_update(with: &Policy.Changeset.delete/1)
end
end

View File

@@ -8,7 +8,8 @@ defmodule Domain.Policies.Authorizer do
@impl Domain.Auth.Authorizer
def list_permissions_for_role(:account_admin_user) do
[
manage_policies_permission()
manage_policies_permission(),
view_available_policies_permission()
]
end

View File

@@ -7,7 +7,7 @@ defmodule Domain.Policies.Policy.Changeset do
@update_fields ~w[name]a
@required_fields @fields
def create_changeset(attrs, %Auth.Subject{} = subject) do
def create(attrs, %Auth.Subject{} = subject) do
%Policy{}
|> cast(attrs, @fields)
|> validate_required(@required_fields)
@@ -17,14 +17,14 @@ defmodule Domain.Policies.Policy.Changeset do
|> put_change(:created_by_identity_id, subject.identity.id)
end
def update_changeset(%Policy{} = policy, attrs) do
def update(%Policy{} = policy, attrs) do
policy
|> cast(attrs, @update_fields)
|> validate_required(@required_fields)
|> changeset()
end
def delete_changeset(%Policy{} = policy) do
def delete(%Policy{} = policy) do
policy
|> change()
|> put_change(:deleted_at, DateTime.utc_now())

View File

@@ -107,20 +107,20 @@ defmodule Domain.Relays do
def create_group(attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_relays_permission()) do
subject.account
|> Group.Changeset.create_changeset(attrs, subject)
|> Group.Changeset.create(attrs, subject)
|> Repo.insert()
end
end
def create_global_group(attrs) do
Group.Changeset.create_changeset(attrs)
Group.Changeset.create(attrs)
|> Repo.insert()
end
def change_group(%Group{} = group, attrs \\ %{}) do
group
|> Repo.preload(:account)
|> Group.Changeset.update_changeset(attrs)
|> Group.Changeset.update(attrs)
end
def update_group(group, attrs \\ %{}, subject)
@@ -133,7 +133,7 @@ defmodule Domain.Relays do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_relays_permission()) do
group
|> Repo.preload(:account)
|> Group.Changeset.update_changeset(attrs, subject)
|> Group.Changeset.update(attrs, subject)
|> Repo.update()
end
end
@@ -153,12 +153,12 @@ defmodule Domain.Relays do
Token.Query.by_group_id(group.id)
|> Repo.all()
|> Enum.each(fn token ->
Token.Changeset.delete_changeset(token)
Token.Changeset.delete(token)
|> Repo.update!()
end)
group
|> Group.Changeset.delete_changeset()
|> Group.Changeset.delete()
end
)
end
@@ -170,7 +170,7 @@ defmodule Domain.Relays do
|> Repo.fetch_and_update(
with: fn token ->
if Domain.Crypto.equal?(secret, token.hash) do
Token.Changeset.use_changeset(token)
Token.Changeset.use(token)
else
:not_found
end
@@ -290,7 +290,7 @@ defmodule Domain.Relays do
end
def upsert_relay(%Token{} = token, attrs) do
changeset = Relay.Changeset.upsert_changeset(token, attrs)
changeset = Relay.Changeset.upsert(token, attrs)
Ecto.Multi.new()
|> Ecto.Multi.insert(:relay, changeset,
@@ -309,7 +309,7 @@ defmodule Domain.Relays do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_relays_permission()) do
Relay.Query.by_id(relay.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(with: &Relay.Changeset.delete_changeset/1)
|> Repo.fetch_and_update(with: &Relay.Changeset.delete/1)
end
end

View File

@@ -6,24 +6,24 @@ defmodule Domain.Relays.Group.Changeset do
@fields ~w[name]a
def create_changeset(attrs) do
def create(attrs) do
%Relays.Group{}
|> changeset(attrs)
|> cast_assoc(:tokens,
with: fn _token, _attrs ->
Relays.Token.Changeset.create_changeset()
Relays.Token.Changeset.create()
end,
required: true
)
|> put_change(:created_by, :system)
end
def create_changeset(%Accounts.Account{} = account, attrs, %Auth.Subject{} = subject) do
def create(%Accounts.Account{} = account, attrs, %Auth.Subject{} = subject) do
%Relays.Group{account: account}
|> changeset(attrs)
|> cast_assoc(:tokens,
with: fn _token, _attrs ->
Relays.Token.Changeset.create_changeset(account, subject)
Relays.Token.Changeset.create(account, subject)
end,
required: true
)
@@ -32,21 +32,20 @@ defmodule Domain.Relays.Group.Changeset do
|> put_change(:created_by_identity_id, subject.identity.id)
end
def update_changeset(%Relays.Group{} = group, attrs, %Auth.Subject{} = subject) do
def update(%Relays.Group{} = group, attrs, %Auth.Subject{} = subject) do
changeset(group, attrs)
|> cast_assoc(:tokens,
with: fn _token, _attrs ->
Relays.Token.Changeset.create_changeset(group.account, subject)
end,
required: true
Relays.Token.Changeset.create(group.account, subject)
end
)
end
def update_changeset(%Relays.Group{} = group, attrs) do
def update(%Relays.Group{} = group, attrs) do
changeset(group, attrs)
|> cast_assoc(:tokens,
with: fn _token, _attrs ->
Relays.Token.Changeset.create_changeset()
Relays.Token.Changeset.create()
end,
required: true
)
@@ -57,13 +56,13 @@ defmodule Domain.Relays.Group.Changeset do
|> cast(attrs, @fields)
|> trim_change(:name)
|> put_default_value(:name, &Domain.NameGenerator.generate/0)
|> validate_length(:name, min: 1, max: 64)
|> validate_required(@fields)
|> validate_length(:name, min: 1, max: 64)
|> unique_constraint(:name, name: :relay_groups_name_index)
|> unique_constraint(:name, name: :relay_groups_account_id_name_index)
end
def delete_changeset(%Relays.Group{} = group) do
def delete(%Relays.Group{} = group) do
group
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())

View File

@@ -21,7 +21,7 @@ defmodule Domain.Relays.Relay.Changeset do
def upsert_on_conflict, do: {:replace, @conflict_replace_fields}
def upsert_changeset(%Relays.Token{} = token, attrs) do
def upsert(%Relays.Token{} = token, attrs) do
%Relays.Relay{}
|> cast(attrs, @upsert_fields)
|> validate_required(~w[last_seen_user_agent last_seen_remote_ip]a)
@@ -39,7 +39,7 @@ defmodule Domain.Relays.Relay.Changeset do
|> assoc_constraint(:token)
end
def delete_changeset(%Relays.Relay{} = relay) do
def delete(%Relays.Relay{} = relay) do
relay
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())

View File

@@ -4,7 +4,7 @@ defmodule Domain.Relays.Token.Changeset do
alias Domain.Accounts
alias Domain.Relays
def create_changeset do
def create do
%Relays.Token{}
|> change()
|> put_change(:value, Domain.Crypto.rand_string(64))
@@ -14,22 +14,22 @@ defmodule Domain.Relays.Token.Changeset do
|> put_change(:created_by, :system)
end
def create_changeset(%Accounts.Account{} = account, %Auth.Subject{} = subject) do
create_changeset()
def create(%Accounts.Account{} = account, %Auth.Subject{} = subject) do
create()
|> put_change(:account_id, account.id)
|> put_change(:created_by, :identity)
|> put_change(:created_by_identity_id, subject.identity.id)
end
def use_changeset(%Relays.Token{} = token) do
def use(%Relays.Token{} = token) do
# TODO: While we don't have token rotation implemented, the tokens are all multi-use
# delete_changeset(token)
# delete(token)
token
|> change()
end
def delete_changeset(%Relays.Token{} = token) do
def delete(%Relays.Token{} = token) do
token
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())

View File

@@ -37,7 +37,10 @@ defmodule Domain.Repo do
[{:with, changeset_fun :: (term() -> Ecto.Changeset.t())}],
opts :: Keyword.t()
) ::
{:ok, Ecto.Schema.t()} | {:error, :not_found} | {:error, Ecto.Changeset.t()}
{:ok, Ecto.Schema.t()}
| {:error, :not_found}
| {:error, Ecto.Changeset.t()}
| {:error, term()}
def fetch_and_update(queryable, [with: changeset_fun], opts \\ [])
when is_function(changeset_fun, 1) do
transaction(fn ->
@@ -47,7 +50,7 @@ defmodule Domain.Repo do
schema
|> changeset_fun.()
|> case do
%Ecto.Changeset{} = changeset -> update(changeset, opts)
%Ecto.Changeset{} = changeset -> update(changeset, mode: :savepoint)
reason -> {:error, reason}
end
end
@@ -65,4 +68,25 @@ defmodule Domain.Repo do
def list(queryable, opts \\ []) do
{:ok, __MODULE__.all(queryable, opts)}
end
@doc """
Peek is used to fetch a preview of the a association for each of schemas.
It takes list of schemas and queryable which is used to preload a few assocs along with
total count of assocs available as `%{id: schema.id, count: schema_counts.count, item: assocs}` map.
"""
def peek(queryable, schemas) do
ids = schemas |> Enum.map(& &1.id) |> Enum.uniq()
preview = Map.new(ids, fn id -> {id, %{count: 0, items: []}} end)
preview =
queryable
|> all()
|> Enum.group_by(&{&1.id, &1.count}, & &1.item)
|> Enum.reduce(preview, fn {{id, count}, items}, acc ->
Map.put(acc, id, %{count: count, items: items})
end)
{:ok, preview}
end
end

View File

@@ -98,7 +98,7 @@ defmodule Domain.Resources do
def create_resource(attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_resources_permission()) do
changeset = Resource.Changeset.create_changeset(subject.account, attrs, subject)
changeset = Resource.Changeset.create(subject.account, attrs, subject)
Ecto.Multi.new()
|> Ecto.Multi.insert(:resource, changeset, returning: true)
@@ -106,7 +106,7 @@ defmodule Domain.Resources do
|> resolve_address_multi(:ipv6)
|> Ecto.Multi.update(:resource_with_address, fn
%{resource: %Resource{} = resource, ipv4: ipv4, ipv6: ipv6} ->
Resource.Changeset.finalize_create_changeset(resource, ipv4, ipv6)
Resource.Changeset.finalize_create(resource, ipv4, ipv6)
end)
|> Repo.transaction()
|> case do
@@ -145,7 +145,7 @@ defmodule Domain.Resources do
def update_resource(%Resource{} = resource, attrs, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_resources_permission()) do
resource
|> Resource.Changeset.update_changeset(attrs, subject)
|> Resource.Changeset.update(attrs, subject)
|> Repo.update()
|> case do
{:ok, resource} ->
@@ -167,7 +167,7 @@ defmodule Domain.Resources do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_resources_permission()) do
Resource.Query.by_id(resource.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(with: &Resource.Changeset.delete_changeset/1)
|> Repo.fetch_and_update(with: &Resource.Changeset.delete/1)
|> case do
{:ok, resource} ->
# Phoenix.PubSub.broadcast(

View File

@@ -8,7 +8,8 @@ defmodule Domain.Resources.Authorizer do
@impl Domain.Auth.Authorizer
def list_permissions_for_role(:account_admin_user) do
[
manage_resources_permission()
manage_resources_permission(),
view_available_resources_permission()
]
end

View File

@@ -7,7 +7,7 @@ defmodule Domain.Resources.Resource.Changeset do
@update_fields ~w[name]a
@required_fields ~w[address type]a
def create_changeset(%Accounts.Account{} = account, attrs, %Auth.Subject{} = subject) do
def create(%Accounts.Account{} = account, attrs, %Auth.Subject{} = subject) do
%Resource{}
|> cast(attrs, @fields)
|> validate_required(@required_fields)
@@ -22,7 +22,7 @@ defmodule Domain.Resources.Resource.Changeset do
|> put_change(:created_by_identity_id, subject.identity.id)
end
def finalize_create_changeset(%Resource{} = resource, ipv4, ipv6) do
def finalize_create(%Resource{} = resource, ipv4, ipv6) do
resource
|> change()
|> put_change(:ipv4, ipv4)
@@ -73,7 +73,7 @@ defmodule Domain.Resources.Resource.Changeset do
end
end
def update_changeset(%Resource{} = resource, attrs, %Auth.Subject{} = subject) do
def update(%Resource{} = resource, attrs, %Auth.Subject{} = subject) do
resource
|> cast(attrs, @update_fields)
|> validate_required(@required_fields)
@@ -99,7 +99,7 @@ defmodule Domain.Resources.Resource.Changeset do
)
end
def delete_changeset(%Resource{} = resource) do
def delete(%Resource{} = resource) do
resource
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())

View File

@@ -328,6 +328,16 @@ defmodule Domain.Validator do
end)
end
def validate_date(changeset, field, greater_than: greater_than) do
validate_change(changeset, field, fn _current_field, value ->
if Date.compare(value, greater_than) == :gt do
[]
else
[{field, "must be greater than #{inspect(greater_than)}"}]
end
end)
end
@doc """
Applies a validation function for every elements of the list.

View File

@@ -0,0 +1,9 @@
defmodule Domain.Repo.Migrations.CreatedAuthIdentitiesInsertedAt do
use Ecto.Migration
def change do
alter table(:auth_identities) do
timestamps(updated_at: false)
end
end
end

View File

@@ -0,0 +1,10 @@
defmodule Domain.Repo.Migrations.AddActorGroupsCreatedBy do
use Ecto.Migration
def change do
alter table(:actor_groups) do
add(:created_by, :string, null: false)
add(:created_by_identity_id, references(:auth_identities, type: :binary_id))
end
end
end

View File

@@ -0,0 +1,9 @@
defmodule Domain.Repo.Migrations.AddActorsLastSyncedAt do
use Ecto.Migration
def change do
alter table(:actors) do
add(:last_synced_at, :utc_datetime_usec)
end
end
end

View File

@@ -53,15 +53,15 @@ IO.puts("")
adapter_config: %{}
})
{:ok, _oidc_provider} =
{:ok, oidc_provider} =
Auth.create_provider(account, %{
name: "Vault",
name: "OIDC",
adapter: :openid_connect,
adapter_config: %{
"client_id" => "CLIENT_ID",
"client_secret" => "CLIENT_SECRET",
"response_type" => "code",
"scope" => "openid email profile",
"scope" => "openid email name groups",
"discovery_document_uri" => "https://common.auth0.com/.well-known/openid-configuration"
}
})
@@ -73,7 +73,7 @@ IO.puts("")
adapter_config: %{}
})
{:ok, other_email_provider} =
{:ok, _other_email_provider} =
Auth.create_provider(other_account, %{
name: "email",
adapter: :email,
@@ -91,36 +91,35 @@ unprivileged_actor_email = "firezone-unprivileged-1@localhost"
admin_actor_email = "firezone@localhost"
{:ok, unprivileged_actor} =
Actors.create_actor(email_provider, unprivileged_actor_email, %{
Actors.create_actor(account, %{
type: :account_user,
name: "Firezone Unprivileged"
})
{:ok, admin_actor} =
Actors.create_actor(email_provider, admin_actor_email, %{
Actors.create_actor(account, %{
type: :account_admin_user,
name: "Firezone Admin"
})
{:ok, service_account_actor} =
Actors.create_actor(token_provider, "backup-manager", %{
Actors.create_actor(account, %{
"type" => :service_account,
"name" => "Backup Manager",
"provider" => %{
expires_at: DateTime.utc_now() |> DateTime.add(365, :day)
}
"name" => "Backup Manager"
})
{:ok, unprivileged_actor_email_identity} =
Auth.create_identity(unprivileged_actor, email_provider, %{
provider_identifier: unprivileged_actor_email
})
{:ok, unprivileged_actor_userpass_identity} =
Auth.create_identity(unprivileged_actor, userpass_provider, unprivileged_actor_email, %{
"password" => "Firezone1234",
"password_confirmation" => "Firezone1234"
})
{:ok, _admin_actor_userpass_identity} =
Auth.create_identity(admin_actor, userpass_provider, admin_actor_email, %{
"password" => "Firezone1234",
"password_confirmation" => "Firezone1234"
Auth.create_identity(unprivileged_actor, userpass_provider, %{
provider_identifier: unprivileged_actor_email,
provider_virtual_state: %{
"password" => "Firezone1234",
"password_confirmation" => "Firezone1234"
}
})
unprivileged_actor_userpass_identity =
@@ -128,41 +127,64 @@ unprivileged_actor_userpass_identity =
id: "7da7d1cd-111c-44a7-b5ac-4027b9d230e5"
)
{:ok, admin_actor_email_identity} =
Auth.create_identity(admin_actor, email_provider, %{
provider_identifier: admin_actor_email
})
{:ok, _admin_actor_userpass_identity} =
Auth.create_identity(admin_actor, userpass_provider, %{
provider_identifier: admin_actor_email,
provider_virtual_state: %{
"password" => "Firezone1234",
"password_confirmation" => "Firezone1234"
}
})
{:ok, service_account_actor_token_identity} =
Auth.create_identity(service_account_actor, token_provider, %{
provider_identifier: "tok-#{Ecto.UUID.generate()}",
provider_virtual_state: %{
"expires_at" => DateTime.utc_now() |> DateTime.add(365, :day)
}
})
# Other Account Users
other_unprivileged_actor_email = "other-unprivileged-1@localhost"
other_admin_actor_email = "other@localhost"
{:ok, other_unprivileged_actor} =
Actors.create_actor(other_email_provider, other_unprivileged_actor_email, %{
Actors.create_actor(other_account, %{
type: :account_user,
name: "Other Unprivileged"
})
{:ok, other_admin_actor} =
Actors.create_actor(other_email_provider, other_admin_actor_email, %{
Actors.create_actor(other_account, %{
type: :account_admin_user,
name: "Other Admin"
})
{:ok, _other_unprivileged_actor_userpass_identity} =
Auth.create_identity(
other_unprivileged_actor,
other_userpass_provider,
other_unprivileged_actor_email,
%{
Auth.create_identity(other_unprivileged_actor, other_userpass_provider, %{
provider_identifier: other_unprivileged_actor_email,
provider_virtual_state: %{
"password" => "Firezone1234",
"password_confirmation" => "Firezone1234"
}
)
{:ok, _other_admin_actor_userpass_identity} =
Auth.create_identity(other_admin_actor, other_userpass_provider, other_admin_actor_email, %{
"password" => "Firezone1234",
"password_confirmation" => "Firezone1234"
})
unprivileged_actor_token = hd(unprivileged_actor.identities).provider_virtual_state.sign_in_token
admin_actor_token = hd(admin_actor.identities).provider_virtual_state.sign_in_token
{:ok, _other_admin_actor_userpass_identity} =
Auth.create_identity(other_admin_actor, other_userpass_provider, %{
provider_identifier: other_admin_actor_email,
provider_virtual_state: %{
"password" => "Firezone1234",
"password_confirmation" => "Firezone1234"
}
})
unprivileged_actor_token = unprivileged_actor_email_identity.provider_virtual_state.sign_in_token
admin_actor_token = admin_actor_email_identity.provider_virtual_state.sign_in_token
unprivileged_subject =
Auth.build_subject(
@@ -174,7 +196,7 @@ unprivileged_subject =
admin_subject =
Auth.build_subject(
hd(admin_actor.identities),
admin_actor_email_identity,
nil,
"iOS/12.5 (iPhone) connlib/0.7.412",
{100, 64, 100, 58}
@@ -190,11 +212,11 @@ for {type, login, password, email_token} <- [
IO.puts(" #{login}, #{type}, password: #{password}, email token: #{email_token} (exp in 15m)")
end
service_account_identity = hd(service_account_actor.identities)
service_account_token = service_account_identity.provider_virtual_state.secret
service_account_token = service_account_actor_token_identity.provider_virtual_state.changes.secret
IO.puts(
" #{service_account_identity.provider_identifier}, #{service_account_actor.type}, token: #{service_account_token}"
" #{service_account_actor_token_identity.provider_identifier}," <>
"#{service_account_actor.type}, token: #{service_account_token}"
)
IO.puts("")
@@ -228,7 +250,12 @@ IO.puts("Created Actor Groups: ")
{:ok, eng_group} = Actors.create_group(%{name: "Engineering"}, admin_subject)
{:ok, finance_group} = Actors.create_group(%{name: "Finance"}, admin_subject)
{:ok, all_group} = Actors.create_group(%{name: "All Employees"}, admin_subject)
{:ok, all_group} =
Actors.create_group(
%{name: "All Employees", provider_id: oidc_provider.id, provider_identifier: "foo"},
admin_subject
)
for group <- [eng_group, finance_group, all_group] do
IO.puts(" Name: #{group.name} ID: #{group.id}")
@@ -261,7 +288,7 @@ IO.puts("")
relay_group =
account
|> Relays.Group.Changeset.create_changeset(
|> Relays.Group.Changeset.create(
%{name: "mycorp-aws-relays", tokens: [%{}]},
admin_subject
)
@@ -296,7 +323,7 @@ IO.puts("")
gateway_group =
account
|> Gateways.Group.Changeset.create_changeset(
|> Gateways.Group.Changeset.create(
%{name_prefix: "mycro-aws-gws", tags: ["aws", "in-da-cloud"], tokens: [%{}]},
admin_subject
)

View File

@@ -2,14 +2,13 @@ defmodule Domain.AccountsTest do
use Domain.DataCase, async: true
import Domain.Accounts
alias Domain.Accounts
alias Domain.{AccountsFixtures, ActorsFixtures, AuthFixtures}
describe "fetch_account_by_id/2" do
setup do
account = AccountsFixtures.create_account()
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
account = Fixtures.Accounts.create_account()
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
subject = Fixtures.Auth.create_subject(identity: identity)
%{
account: account,
@@ -33,7 +32,7 @@ defmodule Domain.AccountsTest do
end
test "returns error when subject has no permission to view accounts", %{subject: subject} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert fetch_account_by_id(Ecto.UUID.generate(), subject) ==
{:error,
@@ -45,11 +44,11 @@ defmodule Domain.AccountsTest do
describe "fetch_account_by_id_or_slug/2" do
setup do
account =
AccountsFixtures.create_account(slug: "follow_the_#{System.unique_integer([:positive])}")
Fixtures.Accounts.create_account(slug: "follow_the_#{System.unique_integer([:positive])}")
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
subject = Fixtures.Auth.create_subject(identity: identity)
%{
account: account,
@@ -70,7 +69,7 @@ defmodule Domain.AccountsTest do
end
test "returns error when subject has no permission to view accounts", %{subject: subject} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert fetch_account_by_id_or_slug(Ecto.UUID.generate(), subject) ==
{:error,
@@ -87,7 +86,7 @@ defmodule Domain.AccountsTest do
test "returns account when account exists" do
account =
AccountsFixtures.create_account(slug: "follow_the_#{System.unique_integer([:positive])}")
Fixtures.Accounts.create_account(slug: "follow_the_#{System.unique_integer([:positive])}")
assert {:ok, fetched_account} = fetch_account_by_id_or_slug(account.id)
assert fetched_account.id == account.id
@@ -107,7 +106,7 @@ defmodule Domain.AccountsTest do
end
test "returns account" do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
assert {:ok, returned_account} = fetch_account_by_id(account.id)
assert returned_account.id == account.id
end
@@ -115,14 +114,14 @@ defmodule Domain.AccountsTest do
describe "ensure_has_access_to/2" do
test "returns :ok if subject has access to the account" do
subject = AuthFixtures.create_subject()
subject = Fixtures.Auth.create_subject()
assert ensure_has_access_to(subject, subject.account) == :ok
end
test "returns :error if subject has no access to the account" do
account = AccountsFixtures.create_account()
subject = AuthFixtures.create_subject()
account = Fixtures.Accounts.create_account()
subject = Fixtures.Auth.create_subject()
assert ensure_has_access_to(subject, account) == {:error, :unauthorized}
end

File diff suppressed because it is too large Load Diff

View File

@@ -2,14 +2,13 @@ defmodule Domain.Auth.Adapters.EmailTest do
use Domain.DataCase, async: true
import Domain.Auth.Adapters.Email
alias Domain.Auth
alias Domain.{AccountsFixtures, AuthFixtures}
describe "identity_changeset/2" do
setup do
Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark)
account = AccountsFixtures.create_account()
provider = AuthFixtures.create_email_provider(account: account)
account = Fixtures.Accounts.create_account()
provider = Fixtures.Auth.create_email_provider(account: account)
changeset = %Auth.Identity{} |> Ecto.Changeset.change()
%{
@@ -44,13 +43,13 @@ defmodule Domain.Auth.Adapters.EmailTest do
test "returns changeset as is" do
Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark)
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
changeset = %Ecto.Changeset{data: %Domain.Auth.Provider{account_id: account.id}}
assert provider_changeset(changeset) == changeset
end
test "returns error when email adapter is not configured" do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
changeset = %Ecto.Changeset{data: %Domain.Auth.Provider{account_id: account.id}}
changeset = provider_changeset(changeset)
assert changeset.errors == [adapter: {"email adapter is not configured", []}]
@@ -60,7 +59,7 @@ defmodule Domain.Auth.Adapters.EmailTest do
describe "ensure_provisioned/1" do
test "does nothing for a provider" do
Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark)
provider = AuthFixtures.create_email_provider()
provider = Fixtures.Auth.create_email_provider()
assert ensure_provisioned(provider) == {:ok, provider}
end
end
@@ -68,14 +67,14 @@ defmodule Domain.Auth.Adapters.EmailTest do
describe "ensure_deprovisioned/1" do
test "does nothing for a provider" do
Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark)
provider = AuthFixtures.create_email_provider()
provider = Fixtures.Auth.create_email_provider()
assert ensure_deprovisioned(provider) == {:ok, provider}
end
end
describe "request_sign_in_token/1" do
test "returns identity with updated sign-in token" do
identity = AuthFixtures.create_identity()
identity = Fixtures.Auth.create_identity()
assert {:ok, identity} = request_sign_in_token(identity)
@@ -97,9 +96,9 @@ defmodule Domain.Auth.Adapters.EmailTest do
setup do
Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark)
account = AccountsFixtures.create_account()
provider = AuthFixtures.create_email_provider(account: account)
identity = AuthFixtures.create_identity(account: account, provider: provider)
account = Fixtures.Accounts.create_account()
provider = Fixtures.Auth.create_email_provider(account: account)
identity = Fixtures.Auth.create_identity(account: account, provider: provider)
token = identity.provider_virtual_state.sign_in_token
%{account: account, provider: provider, identity: identity, token: token}
@@ -122,7 +121,7 @@ defmodule Domain.Auth.Adapters.EmailTest do
forty_seconds_ago = DateTime.utc_now() |> DateTime.add(-1 * 15 * 60 - 1, :second)
identity =
AuthFixtures.create_identity(
Fixtures.Auth.create_identity(
account: account,
provider: provider,
provider_state: %{

View File

@@ -0,0 +1,253 @@
defmodule Domain.Auth.Adapters.GoogleWorkspace.JobsTest do
use Domain.DataCase, async: true
alias Domain.{Auth, Actors}
alias Domain.Mocks.GoogleWorkspaceDirectory
import Domain.Auth.Adapters.GoogleWorkspace.Jobs
describe "refresh_access_tokens/1" do
setup do
account = Fixtures.Accounts.create_account()
{provider, bypass} =
Fixtures.Auth.start_and_create_google_workspace_provider(account: account)
provider =
Domain.Fixture.update!(provider, %{
adapter_state: %{
"access_token" => "OIDC_ACCESS_TOKEN",
"refresh_token" => "OIDC_REFRESH_TOKEN",
"expires_at" => DateTime.utc_now() |> DateTime.add(15, :minute),
"claims" => "openid email profile offline_access"
}
})
identity = Fixtures.Auth.create_identity(account: account, provider: provider)
%{
bypass: bypass,
account: account,
provider: provider,
identity: identity
}
end
test "refreshes the access token", %{
provider: provider,
identity: identity,
bypass: bypass
} do
{token, claims} = Mocks.OpenIDConnect.generate_openid_connect_token(provider, identity)
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{
"token_type" => "Bearer",
"id_token" => token,
"access_token" => "MY_ACCESS_TOKEN",
"refresh_token" => "MY_REFRESH_TOKEN",
"expires_in" => nil
})
Mocks.OpenIDConnect.expect_userinfo(bypass)
assert refresh_access_tokens(%{}) == :ok
provider = Repo.get!(Domain.Auth.Provider, provider.id)
assert %{
"access_token" => "MY_ACCESS_TOKEN",
"claims" => ^claims,
"expires_at" => expires_at,
"refresh_token" => "MY_REFRESH_TOKEN",
"userinfo" => %{
"email" => "ada@example.com",
"email_verified" => true,
"family_name" => "Lovelace",
"given_name" => "Ada",
"locale" => "en",
"name" => "Ada Lovelace",
"picture" =>
"https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg",
"sub" => "353690423699814251281"
}
} = provider.adapter_state
assert expires_at
end
test "does not crash when endpoint it not available", %{
bypass: bypass
} do
Bypass.down(bypass)
assert refresh_access_tokens(%{}) == :ok
end
end
describe "sync_directory/1" do
setup do
account = Fixtures.Accounts.create_account()
{provider, bypass} =
Fixtures.Auth.start_and_create_google_workspace_provider(account: account)
%{
bypass: bypass,
account: account,
provider: provider
}
end
test "syncs IdP data", %{provider: provider} do
bypass = Bypass.open()
groups = [
%{
"kind" => "admin#directory#group",
"id" => "GROUP_ID1",
"etag" => "\"ET\"",
"email" => "i@fiez.xxx",
"name" => "Infrastructure",
"directMembersCount" => "5",
"description" => "Group to handle infrastructure alerts and management",
"adminCreated" => true,
"aliases" => [
"pnr@firez.one"
],
"nonEditableAliases" => [
"i@ext.fiez.xxx"
]
}
]
organization_units = [
%{
"kind" => "admin#directory#orgUnit",
"name" => "Engineering",
"description" => "Engineering team",
"etag" => "\"ET\"",
"blockInheritance" => false,
"orgUnitId" => "OU_ID1",
"orgUnitPath" => "/Engineering",
"parentOrgUnitId" => "OU_ID0",
"parentOrgUnitPath" => "/"
}
]
users = [
%{
"agreedToTerms" => true,
"archived" => false,
"changePasswordAtNextLogin" => false,
"creationTime" => "2023-06-10T17:32:06.000Z",
"customerId" => "CustomerID1",
"emails" => [
%{"address" => "b@firez.xxx", "primary" => true},
%{"address" => "b@ext.firez.xxx"}
],
"etag" => "\"ET-61Bnx4\"",
"id" => "USER_ID1",
"includeInGlobalAddressList" => true,
"ipWhitelisted" => false,
"isAdmin" => false,
"isDelegatedAdmin" => false,
"isEnforcedIn2Sv" => false,
"isEnrolledIn2Sv" => false,
"isMailboxSetup" => true,
"kind" => "admin#directory#user",
"languages" => [%{"languageCode" => "en", "preference" => "preferred"}],
"lastLoginTime" => "2023-06-26T13:53:30.000Z",
"name" => %{
"familyName" => "Manifold",
"fullName" => "Brian Manifold",
"givenName" => "Brian"
},
"nonEditableAliases" => ["b@ext.firez.xxx"],
"orgUnitPath" => "/Engineering",
"organizations" => [
%{
"customType" => "",
"department" => "Engineering",
"location" => "",
"name" => "Firezone, Inc.",
"primary" => true,
"title" => "Senior Fullstack Engineer",
"type" => "work"
}
],
"phones" => [%{"type" => "mobile", "value" => "(567) 111-2233"}],
"primaryEmail" => "b@firez.xxx",
"recoveryEmail" => "xxx@xxx.com",
"suspended" => false,
"thumbnailPhotoEtag" => "\"ET\"",
"thumbnailPhotoUrl" =>
"https://lh3.google.com/ao/AP2z2aWvm9JM99oCFZ1TVOJgQZlmZdMMYNr7w9G0jZApdTuLHfAueGFb_XzgTvCNRhGw=s96-c"
}
]
members = [
%{
"kind" => "admin#directory#member",
"etag" => "\"ET\"",
"id" => "USER_ID1",
"email" => "b@firez.xxx",
"role" => "MEMBER",
"type" => "USER",
"status" => "ACTIVE"
}
]
GoogleWorkspaceDirectory.override_endpoint_url("http://localhost:#{bypass.port}/")
GoogleWorkspaceDirectory.mock_groups_list_endpoint(bypass, groups)
GoogleWorkspaceDirectory.mock_organization_units_list_endpoint(bypass, organization_units)
GoogleWorkspaceDirectory.mock_users_list_endpoint(bypass, users)
Enum.each(groups, fn group ->
GoogleWorkspaceDirectory.mock_group_members_list_endpoint(bypass, group["id"], members)
end)
assert sync_directory(%{}) == :ok
groups = Actors.Group |> Repo.all()
assert length(groups) == 2
for group <- groups do
assert group.provider_identifier in ["G:GROUP_ID1", "OU:OU_ID1"]
assert group.name in ["OrgUnit:Engineering", "Group:Infrastructure"]
assert group.inserted_at
assert group.updated_at
assert group.created_by == :provider
assert group.provider_id == provider.id
end
identities = Auth.Identity |> Repo.all() |> Repo.preload(:actor)
assert length(identities) == 1
for identity <- identities do
assert identity.inserted_at
assert identity.created_by == :provider
assert identity.provider_id == provider.id
assert identity.provider_identifier in ["USER_ID1"]
assert identity.actor.name in ["Brian Manifold"]
assert identity.actor.last_synced_at
end
memberships = Actors.Membership |> Repo.all()
assert length(memberships) == 2
membership_tuples = Enum.map(memberships, &{&1.group_id, &1.actor_id})
for identity <- identities, group <- groups do
assert {group.id, identity.actor_id} in membership_tuples
end
end
test "does not crash on endpoint errors" do
bypass = Bypass.open()
Bypass.down(bypass)
GoogleWorkspaceDirectory.override_endpoint_url("http://localhost:#{bypass.port}/")
assert sync_directory(%{}) == :ok
assert Repo.aggregate(Actors.Group, :count) == 0
end
end
end

View File

@@ -3,15 +3,13 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
import Domain.Auth.Adapters.GoogleWorkspace
alias Domain.Auth
alias Domain.Auth.Adapters.OpenIDConnect.PKCE
alias Domain.{AccountsFixtures, AuthFixtures}
describe "identity_changeset/2" do
setup do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
{provider, bypass} =
AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_google_workspace_provider(account: account)
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
changeset = %Auth.Identity{} |> Ecto.Changeset.change()
@@ -41,7 +39,7 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
assert %Ecto.Changeset{} = changeset = provider_changeset(changeset)
assert errors_on(changeset) == %{adapter_config: ["can't be blank"]}
attrs = AuthFixtures.provider_attrs(adapter: :google_workspace, adapter_config: %{})
attrs = Fixtures.Auth.provider_attrs(adapter: :google_workspace, adapter_config: %{})
changeset = Ecto.Changeset.change(%Auth.Provider{}, attrs)
assert %Ecto.Changeset{} = changeset = provider_changeset(changeset)
@@ -54,16 +52,17 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
end
test "returns changeset on valid adapter config" do
account = AccountsFixtures.create_account()
{_bypass, discovery_document_uri} = AuthFixtures.discovery_document_server()
account = Fixtures.Accounts.create_account()
bypass = Domain.Mocks.OpenIDConnect.discovery_document_server()
discovery_document_url = "http://localhost:#{bypass.port}/.well-known/openid-configuration"
attrs =
AuthFixtures.provider_attrs(
Fixtures.Auth.provider_attrs(
adapter: :google_workspace,
adapter_config: %{
client_id: "client_id",
client_secret: "client_secret",
discovery_document_uri: discovery_document_uri
discovery_document_uri: discovery_document_url
}
)
@@ -91,30 +90,26 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
"response_type" => "code",
"client_id" => "client_id",
"client_secret" => "client_secret",
"discovery_document_uri" => discovery_document_uri
"discovery_document_uri" => discovery_document_url
}
end
end
describe "ensure_deprovisioned/1" do
test "does nothing for a provider" do
{provider, _bypass} =
AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_google_workspace_provider()
{provider, _bypass} = Fixtures.Auth.start_and_create_google_workspace_provider()
assert ensure_deprovisioned(provider) == {:ok, provider}
end
end
describe "verify_and_update_identity/2" do
setup do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
{provider, bypass} =
AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_google_workspace_provider(account: account)
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
identity = AuthFixtures.create_identity(account: account, provider: provider)
identity = Fixtures.Auth.create_identity(account: account, provider: provider)
%{account: account, provider: provider, identity: identity, bypass: bypass}
end
@@ -124,10 +119,10 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
identity: identity,
bypass: bypass
} do
{token, claims} = AuthFixtures.generate_openid_connect_token(provider, identity)
{token, claims} = Mocks.OpenIDConnect.generate_openid_connect_token(provider, identity)
AuthFixtures.expect_refresh_token(bypass, %{"id_token" => token})
AuthFixtures.expect_userinfo(bypass)
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{"id_token" => token})
Mocks.OpenIDConnect.expect_userinfo(bypass)
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -159,9 +154,9 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
identity: identity,
bypass: bypass
} do
{token, _claims} = AuthFixtures.generate_openid_connect_token(provider, identity)
{token, _claims} = Mocks.OpenIDConnect.generate_openid_connect_token(provider, identity)
AuthFixtures.expect_refresh_token(bypass, %{
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{
"token_type" => "Bearer",
"id_token" => token,
"access_token" => "MY_ACCESS_TOKEN",
@@ -169,7 +164,7 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
"expires_in" => 3600
})
AuthFixtures.expect_userinfo(bypass)
Mocks.OpenIDConnect.expect_userinfo(bypass)
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -190,11 +185,11 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
forty_seconds_ago = DateTime.utc_now() |> DateTime.add(-40, :second) |> DateTime.to_unix()
{token, _claims} =
AuthFixtures.generate_openid_connect_token(provider, identity, %{
Mocks.OpenIDConnect.generate_openid_connect_token(provider, identity, %{
"exp" => forty_seconds_ago
})
AuthFixtures.expect_refresh_token(bypass, %{"id_token" => token})
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{"id_token" => token})
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -209,7 +204,7 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
} do
token = "foo"
AuthFixtures.expect_refresh_token(bypass, %{"id_token" => token})
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{"id_token" => token})
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -224,9 +219,11 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
bypass: bypass
} do
{token, _claims} =
AuthFixtures.generate_openid_connect_token(provider, identity, %{"sub" => "foo@bar.com"})
Mocks.OpenIDConnect.generate_openid_connect_token(provider, identity, %{
"sub" => "foo@bar.com"
})
AuthFixtures.expect_refresh_token(bypass, %{
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{
"token_type" => "Bearer",
"id_token" => token,
"access_token" => "MY_ACCESS_TOKEN",
@@ -234,7 +231,7 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
"expires_in" => 3600
})
AuthFixtures.expect_userinfo(bypass)
Mocks.OpenIDConnect.expect_userinfo(bypass)
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -248,10 +245,10 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
provider: provider,
bypass: bypass
} do
identity = AuthFixtures.create_identity(account: account)
{token, _claims} = AuthFixtures.generate_openid_connect_token(provider, identity)
identity = Fixtures.Auth.create_identity(account: account)
{token, _claims} = Mocks.OpenIDConnect.generate_openid_connect_token(provider, identity)
AuthFixtures.expect_refresh_token(bypass, %{
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{
"token_type" => "Bearer",
"id_token" => token,
"access_token" => "MY_ACCESS_TOKEN",
@@ -259,7 +256,7 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
"expires_in" => 3600
})
AuthFixtures.expect_userinfo(bypass)
Mocks.OpenIDConnect.expect_userinfo(bypass)
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"

View File

@@ -3,15 +3,13 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
import Domain.Auth.Adapters.OpenIDConnect
alias Domain.Auth
alias Domain.Auth.Adapters.OpenIDConnect.{PKCE, State}
alias Domain.{AccountsFixtures, AuthFixtures}
describe "identity_changeset/2" do
setup do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
{provider, bypass} =
AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_openid_connect_provider(account: account)
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
changeset = %Auth.Identity{} |> Ecto.Changeset.change()
@@ -37,12 +35,12 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
describe "provider_changeset/1" do
test "returns changeset errors in invalid adapter config" do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
changeset = Ecto.Changeset.change(%Auth.Provider{account_id: account.id}, %{})
assert %Ecto.Changeset{} = changeset = provider_changeset(changeset)
assert errors_on(changeset) == %{adapter_config: ["can't be blank"]}
attrs = AuthFixtures.provider_attrs(adapter: :openid_connect, adapter_config: %{})
attrs = Fixtures.Auth.provider_attrs(adapter: :openid_connect, adapter_config: %{})
changeset = Ecto.Changeset.change(%Auth.Provider{account_id: account.id}, attrs)
assert %Ecto.Changeset{} = changeset = provider_changeset(changeset)
@@ -56,16 +54,17 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
end
test "returns changeset on valid adapter config" do
account = AccountsFixtures.create_account()
{_bypass, discovery_document_uri} = AuthFixtures.discovery_document_server()
account = Fixtures.Accounts.create_account()
bypass = Domain.Mocks.OpenIDConnect.discovery_document_server()
discovery_document_url = "http://localhost:#{bypass.port}/.well-known/openid-configuration"
attrs =
AuthFixtures.provider_attrs(
Fixtures.Auth.provider_attrs(
adapter: :openid_connect,
adapter_config: %{
client_id: "client_id",
client_secret: "client_secret",
discovery_document_uri: discovery_document_uri
discovery_document_uri: discovery_document_url
}
)
@@ -82,18 +81,17 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
"response_type" => "code",
"client_id" => "client_id",
"client_secret" => "client_secret",
"discovery_document_uri" => discovery_document_uri
"discovery_document_uri" => discovery_document_url
}
end
end
describe "ensure_provisioned/1" do
test "does nothing for a provider" do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
{provider, _bypass} =
AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_openid_connect_provider(account: account)
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
assert ensure_provisioned(provider) == {:ok, provider}
end
@@ -101,11 +99,10 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
describe "ensure_deprovisioned/1" do
test "does nothing for a provider" do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
{provider, _bypass} =
AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_openid_connect_provider(account: account)
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
assert ensure_deprovisioned(provider) == {:ok, provider}
end
@@ -113,11 +110,10 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
describe "authorization_uri/1" do
test "returns authorization uri for a provider" do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
{provider, bypass} =
AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_openid_connect_provider(account: account)
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
assert {:ok, authorization_uri, {state, verifier}} =
authorization_uri(provider, "https://example.com/")
@@ -162,13 +158,12 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
describe "verify_and_update_identity/2" do
setup do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
{provider, bypass} =
AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_openid_connect_provider(account: account)
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
identity = AuthFixtures.create_identity(account: account, provider: provider)
identity = Fixtures.Auth.create_identity(account: account, provider: provider)
%{account: account, provider: provider, identity: identity, bypass: bypass}
end
@@ -178,10 +173,10 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
identity: identity,
bypass: bypass
} do
{token, claims} = AuthFixtures.generate_openid_connect_token(provider, identity)
{token, claims} = Mocks.OpenIDConnect.generate_openid_connect_token(provider, identity)
AuthFixtures.expect_refresh_token(bypass, %{"id_token" => token})
AuthFixtures.expect_userinfo(bypass)
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{"id_token" => token})
Mocks.OpenIDConnect.expect_userinfo(bypass)
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -213,9 +208,9 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
identity: identity,
bypass: bypass
} do
{token, _claims} = AuthFixtures.generate_openid_connect_token(provider, identity)
{token, _claims} = Mocks.OpenIDConnect.generate_openid_connect_token(provider, identity)
AuthFixtures.expect_refresh_token(bypass, %{
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{
"token_type" => "Bearer",
"id_token" => token,
"access_token" => "MY_ACCESS_TOKEN",
@@ -223,7 +218,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
"expires_in" => 3600
})
AuthFixtures.expect_userinfo(bypass)
Mocks.OpenIDConnect.expect_userinfo(bypass)
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -244,11 +239,11 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
forty_seconds_ago = DateTime.utc_now() |> DateTime.add(-40, :second) |> DateTime.to_unix()
{token, _claims} =
AuthFixtures.generate_openid_connect_token(provider, identity, %{
Mocks.OpenIDConnect.generate_openid_connect_token(provider, identity, %{
"exp" => forty_seconds_ago
})
AuthFixtures.expect_refresh_token(bypass, %{"id_token" => token})
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{"id_token" => token})
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -263,7 +258,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
} do
token = "foo"
AuthFixtures.expect_refresh_token(bypass, %{"id_token" => token})
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{"id_token" => token})
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -278,9 +273,11 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
bypass: bypass
} do
{token, _claims} =
AuthFixtures.generate_openid_connect_token(provider, identity, %{"sub" => "foo@bar.com"})
Mocks.OpenIDConnect.generate_openid_connect_token(provider, identity, %{
"sub" => "foo@bar.com"
})
AuthFixtures.expect_refresh_token(bypass, %{
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{
"token_type" => "Bearer",
"id_token" => token,
"access_token" => "MY_ACCESS_TOKEN",
@@ -288,7 +285,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
"expires_in" => 3600
})
AuthFixtures.expect_userinfo(bypass)
Mocks.OpenIDConnect.expect_userinfo(bypass)
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -302,10 +299,10 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
provider: provider,
bypass: bypass
} do
identity = AuthFixtures.create_identity(account: account)
{token, _claims} = AuthFixtures.generate_openid_connect_token(provider, identity)
identity = Fixtures.Auth.create_identity(account: account)
{token, _claims} = Mocks.OpenIDConnect.generate_openid_connect_token(provider, identity)
AuthFixtures.expect_refresh_token(bypass, %{
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{
"token_type" => "Bearer",
"id_token" => token,
"access_token" => "MY_ACCESS_TOKEN",
@@ -313,7 +310,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
"expires_in" => 3600
})
AuthFixtures.expect_userinfo(bypass)
Mocks.OpenIDConnect.expect_userinfo(bypass)
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -338,13 +335,12 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
describe "refresh_token/1" do
setup do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
{provider, bypass} =
AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_openid_connect_provider(account: account)
Fixtures.Auth.start_and_create_openid_connect_provider(account: account)
identity = AuthFixtures.create_identity(account: account, provider: provider)
identity = Fixtures.Auth.create_identity(account: account, provider: provider)
%{account: account, provider: provider, identity: identity, bypass: bypass}
end
@@ -354,9 +350,9 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
identity: identity,
bypass: bypass
} do
{token, claims} = AuthFixtures.generate_openid_connect_token(provider, identity)
{token, claims} = Mocks.OpenIDConnect.generate_openid_connect_token(provider, identity)
AuthFixtures.expect_refresh_token(bypass, %{
Mocks.OpenIDConnect.expect_refresh_token(bypass, %{
"token_type" => "Bearer",
"id_token" => token,
"access_token" => "MY_ACCESS_TOKEN",
@@ -364,14 +360,14 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
"expires_in" => nil
})
AuthFixtures.expect_userinfo(bypass)
Mocks.OpenIDConnect.expect_userinfo(bypass)
assert {:ok, identity, expires_at} = refresh_token(identity)
assert {:ok, provider} = refresh_access_token(provider)
assert identity.provider_state == %{
assert %{
access_token: "MY_ACCESS_TOKEN",
claims: claims,
expires_at: expires_at,
claims: ^claims,
expires_at: _expires_at,
refresh_token: "MY_REFRESH_TOKEN",
userinfo: %{
"email" => "ada@example.com",
@@ -384,9 +380,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
"https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg",
"sub" => "353690423699814251281"
}
}
assert DateTime.diff(expires_at, DateTime.utc_now()) in 5..15
} = provider.adapter_state
end
end
end

View File

@@ -2,12 +2,11 @@ defmodule Domain.Auth.Adapters.TokenTest do
use Domain.DataCase, async: true
import Domain.Auth.Adapters.Token
alias Domain.Auth
alias Domain.{AccountsFixtures, AuthFixtures}
describe "identity_changeset/2" do
setup do
account = AccountsFixtures.create_account()
provider = AuthFixtures.create_token_provider(account: account)
account = Fixtures.Accounts.create_account()
provider = Fixtures.Auth.create_token_provider(account: account)
%{
account: account,
@@ -28,7 +27,7 @@ defmodule Domain.Auth.Adapters.TokenTest do
assert %{provider_state: state, provider_virtual_state: virtual_state} = changeset.changes
assert %{"secret_hash" => secret_hash} = state
assert %{secret: secret} = virtual_state
assert %{changes: %{secret: secret}} = virtual_state
assert Domain.Crypto.equal?(secret, secret_hash)
end
@@ -76,25 +75,25 @@ defmodule Domain.Auth.Adapters.TokenTest do
describe "ensure_provisioned/1" do
test "does nothing for a provider" do
provider = AuthFixtures.create_token_provider()
provider = Fixtures.Auth.create_token_provider()
assert ensure_provisioned(provider) == {:ok, provider}
end
end
describe "ensure_deprovisioned/1" do
test "does nothing for a provider" do
provider = AuthFixtures.create_token_provider()
provider = Fixtures.Auth.create_token_provider()
assert ensure_deprovisioned(provider) == {:ok, provider}
end
end
describe "verify_secret/2" do
setup do
account = AccountsFixtures.create_account()
provider = AuthFixtures.create_token_provider(account: account)
account = Fixtures.Accounts.create_account()
provider = Fixtures.Auth.create_token_provider(account: account)
identity =
AuthFixtures.create_identity(
Fixtures.Auth.create_identity(
account: account,
provider: provider,
provider_virtual_state: %{
@@ -124,13 +123,13 @@ defmodule Domain.Auth.Adapters.TokenTest do
)
|> Repo.update!()
assert verify_secret(identity, identity.provider_virtual_state.secret) ==
assert verify_secret(identity, identity.provider_virtual_state.changes.secret) ==
{:error, :expired_secret}
end
test "returns :ok on valid secret", %{identity: identity} do
assert {:ok, verified_identity, expires_at} =
verify_secret(identity, identity.provider_virtual_state.secret)
verify_secret(identity, identity.provider_virtual_state.changes.secret)
assert verified_identity.provider_state["secret_hash"] ==
identity.provider_state["secret_hash"]

View File

@@ -2,12 +2,11 @@ defmodule Domain.Auth.Adapters.UserPassTest do
use Domain.DataCase, async: true
import Domain.Auth.Adapters.UserPass
alias Domain.Auth
alias Domain.{AccountsFixtures, AuthFixtures}
describe "identity_changeset/2" do
setup do
account = AccountsFixtures.create_account()
provider = AuthFixtures.create_userpass_provider(account: account)
account = Fixtures.Accounts.create_account()
provider = Fixtures.Auth.create_userpass_provider(account: account)
%{
account: account,
@@ -26,12 +25,10 @@ defmodule Domain.Auth.Adapters.UserPassTest do
)
assert %Ecto.Changeset{} = changeset = identity_changeset(provider, changeset)
assert %{provider_state: state, provider_virtual_state: virtual_state} = changeset.changes
assert %{provider_state: state, provider_virtual_state: %{}} = changeset.changes
assert %{"password_hash" => password_hash} = state
assert Domain.Crypto.equal?("Firezone1234", password_hash)
assert virtual_state == %{}
end
test "returns error on invalid attrs", %{provider: provider} do
@@ -51,7 +48,7 @@ defmodule Domain.Auth.Adapters.UserPassTest do
assert errors_on(changeset) == %{
provider_virtual_state: %{
password: ["should be at least 12 byte(s)"],
password_confirmation: ["does not match confirmation"]
password_confirmation: ["does not match confirmation", "can't be blank"]
}
}
@@ -100,25 +97,25 @@ defmodule Domain.Auth.Adapters.UserPassTest do
describe "ensure_provisioned/1" do
test "does nothing for a provider" do
provider = AuthFixtures.create_userpass_provider()
provider = Fixtures.Auth.create_userpass_provider()
assert ensure_provisioned(provider) == {:ok, provider}
end
end
describe "ensure_deprovisioned/1" do
test "does nothing for a provider" do
provider = AuthFixtures.create_userpass_provider()
provider = Fixtures.Auth.create_userpass_provider()
assert ensure_deprovisioned(provider) == {:ok, provider}
end
end
describe "verify_secret/2" do
setup do
account = AccountsFixtures.create_account()
provider = AuthFixtures.create_userpass_provider(account: account)
account = Fixtures.Accounts.create_account()
provider = Fixtures.Auth.create_userpass_provider(account: account)
identity =
AuthFixtures.create_identity(
Fixtures.Auth.create_identity(
account: account,
provider: provider,
provider_virtual_state: %{

File diff suppressed because it is too large Load Diff

View File

@@ -55,6 +55,7 @@ defmodule Domain.Config.ValidatorTest do
{:error, [{"invalid", ["must be one of: integer, boolean"]}]}
end
# TODO: uncomment once we have at least one config embed
# test "validates embeds" do
# type = {:json_array, {:embed, Domain.Config.Configuration.SAMLIdentityProvider}}
@@ -62,7 +63,7 @@ defmodule Domain.Config.ValidatorTest do
# changeset: {Domain.Config.Configuration.SAMLIdentityProvider, :create_changeset, []}
# ]
# attrs = Domain.ConfigFixtures.saml_identity_providers_attrs()
# attrs = Domain.Fixtures.Config.saml_identity_providers_attrs()
# assert validate(:key, [attrs], type, opts) ==
# {:ok,

View File

@@ -2,8 +2,6 @@ defmodule Domain.ConfigTest do
use Domain.DataCase, async: true
import Domain.Config
alias Domain.Config
alias Domain.{AccountsFixtures, AuthFixtures, ActorsFixtures}
alias Domain.ConfigFixtures
defmodule Test do
use Domain.Config.Definition
@@ -85,8 +83,8 @@ defmodule Domain.ConfigTest do
describe "fetch_resolved_configs!/1" do
setup do
account = AccountsFixtures.create_account()
ConfigFixtures.upsert_configuration(account: account)
account = Fixtures.Accounts.create_account()
Fixtures.Config.upsert_configuration(account: account)
%{account: account}
end
@@ -132,8 +130,8 @@ defmodule Domain.ConfigTest do
describe "fetch_resolved_configs_with_sources!/1" do
setup do
account = AccountsFixtures.create_account()
ConfigFixtures.upsert_configuration(account: account)
account = Fixtures.Accounts.create_account()
Fixtures.Config.upsert_configuration(account: account)
%{account: account}
end
@@ -336,14 +334,14 @@ defmodule Domain.ConfigTest do
describe "get_account_config_by_account_id/1" do
setup do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
%{account: account}
end
test "returns configuration for an account if it exists", %{
account: account
} do
configuration = ConfigFixtures.upsert_configuration(account: account)
configuration = Fixtures.Config.upsert_configuration(account: account)
assert get_account_config_by_account_id(account.id) == configuration
end
@@ -359,11 +357,11 @@ defmodule Domain.ConfigTest do
describe "fetch_account_config/1" do
setup do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
subject = Fixtures.Auth.create_subject(identity: identity)
%{
account: account,
@@ -377,7 +375,7 @@ defmodule Domain.ConfigTest do
account: account,
subject: subject
} do
configuration = ConfigFixtures.upsert_configuration(account: account)
configuration = Fixtures.Config.upsert_configuration(account: account)
assert fetch_account_config(subject) == {:ok, configuration}
end
@@ -396,7 +394,7 @@ defmodule Domain.ConfigTest do
test "returns error when subject does not have permission to read configuration", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert fetch_account_config(subject) ==
{:error,
@@ -406,8 +404,8 @@ defmodule Domain.ConfigTest do
describe "change_account_config/2" do
setup do
account = AccountsFixtures.create_account()
configuration = ConfigFixtures.upsert_configuration(account: account)
account = Fixtures.Accounts.create_account()
configuration = Fixtures.Config.upsert_configuration(account: account)
%{account: account, configuration: configuration}
end
@@ -419,14 +417,14 @@ defmodule Domain.ConfigTest do
describe "update_config/3" do
test "returns error when subject can not manage account configuration" do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
config = get_account_config_by_account_id(account.id)
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
subject =
AuthFixtures.create_subject(identity)
|> AuthFixtures.remove_permissions()
Fixtures.Auth.create_subject(identity: identity)
|> Fixtures.Auth.remove_permissions()
assert update_config(config, %{}, subject) ==
{:error,
@@ -436,7 +434,7 @@ defmodule Domain.ConfigTest do
describe "update_config/2" do
setup do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
%{account: account}
end
@@ -495,7 +493,7 @@ defmodule Domain.ConfigTest do
end
test "changes database config value when it existed", %{account: account} do
ConfigFixtures.upsert_configuration(account: account)
Fixtures.Config.upsert_configuration(account: account)
config = get_account_config_by_account_id(account.id)
attrs = %{devices_upstream_dns: ["foobar.com", "google.com"]}

View File

@@ -1,23 +1,21 @@
defmodule Domain.DevicesTest do
use Domain.DataCase, async: true
import Domain.Devices
alias Domain.AccountsFixtures
alias Domain.{NetworkFixtures, ActorsFixtures, AuthFixtures, DevicesFixtures}
alias Domain.Devices
setup do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
unprivileged_actor = ActorsFixtures.create_actor(type: :account_user, account: account)
unprivileged_actor = Fixtures.Actors.create_actor(type: :account_user, account: account)
unprivileged_identity =
AuthFixtures.create_identity(account: account, actor: unprivileged_actor)
Fixtures.Auth.create_identity(account: account, actor: unprivileged_actor)
unprivileged_subject = AuthFixtures.create_subject(unprivileged_identity)
unprivileged_subject = Fixtures.Auth.create_subject(identity: unprivileged_identity)
admin_actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
admin_identity = AuthFixtures.create_identity(account: account, actor: admin_actor)
admin_subject = AuthFixtures.create_subject(admin_identity)
admin_actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
admin_identity = Fixtures.Auth.create_identity(account: account, actor: admin_actor)
admin_subject = Fixtures.Auth.create_subject(identity: admin_identity)
%{
account: account,
@@ -32,10 +30,10 @@ defmodule Domain.DevicesTest do
describe "count_by_account_id/0" do
test "counts devices for an account", %{account: account} do
DevicesFixtures.create_device(account: account)
DevicesFixtures.create_device(account: account)
DevicesFixtures.create_device(account: account)
DevicesFixtures.create_device()
Fixtures.Devices.create_device(account: account)
Fixtures.Devices.create_device(account: account)
Fixtures.Devices.create_device(account: account)
Fixtures.Devices.create_device()
assert count_by_account_id(account.id) == 3
end
@@ -47,7 +45,7 @@ defmodule Domain.DevicesTest do
end
test "returns count of devices for a actor" do
device = DevicesFixtures.create_device()
device = Fixtures.Devices.create_device()
assert count_by_actor_id(device.actor_id) == 1
end
end
@@ -62,14 +60,14 @@ defmodule Domain.DevicesTest do
unprivileged_subject: subject
} do
device =
DevicesFixtures.create_device(actor: actor)
|> DevicesFixtures.delete_device()
Fixtures.Devices.create_device(actor: actor)
|> Fixtures.Devices.delete_device()
assert fetch_device_by_id(device.id, subject) == {:error, :not_found}
end
test "returns device by id", %{unprivileged_actor: actor, unprivileged_subject: subject} do
device = DevicesFixtures.create_device(actor: actor)
device = Fixtures.Devices.create_device(actor: actor)
assert fetch_device_by_id(device.id, subject) == {:ok, device}
end
@@ -77,12 +75,12 @@ defmodule Domain.DevicesTest do
account: account,
unprivileged_subject: subject
} do
device = DevicesFixtures.create_device(account: account)
device = Fixtures.Devices.create_device(account: account)
subject =
subject
|> AuthFixtures.remove_permissions()
|> AuthFixtures.add_permission(Devices.Authorizer.manage_devices_permission())
|> Fixtures.Auth.remove_permissions()
|> Fixtures.Auth.add_permission(Devices.Authorizer.manage_devices_permission())
assert fetch_device_by_id(device.id, subject) == {:ok, device}
end
@@ -90,12 +88,12 @@ defmodule Domain.DevicesTest do
test "does not returns device that belongs to another account with manage permission", %{
unprivileged_subject: subject
} do
device = DevicesFixtures.create_device()
device = Fixtures.Devices.create_device()
subject =
subject
|> AuthFixtures.remove_permissions()
|> AuthFixtures.add_permission(Devices.Authorizer.manage_devices_permission())
|> Fixtures.Auth.remove_permissions()
|> Fixtures.Auth.add_permission(Devices.Authorizer.manage_devices_permission())
assert fetch_device_by_id(device.id, subject) == {:error, :not_found}
end
@@ -103,12 +101,12 @@ defmodule Domain.DevicesTest do
test "does not return device that belongs to another actor with manage_own permission", %{
unprivileged_subject: subject
} do
device = DevicesFixtures.create_device()
device = Fixtures.Devices.create_device()
subject =
subject
|> AuthFixtures.remove_permissions()
|> AuthFixtures.add_permission(Devices.Authorizer.manage_own_devices_permission())
|> Fixtures.Auth.remove_permissions()
|> Fixtures.Auth.add_permission(Devices.Authorizer.manage_own_devices_permission())
assert fetch_device_by_id(device.id, subject) == {:error, :not_found}
end
@@ -121,7 +119,7 @@ defmodule Domain.DevicesTest do
test "returns error when subject has no permission to view devices", %{
unprivileged_subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert fetch_device_by_id(Ecto.UUID.generate(), subject) ==
{:error,
@@ -147,8 +145,8 @@ defmodule Domain.DevicesTest do
unprivileged_actor: actor,
unprivileged_subject: subject
} do
DevicesFixtures.create_device(actor: actor)
|> DevicesFixtures.delete_device()
Fixtures.Devices.create_device(actor: actor)
|> Fixtures.Devices.delete_device()
assert list_devices(subject) == {:ok, []}
end
@@ -156,7 +154,7 @@ defmodule Domain.DevicesTest do
test "does not list devices in other accounts", %{
unprivileged_subject: subject
} do
DevicesFixtures.create_device()
Fixtures.Devices.create_device()
assert list_devices(subject) == {:ok, []}
end
@@ -166,8 +164,8 @@ defmodule Domain.DevicesTest do
admin_actor: other_actor,
unprivileged_subject: subject
} do
device = DevicesFixtures.create_device(actor: actor)
DevicesFixtures.create_device(actor: other_actor)
device = Fixtures.Devices.create_device(actor: actor)
Fixtures.Devices.create_device(actor: other_actor)
assert list_devices(subject) == {:ok, [device]}
end
@@ -177,8 +175,8 @@ defmodule Domain.DevicesTest do
admin_actor: admin_actor,
admin_subject: subject
} do
DevicesFixtures.create_device(actor: admin_actor)
DevicesFixtures.create_device(actor: other_actor)
Fixtures.Devices.create_device(actor: admin_actor)
Fixtures.Devices.create_device(actor: other_actor)
assert {:ok, devices} = list_devices(subject)
assert length(devices) == 2
@@ -187,7 +185,7 @@ defmodule Domain.DevicesTest do
test "returns error when subject has no permission to manage devices", %{
unprivileged_subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert list_devices(subject) ==
{:error,
@@ -211,7 +209,7 @@ defmodule Domain.DevicesTest do
} do
assert list_devices_by_actor_id(Ecto.UUID.generate(), subject) == {:ok, []}
assert list_devices_by_actor_id(actor.id, subject) == {:ok, []}
DevicesFixtures.create_device()
Fixtures.Devices.create_device()
assert list_devices_by_actor_id(actor.id, subject) == {:ok, []}
end
@@ -224,8 +222,8 @@ defmodule Domain.DevicesTest do
unprivileged_identity: identity,
unprivileged_subject: subject
} do
DevicesFixtures.create_device(identity: identity)
|> DevicesFixtures.delete_device()
Fixtures.Devices.create_device(identity: identity)
|> Fixtures.Devices.delete_device()
assert list_devices_by_actor_id(actor.id, subject) == {:ok, []}
end
@@ -234,8 +232,8 @@ defmodule Domain.DevicesTest do
unprivileged_subject: unprivileged_subject,
admin_subject: admin_subject
} do
actor = ActorsFixtures.create_actor(type: :account_user)
DevicesFixtures.create_device(actor: actor)
actor = Fixtures.Actors.create_actor(type: :account_user)
Fixtures.Devices.create_device(actor: actor)
assert list_devices_by_actor_id(actor.id, unprivileged_subject) == {:ok, []}
assert list_devices_by_actor_id(actor.id, admin_subject) == {:ok, []}
@@ -246,8 +244,8 @@ defmodule Domain.DevicesTest do
admin_actor: other_actor,
unprivileged_subject: subject
} do
device = DevicesFixtures.create_device(actor: actor)
DevicesFixtures.create_device(actor: other_actor)
device = Fixtures.Devices.create_device(actor: actor)
Fixtures.Devices.create_device(actor: other_actor)
assert list_devices_by_actor_id(actor.id, subject) == {:ok, [device]}
assert list_devices_by_actor_id(other_actor.id, subject) == {:ok, []}
@@ -258,8 +256,8 @@ defmodule Domain.DevicesTest do
admin_actor: admin_actor,
admin_subject: subject
} do
DevicesFixtures.create_device(actor: admin_actor)
DevicesFixtures.create_device(actor: other_actor)
Fixtures.Devices.create_device(actor: admin_actor)
Fixtures.Devices.create_device(actor: other_actor)
assert {:ok, [_device]} = list_devices_by_actor_id(admin_actor.id, subject)
assert {:ok, [_device]} = list_devices_by_actor_id(other_actor.id, subject)
@@ -268,7 +266,7 @@ defmodule Domain.DevicesTest do
test "returns error when subject has no permission to manage devices", %{
unprivileged_subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert list_devices_by_actor_id(Ecto.UUID.generate(), subject) ==
{:error,
@@ -287,8 +285,8 @@ defmodule Domain.DevicesTest do
describe "change_device/1" do
test "returns changeset with given changes", %{admin_actor: actor} do
device = DevicesFixtures.create_device(actor: actor)
device_attrs = DevicesFixtures.device_attrs()
device = Fixtures.Devices.create_device(actor: actor)
device_attrs = Fixtures.Devices.device_attrs()
assert changeset = change_device(device, device_attrs)
assert %Ecto.Changeset{data: %Domain.Devices.Device{}} = changeset
@@ -322,7 +320,7 @@ defmodule Domain.DevicesTest do
admin_subject: subject
} do
attrs =
DevicesFixtures.device_attrs()
Fixtures.Devices.device_attrs()
|> Map.delete(:name)
assert {:ok, device} = upsert_device(attrs, subject)
@@ -347,8 +345,8 @@ defmodule Domain.DevicesTest do
test "updates device when it already exists", %{
admin_subject: subject
} do
device = DevicesFixtures.create_device(subject: subject)
attrs = DevicesFixtures.device_attrs(external_id: device.external_id)
device = Fixtures.Devices.create_device(subject: subject)
attrs = Fixtures.Devices.device_attrs(external_id: device.external_id)
subject = %{
subject
@@ -383,10 +381,10 @@ defmodule Domain.DevicesTest do
test "does not reserve additional addresses on update", %{
admin_subject: subject
} do
device = DevicesFixtures.create_device(subject: subject)
device = Fixtures.Devices.create_device(subject: subject)
attrs =
DevicesFixtures.device_attrs(
Fixtures.Devices.device_attrs(
external_id: device.external_id,
last_seen_user_agent: "iOS/12.5 (iPhone) connlib/0.7.411",
last_seen_remote_ip: %Postgrex.INET{address: {100, 64, 100, 100}}
@@ -410,7 +408,7 @@ defmodule Domain.DevicesTest do
admin_subject: subject
} do
attrs =
DevicesFixtures.device_attrs()
Fixtures.Devices.device_attrs()
|> Map.delete(:name)
assert {:ok, _device} = upsert_device(attrs, subject)
@@ -420,7 +418,7 @@ defmodule Domain.DevicesTest do
account: account,
admin_subject: subject
} do
attrs = DevicesFixtures.device_attrs(account: account)
attrs = Fixtures.Devices.device_attrs(account: account)
assert {:ok, device} = upsert_device(attrs, subject)
addresses =
@@ -435,11 +433,11 @@ defmodule Domain.DevicesTest do
assert %{address: device.ipv6, type: :ipv6} in addresses
assert_raise Ecto.ConstraintError, fn ->
NetworkFixtures.create_address(address: device.ipv4, account: account)
Fixtures.Network.create_address(address: device.ipv4, account: account)
end
assert_raise Ecto.ConstraintError, fn ->
NetworkFixtures.create_address(address: device.ipv6, account: account)
Fixtures.Network.create_address(address: device.ipv6, account: account)
end
end
@@ -447,17 +445,17 @@ defmodule Domain.DevicesTest do
account: account,
admin_subject: subject
} do
attrs = DevicesFixtures.device_attrs(account: account)
attrs = Fixtures.Devices.device_attrs(account: account)
assert {:ok, device} = upsert_device(attrs, subject)
assert %Domain.Network.Address{} = NetworkFixtures.create_address(address: device.ipv4)
assert %Domain.Network.Address{} = NetworkFixtures.create_address(address: device.ipv6)
assert %Domain.Network.Address{} = Fixtures.Network.create_address(address: device.ipv4)
assert %Domain.Network.Address{} = Fixtures.Network.create_address(address: device.ipv6)
end
test "returns error when subject has no permission to create devices", %{
admin_subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert upsert_device(%{}, subject) ==
{:error,
@@ -468,7 +466,7 @@ defmodule Domain.DevicesTest do
describe "update_device/3" do
test "allows admin actor to update own devices", %{admin_actor: actor, admin_subject: subject} do
device = DevicesFixtures.create_device(actor: actor)
device = Fixtures.Devices.create_device(actor: actor)
attrs = %{name: "new name"}
assert {:ok, device} = update_device(device, attrs, subject)
@@ -480,7 +478,7 @@ defmodule Domain.DevicesTest do
account: account,
admin_subject: subject
} do
device = DevicesFixtures.create_device(account: account)
device = Fixtures.Devices.create_device(account: account)
attrs = %{name: "new name"}
assert {:ok, device} = update_device(device, attrs, subject)
@@ -492,7 +490,7 @@ defmodule Domain.DevicesTest do
unprivileged_actor: actor,
unprivileged_subject: subject
} do
device = DevicesFixtures.create_device(actor: actor)
device = Fixtures.Devices.create_device(actor: actor)
attrs = %{name: "new name"}
assert {:ok, device} = update_device(device, attrs, subject)
@@ -504,7 +502,7 @@ defmodule Domain.DevicesTest do
account: account,
unprivileged_subject: subject
} do
device = DevicesFixtures.create_device(account: account)
device = Fixtures.Devices.create_device(account: account)
attrs = %{name: "new name"}
assert update_device(device, attrs, subject) ==
@@ -516,7 +514,7 @@ defmodule Domain.DevicesTest do
test "does not allow admin actor to update devices in other accounts", %{
admin_subject: subject
} do
device = DevicesFixtures.create_device()
device = Fixtures.Devices.create_device()
attrs = %{name: "new name"}
assert update_device(device, attrs, subject) == {:error, :not_found}
@@ -526,7 +524,7 @@ defmodule Domain.DevicesTest do
admin_actor: actor,
admin_subject: subject
} do
device = DevicesFixtures.create_device(actor: actor)
device = Fixtures.Devices.create_device(actor: actor)
attrs = %{name: nil, public_key: nil}
assert {:error, changeset} = update_device(device, attrs, subject)
@@ -535,7 +533,7 @@ defmodule Domain.DevicesTest do
end
test "returns error on invalid attrs", %{admin_actor: actor, admin_subject: subject} do
device = DevicesFixtures.create_device(actor: actor)
device = Fixtures.Devices.create_device(actor: actor)
attrs = %{
name: String.duplicate("a", 256)
@@ -552,7 +550,7 @@ defmodule Domain.DevicesTest do
admin_actor: actor,
admin_subject: subject
} do
device = DevicesFixtures.create_device(actor: actor)
device = Fixtures.Devices.create_device(actor: actor)
fields = Devices.Device.__schema__(:fields) -- [:name]
value = -1
@@ -567,16 +565,16 @@ defmodule Domain.DevicesTest do
admin_actor: actor,
admin_subject: subject
} do
device = DevicesFixtures.create_device(actor: actor)
device = Fixtures.Devices.create_device(actor: actor)
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert update_device(device, %{}, subject) ==
{:error,
{:unauthorized,
[missing_permissions: [Devices.Authorizer.manage_own_devices_permission()]]}}
device = DevicesFixtures.create_device()
device = Fixtures.Devices.create_device()
assert update_device(device, %{}, subject) ==
{:error,
@@ -587,7 +585,7 @@ defmodule Domain.DevicesTest do
describe "delete_device/2" do
test "returns error on state conflict", %{admin_actor: actor, admin_subject: subject} do
device = DevicesFixtures.create_device(actor: actor)
device = Fixtures.Devices.create_device(actor: actor)
assert {:ok, deleted} = delete_device(device, subject)
assert delete_device(deleted, subject) == {:error, :not_found}
@@ -595,7 +593,7 @@ defmodule Domain.DevicesTest do
end
test "admin can delete own devices", %{admin_actor: actor, admin_subject: subject} do
device = DevicesFixtures.create_device(actor: actor)
device = Fixtures.Devices.create_device(actor: actor)
assert {:ok, deleted} = delete_device(device, subject)
assert deleted.deleted_at
@@ -605,7 +603,7 @@ defmodule Domain.DevicesTest do
unprivileged_actor: actor,
admin_subject: subject
} do
device = DevicesFixtures.create_device(actor: actor)
device = Fixtures.Devices.create_device(actor: actor)
assert {:ok, deleted} = delete_device(device, subject)
assert deleted.deleted_at
@@ -614,7 +612,7 @@ defmodule Domain.DevicesTest do
test "admin can not delete devices in other accounts", %{
admin_subject: subject
} do
device = DevicesFixtures.create_device()
device = Fixtures.Devices.create_device()
assert delete_device(device, subject) == {:error, :not_found}
end
@@ -624,7 +622,7 @@ defmodule Domain.DevicesTest do
unprivileged_actor: actor,
unprivileged_subject: subject
} do
device = DevicesFixtures.create_device(account: account, actor: actor)
device = Fixtures.Devices.create_device(account: account, actor: actor)
assert {:ok, deleted} = delete_device(device, subject)
assert deleted.deleted_at
@@ -634,14 +632,14 @@ defmodule Domain.DevicesTest do
account: account,
unprivileged_subject: subject
} do
device = DevicesFixtures.create_device()
device = Fixtures.Devices.create_device()
assert delete_device(device, subject) ==
{:error,
{:unauthorized,
[missing_permissions: [Devices.Authorizer.manage_devices_permission()]]}}
device = DevicesFixtures.create_device(account: account)
device = Fixtures.Devices.create_device(account: account)
assert delete_device(device, subject) ==
{:error,
@@ -655,16 +653,16 @@ defmodule Domain.DevicesTest do
admin_actor: actor,
admin_subject: subject
} do
device = DevicesFixtures.create_device(actor: actor)
device = Fixtures.Devices.create_device(actor: actor)
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert delete_device(device, subject) ==
{:error,
{:unauthorized,
[missing_permissions: [Devices.Authorizer.manage_own_devices_permission()]]}}
device = DevicesFixtures.create_device()
device = Fixtures.Devices.create_device()
assert delete_device(device, subject) ==
{:error,
@@ -672,4 +670,25 @@ defmodule Domain.DevicesTest do
[missing_permissions: [Devices.Authorizer.manage_devices_permission()]]}}
end
end
describe "delete_actor_devices/1" do
test "removes all devices that belong to an actor" do
actor = Fixtures.Actors.create_actor()
Fixtures.Devices.create_device(actor: actor)
Fixtures.Devices.create_device(actor: actor)
Fixtures.Devices.create_device(actor: actor)
assert Repo.aggregate(Devices.Device.Query.all(), :count) == 3
assert delete_actor_devices(actor) == :ok
assert Repo.aggregate(Devices.Device.Query.all(), :count) == 0
end
test "does not remove devices that belong to another actor" do
actor = Fixtures.Actors.create_actor()
Fixtures.Devices.create_device()
assert delete_actor_devices(actor) == :ok
assert Repo.aggregate(Devices.Device.Query.all(), :count) == 1
end
end
end

View File

@@ -1,15 +1,13 @@
defmodule Domain.GatewaysTest do
use Domain.DataCase, async: true
import Domain.Gateways
alias Domain.{AccountsFixtures, ResourcesFixtures}
alias Domain.{NetworkFixtures, ActorsFixtures, AuthFixtures, GatewaysFixtures}
alias Domain.Gateways
setup do
account = AccountsFixtures.create_account()
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
account = Fixtures.Accounts.create_account()
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
subject = Fixtures.Auth.create_subject(identity: identity)
%{
account: account,
@@ -27,7 +25,7 @@ defmodule Domain.GatewaysTest do
test "does not return groups from other accounts", %{
subject: subject
} do
group = GatewaysFixtures.create_group()
group = Fixtures.Gateways.create_group()
assert fetch_group_by_id(group.id, subject) == {:error, :not_found}
end
@@ -36,14 +34,14 @@ defmodule Domain.GatewaysTest do
subject: subject
} do
group =
GatewaysFixtures.create_group(account: account)
|> GatewaysFixtures.delete_group()
Fixtures.Gateways.create_group(account: account)
|> Fixtures.Gateways.delete_group()
assert fetch_group_by_id(group.id, subject) == {:error, :not_found}
end
test "returns group by id", %{account: account, subject: subject} do
group = GatewaysFixtures.create_group(account: account)
group = Fixtures.Gateways.create_group(account: account)
assert {:ok, fetched_group} = fetch_group_by_id(group.id, subject)
assert fetched_group.id == group.id
end
@@ -52,7 +50,7 @@ defmodule Domain.GatewaysTest do
account: account,
subject: subject
} do
group = GatewaysFixtures.create_group(account: account)
group = Fixtures.Gateways.create_group(account: account)
assert {:ok, fetched_group} = fetch_group_by_id(group.id, subject)
assert fetched_group.id == group.id
end
@@ -65,7 +63,7 @@ defmodule Domain.GatewaysTest do
test "returns error when subject has no permission to view groups", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert fetch_group_by_id(Ecto.UUID.generate(), subject) ==
{:error,
@@ -82,7 +80,7 @@ defmodule Domain.GatewaysTest do
test "does not list groups from other accounts", %{
subject: subject
} do
GatewaysFixtures.create_group()
Fixtures.Gateways.create_group()
assert list_groups(subject) == {:ok, []}
end
@@ -90,8 +88,8 @@ defmodule Domain.GatewaysTest do
account: account,
subject: subject
} do
GatewaysFixtures.create_group(account: account)
|> GatewaysFixtures.delete_group()
Fixtures.Gateways.create_group(account: account)
|> Fixtures.Gateways.delete_group()
assert list_groups(subject) == {:ok, []}
end
@@ -100,9 +98,9 @@ defmodule Domain.GatewaysTest do
account: account,
subject: subject
} do
GatewaysFixtures.create_group(account: account)
GatewaysFixtures.create_group(account: account)
GatewaysFixtures.create_group()
Fixtures.Gateways.create_group(account: account)
Fixtures.Gateways.create_group(account: account)
Fixtures.Gateways.create_group()
assert {:ok, groups} = list_groups(subject)
assert length(groups) == 2
@@ -111,7 +109,7 @@ defmodule Domain.GatewaysTest do
test "returns error when subject has no permission to manage groups", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert list_groups(subject) ==
{:error,
@@ -156,7 +154,7 @@ defmodule Domain.GatewaysTest do
assert {:error, changeset} = create_group(attrs, subject)
assert "should be at most 64 characters long" in errors_on(changeset).tags
GatewaysFixtures.create_group(account: account, name_prefix: "foo")
Fixtures.Gateways.create_group(account: account, name_prefix: "foo")
attrs = %{name_prefix: "foo", tokens: [%{}]}
assert {:error, changeset} = create_group(attrs, subject)
assert "has already been taken" in errors_on(changeset).name_prefix
@@ -185,7 +183,7 @@ defmodule Domain.GatewaysTest do
test "returns error when subject has no permission to manage groups", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert create_group(%{}, subject) ==
{:error,
@@ -196,10 +194,10 @@ defmodule Domain.GatewaysTest do
describe "change_group/1" do
test "returns changeset with given changes" do
group = GatewaysFixtures.create_group()
group = Fixtures.Gateways.create_group()
group_attrs =
GatewaysFixtures.group_attrs()
Fixtures.Gateways.group_attrs()
|> Map.delete(:tokens)
assert changeset = change_group(group, group_attrs)
@@ -212,7 +210,7 @@ defmodule Domain.GatewaysTest do
test "does not allow to reset required fields to empty values", %{
subject: subject
} do
group = GatewaysFixtures.create_group()
group = Fixtures.Gateways.create_group()
attrs = %{name_prefix: nil}
assert {:error, changeset} = update_group(group, attrs, subject)
@@ -221,7 +219,7 @@ defmodule Domain.GatewaysTest do
end
test "returns error on invalid attrs", %{account: account, subject: subject} do
group = GatewaysFixtures.create_group(account: account)
group = Fixtures.Gateways.create_group(account: account)
attrs = %{
name_prefix: String.duplicate("A", 65),
@@ -243,14 +241,14 @@ defmodule Domain.GatewaysTest do
assert {:error, changeset} = update_group(group, attrs, subject)
assert "should be at most 64 characters long" in errors_on(changeset).tags
GatewaysFixtures.create_group(account: account, name_prefix: "foo")
Fixtures.Gateways.create_group(account: account, name_prefix: "foo")
attrs = %{name_prefix: "foo"}
assert {:error, changeset} = update_group(group, attrs, subject)
assert "has already been taken" in errors_on(changeset).name_prefix
end
test "updates a group", %{account: account, subject: subject} do
group = GatewaysFixtures.create_group(account: account)
group = Fixtures.Gateways.create_group(account: account)
attrs = %{
name_prefix: "foo",
@@ -266,9 +264,9 @@ defmodule Domain.GatewaysTest do
account: account,
subject: subject
} do
group = GatewaysFixtures.create_group(account: account)
group = Fixtures.Gateways.create_group(account: account)
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert update_group(group, %{}, subject) ==
{:error,
@@ -279,7 +277,7 @@ defmodule Domain.GatewaysTest do
describe "delete_group/2" do
test "returns error on state conflict", %{account: account, subject: subject} do
group = GatewaysFixtures.create_group(account: account)
group = Fixtures.Gateways.create_group(account: account)
assert {:ok, deleted} = delete_group(group, subject)
assert delete_group(deleted, subject) == {:error, :not_found}
@@ -287,16 +285,16 @@ defmodule Domain.GatewaysTest do
end
test "deletes groups", %{account: account, subject: subject} do
group = GatewaysFixtures.create_group(account: account)
group = Fixtures.Gateways.create_group(account: account)
assert {:ok, deleted} = delete_group(group, subject)
assert deleted.deleted_at
end
test "deletes all tokens when group is deleted", %{account: account, subject: subject} do
group = GatewaysFixtures.create_group(account: account)
GatewaysFixtures.create_token(group: group)
GatewaysFixtures.create_token(group: [account: account])
group = Fixtures.Gateways.create_group(account: account)
Fixtures.Gateways.create_token(group: group)
Fixtures.Gateways.create_token(group: [account: account])
assert {:ok, deleted} = delete_group(group, subject)
assert deleted.deleted_at
@@ -312,9 +310,9 @@ defmodule Domain.GatewaysTest do
test "returns error when subject has no permission to delete groups", %{
subject: subject
} do
group = GatewaysFixtures.create_group()
group = Fixtures.Gateways.create_group()
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert delete_group(group, subject) ==
{:error,
@@ -325,7 +323,7 @@ defmodule Domain.GatewaysTest do
describe "use_token_by_id_and_secret/2" do
test "returns token when secret is valid" do
token = GatewaysFixtures.create_token()
token = Fixtures.Gateways.create_token()
assert {:ok, token} = use_token_by_id_and_secret(token.id, token.value)
assert is_nil(token.value)
# TODO: While we don't have token rotation implemented, the tokens are all multi-use
@@ -335,7 +333,7 @@ defmodule Domain.GatewaysTest do
# TODO: While we don't have token rotation implemented, the tokens are all multi-use
# test "returns error when secret was already used" do
# token = GatewaysFixtures.create_token()
# token = Fixtures.Gateways.create_token()
# assert {:ok, _token} = use_token_by_id_and_secret(token.id, token.value)
# assert use_token_by_id_and_secret(token.id, token.value) == {:error, :not_found}
@@ -350,7 +348,7 @@ defmodule Domain.GatewaysTest do
end
test "returns error when secret is invalid" do
token = GatewaysFixtures.create_token()
token = Fixtures.Gateways.create_token()
assert use_token_by_id_and_secret(token.id, "bar") == {:error, :not_found}
end
end
@@ -363,7 +361,7 @@ defmodule Domain.GatewaysTest do
test "does not return gateways from other accounts", %{
subject: subject
} do
gateway = GatewaysFixtures.create_gateway()
gateway = Fixtures.Gateways.create_gateway()
assert fetch_gateway_by_id(gateway.id, subject) == {:error, :not_found}
end
@@ -372,14 +370,14 @@ defmodule Domain.GatewaysTest do
subject: subject
} do
gateway =
GatewaysFixtures.create_gateway(account: account)
|> GatewaysFixtures.delete_gateway()
Fixtures.Gateways.create_gateway(account: account)
|> Fixtures.Gateways.delete_gateway()
assert fetch_gateway_by_id(gateway.id, subject) == {:error, :not_found}
end
test "returns gateway by id", %{account: account, subject: subject} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
assert fetch_gateway_by_id(gateway.id, subject) == {:ok, gateway}
end
@@ -387,7 +385,7 @@ defmodule Domain.GatewaysTest do
account: account,
subject: subject
} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
assert fetch_gateway_by_id(gateway.id, subject) == {:ok, gateway}
end
@@ -399,7 +397,7 @@ defmodule Domain.GatewaysTest do
test "returns error when subject has no permission to view gateways", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert fetch_gateway_by_id(Ecto.UUID.generate(), subject) ==
{:error,
@@ -417,7 +415,7 @@ defmodule Domain.GatewaysTest do
# TODO: add a test that soft-deleted assocs are not preloaded
test "associations are preloaded when opts given", %{account: account, subject: subject} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
{:ok, gateway} = fetch_gateway_by_id(gateway.id, subject, preload: [:group, :account])
assert Ecto.assoc_loaded?(gateway.group) == true
@@ -433,8 +431,8 @@ defmodule Domain.GatewaysTest do
test "does not list deleted gateways", %{
subject: subject
} do
GatewaysFixtures.create_gateway()
|> GatewaysFixtures.delete_gateway()
Fixtures.Gateways.create_gateway()
|> Fixtures.Gateways.delete_gateway()
assert list_gateways(subject) == {:ok, []}
end
@@ -443,10 +441,10 @@ defmodule Domain.GatewaysTest do
account: account,
subject: subject
} do
offline_gateway = GatewaysFixtures.create_gateway(account: account)
online_gateway = GatewaysFixtures.create_gateway(account: account)
offline_gateway = Fixtures.Gateways.create_gateway(account: account)
online_gateway = Fixtures.Gateways.create_gateway(account: account)
:ok = connect_gateway(online_gateway)
GatewaysFixtures.create_gateway()
Fixtures.Gateways.create_gateway()
assert {:ok, gateways} = list_gateways(subject)
assert length(gateways) == 2
@@ -463,7 +461,7 @@ defmodule Domain.GatewaysTest do
test "returns error when subject has no permission to manage gateways", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert list_gateways(subject) ==
{:error,
@@ -473,8 +471,8 @@ defmodule Domain.GatewaysTest do
# TODO: add a test that soft-deleted assocs are not preloaded
test "associations are preloaded when opts given", %{account: account, subject: subject} do
GatewaysFixtures.create_gateway(account: account)
GatewaysFixtures.create_gateway(account: account)
Fixtures.Gateways.create_gateway(account: account)
Fixtures.Gateways.create_gateway(account: account)
{:ok, gateways} = list_gateways(subject, preload: [:group, :account])
assert length(gateways) == 2
@@ -486,23 +484,23 @@ defmodule Domain.GatewaysTest do
describe "list_connected_gateways_for_resource/1" do
test "returns empty list when there are no online gateways", %{account: account} do
resource = ResourcesFixtures.create_resource(account: account)
resource = Fixtures.Resources.create_resource(account: account)
GatewaysFixtures.create_gateway(account: account)
Fixtures.Gateways.create_gateway(account: account)
GatewaysFixtures.create_gateway(account: account)
|> GatewaysFixtures.delete_gateway()
Fixtures.Gateways.create_gateway(account: account)
|> Fixtures.Gateways.delete_gateway()
assert list_connected_gateways_for_resource(resource) == {:ok, []}
end
test "returns list of connected gateways for a given resource", %{account: account} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
resource =
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: gateway.group_id}]
connections: [%{gateway_group_id: gateway.group_id}]
)
assert connect_gateway(gateway) == :ok
@@ -514,8 +512,8 @@ defmodule Domain.GatewaysTest do
test "does not return connected gateways that are not connected to given resource", %{
account: account
} do
resource = ResourcesFixtures.create_resource(account: account)
gateway = GatewaysFixtures.create_gateway(account: account)
resource = Fixtures.Resources.create_resource(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
assert connect_gateway(gateway) == :ok
@@ -525,30 +523,30 @@ defmodule Domain.GatewaysTest do
describe "gateway_can_connect_to_resource?/2" do
test "returns true when gateway can connect to resource", %{account: account} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
:ok = connect_gateway(gateway)
resource =
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: gateway.group_id}]
connections: [%{gateway_group_id: gateway.group_id}]
)
assert gateway_can_connect_to_resource?(gateway, resource)
end
test "returns false when gateway cannot connect to resource", %{account: account} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
:ok = connect_gateway(gateway)
resource = ResourcesFixtures.create_resource(account: account)
resource = Fixtures.Resources.create_resource(account: account)
refute gateway_can_connect_to_resource?(gateway, resource)
end
test "returns false when gateway is offline", %{account: account} do
gateway = GatewaysFixtures.create_gateway(account: account)
resource = ResourcesFixtures.create_resource(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
resource = Fixtures.Resources.create_resource(account: account)
refute gateway_can_connect_to_resource?(gateway, resource)
end
@@ -556,8 +554,8 @@ defmodule Domain.GatewaysTest do
describe "change_gateway/1" do
test "returns changeset with given changes" do
gateway = GatewaysFixtures.create_gateway()
gateway_attrs = GatewaysFixtures.gateway_attrs()
gateway = Fixtures.Gateways.create_gateway()
gateway_attrs = Fixtures.Gateways.gateway_attrs()
assert changeset = change_gateway(gateway, gateway_attrs)
assert %Ecto.Changeset{data: %Domain.Gateways.Gateway{}} = changeset
@@ -568,7 +566,7 @@ defmodule Domain.GatewaysTest do
describe "upsert_gateway/3" do
setup context do
token = GatewaysFixtures.create_token(account: context.account)
token = Fixtures.Gateways.create_token(account: context.account)
context
|> Map.put(:token, token)
@@ -598,7 +596,7 @@ defmodule Domain.GatewaysTest do
token: token
} do
attrs =
GatewaysFixtures.gateway_attrs()
Fixtures.Gateways.gateway_attrs()
|> Map.delete(:name)
assert {:ok, gateway} = upsert_gateway(token, attrs)
@@ -621,10 +619,10 @@ defmodule Domain.GatewaysTest do
test "updates gateway when it already exists", %{
token: token
} do
gateway = GatewaysFixtures.create_gateway(token: token)
gateway = Fixtures.Gateways.create_gateway(token: token)
attrs =
GatewaysFixtures.gateway_attrs(
Fixtures.Gateways.gateway_attrs(
external_id: gateway.external_id,
last_seen_remote_ip: {100, 64, 100, 101},
last_seen_user_agent: "iOS/12.5 (iPhone) connlib/0.7.411"
@@ -655,10 +653,10 @@ defmodule Domain.GatewaysTest do
test "does not reserve additional addresses on update", %{
token: token
} do
gateway = GatewaysFixtures.create_gateway(token: token)
gateway = Fixtures.Gateways.create_gateway(token: token)
attrs =
GatewaysFixtures.gateway_attrs(
Fixtures.Gateways.gateway_attrs(
external_id: gateway.external_id,
last_seen_user_agent: "iOS/12.5 (iPhone) connlib/0.7.411",
last_seen_remote_ip: %Postgrex.INET{address: {100, 64, 100, 100}}
@@ -682,7 +680,7 @@ defmodule Domain.GatewaysTest do
account: account,
token: token
} do
attrs = GatewaysFixtures.gateway_attrs()
attrs = Fixtures.Gateways.gateway_attrs()
assert {:ok, gateway} = upsert_gateway(token, attrs)
addresses =
@@ -697,14 +695,14 @@ defmodule Domain.GatewaysTest do
assert %{address: gateway.ipv6, type: :ipv6} in addresses
assert_raise Ecto.ConstraintError, fn ->
NetworkFixtures.create_address(account: account, address: gateway.ipv4)
Fixtures.Network.create_address(account: account, address: gateway.ipv4)
end
end
end
describe "update_gateway/3" do
test "updates gateways", %{account: account, subject: subject} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
attrs = %{name_suffix: "Foo"}
assert {:ok, gateway} = update_gateway(gateway, attrs, subject)
@@ -716,7 +714,7 @@ defmodule Domain.GatewaysTest do
account: account,
subject: subject
} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
attrs = %{name_suffix: nil}
assert {:error, changeset} = update_gateway(gateway, attrs, subject)
@@ -725,7 +723,7 @@ defmodule Domain.GatewaysTest do
end
test "returns error on invalid attrs", %{account: account, subject: subject} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
attrs = %{
name_suffix: String.duplicate("a", 256)
@@ -742,7 +740,7 @@ defmodule Domain.GatewaysTest do
account: account,
subject: subject
} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
fields = Gateways.Gateway.__schema__(:fields) -- [:name_suffix]
value = -1
@@ -756,9 +754,9 @@ defmodule Domain.GatewaysTest do
test "returns error when subject has no permission to update gateways", %{
subject: subject
} do
gateway = GatewaysFixtures.create_gateway()
gateway = Fixtures.Gateways.create_gateway()
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert update_gateway(gateway, %{}, subject) ==
{:error,
@@ -769,7 +767,7 @@ defmodule Domain.GatewaysTest do
describe "delete_gateway/2" do
test "returns error on state conflict", %{account: account, subject: subject} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
assert {:ok, deleted} = delete_gateway(gateway, subject)
assert delete_gateway(deleted, subject) == {:error, :not_found}
@@ -777,7 +775,7 @@ defmodule Domain.GatewaysTest do
end
test "deletes gateways", %{account: account, subject: subject} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
assert {:ok, deleted} = delete_gateway(gateway, subject)
assert deleted.deleted_at
@@ -786,9 +784,9 @@ defmodule Domain.GatewaysTest do
test "returns error when subject has no permission to delete gateways", %{
subject: subject
} do
gateway = GatewaysFixtures.create_gateway()
gateway = Fixtures.Gateways.create_gateway()
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert delete_gateway(gateway, subject) ==
{:error,
@@ -799,26 +797,26 @@ defmodule Domain.GatewaysTest do
describe "load_balance_gateways/1" do
test "returns random gateway" do
gateways = Enum.map(1..10, fn _ -> GatewaysFixtures.create_gateway() end)
gateways = Enum.map(1..10, fn _ -> Fixtures.Gateways.create_gateway() end)
assert Enum.member?(gateways, load_balance_gateways(gateways))
end
end
describe "load_balance_gateways/2" do
test "returns random gateway if no gateways are already connected" do
gateways = Enum.map(1..10, fn _ -> GatewaysFixtures.create_gateway() end)
gateways = Enum.map(1..10, fn _ -> Fixtures.Gateways.create_gateway() end)
assert Enum.member?(gateways, load_balance_gateways(gateways, []))
end
test "reuses gateway that is already connected to reduce the latency" do
gateways = Enum.map(1..10, fn _ -> GatewaysFixtures.create_gateway() end)
gateways = Enum.map(1..10, fn _ -> Fixtures.Gateways.create_gateway() end)
[connected_gateway | _] = gateways
assert load_balance_gateways(gateways, [connected_gateway.id]) == connected_gateway
end
test "returns random gateway from the connected ones" do
gateways = Enum.map(1..10, fn _ -> GatewaysFixtures.create_gateway() end)
gateways = Enum.map(1..10, fn _ -> Fixtures.Gateways.create_gateway() end)
[connected_gateway1, connected_gateway2 | _] = gateways
assert load_balance_gateways(gateways, [connected_gateway1.id, connected_gateway2.id]) in [
@@ -830,7 +828,7 @@ defmodule Domain.GatewaysTest do
describe "encode_token!/1" do
test "returns encoded token" do
token = GatewaysFixtures.create_token()
token = Fixtures.Gateways.create_token()
assert encrypted_secret = encode_token!(token)
config = Application.fetch_env!(:domain, Domain.Gateways)
@@ -844,7 +842,7 @@ defmodule Domain.GatewaysTest do
describe "authorize_gateway/1" do
test "returns token when encoded secret is valid" do
token = GatewaysFixtures.create_token()
token = Fixtures.Gateways.create_token()
encoded_token = encode_token!(token)
assert {:ok, fetched_token} = authorize_gateway(encoded_token)
assert fetched_token.id == token.id

View File

@@ -1,10 +1,9 @@
defmodule Domain.Network.Address.QueryTest do
use Domain.DataCase, async: true
import Domain.Network.Address.Query
alias Domain.{AccountsFixtures, NetworkFixtures}
setup do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
%{account: account}
end
@@ -23,7 +22,7 @@ defmodule Domain.Network.Address.QueryTest do
offset = 3
queryable = next_available_address(account.id, cidr, offset)
NetworkFixtures.create_address(account: account, address: "10.3.3.3")
Fixtures.Network.create_address(account: account, address: "10.3.3.3")
assert Repo.one(queryable) == %Postgrex.INET{address: {10, 3, 3, 4}}
end
@@ -33,7 +32,7 @@ defmodule Domain.Network.Address.QueryTest do
offset = 3
queryable = next_available_address(account.id, cidr, offset)
NetworkFixtures.create_address(address: "10.3.3.3")
Fixtures.Network.create_address(address: "10.3.3.3")
assert Repo.one(queryable) == %Postgrex.INET{address: {10, 3, 3, 3}}
end
@@ -46,11 +45,11 @@ defmodule Domain.Network.Address.QueryTest do
queryable = next_available_address(account.id, cidr, offset)
NetworkFixtures.create_address(account: account, address: "10.3.4.3")
NetworkFixtures.create_address(account: account, address: "10.3.4.4")
Fixtures.Network.create_address(account: account, address: "10.3.4.3")
Fixtures.Network.create_address(account: account, address: "10.3.4.4")
assert Repo.one(queryable) == %Postgrex.INET{address: {10, 3, 4, 5}}
NetworkFixtures.create_address(account: account, address: "10.3.4.5")
Fixtures.Network.create_address(account: account, address: "10.3.4.5")
assert Repo.one(queryable) == %Postgrex.INET{address: {10, 3, 4, 6}}
end
@@ -62,13 +61,13 @@ defmodule Domain.Network.Address.QueryTest do
queryable = next_available_address(account.id, cidr, offset)
NetworkFixtures.create_address(account: account, address: "10.3.5.5")
NetworkFixtures.create_address(account: account, address: "10.3.5.6")
Fixtures.Network.create_address(account: account, address: "10.3.5.5")
Fixtures.Network.create_address(account: account, address: "10.3.5.6")
# Notice: end of range is 10.3.5.7
# but it's a broadcast address that we don't allow to assign
assert Repo.one(queryable) == %Postgrex.INET{address: {10, 3, 5, 4}}
NetworkFixtures.create_address(account: account, address: "10.3.5.4")
Fixtures.Network.create_address(account: account, address: "10.3.5.4")
assert Repo.one(queryable) == %Postgrex.INET{address: {10, 3, 5, 3}}
end
@@ -76,8 +75,8 @@ defmodule Domain.Network.Address.QueryTest do
cidr = string_to_cidr("10.3.6.0/30")
offset = 1
NetworkFixtures.create_address(account: account, address: "10.3.6.1")
NetworkFixtures.create_address(account: account, address: "10.3.6.2")
Fixtures.Network.create_address(account: account, address: "10.3.6.1")
Fixtures.Network.create_address(account: account, address: "10.3.6.2")
queryable = next_available_address(account.id, cidr, offset)
assert is_nil(Repo.one(queryable))
@@ -157,7 +156,7 @@ defmodule Domain.Network.Address.QueryTest do
cidr = string_to_cidr("fd00::3:2:0/126")
offset = 3
NetworkFixtures.create_address(account: account, address: "fd00::3:2:2")
Fixtures.Network.create_address(account: account, address: "fd00::3:2:2")
queryable = next_available_address(account.id, cidr, offset)
assert is_nil(Repo.one(queryable))

View File

@@ -1,11 +1,10 @@
defmodule Domain.NetworkTest do
use Domain.DataCase, async: true
alias Domain.AccountsFixtures
import Domain.Network
describe "fetch_next_available_address!/2" do
setup do
account = AccountsFixtures.create_account()
account = Fixtures.Accounts.create_account()
%{account: account}
end

View File

@@ -2,22 +2,13 @@ defmodule Domain.PoliciesTest do
alias Web.Policies
use Domain.DataCase, async: true
import Domain.Policies
alias Domain.{
AccountsFixtures,
ActorsFixtures,
AuthFixtures,
PoliciesFixtures,
ResourcesFixtures
}
alias Domain.Policies
setup do
account = AccountsFixtures.create_account()
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
account = Fixtures.Accounts.create_account()
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
subject = Fixtures.Auth.create_subject(identity: identity)
%{
account: account,
@@ -37,7 +28,7 @@ defmodule Domain.PoliciesTest do
end
test "returns policy when policy exists", %{account: account, subject: subject} do
policy = PoliciesFixtures.create_policy(account: account)
policy = Fixtures.Policies.create_policy(account: account)
assert {:ok, fetched_policy} = fetch_policy_by_id(policy.id, subject)
assert fetched_policy.id == policy.id
@@ -45,19 +36,19 @@ defmodule Domain.PoliciesTest do
test "does not return deleted policy", %{account: account, subject: subject} do
{:ok, policy} =
PoliciesFixtures.create_policy(account: account)
Fixtures.Policies.create_policy(account: account)
|> delete_policy(subject)
assert fetch_policy_by_id(policy.id, subject) == {:error, :not_found}
end
test "does not return policies in other accounts", %{subject: subject} do
policy = PoliciesFixtures.create_policy()
policy = Fixtures.Policies.create_policy()
assert fetch_policy_by_id(policy.id, subject) == {:error, :not_found}
end
test "returns error when subject has no permission to view policies", %{subject: subject} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert fetch_policy_by_id(Ecto.UUID.generate(), subject) ==
{:error,
@@ -75,7 +66,7 @@ defmodule Domain.PoliciesTest do
# TODO: add a test that soft-deleted assocs are not preloaded
test "associations are preloaded when opts given", %{account: account, subject: subject} do
policy = PoliciesFixtures.create_policy(account: account)
policy = Fixtures.Policies.create_policy(account: account)
{:ok, policy} = fetch_policy_by_id(policy.id, subject, preload: [:actor_group, :resource])
assert Ecto.assoc_loaded?(policy.actor_group)
@@ -89,39 +80,37 @@ defmodule Domain.PoliciesTest do
end
test "does not list policies from other accounts", %{subject: subject} do
PoliciesFixtures.create_policy()
Fixtures.Policies.create_policy()
assert list_policies(subject) == {:ok, []}
end
test "does not list deleted policies", %{account: account, subject: subject} do
PoliciesFixtures.create_policy(account: account)
Fixtures.Policies.create_policy(account: account)
|> delete_policy(subject)
assert list_policies(subject) == {:ok, []}
end
test "returns all policies for account admin subject", %{account: account} do
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
subject = Fixtures.Auth.create_subject(identity: identity)
PoliciesFixtures.create_policy(account: account)
PoliciesFixtures.create_policy(account: account)
PoliciesFixtures.create_policy()
Fixtures.Policies.create_policy(account: account)
Fixtures.Policies.create_policy(account: account)
Fixtures.Policies.create_policy()
assert {:ok, policies} = list_policies(subject)
assert length(policies) == 2
end
test "returns select policies for non-admin subject", %{account: account, subject: subject} do
unprivileged_actor = ActorsFixtures.create_actor(type: :account_user, account: account)
unprivileged_actor = Fixtures.Actors.create_actor(type: :account_user, account: account)
unpriviledged_identity =
AuthFixtures.create_identity(account: account, actor: unprivileged_actor)
unprivileged_subject =
Fixtures.Auth.create_subject(account: account, identity: [actor: unprivileged_actor])
unprivileged_subject = AuthFixtures.create_subject(unpriviledged_identity)
actor_group = ActorsFixtures.create_group(account: account, subject: subject)
actor_group = Fixtures.Actors.create_group(account: account, subject: subject)
Domain.Actors.update_group(
actor_group,
@@ -129,16 +118,16 @@ defmodule Domain.PoliciesTest do
subject
)
PoliciesFixtures.create_policy(account: account, actor_group: actor_group)
PoliciesFixtures.create_policy(account: account)
PoliciesFixtures.create_policy()
Fixtures.Policies.create_policy(account: account, actor_group: actor_group)
Fixtures.Policies.create_policy(account: account)
Fixtures.Policies.create_policy()
assert {:ok, policies} = list_policies(unprivileged_subject)
assert length(policies) == 1
end
test "returns error when subject has no permission to view policies", %{subject: subject} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert list_policies(subject) ==
{:error,
@@ -177,7 +166,7 @@ defmodule Domain.PoliciesTest do
end
test "returns error when subject has no permission to manage policies", %{subject: subject} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert create_policy(%{}, subject) ==
{:error,
@@ -194,10 +183,10 @@ defmodule Domain.PoliciesTest do
account: account,
subject: subject
} do
other_account = AccountsFixtures.create_account()
other_account = Fixtures.Accounts.create_account()
resource = ResourcesFixtures.create_resource(account: account)
other_actor_group = ActorsFixtures.create_group(account: other_account)
resource = Fixtures.Resources.create_resource(account: account)
other_actor_group = Fixtures.Actors.create_group(account: other_account)
attrs = %{
account_id: account.id,
@@ -215,10 +204,10 @@ defmodule Domain.PoliciesTest do
account: account,
subject: subject
} do
other_account = AccountsFixtures.create_account()
other_account = Fixtures.Accounts.create_account()
other_resource = ResourcesFixtures.create_resource(account: other_account)
actor_group = ActorsFixtures.create_group(account: account)
other_resource = Fixtures.Resources.create_resource(account: other_account)
actor_group = Fixtures.Actors.create_group(account: account)
attrs = %{
account_id: account.id,
@@ -237,7 +226,7 @@ defmodule Domain.PoliciesTest do
describe "update_policy/3" do
setup context do
policy =
PoliciesFixtures.create_policy(
Fixtures.Policies.create_policy(
account: context.account,
subject: context.subject
)
@@ -250,7 +239,7 @@ defmodule Domain.PoliciesTest do
end
test "returns changeset error on invalid params", %{account: account, subject: subject} do
policy = PoliciesFixtures.create_policy(account: account, subject: subject)
policy = Fixtures.Policies.create_policy(account: account, subject: subject)
assert {:error, changeset} =
update_policy(
@@ -274,7 +263,7 @@ defmodule Domain.PoliciesTest do
account: account,
subject: subject
} do
new_actor_group = ActorsFixtures.create_group(account: account)
new_actor_group = Fixtures.Actors.create_group(account: account)
assert {:ok, updated_policy} =
update_policy(policy, %{actor_group_id: new_actor_group.id}, subject)
@@ -287,7 +276,7 @@ defmodule Domain.PoliciesTest do
account: account,
subject: subject
} do
new_resource = ResourcesFixtures.create_resource(account: account)
new_resource = Fixtures.Resources.create_resource(account: account)
assert {:ok, updated_policy} =
update_policy(policy, %{resource_id: new_resource.id}, subject)
@@ -299,7 +288,7 @@ defmodule Domain.PoliciesTest do
policy: policy,
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert update_policy(policy, %{name: "Name Change Attempt"}, subject) ==
{:error,
@@ -312,10 +301,13 @@ defmodule Domain.PoliciesTest do
end
test "return error when subject is outside of account", %{policy: policy} do
other_account = AccountsFixtures.create_account()
other_actor = ActorsFixtures.create_actor(type: :account_admin_user, account: other_account)
other_identity = AuthFixtures.create_identity(account: other_account, actor: other_actor)
other_subject = AuthFixtures.create_subject(other_identity)
other_account = Fixtures.Accounts.create_account()
other_actor =
Fixtures.Actors.create_actor(type: :account_admin_user, account: other_account)
other_identity = Fixtures.Auth.create_identity(account: other_account, actor: other_actor)
other_subject = Fixtures.Auth.create_subject(identity: other_identity)
assert {:error, :unauthorized} =
update_policy(policy, %{name: "Should not be allowed"}, other_subject)
@@ -325,7 +317,7 @@ defmodule Domain.PoliciesTest do
describe "delete_policy/2" do
setup context do
policy =
PoliciesFixtures.create_policy(
Fixtures.Policies.create_policy(
account: context.account,
subject: context.subject
)
@@ -342,7 +334,7 @@ defmodule Domain.PoliciesTest do
policy: policy,
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert delete_policy(policy, subject) ==
{:error,
@@ -363,11 +355,7 @@ defmodule Domain.PoliciesTest do
test "returns error when subject attempts to delete policy outside of account", %{
policy: policy
} do
other_account = AccountsFixtures.create_account()
other_actor = ActorsFixtures.create_actor(type: :account_admin_user, account: other_account)
other_identity = AuthFixtures.create_identity(account: other_account, actor: other_actor)
other_subject = AuthFixtures.create_subject(other_identity)
other_subject = Fixtures.Auth.create_subject()
assert delete_policy(policy, other_subject) == {:error, :not_found}
end
end

View File

@@ -1,15 +1,13 @@
defmodule Domain.RelaysTest do
use Domain.DataCase, async: true
import Domain.Relays
alias Domain.{AccountsFixtures, ActorsFixtures, AuthFixtures, ResourcesFixtures}
alias Domain.RelaysFixtures
alias Domain.Relays
setup do
account = AccountsFixtures.create_account()
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
account = Fixtures.Accounts.create_account()
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
subject = Fixtures.Auth.create_subject(identity: identity)
%{
account: account,
@@ -27,7 +25,7 @@ defmodule Domain.RelaysTest do
test "does not return groups from other accounts", %{
subject: subject
} do
group = RelaysFixtures.create_group()
group = Fixtures.Relays.create_group()
assert fetch_group_by_id(group.id, subject) == {:error, :not_found}
end
@@ -36,14 +34,14 @@ defmodule Domain.RelaysTest do
subject: subject
} do
group =
RelaysFixtures.create_group(account: account)
|> RelaysFixtures.delete_group()
Fixtures.Relays.create_group(account: account)
|> Fixtures.Relays.delete_group()
assert fetch_group_by_id(group.id, subject) == {:error, :not_found}
end
test "returns group by id", %{account: account, subject: subject} do
group = RelaysFixtures.create_group(account: account)
group = Fixtures.Relays.create_group(account: account)
assert {:ok, fetched_group} = fetch_group_by_id(group.id, subject)
assert fetched_group.id == group.id
end
@@ -51,7 +49,7 @@ defmodule Domain.RelaysTest do
test "returns global group by id", %{
subject: subject
} do
group = RelaysFixtures.create_global_group()
group = Fixtures.Relays.create_global_group()
assert {:ok, fetched_group} = fetch_group_by_id(group.id, subject)
assert fetched_group.id == group.id
end
@@ -60,7 +58,7 @@ defmodule Domain.RelaysTest do
account: account,
subject: subject
} do
group = RelaysFixtures.create_group(account: account)
group = Fixtures.Relays.create_group(account: account)
assert {:ok, fetched_group} = fetch_group_by_id(group.id, subject)
assert fetched_group.id == group.id
end
@@ -73,7 +71,7 @@ defmodule Domain.RelaysTest do
test "returns error when subject has no permission to view groups", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert fetch_group_by_id(Ecto.UUID.generate(), subject) ==
{:error,
@@ -90,7 +88,7 @@ defmodule Domain.RelaysTest do
test "does not list groups from other accounts", %{
subject: subject
} do
RelaysFixtures.create_group()
Fixtures.Relays.create_group()
assert list_groups(subject) == {:ok, []}
end
@@ -98,8 +96,8 @@ defmodule Domain.RelaysTest do
account: account,
subject: subject
} do
RelaysFixtures.create_group(account: account)
|> RelaysFixtures.delete_group()
Fixtures.Relays.create_group(account: account)
|> Fixtures.Relays.delete_group()
assert list_groups(subject) == {:ok, []}
end
@@ -108,16 +106,16 @@ defmodule Domain.RelaysTest do
account: account,
subject: subject
} do
RelaysFixtures.create_group(account: account)
RelaysFixtures.create_group(account: account)
RelaysFixtures.create_group()
Fixtures.Relays.create_group(account: account)
Fixtures.Relays.create_group(account: account)
Fixtures.Relays.create_group()
assert {:ok, groups} = list_groups(subject)
assert length(groups) == 2
end
test "returns global groups", %{subject: subject} do
RelaysFixtures.create_global_group()
Fixtures.Relays.create_global_group()
assert {:ok, [_group]} = list_groups(subject)
end
@@ -125,7 +123,7 @@ defmodule Domain.RelaysTest do
test "returns error when subject has no permission to manage groups", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert list_groups(subject) ==
{:error,
@@ -160,7 +158,7 @@ defmodule Domain.RelaysTest do
name: ["should be at most 64 character(s)"]
}
RelaysFixtures.create_group(account: account, name: "foo")
Fixtures.Relays.create_group(account: account, name: "foo")
attrs = %{name: "foo", tokens: [%{}]}
assert {:error, changeset} = create_group(attrs, subject)
assert "has already been taken" in errors_on(changeset).name
@@ -187,7 +185,7 @@ defmodule Domain.RelaysTest do
test "returns error when subject has no permission to manage groups", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert create_group(%{}, subject) ==
{:error,
@@ -214,7 +212,7 @@ defmodule Domain.RelaysTest do
name: ["should be at most 64 character(s)"]
}
RelaysFixtures.create_global_group(name: "foo")
Fixtures.Relays.create_global_group(name: "foo")
attrs = %{name: "foo", tokens: [%{}]}
assert {:error, changeset} = create_global_group(attrs)
assert "has already been taken" in errors_on(changeset).name
@@ -241,10 +239,10 @@ defmodule Domain.RelaysTest do
describe "change_group/1" do
test "returns changeset with given changes" do
group = RelaysFixtures.create_group()
group = Fixtures.Relays.create_group()
group_attrs =
RelaysFixtures.group_attrs()
Fixtures.Relays.group_attrs()
|> Map.delete(:tokens)
assert changeset = change_group(group, group_attrs)
@@ -257,7 +255,7 @@ defmodule Domain.RelaysTest do
test "does not allow to reset required fields to empty values", %{
subject: subject
} do
group = RelaysFixtures.create_group()
group = Fixtures.Relays.create_group()
attrs = %{name: nil}
assert {:error, changeset} = update_group(group, attrs, subject)
@@ -266,7 +264,7 @@ defmodule Domain.RelaysTest do
end
test "returns error on invalid attrs", %{account: account, subject: subject} do
group = RelaysFixtures.create_group(account: account)
group = Fixtures.Relays.create_group(account: account)
attrs = %{
name: String.duplicate("A", 65)
@@ -278,14 +276,14 @@ defmodule Domain.RelaysTest do
name: ["should be at most 64 character(s)"]
}
RelaysFixtures.create_group(account: account, name: "foo")
Fixtures.Relays.create_group(account: account, name: "foo")
attrs = %{name: "foo"}
assert {:error, changeset} = update_group(group, attrs, subject)
assert "has already been taken" in errors_on(changeset).name
end
test "updates a group", %{account: account, subject: subject} do
group = RelaysFixtures.create_group(account: account)
group = Fixtures.Relays.create_group(account: account)
attrs = %{
name: "foo"
@@ -296,7 +294,7 @@ defmodule Domain.RelaysTest do
end
test "does not allow updating global group", %{subject: subject} do
group = RelaysFixtures.create_global_group()
group = Fixtures.Relays.create_global_group()
attrs = %{name: "foo"}
assert update_group(group, attrs, subject) == {:error, :unauthorized}
end
@@ -305,9 +303,9 @@ defmodule Domain.RelaysTest do
account: account,
subject: subject
} do
group = RelaysFixtures.create_group(account: account)
group = Fixtures.Relays.create_group(account: account)
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert update_group(group, %{}, subject) ==
{:error,
@@ -318,7 +316,7 @@ defmodule Domain.RelaysTest do
describe "delete_group/2" do
test "returns error on state conflict", %{account: account, subject: subject} do
group = RelaysFixtures.create_group(account: account)
group = Fixtures.Relays.create_group(account: account)
assert {:ok, deleted} = delete_group(group, subject)
assert delete_group(deleted, subject) == {:error, :not_found}
@@ -326,21 +324,21 @@ defmodule Domain.RelaysTest do
end
test "deletes groups", %{account: account, subject: subject} do
group = RelaysFixtures.create_group(account: account)
group = Fixtures.Relays.create_group(account: account)
assert {:ok, deleted} = delete_group(group, subject)
assert deleted.deleted_at
end
test "does not allow deleting global group", %{subject: subject} do
group = RelaysFixtures.create_global_group()
group = Fixtures.Relays.create_global_group()
assert delete_group(group, subject) == {:error, :unauthorized}
end
test "deletes all tokens when group is deleted", %{account: account, subject: subject} do
group = RelaysFixtures.create_group(account: account)
RelaysFixtures.create_token(group: group)
RelaysFixtures.create_token(group: [account: account])
group = Fixtures.Relays.create_group(account: account)
Fixtures.Relays.create_token(group: group)
Fixtures.Relays.create_token(group: [account: account])
assert {:ok, deleted} = delete_group(group, subject)
assert deleted.deleted_at
@@ -356,9 +354,9 @@ defmodule Domain.RelaysTest do
test "returns error when subject has no permission to delete groups", %{
subject: subject
} do
group = RelaysFixtures.create_group()
group = Fixtures.Relays.create_group()
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert delete_group(group, subject) ==
{:error,
@@ -369,7 +367,7 @@ defmodule Domain.RelaysTest do
describe "use_token_by_id_and_secret/2" do
test "returns token when secret is valid" do
token = RelaysFixtures.create_token()
token = Fixtures.Relays.create_token()
assert {:ok, token} = use_token_by_id_and_secret(token.id, token.value)
assert is_nil(token.value)
# TODO: While we don't have token rotation implemented, the tokens are all multi-use
@@ -379,7 +377,7 @@ defmodule Domain.RelaysTest do
# TODO: While we don't have token rotation implemented, the tokens are all multi-use
# test "returns error when secret was already used" do
# token = RelaysFixtures.create_token()
# token = Fixtures.Relays.create_token()
# assert {:ok, _token} = use_token_by_id_and_secret(token.id, token.value)
# assert use_token_by_id_and_secret(token.id, token.value) == {:error, :not_found}
@@ -394,7 +392,7 @@ defmodule Domain.RelaysTest do
end
test "returns error when secret is invalid" do
token = RelaysFixtures.create_token()
token = Fixtures.Relays.create_token()
assert use_token_by_id_and_secret(token.id, "bar") == {:error, :not_found}
end
end
@@ -407,7 +405,7 @@ defmodule Domain.RelaysTest do
test "does not return relays from other accounts", %{
subject: subject
} do
relay = RelaysFixtures.create_relay()
relay = Fixtures.Relays.create_relay()
assert fetch_relay_by_id(relay.id, subject) == {:error, :not_found}
end
@@ -416,14 +414,14 @@ defmodule Domain.RelaysTest do
subject: subject
} do
relay =
RelaysFixtures.create_relay(account: account)
|> RelaysFixtures.delete_relay()
Fixtures.Relays.create_relay(account: account)
|> Fixtures.Relays.delete_relay()
assert fetch_relay_by_id(relay.id, subject) == {:error, :not_found}
end
test "returns relay by id", %{account: account, subject: subject} do
relay = RelaysFixtures.create_relay(account: account)
relay = Fixtures.Relays.create_relay(account: account)
assert fetch_relay_by_id(relay.id, subject) == {:ok, relay}
end
@@ -431,7 +429,7 @@ defmodule Domain.RelaysTest do
account: account,
subject: subject
} do
relay = RelaysFixtures.create_relay(account: account)
relay = Fixtures.Relays.create_relay(account: account)
assert fetch_relay_by_id(relay.id, subject) == {:ok, relay}
end
@@ -443,7 +441,7 @@ defmodule Domain.RelaysTest do
test "returns error when subject has no permission to view relays", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert fetch_relay_by_id(Ecto.UUID.generate(), subject) ==
{:error,
@@ -460,8 +458,8 @@ defmodule Domain.RelaysTest do
test "does not list deleted relays", %{
subject: subject
} do
RelaysFixtures.create_relay()
|> RelaysFixtures.delete_relay()
Fixtures.Relays.create_relay()
|> Fixtures.Relays.delete_relay()
assert list_relays(subject) == {:ok, []}
end
@@ -470,12 +468,12 @@ defmodule Domain.RelaysTest do
account: account,
subject: subject
} do
RelaysFixtures.create_relay(account: account)
RelaysFixtures.create_relay(account: account)
RelaysFixtures.create_relay()
Fixtures.Relays.create_relay(account: account)
Fixtures.Relays.create_relay(account: account)
Fixtures.Relays.create_relay()
group = RelaysFixtures.create_global_group()
relay = RelaysFixtures.create_relay(group: group)
group = Fixtures.Relays.create_global_group()
relay = Fixtures.Relays.create_relay(group: group)
assert {:ok, relays} = list_relays(subject)
assert length(relays) == 3
@@ -490,7 +488,7 @@ defmodule Domain.RelaysTest do
test "returns error when subject has no permission to manage relays", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert list_relays(subject) ==
{:error,
@@ -501,19 +499,19 @@ defmodule Domain.RelaysTest do
describe "list_connected_relays_for_resource/1" do
test "returns empty list when there are no online relays", %{account: account} do
resource = ResourcesFixtures.create_resource(account: account)
resource = Fixtures.Resources.create_resource(account: account)
RelaysFixtures.create_relay(account: account)
Fixtures.Relays.create_relay(account: account)
RelaysFixtures.create_relay(account: account)
|> RelaysFixtures.delete_relay()
Fixtures.Relays.create_relay(account: account)
|> Fixtures.Relays.delete_relay()
assert list_connected_relays_for_resource(resource) == {:ok, []}
end
test "returns list of connected account relays", %{account: account} do
resource = ResourcesFixtures.create_resource(account: account)
relay = RelaysFixtures.create_relay(account: account)
resource = Fixtures.Resources.create_resource(account: account)
relay = Fixtures.Relays.create_relay(account: account)
stamp_secret = Ecto.UUID.generate()
assert connect_relay(relay, stamp_secret) == :ok
@@ -525,9 +523,9 @@ defmodule Domain.RelaysTest do
end
test "returns list of connected global relays", %{account: account} do
resource = ResourcesFixtures.create_resource(account: account)
group = RelaysFixtures.create_global_group()
relay = RelaysFixtures.create_relay(group: group)
resource = Fixtures.Resources.create_resource(account: account)
group = Fixtures.Relays.create_global_group()
relay = Fixtures.Relays.create_relay(group: group)
stamp_secret = Ecto.UUID.generate()
assert connect_relay(relay, stamp_secret) == :ok
@@ -541,7 +539,7 @@ defmodule Domain.RelaysTest do
describe "generate_username_and_password/1" do
test "returns username and password", %{account: account} do
relay = RelaysFixtures.create_relay(account: account)
relay = Fixtures.Relays.create_relay(account: account)
stamp_secret = Ecto.UUID.generate()
relay = %{relay | stamp_secret: stamp_secret}
expires_at = DateTime.utc_now() |> DateTime.add(3, :second)
@@ -563,7 +561,7 @@ defmodule Domain.RelaysTest do
describe "upsert_relay/3" do
setup context do
token = RelaysFixtures.create_token(account: context.account)
token = Fixtures.Relays.create_token(account: context.account)
context
|> Map.put(:token, token)
@@ -599,7 +597,7 @@ defmodule Domain.RelaysTest do
token: token
} do
attrs =
RelaysFixtures.relay_attrs()
Fixtures.Relays.relay_attrs()
|> Map.delete(:name)
assert {:ok, relay} = upsert_relay(token, attrs)
@@ -623,7 +621,7 @@ defmodule Domain.RelaysTest do
token: token
} do
attrs =
RelaysFixtures.relay_attrs()
Fixtures.Relays.relay_attrs()
|> Map.drop([:name, :ipv4])
assert {:ok, _relay} = upsert_relay(token, attrs)
@@ -635,10 +633,10 @@ defmodule Domain.RelaysTest do
test "updates relay when it already exists", %{
token: token
} do
relay = RelaysFixtures.create_relay(token: token)
relay = Fixtures.Relays.create_relay(token: token)
attrs =
RelaysFixtures.relay_attrs(
Fixtures.Relays.relay_attrs(
ipv4: relay.ipv4,
last_seen_remote_ip: relay.ipv4,
last_seen_user_agent: "iOS/12.5 (iPhone) connlib/0.7.411"
@@ -667,12 +665,12 @@ defmodule Domain.RelaysTest do
end
test "updates global relay when it already exists" do
group = RelaysFixtures.create_global_group()
group = Fixtures.Relays.create_global_group()
token = hd(group.tokens)
relay = RelaysFixtures.create_relay(group: group, token: token)
relay = Fixtures.Relays.create_relay(group: group, token: token)
attrs =
RelaysFixtures.relay_attrs(
Fixtures.Relays.relay_attrs(
ipv4: relay.ipv4,
last_seen_remote_ip: relay.ipv4,
last_seen_user_agent: "iOS/12.5 (iPhone) connlib/0.7.411"
@@ -703,7 +701,7 @@ defmodule Domain.RelaysTest do
describe "delete_relay/2" do
test "returns error on state conflict", %{account: account, subject: subject} do
relay = RelaysFixtures.create_relay(account: account)
relay = Fixtures.Relays.create_relay(account: account)
assert {:ok, deleted} = delete_relay(relay, subject)
assert delete_relay(deleted, subject) == {:error, :not_found}
@@ -711,7 +709,7 @@ defmodule Domain.RelaysTest do
end
test "deletes relays", %{account: account, subject: subject} do
relay = RelaysFixtures.create_relay(account: account)
relay = Fixtures.Relays.create_relay(account: account)
assert {:ok, deleted} = delete_relay(relay, subject)
assert deleted.deleted_at
@@ -720,9 +718,9 @@ defmodule Domain.RelaysTest do
test "returns error when subject has no permission to delete relays", %{
subject: subject
} do
relay = RelaysFixtures.create_relay()
relay = Fixtures.Relays.create_relay()
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert delete_relay(relay, subject) ==
{:error,
@@ -733,7 +731,7 @@ defmodule Domain.RelaysTest do
describe "encode_token!/1" do
test "returns encoded token" do
token = RelaysFixtures.create_token()
token = Fixtures.Relays.create_token()
assert encrypted_secret = encode_token!(token)
config = Application.fetch_env!(:domain, Domain.Relays)
@@ -747,7 +745,7 @@ defmodule Domain.RelaysTest do
describe "authorize_relay/1" do
test "returns token when encoded secret is valid" do
token = RelaysFixtures.create_token()
token = Fixtures.Relays.create_token()
encoded_token = encode_token!(token)
assert {:ok, fetched_token} = authorize_relay(encoded_token)
assert fetched_token.id == token.id

View File

@@ -1,15 +1,13 @@
defmodule Domain.ResourcesTest do
use Domain.DataCase, async: true
import Domain.Resources
alias Domain.{AccountsFixtures, ActorsFixtures, AuthFixtures, GatewaysFixtures, NetworkFixtures}
alias Domain.ResourcesFixtures
alias Domain.Resources
setup do
account = AccountsFixtures.create_account()
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
account = Fixtures.Accounts.create_account()
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
subject = Fixtures.Auth.create_subject(identity: identity)
%{
account: account,
@@ -29,7 +27,7 @@ defmodule Domain.ResourcesTest do
end
test "returns resource when resource exists", %{account: account, subject: subject} do
resource = ResourcesFixtures.create_resource(account: account)
resource = Fixtures.Resources.create_resource(account: account)
assert {:ok, fetched_resource} = fetch_resource_by_id(resource.id, subject)
assert fetched_resource.id == resource.id
@@ -37,19 +35,19 @@ defmodule Domain.ResourcesTest do
test "does not return deleted resources", %{account: account, subject: subject} do
{:ok, resource} =
ResourcesFixtures.create_resource(account: account)
Fixtures.Resources.create_resource(account: account)
|> delete_resource(subject)
assert fetch_resource_by_id(resource.id, subject) == {:error, :not_found}
end
test "does not return resources in other accounts", %{subject: subject} do
resource = ResourcesFixtures.create_resource()
resource = Fixtures.Resources.create_resource()
assert fetch_resource_by_id(resource.id, subject) == {:error, :not_found}
end
test "returns error when subject has no permission to view resources", %{subject: subject} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert fetch_resource_by_id(Ecto.UUID.generate(), subject) ==
{:error,
@@ -67,7 +65,7 @@ defmodule Domain.ResourcesTest do
# TODO: add a test that soft-deleted assocs are not preloaded
test "associations are preloaded when opts given", %{account: account, subject: subject} do
resource = ResourcesFixtures.create_resource(account: account)
resource = Fixtures.Resources.create_resource(account: account)
{:ok, resource} = fetch_resource_by_id(resource.id, subject, preload: :connections)
assert Ecto.assoc_loaded?(resource.connections) == true
@@ -82,7 +80,7 @@ defmodule Domain.ResourcesTest do
test "does not list resources from other accounts", %{
subject: subject
} do
ResourcesFixtures.create_resource()
Fixtures.Resources.create_resource()
assert list_resources(subject) == {:ok, []}
end
@@ -90,7 +88,7 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
ResourcesFixtures.create_resource(account: account)
Fixtures.Resources.create_resource(account: account)
|> delete_resource(subject)
assert list_resources(subject) == {:ok, []}
@@ -99,13 +97,13 @@ defmodule Domain.ResourcesTest do
test "returns all resources for account admin subject", %{
account: account
} do
actor = ActorsFixtures.create_actor(type: :account_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
actor = Fixtures.Actors.create_actor(type: :account_user, account: account)
identity = Fixtures.Auth.create_identity(account: account, actor: actor)
subject = Fixtures.Auth.create_subject(identity: identity)
ResourcesFixtures.create_resource(account: account)
ResourcesFixtures.create_resource(account: account)
ResourcesFixtures.create_resource()
Fixtures.Resources.create_resource(account: account)
Fixtures.Resources.create_resource(account: account)
Fixtures.Resources.create_resource()
assert {:ok, resources} = list_resources(subject)
assert length(resources) == 2
@@ -115,9 +113,9 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
ResourcesFixtures.create_resource(account: account)
ResourcesFixtures.create_resource(account: account)
ResourcesFixtures.create_resource()
Fixtures.Resources.create_resource(account: account)
Fixtures.Resources.create_resource(account: account)
Fixtures.Resources.create_resource()
assert {:ok, resources} = list_resources(subject)
assert length(resources) == 2
@@ -126,7 +124,7 @@ defmodule Domain.ResourcesTest do
test "returns error when subject has no permission to manage resources", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert list_resources(subject) ==
{:error,
@@ -148,7 +146,7 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
assert list_resources_for_gateway(gateway, subject) == {:ok, []}
end
@@ -157,8 +155,8 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
gateway = GatewaysFixtures.create_gateway(account: account)
ResourcesFixtures.create_resource()
gateway = Fixtures.Gateways.create_gateway(account: account)
Fixtures.Resources.create_resource()
assert list_resources_for_gateway(gateway, subject) == {:ok, []}
end
@@ -167,12 +165,12 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
group = GatewaysFixtures.create_group(account: account, subject: subject)
gateway = GatewaysFixtures.create_gateway(account: account, group: group)
group = Fixtures.Gateways.create_group(account: account, subject: subject)
gateway = Fixtures.Gateways.create_gateway(account: account, group: group)
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: group.id}]
connections: [%{gateway_group_id: group.id}]
)
|> delete_resource(subject)
@@ -183,20 +181,20 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
group = GatewaysFixtures.create_group(account: account, subject: subject)
gateway = GatewaysFixtures.create_gateway(account: account, group: group)
group = Fixtures.Gateways.create_group(account: account, subject: subject)
gateway = Fixtures.Gateways.create_gateway(account: account, group: group)
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: group.id}]
connections: [%{gateway_group_id: group.id}]
)
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: group.id}]
connections: [%{gateway_group_id: group.id}]
)
ResourcesFixtures.create_resource(account: account)
Fixtures.Resources.create_resource(account: account)
assert {:ok, resources} = list_resources_for_gateway(gateway, subject)
assert length(resources) == 2
@@ -206,15 +204,15 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
group = GatewaysFixtures.create_group(account: account, subject: subject)
gateway = GatewaysFixtures.create_gateway(account: account, group: group)
group = Fixtures.Gateways.create_group(account: account, subject: subject)
gateway = Fixtures.Gateways.create_gateway(account: account, group: group)
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: group.id}]
connections: [%{gateway_group_id: group.id}]
)
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert list_resources_for_gateway(gateway, subject) ==
{:error,
@@ -236,7 +234,7 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
assert count_resources_for_gateway(gateway, subject) == {:ok, 0}
end
@@ -245,15 +243,15 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
group = GatewaysFixtures.create_group(account: account, subject: subject)
gateway = GatewaysFixtures.create_gateway(account: account, group: group)
group = Fixtures.Gateways.create_group(account: account, subject: subject)
gateway = Fixtures.Gateways.create_gateway(account: account, group: group)
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: group.id}]
connections: [%{gateway_group_id: group.id}]
)
ResourcesFixtures.create_resource(account: account)
Fixtures.Resources.create_resource(account: account)
assert count_resources_for_gateway(gateway, subject) == {:ok, 1}
end
@@ -262,17 +260,17 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
group = GatewaysFixtures.create_group(account: account, subject: subject)
gateway = GatewaysFixtures.create_gateway(account: account, group: group)
group = Fixtures.Gateways.create_group(account: account, subject: subject)
gateway = Fixtures.Gateways.create_gateway(account: account, group: group)
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: group.id}]
connections: [%{gateway_group_id: group.id}]
)
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: group.id}]
connections: [%{gateway_group_id: group.id}]
)
|> delete_resource(subject)
@@ -284,15 +282,15 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
group = GatewaysFixtures.create_group(account: account, subject: subject)
gateway = GatewaysFixtures.create_gateway(account: account, group: group)
group = Fixtures.Gateways.create_group(account: account, subject: subject)
gateway = Fixtures.Gateways.create_gateway(account: account, group: group)
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: group.id}]
connections: [%{gateway_group_id: group.id}]
)
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert count_resources_for_gateway(gateway, subject) ==
{:error,
@@ -373,9 +371,9 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
subject: subject,
type: :cidr,
@@ -391,14 +389,15 @@ defmodule Domain.ResourcesTest do
assert {:error, changeset} = create_resource(attrs, subject)
assert "can not overlap with other resource ranges" in errors_on(changeset).address
subject = AuthFixtures.create_subject()
# range is unique per account
subject = Fixtures.Auth.create_subject(actor: [type: :account_admin_user])
assert {:ok, _resource} = create_resource(attrs, subject)
end
test "returns error on duplicate name", %{account: account, subject: subject} do
gateway = GatewaysFixtures.create_gateway(account: account)
resource = ResourcesFixtures.create_resource(account: account, subject: subject)
address = ResourcesFixtures.resource_attrs().address
gateway = Fixtures.Gateways.create_gateway(account: account)
resource = Fixtures.Resources.create_resource(account: account, subject: subject)
address = Fixtures.Resources.resource_attrs().address
attrs = %{
"name" => resource.name,
@@ -412,10 +411,10 @@ defmodule Domain.ResourcesTest do
end
test "creates a dns resource", %{account: account, subject: subject} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
attrs =
ResourcesFixtures.resource_attrs(
Fixtures.Resources.resource_attrs(
connections: [
%{gateway_group_id: gateway.group_id}
]
@@ -447,11 +446,11 @@ defmodule Domain.ResourcesTest do
end
test "creates a cidr resource", %{account: account, subject: subject} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
address_count = Repo.aggregate(Domain.Network.Address, :count)
attrs =
ResourcesFixtures.resource_attrs(
Fixtures.Resources.resource_attrs(
connections: [
%{gateway_group_id: gateway.group_id}
],
@@ -493,10 +492,10 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
attrs =
ResourcesFixtures.resource_attrs(
Fixtures.Resources.resource_attrs(
connections: [
%{gateway_group_id: gateway.group_id}
]
@@ -515,11 +514,11 @@ defmodule Domain.ResourcesTest do
assert %{address: resource.ipv6, type: :ipv6} in addresses
assert_raise Ecto.ConstraintError, fn ->
NetworkFixtures.create_address(address: resource.ipv4, account: account)
Fixtures.Network.create_address(address: resource.ipv4, account: account)
end
assert_raise Ecto.ConstraintError, fn ->
NetworkFixtures.create_address(address: resource.ipv6, account: account)
Fixtures.Network.create_address(address: resource.ipv6, account: account)
end
end
@@ -527,10 +526,10 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
gateway = GatewaysFixtures.create_gateway(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
attrs =
ResourcesFixtures.resource_attrs(
Fixtures.Resources.resource_attrs(
connections: [
%{gateway_group_id: gateway.group_id}
]
@@ -538,14 +537,14 @@ defmodule Domain.ResourcesTest do
assert {:ok, resource} = create_resource(attrs, subject)
assert %Domain.Network.Address{} = NetworkFixtures.create_address(address: resource.ipv4)
assert %Domain.Network.Address{} = NetworkFixtures.create_address(address: resource.ipv6)
assert %Domain.Network.Address{} = Fixtures.Network.create_address(address: resource.ipv4)
assert %Domain.Network.Address{} = Fixtures.Network.create_address(address: resource.ipv6)
end
test "returns error when subject has no permission to create resources", %{
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert create_resource(%{}, subject) ==
{:error,
@@ -557,7 +556,7 @@ defmodule Domain.ResourcesTest do
describe "update_resource/3" do
setup context do
resource =
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: context.account,
subject: context.subject
)
@@ -593,14 +592,14 @@ defmodule Domain.ResourcesTest do
end
test "allows to update connections", %{account: account, resource: resource, subject: subject} do
gateway1 = GatewaysFixtures.create_gateway(account: account)
gateway1 = Fixtures.Gateways.create_gateway(account: account)
attrs = %{"connections" => [%{gateway_group_id: gateway1.group_id}]}
assert {:ok, resource} = update_resource(resource, attrs, subject)
gateway_group_ids = Enum.map(resource.connections, & &1.gateway_group_id)
assert gateway_group_ids == [gateway1.group_id]
gateway2 = GatewaysFixtures.create_gateway(account: account)
gateway2 = Fixtures.Gateways.create_gateway(account: account)
attrs = %{
"connections" => [
@@ -638,7 +637,7 @@ defmodule Domain.ResourcesTest do
resource: resource,
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert update_resource(resource, %{}, subject) ==
{:error,
@@ -650,7 +649,7 @@ defmodule Domain.ResourcesTest do
describe "delete_resource/2" do
setup context do
resource =
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: context.account,
subject: context.subject
)
@@ -676,7 +675,7 @@ defmodule Domain.ResourcesTest do
resource: resource,
subject: subject
} do
subject = AuthFixtures.remove_permissions(subject)
subject = Fixtures.Auth.remove_permissions(subject)
assert delete_resource(resource, subject) ==
{:error,
@@ -690,21 +689,21 @@ defmodule Domain.ResourcesTest do
account: account,
subject: subject
} do
group = GatewaysFixtures.create_group(account: account, subject: subject)
gateway = GatewaysFixtures.create_gateway(account: account, group: group)
group = Fixtures.Gateways.create_group(account: account, subject: subject)
gateway = Fixtures.Gateways.create_gateway(account: account, group: group)
resource =
ResourcesFixtures.create_resource(
Fixtures.Resources.create_resource(
account: account,
gateway_groups: [%{gateway_group_id: group.id}]
connections: [%{gateway_group_id: group.id}]
)
assert connected?(resource, gateway)
end
test "raises resource and gateway don't belong to the same account" do
gateway = GatewaysFixtures.create_gateway()
resource = ResourcesFixtures.create_resource()
gateway = Fixtures.Gateways.create_gateway()
resource = Fixtures.Resources.create_resource()
assert_raise FunctionClauseError, fn ->
connected?(resource, gateway)
@@ -712,8 +711,8 @@ defmodule Domain.ResourcesTest do
end
test "returns false when resource has no connection to a gateway", %{account: account} do
gateway = GatewaysFixtures.create_gateway(account: account)
resource = ResourcesFixtures.create_resource(account: account)
gateway = Fixtures.Gateways.create_gateway(account: account)
resource = Fixtures.Resources.create_resource(account: account)
refute connected?(resource, gateway)
end

View File

@@ -1,150 +0,0 @@
# defmodule Domain.TelemetryTest do
# use Domain.DataCase, async: true
# # import Domain.TestHelpers
# # alias Domain.Telemetry
# # alias Domain.MFAFixtures
# # describe "user" do
# # setup :create_user
# # test "count" do
# # ping_data = Telemetry.ping_data()
# # assert ping_data[:user_count] == 1
# # end
# # test "count mfa", %{user: user} do
# # {:ok, [user: other_user]} = create_user(%{})
# # MFAFixtures.create_totp_method(user: user)
# # MFAFixtures.create_totp_method(user: other_user)
# # ping_data = Telemetry.ping_data()
# # assert ping_data[:users_with_mfa] == 2
# # assert ping_data[:users_with_mfa_totp] == 2
# # end
# # end
# # describe "device" do
# # setup [:create_devices, :create_other_user_device]
# # test "count" do
# # ping_data = Telemetry.ping_data()
# # assert ping_data[:device_count] == 6
# # end
# # test "max count for users" do
# # ping_data = Telemetry.ping_data()
# # assert ping_data[:max_devices_for_users] == 5
# # end
# # end
# # describe "auth" do
# # test "count openid providers" do
# # Domain.ConfigFixtures.start_openid_providers([
# # "google",
# # "okta",
# # "auth0",
# # "azure",
# # "onelogin",
# # "keycloak",
# # "vault"
# # ])
# # ping_data = Telemetry.ping_data()
# # assert ping_data[:openid_providers] == 7
# # end
# # test "disable vpn on oidc error enabled" do
# # Domain.Config.put_config!(:disable_vpn_on_oidc_error, true)
# # ping_data = Telemetry.ping_data()
# # assert ping_data[:disable_vpn_on_oidc_error]
# # end
# # test "disable vpn on oidc error disabled" do
# # Domain.Config.put_config!(:disable_vpn_on_oidc_error, false)
# # ping_data = Telemetry.ping_data()
# # refute ping_data[:disable_vpn_on_oidc_error]
# # end
# # test "local authentication enabled" do
# # Domain.Config.put_config!(:local_auth_enabled, true)
# # ping_data = Telemetry.ping_data()
# # assert ping_data[:local_authentication]
# # end
# # test "local authentication disabled" do
# # Domain.Config.put_config!(:local_auth_enabled, false)
# # ping_data = Telemetry.ping_data()
# # refute ping_data[:local_authentication]
# # end
# # test "unprivileged device management enabled" do
# # Domain.Config.put_config!(:allow_unprivileged_device_management, true)
# # ping_data = Telemetry.ping_data()
# # assert ping_data[:unprivileged_device_management]
# # end
# # test "unprivileged device configuration enabled" do
# # Domain.Config.put_config!(:allow_unprivileged_device_configuration, true)
# # ping_data = Telemetry.ping_data()
# # assert ping_data[:unprivileged_device_configuration]
# # end
# # test "unprivileged device configuration disabled" do
# # Domain.Config.put_config!(:allow_unprivileged_device_configuration, false)
# # ping_data = Telemetry.ping_data()
# # refute ping_data[:unprivileged_device_configuration]
# # end
# # end
# # describe "database" do
# # test "local hostname" do
# # Domain.Config.put_env_override(:domain, Domain.Repo, hostname: "localhost")
# # ping_data = Telemetry.ping_data()
# # refute ping_data[:external_database]
# # end
# # test "local url" do
# # Domain.Config.put_env_override(:domain, Domain.Repo, url: "postgres://127.0.0.1")
# # ping_data = Telemetry.ping_data()
# # refute ping_data[:external_database]
# # end
# # test "external hostname" do
# # Domain.Config.put_env_override(:domain, Domain.Repo, hostname: "firezone.dev")
# # ping_data = Telemetry.ping_data()
# # assert ping_data[:external_database]
# # end
# # test "external url" do
# # Domain.Config.put_env_override(:domain, Domain.Repo, url: "postgres://firezone.dev")
# # ping_data = Telemetry.ping_data()
# # assert ping_data[:external_database]
# # end
# # end
# end

View File

@@ -23,6 +23,8 @@ defmodule Domain.DataCase do
import Domain.DataCase
alias Domain.Repo
alias Domain.Fixtures
alias Domain.Mocks
end
end

View File

@@ -0,0 +1,65 @@
defmodule Domain.Fixture do
alias Domain.Repo
defmacro __using__(_opts) do
quote do
import Domain.Fixture
alias Domain.Repo
alias Domain.Fixtures
end
end
def pop_assoc_fixture_id(attrs, key, callback) do
case Map.fetch(attrs, :"#{key}_id") do
{:ok, id} when not is_nil(id) ->
{id, attrs}
_other ->
{assoc, attrs} = pop_assoc_fixture(attrs, key, callback)
{assoc.id, attrs}
end
end
def pop_assoc_fixture(attrs, key, callback) do
case Map.pop(attrs, key, %{}) do
{%{__struct__: _struct} = assoc_struct, attrs} ->
{assoc_struct, attrs}
{assoc_attrs, attrs} ->
{apply_assoc_fixture(callback, assoc_attrs), attrs}
end
end
defp apply_assoc_fixture(callback, _attrs) when is_function(callback, 0), do: callback.()
defp apply_assoc_fixture(callback, attrs) when is_function(callback, 1), do: callback.(attrs)
def update!(schema, changes) do
schema
|> Ecto.Changeset.change(Enum.into(changes, %{}))
|> Repo.update!()
end
def unique_integer do
System.unique_integer([:positive])
end
def unique_ipv4 do
number = unique_integer()
<<a::size(8), b::size(8), c::size(8), d::size(8)>> = <<number::32>>
{a, b, c, d}
end
def unique_ipv6 do
number = unique_integer()
<<a::size(16), b::size(16), c::size(16), d::size(16), e::size(16), f::size(16), g::size(16),
h::size(16)>> = <<number::128>>
{a, b, c, d, e, f, g, h}
end
def unique_public_key do
:crypto.strong_rand_bytes(32)
|> Base.encode64()
end
end

View File

@@ -1,9 +1,10 @@
defmodule Domain.AccountsFixtures do
defmodule Domain.Fixtures.Accounts do
use Domain.Fixture
alias Domain.Accounts
def account_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
name: "acc-#{counter()}"
name: "acc-#{unique_integer()}"
})
end
@@ -12,8 +13,4 @@ defmodule Domain.AccountsFixtures do
{:ok, account} = Accounts.create_account(attrs)
account
end
defp counter do
System.unique_integer([:positive])
end
end

View File

@@ -0,0 +1,136 @@
defmodule Domain.Fixtures.Actors do
use Domain.Fixture
alias Domain.Actors
def group_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
name: "group-#{unique_integer()}"
})
end
def create_group(attrs \\ %{}) do
attrs = group_attrs(attrs)
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
{provider, attrs} =
Map.pop(attrs, :provider)
{provider_identifier, attrs} =
Map.pop_lazy(attrs, :provider_identifier, fn ->
if provider do
Fixtures.Auth.random_provider_identifier(provider)
end
end)
{subject, attrs} =
pop_assoc_fixture(attrs, :subject, fn assoc_attrs ->
assoc_attrs
|> Enum.into(%{account: account, actor: [type: :account_admin_user]})
|> Fixtures.Auth.create_subject()
end)
{:ok, group} =
attrs
|> Map.put(:provider_identifier, provider_identifier)
|> Actors.create_group(subject)
if provider do
update!(group, provider_id: provider.id, provider_identifier: provider_identifier)
else
group
end
end
def delete_group(group) do
group = Repo.preload(group, :account)
subject =
Fixtures.Auth.create_subject(
account: group.account,
actor: [type: :account_admin_user]
)
{:ok, group} = Actors.delete_group(group, subject)
group
end
def actor_attrs(attrs \\ %{}) do
first_name = Enum.random(~w[Wade Dave Seth Riley Gilbert Jorge Dan Brian Roberto Ramon Juan])
last_name = Enum.random(~w[Robyn Traci Desiree Jon Bob Karl Joe Alberta Lynda Cara Brandi B])
Enum.into(attrs, %{
name: "#{first_name} #{last_name}",
type: :account_user
})
end
def create_actor(attrs \\ %{}) do
attrs = actor_attrs(attrs)
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
{provider, attrs} =
pop_assoc_fixture(attrs, :provider, fn assoc_attrs ->
{provider, _bypass} =
assoc_attrs
|> Enum.into(%{account: account})
|> Fixtures.Auth.start_and_create_openid_connect_provider()
provider
end)
Actors.Actor.Changeset.create(provider.account_id, attrs)
|> Repo.insert!()
end
def create_membership(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
{provider, attrs} =
Map.pop(attrs, :provider)
{group_id, attrs} =
pop_assoc_fixture_id(attrs, :group, fn assoc_attrs ->
assoc_attrs
|> Enum.into(%{account: account, provider: provider})
|> create_group()
end)
{actor_id, _attrs} =
pop_assoc_fixture_id(attrs, :actor, fn assoc_attrs ->
assoc_attrs
|> Enum.into(%{account: account})
|> create_actor()
end)
Actors.Membership.Changeset.changeset(account.id, %Actors.Membership{}, %{
group_id: group_id,
actor_id: actor_id
})
|> Repo.insert!()
end
def update(actor, updates) do
update!(actor, updates)
end
def disable(actor) do
update!(actor, %{disabled_at: DateTime.utc_now()})
end
def delete(actor) do
update!(actor, %{deleted_at: DateTime.utc_now()})
end
end

View File

@@ -1,131 +0,0 @@
defmodule Domain.ActorsFixtures do
alias Domain.Repo
alias Domain.Actors
alias Domain.{AccountsFixtures, AuthFixtures}
def group_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
name: "group-#{counter()}"
})
end
def create_group(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
Map.pop_lazy(attrs, :account, fn ->
AccountsFixtures.create_account()
end)
{provider, attrs} =
Map.pop(attrs, :provider)
{provider_identifier, attrs} =
Map.pop_lazy(attrs, :provider_identifier, fn ->
Ecto.UUID.generate()
end)
{subject, attrs} =
Map.pop_lazy(attrs, :subject, fn ->
actor = create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
AuthFixtures.create_subject(identity)
end)
attrs = group_attrs(attrs)
{:ok, group} = Actors.create_group(attrs, subject)
if provider do
group
|> Ecto.Changeset.change(provider_id: provider.id, provider_identifier: provider_identifier)
|> Repo.update!()
else
group
end
end
# def create_provider_group(attrs \\ %{}) do
# attrs = Enum.into(attrs, %{})
# {account, attrs} =
# Map.pop_lazy(attrs, :account, fn ->
# AccountsFixtures.create_account()
# end)
# {provider_identifier, attrs} =
# Map.pop_lazy(attrs, :provider_identifier, fn ->
# Ecto.UUID.generate()
# end)
# {provider, attrs} =
# Map.pop_lazy(attrs, :account, fn ->
# AccountsFixtures.create_account()
# end)
# attrs = group_attrs(attrs)
# {:ok, group} = Actors.upsert_provider_group(provider, provider_identifier, attrs)
# group
# end
def delete_group(group) do
group = Repo.preload(group, :account)
actor = create_actor(type: :account_admin_user, account: group.account)
identity = AuthFixtures.create_identity(account: group.account, actor: actor)
subject = AuthFixtures.create_subject(identity)
{:ok, group} = Actors.delete_group(group, subject)
group
end
def actor_attrs(attrs \\ %{}) do
first_name = Enum.random(~w[Wade Dave Seth Riley Gilbert Jorge Dan Brian Roberto Ramon])
last_name = Enum.random(~w[Robyn Traci Desiree Jon Bob Karl Joe Alberta Lynda Cara Brandi])
Enum.into(attrs, %{
name: "#{first_name} #{last_name}",
type: :account_user
})
end
def create_actor(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
Map.pop_lazy(attrs, :account, fn ->
AccountsFixtures.create_account()
end)
{provider, attrs} =
Map.pop_lazy(attrs, :provider, fn ->
{provider, _bypass} =
AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_openid_connect_provider(account: account)
provider
end)
attrs = actor_attrs(attrs)
Actors.Actor.Changeset.create_changeset(provider.account_id, attrs)
|> Repo.insert!()
end
def update(actor, updates) do
actor
|> Ecto.Changeset.change(Map.new(updates))
|> Repo.update!()
end
def disable(actor) do
update(actor, %{disabled_at: DateTime.utc_now()})
end
def delete(actor) do
update(actor, %{deleted_at: DateTime.utc_now()})
end
defp counter do
System.unique_integer([:positive])
end
end

View File

@@ -0,0 +1,353 @@
defmodule Domain.Fixtures.Auth do
use Domain.Fixture
alias Domain.Auth
def user_password, do: "Hello w0rld!"
def remote_ip, do: {100, 64, 100, 58}
def user_agent, do: "iOS/12.5 (iPhone) connlib/0.7.412"
def email, do: "user-#{unique_integer()}@example.com"
def random_provider_identifier(%Domain.Auth.Provider{adapter: :email, name: name}) do
"user-#{unique_integer()}@#{String.downcase(name)}.com"
end
def random_provider_identifier(%Domain.Auth.Provider{adapter: :openid_connect}) do
Ecto.UUID.generate()
end
def random_provider_identifier(%Domain.Auth.Provider{adapter: :google_workspace}) do
Ecto.UUID.generate()
end
def random_provider_identifier(%Domain.Auth.Provider{adapter: :token}) do
Ecto.UUID.generate()
end
def random_provider_identifier(%Domain.Auth.Provider{adapter: :userpass, name: name}) do
"user-#{unique_integer()}@#{String.downcase(name)}.com"
end
def provider_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
name: "provider-#{unique_integer()}",
adapter: :email,
adapter_config: %{},
created_by: :system,
provisioner: :manual
})
end
def openid_connect_adapter_config(overrides \\ %{}) do
for {k, v} <- overrides,
into: %{
"discovery_document_uri" =>
"https://firezone.example.com/.well-known/openid-configuration",
"client_id" => "client-id-#{unique_integer()}",
"client_secret" => "client-secret-#{unique_integer()}",
"response_type" => "code",
"scope" => "openid email profile"
} do
{to_string(k), v}
end
end
def create_email_provider(attrs \\ %{}) do
attrs = provider_attrs(attrs)
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
{:ok, provider} = Auth.create_provider(account, attrs)
provider
end
def start_and_create_openid_connect_provider(attrs \\ %{}) do
bypass = Domain.Mocks.OpenIDConnect.discovery_document_server()
adapter_config =
openid_connect_adapter_config(
discovery_document_uri: "http://localhost:#{bypass.port}/.well-known/openid-configuration"
)
provider =
attrs
|> Enum.into(%{adapter_config: adapter_config})
|> create_openid_connect_provider()
{provider, bypass}
end
def start_and_create_google_workspace_provider(attrs \\ %{}) do
bypass = Domain.Mocks.OpenIDConnect.discovery_document_server()
adapter_config =
openid_connect_adapter_config(
discovery_document_uri: "http://localhost:#{bypass.port}/.well-known/openid-configuration"
)
provider =
attrs
|> Enum.into(%{adapter_config: adapter_config})
|> create_google_workspace_provider()
{provider, bypass}
end
def create_openid_connect_provider(attrs \\ %{}) do
attrs =
%{
adapter: :openid_connect,
provisioner: :just_in_time
}
|> Map.merge(Enum.into(attrs, %{}))
|> provider_attrs()
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
{:ok, provider} = Auth.create_provider(account, attrs)
provider =
provider
|> Ecto.Changeset.change(
disabled_at: nil,
adapter_state: %{}
)
|> Repo.update!()
provider
end
def create_google_workspace_provider(attrs \\ %{}) do
attrs =
%{
adapter: :google_workspace,
provisioner: :custom
}
|> Map.merge(Enum.into(attrs, %{}))
|> provider_attrs()
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
{:ok, provider} = Auth.create_provider(account, attrs)
update!(provider,
disabled_at: nil,
adapter_state: %{
"access_token" => "OIDC_ACCESS_TOKEN",
"refresh_token" => "OIDC_REFRESH_TOKEN",
"expires_at" => DateTime.utc_now() |> DateTime.add(1, :day),
"claims" => "openid email profile offline_access"
}
)
end
def create_userpass_provider(attrs \\ %{}) do
attrs =
attrs
|> Enum.into(%{adapter: :userpass})
|> provider_attrs()
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
{:ok, provider} = Auth.create_provider(account, attrs)
provider
end
def create_token_provider(attrs \\ %{}) do
attrs =
attrs
|> Enum.into(%{adapter: :token})
|> provider_attrs()
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
{:ok, provider} = Auth.create_provider(account, attrs)
provider
end
def disable_provider(provider) do
provider = Repo.preload(provider, :account)
subject =
Fixtures.Auth.create_subject(
account: provider.account,
actor: [type: :account_admin_user]
)
{:ok, group} = Auth.disable_provider(provider, subject)
group
end
def delete_provider(provider) do
update!(provider, deleted_at: DateTime.utc_now())
end
def identity_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
provider_virtual_state: %{}
})
end
def create_identity(attrs \\ %{}) do
attrs = identity_attrs(attrs)
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
{provider, attrs} =
pop_assoc_fixture(attrs, :provider, fn assoc_attrs ->
{provider, _bypass} =
assoc_attrs
|> Enum.into(%{account: account})
|> start_and_create_openid_connect_provider()
provider
end)
{provider_identifier, attrs} =
Map.pop_lazy(attrs, :provider_identifier, fn ->
random_provider_identifier(provider)
end)
{provider_state, attrs} =
Map.pop(attrs, :provider_state)
{actor, attrs} =
pop_assoc_fixture(attrs, :actor, fn assoc_attrs ->
assoc_attrs
|> Enum.into(%{
account: account,
provider: provider,
provider_identifier: provider_identifier
})
|> Fixtures.Actors.create_actor()
end)
attrs = Map.put(attrs, :provider_identifier, provider_identifier)
{:ok, identity} = Auth.upsert_identity(actor, provider, attrs)
if provider_state do
identity
|> Ecto.Changeset.change(provider_state: provider_state)
|> Repo.update!()
else
identity
end
end
def delete_identity(identity) do
update!(identity, deleted_at: DateTime.utc_now())
end
def create_subject(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
relation = attrs[:provider] || attrs[:actor] || attrs[:identity]
if not is_nil(relation) and is_struct(relation) do
Repo.get!(Domain.Accounts.Account, relation.account_id)
else
Fixtures.Accounts.create_account(assoc_attrs)
end
end)
{provider, attrs} =
pop_assoc_fixture(attrs, :provider, fn assoc_attrs ->
relation = attrs[:identity]
if not is_nil(relation) and is_struct(relation) do
Repo.get!(Domain.Auth.Provider, relation.provider_id)
else
{provider, _bypass} =
assoc_attrs
|> Enum.into(%{account: account})
|> start_and_create_openid_connect_provider()
provider
end
end)
{provider_identifier, attrs} =
Map.pop_lazy(attrs, :provider_identifier, fn ->
random_provider_identifier(provider)
end)
{actor, attrs} =
pop_assoc_fixture(attrs, :actor, fn assoc_attrs ->
relation = attrs[:identity]
if not is_nil(relation) and is_struct(relation) do
Repo.get!(Domain.Actors.Actor, relation.actor_id)
else
assoc_attrs
|> Enum.into(%{
type: :account_admin_user,
account: account,
provider: provider,
provider_identifier: provider_identifier
})
|> Fixtures.Actors.create_actor()
end
end)
{identity, attrs} =
pop_assoc_fixture(attrs, :identity, fn assoc_attrs ->
assoc_attrs
|> Enum.into(%{
actor: actor,
account: account,
provider: provider,
provider_identifier: provider_identifier
})
|> create_identity()
end)
{expires_at, attrs} =
Map.pop_lazy(attrs, :expires_at, fn ->
DateTime.utc_now() |> DateTime.add(60, :second)
end)
{user_agent, attrs} =
Map.pop_lazy(attrs, :user_agent, fn ->
user_agent()
end)
{remote_ip, _attrs} =
Map.pop_lazy(attrs, :remote_ip, fn ->
remote_ip()
end)
Auth.build_subject(identity, expires_at, user_agent, remote_ip)
end
def remove_permissions(%Auth.Subject{} = subject) do
%{subject | permissions: MapSet.new()}
end
def set_permissions(%Auth.Subject{} = subject, permissions) do
%{subject | permissions: MapSet.new(permissions)}
end
def add_permission(%Auth.Subject{} = subject, permission) do
%{subject | permissions: MapSet.put(subject.permissions, permission)}
end
end

View File

@@ -1,545 +0,0 @@
defmodule Domain.AuthFixtures do
alias Domain.Repo
alias Domain.Auth
alias Domain.AccountsFixtures
alias Domain.ActorsFixtures
def user_password, do: "Hello w0rld!"
def remote_ip, do: {100, 64, 100, 58}
def user_agent, do: "iOS/12.5 (iPhone) connlib/0.7.412"
def email, do: "user-#{counter()}@example.com"
def random_provider_identifier(%Domain.Auth.Provider{adapter: :email, name: name}) do
"user-#{counter()}@#{String.downcase(name)}.com"
end
def random_provider_identifier(%Domain.Auth.Provider{adapter: :openid_connect}) do
Ecto.UUID.generate()
end
def random_provider_identifier(%Domain.Auth.Provider{adapter: :google_workspace}) do
Ecto.UUID.generate()
end
def random_provider_identifier(%Domain.Auth.Provider{adapter: :token}) do
Ecto.UUID.generate()
end
def random_provider_identifier(%Domain.Auth.Provider{adapter: :userpass, name: name}) do
"user-#{counter()}@#{String.downcase(name)}.com"
end
def provider_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
name: "provider-#{counter()}",
adapter: :email,
adapter_config: %{},
created_by: :system,
provisioner: :manual
})
end
def create_email_provider(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
Map.pop_lazy(attrs, :account, fn ->
AccountsFixtures.create_account()
end)
attrs = provider_attrs(attrs)
{:ok, provider} = Auth.create_provider(account, attrs)
provider
end
def create_openid_connect_provider({bypass, [provider_attrs]}, attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
Map.pop_lazy(attrs, :account, fn ->
AccountsFixtures.create_account()
end)
attrs =
%{
adapter: :openid_connect,
adapter_config: provider_attrs,
provisioner: :just_in_time
}
|> Map.merge(attrs)
|> provider_attrs()
{:ok, provider} = Auth.create_provider(account, attrs)
provider =
provider
|> Ecto.Changeset.change(
disabled_at: nil,
adapter_state: %{}
)
|> Repo.update!()
{provider, bypass}
end
def create_google_workspace_provider({bypass, [provider_attrs]}, attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
Map.pop_lazy(attrs, :account, fn ->
AccountsFixtures.create_account()
end)
attrs =
%{
adapter: :google_workspace,
adapter_config: provider_attrs,
provisioner: :custom
}
|> Map.merge(attrs)
|> provider_attrs()
{:ok, provider} = Auth.create_provider(account, attrs)
provider =
provider
|> Ecto.Changeset.change(
disabled_at: nil,
adapter_state: %{
"access_token" => "OIDC_ACCESS_TOKEN",
"refresh_token" => "OIDC_REFRESH_TOKEN",
"expires_at" => DateTime.utc_now() |> DateTime.add(1, :day),
"claims" => "openid email profile offline_access"
}
)
|> Repo.update!()
{provider, bypass}
end
def create_userpass_provider(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, _attrs} =
Map.pop_lazy(attrs, :account, fn ->
AccountsFixtures.create_account()
end)
attrs = provider_attrs(adapter: :userpass)
{:ok, provider} = Auth.create_provider(account, attrs)
provider
end
def create_token_provider(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, _attrs} =
Map.pop_lazy(attrs, :account, fn ->
AccountsFixtures.create_account()
end)
attrs = provider_attrs(adapter: :token)
{:ok, provider} = Auth.create_provider(account, attrs)
provider
end
def disable_provider(provider) do
provider = Repo.preload(provider, :account)
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: provider.account)
identity = create_identity(account: provider.account, actor: actor)
subject = create_subject(identity)
{:ok, group} = Auth.disable_provider(provider, subject)
group
end
def create_identity(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
Map.pop_lazy(attrs, :account, fn ->
AccountsFixtures.create_account()
end)
{provider, attrs} =
Map.pop_lazy(attrs, :provider, fn ->
{provider, _bypass} =
start_openid_providers(["google"])
|> create_openid_connect_provider(account: account)
provider
end)
{provider_identifier, attrs} =
Map.pop_lazy(attrs, :provider_identifier, fn ->
random_provider_identifier(provider)
end)
{actor_default_type, attrs} =
Map.pop(attrs, :actor_default_type, :account_user)
{actor, _attrs} =
Map.pop_lazy(attrs, :actor, fn ->
ActorsFixtures.create_actor(
type: actor_default_type,
account: account,
provider: provider,
provider_identifier: provider_identifier
)
end)
{provider_virtual_state, attrs} =
Map.pop_lazy(attrs, :provider_virtual_state, fn ->
%{}
end)
{:ok, identity} =
Auth.upsert_identity(actor, provider, provider_identifier, provider_virtual_state)
if state = Map.get(attrs, :provider_state) do
identity
|> Ecto.Changeset.change(provider_state: state)
|> Repo.update!()
else
identity
end
end
def delete_identity(identity) do
identity
|> Ecto.Changeset.change(deleted_at: DateTime.utc_now())
|> Repo.update!()
end
def create_subject do
account = AccountsFixtures.create_account()
{provider, _bypass} =
start_openid_providers(["google"])
|> create_openid_connect_provider(account: account)
actor =
ActorsFixtures.create_actor(
type: :account_admin_user,
account: account,
provider: provider
)
identity = create_identity(actor: actor, account: account, provider: provider)
create_subject(identity)
end
def create_subject(%Auth.Identity{} = identity) do
identity = Repo.preload(identity, [:account, :actor])
%Auth.Subject{
identity: identity,
actor: identity.actor,
permissions: Auth.Roles.build(identity.actor.type).permissions,
account: identity.account,
expires_at: DateTime.utc_now() |> DateTime.add(60, :second),
context: %Auth.Context{remote_ip: remote_ip(), user_agent: user_agent()}
}
end
def remove_permissions(%Auth.Subject{} = subject) do
%{subject | permissions: MapSet.new()}
end
def set_permissions(%Auth.Subject{} = subject, permissions) do
%{subject | permissions: MapSet.new(permissions)}
end
def add_permission(%Auth.Subject{} = subject, permission) do
%{subject | permissions: MapSet.put(subject.permissions, permission)}
end
def start_openid_providers(provider_names, overrides \\ %{}) do
{bypass, discovery_document_url} = discovery_document_server()
openid_connect_providers_attrs =
discovery_document_url
|> openid_connect_providers_attrs()
|> Enum.filter(fn {name, _config} ->
name in provider_names
end)
|> Enum.map(fn {_name, config} ->
config
|> Enum.into(%{})
|> Map.merge(overrides)
end)
{bypass, openid_connect_providers_attrs}
end
def openid_connect_provider_attrs(overrides \\ %{}) do
Enum.into(overrides, %{
"discovery_document_uri" => "https://firezone.example.com/.well-known/openid-configuration",
"client_id" => "google-client-id-#{counter()}",
"client_secret" => "google-client-secret",
"response_type" => "code",
"scope" => "openid email profile"
})
end
defp openid_connect_providers_attrs(discovery_document_url) do
%{
"google" => %{
"discovery_document_uri" => discovery_document_url,
"client_id" => "google-client-id-#{counter()}",
"client_secret" => "google-client-secret",
"response_type" => "code",
"scope" => "openid email profile"
},
"okta" => %{
"discovery_document_uri" => discovery_document_url,
"client_id" => "okta-client-id-#{counter()}",
"client_secret" => "okta-client-secret",
"response_type" => "code",
"scope" => "openid email profile offline_access"
},
"auth0" => %{
"discovery_document_uri" => discovery_document_url,
"client_id" => "auth0-client-id-#{counter()}",
"client_secret" => "auth0-client-secret",
"response_type" => "code",
"scope" => "openid email profile"
},
"azure" => %{
"discovery_document_uri" => discovery_document_url,
"client_id" => "azure-client-id-#{counter()}",
"client_secret" => "azure-client-secret",
"response_type" => "code",
"scope" => "openid email profile offline_access"
},
"onelogin" => %{
"discovery_document_uri" => discovery_document_url,
"client_id" => "onelogin-client-id-#{counter()}",
"client_secret" => "onelogin-client-secret",
"response_type" => "code",
"scope" => "openid email profile offline_access"
},
"keycloak" => %{
"discovery_document_uri" => discovery_document_url,
"client_id" => "keycloak-client-id-#{counter()}",
"client_secret" => "keycloak-client-secret",
"response_type" => "code",
"scope" => "openid email profile offline_access"
},
"vault" => %{
"discovery_document_uri" => discovery_document_url,
"client_id" => "vault-client-id-#{counter()}",
"client_secret" => "vault-client-secret",
"response_type" => "code",
"scope" => "openid email profile offline_access"
}
}
end
def jwks_attrs do
%{
"alg" => "RS256",
"d" =>
"X8TM24Zqbiha9geYYk_vZpANu16IadJLJLJ7ucTc3JaMbK8NCYNcHMoXKnNYPFxmq-UWAEIwh-2" <>
"txOiOxuChVrblpfyE4SBJio1T0AUcCwmm8U6G-CsSHMMzWTt2dMTnArHjdyAIgOVRW5SVzhTT" <>
"taf4JY-47S-fbcJ7g0hmBbVih5i1sE2fad4I4qFHT-YFU_pnUHbteR6GQuRW4r03Eon8Aje6a" <>
"l2AxcYnfF8_cSOIOpkDgGavTtGYhhZPi2jZ7kPm6QGkNW5CyfEq5PGB6JOihw-XIFiiMzYgx0" <>
"52rnzoqALoLheXrI0By4kgHSmcqOOmq7aiOff45rlSbpsR",
"e" => "AQAB",
"kid" => "example@firezone.dev",
"kty" => "RSA",
"n" =>
"qlKll8no4lPYXNSuTTnacpFHiXwPOv_htCYvIXmiR7CWhiiOHQqj7KWXIW7TGxyoLVIyeRM4mwv" <>
"kLI-UgsSMYdEKTT0j7Ydjrr0zCunPu5Gxr2yOmcRaszAzGxJL5DwpA0V40RqMlm5OuwdqS4To" <>
"_p9LlLxzMF6RZe1OqslV5RZ4Y8FmrWq6BV98eIziEHL0IKdsAIrrOYkkcLDdQeMNuTp_yNB8X" <>
"l2TdWSdsbRomrs2dCtCqZcXTsy2EXDceHvYhgAB33nh_w17WLrZQwMM-7kJk36Kk54jZd7i80" <>
"AJf_s_plXn1mEh-L5IAL1vg3a9EOMFUl-lPiGqc3td_ykH",
"use" => "sig"
}
end
def expect_refresh_token(bypass, attrs \\ %{}) do
test_pid = self()
Bypass.expect(bypass, "POST", "/oauth/token", fn conn ->
conn = fetch_conn_params(conn)
send(test_pid, {:request, conn})
Plug.Conn.resp(conn, 200, Jason.encode!(attrs))
end)
end
def expect_refresh_token_failure(bypass, attrs \\ %{}) do
test_pid = self()
Bypass.expect(bypass, "POST", "/oauth/token", fn conn ->
conn = fetch_conn_params(conn)
send(test_pid, {:request, conn})
Plug.Conn.resp(conn, 401, Jason.encode!(attrs))
end)
end
def expect_userinfo(bypass, attrs \\ %{}) do
test_pid = self()
Bypass.expect(bypass, "GET", "/userinfo", fn conn ->
attrs =
Map.merge(
%{
"sub" => "353690423699814251281",
"name" => "Ada Lovelace",
"given_name" => "Ada",
"family_name" => "Lovelace",
"picture" =>
"https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg",
"email" => "ada@example.com",
"email_verified" => true,
"locale" => "en"
},
attrs
)
conn = fetch_conn_params(conn)
send(test_pid, {:request, conn})
Plug.Conn.resp(conn, 200, Jason.encode!(attrs))
end)
end
def discovery_document_server do
bypass = Bypass.open()
endpoint = "http://localhost:#{bypass.port}"
test_pid = self()
Bypass.stub(bypass, "GET", "/.well-known/jwks.json", fn conn ->
attrs = %{"keys" => [jwks_attrs()]}
Plug.Conn.resp(conn, 200, Jason.encode!(attrs))
end)
Bypass.stub(bypass, "GET", "/.well-known/openid-configuration", fn conn ->
conn = fetch_conn_params(conn)
send(test_pid, {:request, conn})
attrs = %{
"issuer" => "#{endpoint}/",
"authorization_endpoint" => "#{endpoint}/authorize",
"token_endpoint" => "#{endpoint}/oauth/token",
"device_authorization_endpoint" => "#{endpoint}/oauth/device/code",
"userinfo_endpoint" => "#{endpoint}/userinfo",
"mfa_challenge_endpoint" => "#{endpoint}/mfa/challenge",
"jwks_uri" => "#{endpoint}/.well-known/jwks.json",
"registration_endpoint" => "#{endpoint}/oidc/register",
"revocation_endpoint" => "#{endpoint}/oauth/revoke",
"end_session_endpoint" => "https://example.com",
"scopes_supported" => [
"openid",
"profile",
"offline_access",
"name",
"given_name",
"family_name",
"nickname",
"email",
"email_verified",
"picture",
"created_at",
"identities",
"phone",
"address"
],
"response_types_supported" => [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token"
],
"code_challenge_methods_supported" => [
"S256",
"plain"
],
"response_modes_supported" => [
"query",
"fragment",
"form_post"
],
"subject_types_supported" => [
"public"
],
"id_token_signing_alg_values_supported" => [
"HS256",
"RS256"
],
"token_endpoint_auth_methods_supported" => [
"client_secret_basic",
"client_secret_post"
],
"claims_supported" => [
"aud",
"auth_time",
"created_at",
"email",
"email_verified",
"exp",
"family_name",
"given_name",
"iat",
"identities",
"iss",
"name",
"nickname",
"phone_number",
"picture",
"sub"
],
"request_uri_parameter_supported" => false,
"request_parameter_supported" => false
}
Plug.Conn.resp(conn, 200, Jason.encode!(attrs))
end)
{bypass, "#{endpoint}/.well-known/openid-configuration"}
end
def generate_openid_connect_token(provider, identity, claims \\ %{}) do
claims =
Map.merge(
%{
"email" => identity.provider_identifier,
"sub" => identity.provider_identifier,
"aud" => provider.adapter_config["client_id"],
"exp" => DateTime.utc_now() |> DateTime.add(10, :second) |> DateTime.to_unix()
},
claims
)
{sign_openid_connect_token(claims), claims}
end
def sign_openid_connect_token(claims) do
jwk = jwks_attrs()
{_alg, token} =
jwk
|> JOSE.JWK.from()
|> JOSE.JWS.sign(Jason.encode!(claims), %{"alg" => "RS256"})
|> JOSE.JWS.compact()
token
end
defp fetch_conn_params(conn) do
opts = Plug.Parsers.init(parsers: [:urlencoded, :json], pass: ["*/*"], json_decoder: Jason)
conn
|> Plug.Conn.fetch_query_params()
|> Plug.Parsers.call(opts)
end
defp counter do
System.unique_integer([:positive])
end
end

View File

@@ -1,6 +1,6 @@
defmodule Domain.ConfigFixtures do
defmodule Domain.Fixtures.Config do
use Domain.Fixture
alias Domain.Config
alias Domain.AccountsFixtures
def configuration_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
@@ -9,15 +9,13 @@ defmodule Domain.ConfigFixtures do
end
def upsert_configuration(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
attrs = configuration_attrs(attrs)
{account, attrs} =
Map.pop_lazy(attrs, :account, fn ->
AccountsFixtures.create_account()
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
attrs = configuration_attrs(attrs)
{:ok, configuration} =
Config.get_account_config_by_account_id(account.id)
|> Config.update_config(attrs)

View File

@@ -0,0 +1,66 @@
defmodule Domain.Fixtures.Devices do
use Domain.Fixture
alias Domain.Devices
def device_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
external_id: Ecto.UUID.generate(),
name: "device-#{unique_integer()}",
public_key: unique_public_key()
})
end
def create_device(attrs \\ %{}) do
attrs = device_attrs(attrs)
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
if relation = attrs[:actor] || attrs[:identity] do
Repo.get!(Domain.Accounts.Account, relation.account_id)
else
Fixtures.Accounts.create_account(assoc_attrs)
end
end)
{actor, attrs} =
pop_assoc_fixture(attrs, :actor, fn assoc_attrs ->
assoc_attrs
|> Enum.into(%{type: :account_admin_user, account: account})
|> Fixtures.Actors.create_actor()
end)
{identity, attrs} =
pop_assoc_fixture(attrs, :identity, fn assoc_attrs ->
assoc_attrs
|> Enum.into(%{account: account, actor: actor})
|> Fixtures.Auth.create_identity()
end)
{subject, attrs} =
pop_assoc_fixture(attrs, :subject, fn assoc_attrs ->
assoc_attrs
|> Enum.into(%{
account: account,
identity: identity,
actor: [type: :account_admin_user]
})
|> Fixtures.Auth.create_subject()
end)
{:ok, device} = Devices.upsert_device(attrs, subject)
%{device | online?: false}
end
def delete_device(device) do
device = Repo.preload(device, :account)
subject =
Fixtures.Auth.create_subject(
account: device.account,
actor: [type: :account_admin_user]
)
{:ok, device} = Devices.delete_device(device, subject)
device
end
end

View File

@@ -1,64 +0,0 @@
defmodule Domain.DevicesFixtures do
alias Domain.Repo
alias Domain.Devices
alias Domain.{AccountsFixtures, ActorsFixtures, AuthFixtures}
def device_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
external_id: Ecto.UUID.generate(),
name: "device-#{counter()}",
public_key: public_key()
})
end
def create_device(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
Map.pop_lazy(attrs, :account, fn ->
if relation = attrs[:actor] || attrs[:identity] do
Repo.get!(Domain.Accounts.Account, relation.account_id)
else
AccountsFixtures.create_account()
end
end)
{actor, attrs} =
Map.pop_lazy(attrs, :actor, fn ->
ActorsFixtures.create_actor(type: :account_admin_user, account: account)
end)
{identity, attrs} =
Map.pop_lazy(attrs, :identity, fn ->
AuthFixtures.create_identity(account: account, actor: actor)
end)
{subject, attrs} =
Map.pop_lazy(attrs, :subject, fn ->
AuthFixtures.create_subject(identity)
end)
attrs = device_attrs(attrs)
{:ok, device} = Devices.upsert_device(attrs, subject)
device
end
def delete_device(device) do
device = Repo.preload(device, :account)
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: device.account)
identity = AuthFixtures.create_identity(account: device.account, actor: actor)
subject = AuthFixtures.create_subject(identity)
{:ok, device} = Devices.delete_device(device, subject)
device
end
def public_key do
:crypto.strong_rand_bytes(32)
|> Base.encode64()
end
defp counter do
System.unique_integer([:positive])
end
end

View File

@@ -0,0 +1,118 @@
defmodule Domain.Fixtures.Gateways do
use Domain.Fixture
alias Domain.Gateways
def group_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
name_prefix: "group-#{unique_integer()}",
tags: ["aws", "aws-us-east-#{unique_integer()}"],
tokens: [%{}]
})
end
def create_group(attrs \\ %{}) do
attrs = group_attrs(attrs)
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
{subject, attrs} =
pop_assoc_fixture(attrs, :subject, fn assoc_attrs ->
assoc_attrs
|> Enum.into(%{account: account, actor: [type: :account_admin_user]})
|> Fixtures.Auth.create_subject()
end)
{:ok, group} = Gateways.create_group(attrs, subject)
group
end
def delete_group(group) do
group = Repo.preload(group, :account)
subject =
Fixtures.Auth.create_subject(
account: group.account,
actor: [type: :account_admin_user]
)
{:ok, group} = Gateways.delete_group(group, subject)
group
end
def create_token(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
{group, attrs} =
pop_assoc_fixture(attrs, :group, fn assoc_attrs ->
assoc_attrs
|> Enum.into(%{account: account})
|> create_group()
end)
{subject, _attrs} =
pop_assoc_fixture(attrs, :subject, fn assoc_attrs ->
assoc_attrs
|> Enum.into(%{account: account, actor: [type: :account_admin_user]})
|> Fixtures.Auth.create_subject()
end)
Gateways.Token.Changeset.create(account, subject)
|> Ecto.Changeset.put_change(:group_id, group.id)
|> Repo.insert!()
end
def gateway_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
external_id: Ecto.UUID.generate(),
name_suffix: "gw-#{Domain.Crypto.rand_string(5)}",
public_key: unique_public_key(),
last_seen_user_agent: "iOS/12.7 (iPhone) connlib/0.7.412",
last_seen_remote_ip: %Postgrex.INET{address: {189, 172, 73, 153}}
})
end
def create_gateway(attrs \\ %{}) do
attrs = gateway_attrs(attrs)
{account, attrs} =
pop_assoc_fixture(attrs, :account, fn assoc_attrs ->
Fixtures.Accounts.create_account(assoc_attrs)
end)
{group, attrs} =
pop_assoc_fixture(attrs, :group, fn assoc_attrs ->
assoc_attrs
|> Enum.into(%{account: account})
|> create_group()
end)
{token, attrs} =
Map.pop_lazy(attrs, :token, fn ->
hd(group.tokens)
end)
{:ok, gateway} = Gateways.upsert_gateway(token, attrs)
%{gateway | online?: false}
end
def delete_gateway(gateway) do
gateway = Repo.preload(gateway, :account)
subject =
Fixtures.Auth.create_subject(
account: gateway.account,
actor: [type: :account_admin_user]
)
{:ok, gateway} = Gateways.delete_gateway(gateway, subject)
gateway
end
end

View File

@@ -1,132 +0,0 @@
defmodule Domain.GatewaysFixtures do
alias Domain.AccountsFixtures
alias Domain.Repo
alias Domain.Gateways
alias Domain.{AccountsFixtures, ActorsFixtures, AuthFixtures}
def group_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
name_prefix: "group-#{counter()}",
tags: ["aws", "aws-us-east-#{counter()}"],
tokens: [%{}]
})
end
def create_group(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
Map.pop_lazy(attrs, :account, fn ->
AccountsFixtures.create_account()
end)
{subject, attrs} =
Map.pop_lazy(attrs, :subject, fn ->
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
AuthFixtures.create_subject(identity)
end)
attrs = group_attrs(attrs)
{:ok, group} = Gateways.create_group(attrs, subject)
group
end
def delete_group(group) do
group = Repo.preload(group, :account)
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: group.account)
identity = AuthFixtures.create_identity(account: group.account, actor: actor)
subject = AuthFixtures.create_subject(identity)
{:ok, group} = Gateways.delete_group(group, subject)
group
end
def create_token(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
Map.pop_lazy(attrs, :account, fn ->
AccountsFixtures.create_account()
end)
{group, attrs} =
case Map.pop(attrs, :group, %{}) do
{%Gateways.Group{} = group, _attrs} ->
{group, attrs}
{group_attrs, attrs} ->
group_attrs = Enum.into(group_attrs, %{account: account})
{create_group(group_attrs), attrs}
end
{subject, _attrs} =
Map.pop_lazy(attrs, :subject, fn ->
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
AuthFixtures.create_subject(identity)
end)
Gateways.Token.Changeset.create_changeset(account, subject)
|> Ecto.Changeset.put_change(:group_id, group.id)
|> Repo.insert!()
end
def gateway_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
external_id: Ecto.UUID.generate(),
name_suffix: "gw-#{Domain.Crypto.rand_string(5)}",
public_key: public_key(),
last_seen_user_agent: "iOS/12.7 (iPhone) connlib/0.7.412",
last_seen_remote_ip: %Postgrex.INET{address: {189, 172, 73, 153}}
})
end
def create_gateway(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
Map.pop_lazy(attrs, :account, fn ->
AccountsFixtures.create_account()
end)
{group, attrs} =
case Map.pop(attrs, :group, %{}) do
{%Gateways.Group{} = group, attrs} ->
{group, attrs}
{group_attrs, attrs} ->
group_attrs = Enum.into(group_attrs, %{account: account})
group = create_group(group_attrs)
{group, attrs}
end
{token, attrs} =
Map.pop_lazy(attrs, :token, fn ->
hd(group.tokens)
end)
attrs = gateway_attrs(attrs)
{:ok, gateway} = Gateways.upsert_gateway(token, attrs)
%{gateway | online?: false}
end
def delete_gateway(gateway) do
gateway = Repo.preload(gateway, :account)
actor = ActorsFixtures.create_actor(type: :account_admin_user, account: gateway.account)
identity = AuthFixtures.create_identity(account: gateway.account, actor: actor)
subject = AuthFixtures.create_subject(identity)
{:ok, gateway} = Gateways.delete_gateway(gateway, subject)
gateway
end
def public_key do
:crypto.strong_rand_bytes(32)
|> Base.encode64()
end
defp counter do
System.unique_integer([:positive])
end
end

Some files were not shown because too many files have changed in this diff Show More