class UsersController extends PublicApiController (View source)

/api/v2/users: lists, creates, updates and deletes users and assigns their permission template.

Constants

protected MAX_PAGE_SIZE

CHANGE_REQUEST_REQUIRED

Refusal for a direct write when the caller's changes to the zone go through review

CHANGE_APPROVAL_DISABLED

Every change request endpoint answers this while approval.enabled is off

private MAX_GROUPS_PER_REQUEST

Guards against a single request fanning out into thousands of membership queries.

private USER_SORT_FIELDS

Fields accepted by the sort parameter of GET /users

Properties

protected PDO $db from  BaseController
protected array $requestData from  BaseController
protected Request $httpRequest from  BaseController
protected MessageService $messageService from  BaseController
protected ConfigurationInterface $config from  BaseController
protected LoggerInterface $logger from  BaseController
protected Request $request from  AbstractApiController
protected array $pathParameters from  PublicApiController
protected int $authenticatedUserId from  PublicApiController
protected ApiKeyScope|null $apiKeyScope

Permission scope of the API key used for this request. Null when the request authenticated via HTTP Basic auth or no key scope could be resolved, in which case {self::getApiKeyScope()} returns an unrestricted scope.

from  PublicApiController

Methods

void
run()

Handle user-related requests

__construct(array $request, array $pathParameters = [])

PublicApiController constructor

static void
bindContext(BootContext $context)

Hands the booted context to every controller the router builds.

static BootContext
requestContext()

No description

bool
requiresCsrfValidation()

API requests authenticate per call with an API key or HTTP Basic credentials, so there is no session-bound form token to check.

bool
isPost()

Checks if the current request is a POST request.

getConfig()

Gets the application configuration.

mixed
getModuleConfig(string $module, string $key, mixed $default = null)

Get a module config value with legacy fallback.

void
render(string $template, array $params)

Renders a template with the given parameters.

getPdnsCapabilities()

API requests carry no session, so the PowerDNS version is fetched on first use and kept for this request only. Callers ask lazily (a zone create with a catalog kind), so ordinary requests never pay for the lookup.

array|null
getCachedPdnsServerInfo(bool $allowExpired = true)

The server info the page chrome reads for zone type labels. Never calls PowerDNS (it renders on login pages too); the dashboard refreshes the cache, and an expired entry still beats flipping labels to Master/Slave.

getRecordTypeCapabilities()

Capabilities to use when filtering selectable record types, or null to skip version filtering entirely.

void
refreshPdnsCapabilities()

Trigger a session-cached refresh of PowerDNS version + capabilities.

void
validateCsrfToken()

Validates the CSRF token from the request.

void
redirect(string $url, array $args = [])

Redirects to a specified URL with optional arguments.

void
setMessage(string $script, string $type, string $content)

Sets a message to be displayed for a specific script.

array|null
getMessages(string $script)

Gets messages for a specific script.

void
checkCondition(bool $condition, string $errorMessage)

Checks a condition and displays an error message if the condition is true.

never
halt(string $kind, string $target)

Ends the request after its response was written; see RequestHalted.

never
sendAndHalt(Response $response)

Sends a prepared response and ends the request; the API controllers answer every request this way.

services()

Lazily builds the shared service factory so per-request memoized instances (backend provider, permission cache) span all accessors.

moduleServices()

The same factory narrowed to the module SDK contract; module controllers use this instead of services() so their core dependencies stay declared.

backendCapabilities()

The request's backend as a capability probe; controllers ask it what the backend can do instead of re-reading dns.backend from the configuration.

bool
isApiBackend()

Backend identity, for the few places that report it rather than act on a capability (the is_api_backend template variable, the API error banner).

array
apiEscapeRiskRrsets(int $zoneId, string $zoneName)

RRsets of the zone that PowerDNS Auth 5.1.x would alter on the next API write to them; always empty on the SQL backend, which writes content as entered.

int
resolveRowsPerPage(int $fallback = PaginationService::DEFAULT_ROWS_PER_PAGE)

Resolves the page size for a listing: the request value wins, then the user's stored preference, then interface.rows_per_page with $fallback when unset.

string
presentPagination(int $totalItems, int $itemsPerPage, string $path, array $queryParams = [])

Renders a pagination widget for a paginated listing.

array
paginationVariables(int $totalItems, int $itemsPerPage, string $path, array $queryParams = [])

The pagination (pre-rendered string, kept for theme forks) and pagination_items (for _partials/pagination.html) template variables for a paginated listing.

bool
hasPermission(string $permission)

Check if the logged-in user has the given permission

string
getViewPermissionLevel()

The logged-in user's zone view level: "all", "own" or "none"

bool
canViewZones()

Whether the logged-in user may view any zone; flashes the refusal when not, so a zone lookup skipped on "none" still explains itself on the page.

bool
isZoneOwner(int $zoneId)

Check if the logged-in user owns the given zone directly or via group membership

void
requireZoneView(int $zoneId)

Stops the request unless the current user may open the zone (showError exits).

createZoneManagementService()

The zone service the API uses, told what the connected server supports.

createZoneCreateService()

The zone creation flow of the add-zone forms, told what the connected server supports.

moduleRegistry()

The request's loaded module registry.

array
moduleCapabilityData(string $capability, array $context = [])

What the enabled modules offer for a capability (wizard actions, lookup links, export formats), honouring each module's admin restriction.

bool
moduleProvides(string $capability)

No description

changeApproval()

Change-approval answers for one user and zone; callers pass the user id, usually getCurrentUserId(). One instance per request.

string
changeApprovalModeForZone(int $zoneId)

No description

directWriteZoneGate()

For a write that lands in a zone other than the one the request was checked against, such as the PTR of an A record: refused where the user must file a change request.

int|null
getCurrentUserId()

Get current user ID

array
assignableOwners(array $users)

The users a new zone may be given to: everyone when the create path lets the caller assign other owners and they may see other users, otherwise only the current user.

int|string
preservedOwnerChoice(array $assignableOwners, mixed $ownerInput)

The owner an add-zone form shows again after a failed submit: the posted id when the picker can offer it, '' for an explicit "no user owner", otherwise the current user.

void
reportZoneWrite(string $script, ZoneWriteResult $result, string $successMessage)

Flash the outcome of a zone write to a page: the given text on success, the result's reason on failure.

session()

The request's session, shared by every collaborator this controller builds.

getUserContextService()

Get the user context service

void
setCurrentPage(string $page)

Sets the current page identifier used for navigation highlighting.

void
setPageTitle(string $title)

Sets the page title displayed in the header.

void
checkPermission(string $permission, string $errorMessage)

Checks if the user has a specific permission and displays an error message if not.

int
requireNumericParam(string $name, string|null $error = null)

Reads a positive integer request parameter (an id) or ends the request with an error page.

void
renderNotFound()

Answers with the standard 404 page, for routes whose feature is switched off.

void
showError(string $error, string|null $recordName = null)

Displays an error message.

array
getRequest()

Gets the request data.

string
getSafeRequestValue(string $key)

Gets a safe value from the request data.

void
setValidationConstraints(array $constraints)

Sets validation constraints for the request data.

bool
doValidateRequest(array|null $data = null)

Validates the request data.

void
showFirstValidationError(array|null $data = null)

Displays the first validation error.

void
addSystemMessage(string $type, string $content)

Adds a system-wide message that will be displayed on any page

string|null
arrayQueryParameter()

The first query parameter sent as an array (?type[]=A), or null when every value is a scalar. InputBag::get() throws on such a value, so subclasses refuse the request before any handler reads the query. The key is scrubbed to valid UTF-8 because it is echoed in a JSON error.

bool
isJsonRequest()

Checks if the current request is a JSON request

array|null
getJsonInput()

Get JSON input from request body

array|null
getValidatedJsonBody()

Get the JSON request body validated as a non-empty array.

JsonResponse
returnJsonResponse(mixed $data, int $status = 200, array $headers = [])

Override to record every public API response in the audit log

JsonResponse
returnApiResponse(mixed $data, bool $success = true, string|null $message = null, int $status = 200, array $additionalFields = [], array $headers = [])

Wrapped API response: {success, data, message?} plus any additional top-level fields.

JsonResponse
returnApiError(string $message, int $status = 400, mixed $data = null, array $additionalFields = [], array $headers = [])

Wrapped API error: {success: false, data, message} plus any additional top-level fields.

JsonResponse
methodNotAllowed(array $allowedMethods)

Return a 405 Method Not Allowed error with the RFC 9110 Allow header listing the methods the endpoint supports.

JsonResponse
returnErrorResponse(string $message, int $status = 400, string|null $code = null)

Return an error response

string|null
inputString(array $input, string $key, string|null $default = null)

Extract a string value from input array.

int|null
inputInt(array $input, string $key, int|null $default = null)

Extract an integer value from input array.

bool|null
inputBool(array $input, string $key, bool|null $default = null)

Extract a boolean value from input array.

int|null
inputIntFromBool(array $input, string $key, int|null $default = 0)

Extract an integer value that also accepts JSON booleans and "true"/"false" strings.

string
inputTemplate(array $input)

Extract a template ID from input. Accepts string IDs, "none", and JSON integers.

void
authenticateApiRequest()

Authenticate the API request using API key or HTTP Basic auth

void
refuseArrayQueryParameters()

Answer 400 when a query parameter arrives as an array; see arrayQueryParameter().

string|null
getApiKeyFromRequest()

Get API key from request headers

bool
requiresAuthentication()

Check if the current API endpoint requires authentication Can be overridden in child classes for public endpoints

string
zoneEditDeniedMessage(string|null $zoneType)

Pick the right "cannot edit this zone's records" message: read-only zones (Secondary, Consumer) replicate from a primary and are rejected for a different reason than a missing edit permission. Keeps the public error contract accurate for both cases.

JsonResponse|null
refuseWhenChangeRequestRequired(ApiPermissionService $permissions, int $userId, int $zoneId)

The 403 a direct write gets when the caller's changes to the zone are routed through change requests, or null when the write may proceed.

int
getAuthenticatedUserId()

Get the authenticated user ID (stateless)

string
getAuthenticatedUsername()

No description

getApiKeyScope()

Get the permission scope of the API key for this request. Requests without a key scope (Basic auth, or an unresolvable key) are treated as unrestricted.

void
enforceApiKeyMethodScope()

Reject the request with 403 when the API key's read-only/operation scope does not permit the HTTP method. This is a request-global gate.

bool
acceptsZoneRestrictedKey()

Whether an API key restricted to specific zones may call this endpoint.

array
requiredApiKeyOperations()

Operations the current request performs, all of which the API key must permit. Defaults to the single operation implied by the HTTP method.

never
sendApiKeyOperationForbidden()

Send a 403 for an operation the API key may not perform, and stop.

JsonResponse|null
enforceApiKeyZoneScope(int $zoneId)

Guard a zone-scoped endpoint against the API key's zone restriction.

Closure|null
apiKeyZoneGate()

The key's zone scope as a check for a write that lands outside the requested zone, such as the PTR of an A record; null for a key with no zone restriction.

array
pagingParameters()

The opt-in paging parameters of a list request, see ListPaging.

bool
anyQueryParameter(array $parameters)

Whether the request sets any of these query parameters to a non-empty value; an empty filter keeps every row, the same as an absent one.

array
queryNeedles(array $parameters)

The non-empty query parameters among these, for a repository pre-filter.

array
filterSortAndPage(array $rows, ListSort $sort, array $filters)

Filter, sort and page a list that is already loaded. Each filter maps a query parameter to the values of a row it searches as a case-insensitive substring.

string
formatV2RecordContent(string $type, string $content)

Apply V2 record-content formatting.

string
recordWriteErrorMessage(RecordWriteResult $result, string $backendFailureText)

The API wording for a refused record write: a duplicate and a backend fault keep the contract strings, a backend verdict appends its reason to them, every other refusal carries the manager's reason.

bool
recordBelongsToZone(array $record, int $zoneId, string|null $zoneName)

Whether a record fetched by id belongs to the zone addressed in the path.

string
normalizeV2RecordName(string $name, string $zoneName)

Record names are stored as punycode and always carry the zone suffix.

string
stripTxtQuotes(string $content, string $type)

Strip quotes from single-string TXT records for V2 API responses

JsonResponse
handleException(Throwable $e, string $context, string $userMessage = 'An error occurred', int $statusCode = 500)

Handle exception and return JSON error response

Details

void run()

Handle user-related requests

Return Value

void

__construct(array $request, array $pathParameters = [])

PublicApiController constructor

