PermitEx (permit_ex v0.3.0)

Copy Markdown View Source

Role and permission management for Ecto and Phoenix applications.

PermitEx keeps the core authorization model intentionally small: users receive roles globally or inside an optional context, roles receive permissions, and permissions are checked against the current scope.

Users always receive permissions through roles — there is no direct user-permission assignment.

Atom safety

Permission and role identifiers can be passed as atoms for convenience (can?(scope, :orders_manage)), but atoms must always be compile-time literals. Never derive them from user input via String.to_atom/1 — the atom table is not garbage-collected and exhausting it crashes the VM. Use strings for any value that originates from user input or external data.

Policy contract

allowed?/4 and authorize/4 accept an optional :policy module implementing PermitEx.Policy. The policy callback receives the full opts keyword list. Unexpected return values or raised exceptions propagate to the caller — wrap your policy in a try/rescue if you need guaranteed fail-closed semantics.

Optional features (all off by default)

config :permit_ex,
  cache: true,
  cache_ttl: :timer.minutes(5),
  wildcards: true,
  super_roles: ["super_admin"]

Summary

Functions

Checks a permission and an optional resource policy.

Assigns one role to a user in a context.

Assigns many roles to a user without removing existing roles.

Returns :ok or {:error, :unauthorized} for command-style flows.

Checks a permission and an optional resource policy.

Returns :ok or {:error, :unauthorized} when the scope has the role.

Returns true when the given scope or permission collection includes permission.

Returns true when every permission is granted.

Returns true when at least one of the permissions is granted.

Clones global role templates into a context.

Creates a permission.

Creates a global role or a context role when context_id is present.

Gets a permission by id.

Gets a permission by name.

Gets a role by id.

Gets a global or context-specific role by name.

Adds one permission to a role without removing existing ones.

Adds many permissions to a role without removing existing ones.

Returns true when every role is present.

Returns true when at least one of the roles is present.

Returns true when the given scope or role collection includes role.

Invalidates cached scope data.

Invalidates the entire scope cache.

Lists permissions ordered by name.

Lists permissions assigned to a role.

Lists global roles and roles for the given context, with permissions preloaded.

Lists role assignments for a user.

Loads permission names for the user in a context.

Removes one permission from a role.

Removes many permissions from a role.

Removes one role from a user in a context.

Returns a map of role name to permission names for the given context.

Loads roles assigned to a user in a context.

Lists roles that belong to one context.

Loads roles and permissions for a user in a single query.

Seeds permissions and roles in one transaction.

Replaces all permissions assigned to a role.

Replaces all roles assigned to a user in a context.

Updates a role. Locked roles require force?: true.

Creates or updates a context role by name.

Creates or updates a permission by name.

Creates or updates a global role by name.

Lists user ids that have the given permission through any role in the context.

Types

permission()

@type permission() :: String.t() | atom()

role()

@type role() :: PermitEx.Role.t() | Ecto.UUID.t() | String.t()

scope()

@type scope() :: %{
  optional(:permissions) => Enumerable.t(),
  optional(:roles) => Enumerable.t()
}

Functions

allowed?(scope, permission, resource \\ nil, opts \\ [])

Checks a permission and an optional resource policy.

Pass a policy module with :policy. The policy module must implement PermitEx.Policy.authorize/3. Full opts are forwarded to the policy.

assign_role(user_id, role_or_id, context_id \\ nil, opts \\ [])

Assigns one role to a user in a context.

assign_roles(user_id, roles, context_id \\ nil, opts \\ [])

Assigns many roles to a user without removing existing roles.

authorize(scope_or_permissions, permission)

Returns :ok or {:error, :unauthorized} for command-style flows.

authorize(scope, permission, resource_or_opts, opts \\ [])

Checks a permission and an optional resource policy.

Returns :ok or {:error, reason}. Pass a policy with :policy.

PermitEx.authorize(scope, "orders:manage", order, policy: MyApp.OrderPolicy)
PermitEx.authorize(scope, "orders:manage", policy: MyApp.OrderPolicy, resource: order)

authorize_role(scope_or_roles, role)

Returns :ok or {:error, :unauthorized} when the scope has the role.

can?(scope_or_permissions, permission)

Returns true when the given scope or permission collection includes permission.

When config :permit_ex, wildcards: true, granted permissions may use * (orders:* or *). When super_roles is configured and the scope has one of those roles, every permission check succeeds.

can_all?(scope_or_permissions, permissions)

Returns true when every permission is granted.

can_any?(scope_or_permissions, permissions)

Returns true when at least one of the permissions is granted.

clone_roles_to_context(context_id, opts \\ [])

Clones global role templates into a context.

A global role is any role with context_id == nil. The cloned context role receives the same name, description, locked flag, and permissions. Existing context roles are updated idempotently.

PermitEx.clone_roles_to_context(workspace.id)
PermitEx.clone_roles_to_context(workspace.id, roles: ["admin", "viewer"])

create_permission(attrs, opts \\ [])

Creates a permission.

create_role(attrs, opts \\ [])

Creates a global role or a context role when context_id is present.

delete_permission(permission, opts \\ [])

Deletes a permission.

delete_role(role, opts \\ [])

Deletes a role.

Locked roles return {:error, :role_locked} unless force?: true is passed.

get_permission(id, opts \\ [])

Gets a permission by id.

get_permission_by_name(name, opts \\ [])

Gets a permission by name.

get_role_by_id(id, opts \\ [])

Gets a role by id.

get_role_by_name(name, context_id \\ nil, opts \\ [])

Gets a global or context-specific role by name.

give_permission(role_ref, permission, opts \\ [])

Adds one permission to a role without removing existing ones.

Locked roles require force?: true.

give_permissions(role_ref, permissions, opts \\ [])

Adds many permissions to a role without removing existing ones.

Locked roles require force?: true.

has_all_roles?(scope_or_roles, roles)

Returns true when every role is present.

has_any_role?(scope_or_roles, roles)

Returns true when at least one of the roles is present.

has_permission?(scope_or_permissions, permission)

Alias for can?/2.

has_role?(scope_or_roles, role)

Returns true when the given scope or role collection includes role.

invalidate_cache(user_id, context_id \\ nil)

Invalidates cached scope data.

Pass a context_id for one scope (nil = global). Pass :all as the second argument to drop every cached scope for that user.

invalidate_cache_all()

Invalidates the entire scope cache.

list_permissions(opts \\ [])

Lists permissions ordered by name.

list_role_permissions(role_ref, opts \\ [])

Lists permissions assigned to a role.

list_roles(context_id \\ nil, opts \\ [])

Lists global roles and roles for the given context, with permissions preloaded.

list_user_roles(user_id, context_id \\ nil, opts \\ [])

Lists role assignments for a user.

normalize_permission(permission)

normalize_role(role)

permissions_for(user_id, context_id \\ nil, opts \\ [])

Loads permission names for the user in a context.

revoke_permission(role_ref, permission, opts \\ [])

Removes one permission from a role.

Locked roles require force?: true.

revoke_permissions(role_ref, permissions, opts \\ [])

Removes many permissions from a role.

Locked roles require force?: true.

revoke_role(user_id, role_or_id, context_id \\ nil, opts \\ [])

Removes one role from a user in a context.

role_matrix(context_id \\ nil, opts \\ [])

Returns a map of role name to permission names for the given context.

Useful for rendering admin permission matrices and exposing role definitions via API. Roles with no permissions appear with an empty list.

PermitEx.role_matrix()
#=> %{"admin" => ["orders:manage", "orders:view"], "viewer" => ["orders:view"]}

PermitEx.role_matrix(workspace.id)

roles_for(user_id, context_id \\ nil, opts \\ [])

Loads roles assigned to a user in a context.

roles_for_context(context_id, opts \\ [])

Lists roles that belong to one context.

scope_data_for(user_id, context_id \\ nil, opts \\ [])

Loads roles and permissions for a user in a single query.

Results are cached when config :permit_ex, cache: true. Pass skip_cache: true to force a database load.

seed!(definitions, opts \\ [])

Seeds permissions and roles in one transaction.

Pass context_id: to create context roles instead of global templates.

Expected shape:

PermitEx.seed!(
  permissions: [
    {"orders:view", "View orders"},
    {"orders:manage", "Manage orders"}
  ],
  roles: [
    {"admin", "Context admin", ["orders:view", "orders:manage"]},
    {"viewer", "Read-only user", ["orders:view"]}
  ]
)

PermitEx.seed!(definitions, context_id: workspace.id)

sync_context_roles_from_templates(context_id, opts \\ [])

Alias for clone_roles_to_context/2.

sync_permissions(role_ref, permissions, opts \\ [])

Alias for sync_role_permissions/3.

sync_role_permissions(role_ref, permissions, opts \\ [])

Replaces all permissions assigned to a role.

Accepts a role struct, role id, or role name. Permissions can be names, ids, atoms, or %PermitEx.Permission{} structs. Missing permissions return {:error, {:permissions_not_found, missing}} unless allow_missing?: true is passed.

Locked roles require force?: true.

sync_roles(user_id, roles, context_id \\ nil, opts \\ [])

Alias for sync_user_roles/4.

sync_user_roles(user_id, roles, context_id \\ nil, opts \\ [])

Replaces all roles assigned to a user in a context.

This is the Spatie-style syncRoles equivalent. It accepts role structs, ids or names and leaves the user with exactly the resolved roles.

update_permission(permission, attrs, opts \\ [])

Updates a permission.

update_role(role, attrs, opts \\ [])

Updates a role. Locked roles require force?: true.

upsert_context_role(name, context_id, attrs \\ %{}, opts \\ [])

Creates or updates a context role by name.

upsert_permission(name, attrs \\ %{}, opts \\ [])

Creates or updates a permission by name.

upsert_role(name, attrs \\ %{}, opts \\ [])

Creates or updates a global role by name.

users_with_permission(permission_ref, context_id \\ nil, opts \\ [])

Lists user ids that have the given permission through any role in the context.

users_with_role(role_ref, context_id \\ nil, opts \\ [])

Lists user ids assigned to a role.