mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 18:18:55 +00:00
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:
@@ -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),
|
||||
|
||||
@@ -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 = %{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
82
elixir/apps/domain/lib/domain/actors/group/sync.ex
Normal file
82
elixir/apps/domain/lib/domain/actors/group/sync.ex
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
113
elixir/apps/domain/lib/domain/actors/membership/sync.ex
Normal file
113
elixir/apps/domain/lib/domain/actors/membership/sync.ex
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
78
elixir/apps/domain/lib/domain/auth/identity/sync.ex
Normal file
78
elixir/apps/domain/lib/domain/auth/identity/sync.ex
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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: %{
|
||||
|
||||
@@ -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
|
||||
@@ -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/"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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"]}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -23,6 +23,8 @@ defmodule Domain.DataCase do
|
||||
import Domain.DataCase
|
||||
|
||||
alias Domain.Repo
|
||||
alias Domain.Fixtures
|
||||
alias Domain.Mocks
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
65
elixir/apps/domain/test/support/fixture.ex
Normal file
65
elixir/apps/domain/test/support/fixture.ex
Normal 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
|
||||
@@ -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
|
||||
136
elixir/apps/domain/test/support/fixtures/actors.ex
Normal file
136
elixir/apps/domain/test/support/fixtures/actors.ex
Normal 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
|
||||
@@ -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
|
||||
353
elixir/apps/domain/test/support/fixtures/auth.ex
Normal file
353
elixir/apps/domain/test/support/fixtures/auth.ex
Normal 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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
66
elixir/apps/domain/test/support/fixtures/devices.ex
Normal file
66
elixir/apps/domain/test/support/fixtures/devices.ex
Normal 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
|
||||
@@ -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
|
||||
118
elixir/apps/domain/test/support/fixtures/gateways.ex
Normal file
118
elixir/apps/domain/test/support/fixtures/gateways.ex
Normal 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
|
||||
@@ -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
Reference in New Issue
Block a user