Parameters

array $request
array $pathParameters

Optional path parameters for RESTful routes

static void bindContext(BootContext $context)

Hands the booted context to every controller the router builds.

Controllers are constructed by class name with request data only, so the router cannot pass it through their constructors.

Parameters

BootContext $context

Return Value

void

static protected BootContext requestContext()

No description

Return Value

BootContext

protected bool requiresCsrfValidation()

API requests authenticate per call with an API key or HTTP Basic credentials, so there is no session-bound form token to check.

Return Value

bool

bool isPost()

Checks if the current request is a POST request.

Return Value

bool

True if the request method is POST, false otherwise.

ConfigurationInterface getConfig()

Gets the application configuration.

Return Value

ConfigurationInterface

The application configuration.

protected mixed getModuleConfig(string $module, string $key, mixed $default = null)

Get a module config value with legacy fallback.

Checks modules.. first, then falls back to . for backward compatibility with pre-module config layouts.

Parameters

string $module
string $key
mixed $default

Return Value

mixed

void render(string $template, array $params)

Renders a template with the given parameters.

Parameters

string $template

The template to render.

array $params

The parameters to pass to the template.

Return Value

void

protected PdnsCapabilities getPdnsCapabilities()

API requests carry no session, so the PowerDNS version is fetched on first use and kept for this request only. Callers ask lazily (a zone create with a catalog kind), so ordinary requests never pay for the lookup.

Return Value

PdnsCapabilities

protected array|null getCachedPdnsServerInfo(bool $allowExpired = true)

The server info the page chrome reads for zone type labels. Never calls PowerDNS (it renders on login pages too); the dashboard refreshes the cache, and an expired entry still beats flipping labels to Master/Slave.

The dashboard passes false so its version badge shows only a fresh value.

Parameters

bool $allowExpired

Return Value

array|null

protected PdnsCapabilities|null getRecordTypeCapabilities()

Capabilities to use when filtering selectable record types, or null to skip version filtering entirely.

Only API backends can report their PowerDNS version, so only there can we safely hide types a newer-than-server feature would need. On SQL backends the version is never known, and strict-unknown filtering would wrongly and permanently drop valid types (HTTPS, SVCB, ZONEMD, ...) from every form. Returning null there leaves the configured/default type list intact.

Return Value

PdnsCapabilities|null

protected void refreshPdnsCapabilities()

Trigger a session-cached refresh of PowerDNS version + capabilities.

Runs in both API and SQL backend modes: the version display is useful in either mode whenever pdns_api is configured, even though capability gates only matter for API mode. getPdnsCapabilities() calls this itself once the cached entry expires; call it directly only where a fresh version matters more than the cached one - e.g. the dashboard.

Return Value

void

void validateCsrfToken()

Validates the CSRF token from the request.

Return Value

void

void redirect(string $url, array $args = [])

Redirects to a specified URL with optional arguments.

Automatically prepends base_url_prefix for subfolder deployments.

Parameters

string $url

The URL to redirect to.

array $args

The arguments to pass as query parameters.

Return Value

void

void setMessage(string $script, string $type, string $content)

Sets a message to be displayed for a specific script.

Parameters

string $script

The script to set the message for.

string $type

The type of message (error, warning, success, info).

string $content

The content of the message.

Return Value

void

array|null getMessages(string $script)

Gets messages for a specific script.

Parameters

string $script

The script to get messages for.

Return Value

array|null

The messages for the script, or null if no messages are set.

void checkCondition(bool $condition, string $errorMessage)

Checks a condition and displays an error message if the condition is true.

Parameters

bool $condition

The condition to check.

string $errorMessage

The error message to display if the condition is true.

Return Value

void

protected never halt(string $kind, string $target)

Ends the request after its response was written; see RequestHalted.

Parameters

string $kind
string $target

Return Value

never

protected never sendAndHalt(Response $response)

Sends a prepared response and ends the request; the API controllers answer every request this way.

Parameters

Response $response

Return Value

never

protected ControllerServiceFactory services()

Lazily builds the shared service factory so per-request memoized instances (backend provider, permission cache) span all accessors.

protected ModuleServices moduleServices()

The same factory narrowed to the module SDK contract; module controllers use this instead of services() so their core dependencies stay declared.

Return Value

