feat: implement PermissionSets module with all 4 permission sets
- Add types for scope, action, resource_permission, permission_set - Implement get_permissions/1 for all 4 sets (own_data, read_only, normal_user, admin) - Implement valid_permission_set?/1 for string and atom validation - Implement permission_set_name_to_atom/1 with error handling
This commit is contained in:
parent
634d3bd446
commit
3a0fb4e84f
2 changed files with 813 additions and 5 deletions
|
|
@ -2,23 +2,60 @@ defmodule Mv.Authorization.PermissionSets do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Defines the four hardcoded permission sets for the application.
|
Defines the four hardcoded permission sets for the application.
|
||||||
|
|
||||||
This is a minimal stub implementation. The full implementation
|
Each permission set specifies:
|
||||||
with all permission details will be added in a subsequent issue.
|
- Resource permissions (what CRUD operations on which resources)
|
||||||
|
- Page permissions (which LiveView pages can be accessed)
|
||||||
|
- Scopes (own, linked, all)
|
||||||
|
|
||||||
## Permission Sets
|
## Permission Sets
|
||||||
|
|
||||||
1. **own_data** - Default for "Mitglied" role
|
1. **own_data** - Default for "Mitglied" role
|
||||||
|
- Can only access own user data and linked member/custom field values
|
||||||
|
- Cannot create new members or manage system
|
||||||
|
|
||||||
2. **read_only** - For "Vorstand" and "Buchhaltung" roles
|
2. **read_only** - For "Vorstand" and "Buchhaltung" roles
|
||||||
|
- Can read all member data
|
||||||
|
- Cannot create, update, or delete
|
||||||
|
|
||||||
3. **normal_user** - For "Kassenwart" role
|
3. **normal_user** - For "Kassenwart" role
|
||||||
|
- Create/Read/Update members (no delete for safety), full CRUD on custom field values
|
||||||
|
- Cannot manage custom fields or users
|
||||||
|
|
||||||
4. **admin** - For "Admin" role
|
4. **admin** - For "Admin" role
|
||||||
|
- Unrestricted access to all resources
|
||||||
|
- Can manage users, roles, custom fields
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
# Get list of all valid permission set names
|
# Get permissions for a role's permission set
|
||||||
PermissionSets.all_permission_sets()
|
permissions = PermissionSets.get_permissions(:admin)
|
||||||
# => [:own_data, :read_only, :normal_user, :admin]
|
|
||||||
|
# Check if a permission set name is valid
|
||||||
|
PermissionSets.valid_permission_set?("read_only") # => true
|
||||||
|
|
||||||
|
# Convert string to atom safely
|
||||||
|
{:ok, atom} = PermissionSets.permission_set_name_to_atom("own_data")
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
All functions are pure and compile-time. Permission lookups are < 1 microsecond.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@type scope :: :own | :linked | :all
|
||||||
|
@type action :: :read | :create | :update | :destroy
|
||||||
|
|
||||||
|
@type resource_permission :: %{
|
||||||
|
resource: String.t(),
|
||||||
|
action: action(),
|
||||||
|
scope: scope(),
|
||||||
|
granted: boolean()
|
||||||
|
}
|
||||||
|
|
||||||
|
@type permission_set :: %{
|
||||||
|
resources: [resource_permission()],
|
||||||
|
pages: [String.t()]
|
||||||
|
}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns the list of all valid permission set names.
|
Returns the list of all valid permission set names.
|
||||||
|
|
||||||
|
|
@ -31,4 +68,207 @@ defmodule Mv.Authorization.PermissionSets do
|
||||||
def all_permission_sets do
|
def all_permission_sets do
|
||||||
[:own_data, :read_only, :normal_user, :admin]
|
[:own_data, :read_only, :normal_user, :admin]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns permissions for the given permission set.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> permissions = PermissionSets.get_permissions(:admin)
|
||||||
|
iex> Enum.any?(permissions.resources, fn p ->
|
||||||
|
...> p.resource == "User" and p.action == :destroy
|
||||||
|
...> end)
|
||||||
|
true
|
||||||
|
|
||||||
|
iex> PermissionSets.get_permissions(:invalid)
|
||||||
|
** (FunctionClauseError) no function clause matching
|
||||||
|
"""
|
||||||
|
@spec get_permissions(atom()) :: permission_set()
|
||||||
|
|
||||||
|
def get_permissions(:own_data) do
|
||||||
|
%{
|
||||||
|
resources: [
|
||||||
|
# User: Can always read/update own credentials
|
||||||
|
%{resource: "User", action: :read, scope: :own, granted: true},
|
||||||
|
%{resource: "User", action: :update, scope: :own, granted: true},
|
||||||
|
|
||||||
|
# Member: Can read/update linked member
|
||||||
|
%{resource: "Member", action: :read, scope: :linked, granted: true},
|
||||||
|
%{resource: "Member", action: :update, scope: :linked, granted: true},
|
||||||
|
|
||||||
|
# CustomFieldValue: Can read/update custom field values of linked member
|
||||||
|
%{resource: "CustomFieldValue", action: :read, scope: :linked, granted: true},
|
||||||
|
%{resource: "CustomFieldValue", action: :update, scope: :linked, granted: true},
|
||||||
|
|
||||||
|
# CustomField: Can read all (needed for forms)
|
||||||
|
%{resource: "CustomField", action: :read, scope: :all, granted: true}
|
||||||
|
],
|
||||||
|
pages: [
|
||||||
|
# Home page
|
||||||
|
"/",
|
||||||
|
# Own profile
|
||||||
|
"/profile",
|
||||||
|
# Linked member detail (filtered by policy)
|
||||||
|
"/members/:id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_permissions(:read_only) do
|
||||||
|
%{
|
||||||
|
resources: [
|
||||||
|
# User: Can read/update own credentials only
|
||||||
|
%{resource: "User", action: :read, scope: :own, granted: true},
|
||||||
|
%{resource: "User", action: :update, scope: :own, granted: true},
|
||||||
|
|
||||||
|
# Member: Can read all members, no modifications
|
||||||
|
%{resource: "Member", action: :read, scope: :all, granted: true},
|
||||||
|
|
||||||
|
# CustomFieldValue: Can read all custom field values
|
||||||
|
%{resource: "CustomFieldValue", action: :read, scope: :all, granted: true},
|
||||||
|
|
||||||
|
# CustomField: Can read all
|
||||||
|
%{resource: "CustomField", action: :read, scope: :all, granted: true}
|
||||||
|
],
|
||||||
|
pages: [
|
||||||
|
"/",
|
||||||
|
# Member list
|
||||||
|
"/members",
|
||||||
|
# Member detail
|
||||||
|
"/members/:id",
|
||||||
|
# Custom field values overview
|
||||||
|
"/custom_field_values"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_permissions(:normal_user) do
|
||||||
|
%{
|
||||||
|
resources: [
|
||||||
|
# User: Can read/update own credentials only
|
||||||
|
%{resource: "User", action: :read, scope: :own, granted: true},
|
||||||
|
%{resource: "User", action: :update, scope: :own, granted: true},
|
||||||
|
|
||||||
|
# Member: Full CRUD except destroy (safety)
|
||||||
|
%{resource: "Member", action: :read, scope: :all, granted: true},
|
||||||
|
%{resource: "Member", action: :create, scope: :all, granted: true},
|
||||||
|
%{resource: "Member", action: :update, scope: :all, granted: true},
|
||||||
|
# Note: destroy intentionally omitted for safety
|
||||||
|
|
||||||
|
# CustomFieldValue: Full CRUD
|
||||||
|
%{resource: "CustomFieldValue", action: :read, scope: :all, granted: true},
|
||||||
|
%{resource: "CustomFieldValue", action: :create, scope: :all, granted: true},
|
||||||
|
%{resource: "CustomFieldValue", action: :update, scope: :all, granted: true},
|
||||||
|
%{resource: "CustomFieldValue", action: :destroy, scope: :all, granted: true},
|
||||||
|
|
||||||
|
# CustomField: Read only (admin manages definitions)
|
||||||
|
%{resource: "CustomField", action: :read, scope: :all, granted: true}
|
||||||
|
],
|
||||||
|
pages: [
|
||||||
|
"/",
|
||||||
|
"/members",
|
||||||
|
# Create member
|
||||||
|
"/members/new",
|
||||||
|
"/members/:id",
|
||||||
|
# Edit member
|
||||||
|
"/members/:id/edit",
|
||||||
|
"/custom_field_values",
|
||||||
|
"/custom_field_values/new",
|
||||||
|
"/custom_field_values/:id/edit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_permissions(:admin) do
|
||||||
|
%{
|
||||||
|
resources: [
|
||||||
|
# User: Full management including other users
|
||||||
|
%{resource: "User", action: :read, scope: :all, granted: true},
|
||||||
|
%{resource: "User", action: :create, scope: :all, granted: true},
|
||||||
|
%{resource: "User", action: :update, scope: :all, granted: true},
|
||||||
|
%{resource: "User", action: :destroy, scope: :all, granted: true},
|
||||||
|
|
||||||
|
# Member: Full CRUD
|
||||||
|
%{resource: "Member", action: :read, scope: :all, granted: true},
|
||||||
|
%{resource: "Member", action: :create, scope: :all, granted: true},
|
||||||
|
%{resource: "Member", action: :update, scope: :all, granted: true},
|
||||||
|
%{resource: "Member", action: :destroy, scope: :all, granted: true},
|
||||||
|
|
||||||
|
# CustomFieldValue: Full CRUD
|
||||||
|
%{resource: "CustomFieldValue", action: :read, scope: :all, granted: true},
|
||||||
|
%{resource: "CustomFieldValue", action: :create, scope: :all, granted: true},
|
||||||
|
%{resource: "CustomFieldValue", action: :update, scope: :all, granted: true},
|
||||||
|
%{resource: "CustomFieldValue", action: :destroy, scope: :all, granted: true},
|
||||||
|
|
||||||
|
# CustomField: Full CRUD (admin manages custom field definitions)
|
||||||
|
%{resource: "CustomField", action: :read, scope: :all, granted: true},
|
||||||
|
%{resource: "CustomField", action: :create, scope: :all, granted: true},
|
||||||
|
%{resource: "CustomField", action: :update, scope: :all, granted: true},
|
||||||
|
%{resource: "CustomField", action: :destroy, scope: :all, granted: true},
|
||||||
|
|
||||||
|
# Role: Full CRUD (admin manages roles)
|
||||||
|
%{resource: "Role", action: :read, scope: :all, granted: true},
|
||||||
|
%{resource: "Role", action: :create, scope: :all, granted: true},
|
||||||
|
%{resource: "Role", action: :update, scope: :all, granted: true},
|
||||||
|
%{resource: "Role", action: :destroy, scope: :all, granted: true}
|
||||||
|
],
|
||||||
|
pages: [
|
||||||
|
# Wildcard: Admin can access all pages
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Checks if a permission set name (string or atom) is valid.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> PermissionSets.valid_permission_set?("admin")
|
||||||
|
true
|
||||||
|
|
||||||
|
iex> PermissionSets.valid_permission_set?(:read_only)
|
||||||
|
true
|
||||||
|
|
||||||
|
iex> PermissionSets.valid_permission_set?("invalid")
|
||||||
|
false
|
||||||
|
"""
|
||||||
|
@spec valid_permission_set?(String.t() | atom()) :: boolean()
|
||||||
|
def valid_permission_set?(name) when is_binary(name) do
|
||||||
|
case permission_set_name_to_atom(name) do
|
||||||
|
{:ok, _atom} -> true
|
||||||
|
{:error, _} -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_permission_set?(name) when is_atom(name) do
|
||||||
|
name in all_permission_sets()
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_permission_set?(_), do: false
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Converts a permission set name string to atom safely.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> PermissionSets.permission_set_name_to_atom("admin")
|
||||||
|
{:ok, :admin}
|
||||||
|
|
||||||
|
iex> PermissionSets.permission_set_name_to_atom("invalid")
|
||||||
|
{:error, :invalid_permission_set}
|
||||||
|
"""
|
||||||
|
@spec permission_set_name_to_atom(String.t()) ::
|
||||||
|
{:ok, atom()} | {:error, :invalid_permission_set}
|
||||||
|
def permission_set_name_to_atom(name) when is_binary(name) do
|
||||||
|
atom = String.to_existing_atom(name)
|
||||||
|
|
||||||
|
if valid_permission_set?(atom) do
|
||||||
|
{:ok, atom}
|
||||||
|
else
|
||||||
|
{:error, :invalid_permission_set}
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
ArgumentError -> {:error, :invalid_permission_set}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
568
test/mv/authorization/permission_sets_test.exs
Normal file
568
test/mv/authorization/permission_sets_test.exs
Normal file
|
|
@ -0,0 +1,568 @@
|
||||||
|
defmodule Mv.Authorization.PermissionSetsTest do
|
||||||
|
@moduledoc """
|
||||||
|
Tests for the PermissionSets module that defines hardcoded permission sets.
|
||||||
|
"""
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias Mv.Authorization.PermissionSets
|
||||||
|
|
||||||
|
describe "all_permission_sets/0" do
|
||||||
|
test "returns all four permission sets" do
|
||||||
|
sets = PermissionSets.all_permission_sets()
|
||||||
|
|
||||||
|
assert length(sets) == 4
|
||||||
|
assert :own_data in sets
|
||||||
|
assert :read_only in sets
|
||||||
|
assert :normal_user in sets
|
||||||
|
assert :admin in sets
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "get_permissions/1" do
|
||||||
|
test "returns map with :resources and :pages keys for :own_data" do
|
||||||
|
permissions = PermissionSets.get_permissions(:own_data)
|
||||||
|
|
||||||
|
assert Map.has_key?(permissions, :resources)
|
||||||
|
assert Map.has_key?(permissions, :pages)
|
||||||
|
assert is_list(permissions.resources)
|
||||||
|
assert is_list(permissions.pages)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns map with :resources and :pages keys for :read_only" do
|
||||||
|
permissions = PermissionSets.get_permissions(:read_only)
|
||||||
|
|
||||||
|
assert Map.has_key?(permissions, :resources)
|
||||||
|
assert Map.has_key?(permissions, :pages)
|
||||||
|
assert is_list(permissions.resources)
|
||||||
|
assert is_list(permissions.pages)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns map with :resources and :pages keys for :normal_user" do
|
||||||
|
permissions = PermissionSets.get_permissions(:normal_user)
|
||||||
|
|
||||||
|
assert Map.has_key?(permissions, :resources)
|
||||||
|
assert Map.has_key?(permissions, :pages)
|
||||||
|
assert is_list(permissions.resources)
|
||||||
|
assert is_list(permissions.pages)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns map with :resources and :pages keys for :admin" do
|
||||||
|
permissions = PermissionSets.get_permissions(:admin)
|
||||||
|
|
||||||
|
assert Map.has_key?(permissions, :resources)
|
||||||
|
assert Map.has_key?(permissions, :pages)
|
||||||
|
assert is_list(permissions.resources)
|
||||||
|
assert is_list(permissions.pages)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "each resource permission has required keys" do
|
||||||
|
permissions = PermissionSets.get_permissions(:own_data)
|
||||||
|
|
||||||
|
Enum.each(permissions.resources, fn perm ->
|
||||||
|
assert Map.has_key?(perm, :resource)
|
||||||
|
assert Map.has_key?(perm, :action)
|
||||||
|
assert Map.has_key?(perm, :scope)
|
||||||
|
assert Map.has_key?(perm, :granted)
|
||||||
|
assert is_binary(perm.resource)
|
||||||
|
assert perm.action in [:read, :create, :update, :destroy]
|
||||||
|
assert perm.scope in [:own, :linked, :all]
|
||||||
|
assert is_boolean(perm.granted)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "pages lists are non-empty for all permission sets" do
|
||||||
|
for set <- [:own_data, :read_only, :normal_user, :admin] do
|
||||||
|
permissions = PermissionSets.get_permissions(set)
|
||||||
|
|
||||||
|
assert permissions.pages != [],
|
||||||
|
"Permission set #{set} should have at least one page"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "get_permissions/1 - :own_data permission content" do
|
||||||
|
test "allows User read/update with scope :own" do
|
||||||
|
permissions = PermissionSets.get_permissions(:own_data)
|
||||||
|
|
||||||
|
user_read =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :read end)
|
||||||
|
|
||||||
|
user_update =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :update end)
|
||||||
|
|
||||||
|
assert user_read.scope == :own
|
||||||
|
assert user_read.granted == true
|
||||||
|
assert user_update.scope == :own
|
||||||
|
assert user_update.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows Member read/update with scope :linked" do
|
||||||
|
permissions = PermissionSets.get_permissions(:own_data)
|
||||||
|
|
||||||
|
member_read =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :read end)
|
||||||
|
|
||||||
|
member_update =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :update end)
|
||||||
|
|
||||||
|
assert member_read.scope == :linked
|
||||||
|
assert member_read.granted == true
|
||||||
|
assert member_update.scope == :linked
|
||||||
|
assert member_update.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows CustomFieldValue read/update with scope :linked" do
|
||||||
|
permissions = PermissionSets.get_permissions(:own_data)
|
||||||
|
|
||||||
|
cfv_read =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :read
|
||||||
|
end)
|
||||||
|
|
||||||
|
cfv_update =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :update
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert cfv_read.scope == :linked
|
||||||
|
assert cfv_read.granted == true
|
||||||
|
assert cfv_update.scope == :linked
|
||||||
|
assert cfv_update.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows CustomField read with scope :all" do
|
||||||
|
permissions = PermissionSets.get_permissions(:own_data)
|
||||||
|
|
||||||
|
cf_read =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomField" && p.action == :read
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert cf_read.scope == :all
|
||||||
|
assert cf_read.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "includes correct pages" do
|
||||||
|
permissions = PermissionSets.get_permissions(:own_data)
|
||||||
|
|
||||||
|
assert "/" in permissions.pages
|
||||||
|
assert "/profile" in permissions.pages
|
||||||
|
assert "/members/:id" in permissions.pages
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "get_permissions/1 - :read_only permission content" do
|
||||||
|
test "allows User read/update with scope :own" do
|
||||||
|
permissions = PermissionSets.get_permissions(:read_only)
|
||||||
|
|
||||||
|
user_read =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :read end)
|
||||||
|
|
||||||
|
user_update =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :update end)
|
||||||
|
|
||||||
|
assert user_read.scope == :own
|
||||||
|
assert user_read.granted == true
|
||||||
|
assert user_update.scope == :own
|
||||||
|
assert user_update.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows Member read with scope :all" do
|
||||||
|
permissions = PermissionSets.get_permissions(:read_only)
|
||||||
|
|
||||||
|
member_read =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :read end)
|
||||||
|
|
||||||
|
assert member_read.scope == :all
|
||||||
|
assert member_read.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does NOT allow Member create/update/destroy" do
|
||||||
|
permissions = PermissionSets.get_permissions(:read_only)
|
||||||
|
|
||||||
|
member_create =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :create end)
|
||||||
|
|
||||||
|
member_update =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :update end)
|
||||||
|
|
||||||
|
member_destroy =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "Member" && p.action == :destroy
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert member_create == nil || member_create.granted == false
|
||||||
|
assert member_update == nil || member_update.granted == false
|
||||||
|
assert member_destroy == nil || member_destroy.granted == false
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows CustomFieldValue read with scope :all" do
|
||||||
|
permissions = PermissionSets.get_permissions(:read_only)
|
||||||
|
|
||||||
|
cfv_read =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :read
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert cfv_read.scope == :all
|
||||||
|
assert cfv_read.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does NOT allow CustomFieldValue create/update/destroy" do
|
||||||
|
permissions = PermissionSets.get_permissions(:read_only)
|
||||||
|
|
||||||
|
cfv_create =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :create
|
||||||
|
end)
|
||||||
|
|
||||||
|
cfv_update =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :update
|
||||||
|
end)
|
||||||
|
|
||||||
|
cfv_destroy =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :destroy
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert cfv_create == nil || cfv_create.granted == false
|
||||||
|
assert cfv_update == nil || cfv_update.granted == false
|
||||||
|
assert cfv_destroy == nil || cfv_destroy.granted == false
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows CustomField read with scope :all" do
|
||||||
|
permissions = PermissionSets.get_permissions(:read_only)
|
||||||
|
|
||||||
|
cf_read =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomField" && p.action == :read
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert cf_read.scope == :all
|
||||||
|
assert cf_read.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "includes correct pages" do
|
||||||
|
permissions = PermissionSets.get_permissions(:read_only)
|
||||||
|
|
||||||
|
assert "/" in permissions.pages
|
||||||
|
assert "/members" in permissions.pages
|
||||||
|
assert "/members/:id" in permissions.pages
|
||||||
|
assert "/custom_field_values" in permissions.pages
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "get_permissions/1 - :normal_user permission content" do
|
||||||
|
test "allows User read/update with scope :own" do
|
||||||
|
permissions = PermissionSets.get_permissions(:normal_user)
|
||||||
|
|
||||||
|
user_read =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :read end)
|
||||||
|
|
||||||
|
user_update =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :update end)
|
||||||
|
|
||||||
|
assert user_read.scope == :own
|
||||||
|
assert user_read.granted == true
|
||||||
|
assert user_update.scope == :own
|
||||||
|
assert user_update.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows Member read/create/update with scope :all" do
|
||||||
|
permissions = PermissionSets.get_permissions(:normal_user)
|
||||||
|
|
||||||
|
member_read =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :read end)
|
||||||
|
|
||||||
|
member_create =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :create end)
|
||||||
|
|
||||||
|
member_update =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :update end)
|
||||||
|
|
||||||
|
assert member_read.scope == :all
|
||||||
|
assert member_read.granted == true
|
||||||
|
assert member_create.scope == :all
|
||||||
|
assert member_create.granted == true
|
||||||
|
assert member_update.scope == :all
|
||||||
|
assert member_update.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does NOT allow Member destroy (safety)" do
|
||||||
|
permissions = PermissionSets.get_permissions(:normal_user)
|
||||||
|
|
||||||
|
member_destroy =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "Member" && p.action == :destroy
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert member_destroy == nil || member_destroy.granted == false
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows CustomFieldValue full CRUD with scope :all" do
|
||||||
|
permissions = PermissionSets.get_permissions(:normal_user)
|
||||||
|
|
||||||
|
cfv_read =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :read
|
||||||
|
end)
|
||||||
|
|
||||||
|
cfv_create =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :create
|
||||||
|
end)
|
||||||
|
|
||||||
|
cfv_update =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :update
|
||||||
|
end)
|
||||||
|
|
||||||
|
cfv_destroy =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :destroy
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert cfv_read.scope == :all
|
||||||
|
assert cfv_read.granted == true
|
||||||
|
assert cfv_create.scope == :all
|
||||||
|
assert cfv_create.granted == true
|
||||||
|
assert cfv_update.scope == :all
|
||||||
|
assert cfv_update.granted == true
|
||||||
|
assert cfv_destroy.scope == :all
|
||||||
|
assert cfv_destroy.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows CustomField read with scope :all" do
|
||||||
|
permissions = PermissionSets.get_permissions(:normal_user)
|
||||||
|
|
||||||
|
cf_read =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomField" && p.action == :read
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert cf_read.scope == :all
|
||||||
|
assert cf_read.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "includes correct pages" do
|
||||||
|
permissions = PermissionSets.get_permissions(:normal_user)
|
||||||
|
|
||||||
|
assert "/" in permissions.pages
|
||||||
|
assert "/members" in permissions.pages
|
||||||
|
assert "/members/new" in permissions.pages
|
||||||
|
assert "/members/:id" in permissions.pages
|
||||||
|
assert "/members/:id/edit" in permissions.pages
|
||||||
|
assert "/custom_field_values" in permissions.pages
|
||||||
|
assert "/custom_field_values/new" in permissions.pages
|
||||||
|
assert "/custom_field_values/:id/edit" in permissions.pages
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "get_permissions/1 - :admin permission content" do
|
||||||
|
test "allows User full CRUD with scope :all" do
|
||||||
|
permissions = PermissionSets.get_permissions(:admin)
|
||||||
|
|
||||||
|
user_read =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :read end)
|
||||||
|
|
||||||
|
user_create =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :create end)
|
||||||
|
|
||||||
|
user_update =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :update end)
|
||||||
|
|
||||||
|
user_destroy =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "User" && p.action == :destroy end)
|
||||||
|
|
||||||
|
assert user_read.scope == :all
|
||||||
|
assert user_read.granted == true
|
||||||
|
assert user_create.scope == :all
|
||||||
|
assert user_create.granted == true
|
||||||
|
assert user_update.scope == :all
|
||||||
|
assert user_update.granted == true
|
||||||
|
assert user_destroy.scope == :all
|
||||||
|
assert user_destroy.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows Member full CRUD with scope :all" do
|
||||||
|
permissions = PermissionSets.get_permissions(:admin)
|
||||||
|
|
||||||
|
member_read =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :read end)
|
||||||
|
|
||||||
|
member_create =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :create end)
|
||||||
|
|
||||||
|
member_update =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Member" && p.action == :update end)
|
||||||
|
|
||||||
|
member_destroy =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "Member" && p.action == :destroy
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert member_read.scope == :all
|
||||||
|
assert member_read.granted == true
|
||||||
|
assert member_create.scope == :all
|
||||||
|
assert member_create.granted == true
|
||||||
|
assert member_update.scope == :all
|
||||||
|
assert member_update.granted == true
|
||||||
|
assert member_destroy.scope == :all
|
||||||
|
assert member_destroy.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows CustomFieldValue full CRUD with scope :all" do
|
||||||
|
permissions = PermissionSets.get_permissions(:admin)
|
||||||
|
|
||||||
|
cfv_read =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :read
|
||||||
|
end)
|
||||||
|
|
||||||
|
cfv_create =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :create
|
||||||
|
end)
|
||||||
|
|
||||||
|
cfv_update =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :update
|
||||||
|
end)
|
||||||
|
|
||||||
|
cfv_destroy =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomFieldValue" && p.action == :destroy
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert cfv_read.scope == :all
|
||||||
|
assert cfv_read.granted == true
|
||||||
|
assert cfv_create.scope == :all
|
||||||
|
assert cfv_create.granted == true
|
||||||
|
assert cfv_update.scope == :all
|
||||||
|
assert cfv_update.granted == true
|
||||||
|
assert cfv_destroy.scope == :all
|
||||||
|
assert cfv_destroy.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows CustomField full CRUD with scope :all" do
|
||||||
|
permissions = PermissionSets.get_permissions(:admin)
|
||||||
|
|
||||||
|
cf_read =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomField" && p.action == :read
|
||||||
|
end)
|
||||||
|
|
||||||
|
cf_create =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomField" && p.action == :create
|
||||||
|
end)
|
||||||
|
|
||||||
|
cf_update =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomField" && p.action == :update
|
||||||
|
end)
|
||||||
|
|
||||||
|
cf_destroy =
|
||||||
|
Enum.find(permissions.resources, fn p ->
|
||||||
|
p.resource == "CustomField" && p.action == :destroy
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert cf_read.scope == :all
|
||||||
|
assert cf_read.granted == true
|
||||||
|
assert cf_create.scope == :all
|
||||||
|
assert cf_create.granted == true
|
||||||
|
assert cf_update.scope == :all
|
||||||
|
assert cf_update.granted == true
|
||||||
|
assert cf_destroy.scope == :all
|
||||||
|
assert cf_destroy.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows Role full CRUD with scope :all" do
|
||||||
|
permissions = PermissionSets.get_permissions(:admin)
|
||||||
|
|
||||||
|
role_read =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Role" && p.action == :read end)
|
||||||
|
|
||||||
|
role_create =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Role" && p.action == :create end)
|
||||||
|
|
||||||
|
role_update =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Role" && p.action == :update end)
|
||||||
|
|
||||||
|
role_destroy =
|
||||||
|
Enum.find(permissions.resources, fn p -> p.resource == "Role" && p.action == :destroy end)
|
||||||
|
|
||||||
|
assert role_read.scope == :all
|
||||||
|
assert role_read.granted == true
|
||||||
|
assert role_create.scope == :all
|
||||||
|
assert role_create.granted == true
|
||||||
|
assert role_update.scope == :all
|
||||||
|
assert role_update.granted == true
|
||||||
|
assert role_destroy.scope == :all
|
||||||
|
assert role_destroy.granted == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "has wildcard page permission" do
|
||||||
|
permissions = PermissionSets.get_permissions(:admin)
|
||||||
|
|
||||||
|
assert "*" in permissions.pages
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "valid_permission_set?/1" do
|
||||||
|
test "returns true for valid permission set string" do
|
||||||
|
assert PermissionSets.valid_permission_set?("own_data") == true
|
||||||
|
assert PermissionSets.valid_permission_set?("read_only") == true
|
||||||
|
assert PermissionSets.valid_permission_set?("normal_user") == true
|
||||||
|
assert PermissionSets.valid_permission_set?("admin") == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns true for valid permission set atom" do
|
||||||
|
assert PermissionSets.valid_permission_set?(:own_data) == true
|
||||||
|
assert PermissionSets.valid_permission_set?(:read_only) == true
|
||||||
|
assert PermissionSets.valid_permission_set?(:normal_user) == true
|
||||||
|
assert PermissionSets.valid_permission_set?(:admin) == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns false for invalid permission set string" do
|
||||||
|
assert PermissionSets.valid_permission_set?("invalid") == false
|
||||||
|
assert PermissionSets.valid_permission_set?("") == false
|
||||||
|
assert PermissionSets.valid_permission_set?("admin_user") == false
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns false for invalid permission set atom" do
|
||||||
|
assert PermissionSets.valid_permission_set?(:invalid) == false
|
||||||
|
assert PermissionSets.valid_permission_set?(:unknown) == false
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns false for nil input" do
|
||||||
|
assert PermissionSets.valid_permission_set?(nil) == false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "permission_set_name_to_atom/1" do
|
||||||
|
test "returns {:ok, atom} for valid permission set name" do
|
||||||
|
assert PermissionSets.permission_set_name_to_atom("own_data") == {:ok, :own_data}
|
||||||
|
assert PermissionSets.permission_set_name_to_atom("read_only") == {:ok, :read_only}
|
||||||
|
assert PermissionSets.permission_set_name_to_atom("normal_user") == {:ok, :normal_user}
|
||||||
|
assert PermissionSets.permission_set_name_to_atom("admin") == {:ok, :admin}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns {:error, :invalid_permission_set} for invalid permission set name" do
|
||||||
|
assert PermissionSets.permission_set_name_to_atom("invalid") ==
|
||||||
|
{:error, :invalid_permission_set}
|
||||||
|
|
||||||
|
assert PermissionSets.permission_set_name_to_atom("") == {:error, :invalid_permission_set}
|
||||||
|
|
||||||
|
assert PermissionSets.permission_set_name_to_atom("admin_user") ==
|
||||||
|
{:error, :invalid_permission_set}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles non-existent atom gracefully" do
|
||||||
|
# String.to_existing_atom will raise ArgumentError for non-existent atoms
|
||||||
|
assert PermissionSets.permission_set_name_to_atom("nonexistent_atom_12345") ==
|
||||||
|
{:error, :invalid_permission_set}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue