Commit Graph
4 Commits
Author SHA1 Message Date
Adam Daley b364588f30 Harden release cleanup and updater integrity (#4166) 2026-08-15 15:27:15 +01:00
Adam Daley 6a10ee3e9f Migrate admin identity from Model_Admin to Doctrine entity (#4136)
* Migrate admin identity from Model_Admin to Doctrine entity

* Fix admin ID access in logger when admin identity is a Doctrine entity

* Harden staff service flows and cron admin creation against race conditions
2026-08-10 23:15:24 +01:00
FOSSBilling BotandFOSSBilling Bot 3c6ab0bd4f Apply PHP Code Styling & Quality Changes (#3739)
Co-authored-by: FOSSBilling Bot <fossbilling-bot>
2026-06-09 19:17:44 +01:00
Adam Daley 5ecfeb335e Harden templates and API responses against missing data (#3730)
* Guard Activity index template against missing client/staff nested data

The activity log API at Activity/Api/Admin.php:38-49 only injects a
nested 'client' or 'staff' key when the corresponding *_id is set
(via isset() which returns false for null). System events and
client-only events therefore have no 'client' key, while staff-only
events have no 'client' key.

Under strict_variables, '{% if event.client %}' (line 191) throws
"Key 'client' ... does not exist" for any event without a client.

Switch the conditional to event.client_id / event.staff_id (always
present as SQL columns) and add '|default("")' to the nested
event.client.email / event.client.name / event.staff.email / event.staff.name
accesses so deleted-but-referenced records render gracefully.

* Guard Security iplookup template against empty country/asn data

When the GeoIP or ASN lookup fails (IncompleteRecord from MaxMind), the
Service catches and substitutes [] for the missing data. Under
strict_variables, the empty mapping at record.asn / record.country
causes line 48 to throw "sequence/mapping is empty".

Twig's |default filter on a GetAttrExpression compiles to a safe
is-defined check, so it correctly falls through to the placeholder
when the underlying array is empty or the key is missing.

Verified:
- /admin/security/iplookup?ip=192.168.1.1 (private IP, empty asn)
  renders the row with 'Unknown' ASN cells.
- /admin/security/iplookup?ip=8.8.8.8 still renders
  '15169' / 'GOOGLE' / 'United States'.

* Initialize notes/rel arrays in Support toApiArray so templates render without errors

Two related issues surfaced under strict_variables on the admin support
ticket page:

1. toApiArray only set $data['notes'] inside the foreach loop over
   ticket notes. Tickets without any notes therefore had no 'notes' key
   and 'ticket.notes|length' in mod_support_ticket.html.twig:42 threw
   'Key "notes" does not exist'.

2. _getRelDetails returned [] (empty array) when a ticket has no
   related entity. mod_support_ticket.html.twig sets {% set task =
   ticket.rel %} and then accesses task.task, task.type, task.status
   etc. on the next lines. Any access into an empty mapping throws
   under strict_variables.

Fixes:
- Service::toApiArray: always initialize $data['notes'] = [] for admin
  identity so the key is present even when empty.
- Service::_getRelDetails: always return the base structure with all
  fields (id, type, task, new_value, status) set to null when there
  is no related entity. Skip the DB lookups for the non-rel case.

Template hardening (defense-in-depth, in case other code paths return
a different shape):
- mod_support_ticket.html.twig:42: 'ticket.notes|length' -> 'is defined and
  ticket.notes|length'.
- mod_support_ticket.html.twig:222: 'ticket.notes' -> 'ticket.notes|default([])'.
- mod_support_tickets.html.twig:238: same is defined guard.

Verified on dev:
- /admin/support/ticket/1 renders without errors.
- API now returns rel as {id: null, type: null, ...} and notes: [] for
  tickets without related data.
- 1777 local tests pass, PHPStan clean.

* Harden tests and add Twig/ToApi linters

* Remove redundant setAccessible calls in tests

* Update cspell.json word list

* Add UrlAwarePermissiveContainer & trim errors

* Add geoip stub and fix ParserFactory usage

* Enforce and apply parentheses for new expressions
2026-06-06 18:32:19 +01:00