ModuleServices

protected BackendCapabilitiesInterface backendCapabilities()

The request's backend as a capability probe; controllers ask it what the backend can do instead of re-reading dns.backend from the configuration.

protected bool isApiBackend()

Backend identity, for the few places that report it rather than act on a capability (the is_api_backend template variable, the API error banner).

Return Value

bool

protected array apiEscapeRiskRrsets(int $zoneId, string $zoneName)

RRsets of the zone that PowerDNS Auth 5.1.x would alter on the next API write to them; always empty on the SQL backend, which writes content as entered.

Parameters

int $zoneId
string $zoneName

Return Value

array

protected int resolveRowsPerPage(int $fallback = PaginationService::DEFAULT_ROWS_PER_PAGE)

Resolves the page size for a listing: the request value wins, then the user's stored preference, then interface.rows_per_page with $fallback when unset.

Parameters

int $fallback

Return Value

int

protected string presentPagination(int $totalItems, int $itemsPerPage, string $path, array $queryParams = [])

Renders a pagination widget for a paginated listing.

$path is the route with the {PageNumber} placeholder already in place (e.g. '/zones/forward?start={PageNumber}'). $queryParams are extra key => value pairs appended urlencoded, in order, skipping absent and blank values.

Parameters

int $totalItems
int $itemsPerPage
string $path
array $queryParams

Return Value

string

protected array paginationVariables(int $totalItems, int $itemsPerPage, string $path, array $queryParams = [])

The pagination (pre-rendered string, kept for theme forks) and pagination_items (for _partials/pagination.html) template variables for a paginated listing.

Parameters

int $totalItems
int $itemsPerPage
string $path
array $queryParams

Return Value

array

protected ZoneSortingService createZoneSortingService()

No description

Return Value

ZoneSortingService

protected bool hasPermission(string $permission)

Check if the logged-in user has the given permission

Parameters

string $permission

Return Value

bool

protected string getViewPermissionLevel()

The logged-in user's zone view level: "all", "own" or "none"

Return Value

string

protected bool canViewZones()

Whether the logged-in user may view any zone; flashes the refusal when not, so a zone lookup skipped on "none" still explains itself on the page.

Return Value

bool

protected bool isZoneOwner(int $zoneId)

Check if the logged-in user owns the given zone directly or via group membership

Parameters

int $zoneId

Return Value

bool

protected void requireZoneView(int $zoneId)

Stops the request unless the current user may open the zone (showError exits).

Parameters

int $zoneId

Return Value

void

protected ZoneManagementService createZoneManagementService()

The zone service the API uses, told what the connected server supports.

Return Value

ZoneManagementService

protected ZoneCreateService createZoneCreateService()

The zone creation flow of the add-zone forms, told what the connected server supports.

Return Value

ZoneCreateService

protected ModuleRegistry moduleRegistry()

The request's loaded module registry.

Return Value

ModuleRegistry

protected array moduleCapabilityData(string $capability, array $context = [])

What the enabled modules offer for a capability (wizard actions, lookup links, export formats), honouring each module's admin restriction.

Parameters

string $capability
array $context

Placeholders for the modules' url patterns, e.g. zone_id

Return Value

array

protected bool moduleProvides(string $capability)

No description

Parameters

string $capability

Return Value

bool

protected ChangeApprovalContext changeApproval()

Change-approval answers for one user and zone; callers pass the user id, usually getCurrentUserId(). One instance per request.

Return Value

ChangeApprovalContext

protected string changeApprovalModeForZone(int $zoneId)

No description

Parameters

int $zoneId

Return Value

string

protected Closure directWriteZoneGate()

For a write that lands in a zone other than the one the request was checked against, such as the PTR of an A record: refused where the user must file a change request.

Return Value

Closure

protected int|null getCurrentUserId()

Get current user ID

Return Value

int|null

protected array assignableOwners(array $users)

The users a new zone may be given to: everyone when the create path lets the caller assign other owners and they may see other users, otherwise only the current user.

Parameters

array $users

Rows with an 'id' key

Return Value

array

protected int|string preservedOwnerChoice(array $assignableOwners, mixed $ownerInput)

The owner an add-zone form shows again after a failed submit: the posted id when the picker can offer it, '' for an explicit "no user owner", otherwise the current user.

