Implement the permission hierachy #2
7 changed files with 198 additions and 5 deletions
|
|
@ -6,5 +6,5 @@ S3_URL=
|
|||
S3_BUCKET=
|
||||
POSTGRES_DB=holder
|
||||
POSTGRES_USER=holder
|
||||
POSTGRES_PASSWORD=
|
||||
POSTGRES_PASSWORD=boobs
|
||||
POSTGRES_HOSTNAME=localhost
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
elixir 1.18.2-otp-27
|
||||
erlang 27.2.2
|
||||
elixir 1.18.4-otp-27
|
||||
erlang 27.3.4.2
|
||||
|
|
|
|||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -2,5 +2,5 @@
|
|||
"[elixir]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"elixirLS.dialyzerEnabled": false
|
||||
"elixirLS.dialyzerEnabled": true
|
||||
}
|
||||
|
|
@ -6,4 +6,6 @@ config :nostrum,
|
|||
youtubedl: 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}),
|
||||
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
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ defmodule Holder.Hold.Permission do
|
|||
alias Holder.Schema
|
||||
alias Holder.Repo
|
||||
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()
|
||||
|
||||
|
|
@ -47,4 +49,162 @@ defmodule Holder.Hold.Permission do
|
|||
|> List.wrap()
|
||||
|> Keyword.from_keys(id)
|
||||
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
|
||||
|
|
|
|||
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