Packages

Authorization system with role support

Current section

Files

Jump to
chalk_authorization lib chalk_authorization.ex
Raw

lib/chalk_authorization.ex

defmodule ChalkAuthorization do
defmacro __using__([repo: repo, group_permissions: group_permissions]) do
quote do
@doc """
The translation between a permission and it's integer representation
"""
def permission_map,
do: Application.get_env(:chalk_authorization, :permission_map, %{ c: 1, r: 2, u: 4, d: 8 })
@doc """
The changeset for update permissions.
"""
def permissions_changeset(item, attrs),
do: cast(item, attrs, [:superuser, :groups, :permissions])
@doc nil
def can?(nil, _action, _element),
do: false
def can?(user, permission, element) when is_atom(permission),
do: can?(user, Atom.to_string(permission), element)
def can?(%{superuser: true}, _permission, _element),
do: true
def can?(%{groups: [group | groups]} = user, permission, element),
do: user
|> get_group_permissions(group)
|> Map.put(:groups, groups)
|> can?(permission, element)
def can?(user, permission, element),
do: user
|> get_permissions(Atom.to_string element)
|> permissions_int_to_string
|> String.contains?(permission)
@doc nil
defp get_group_permissions(),
do: (unquote(group_permissions) || %{})
defp get_group_permissions(user, group),
do: if (Map.has_key?(get_group_permissions(), group))
&& (Enum.member?(user.groups, group)),
do: upgrade_to_group(user, Map.get(get_group_permissions(), group)),
else: user
defp upgrade_to_group(%{permissions: permissions} = user, group_permissions),
do: Map.put(user, :permissions, upgrade_to_group(permissions, Map.to_list(group_permissions)))
defp upgrade_to_group(permissions, []),
do: permissions
defp upgrade_to_group(permissions, [{permission, value} | group_permissions]),
do: if (Map.has_key?(permissions, permission))
&& (permissions[permission] >= value),
do: permissions,
else: Map.put(permissions, permission, value)
|> upgrade_to_group(group_permissions)
@doc nil
def get_permissions(user, element) when is_atom(element),
do: get_permissions(user, Atom.to_string element)
def get_permissions(user, element),
do: if Map.has_key?(user.permissions, element),
do: user.permissions[element],
else: 0
@doc nil
def add_group(user, []),
do: user
def add_group(user, [group | groups]),
do: add_group(add_group(user, group), groups)
def add_group(user, group) when not is_bitstring(group),
do: add_group(user, "#{group}")
def add_group(%{groups: groups} = user, group),
do: user
|> __MODULE__.permissions_changeset(%{groups: (groups ++ [group]) |> Enum.sort() |> Enum.uniq()})
|> unquote(repo).update()
|> elem(1)
@doc nil
def remove_group(user, []),
do: user
def remove_group(user, [group | groups]),
do: remove_group(remove_group(user, group), groups)
def remove_group(user, group) when not is_bitstring(group),
do: remove_group(user, "#{group}")
def remove_group(%{groups: groups} = user, group),
do: user
|> __MODULE__.permissions_changeset(%{groups: Enum.reject(groups, fn g -> g == group end)})
|> unquote(repo).update()
|> elem(1)
@doc nil
def is_a?(user, groups) when is_list(groups),
do: groups |> Enum.all?(fn g -> user |> is_a?(g) end)
def is_a?(user, group) when not is_bitstring(group),
do: is_a?(user, "#{group}")
def is_a?(%{groups: groups}, group),
do: Enum.member?(groups, group)
@doc nil
def set_permissions(user, element, value) when is_atom(element),
do: set_permissions(user, Atom.to_string(element), value)
def set_permissions(user, element, value) when is_integer value do
if ((value >= 0) and (value <= Enum.sum(Map.values permission_map())) ) do
user
|> __MODULE__.permissions_changeset(%{permissions: Map.put(user.permissions, element, value)})
|> unquote(repo).update()
else
{:error, user}
end
end
def set_permissions(user, element, value) do
case value do
"+" <> permissions ->
set_permissions(
user,
element,
(get_permissions(user, element) + permissions_string_to_int(permissions))
)
"-" <> permissions ->
set_permissions(
user,
element,
(get_permissions(user, element) - permissions_string_to_int(permissions))
)
_ ->
set_permissions(user, element, permissions_string_to_int(value))
end
end
@doc nil
defp permissions_string_to_int(string) do
string
|> String.graphemes
|> Enum.uniq
|> Enum.map(fn (p) -> permission_map()[String.to_atom(p)] end)
|> Enum.sum
end
@doc nil
defp permissions_int_to_string(int) when is_integer int do
keys = Map.keys(permission_map())
|> Enum.sort_by(fn (k) -> permission_map()[k] end)
|> Enum.reverse()
permissions_int_to_string(int, keys, [])
end
defp permissions_int_to_string(rest, [], acc) do
cond do
rest == 0 ->
acc |> Enum.map(fn (a) -> Atom.to_string(a) end) |> Enum.join()
true ->
:error
end
end
defp permissions_int_to_string(rest, [key | tail], acc) do
if rest - permission_map()[key] >= 0 do
permissions_int_to_string(rest - permission_map()[key], tail, [key | acc] )
else
permissions_int_to_string(rest, tail, acc)
end
end
def set_superuser(%{superuser: _} = user, boolean) when is_boolean(boolean),
do: user
|> __MODULE__.permissions_changeset(%{superuser: boolean})
|> unquote(repo).update()
|> elem(1)
end
end
end