mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
feat(portal): Time based policies (#6115)
Flows authorized by time-based policies will now expire at the latest time permitted by the policy.
This commit is contained in:
@@ -32,7 +32,7 @@ defmodule Domain.Flows do
|
||||
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.create_flows_permission()),
|
||||
{:ok, resource} <-
|
||||
Resources.fetch_and_authorize_resource_by_id(resource_id, subject, opts),
|
||||
{:ok, policy} <- fetch_conforming_policy(resource, client) do
|
||||
{:ok, policy, conformation_expires_at} <- fetch_conforming_policy(resource, client) do
|
||||
flow =
|
||||
Flow.Changeset.create(%{
|
||||
token_id: token_id,
|
||||
@@ -44,7 +44,7 @@ defmodule Domain.Flows do
|
||||
client_remote_ip: client_remote_ip,
|
||||
client_user_agent: client_user_agent,
|
||||
gateway_remote_ip: gateway_remote_ip,
|
||||
expires_at: expires_at
|
||||
expires_at: conformation_expires_at || expires_at
|
||||
})
|
||||
|> Repo.insert!()
|
||||
|
||||
@@ -55,8 +55,8 @@ defmodule Domain.Flows do
|
||||
defp fetch_conforming_policy(%Resources.Resource{} = resource, client) do
|
||||
Enum.reduce_while(resource.authorized_by_policies, {:error, []}, fn policy, {:error, acc} ->
|
||||
case Policies.ensure_client_conforms_policy_conditions(client, policy) do
|
||||
:ok ->
|
||||
{:halt, {:ok, policy}}
|
||||
{:ok, expires_at} ->
|
||||
{:halt, {:ok, policy, expires_at}}
|
||||
|
||||
{:error, {:forbidden, violated_properties: violated_properties}} ->
|
||||
{:cont, {:error, violated_properties ++ acc}}
|
||||
@@ -66,8 +66,8 @@ defmodule Domain.Flows do
|
||||
{:error, violated_properties} ->
|
||||
{:error, {:forbidden, violated_properties: violated_properties}}
|
||||
|
||||
{:ok, policy} ->
|
||||
{:ok, policy}
|
||||
{:ok, policy, expires_at} ->
|
||||
{:ok, policy, expires_at}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -167,8 +167,8 @@ defmodule Domain.Policies do
|
||||
|
||||
def ensure_client_conforms_policy_conditions(%Clients.Client{} = client, %Policy{} = policy) do
|
||||
case Condition.Evaluator.ensure_conforms(policy.conditions, client) do
|
||||
:ok ->
|
||||
:ok
|
||||
{:ok, expires_at} ->
|
||||
{:ok, expires_at}
|
||||
|
||||
{:error, violated_properties} ->
|
||||
{:error, {:forbidden, violated_properties: violated_properties}}
|
||||
|
||||
@@ -6,79 +6,118 @@ defmodule Domain.Policies.Condition.Evaluator do
|
||||
@days_of_week ~w[M T W R F S U]
|
||||
|
||||
def ensure_conforms([], %Clients.Client{}) do
|
||||
:ok
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
def ensure_conforms(conditions, %Clients.Client{} = client) when is_list(conditions) do
|
||||
client = Repo.preload(client, :identity)
|
||||
|
||||
conditions
|
||||
|> Enum.reduce([], fn condition, violated_properties ->
|
||||
cond do
|
||||
conforms?(condition, client) -> violated_properties
|
||||
condition.property in violated_properties -> violated_properties
|
||||
true -> [condition.property | violated_properties]
|
||||
|> Enum.reduce({[], nil}, fn condition, {violated_properties, min_expires_at} ->
|
||||
if condition.property in violated_properties do
|
||||
{violated_properties, min_expires_at}
|
||||
else
|
||||
case fetch_conformation_expiration(condition, client) do
|
||||
{:ok, expires_at} ->
|
||||
{violated_properties, min_expires_at(expires_at, min_expires_at)}
|
||||
|
||||
:error ->
|
||||
{[condition.property | violated_properties], min_expires_at}
|
||||
end
|
||||
end
|
||||
end)
|
||||
|> case do
|
||||
[] -> :ok
|
||||
violated_properties -> {:error, Enum.reverse(violated_properties)}
|
||||
{[], expires_at} -> {:ok, expires_at}
|
||||
{violated_properties, _expires_at} -> {:error, Enum.reverse(violated_properties)}
|
||||
end
|
||||
end
|
||||
|
||||
def conforms?(
|
||||
defp min_expires_at(expires_at, nil), do: expires_at
|
||||
|
||||
defp min_expires_at(expires_at, min_expires_at),
|
||||
do: Enum.min([expires_at, min_expires_at], DateTime)
|
||||
|
||||
def fetch_conformation_expiration(
|
||||
%Condition{property: :remote_ip_location_region, operator: :is_in, values: values},
|
||||
%Clients.Client{} = client
|
||||
) do
|
||||
client.last_seen_remote_ip_location_region in values
|
||||
if client.last_seen_remote_ip_location_region in values do
|
||||
{:ok, nil}
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def conforms?(
|
||||
def fetch_conformation_expiration(
|
||||
%Condition{property: :remote_ip_location_region, operator: :is_not_in, values: values},
|
||||
%Clients.Client{} = client
|
||||
) do
|
||||
client.last_seen_remote_ip_location_region not in values
|
||||
if client.last_seen_remote_ip_location_region in values do
|
||||
:error
|
||||
else
|
||||
{:ok, nil}
|
||||
end
|
||||
end
|
||||
|
||||
def conforms?(
|
||||
def fetch_conformation_expiration(
|
||||
%Condition{property: :remote_ip, operator: :is_in_cidr, values: values},
|
||||
%Clients.Client{} = client
|
||||
) do
|
||||
Enum.any?(values, fn cidr ->
|
||||
Enum.reduce_while(values, :error, fn cidr, :error ->
|
||||
{:ok, inet} = Domain.Types.INET.cast(cidr)
|
||||
cidr = %{inet | netmask: inet.netmask || Domain.Types.CIDR.max_netmask(inet)}
|
||||
Domain.Types.CIDR.contains?(cidr, client.last_seen_remote_ip)
|
||||
|
||||
if Domain.Types.CIDR.contains?(cidr, client.last_seen_remote_ip) do
|
||||
{:halt, {:ok, nil}}
|
||||
else
|
||||
{:cont, :error}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def conforms?(
|
||||
def fetch_conformation_expiration(
|
||||
%Condition{property: :remote_ip, operator: :is_not_in_cidr, values: values},
|
||||
%Clients.Client{} = client
|
||||
) do
|
||||
Enum.all?(values, fn cidr ->
|
||||
Enum.reduce_while(values, {:ok, nil}, fn cidr, {:ok, nil} ->
|
||||
{:ok, inet} = Domain.Types.INET.cast(cidr)
|
||||
cidr = %{inet | netmask: inet.netmask || Domain.Types.CIDR.max_netmask(inet)}
|
||||
not Domain.Types.CIDR.contains?(cidr, client.last_seen_remote_ip)
|
||||
|
||||
if Domain.Types.CIDR.contains?(cidr, client.last_seen_remote_ip) do
|
||||
{:halt, :error}
|
||||
else
|
||||
{:cont, {:ok, nil}}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def conforms?(
|
||||
def fetch_conformation_expiration(
|
||||
%Condition{property: :provider_id, operator: :is_in, values: values},
|
||||
%Clients.Client{} = client
|
||||
) do
|
||||
client = Repo.preload(client, :identity)
|
||||
client.identity.provider_id in values
|
||||
|
||||
if client.identity.provider_id in values do
|
||||
{:ok, nil}
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def conforms?(
|
||||
def fetch_conformation_expiration(
|
||||
%Condition{property: :provider_id, operator: :is_not_in, values: values},
|
||||
%Clients.Client{} = client
|
||||
) do
|
||||
client = Repo.preload(client, :identity)
|
||||
client.identity.provider_id not in values
|
||||
|
||||
if client.identity.provider_id in values do
|
||||
:error
|
||||
else
|
||||
{:ok, nil}
|
||||
end
|
||||
end
|
||||
|
||||
def conforms?(
|
||||
def fetch_conformation_expiration(
|
||||
%Condition{
|
||||
property: :current_utc_datetime,
|
||||
operator: :is_in_day_of_week_time_ranges,
|
||||
@@ -86,33 +125,75 @@ defmodule Domain.Policies.Condition.Evaluator do
|
||||
},
|
||||
%Clients.Client{}
|
||||
) do
|
||||
datetime_in_day_of_the_week_time_ranges?(DateTime.utc_now(), values)
|
||||
case find_day_of_the_week_time_range(values, DateTime.utc_now()) do
|
||||
nil -> :error
|
||||
expires_at -> {:ok, expires_at}
|
||||
end
|
||||
end
|
||||
|
||||
def datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) do
|
||||
def find_day_of_the_week_time_range(dow_time_ranges, datetime) do
|
||||
dow_time_ranges
|
||||
|> parse_days_of_week_time_ranges()
|
||||
|> case do
|
||||
{:ok, dow_time_ranges} ->
|
||||
Enum.any?(dow_time_ranges, fn {day, time_ranges} ->
|
||||
dow_time_ranges
|
||||
|> Enum.find_value(fn {day, time_ranges} ->
|
||||
time_ranges = merge_joint_time_ranges(time_ranges)
|
||||
datetime_in_time_ranges?(datetime, day, time_ranges)
|
||||
end)
|
||||
|
||||
{:error, _reason} ->
|
||||
false
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
# Merge ranges, eg. 4-11,11-22 = 4-22
|
||||
def merge_joint_time_ranges(time_ranges) do
|
||||
merged_time_ranges =
|
||||
Enum.reduce(time_ranges, [], fn {start_time, end_time, timezone}, acc ->
|
||||
index =
|
||||
Enum.find_index(acc, fn {acc_start_time, acc_end_time, acc_timezone} ->
|
||||
acc_timezone == timezone and
|
||||
(time_in_range?(start_time, acc_start_time, acc_end_time) or
|
||||
time_in_range?(end_time, acc_start_time, acc_end_time) or
|
||||
time_in_range?(acc_start_time, start_time, end_time) or
|
||||
time_in_range?(acc_end_time, start_time, end_time))
|
||||
end)
|
||||
|
||||
if index == nil do
|
||||
[{start_time, end_time, timezone}] ++ acc
|
||||
else
|
||||
{{acc_start_time, acc_end_time, _timezone}, acc} = List.pop_at(acc, index)
|
||||
start_time = Enum.min([start_time, acc_start_time], Time)
|
||||
end_time = Enum.max([end_time, acc_end_time], Time)
|
||||
[{start_time, end_time, timezone}] ++ acc
|
||||
end
|
||||
end)
|
||||
|> Enum.reverse()
|
||||
|
||||
if merged_time_ranges == time_ranges do
|
||||
merged_time_ranges
|
||||
else
|
||||
merge_joint_time_ranges(merged_time_ranges)
|
||||
end
|
||||
end
|
||||
|
||||
defp time_in_range?(time, range_start, range_end) do
|
||||
Time.compare(range_start, time) in [:lt, :eq] and
|
||||
Time.compare(time, range_end) in [:lt, :eq]
|
||||
end
|
||||
|
||||
defp datetime_in_time_ranges?(datetime, day_of_the_week, time_ranges) do
|
||||
Enum.any?(time_ranges, fn {start_time, end_time, timezone} ->
|
||||
{:ok, datetime} = DateTime.shift_zone(datetime, timezone, Tzdata.TimeZoneDatabase)
|
||||
Enum.find_value(time_ranges, fn {start_time, end_time, timezone} ->
|
||||
datetime = DateTime.shift_zone!(datetime, timezone, Tzdata.TimeZoneDatabase)
|
||||
date = DateTime.to_date(datetime)
|
||||
time = DateTime.to_time(datetime)
|
||||
|
||||
if Enum.at(@days_of_week, Date.day_of_week(date) - 1) == day_of_the_week do
|
||||
Time.compare(start_time, time) != :gt and Time.compare(time, end_time) != :gt
|
||||
else
|
||||
false
|
||||
if Enum.at(@days_of_week, Date.day_of_week(date) - 1) == day_of_the_week and
|
||||
Time.compare(start_time, time) != :gt and Time.compare(time, end_time) != :gt do
|
||||
DateTime.new!(date, end_time, timezone, Tzdata.TimeZoneDatabase)
|
||||
|> DateTime.shift_zone!("UTC", Tzdata.TimeZoneDatabase)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -172,6 +172,13 @@ defmodule Domain.FlowsTest do
|
||||
actor_group2 = Fixtures.Actors.create_group(account: account)
|
||||
Fixtures.Actors.create_membership(account: account, actor: actor, group: actor_group2)
|
||||
|
||||
time = Time.utc_now()
|
||||
one_hour_ago = Time.add(time, -1, :hour)
|
||||
one_hour_in_future = Time.add(time, 1, :hour)
|
||||
|
||||
date = Date.utc_today()
|
||||
day_of_week = Enum.at(~w[M T W R F S U], Date.day_of_week(date) - 1)
|
||||
|
||||
Fixtures.Policies.create_policy(
|
||||
account: account,
|
||||
actor_group: actor_group2,
|
||||
@@ -181,6 +188,13 @@ defmodule Domain.FlowsTest do
|
||||
property: :remote_ip_location_region,
|
||||
operator: :is_not_in,
|
||||
values: [client.last_seen_remote_ip_location_region]
|
||||
},
|
||||
%{
|
||||
property: :current_utc_datetime,
|
||||
operator: :is_in_day_of_week_time_ranges,
|
||||
values: [
|
||||
"#{day_of_week}/#{one_hour_ago}-#{one_hour_in_future}/UTC"
|
||||
]
|
||||
}
|
||||
]
|
||||
)
|
||||
@@ -189,6 +203,7 @@ defmodule Domain.FlowsTest do
|
||||
authorize_flow(client, gateway, resource.id, subject)
|
||||
|
||||
assert flow.policy_id == policy.id
|
||||
assert DateTime.diff(flow.expires_at, DateTime.new!(date, one_hour_in_future)) < 5
|
||||
end
|
||||
|
||||
test "creates a flow when all conditions for at least one of the policies are satisfied", %{
|
||||
@@ -223,8 +238,10 @@ defmodule Domain.FlowsTest do
|
||||
]
|
||||
)
|
||||
|
||||
assert {:ok, _fetched_resource, _flow} =
|
||||
assert {:ok, _fetched_resource, flow} =
|
||||
authorize_flow(client, gateway, resource.id, subject)
|
||||
|
||||
assert flow.expires_at == subject.expires_at
|
||||
end
|
||||
|
||||
test "creates a network flow for users", %{
|
||||
|
||||
@@ -5,7 +5,7 @@ defmodule Domain.Policies.Condition.EvaluatorTest do
|
||||
describe "ensure_conforms/2" do
|
||||
test "returns ok when there are no conditions" do
|
||||
client = %Domain.Clients.Client{}
|
||||
assert ensure_conforms([], client) == :ok
|
||||
assert ensure_conforms([], client) == {:ok, nil}
|
||||
end
|
||||
|
||||
test "returns ok when all conditions are met" do
|
||||
@@ -27,7 +27,7 @@ defmodule Domain.Policies.Condition.EvaluatorTest do
|
||||
}
|
||||
]
|
||||
|
||||
assert ensure_conforms(conditions, client) == :ok
|
||||
assert ensure_conforms(conditions, client) == {:ok, nil}
|
||||
end
|
||||
|
||||
test "returns error when all conditions are not met" do
|
||||
@@ -77,7 +77,7 @@ defmodule Domain.Policies.Condition.EvaluatorTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "conforms?/2" do
|
||||
describe "fetch_conformation_expiration/2" do
|
||||
test "when client last seen remote ip location region is in or not in the values" do
|
||||
condition = %Domain.Policies.Condition{
|
||||
property: :remote_ip_location_region,
|
||||
@@ -88,15 +88,17 @@ defmodule Domain.Policies.Condition.EvaluatorTest do
|
||||
last_seen_remote_ip_location_region: "US"
|
||||
}
|
||||
|
||||
assert conforms?(%{condition | operator: :is_in}, client) == true
|
||||
assert conforms?(%{condition | operator: :is_not_in}, client) == false
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_in}, client) == {:ok, nil}
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_not_in}, client) == :error
|
||||
|
||||
client = %Domain.Clients.Client{
|
||||
last_seen_remote_ip_location_region: "CA"
|
||||
}
|
||||
|
||||
assert conforms?(%{condition | operator: :is_in}, client) == false
|
||||
assert conforms?(%{condition | operator: :is_not_in}, client) == true
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_in}, client) == :error
|
||||
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_not_in}, client) ==
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
test "when client last seen remote ip is in or not in the CIDR values" do
|
||||
@@ -109,15 +111,20 @@ defmodule Domain.Policies.Condition.EvaluatorTest do
|
||||
last_seen_remote_ip: %Postgrex.INET{address: {192, 168, 0, 1}}
|
||||
}
|
||||
|
||||
assert conforms?(%{condition | operator: :is_in_cidr}, client) == true
|
||||
assert conforms?(%{condition | operator: :is_not_in_cidr}, client) == false
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_in_cidr}, client) ==
|
||||
{:ok, nil}
|
||||
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_not_in_cidr}, client) ==
|
||||
:error
|
||||
|
||||
client = %Domain.Clients.Client{
|
||||
last_seen_remote_ip: %Postgrex.INET{address: {10, 168, 0, 1}}
|
||||
}
|
||||
|
||||
assert conforms?(%{condition | operator: :is_in_cidr}, client) == false
|
||||
assert conforms?(%{condition | operator: :is_not_in_cidr}, client) == true
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_in_cidr}, client) == :error
|
||||
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_not_in_cidr}, client) ==
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
test "when client last seen remote ip is in or not in the IP values" do
|
||||
@@ -130,15 +137,20 @@ defmodule Domain.Policies.Condition.EvaluatorTest do
|
||||
last_seen_remote_ip: %Postgrex.INET{address: {192, 168, 0, 1}}
|
||||
}
|
||||
|
||||
assert conforms?(%{condition | operator: :is_in_cidr}, client) == true
|
||||
assert conforms?(%{condition | operator: :is_not_in_cidr}, client) == false
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_in_cidr}, client) ==
|
||||
{:ok, nil}
|
||||
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_not_in_cidr}, client) ==
|
||||
:error
|
||||
|
||||
client = %Domain.Clients.Client{
|
||||
last_seen_remote_ip: %Postgrex.INET{address: {10, 168, 0, 1}}
|
||||
}
|
||||
|
||||
assert conforms?(%{condition | operator: :is_in_cidr}, client) == false
|
||||
assert conforms?(%{condition | operator: :is_not_in_cidr}, client) == true
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_in_cidr}, client) == :error
|
||||
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_not_in_cidr}, client) ==
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
test "when client identity provider id is in or not in the values" do
|
||||
@@ -153,8 +165,8 @@ defmodule Domain.Policies.Condition.EvaluatorTest do
|
||||
}
|
||||
}
|
||||
|
||||
assert conforms?(%{condition | operator: :is_in}, client) == true
|
||||
assert conforms?(%{condition | operator: :is_not_in}, client) == false
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_in}, client) == {:ok, nil}
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_not_in}, client) == :error
|
||||
|
||||
client = %Domain.Clients.Client{
|
||||
identity: %Domain.Auth.Identity{
|
||||
@@ -162,12 +174,14 @@ defmodule Domain.Policies.Condition.EvaluatorTest do
|
||||
}
|
||||
}
|
||||
|
||||
assert conforms?(%{condition | operator: :is_in}, client) == false
|
||||
assert conforms?(%{condition | operator: :is_not_in}, client) == true
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_in}, client) == :error
|
||||
|
||||
assert fetch_conformation_expiration(%{condition | operator: :is_not_in}, client) ==
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
test "when client current UTC datetime is in the day of the week time ranges" do
|
||||
# this is tested separately in datetime_in_day_of_the_week_time_ranges?/2
|
||||
# this is deeply tested separately in find_day_of_the_week_time_range/2
|
||||
condition = %Domain.Policies.Condition{
|
||||
property: :current_utc_datetime,
|
||||
values: []
|
||||
@@ -175,26 +189,57 @@ defmodule Domain.Policies.Condition.EvaluatorTest do
|
||||
|
||||
client = %Domain.Clients.Client{}
|
||||
|
||||
assert conforms?(%{condition | operator: :is_in_day_of_week_time_ranges}, client) == false
|
||||
assert fetch_conformation_expiration(
|
||||
%{condition | operator: :is_in_day_of_week_time_ranges},
|
||||
client
|
||||
) == :error
|
||||
end
|
||||
end
|
||||
|
||||
describe "datetime_in_day_of_the_week_time_ranges?/2" do
|
||||
describe "find_day_of_the_week_time_range/2" do
|
||||
test "returns true when datetime is in the day of the week time ranges" do
|
||||
# Friday
|
||||
datetime = ~U[2021-01-01 10:00:00Z]
|
||||
|
||||
# Exact match
|
||||
dow_time_ranges = ["F/10:00:00-10:00:00/UTC"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == true
|
||||
|
||||
dow_time_ranges = ["F/10:00:00-11:00:00/UTC"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == true
|
||||
assert DateTime.compare(
|
||||
find_day_of_the_week_time_range(dow_time_ranges, datetime),
|
||||
~U[2021-01-01 10:00:00Z]
|
||||
) == :eq
|
||||
|
||||
dow_time_ranges = ["F/09:00:00-10:00:00/UTC"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == true
|
||||
# Range start match
|
||||
dow_time_ranges = ["F/10:00:00-11:00:00,20:00-22:00/UTC"]
|
||||
|
||||
assert DateTime.compare(
|
||||
find_day_of_the_week_time_range(dow_time_ranges, datetime),
|
||||
~U[2021-01-01 11:00:00Z]
|
||||
) == :eq
|
||||
|
||||
# Range end match
|
||||
dow_time_ranges = ["F/09:00:00-10:00:00,11-22/UTC"]
|
||||
|
||||
assert DateTime.compare(
|
||||
find_day_of_the_week_time_range(dow_time_ranges, datetime),
|
||||
~U[2021-01-01 10:00:00Z]
|
||||
) == :eq
|
||||
|
||||
# Entire day match
|
||||
dow_time_ranges = ["F/true/UTC"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == true
|
||||
|
||||
assert DateTime.compare(
|
||||
find_day_of_the_week_time_range(dow_time_ranges, datetime),
|
||||
~U[2021-01-01 23:59:59Z]
|
||||
) == :eq
|
||||
|
||||
# Finds greatest expiration time
|
||||
dow_time_ranges = ["F/09:00:00-11:00:00,11-15,14-22/UTC"]
|
||||
|
||||
assert DateTime.compare(
|
||||
find_day_of_the_week_time_range(dow_time_ranges, datetime),
|
||||
~U[2021-01-01 22:00:00Z]
|
||||
) == :eq
|
||||
end
|
||||
|
||||
test "returns false when datetime is not in the day of the week time ranges" do
|
||||
@@ -202,55 +247,209 @@ defmodule Domain.Policies.Condition.EvaluatorTest do
|
||||
datetime = ~U[2021-01-01 10:00:00Z]
|
||||
|
||||
dow_time_ranges = ["F/09:00:00-09:59:59/UTC"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == false
|
||||
assert find_day_of_the_week_time_range(dow_time_ranges, datetime) == nil
|
||||
|
||||
dow_time_ranges = ["F/10:00:01-11:00:00/UTC"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == false
|
||||
assert find_day_of_the_week_time_range(dow_time_ranges, datetime) == nil
|
||||
|
||||
dow_time_ranges = ["M/09:00:00-11:00:00/UTC"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == false
|
||||
assert find_day_of_the_week_time_range(dow_time_ranges, datetime) == nil
|
||||
|
||||
dow_time_ranges = ["U/true/UTC"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == false
|
||||
assert find_day_of_the_week_time_range(dow_time_ranges, datetime) == nil
|
||||
end
|
||||
|
||||
test "handles different timezones" do
|
||||
# Friday in UTC, Thursday in US/Pacific (UTC-8)
|
||||
# 01:00 Friday in UTC, 07:00 Thursday in US/Pacific (UTC-8)
|
||||
datetime = ~U[2021-01-01 01:00:00Z]
|
||||
|
||||
# Thursday in US/Pacific ends at 07:59:59 UTC
|
||||
dow_time_ranges = ["R/true/US/Pacific"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == true
|
||||
|
||||
assert DateTime.compare(
|
||||
find_day_of_the_week_time_range(dow_time_ranges, datetime),
|
||||
~U[2021-01-01 07:59:59Z]
|
||||
) == :eq
|
||||
|
||||
# Friday in UTC
|
||||
dow_time_ranges = ["F/true/UTC"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == true
|
||||
|
||||
assert DateTime.compare(
|
||||
find_day_of_the_week_time_range(dow_time_ranges, datetime),
|
||||
~U[2021-01-01 23:59:59Z]
|
||||
) == :eq
|
||||
|
||||
# 19:00 Thursday in US/Pacific (UTC-8) = 03:00 Friday in UTC
|
||||
dow_time_ranges = ["R/15:00:00-19:00:00/US/Pacific"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == true
|
||||
|
||||
assert DateTime.compare(
|
||||
find_day_of_the_week_time_range(dow_time_ranges, datetime),
|
||||
~U[2021-01-01 03:00:00Z]
|
||||
) == :eq
|
||||
|
||||
# given datetime is 07:00 Thursday in US/Pacific (UTC-8), so Friday in UTC should not match
|
||||
dow_time_ranges = ["R/00:00:00-02:00:00/US/Pacific"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == false
|
||||
assert find_day_of_the_week_time_range(dow_time_ranges, datetime) == nil
|
||||
|
||||
# Poland timezone is UTC+1, given datetime in UTC is 02:00 Friday in Poland
|
||||
dow_time_ranges = ["F/02:00:00-04:00:00/Poland"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == true
|
||||
|
||||
assert DateTime.compare(
|
||||
find_day_of_the_week_time_range(dow_time_ranges, datetime),
|
||||
~U[2021-01-01 03:00:00Z]
|
||||
) == :eq
|
||||
end
|
||||
|
||||
test "returns false when ranges are invalid" do
|
||||
datetime = ~U[2021-01-01 10:00:00Z]
|
||||
|
||||
dow_time_ranges = ["F/10:00:00-09:59:59/UTC"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == false
|
||||
assert find_day_of_the_week_time_range(dow_time_ranges, datetime) == nil
|
||||
|
||||
dow_time_ranges = ["F/11:00:00-12:00:00-/US/Pacific"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == false
|
||||
assert find_day_of_the_week_time_range(dow_time_ranges, datetime) == nil
|
||||
|
||||
dow_time_ranges = ["F/false/UTC"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == false
|
||||
assert find_day_of_the_week_time_range(dow_time_ranges, datetime) == nil
|
||||
|
||||
dow_time_ranges = ["F/10-11"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == false
|
||||
assert find_day_of_the_week_time_range(dow_time_ranges, datetime) == nil
|
||||
|
||||
dow_time_ranges = ["F/10-11/"]
|
||||
assert datetime_in_day_of_the_week_time_ranges?(datetime, dow_time_ranges) == false
|
||||
assert find_day_of_the_week_time_range(dow_time_ranges, datetime) == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "merge_joint_time_ranges/1" do
|
||||
test "does nothing on empty time ranges" do
|
||||
time_ranges = []
|
||||
assert merge_joint_time_ranges(time_ranges) == time_ranges
|
||||
end
|
||||
|
||||
test "does nothing on single time range" do
|
||||
time_ranges = [{~T[10:00:00], ~T[11:00:00], "UTC"}]
|
||||
assert merge_joint_time_ranges(time_ranges) == time_ranges
|
||||
end
|
||||
|
||||
test "does not merge overlapping time ranges that do not overlap" do
|
||||
time_ranges = [
|
||||
{~T[10:00:00], ~T[11:00:00], "UTC"},
|
||||
{~T[11:00:01], ~T[12:00:00], "UTC"}
|
||||
]
|
||||
|
||||
assert merge_joint_time_ranges(time_ranges) == time_ranges
|
||||
|
||||
time_ranges = [
|
||||
{~T[09:00:00], ~T[10:00:00], "UTC"},
|
||||
{~T[11:00:00], ~T[12:00:00], "UTC"},
|
||||
{~T[10:00:01], ~T[10:00:02], "UTC"}
|
||||
]
|
||||
|
||||
assert merge_joint_time_ranges(time_ranges) == time_ranges
|
||||
end
|
||||
|
||||
test "does not merge overlapping time ranges that have different timezones" do
|
||||
time_ranges = [
|
||||
{~T[10:00:00], ~T[11:00:00], "UTC"},
|
||||
{~T[10:30:00], ~T[12:00:00], "PDT"}
|
||||
]
|
||||
|
||||
assert merge_joint_time_ranges(time_ranges) == time_ranges
|
||||
end
|
||||
|
||||
test "merges overlapping time ranges" do
|
||||
time_ranges = [
|
||||
{~T[10:00:00], ~T[11:00:00], "UTC"},
|
||||
{~T[10:30:00], ~T[12:00:00], "UTC"}
|
||||
]
|
||||
|
||||
assert merge_joint_time_ranges(time_ranges) == [
|
||||
{~T[10:00:00], ~T[12:00:00], "UTC"}
|
||||
]
|
||||
|
||||
time_ranges = [
|
||||
{~T[10:00:00], ~T[11:00:00], "UTC"},
|
||||
{~T[11:00:00], ~T[12:00:00], "UTC"}
|
||||
]
|
||||
|
||||
assert merge_joint_time_ranges(time_ranges) == [
|
||||
{~T[10:00:00], ~T[12:00:00], "UTC"}
|
||||
]
|
||||
|
||||
time_ranges = [
|
||||
{~T[10:00:00], ~T[11:00:00], "UTC"},
|
||||
{~T[09:00:00], ~T[10:00:00], "UTC"}
|
||||
]
|
||||
|
||||
assert merge_joint_time_ranges(time_ranges) == [
|
||||
{~T[09:00:00], ~T[11:00:00], "UTC"}
|
||||
]
|
||||
|
||||
time_ranges = [
|
||||
{~T[10:00:00], ~T[11:00:00], "UTC"},
|
||||
{~T[10:00:00], ~T[12:00:00], "UTC"}
|
||||
]
|
||||
|
||||
assert merge_joint_time_ranges(time_ranges) == [
|
||||
{~T[10:00:00], ~T[12:00:00], "UTC"}
|
||||
]
|
||||
|
||||
time_ranges = [
|
||||
{~T[10:00:00], ~T[11:00:00], "UTC"},
|
||||
{~T[09:00:00], ~T[12:00:00], "UTC"}
|
||||
]
|
||||
|
||||
assert merge_joint_time_ranges(time_ranges) == [
|
||||
{~T[09:00:00], ~T[12:00:00], "UTC"}
|
||||
]
|
||||
|
||||
time_ranges = [
|
||||
{~T[09:00:00], ~T[12:00:00], "UTC"},
|
||||
{~T[10:00:00], ~T[11:00:00], "UTC"}
|
||||
]
|
||||
|
||||
assert merge_joint_time_ranges(time_ranges) == [
|
||||
{~T[09:00:00], ~T[12:00:00], "UTC"}
|
||||
]
|
||||
end
|
||||
|
||||
test "merges multiple overlapping time ranges" do
|
||||
time_ranges = [
|
||||
{~T[09:00:00], ~T[10:00:00], "UTC"},
|
||||
{~T[11:00:00], ~T[12:00:00], "UTC"},
|
||||
{~T[10:00:00], ~T[11:00:00], "UTC"}
|
||||
]
|
||||
|
||||
assert merge_joint_time_ranges(time_ranges) == [
|
||||
{~T[09:00:00], ~T[12:00:00], "UTC"}
|
||||
]
|
||||
|
||||
time_ranges = [
|
||||
{~T[09:00:00], ~T[12:00:00], "UTC"},
|
||||
{~T[11:00:00], ~T[12:00:00], "UTC"},
|
||||
{~T[10:00:00], ~T[11:00:00], "UTC"},
|
||||
{~T[09:00:00], ~T[10:00:00], "UTC"},
|
||||
{~T[01:00:00], ~T[10:00:00], "UTC"}
|
||||
]
|
||||
|
||||
assert merge_joint_time_ranges(time_ranges) == [
|
||||
{~T[01:00:00], ~T[12:00:00], "UTC"}
|
||||
]
|
||||
end
|
||||
|
||||
test "merges two sets of overlapping time ranges" do
|
||||
time_ranges = [
|
||||
{~T[09:00:00], ~T[12:00:00], "UTC"},
|
||||
{~T[11:00:00], ~T[13:00:00], "UTC"},
|
||||
{~T[10:00:00], ~T[11:00:00], "UTC"},
|
||||
{~T[02:00:00], ~T[05:00:00], "UTC"},
|
||||
{~T[01:00:00], ~T[08:00:00], "UTC"}
|
||||
]
|
||||
|
||||
assert merge_joint_time_ranges(time_ranges) == [
|
||||
{~T[09:00:00], ~T[13:00:00], "UTC"},
|
||||
{~T[01:00:00], ~T[08:00:00], "UTC"}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -979,7 +979,7 @@ defmodule Domain.PoliciesTest do
|
||||
]
|
||||
}
|
||||
|
||||
assert ensure_client_conforms_policy_conditions(client, policy) == :ok
|
||||
assert ensure_client_conforms_policy_conditions(client, policy) == {:ok, nil}
|
||||
end
|
||||
|
||||
test "returns error when client conforms to policy conditions", %{} do
|
||||
|
||||
@@ -256,6 +256,11 @@ defmodule Web.Policies.Components do
|
||||
providers={@providers}
|
||||
disabled={@policy_conditions_enabled? == false}
|
||||
/>
|
||||
<.current_utc_datetime_condition_form
|
||||
form={@form}
|
||||
timezone={@timezone}
|
||||
disabled={@policy_conditions_enabled? == false}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
"""
|
||||
@@ -518,95 +523,93 @@ defmodule Web.Policies.Components do
|
||||
"""
|
||||
end
|
||||
|
||||
# TODO: Uncomment this once policy time conditions are finished
|
||||
# defp current_utc_datetime_condition_form(assigns) do
|
||||
# assigns = assign_new(assigns, :days_of_week, fn -> @days_of_week end)
|
||||
defp current_utc_datetime_condition_form(assigns) do
|
||||
assigns = assign_new(assigns, :days_of_week, fn -> @days_of_week end)
|
||||
|
||||
# ~H"""
|
||||
# <fieldset class="mb-2">
|
||||
# <% condition_form = find_condition_form(@form[:conditions], :current_utc_datetime) %>
|
||||
~H"""
|
||||
<fieldset class="mb-2">
|
||||
<% condition_form = find_condition_form(@form[:conditions], :current_utc_datetime) %>
|
||||
|
||||
# <.input
|
||||
# type="hidden"
|
||||
# field={condition_form[:property]}
|
||||
# name="policy[conditions][current_utc_datetime][property]"
|
||||
# id="policy_conditions_current_utc_datetime_property"
|
||||
# value="current_utc_datetime"
|
||||
# />
|
||||
<.input
|
||||
type="hidden"
|
||||
field={condition_form[:property]}
|
||||
name="policy[conditions][current_utc_datetime][property]"
|
||||
id="policy_conditions_current_utc_datetime_property"
|
||||
value="current_utc_datetime"
|
||||
/>
|
||||
|
||||
# <.input
|
||||
# type="hidden"
|
||||
# name="policy[conditions][current_utc_datetime][operator]"
|
||||
# id="policy_conditions_current_utc_datetime_operator"
|
||||
# field={condition_form[:operator]}
|
||||
# value={:is_in_day_of_week_time_ranges}
|
||||
# />
|
||||
<.input
|
||||
type="hidden"
|
||||
name="policy[conditions][current_utc_datetime][operator]"
|
||||
id="policy_conditions_current_utc_datetime_operator"
|
||||
field={condition_form[:operator]}
|
||||
value={:is_in_day_of_week_time_ranges}
|
||||
/>
|
||||
|
||||
# <div
|
||||
# class="hover:bg-neutral-100 cursor-pointer border border-neutral-200 shadow-b rounded-t px-4 py-2"
|
||||
# phx-click={
|
||||
# JS.toggle_class("hidden",
|
||||
# to: "#policy_conditions_current_utc_datetime_condition"
|
||||
# )
|
||||
# |> JS.toggle_class("bg-neutral-50")
|
||||
# |> JS.toggle_class("hero-chevron-down",
|
||||
# to: "#policy_conditions_current_utc_datetime_chevron"
|
||||
# )
|
||||
# |> JS.toggle_class("hero-chevron-up",
|
||||
# to: "#policy_conditions_current_utc_datetime_chevron"
|
||||
# )
|
||||
# }
|
||||
# >
|
||||
# <legend class="flex justify-between items-center text-neutral-700">
|
||||
# <span class="flex items-center">
|
||||
# <.icon name="hero-clock" class="w-5 h-5 mr-2" /> Current time
|
||||
# </span>
|
||||
# <span class="shadow bg-white w-6 h-6 flex items-center justify-center rounded-full">
|
||||
# <.icon
|
||||
# id="policy_conditions_current_utc_datetime_chevron"
|
||||
# name="hero-chevron-down"
|
||||
# class="w-5 h-5"
|
||||
# />
|
||||
# </span>
|
||||
# </legend>
|
||||
# </div>
|
||||
<div
|
||||
class="hover:bg-neutral-100 cursor-pointer border border-neutral-200 shadow-b rounded-t px-4 py-2"
|
||||
phx-click={
|
||||
JS.toggle_class("hidden",
|
||||
to: "#policy_conditions_current_utc_datetime_condition"
|
||||
)
|
||||
|> JS.toggle_class("bg-neutral-50")
|
||||
|> JS.toggle_class("hero-chevron-down",
|
||||
to: "#policy_conditions_current_utc_datetime_chevron"
|
||||
)
|
||||
|> JS.toggle_class("hero-chevron-up",
|
||||
to: "#policy_conditions_current_utc_datetime_chevron"
|
||||
)
|
||||
}
|
||||
>
|
||||
<legend class="flex justify-between items-center text-neutral-700">
|
||||
<span class="flex items-center">
|
||||
<.icon name="hero-clock" class="w-5 h-5 mr-2" /> Current time
|
||||
</span>
|
||||
<span class="shadow bg-white w-6 h-6 flex items-center justify-center rounded-full">
|
||||
<.icon
|
||||
id="policy_conditions_current_utc_datetime_chevron"
|
||||
name="hero-chevron-down"
|
||||
class="w-5 h-5"
|
||||
/>
|
||||
</span>
|
||||
</legend>
|
||||
</div>
|
||||
|
||||
# <div
|
||||
# id="policy_conditions_current_utc_datetime_condition"
|
||||
# class={[
|
||||
# "p-4 border-neutral-200 border-l border-r border-b rounded-b",
|
||||
# condition_values_empty?(condition_form) && "hidden"
|
||||
# ]}
|
||||
# >
|
||||
# <p class="text-sm text-neutral-500 mb-2">
|
||||
# Restrict access based on the current time of the day in 24hr format.
|
||||
# Multiple time ranges per day are supported.
|
||||
# </p>
|
||||
# <div class="space-y-2">
|
||||
# <.input
|
||||
# type="select"
|
||||
# label="Timezone"
|
||||
# name="policy[conditions][current_utc_datetime][timezone]"
|
||||
# id="policy_conditions_current_utc_datetime_timezone"
|
||||
# field={condition_form[:timezone]}
|
||||
# options={Tzdata.zone_list()}
|
||||
# disabled={@disabled}
|
||||
# value={condition_form[:timezone].value || @timezone}
|
||||
# />
|
||||
<div
|
||||
id="policy_conditions_current_utc_datetime_condition"
|
||||
class={[
|
||||
"p-4 border-neutral-200 border-l border-r border-b rounded-b",
|
||||
condition_values_empty?(condition_form) && "hidden"
|
||||
]}
|
||||
>
|
||||
<p class="text-sm text-neutral-500 mb-2">
|
||||
Restrict access based on the current time of the day in 24hr format. Multiple time ranges per day are supported.
|
||||
</p>
|
||||
<div class="space-y-2">
|
||||
<.input
|
||||
type="select"
|
||||
label="Timezone"
|
||||
name="policy[conditions][current_utc_datetime][timezone]"
|
||||
id="policy_conditions_current_utc_datetime_timezone"
|
||||
field={condition_form[:timezone]}
|
||||
options={Tzdata.zone_list()}
|
||||
disabled={@disabled}
|
||||
value={condition_form[:timezone].value || @timezone}
|
||||
/>
|
||||
|
||||
# <div class="space-y-2">
|
||||
# <.current_utc_datetime_condition_day_input
|
||||
# :for={{code, _name} <- @days_of_week}
|
||||
# disabled={@disabled}
|
||||
# condition_form={condition_form}
|
||||
# day={code}
|
||||
# />
|
||||
# </div>
|
||||
# </div>
|
||||
# </div>
|
||||
# </fieldset>
|
||||
# """
|
||||
# end
|
||||
<div class="space-y-2">
|
||||
<.current_utc_datetime_condition_day_input
|
||||
:for={{code, _name} <- @days_of_week}
|
||||
disabled={@disabled}
|
||||
condition_form={condition_form}
|
||||
day={code}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
"""
|
||||
end
|
||||
|
||||
defp find_condition_form(form_field, property) do
|
||||
condition_form =
|
||||
@@ -620,32 +623,30 @@ defmodule Web.Policies.Components do
|
||||
condition_form || to_form(%{})
|
||||
end
|
||||
|
||||
# TODO: Uncomment this once policy time conditions are finished
|
||||
# defp current_utc_datetime_condition_day_input(assigns) do
|
||||
# ~H"""
|
||||
# <.input
|
||||
# type="text"
|
||||
# label={day_of_week_name(@day)}
|
||||
# field={@condition_form[:values]}
|
||||
# name={"policy[conditions][current_utc_datetime][values][#{@day}]"}
|
||||
# id={"policy_conditions_current_utc_datetime_values_#{@day}"}
|
||||
# placeholder="E.g. 9:00-12:00, 13:00-17:00"
|
||||
# value={get_datetime_range_for_day_of_week(@day, @condition_form[:values])}
|
||||
# disabled={@disabled}
|
||||
# value_index={day_of_week_index(@day)}
|
||||
# />
|
||||
# """
|
||||
# end
|
||||
defp current_utc_datetime_condition_day_input(assigns) do
|
||||
~H"""
|
||||
<.input
|
||||
type="text"
|
||||
label={day_of_week_name(@day)}
|
||||
field={@condition_form[:values]}
|
||||
name={"policy[conditions][current_utc_datetime][values][#{@day}]"}
|
||||
id={"policy_conditions_current_utc_datetime_values_#{@day}"}
|
||||
placeholder="E.g. 9:00-12:00, 13:00-17:00"
|
||||
value={get_datetime_range_for_day_of_week(@day, @condition_form[:values])}
|
||||
disabled={@disabled}
|
||||
value_index={day_of_week_index(@day)}
|
||||
/>
|
||||
"""
|
||||
end
|
||||
|
||||
# TODO: Uncomment this once policy time conditions are finished
|
||||
# defp get_datetime_range_for_day_of_week(day, form_field) do
|
||||
# Enum.find_value(form_field.value || [], fn dow_time_ranges ->
|
||||
# case String.split(dow_time_ranges, "/", parts: 3) do
|
||||
# [^day, ranges, _timezone] -> ranges
|
||||
# _other -> false
|
||||
# end
|
||||
# end)
|
||||
# end
|
||||
defp get_datetime_range_for_day_of_week(day, form_field) do
|
||||
Enum.find_value(form_field.value || [], fn dow_time_ranges ->
|
||||
case String.split(dow_time_ranges, "/", parts: 3) do
|
||||
[^day, ranges, _timezone] -> ranges
|
||||
_other -> false
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp condition_operator_options(property) do
|
||||
Domain.Policies.Condition.Changeset.valid_operators_for_property(property)
|
||||
|
||||
@@ -60,6 +60,16 @@ defmodule Web.Live.Policies.NewTest do
|
||||
|
||||
assert find_inputs(form) == [
|
||||
"policy[actor_group_id]",
|
||||
"policy[conditions][current_utc_datetime][operator]",
|
||||
"policy[conditions][current_utc_datetime][property]",
|
||||
"policy[conditions][current_utc_datetime][timezone]",
|
||||
"policy[conditions][current_utc_datetime][values][F]",
|
||||
"policy[conditions][current_utc_datetime][values][M]",
|
||||
"policy[conditions][current_utc_datetime][values][R]",
|
||||
"policy[conditions][current_utc_datetime][values][S]",
|
||||
"policy[conditions][current_utc_datetime][values][T]",
|
||||
"policy[conditions][current_utc_datetime][values][U]",
|
||||
"policy[conditions][current_utc_datetime][values][W]",
|
||||
"policy[conditions][provider_id][operator]",
|
||||
"policy[conditions][provider_id][property]",
|
||||
"policy[conditions][provider_id][values][]",
|
||||
@@ -89,6 +99,16 @@ defmodule Web.Live.Policies.NewTest do
|
||||
|
||||
assert find_inputs(form) == [
|
||||
"policy[actor_group_id]",
|
||||
"policy[conditions][current_utc_datetime][operator]",
|
||||
"policy[conditions][current_utc_datetime][property]",
|
||||
"policy[conditions][current_utc_datetime][timezone]",
|
||||
"policy[conditions][current_utc_datetime][values][F]",
|
||||
"policy[conditions][current_utc_datetime][values][M]",
|
||||
"policy[conditions][current_utc_datetime][values][R]",
|
||||
"policy[conditions][current_utc_datetime][values][S]",
|
||||
"policy[conditions][current_utc_datetime][values][T]",
|
||||
"policy[conditions][current_utc_datetime][values][U]",
|
||||
"policy[conditions][current_utc_datetime][values][W]",
|
||||
"policy[conditions][provider_id][operator]",
|
||||
"policy[conditions][provider_id][property]",
|
||||
"policy[conditions][provider_id][values][]",
|
||||
@@ -129,6 +149,16 @@ defmodule Web.Live.Policies.NewTest do
|
||||
|
||||
assert find_inputs(form) == [
|
||||
"policy[actor_group_id]",
|
||||
"policy[conditions][current_utc_datetime][operator]",
|
||||
"policy[conditions][current_utc_datetime][property]",
|
||||
"policy[conditions][current_utc_datetime][timezone]",
|
||||
"policy[conditions][current_utc_datetime][values][F]",
|
||||
"policy[conditions][current_utc_datetime][values][M]",
|
||||
"policy[conditions][current_utc_datetime][values][R]",
|
||||
"policy[conditions][current_utc_datetime][values][S]",
|
||||
"policy[conditions][current_utc_datetime][values][T]",
|
||||
"policy[conditions][current_utc_datetime][values][U]",
|
||||
"policy[conditions][current_utc_datetime][values][W]",
|
||||
"policy[conditions][provider_id][operator]",
|
||||
"policy[conditions][provider_id][property]",
|
||||
"policy[conditions][provider_id][values][]",
|
||||
@@ -254,6 +284,20 @@ defmodule Web.Live.Policies.NewTest do
|
||||
actor_group_id: group.id,
|
||||
resource_id: resource.id,
|
||||
conditions: %{
|
||||
current_utc_datetime: %{
|
||||
property: "current_utc_datetime",
|
||||
operator: "is_in_day_of_week_time_ranges",
|
||||
timezone: "US/Pacific",
|
||||
values: %{
|
||||
M: "true",
|
||||
T: "",
|
||||
W: "true",
|
||||
R: "",
|
||||
F: "",
|
||||
S: "10:00:00-15:00:00",
|
||||
U: "23:00:00-23:59:59"
|
||||
}
|
||||
},
|
||||
provider_id: %{
|
||||
property: "provider_id",
|
||||
operator: "is_in",
|
||||
@@ -285,6 +329,19 @@ defmodule Web.Live.Policies.NewTest do
|
||||
assert policy.resource_id == resource.id
|
||||
|
||||
assert policy.conditions == [
|
||||
%Domain.Policies.Condition{
|
||||
property: :current_utc_datetime,
|
||||
operator: :is_in_day_of_week_time_ranges,
|
||||
values: [
|
||||
"M/true/US/Pacific",
|
||||
"T//US/Pacific",
|
||||
"W/true/US/Pacific",
|
||||
"R//US/Pacific",
|
||||
"F//US/Pacific",
|
||||
"S/10:00:00-15:00:00/US/Pacific",
|
||||
"U/23:00:00-23:59:59/US/Pacific"
|
||||
]
|
||||
},
|
||||
%Domain.Policies.Condition{
|
||||
property: :provider_id,
|
||||
operator: :is_in,
|
||||
|
||||
@@ -69,6 +69,24 @@ Restrict access to a specific IP address or range of Client IP addresses.
|
||||
Restrict access based on the authentication provider that was used to
|
||||
authenticate the Client.
|
||||
|
||||
### Time of day
|
||||
|
||||
<Image
|
||||
src="/images/kb/deploy/policies/client_tod.png"
|
||||
alt="Time of day"
|
||||
width={600}
|
||||
height={600}
|
||||
className="mx-auto shadow rounded"
|
||||
/>
|
||||
|
||||
Restrict access to certain time windows throughout the week based on the 24hr
|
||||
time and specified time zone.
|
||||
|
||||
The time zone determines the offset used when determining whether to allow
|
||||
access for a particular Client. For example, if you specify a time window of
|
||||
`08:00-17:00` and time zone of `Eastern`, Clients in the `Pacific` timezone 3
|
||||
hours behind will be allowed access from `05:00-14:00` Eastern time.
|
||||
|
||||
<NextStep href="/kb/deploy/clients">Next: Install Clients</NextStep>
|
||||
|
||||
<SupportOptions />
|
||||
|
||||
Reference in New Issue
Block a user