21 Commits
Author SHA1 Message Date
Adam Daley 3ce592cf86 Harden DirectAdmin GET requests and assertions (#4628)
* Harden DirectAdmin GET requests and assertions

- Avoid trailing ? on empty GET query strings
- Send only user param for CMD_API_SHOW_USER_CONFIG
- Assert exact user key and package path in tests

* Address review nits: exact query and no-query assertions
2026-11-03 18:42:27 +00:00
ToniandAdam Daley ebd84bfaf0 Send GET-only DirectAdmin API commands as GET requests (#4626)
Co-authored-by: Adam Daley <admdly@users.noreply.github.com>
2026-11-03 18:00:50 +00:00
Adam Daley e5fc354f39 Fix partial WHM provisioning recovery and password persistence (#4584)
* Fix recovery from partial WHM provisioning

* Add upgrade coverage for hosting provisioning state

* Reconcile interrupted WHM account creation before retrying

* Verify WHM account identity before resuming reseller setup
2026-10-26 04:36:34 +00:00
Adam Daley 5783431f08 Fix WHM package exception classification (#4583) 2026-10-26 02:36:22 +00:00
Santiago RodríguezandAdam Daley 08298dc89a Add auto-login support to the CWP server manager (#4459)
* Refactor getLoginUrl and improve error handling

* Replace error_log with getLog()->error

* Fall back to the plain CWP panel URL when SSO is unavailable

The auto-login endpoint is not available on every API key, so a failed
user_session request must not break the login button. Mirror the WHM
manager: log the failure (username only, never tokens or URLs) and
return the standard panel URL.

Use timer 5, matching CWP's own WHMCS module, and cover the new
behaviour with unit tests.

* Reuse the CWP test manager helper for the session request test

* Handle transport failures and non-string session URLs in CWP login

Convert Symfony transport exceptions into Server_Exception so every
CWP request failure funnels through the plain panel URL fallback,
and only accept a non-empty string session URL.

* Bound the CWP session request duration

Limit user_session to 10s idle / 20s total so a hung panel fails fast
to the manual login URL instead of stalling the page load. Other
operations keep the existing client defaults.

---------

Co-authored-by: Adam Daley <admdly@users.noreply.github.com>
2026-10-22 22:15:18 +01:00
Adam Daleyandopencode 40de3ca924 Fix WHM activation failing when package already exists (#4565)
* Fix WHM activation failing when package already exists (#4560)

- Request listpkgs with want=all and parse both legacy package and modern data.pkg shapes
- On addpkg already-exists, sync via editpkg and continue; only block activation on other errors
- Include WHM reason in Server_Exception and fix placeholder keys

* Add WHM API terms to spellcheck dictionary

* Address review: use typed exception for WHM already-exists, keep reasons out of API errors

- Add Server_PackageExistsException; request() now throws generic messages and translates already-exists at the WHM boundary
- checkPackageExists() catches the typed exception instead of matching getMessage()

* Fix Rector findings: remove unused catch variable and setAccessible call

* Handle modern WHM metadata.result failures in Whm::request

---------

Co-authored-by: opencode <opencode@localhost>
2026-10-22 17:03:35 +01:00
Adam Daley 95243bf71d Keep DirectAdmin POST fields out of request URLs (#4490) 2026-10-06 21:21:53 +01:00
Adam Daley 30faee0c64 Fix CWP suspension error message placeholders (#4487) 2026-10-06 20:38:11 +01:00
Adam Daley cac9e8b383 Validate missing Hestia hostnames before building API URLs (#4470)
Validate Hestia hostname before using the server manager
2026-10-04 19:00:12 +01:00
Adam Daley c3b5631e7d Handle invalid CWP responses and malformed PayPal IPNs (#4464)
* Handle invalid CWP responses and malformed PayPal IPNs

Reject incomplete CWP account details before synchronization and explain empty or invalid JSON responses. Reject array-valued PayPal IPN fields before verification.

Add regression coverage for failed responses, unchanged accounts and valid synchronization. Addresses FOSSBILLING-NN9, FOSSBILLING-CMA, FOSSBILLING-NJD, FOSSBILLING-JEW and FOSSBILLING-QMT.

* Report CWP HTTP failures separately from invalid JSON
2026-10-04 16:25:04 +01:00
Adam Daley 1432a7cf79 Simplify core library hot paths (#4365)
* Simplify core library hot paths

* Address review feedback
2026-09-19 18:18:22 +01:00
Adam Daley 0d8a236eaf Fix Sentry-reported order validation and cPanel sync issues (#4362)
- Servicedomain order validation accepts the product context like
  its sibling service modules, fixing a TypeError when ordering
  domains through the cart. Fixes FOSSBILLING-Q0R.
- cPanel account sync casts the suspended flag to bool, fixing a
  TypeError when the API reports it as an integer.
  Fixes FOSSBILLING-N3T.
2026-09-19 16:09:13 +01:00
Adam Daley de116893b6 Harden URL, mail, and locale handling (#4359)
* Harden URL, mail, and locale handling

* Keep Sentry hardening mail test offline with unsupported DSN scheme

* Move Sentry regression tests into their module test files

* Drop Sentry issue IDs from regression test names

* Drop Sentry issue ID from Twig cache failure test name

* Drop Sentry issue IDs from test comments
2026-09-19 13:49:38 +01:00
Adam Daley 28d7b70bf8 Preserve Plesk credentials during package updates (#4325)
🐛 Preserve Plesk credentials during package updates
2026-09-15 22:36:59 +01:00
Adam Daley c8439d0dc7 Fix Server_Manager::getPasswordLength() TypeError on non-int config (#4268)
Fix TypeError in Server_Manager::getPasswordLength()

_config['passwordLength'] is untyped, and stored server configs have
been observed carrying a numeric string rather than an int, which
violates this method's declared int return type. Cast defensively at
the boundary.

Confirmed live via Sentry (FOSSBILLING-MZA): ~7,800 occurrences since
June, still ongoing, always fatal to the whole cron run because the
error propagates out of order activation uncaught.
2026-09-02 16:46:32 +01:00
Adam Daley 7bf99170b9 Fix WHM reseller accounts always getting an empty ACL list (#4174)
* Fix WHM reseller accounts always getting an empty ACL list

Server_Manager_Whm::createAccount() called $package->getAcllist(),
but Server_Package has never had that method. Its __call() silently
swallows calls to undefined methods and returns '', so every reseller
account created via WHM had setacls called with an empty acllist -
wiping out the initial privileges WHM had just granted via
setupreseller.

Wire the ACL list through the existing custom-value mechanism instead
(same pattern already used for cgi, cpmod, maxlst, hasshell), and skip
the setacls call entirely when the hosting plan defines no 'acl'
custom value, so the reseller keeps WHM's default privileges instead
of having them stripped.

Fixes #4173

* Whitelist WHM API action names for cspell

src/library/Server/Manager/** is excluded from spellcheck entirely, so
Whm.php's own use of these WHM API1 action/field names never tripped
the checker. tests/Unit/Server/Manager/WhmTest.php isn't under that
exclusion, so add the same words DirectAdmin's custom-value keys
already use this pattern for (listpkgs, addpkg, createacct,
setupreseller, setacls, acllist) to cspell.json's ignoreWords.
2026-08-16 20:29:08 +01:00
Harshvardhan AgrawalandAdam Daley 71a2724da4 Plesk module creates the account, the subscription but the plan i... (#4125)
* Plesk module creates the account, the subscription but the plan i...

Server_Manager_Plesk::createSubscriptionProps() built the webspace add/set request from custom hosting/limits/permissions values but never told Plesk which service plan to assign, so accounts were provisioned outside of any named plan; the fix adds the FOSSBilling hosting plan's name as the request's plan-name, matching how every other adapter (CWP, DirectAdmin, Hestia, WHM) already passes the package name through.

* 🐛 Don't send plan-name on Plesk subscription updates

CodeRabbit correctly flagged that Plesk's webspace/set schema has no plan-related
node at all (unlike add, which supports plan-name/plan-id/plan-guid/plan-external-id).
Sending plan-name through changeAccountPackage() -> webspace/set would have hit the
same XSD-sequence rejection as the original ordering bug, just for a different
reason -- an element Plesk doesn't recognize there at all.

plan-name now stays scoped to the add path only, matching the same conditional
pattern already used for htype. Changing an existing subscription's assigned plan
isn't supported by this code path; that would need a separate switch-subscription
operation (plan-guid/plan-external-id), which is out of scope for #2799.

---------

Co-authored-by: Adam Daley <admdly@users.noreply.github.com>
2026-08-16 17:35:50 +01:00
Adam Daley b364588f30 Harden release cleanup and updater integrity (#4166) 2026-08-15 15:27:15 +01:00
Adam Daley 6a5ea98f57 Fix Plesk subscription updates sending an invalid webspace 'set' request (#4129)
* 🐛 Fix Plesk subscription updates sending an invalid webspace 'set' request

Server_Manager_Plesk::createSubscriptionProps() built the same gen_setup/hosting/limits/permissions
payload for both the 'add' and 'set' webspace operations and placed it directly under the action
node. Plesk's API requires 'set' requests to wrap those settings in <filter> (which subscription to
update) and <values> nodes instead, and its update schema (SetGenSetupType) doesn't accept the
'htype' element that creation does. As written, every call to updateSubscription() (invoked from
changeAccountPackage() when a client's plan changes) sent Plesk a request it would reject.

This only affects the 'set' path; 'add' already matched Plesk's schema for subscription creation.

* 📝 Add 'htype' to cspell word list

src/library/Server/Manager/** is excluded from spellcheck, so this Plesk API field name was
never flagged there, but tests/Unit/Server/Manager/PleskTest.php isn't excluded and now
references it directly.

* 🐛 Filter Plesk webspace 'set' requests by domain, not owner-login

CodeRabbit correctly flagged that filtering by 'owner-login' matches every webspace
a customer owns; Plesk's 'set' operation applies the whole <values> block to every
webspace the filter matches. For a customer with more than one subscription, both
updateSubscription() (createSubscriptionProps 'set') and the pre-existing
changeAccountDomain() would silently overwrite limits/permissions/domain on
subscriptions other than the one being changed.

Filter by 'name' (the domain) instead, matching the single-subscription scoping
deleteSubscription() already uses. changeAccountDomain() now captures the current
domain before setDomain() overwrites it, since it needs the old value for the filter
and the new value for gen_setup.

Also added 'webspace'/'webspaces' to the cspell word list alongside 'htype', for the
same reason as the prior commit -- tests/Unit/Server/Manager isn't spellcheck-exempt.

* 🧹 Assert full gen_setup key order for the add action in PleskTest

Per CodeRabbit nitpick: the test checked htype's presence but not that it's
positioned between owner-login and ip_address as the docblock claims.

* 🧹 Trim redundant comment in createSubscriptionProps

The 'set' branch repeated the filter/values explanation already given in the
method's docblock; kept only the owner-login-vs-name rationale, which was the
actual new information.
2026-08-10 15:54:58 +01:00
Adam Daley adcd85f52b Improve overdue order suspension handling (#4072)
* Improve account suspension workflow

* Address suspension workflow review feedback

* Refine update patcher tests
2026-07-28 19:19:44 +01:00
137a10dbf3 Apply autofixes and update CODEOWNERS for code quality (#3959)
* Apply suggested fix to tests/Unit/FOSSBilling/RateLimiterTest.php from Copilot Autofix

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Apply suggested fix to tests/Unit/FOSSBilling/RateLimiterTest.php from Copilot Autofix

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Apply suggested fix to src/library/Server/Manager/Directadmin.php from Copilot Autofix

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Apply suggested fix to tests/Unit/FOSSBilling/Api/AbstractApiTest.php from Copilot Autofix

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Remove tests path from CODEOWNERS

* Apply suggested fix to src/modules/Order/Service.php from Copilot Autofix

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Fix uncancel emails and zero-credit payments

* Preserve orders when invoice generation fails

* Fix RateLimiterTest CI failures from prior autofix commits

- Add a public RateLimiter::getStatus() so tests can read the token
  limit for a policy without hardcoding it (getRateLimitState() now
  reuses it).
- Fix consumeOrThrow test's toThrow() call: Pest expects the assertion
  closure as the first argument (with the exception type-hinted), not
  as a second argument alongside the class string.

* Fix DirectAdmin apostrophe entity decoding to avoid trailing semicolon

str_replace('&#39', "'", $data) also matched the fully-terminated
&#39; entity, replacing just the &#39 part and leaving the semicolon
behind (e.g. "O&#39;Brien" became "O';Brien"). Replace the terminated
form first, then the legacy unterminated form, so both decode to a
plain apostrophe.

* Fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-11 16:20:59 +01:00