Parameters

array $assignableOwners
mixed $ownerInput

The raw posted value, if any

Return Value

int|string

protected void reportZoneWrite(string $script, ZoneWriteResult $result, string $successMessage)

Flash the outcome of a zone write to a page: the given text on success, the result's reason on failure.

Parameters

string $script
ZoneWriteResult $result
string $successMessage

Return Value

void

protected SessionInterface session()

The request's session, shared by every collaborator this controller builds.

Return Value

SessionInterface

protected UserContextService getUserContextService()

Get the user context service

Return Value

UserContextService

protected void setCurrentPage(string $page)

Sets the current page identifier used for navigation highlighting.

Parameters

string $page

The page identifier

Return Value

void

protected void setPageTitle(string $title)

Sets the page title displayed in the header.

Parameters

string $title

The page title

Return Value

void

void checkPermission(string $permission, string $errorMessage)

Checks if the user has a specific permission and displays an error message if not.

Parameters

string $permission

The permission to check.

string $errorMessage

The error message to display if the user does not have the permission.

Return Value

void

protected int requireNumericParam(string $name, string|null $error = null)

Reads a positive integer request parameter (an id) or ends the request with an error page.

Parameters

string $name
string|null $error

Return Value

int

protected void renderNotFound()

Answers with the standard 404 page, for routes whose feature is switched off.

Return Value

void

void showError(string $error, string|null $recordName = null)

Displays an error message.

Parameters

string $error

The error message to display.

string|null $recordName

Optional record name for context

Return Value

void

array getRequest()

Gets the request data.

Return Value

array

The request data.

string getSafeRequestValue(string $key)

Gets a safe value from the request data.

Parameters

string $key

The key to get the value for.

Return Value

string

The safe value.

void setValidationConstraints(array $constraints)

Sets validation constraints for the request data.

Parameters

array $constraints

The validation constraints.

Return Value

void

bool doValidateRequest(array|null $data = null)

Validates the request data.

Parameters

array|null $data

Optional data to validate. If not provided, uses $this->requestData

Return Value

bool

True if the request data is valid, false otherwise.

void showFirstValidationError(array|null $data = null)

Displays the first validation error.

Parameters

array|null $data

Optional data to validate. If not provided, uses $this->requestData

Return Value

void

void addSystemMessage(string $type, string $content)

Adds a system-wide message that will be displayed on any page

Parameters

string $type

The type of message (error, warning, success, info)

string $content

The content of the message

Return Value

void

protected string|null arrayQueryParameter()

The first query parameter sent as an array (?type[]=A), or null when every value is a scalar. InputBag::get() throws on such a value, so subclasses refuse the request before any handler reads the query. The key is scrubbed to valid UTF-8 because it is echoed in a JSON error.

Return Value

string|null

protected bool isJsonRequest()

Checks if the current request is a JSON request

Return Value

bool

True if the request is JSON, false otherwise

protected array|null getJsonInput()

Get JSON input from request body

Return Value

array|null

Decoded JSON data or null if invalid

protected array|null getValidatedJsonBody()

Get the JSON request body validated as a non-empty array.

Same sources as {\Poweradmin\Application\Controller\Api\self::getJsonInput()} (JSON body with a form-data fallback), but scalar bodies like "abc" or 42 - which decode to non-array values - are rejected as well, so callers can safely use array access on the result. Returns null for anything but a non-empty array.

Return Value

array|null

Decoded body array, or null when missing or invalid

protected JsonResponse returnJsonResponse(mixed $data, int $status = 200, array $headers = [])

Override to record every public API response in the audit log

Parameters

mixed $data

The data to return

int $status

HTTP status code

array $headers

Additional headers to include

Return Value

JsonResponse

protected JsonResponse returnApiResponse(mixed $data, bool $success = true, string|null $message = null, int $status = 200, array $additionalFields = [], array $headers = [])

Wrapped API response: {success, data, message?} plus any additional top-level fields.

Parameters

mixed $data

The data to return

bool $success
string|null $message
int $status
array $additionalFields

Extra top-level fields (pagination, meta)

array $headers

Additional response headers

Return Value

JsonResponse

protected JsonResponse returnApiError(string $message, int $status = 400, mixed $data = null, array $additionalFields = [], array $headers = [])

