Implement the permission hierachy #2
7 changed files with 198 additions and 5 deletions
|
|
@ -6,5 +6,5 @@ S3_URL=
|
||||||
S3_BUCKET=
|
S3_BUCKET=
|
||||||
POSTGRES_DB=holder
|
POSTGRES_DB=holder
|
||||||
POSTGRES_USER=holder
|
POSTGRES_USER=holder
|
||||||
POSTGRES_PASSWORD=
|
POSTGRES_PASSWORD=boobs
|
||||||
POSTGRES_HOSTNAME=localhost
|
POSTGRES_HOSTNAME=localhost
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
elixir 1.18.2-otp-27
|
elixir 1.18.4-otp-27
|
||||||
erlang 27.2.2
|
erlang 27.3.4.2
|
||||||
|
|
|
||||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -2,5 +2,5 @@
|
||||||
"[elixir]": {
|
"[elixir]": {
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
"elixirLS.dialyzerEnabled": false
|
"elixirLS.dialyzerEnabled": true
|
||||||
}
|
}
|
||||||
|
|
@ -6,4 +6,6 @@ config :nostrum,
|
||||||
youtubedl: nil,
|
youtubedl: nil,
|
||||||
streamlink: nil
|
streamlink: nil
|
||||||
|
|
||||||
config :logger, level: :info
|
config :logger, :console,
|
||||||
|
level: :info,
|
||||||
|
format: "$time $metadata[$level]\t$message\n"
|
||||||
|
|
|
||||||
|
|
@ -49,4 +49,13 @@ defmodule Holder.Hold.Controller do
|
||||||
|
|
||||||
def permissions(%Schema.Hold.Controller{id: id}),
|
def permissions(%Schema.Hold.Controller{id: id}),
|
||||||
do: Hold.Permission.aggregate_by(controller_id: id)
|
do: Hold.Permission.aggregate_by(controller_id: id)
|
||||||
|
|
||||||
|
def trustee?(nil), do: false
|
||||||
|
|
||||||
|
def trustee?(id) do
|
||||||
|
case Repo.get(Schema.Hold.Controller, id) do
|
||||||
|
nil -> false
|
||||||
|
c -> c.trustee
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ defmodule Holder.Hold.Permission do
|
||||||
alias Holder.Schema
|
alias Holder.Schema
|
||||||
alias Holder.Repo
|
alias Holder.Repo
|
||||||
alias Holder.Hold.Permission.Flags
|
alias Holder.Hold.Permission.Flags
|
||||||
|
alias Holder.Hold.Permission.Role
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
def aggregate_by(owner) when is_struct(owner), do: resolve_owner(owner) |> aggregate_by()
|
def aggregate_by(owner) when is_struct(owner), do: resolve_owner(owner) |> aggregate_by()
|
||||||
|
|
||||||
|
|
@ -47,4 +49,162 @@ defmodule Holder.Hold.Permission do
|
||||||
|> List.wrap()
|
|> List.wrap()
|
||||||
|> Keyword.from_keys(id)
|
|> Keyword.from_keys(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def all_of(actor_id) do
|
||||||
|
Repo.all(
|
||||||
|
from p in Schema.Hold.Permission,
|
||||||
|
where: p.controller_id == ^actor_id or p.participant_id == ^actor_id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def actor_permissions(actor_id) do
|
||||||
|
has =
|
||||||
|
all_of(actor_id)
|
||||||
|
|> Enum.filter(& &1.has)
|
||||||
|
|
||||||
|
if Enum.empty?(has) do
|
||||||
|
{:error, "Actor not found."}
|
||||||
|
else
|
||||||
|
{:ok,
|
||||||
|
has
|
||||||
|
|> Enum.map(& &1.bitfield)
|
||||||
|
|> Enum.reduce(&Bitwise.bor/2)
|
||||||
|
|> Flags.deserialize()}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_actor(actor_id) do
|
||||||
|
case all_of(actor_id) do
|
||||||
|
[] ->
|
||||||
|
{:error, "Actor not found."}
|
||||||
|
|
||||||
|
ac ->
|
||||||
|
hd(ac)
|
||||||
|
|> Repo.preload([:controller, :participant])
|
||||||
|
|> then(fn
|
||||||
|
%Schema.Hold.Permission{controller: c} when not is_nil(c) -> {:ok, c}
|
||||||
|
%Schema.Hold.Permission{participant: p} when not is_nil(p) -> {:ok, p}
|
||||||
|
_ -> {:error, "Actor not found."}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def actionable(%Schema.Hold.Controller{} = actor, on) do
|
||||||
|
actor
|
||||||
|
|> Repo.preload([:permissions])
|
||||||
|
|> Map.get(:permissions)
|
||||||
|
|> Enum.filter(& &1.has)
|
||||||
|
|> Enum.map(&{Role.deserialize(&1 |> Map.get(on)), Flags.deserialize(&1.bitfield)})
|
||||||
|
end
|
||||||
|
|
||||||
|
def actionable(%Schema.Hold.Participant{} = actor, on) do
|
||||||
|
actor
|
||||||
|
|> Repo.preload([:permissions])
|
||||||
|
|> Map.get(:permissions)
|
||||||
|
|> Enum.filter(& &1.has)
|
||||||
|
|> Enum.map(&{Role.deserialize(&1 |> Map.get(on)), Flags.deserialize(&1.bitfield)})
|
||||||
|
end
|
||||||
|
|
||||||
|
def actionable(actor_id, on) when is_integer(actor_id) do
|
||||||
|
all_of(actor_id)
|
||||||
|
|> Enum.filter(& &1.has)
|
||||||
|
|> Enum.map(&{Role.deserialize(&1 |> Map.get(on)), Flags.deserialize(&1.bitfield)})
|
||||||
|
end
|
||||||
|
|
||||||
|
def grantable(actor_id) do
|
||||||
|
actionable(actor_id, :may_grant_to)
|
||||||
|
end
|
||||||
|
|
||||||
|
def revokeable(actor_id) do
|
||||||
|
actionable(actor_id, :may_revoke_from)
|
||||||
|
end
|
||||||
|
|
||||||
|
def revokeable_by(actor_id) do
|
||||||
|
actionable(actor_id, :may_be_revoked_by)
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_action([], _, _) do
|
||||||
|
{:error, "Actor has no actionable permissions."}
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_action(action, target, permission) do
|
||||||
|
action
|
||||||
|
|> Enum.map(fn {roles, perms} ->
|
||||||
|
Role.from(target) |> Bitfield.difference(roles) |> Enum.empty?() and
|
||||||
|
Bitfield.new([permission]) |> Bitfield.difference(perms) |> Enum.empty?()
|
||||||
|
end)
|
||||||
|
|> then(&{:ok, Enum.all?(&1) and not Enum.empty?(&1)})
|
||||||
|
end
|
||||||
|
|
||||||
|
def check(permission, actor_id) do
|
||||||
|
case actor_permissions(actor_id) do
|
||||||
|
{:ok, perms} ->
|
||||||
|
{:ok,
|
||||||
|
Bitfield.new([permission])
|
||||||
|
|> Bitfield.difference(perms)
|
||||||
|
|> Enum.empty?()}
|
||||||
|
|
||||||
|
e ->
|
||||||
|
e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def may(permission, actor_id, target_id \\ nil)
|
||||||
|
|
||||||
|
def may(permission, actor_id, nil) do
|
||||||
|
check(permission, actor_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def may(:hold_controllers_remove = p, actor_id, target_id) do
|
||||||
|
a = get_actor(actor_id)
|
||||||
|
t = get_actor(target_id)
|
||||||
|
|
||||||
|
different_holds =
|
||||||
|
case {a, t} do
|
||||||
|
{{:ok, actor}, {:ok, target}} ->
|
||||||
|
actor.hold_id !== target.hold_id
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
actor_has = may(p, actor_id, nil)
|
||||||
|
target_has = Holder.Hold.Controller.trustee?(target_id)
|
||||||
|
|
||||||
|
case {actor_has, target_has, different_holds} do
|
||||||
|
{_, _, true} ->
|
||||||
|
{:error, "The actor and target are on different holds."}
|
||||||
|
|
||||||
|
{{:error, _}, _, _} ->
|
||||||
|
actor_has
|
||||||
|
|
||||||
|
{{:ok, true}, true, _} ->
|
||||||
|
{:error, "Trustees cannot be removed as hold controllers."}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:ok, Kernel.and(actor_has |> elem(1), target_has)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def may_grant(permission, actor_id, target_id) do
|
||||||
|
case get_actor(target_id) do
|
||||||
|
{:ok, target} ->
|
||||||
|
grantable(actor_id)
|
||||||
|
|> verify_action(target, permission)
|
||||||
|
|
||||||
|
e ->
|
||||||
|
e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def may_revoke(permission, actor_id, target_id) do
|
||||||
|
case get_actor(target_id) do
|
||||||
|
{:ok, target} ->
|
||||||
|
revokeable(actor_id)
|
||||||
|
|> verify_action(target, permission)
|
||||||
|
|
||||||
|
e ->
|
||||||
|
e
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
22
lib/holder/hold/permission/role.ex
Normal file
22
lib/holder/hold/permission/role.ex
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
defmodule Holder.Hold.Permission.Role do
|
||||||
|
alias Holder.Schema
|
||||||
|
|
||||||
|
use Bitfield, flags: [:participant, :controller, :trustee]
|
||||||
|
|
||||||
|
def participant, do: Bitfield.new([:participant])
|
||||||
|
def controller, do: Bitfield.new([:controller])
|
||||||
|
def trustee, do: Bitfield.new([:trustee])
|
||||||
|
|
||||||
|
def from(%Schema.Hold.Controller{trustee: t}) do
|
||||||
|
if t do
|
||||||
|
[:controller, :trustee]
|
||||||
|
else
|
||||||
|
[:controller]
|
||||||
|
end
|
||||||
|
|> Bitfield.new()
|
||||||
|
end
|
||||||
|
|
||||||
|
def from(%Schema.Hold.Participant{}) do
|
||||||
|
participant()
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue