Current section

Files

Jump to
phoenix_gen_api lib phoenix_gen_api permission.ex
Raw

lib/phoenix_gen_api/permission.ex

defmodule PhoenixGenApi.Permission do
@moduledoc """
Provides permission checking functionality for API requests.
This module implements a flexible permission system that can verify whether a user
has the right to execute a specific request. Permissions can be disabled, or configured
to check that certain request arguments match the requesting user's identity.
## Permission Modes
### Disabled Permissions (`false`)
When `check_permission: false` is set in the FunConfig, all permission checks pass.
This is useful for public endpoints that don't require authentication or authorization.
### Argument-Based Permissions (`{:arg, arg_name}`)
When `check_permission: {:arg, arg_name}` is configured, the system verifies that
the value of the specified argument matches the request's `user_id`. This ensures
users can only access their own data.
For example, with `{:arg, "user_id"}`, a request from user "123" will only be
allowed if `request.args["user_id"] == "123"`.
## Examples
# Public endpoint - no permission check
config = %FunConfig{
request_type: "get_public_data",
check_permission: false
}
request = %Request{user_id: "any_user"}
Permission.check_permission(request, config)
# => true
# User-specific endpoint - must match user_id
config = %FunConfig{
request_type: "get_user_profile",
check_permission: {:arg, "user_id"}
}
# This passes - user accessing their own data
request = %Request{
user_id: "user_123",
args: %{"user_id" => "user_123"}
}
Permission.check_permission(request, config)
# => true
# This fails - user trying to access another user's data
request = %Request{
user_id: "user_123",
args: %{"user_id" => "user_999"}
}
Permission.check_permission(request, config)
# => false
## Security Considerations
- Always use `check_permission!/2` in the execution path to raise on failures
- The `check_permission/2` function is non-raising and returns a boolean
- Missing arguments result in permission denial (returns `false`)
- Permission checks happen before argument validation and function execution
"""
alias PhoenixGenApi.Structs.{FunConfig, Request}
require Logger
@doc """
Checks if a request has permission to be executed based on the configuration.
This is a non-raising version that returns a boolean result. Use this when you
need to check permissions without raising an exception.
## Parameters
- `request` - The `Request` struct containing user information and arguments
- `fun_config` - The `FunConfig` struct with permission settings
## Returns
- `true` - Permission check passed
- `false` - Permission check failed
## Examples
request = %Request{
user_id: "user_123",
args: %{"user_id" => "user_123"}
}
config = %FunConfig{check_permission: {:arg, "user_id"}}
if Permission.check_permission(request, config) do
# Execute the request
else
# Deny access
end
"""
def check_permission(%Request{}, %FunConfig{check_permission: false}) do
true
end
def check_permission(request = %Request{}, %FunConfig{check_permission: {:arg, arg_name}}) do
case Map.get(request.args, arg_name) do
nil ->
Logger.warning(
"gen_api, check permission, missing argument #{inspect(arg_name)} in request: #{inspect(request)}"
)
false
user_id ->
user_id == request.user_id
end
end
@doc """
Checks permission and raises an exception if the check fails.
This is the raising version of `check_permission/2`. It should be used in the
request execution pipeline where permission failures should halt processing.
## Parameters
- `request` - The `Request` struct containing user information and arguments
- `fun_config` - The `FunConfig` struct with permission settings
## Returns
- `nil` - Permission check passed (function returns nothing on success)
## Raises
- `RuntimeError` - If the permission check fails
## Examples
request = %Request{
user_id: "user_123",
args: %{"user_id" => "user_999"}
}
config = %FunConfig{check_permission: {:arg, "user_id"}}
# This will raise because user_ids don't match
Permission.check_permission!(request, config)
# ** (RuntimeError) Permission denied for request from user: "user_123"
## Notes
- Logs a warning with request details when permission is denied
- Always called before request execution in the Executor module
- Returns nothing on success (only side effect is potential exception)
"""
def check_permission!(request = %Request{}, fun_config = %FunConfig{}) do
if not check_permission(request, fun_config) do
Logger.warning(
" gen_api, check permission, failed, request: #{inspect(request)}, fun config: #{inspect(fun_config)}"
)
raise "Permission denied for request from user: #{inspect(request.user_id)}, request: #{inspect(request.request_id)}"
end
end
end