Wrapped API error: {success: false, data, message} plus any additional top-level fields.

Parameters

string $message
int $status
mixed $data

Additional error data

array $additionalFields

Extra top-level fields (meta)

array $headers

Additional response headers

Return Value

JsonResponse

protected JsonResponse methodNotAllowed(array $allowedMethods)

Return a 405 Method Not Allowed error with the RFC 9110 Allow header listing the methods the endpoint supports.

Parameters

array $allowedMethods

HTTP methods the endpoint handles (e.g. ['GET', 'POST'])

Return Value

JsonResponse

protected JsonResponse returnErrorResponse(string $message, int $status = 400, string|null $code = null)

Return an error response

Parameters

string $message

Error message

int $status

HTTP status code

string|null $code

Optional error code

Return Value

JsonResponse

protected string|null inputString(array $input, string $key, string|null $default = null)

Extract a string value from input array.

Returns default only when key is absent; returns null for present non-string values so callers can distinguish missing from malformed.

Parameters

array $input
string $key
string|null $default

Return Value

string|null

protected int|null inputInt(array $input, string $key, int|null $default = null)

Extract an integer value from input array.

Accepts integers and integer-shaped strings. Returns default when key is absent; returns null for present non-integer values.

Parameters

array $input
string $key
int|null $default

Return Value

int|null

protected bool|null inputBool(array $input, string $key, bool|null $default = null)

Extract a boolean value from input array.

Accepts native booleans, int 1/0, and strings "1"/"0"/"true"/"false" for form-encoded compatibility. Returns default when key is absent; returns null for present invalid values.

Parameters

array $input
string $key
bool|null $default

Return Value

bool|null

protected int|null inputIntFromBool(array $input, string $key, int|null $default = 0)

Extract an integer value that also accepts JSON booleans and "true"/"false" strings.

Use for fields like 'disabled' where the API documents boolean but stores as int. Returns default when key is absent; returns null for present invalid values.

Parameters

array $input
string $key
int|null $default

Return Value

int|null

protected string inputTemplate(array $input)

Extract a template ID from input. Accepts string IDs, "none", and JSON integers.

Rejects booleans, arrays, and non-numeric strings.

Parameters

array $input

Return Value

string

protected void authenticateApiRequest()

Authenticate the API request using API key or HTTP Basic auth

Return Value

void

protected void refuseArrayQueryParameters()

Answer 400 when a query parameter arrives as an array; see arrayQueryParameter().

Return Value

void

protected string|null getApiKeyFromRequest()

Get API key from request headers

Return Value

string|null

The API key or null if not found

protected bool requiresAuthentication()

Check if the current API endpoint requires authentication Can be overridden in child classes for public endpoints

Return Value

bool

True if authentication is required, false otherwise

protected string zoneEditDeniedMessage(string|null $zoneType)

Pick the right "cannot edit this zone's records" message: read-only zones (Secondary, Consumer) replicate from a primary and are rejected for a different reason than a missing edit permission. Keeps the public error contract accurate for both cases.

Parameters

string|null $zoneType

Zone kind (MASTER, SLAVE, NATIVE, CONSUMER) when known

Return Value

string

Error message describing why record edits are not allowed

protected JsonResponse|null refuseWhenChangeRequestRequired(ApiPermissionService $permissions, int $userId, int $zoneId)

The 403 a direct write gets when the caller's changes to the zone are routed through change requests, or null when the write may proceed.

Parameters

ApiPermissionService $permissions
int $userId
int $zoneId

Return Value

JsonResponse|null

protected int getAuthenticatedUserId()

Get the authenticated user ID (stateless)

Return Value

int

The authenticated user ID or 0 if not authenticated

protected string getAuthenticatedUsername()

No description

Return Value

string

protected ApiKeyScope getApiKeyScope()

Get the permission scope of the API key for this request. Requests without a key scope (Basic auth, or an unresolvable key) are treated as unrestricted.

Return Value

ApiKeyScope

The resolved scope, never null

protected void enforceApiKeyMethodScope()

Reject the request with 403 when the API key's read-only/operation scope does not permit the HTTP method. This is a request-global gate.

Return Value

void

protected bool acceptsZoneRestrictedKey()

Whether an API key restricted to specific zones may call this endpoint.

Endpoints not tied to any zone override this to refuse such keys.

Return Value

bool

protected array requiredApiKeyOperations()

Operations the current request performs, all of which the API key must permit. Defaults to the single operation implied by the HTTP method.

Override for endpoints where the method is not a 1:1 operation mapping: return the exact set (e.g. [create, update] for an upsert), or [] to skip the central check and enforce the operation scope inside the handler.

Return Value

array

protected never sendApiKeyOperationForbidden()

Send a 403 for an operation the API key may not perform, and stop.

Return Value

never

protected JsonResponse|null enforceApiKeyZoneScope(int $zoneId)

Guard a zone-scoped endpoint against the API key's zone restriction.

Returns a 403 response when the zone is out of scope, or null when allowed. Callers return the response directly: if (($r = $this->enforceApiKeyZoneScope($id)) !== null) { return $r; }

Parameters

int $zoneId

The zone (domain) ID the request targets

Return Value

JsonResponse|null

A 403 response, or null when the zone is in scope

protected Closure|null apiKeyZoneGate()

The key's zone scope as a check for a write that lands outside the requested zone, such as the PTR of an A record; null for a key with no zone restriction.

Return Value

Closure|null

protected array pagingParameters()

The opt-in paging parameters of a list request, see ListPaging.

Return Value

array

[page, perPage]; perPage 0 means "return everything"

protected bool anyQueryParameter(array $parameters)

Whether the request sets any of these query parameters to a non-empty value; an empty filter keeps every row, the same as an absent one.

Parameters

array $parameters

Return Value

bool

protected array queryNeedles(array $parameters)

The non-empty query parameters among these, for a repository pre-filter.

Parameters

array $parameters

Return Value

array

protected array filterSortAndPage(array $rows, ListSort $sort, array $filters)

Filter, sort and page a list that is already loaded. Each filter maps a query parameter to the values of a row it searches as a case-insensitive substring.

Parameters

array $rows
ListSort $sort
array $filters

Return Value

array

[rows of the page, extra response fields]

protected string formatV2RecordContent(string $type, string $content)

Apply V2 record-content formatting.

V2 always quotes single-string TXT records, even when dns.txt_auto_quote is off, so records round-trip: create quotes, read strips, update must re-quote. Used by create and update paths alike so stored content stays consistent.

Parameters

string $type
string $content

Return Value

string

protected string recordWriteErrorMessage(RecordWriteResult $result, string $backendFailureText)

The API wording for a refused record write: a duplicate and a backend fault keep the contract strings, a backend verdict appends its reason to them, every other refusal carries the manager's reason.

Parameters

RecordWriteResult $result
string $backendFailureText

Return Value

string

protected bool recordBelongsToZone(array $record, int $zoneId, string|null $zoneName)

Whether a record fetched by id belongs to the zone addressed in the path.

A zone moved from SQL to API backend mode can be addressed by its zones row id while its records carry the canonical id, so the zone's own canonical id (resolved by name, the way the record lookup resolves it) also matches.

Parameters

array $record
int $zoneId
string|null $zoneName

Return Value

bool

protected string normalizeV2RecordName(string $name, string $zoneName)

Record names are stored as punycode and always carry the zone suffix.

Parameters

string $name
string $zoneName

Return Value

string

protected string stripTxtQuotes(string $content, string $type)

Strip quotes from single-string TXT records for V2 API responses

V2 responses present single-string TXT content unquoted regardless of stored form (zone records are force-quoted on write, template records only when dns.txt_auto_quote is on). Multi-string TXT records (e.g., "part1" "part2") are preserved as-is since they represent long values split across multiple strings.

Parameters

string $content

The TXT record content from database

string $type

The record type

Return Value

string

The formatted content (quotes stripped for single-string TXT records)

protected JsonResponse handleException(Throwable $e, string $context, string $userMessage = 'An error occurred', int $statusCode = 500)

Handle exception and return JSON error response

Catches all throwables (Exception, TypeError, Error, etc.) and returns a proper JSON error response instead of letting PHP display HTML errors. Logs detailed error information for debugging.

Parameters

Throwable $e

The exception/error to handle

string $context

Context description (e.g., method name)

string $userMessage

User-friendly error message

int $statusCode

HTTP status code

Return Value

JsonResponse

JSON error response