mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-25 12:37:47 +09:00
develop
1167
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4d11070ced |
feat: expose RRDtool 1.11 graph axis controls (#8356)
* feat: expose RRDtool 1.11 graph axis controls Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: constrain axis template inheritance checkboxes Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(rrd): exercise native aggregate validation and enforce coverage Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: share changed-code coverage contracts and enforce shell policy Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(ci): run request coverage with portable path matching Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: retain bootstrap coverage and validate request dispatch Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: synchronize native coverage fixture dispatch Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: measure coverage at the exact PR revision Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> |
||
|
|
f28071eb67 |
fix(theme): [develop] restore Midwinter login controls, form state and notifications (#8326)
* fix(theme): preserve Midwinter authentication controls Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs(changelog): describe theme fix Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(ui): preserve form state and notification lifecycles Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: replace the blinking timer during skin setup Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> |
||
|
|
08d0f94f24 |
Fix develop client behavior, popup isolation and PHP method visibility (#8292)
* fix(core): correct client state and declare public method contracts Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(security): isolate Midwinter external homepage popup Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(client): isolate menu state and simplify validated JavaScript guards Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(auth): use certificate expiry truth in rendering assertions Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor(js): remove unused local storage while retaining initializer calls Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(js): encode inline translated values through the shared JSON boundary Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * style(js): wrap encoded translation literals without changing messages Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(client): remove unused checkbox reads from graph resizing Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor(client): declare repeated function locals once Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(ui): run column width regression in browser CI Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> |
||
|
|
9899fdf221 |
fix(installer): allow forced replays after LDAP migration (#8317)
* fix(installer): preserve forced replay on already upgraded schemas Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs(changelog): record forced upgrade replay repair Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> |
||
|
|
06c32ab13f |
fix(installer): preserve upgrade state and propagate failures (#8266)
* fix(installer): preserve upgrade state and propagate failures Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs(changelog): record installer recovery fixes for #8266 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(security): refresh installer sink inventories Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(installer): retain merge precedence for browser parameters Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(installer): defer debug settings until the process lock is held Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(installer): enforce table conversion failures and postconditions Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(installer): refresh SQL audit baseline after clause refactor Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> |
||
|
|
1b9460be99 |
ci: reuse Cacti dependency caches and cover dependency changes (#8246)
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> |
||
|
|
135d31e406 |
issue: make the 1.3.0 upgrade resilient when user_domains tables are already removed (#8190)
* issue: make the 1.3.0 upgrade resilient when user_domains tables are already removed A forced upgrade re-run (e.g. --forcever) after the login_providers migration has dropped user_domains/user_domains_ldap previously failed hard on the raw ALTER TABLE statements, because the guards only checked db_column_exists() (which returns false when the whole table is gone). Guard the three ADD COLUMN statements with db_table_exists(), and make ldap_convert_1_3_0() return early when user_domains_ldap no longer exists, so the MODIFY COLUMN and subsequent legacy operations are skipped. The upgrade now tolerates the re-run as intended. * changelog: 8190.issue fragment for upgrade resilience fix * Add regression coverage for the 1.3.0 user_domains table guards --------- Co-authored-by: TheWitness <TheWitness@users.noreply.github.com> Co-authored-by: TheWitness <copilot@users.noreply.github.com> |
||
|
|
5403cf75bb |
feature: user-defined filter layouts with administrator publishing (#8185)
* feature: user-defined filter layouts with administrator publishing
Add a render_layouts toggle to the CactiTableFilter class so list pages
offer Save / New / Rename / Delete controls for saved filter layouts,
modelled on the monitor plugin. Layouts persist to a new user_layouts
table (id, user_id, page, name, url). The toggle defaults on for every
page except the Console > Presets pages (CDEFs, VDEFs, Colors, GPRINTs,
Data Profiles, SNMP), where it is off.
Each user manages their own layouts from any filter. Administrators who
hold the Settings/Utilities realm can publish a layout to all users
(moving it to user_id = 0) from the filter, and from a new
Console > Presets > Filters management page (layouts.php) where they can
view and manage every user's layouts. Published layouts appear for all
users on that page.
Layout create/rename/delete/publish requests are handled centrally by
filter_layouts_handle_request(), invoked from include/auth.php after the
user has been authorized for the current page, so the controls work on any
filter page while publishing stays gated on the realm. layouts.php itself
is mapped to the Settings/Utilities realm so the Presets > Filters menu
pick is administrator-only.
Schema added to cacti.sql, install/upgrades/1_3_0.php, and
docs/audit_schema.sql. Adds tests/Unit/FilterLayoutsTest.php covering the
URL-safety validation, preset-page defaults, and the request/permission
wiring.
* changelog: 8185.feature fragment for filter layouts
* chore: enforce LF line endings via .gitattributes and normalize new files
* fix: address review - attribute-escape layout options, honor failed/non-editable layout ops, regenerate cacti.pot, move test to Unit/Ui/Html
* chore: renormalize pre-existing CRLF files to LF per .gitattributes
* chore: keep vendored JS (big.js, tablesorter) byte-identical to upstream and exclude from LF normalization
* style: apply php-cs-fixer formatting and resolve PHPStan level 8 findings in filter layouts
* ci: re-trigger checks on current head
* refactor: store filter layouts as a versioned JSON document in a TEXT data column
Replace the user_layouts.url varchar(1024) column with a data TEXT column holding a versioned {version,page,filter} document. The navigable url is rebuilt server-side from the document (page basename + rebuilt query) plus the filter_layout marker, removing the stored-url redirect/XSS surface and making the backend forward-compatible with a structured query builder without a further schema change.
* feature: add Modern/Legacy page filter format with an Edit dialog for filters
Modern (default) collapses the filter to a Layouts selector plus an Edit dialog holding the Filter Name and all filter fields; Save and Publish live in the dialog, with Edit/Rename/Delete/Save As on the bar and time controls/refresh kept on the bar. Legacy keeps the classic inline filter. The format is a per-user General setting (page_filter_format). Field rendering is factored into emit_field() so both layouts share one source.
* fix: render page-action buttons (import, export, sort) on the modern filter bar
Import, Export, Purge, Save, and sort ascending/descending are page actions, not filter inputs, so they render on the bar beside the Layouts selector instead of inside the Edit dialog. Only go/clear are dropped from the bar; the dialog keeps the Search/Clear footer.
* feature: render the modern Edit dialog one filter variable per row
The dialog no longer mirrors the inline filter's multi-field row packing; each filter variable renders on its own row (label + control) for a clean, responsive form. Hidden/validate fields stay in the form without an empty visible row. Rows carry a cactiFilterEditRow hook for label-column alignment styling.
* feature: align the modern edit dialog one-per-row and skin it per theme
Render all dialog fields in a single table so the label column aligns via the existing table-cell layout. Tag the dialog widget with cactiFilterDialog and add a scoped skin to the modern theme (slate titlebar, styled button pane, accented primary action). Add a bar separator between layout and page-action buttons.
* feature: skin the modern filter Edit dialog across the classic themes
Add the scoped .cactiFilterDialog skin (accent titlebar, white title/close, styled button pane with an accented primary action) to cacti, carrot, dark, hollyberry, paper-plane, paw, raspberry, and sunrise, each using its own brand accent. The skin is accent-only and inherits each theme's dialog surface, so it works on light and dark themes. MidWinter is intentionally skipped (its variable-based CSS is handled in a separate Epic).
* fix: delete a user's saved filter layouts when the user is removed
user_remove() now deletes the user's own user_layouts rows alongside their other per-user data. Scoped to user_id so published (user_id = 0) layouts remain available to everyone.
* fix: address PR review — PHPStan return type, escaping, callbacks, SQL columns, mb truncation, contrast, POT
- create_filter(): cast both ob_get_clean() returns to string and drop the now-unused PHPStan baseline entry (fixes Level 6 failure).
- Edit dialog title now uses html_escape_attr() (attribute-context double-encoding).
- Modern Search/Clear honor the page's configured go/clear callbacks instead of hardcoding applyFilter()/clearFilter().
- filter_layouts_get() and layouts.php select explicit columns instead of *.
- Layout name truncation uses mb_substr() to avoid cutting multibyte codepoints.
- layouts.php management callback checks result.ok before reloading.
- carrot/paper-plane dialog accents darkened to meet WCAG AA (>=4.5:1) with white text.
- Added the new translatable msgids (Page Filter Format, Filters, Edit Filter, Filter Name, Save As, ...) to cacti.pot.
* fix: resolve Semgrep reflected-xss by reading filter_layout inside layout_select
The request var is no longer passed into layout_select() as a parameter (which Semgrep tracked parameter-to-return to the print sink); it is read internally like create_filter(), and the option markup remains escaped. No behavior change.
---------
Co-authored-by: TheWitness <copilot@users.noreply.github.com>
Co-authored-by: TheWitness <TheWitness@users.noreply.github.com>
|
||
|
|
3ab4c69fb7 |
PHPStan Level 8 - Phase 1 Conversion (#8135)
* PHPStan Level 8 - Phase 1 Conversion
Reduces the real (no-baseline) PHPStan level 8 error count from ~2,724 to 425
and regenerates phpstan-baseline.neon accordingly (8,941 -> 1,921 lines).
Core type-safety changes
- lib/database.php: narrow db_fetch_row()/db_fetch_row_prepared() to return
array (was bool|array) and db_fetch_assoc()/db_fetch_assoc_prepared() to
return array (was mixed); the _prepared workers coerce the connection-failure
path to an empty array so the declared type is honest. empty()/cacti_sizeof()
checks are unaffected.
- lib/database.php: narrow $db_conn guards from is_object() to instanceof PDO in
the transaction/quote/insert-id helpers so the PDO calls resolve.
- lib/mib_cache.php: the fluent MibCache methods mib()/object()/table()/row()
now return self instead of a bare object so chained calls type-check.
Type-safety casts and guards (mechanical, behaviour-preserving)
- Cast string|false/|null builtin results feeding string parameters
(json_decode, base64_encode, trim, htmle, preg_replace, explode, etc.).
- Cast int|string / int|false values feeding int id/pid parameters.
- Guard resource|false fopen()/popen() handles before fwrite/fclose/fread, and
wrap foreach over array|false/list|false results with `?: []`.
Genuine bugs found and fixed
- cli/add_tree.php / lib/api_tree.php: site tree nodes could not be created via
the CLI - the --site-id option was never parsed, $nodeTypes had no 'site'
entry (passing null to api_tree_item_save(int $type) -> TypeError), and
api_tree_item_save() never persisted site_id. All three fixed.
- tree.php: leaves_exist() is typed : int but returned db_fetch_assoc_prepared()
(an array of rows) for a SELECT COUNT(*); now uses db_fetch_cell_prepared()
and returns the scalar count.
- lib/mib_cache.php: $entries_per_object was a float (division / ceil) used as
an array key (PHP 8.1 implicit float-to-int key deprecation); now cast to int.
Config
- .phpstan.neon: add notIdentical.alwaysTrue and booleanAnd.alwaysFalse to the
existing always-true/false ignore family (same "PHPStan gets confused"
dynamic-constant cause already covered by identical.alwaysTrue,
notEqual.alwaysTrue, booleanAnd.leftAlwaysTrue, etc.).
- phpstan-baseline.neon: regenerated; the remaining 425 entries are the
untouched long tail for Phase 2.
* Remove redundant @phpstan-ignore comments
Follow-up cleanup for the level 8 Phase 1 work. Removes 14 inline
@phpstan-ignore comments that no longer suppress any reported error:
- cli/repair_database.php: `@phpstan-ignore booleanAnd.alwaysFalse` is now
covered by the global booleanAnd.alwaysFalse ignore added to .phpstan.neon.
- 13 others (host.php, include/global.php, include/session.php,
lib/api_automation.php x2, lib/functions.php x2, lib/html_utility.php x2,
lib/installer.php, lib/package.php, lib/reports.php, lib/rrd.php) no longer
match any reported error after the type-safety fixes (e.g. the
db_fetch_row_prepared() return-type narrowing removed the offset-assign
errors the api_automation ignores were suppressing).
Verified with reportUnmatchedIgnoredErrors enabled: 0 unmatched ignores
remain and the full level 8 run still reports 0 errors.
* ci: satisfy validator ratchet and refresh security sink baselines
- lib/api_tree.php: give the new tree-item site_id a real numeric
constraint ('^[-0-9]+$', matching html_reports.php) instead of an
empty regex, so the validator constraint ratchet passes without
bumping its baseline.
- tests/security/baselines: refresh the three sink/hotspot signatures
whose code text changed from the (string) casts added in this branch
(lib/poller.php setsid, cli/rrdresize.php json_encode, lib/import.php
simplexml_load_string).
* ci(js): allow DOMPurify 3.4.16 and update sync_js pin test
The dependabot bump (#8134) moved package.json/package-lock.json to
DOMPurify 3.4.16 on develop but left tests/js/sync_js.test.mjs pinned to
3.4.15, so the PR merge commit fails the 'DOMPurify is pinned' contract.
Bring package.json/package-lock.json to 3.4.16 (matching develop) and
update the pin test to expect 3.4.16.
* test: update sql.php null-handling assertion for the (string) cast
This branch changed scripts/sql.php from trim(\ ?? '') to
trim((string) \) (the phpstan-safe form that coerces both null and
false from shell_exec()). Update SqlScriptsTest's null-return assertion
to match the new cast instead of the removed '?? ''.
* fix: address Copilot review - ownership IDs, fopen guards, mib_cache bound
- chown()/chgrp(): pass integer UID/GID from fileowner()/filegroup()
(guarding the false lookup) instead of string-casting them, which made
them resolve account/group names like '1001' and fail. Covers
lib/api_device.php, lib/boost.php and poller_maintenance.php (x2).
- fopen() failure now short-circuits instead of reporting success:
lib/package.php returns false, lib/clog_webapi.php and utilities.php
render an error, and snmpagent_mibcachechild.php skips the cache write
and sentinel unlink so a concurrent writer's lock is preserved.
- lib/mib_cache.php: keep the float quotient as the loop bound and cast
only the array offset, so a non-even entries/columns split no longer
truncates the loop and drops a cached row.
* revert: keep db_fetch_* returning false on error; baseline Level 8 instead
Per review, restoring the intentional fail-safe: db_fetch_row[_prepared]()
return bool|array and db_fetch_assoc[_prepared]() return mixed again, so a
genuine SQL error (not just a missing connection) is signalled as false and
the === false checks in cdef_is_in_use(), boost_process_* and aggregate.php
work as before. Dropped the is_array() ? : [] coercion.
Level 8 stays green by regenerating phpstan-baseline.neon to capture the
re-introduced bool|array/mixed call-site errors (the ~1,850 the narrowing
had eliminated). Tightening those callers to drop them from the baseline is
Phase 2 work.
---------
Co-authored-by: TheWitness <copilot@users.noreply.github.com>
|
||
|
|
bb92c46e78 |
feat(auth): automatic User Group assignment from Login Provider groups (#8129)
* feat(auth): automatic User Group assignment from Login Provider groups Add per-Login-Provider "Auto Assignment" rules to the User Group editor so a user is added to (or removed from) a Cacti User Group at every login based on the groups their Login Provider reports. User Group editor (user_group_admin.php): - Add General / Auto Assignment / Rights spacers to the General tab and rename "General Group Options" to "Group Options". - Render one text field per eligible Login Provider under Auto Assignment (label = provider name). The admin enters a plain Group Name (e.g. "Cacti Admins"), never a DN. - Store the rules as JSON in the new user_auth_group.auto_assignments column, keyed by login_providers.id. Group resolution: - SAML2 and OpenID match the configured name against the group attribute / group claim values. - LDAP/AD resolve membership live against the directory using a raw group name plus the Search Base, trying AD (memberOf + nested LDAP_MATCHING_RULE_IN_CHAIN), FreeIPA / 389 DS (groupOfNames/groupOfUniqueNames) and RFC2307 posixGroup shapes so one plain name works everywhere. This requires a directory-read bind, so the field is only offered for Anonymous Searching or Specific Searching with a stored service account. - A directory that cannot be reached returns "undetermined" so existing memberships are left untouched rather than revoked on a transient outage. Lifecycle: - auto_assignments is reconciled on every login (lib/auth.php credential flow and login_sso.php redirect flow). - Deleting a Login Provider strips its key from every group's auto_assignments. Schema: add user_auth_group.auto_assignments (cacti.sql + 1_3_0 upgrade). * ci: document free-text group-name validateInput for constraint ratchet * fix(auth): address PR review on automatic User Group assignment - LDAP: add an Active Directory bare-name query (objectClass=group with the member:1.2.840.113556.1.4.1941 nested-membership rule) so a plain group name resolves on AD, which neither the posixGroup nor groupOfNames probe could match. - LDAP: make single-group membership tri-state and propagate an "undetermined" result, so a search that fails after binding (timeout/dropped connection) no longer looks like confirmed non-membership and revokes assignments. - SAML2/OpenID: supportsAutoAssignment() now requires a configured group claim, and applyAutoAssignments() skips any provider that is not currently capable, so a blank claim can never strip users from their groups. - Only reset the permission cache when a membership row actually changed (accumulate db_affected_rows) rather than on every login that has a rule. - Add unit tests for the reconciliation boundaries (add / remove / no-op / incapable / undetermined) and for the LDAP group-query construction (AD plain + nested, posixGroup, groupOfNames, DN mode). - Regenerate locales/po/cacti.pot for the new UI strings. * fix(phpstan): resolve level-8 errors from auto group assignment - Guard supportsAutoAssignment() behind an instanceof AbstractLoginProvider check so it is not called on the base LoginProviderInterface. - Assign the injected field list to a variable (inject_form_variables takes it by reference) and pass an is_array-guarded group row. - Bump the lib/ldap.php ldap_get_entries baseline count 3->6 for the three new group-membership query helpers. |
||
|
|
a52368125e |
feat(auth): rename User Domains to Login Providers, add SAML2 and OpenID Connect (#8066)
* feat(auth): rename User Domains to Login Providers Collapses user_domains/user_domains_ldap into a single login_providers table with a JSON parameters column, migrated in place during the 1.3.0 upgrade. Renames the admin page, menu, breadcrumbs, and help links to match. * feat(auth): rework login providers as Cacti\Auth classes Introduces LoginProviderInterface with credential (LDAP/AD) and redirect (SAML2/OpenID) flavors, replacing the procedural domains_login_process()/domains_ldap_*() functions. Adds blank-field group membership gating shared by every provider type. * feat(auth): add SAML2 and OpenID Connect login providers Adds onelogin/php-saml and firebase/php-jwt, a new cacti_http() SSRF-hardened outbound request helper for every OpenID discovery/token/JWKS call, and login_sso.php to handle the redirect-based SSO flow. Login page now shows a button per enabled SAML2/OpenID provider, and per-provider auth cookie opt-out is enforced on both the password and SSO login paths. * chore(i18n): regenerate cacti.pot * feat(auth): add Local and Basic auth as Cacti\Auth providers LocalAuthLoginProvider (CredentialLoginProviderInterface) and BasicAuthLoginProvider (new PreAuthenticatedLoginProviderInterface, since Basic Auth has no password for Cacti to check) are thin adapters over the existing local_auth_login_process()/basic_auth_login_process() functions, which are left untouched to avoid disturbing their regression-tested lockout/timing/rehash behavior. * feat(auth): move local password verification into LocalAuthLoginProvider secpass_login_process() and local_auth_login_process() are retired; their lockout, constant-time verification, forced-password-change, and rehash-on-login logic now live end to end in the class (verifyCredential/enforcePasswordPolicy/rehashIfNeeded). secpass_check_pass() stays a shared lib/auth.php function since password change/reset/user_admin also call it outside any login flow. Local auth still has no login_providers row: isEnabled() is always true and it cannot be disabled or deleted. * feat(auth): move Login Provider CRUD into Cacti\Auth classes AbstractLoginProvider gains static deleteById()/enableById()/disableById()/makeDefaultById()/persist(); each concrete provider owns its own collectParameters(), dispatched via LoginProviderFactory. login_providers.php's form_save()/form_actions() call these instead of doing raw SQL, and the retired provider_remove()/provider_disable()/provider_enable()/provider_default()/provider_collect_parameters() procedural functions are gone. Local/Basic auth are unaffected: they never extend AbstractLoginProvider, so they structurally cannot expose delete/disable. * fix(auth): address Copilot review findings on SSO/SAML/OIDC security Fixes callback/ACS/SLS/metadata URLs that used the raw provider id instead of the login realm (broke every SSO login), adds InResponseTo correlation for SAML and sub/azp validation for OIDC, requires HTTPS for OIDC discovery/endpoints, pins cacti_http() connections against DNS rebinding via CURLOPT_RESOLVE, exempts login_sso.php from the authenticated-page redirect guard and gates it on auth_method, wraps provider dispatch in a exception boundary, adds a dedicated SAML SLO path instead of routing it through login completion, makes remember-me an explicit opt-in for SSO, fixes disabled/deleted providers not revoking existing auth cookies, restricts the default-realm action to LDAP/AD, and fixes the 1.3.0 migration silently enabling group enforcement for installs that had it off. Also updates the E2E seed script for the login_providers schema. * chore(auth): refresh CI security baselines and document no-op validation Regenerate the sink inventory baseline for the new/renamed lib/Auth/* and login_providers.php files, and add '// no-validation:' comments on the intentionally-unconstrained admin/free-text/secret fields (LDAP DNs/filters, SAML certs/keys, OIDC client credentials, provider display fields) flagged by the validator constraint ratchet. Refreshes the validator baseline to drop the deleted user_domains.php entries. * refactor(ldap): remove dead DB-lookup path from Ldap::__construct The domain_id>0 branch duplicated the JSON-parameter-to-property mapping already owned by LdapLoginProvider::buildLdap(), and nothing constructs Ldap with a real id anymore (all call sites pass 0). Drop the parameter and the branch so Ldap stays a pure connection/protocol class with no DB coupling. Also fixes a latent bug: group_require is a typed bool property, but LdapLoginProvider::authenticate() assigned it an int (1/0) from a strict_types=1 file, which throws a TypeError on every LDAP/AD login attempt. Assign a real bool instead. * fix(auth): fix LDAP login, upgrade ordering, and hardening guard failures LdapLoginProvider compared LdapError::GetErrorDetails()'s int error_num against the string '0' with strict equality, so it never matched and every LDAP/AD login was rejected regardless of valid credentials. Compare against int 0 instead. install/upgrades/1_3_0.php called login_providers_convert_1_3_0() (which reads and then drops user_domains/user_domains_ldap) before the pre-existing legacy ALTER TABLE statements that add the debug/network_timeout/bind_timeout columns those reads depend on, so upgrades failed once the tables were already gone. Moved the call to run after those ALTERs. OpenIdLoginProvider::complete() read state/code from raw \ instead of gnrv(), tripping the hardening pattern guard; switched to the request helper. * style(auth): apply php-cs-fixer formatting and normalize line endings to LF Runs php-cs-fixer across the new lib/Auth/* classes, login_providers.php, login_sso.php, and the other touched files, fixing indentation/alignment/docblock style to match project convention. Also normalizes several files that had picked up CRLF line endings back to the LF the project's .php-cs-fixer.php config (setLineEnding) and the rest of the tracked tree actually use. * fix(auth): address second Copilot review round (9 high/medium findings) - Make login_providers_convert_1_3_0() resumable: CREATE TABLE IF NOT EXISTS and INSERT ... ON DUPLICATE KEY UPDATE so an interrupted upgrade can be re-run instead of leaving a half-migrated, unrecoverable table. - SamlLoginProvider::complete() now fails closed when no saved AuthnRequest ID exists instead of passing null to processResponse(), which skipped InResponseTo validation and accepted unsolicited IdP-initiated assertions. - LoginResult gains an isCredentialFailure flag; only a genuine wrong-password LDAP bind (LdapError::Failure) counts toward the lockout counter now, not connection errors or group-membership denials. - Fixed button_label/debug being read from the JSON parameters column in OpenIdLoginProvider/SamlLoginProvider/LdapLoginProvider when they are actually top-level login_providers columns; AbstractLoginProvider now hydrates and exposes both. - Removed SamlLoginProvider's wantMessagesSigned, which silently required the outer SAML message (not just the assertion) to be signed, rejecting IdPs that only sign the assertion as the UI promises. - Added getTemplateUserId() to CredentialLoginProviderInterface and implemented it on LocalAuthLoginProvider so the contract (not just incidental AbstractLoginProvider inheritance) guarantees the method lib/auth.php calls. - cacti_http()'s CURLOPT_RESOLVE entries now bracket IPv6 literals correctly, and safe-IP validation strips parse_url()'s brackets before checking, fixing IPv6 target pinning. - login_providers.php's type dropdown now swaps the Full Name attribute default (cn/displayName) on an actual user-driven type change, not on page load, so a new Active Directory provider does not silently save the LDAP default. - Extracted auth_apply_group_login_opts() and call it from both auth_login.php and login_sso.php, so SSO logins honor a user's group-derived login_opts the same way password logins already do. * fix(auth): fix PHPStan level 8 errors and pest harness stub drift Test harness fix: the LoginProvidersLoginRealmValidationTest stub LoginResult class was missing the isCredentialFailure property added to the real class, causing 'Undefined property' warnings that broke the harness's JSON output. Added the property and set it on the simulated bad-password failure. PHPStan (48+ errors, all newly introduced by this branch's rewrite, never actually verified until now): narrowed db_fetch_row_prepared()/db_fetch_assoc()'s array|bool (including bare true, which survives a '?: []' short-circuit - fixed with explicit is_array() checks instead) at every call site across auth_login.php, login_sso.php, lib/auth.php, lib/Auth/LocalAuthLoginProvider.php, lib/Auth/LoginProviderFactory.php, and install/upgrades/1_3_0.php. Fixed OpenIdLoginProvider::discover()'s return shape to match its declared docblock instead of returning the raw decoded JSON. Fixed lib/functions.php's cacti_http() to narrow curl_exec()'s string|bool result with is_string() (not ===false, which still leaves 'true' as a residual type) and guard / as non-empty for curl_setopt_array()'s stubs. Fixed a real bug in SamlLoginProvider::processLogout(): processSLO(true) was passing 'true' as \ (the first parameter) instead of \ (the fifth), so it always redirected/exited internally with the default \=false, making Cacti's own logout.php never regain control. Now calls processSLO(keepLocalSession: true, stay: true) and is typed ': never' to match; login_sso.php's now-genuinely-unreachable break statements after processLogout()/initiate() are removed. Fixed login_providers.php passing a expression (not a variable) to inject_form_variables()'s by-reference parameter. Verified: PHPStan analyse clean (0 errors), full Pest Unit/Security suite passes (128 tests), hardening guard/sink inventory/validator ratchet all still clean. * fix(auth): fix ambiguous column and array+bool crash in user_log filter The user filter dropdown query selected a bare 'id' column after joining user_auth and login_providers, which is ambiguous now that login_providers also has an id column (unlike the old user_domains.domain_id) - MySQL rejected the query outright. Qualified as ua.id. Also narrow db_fetch_assoc()'s array|bool result before the \ + \ + \ merge, which was throwing a TypeError whenever the query failed instead of surfacing the real SQL error. * fix(auth): address third Copilot review round (5 findings + tests) - AbstractLoginProvider::persist() now disables the template account for every provider type; SAML2/OpenID also copy it via user_copy() in login_sso_complete() and were left directly usable as a local login. - cacti_http_resolve_safe_ips() now detects IPv4-mapped IPv6 literals (::ffff:0:0/96) via inet_pton() and validates the embedded IPv4 address, closing an SSRF bypass via e.g. ::ffff:169.254.169.254. - login_providers_convert_1_3_0() now tracks db_install_execute()'s status for every row and only drops the legacy user_domains/user_domains_ldap tables once every row (and the final row count) is confirmed migrated. - Ldap::Getcn() now looks up ldap_get_entries()'s always-lowercased attribute keys case-insensitively while still storing the result under the originally-requested key, fixing AD's displayName (and any other mixed-case configured attribute) always resolving to a blank full name. - SamlLoginProvider::processLogout() now redirects to the LogoutResponse URL processSLO() returns for a LogoutRequest (stay:true), instead of discarding it and always redirecting to Cacti's own logout page - federated logout was never actually acknowledged to the IdP. - Added tests/Unit/Security/Auth/OpenIdLoginProviderTest.php (13 tests) covering verifyIdToken() signature/issuer/audience/azp/nonce validation, discover()'s HTTPS enforcement, and complete()'s callback-state and UserInfo-subject-matching checks, against a locally-generated RSA keypair/JWKS. - Regenerated the sink inventory baseline for the new header_redirect call in SamlLoginProvider::processLogout(). Verified: PHPStan clean, full Pest Security/Auth suite passes (830 tests including the 13 new ones), php-cs-fixer/hardening-guard/sink-inventory/validator-ratchet all clean. |
||
|
|
cb49f31ff9 |
Replace csrf-magic with maintained CSRF protection (#7744)
* build: add symfony/security-csrf Backs the CSRF token core that replaces the vendored csrf-magic fork. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: fix security-csrf package path assertion symfony/security-csrf ships CsrfTokenManager.php at the package root, not under src/. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor: register the composer autoloader before the CSRF include CSRF validation ran before the autoloader existed, which blocks any class-based guard. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * feat(csrf): add CactiCsrfGuard over symfony/security-csrf Issues and validates session-scoped tokens; not yet wired into the request path. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * feat(csrf): accept pre-upgrade csrf-magic tokens during the grace window Legacy tokens carry their own expiry, so the fallback closes itself without a config flag. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * feat(csrf): port the output buffer rewriter to CactiCsrfGuard Keeps the hidden field name and JS globals that third-party plugins rely on. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): use json_encode for the JS script context in rewriteBuffer htmlspecialchars() protects the HTML attribute context but not the <script> string literal, which relied on Symfony's token generator never emitting a quote rather than being safe by construction. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor(csrf): move the XHR shim to include/js as Cacti-owned code It has been maintained by the Cacti Group since 2020 and is not an upstream package. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor(csrf): retire the vendored csrf-magic fork The procedural csrf_* surface stays as a deprecated shim so plugins keep working. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * feat(csrf): validate requests through CactiCsrfGuard csrf-magic self-invoked from its own file; the guard is called explicitly. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(install): stop gating the permission step on the csrf secret Retiring csrf-magic removed the directory its default path lives in, and setStep() clamps to the first errored step, so no fresh install could advance. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): key the legacy fallback on the raw secret bytes refresh_csrf.php writes PHP source with a trailing newline, and trimming that yields a different HMAC key than csrf-magic used. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): log when the guard degrades to disabled in a web context The installer is the only web path that can reach it, and failing open in silence is not something csrf-magic could do. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs: changelog for the CSRF library replacement Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): mint the token before the session write callback closes it csrf_startup() minted lazily from the output buffer callback, which runs after session_write_close()'s shutdown-function registration, so a token minted there on a legacy-fallback POST was silently dropped. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): make refresh_csrf.php a no-op Its default secret path no longer ships and live tokens are session-bound, not HMACs over the secret, so rotation neither worked nor did anything meaningful; running it during the upgrade grace window used to delete the legacy secret and kill validation for pre-upgrade tokens outright. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): fall back to the old default secret path when reading it csrf-magic tried its own directory as a second candidate regardless of $path_csrf_secret; the new reader only tried the configured path, so a site pointing $path_csrf_secret at a file that was never created lost the legacy-token grace window entirely. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs(csrf): point the JS header at its BSD license sidecar The header reduces the original BSD terms to prose and the actual text sits in an unreferenced csrf.js.LICENSE, so add a pointer to it. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): restore csrf_check()'s bool return and $fatal contract The shim ignored $fatal and always returned void, so audit/audit.php's `!csrf_check(false)` evaluated `!null` (always true) and denied every request instead of getting a real pass/fail result back. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs: reference the pull request number in the changelog entry Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: resolve includes through Cacti path constants Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(csrf): enforce full coverage Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Harden CSRF fallback handling Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(csrf): assert the component stays out of the index Committing it would leave a second copy to drift against the lock. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: align CSRF checks with PHP 8.2 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs: normalize the CSRF changelog entry * Fix CHANGELOG entry ordering Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(csrf): support constrained test environments * fix(csrf): narrow nullable guard state * test: place CSRF coverage in security domain Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * security(test): match disabled exec exactly Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: run CSRF coverage on PHP 8.3 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
3c20f4ce7e |
Harden Boost data handoffs and graph cache writes (#7729)
* fix(boost): harden data handoffs and cache writes Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Define $updates_ok when there is nothing to flush PHPStan level 6 also flags the poller ownership check, which is the POLLER_ID constant case the file already annotates above. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Use a line comment to match the fixer Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Record the boost process columns in the audit schema Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: resolve includes through Cacti path constants Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Fix Boost handoff review concerns Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: seed settings in Boost database fixture Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: refresh SQL interpolation baseline Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs: record Boost handoff hardening Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: stop boost discarding a cycle and wedging a shard Three defects in the handoff hardening. A rejected ownership handoff returned false, which the poller reads as boost having consumed the data, so it skipped the RRD write. The poller_output rows were already deleted by then, so a stale poller_item copy on a remote poller silently lost the whole cycle. Return true instead and let the poller write directly, matching the database failure path. Any non-OK RRDtool response retained the shard, and the retry then replayed rows already written, which RRDtool rejects as an illegal update time. That failed again on every pass, so the archive tables accumulated and boost never drained. Treat an at-or-behind timestamp as applied, since the sample is in the RRD; genuine errors still retain the shard. The two new poller_output_boost_processes columns passed 'null' where db_install_update_table reads 'NULL', so NOT NULL was never emitted and upgraded installs diverged from cacti.sql. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: spell the NULL column key so upgrades emit NOT NULL db_add_column() reads $column['NULL'] and PHP array keys are case sensitive, so the thirteen definitions in the 1.3.0 upgrade that passed a lowercase 'null' key had the flag discarded and created nullable columns. Fresh installs built from cacti.sql got NOT NULL, upgraded installs did not, and Cacti's own schema audit reports the difference. The same file already spells the key in upper case 144 times, and the wrapper's own docblock example uses the upper case form. Guard the class with a test over every upgrade file rather than only the one that was wrong. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: assert the NULL key plainly toContain() takes needles rather than a failure message. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): close remaining handoff review gaps Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): guard nullable graph output Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): keep in-flight completion rows through the 1.3.0 upgrade The truncate only existed so the run_child UNIQUE key could build over rows that predate run_id; deleting just those rows leaves a running boost its completion records. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor(boost): drop the poller ownership gate Its boost_poller_on_demand() call sits behind the boost_redirect early return that poller.php forces on for every multi-collector install, and its recovery call turns a device deletion on main into a recovery that never brings the collector back online. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): bound atomic cache write allocations Guard upgrade source reads so failures report clearly, write graph cache data in fixed-size chunks, and exercise a multi-chunk payload. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): support Windows cache replacement Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(boost): expand handoff failure coverage Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(boost): support ANSI_QUOTES upgrade checks Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): stop staging after chunk failure Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(recovery): use numeric poller status Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(sql): support ANSI_QUOTES in recovery paths Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): preserve cache on replacement failure Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): retain readable cache permissions Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): repair completion schema at runtime Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(recovery): bound timestamp batch fetches Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): satisfy runtime schema analysis Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(recovery): restore status after transfer failure Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(boost): rely on unit bootstrap helpers Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): refresh generated cache secret Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): normalize cache path separators Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(boost): fail clearly on unreadable fixtures * fix(boost): name ignored duplicate sample keys The staging warning only said rows were dropped, so a retry could not be told from a colliding writer. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Lack of pre-commit hook execution allowed lint failure --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
3f52135d36 | Harden installer cleanup and enforce coverage collection (#7993) | ||
|
|
a155419ff3 |
Serialize process registry admission with portable locks (#7771)
* Serialize process registry admission with Symfony Lock Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Fix PDO process lock mode handling Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: run process lock coverage on PHP 8.2 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: confirm a pid is still ours before signalling it #7574 added the still-running guard to dsstats but left six byte-identical siblings signalling a pid straight out of the processes table. After a crash or a power loss the row survives, the pid is recycled by an unrelated daemon, and the next start SIGTERMs a stranger. That is issue #7425, still live in rrdcheck, boost, poller_commands, float_rrdfiles, poller_reindex_hosts and rebuild_poller_cache. Wrap each kill in cacti_process_still_running(), matching the dsstats shape, and leave unregister_process() outside the guard so a stale row is still cleaned up. Cover all seven with a test that walks every kill site, so a new variant cannot reintroduce it. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: assert the guard plainly toContain() takes needles rather than a failure message, so the file name was being asserted as a second required substring and the test always failed. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs(poller): describe required lock parameters --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> |
||
|
|
ead882172e |
Make installer completion fail closed and add test coverage (#7626)
* Make installer completion fail closed Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Add installer unit and Docker integration gates Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Require installer coverage evidence Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Stabilize installer failure scenarios Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Address installer review feedback Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Reject incomplete installer selection payloads Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Match table validation to rendered controls Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Protect long-running installer migrations Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Load installer table metadata in one query Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Optimize installer completion queries Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Batch installer default settings writes Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Batch installer color upserts Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Batch installer template and poller lookups Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Refresh SQL interpolation baseline after develop rebase Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: use Cacti Composer test setup Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: resolve includes through Cacti path constants Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: align installer workflows with policy Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: refresh security baselines after develop rebase Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: reuse TCP database credentials for installer checks Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(security): refresh sink inventory baseline after rebase onto develop Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: run installer coverage on PHP 8.2 The 8.1 pin is below the ^8.2 composer requirement, so pest died in platform_check before any test ran. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> |
||
|
|
46b14ed23d | Add indexes for host-template and session lookups (#7634) | ||
|
|
68d91f38d9 |
fix(installer): track all 1.3.0 migration writes (#7629)
* Track all 1.3.0 migration writes * Use the standard test license header * Fix NULL handling in plugin timestamp migration --------- Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
f2a5ef0d51 |
fix: cast path_php_binary before passing it to string parameters (#7576) (#7577)
* fix: cast path_php_binary before passing it to string parameters The setting has no default, so it is null on a fresh install, and PHP 9 turns the resulting coercion from a deprecation into a TypeError. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs: append the changelog entry at the end of the issue block Matches .github/copilot-instructions.md, which appends rather than inserting in numeric order. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: use the full licence header on the new test files Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: cast path_php_binary at every site that escapes it The first pass covered three of them; a repo-wide scan found 21, and the test now fails if a new one appears. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: strengthen PHP binary path guard * fix: cover default-config PHP binary reads --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
db39178735 |
fix: harden the poller_item cache and Boost RRD staging subsystems (#7538)
* fix(boost): forward still-open-round archive rows instead of dropping them boost_process_poller_output() correctly excluded rows from an in-progress poll round when building the RRD write, but the archive-table cleanup right after deleted every row for the local_data_id anyway, with no time filter. Those rows now get INSERT IGNORE'd into the live poller_output_boost table before the archive table is cleared, so a still-open round's data survives. Also fixes the duplicate-record guard in the same function: $last_item was initialized once before the results loop and never reassigned, so the comparison against it could never be true. It is now updated after each processed row. Fixes #7519 Fixes #7523 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): gate archive-table drop on shard completeness poller_boost.php decided whether to drop a run's archive tables using SELECT SUM(status) FROM poller_output_boost_processes, an aggregate over whatever completion rows landed. If one child crashed before writing its completion row, the sum from the surviving shards was still positive and every archive table for the run got dropped, including the crashed shard's unprocessed data. The drain loop already computes boost_completed_children() vs $expected_children to detect a crashed child; the drop now uses that same completeness check instead, and leaves the archive tables in place with a warning when it is not met. Fixes #7520 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(dsstats,rrdcheck): apply Boost's startup barrier to launch loops dsstats_launch_children()/rrdcheck_launch_children() fork children via exec_background() (non-blocking) and the callers then wait with a bare while ($running = ..._processes_running($type)) { sleep(2); }. If the parent evaluates that before any child has registered in the processes table, $running reads 0 and the loop body never runs, treating "not started yet" as "already done" -- the same race Boost's boost_all_children_registered() barrier was added to close. dsstats_launch_children()/rrdcheck_launch_children() now return the launched count, and dsstats_wait_for_children()/ rrdcheck_wait_for_children() wait for the running count to reach it (bounded by a deadline) before trusting the drain loop's running-count poll. Fixes #7526 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor(poller): consolidate poller_item deletes behind a shared helper Extract poller_item_delete_for_data_source() and poller_item_delete_for_host() in lib/poller.php and migrate the existing local+remote delete pairs in lib/api_device.php and lib/api_data_source.php onto them, so the local-delete-plus-remote-mirror pattern lives in one place instead of eight. Refs #7535 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(poller): mirror poller_item deletes to the remote poller on handoff api_data_source_change_host(), data_query_remove_disabled_items(), and data_source_to_data_template() each moved or removed a data source's poller_item row locally but never on the remote Data Collector that still owned it, so that Collector kept polling an item it no longer should. All three now resolve the owning remote poller before the local change and purge the row there too via the shared helper. Fixes #7521 Fixes #7524 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(poller): correct poller_item join semantics and cron reset in cmd.php poller_item and host share ~13 identically-named snmp_*/hostname columns; SELECT * across the join let PDO FETCH_ASSOC silently collapse them to host's live values instead of poller_item's cached copies. Select pi.* plus explicitly aliased host/site disabled columns instead. The now already-fetched $poller_items also makes the separate script-server-call COUNT(*) re-join redundant, replaced with an in-PHP count. Also apply the same cron_interval-aware rrd_next_step reset used by the ranged branch to the --allhost branch, which previously ignored it. Fixes #7522, #7525, #7528. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(poller): thread known poller_id through update_poller_cache() callers update_poller_cache() re-derived poller_id via a host/data_local JOIN on every call, even when the caller already had it: repopulate_poller_cache() and push_out_data_input_method() both already select h.poller_id and discarded it, push_out_host() has the host row cached per data source, and api_device_enable_devices()/update_poller_cache_from_query() compute it once outside their loops. Add an optional $poller_id argument (and honor an already-present 'poller_id' array key) so these callers skip the redundant query. Fixes #7527. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(poller): scope global aggregate scans and make process-leveling sargable Three poller.php queries (total_snmp_ports and the mutually-exclusive active_profiles pair) had no poller_id filter, unlike the rest of the file's central-only blocks, so every remote poller redundantly recomputed and overwrote the same global config options; the snmp_port scan is also an unindexed full table scan. Gate them behind poller_id == 1, with remote pollers reading back the value the central poller already stored. Also rewrite the process-leveling $sql_where's rrd_next_step predicate from the non-sargable `rrd_next_step - $poller_interval <= 0` to the algebraically equivalent `rrd_next_step <= $poller_interval`, restoring use of the poller_id_rrd_next_step index. Fixes #7529, #7530. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(poller): batch update_data_query_cache()'s poller cache writes Each changed data source called update_poller_cache($data_source, true), which flushes immediately via poller_update_poller_cache_from_buffer() -- its own present=0 UPDATE, INSERT ON DUPLICATE KEY UPDATE, and stale-row DELETE -- on every iteration. Re-indexing a host where every port changed issued up to 3x as many statements as ports instead of one batched flush. Accumulate changed rows across the loop and call update_poller_cache() with commit=false, then flush once via poller_update_poller_cache_from_buffer() after the loop, matching the batched pattern already used by push_out_data_input_method(). Fixes #7531. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): add USING BTREE to poller_output_boost_local_data_ids indexes MEMORY-engine indexes default to HASH unless USING BTREE is specified, unlike the sibling poller_output/poller_output_boost tables in the same schema file. boost_output_rrd_data()'s ORDER BY/LIMIT query and boost_process_local_data_ids()'s ranged DELETE both need to scan local_data_id in order, which a HASH index can't serve, forcing a filesort on every boost worker pass. Verified against a real MariaDB 11 instance: EXPLAIN on the ORDER BY query shows "Using filesort" with the old HASH schema and no filesort with USING BTREE, using the primary key for ordering instead. Also aligns boost_prepare_process_table()'s dead CREATE TABLE IF NOT EXISTS fallback with the corrected schema, and adds an upgrade step that rebuilds both indexes as BTREE on existing installs (checked via information_schema.STATISTICS, since db_install_add_key() only compares index columns, not algorithm, and would otherwise skip an existing HASH index with the right column list). Closes #7532 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): skip the UNION ALL wrapper for the single-archive-table case boost_process_local_data_ids() always wrapped its flush query in "SELECT * FROM (... UNION ALL ...) t ORDER BY ...", even when there was only one archive table -- the steady-state case, since old archive tables are dropped at the end of each cycle. UNION ALL results wrapped in a derived table are one of the documented cases MySQL/MariaDB's derived-table merging doesn't apply to, so every pass paid for a filesort over an indexless derived table regardless of how well-ordered the underlying archive table's rows already were. Extracts the per-table SELECT into boost_archive_select_sql() and uses it directly, without the wrapper, when there's exactly one archive table. The UNION ALL/derived-table shape is kept as the fallback for the genuinely rare case where a prior crashed run left more than one archive table behind. Verified against a real MariaDB 11 instance and via a SQLite-backed integration test that both shapes return correctly filtered and sorted rows. Closes #7533 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): batch-prefetch RRD field name lookups instead of per-row queries Two N+1 patterns in the Boost hot paths: boost_process_local_data_ids() (poller_boost.php) re-ran the unused_data_source_names/nt_rrd_field_names lookups on every local_data_id boundary in the sorted flush result set. process_poller_output() (lib/poller.php) had the equivalent lookup, but without even a boundary check -- it re-ran per matching row whenever the output contained a ':', including consecutive rows for the same data source. Adds poller_prefetch_rrd_field_names() (lib/poller.php, shared by both callers since poller_boost.php already requires lib/poller.php) to batch-fetch these mappings once per invocation via IN()-batched queries keyed by the full set of local_data_ids in the batch, chunked at 1000 ids to match the array_chunk(..., 1000) convention used elsewhere (lib/api_data_source.php, lib/api_graph.php). Both call sites now look up the prefetched maps in-memory inside their loops. While consolidating the lookups, fixes a latent bug the old process_poller_output() code had in its non-templated fallback branch: a query referencing gti.task_item_id without joining graph_templates_item, which throws an unknown-column error whenever that path is hit. The batched query always joins correctly. Integration test proves both correctness (against a real SQLite-backed schema) and the query-count reduction: 5 local_data_ids that would previously cost up to 10 queries for these two lookups now cost exactly 3, regardless of how many ids are in the batch. Closes #7534 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor(boost): consolidate poller_output_boost inserts into one helper The INSERT INTO poller_output_boost (...) VALUES ... statement was hand-written four times: three in cmd.php (behind the same boost_redirect == 'on' && boost_rrd_update_enable == 'on' guard) using INSERT IGNORE, and once in boost_poller_on_demand() (lib/boost.php) using ON DUPLICATE KEY UPDATE. Beyond the duplication, that was a real data-integrity inconsistency between two paths writing the same shape of data: silently-drop vs last-write-wins on the same key. Since boost_redirect gates cmd.php's writes and boost_poller_on_demand() returns early when boost_redirect is on, the two paths never run concurrently for the same install; they're alternate write paths selected by that one setting, not competing writers. A genuine collision on (local_data_id, rrd_name, time) in either path represents a retry of an already-collected sample -- a batch resent after a partial failure, or two writers racing on the same rounded second -- not two independently meaningful readings, since RRD only stores one value per timestamp regardless. Standardizes on INSERT IGNORE (first write wins), matching the sibling poller_output insert this table shadows, which has always used INSERT IGNORE, and 3 of the 4 existing call sites. Extracts boost_flush_output_batch() (lib/boost.php) as the one shared helper, handling both the batched INSERT and the max_allowed_packet-safe chunking cmd.php's three sites previously didn't do at all (they only chunked by item count). Integration test exercises a genuine duplicate-key collision against a real (SQLite-backed) table and asserts the chosen first-write-wins behavior. Closes #7536 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(tests): resolve cross-branch test interactions found by full-suite verification Two test-only bugs surfaced only when all four fix branches for the poller_item/Boost hardening pass ran together, not when each ran in isolation: - BoostArchiveHandoffIntegrationTest extracted the archive-forward INSERT IGNORE by searching lib/boost.php for the first occurrence of "INSERT IGNORE INTO poller_output_boost". The Boost SQL-optimization branch adds a second, earlier occurrence (the new boost_flush_output_batch() helper's single-line prefix), so the naive search now grabbed the wrong snippet. Anchor on the multi-line "...poller_output_boost\n\tSELECT *" shape that only the archive-forward statement has. - PollerItemRemotePurgeHandOffTest calls push_out_host() (via api_data_source_change_host()), which needs CACTI_PATH_LIBRARY. The test never requires include/global.php (which normally defines it), and was relying on some other file in a full multi-thousand-file suite run having defined it first as a process-wide side effect. Define it directly instead of depending on unguaranteed load order. No production code changed. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * style(poller): satisfy php-cs-fixer strict_param in the script-server count Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): order the single-archive-table flush by the table's PK columns Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(boost): key the max_allowed_packet cache per connection Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(tests): restore the default db_* session after swapping it for a fake handle Left swapped, the fake handle answered every later read_config_option() in the run; on PHP 8.4 the extra deprecation traffic routed through CactiErrorHandler turned that into an uncaught PDOException and killed the pest process. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
7cb994d36c |
fix(installer): recover from expired CSRF tokens (#7392)
* fix(installer): recover from expired CSRF tokens Return a replacement token for expired installer AJAX requests, retry the failed action once, and roll tokens forward after successful responses. Fixes #7343 Signed-off-by: Abdul Mohammad <abdul.mohammad9087@gmail.com> * test(installer): exercise CSRF recovery round trip Signed-off-by: Abdul Mohammad <abdul.mohammad9087@gmail.com> --------- Signed-off-by: Abdul Mohammad <abdul.mohammad9087@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
51eb47b8ea | fix(import): return a strict bool from import_validate_signature() (#7544) | ||
|
|
c741f71c5c |
refactor(mail): replace PHPMailer with Symfony Mailer (#7190)
* refactor(mail): replace phpmailer with symfony mailer Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * style(codex): fix coding standards in Symfony Mailer migration Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(codex): fix TLS downgrade bug and From address overwrite in Symfony Mailer Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(codex): guard SMTP stream setTimeout calls behind SocketStream check setTimeout() exists on SocketStream only; getStream() is typed AbstractStream, which fails PHPStan level 6. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(codex): correct inline graph CID, mandatory STARTTLS, and address validation in Symfony Mailer Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(codex): test inline CID, STARTTLS enforcement, and address-error handling Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(mail): qualify bare local addresses so sendmail recipients still work Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(mail): prune the stale PHPMailer vendor tree on upgrade Replacing PHPMailer with Symfony Mailer dropped phpmailer from composer.json, but the change also removed the prune_deprecated_files entries that cleaned up the vendored tree, so an upgraded install would keep stale PHPMailer code on disk. Add include/vendor/phpmailer (whole directory) to the prune list. Co-Authored-By: Oz <oz-agent@warp.dev> Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(mail): address transport review feedback Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(mail): drop setAutoTls() call missing from symfony/mailer 6.4 EsmtpTransport::setAutoTls() only exists in symfony/mailer >= 7.1, and the 6.4 series (needed for PHP 8.1) has no switch to disable opportunistic STARTTLS, so hide the advertised capability in a small subclass instead. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(mail): vendor the psr/log 1.1.4 sources include/vendor/psr/log was tracked with only its package metadata, so composer treats it as installed and never extracts the classes. That was harmless while nothing loaded them, but Symfony Mailer's AbstractTransport instantiates Psr\Log\NullLogger on every transport, which now fatals at runtime and in the test job. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(mail): match PHPMailer SMTP and attachment semantics Empty secure modes use no-STARTTLS transport; inline disposition is honoured; unreadable attachments fail closed. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: Thomas Vincent <thomas@vincent.dev> Co-authored-by: Oz <oz-agent@warp.dev> |
||
|
|
a610887bc9 |
Add Composer binary path setting with installer detection (#7271)
* Add Composer binary path setting with installer detection First half of #6456: the path setting, auto-detection, and a runnability probe on the Binary Locations step. Ref #6456 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Normalize Windows path in composer probe Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Use line comment per cs-fixer Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Re-ordering issue per spec ChangeLog is order as follows: 1. Issue first, in numeric order 2. Features next in numeric order 3. For issues and features that do not have a number, they should be at the bottom of each section. --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
b2bf39b8dc |
fix(sqli): sanitize sort_column and enforce sort_direction in ORDER BY clauses (#7162)
* fix: correct grammar and possessive errors in English source strings (batch 2) (#1) * fix: correct grammar and possessive errors in English source strings (batch 2) Seven errors found via cacti.pot audit: - automation_networks.php: "senders name" → "sender\'s name" (line 839) - automation_networks.php: "senders Email" → "sender\'s Email" (line 848) - automation_templates.php: "devices sysDescr" → "device\'s sysDescr" (line 1060) - automation_templates.php: "devices sysName" → "device\'s sysName" (line 1067) - automation_templates.php: "devices sysOid" → "device\'s sysOid" (line 1074) - data_templates.php: garbled "it either it value...critical data Data Query" → "its value...Data Query" (line 903) - package_repos.php: "Package Repositorites" → "Package Repositories" (line 436) All changes are to UI description strings inside __() / __esc() calls. cacti.pot should be regenerated via build_gettext.sh after merge. * fix: correct grammar and possessive errors in English source strings (batch 2) Seven errors found via cacti.pot audit: - automation_networks.php: "senders name" → "sender\'s name" (line 839) - automation_networks.php: "senders Email" → "sender\'s Email" (line 848) - automation_templates.php: "devices sysDescr" → "device\'s sysDescr" (line 1060) - automation_templates.php: "devices sysName" → "device\'s sysName" (line 1067) - automation_templates.php: "devices sysOid" → "device\'s sysOid" (line 1074) - data_templates.php: garbled string → "its value...Data Query" (line 903) - package_repos.php: "Package Repositorites" → "Package Repositories" (line 436) All changes are to UI description strings inside __() / __esc() calls. cacti.pot should be regenerated via build_gettext.sh after merge. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: add dependabot config with npm and github-actions * ci: add CodeQL (JS/TS, excludes PHP) with workflow_dispatch * ci: update CodeQL for javascript-typescript, python * fix(ci): use specific runner labels, fix codeql concurrency * Improve IPv6 support in RRDtool proxy and ping utilities (#2) * Improve IPv6 support in RRDtool proxy and ping utilities - Fix RRDtool proxy to dynamically detect IPv6 addresses and use AF_INET6 sockets instead of hardcoded AF_INET (IPv4-only), enabling connections to IPv6 RRDtool proxy servers including backup servers - Strip brackets from IPv6 addresses before passing to socket_connect - Properly close failed sockets before creating new ones for failover - Replace fragile str_contains(':') IPv6 detection in ping with filter_var(FILTER_FLAG_IPV6) for more robust address validation https://claude.ai/code/session_01SbuDigvAkYvPKvdcougsdo * Fix JS code quality: implicit globals, deprecated APIs, loose equality - Add var declarations to prevent implicit globals in install.js (element, enabled, button, buttonCheck) - Remove console.log debug output left in production (install.js) - Replace deprecated jQuery .unbind() with .off() (layout.js) - Fix "depreciated" typo to "deprecated" in deprecation warnings - Convert == / != to === / !== for null, boolean, string, typeof, and numeric comparisons across install.js and realtime.js https://claude.ai/code/session_01SbuDigvAkYvPKvdcougsdo * Replace deprecated jQuery shorthand event methods in layout.js Replace .click(), .keyup(), .keydown(), .mousedown(), .mouseenter(), .mouseleave(), .submit(), .resize() with .on() equivalents. Replace .focus(), .change() trigger calls with .trigger(). These shorthands were deprecated in jQuery 3.5. https://claude.ai/code/session_01SbuDigvAkYvPKvdcougsdo * Replace deprecated jQuery methods in realtime.js Replace .bind() with .on() and .change() trigger calls with .trigger('change'). .bind() was deprecated in jQuery 3.0 and shorthand triggers in jQuery 3.5. https://claude.ai/code/session_01SbuDigvAkYvPKvdcougsdo * Replace deprecated jQuery methods in install.js and fix ===/!== errors Replace .click(), .change(), .focus() with .on()/.trigger() equivalents. Also fix !=== and ==== operators that were incorrectly introduced by a prior replace-all of == to === within existing !== and === expressions. https://claude.ai/code/session_01SbuDigvAkYvPKvdcougsdo * Restore == null where needed to catch both null and undefined In JavaScript, == null matches both null and undefined, which is an intentional idiom. The prior === null conversion broke cases where values come from jQuery .val(), .data(), $.urlParam(), or object property access that may return undefined rather than null. Revert those specific cases while keeping === null where variables are explicitly initialized to null. https://claude.ai/code/session_01SbuDigvAkYvPKvdcougsdo * Replace deprecated jQuery methods in all theme main.js files Replace .unbind().click() with .off('click').on('click'), convert .hover() to .on('mouseenter').on('mouseleave'), replace .change(), .scroll(), .click() shorthands with .on() equivalents, and .blur() with .trigger('blur') across all 10 theme files. https://claude.ai/code/session_01SbuDigvAkYvPKvdcougsdo * Convert string concatenation to template literals Replace 'str' + var + 'str' patterns with ES6 template literals in realtime.js and install.js. Improves readability especially for URL construction and HTML building. Also replace $.parseJSON() with native JSON.parse() in realtime.js. https://claude.ai/code/session_01SbuDigvAkYvPKvdcougsdo * Convert var to const for single-assignment variables Replace var with const where the variable is assigned once and never reassigned within its scope, in install.js and realtime.js. Keeps var for variables that are conditionally reassigned (e.g. size, url). https://claude.ai/code/session_01SbuDigvAkYvPKvdcougsdo --------- Co-authored-by: Claude <noreply@anthropic.com> * fix(sqli): sanitize sort_column and enforce sort_direction in ORDER BY clauses Closes GHSA-q9xg-p762-9jm3 and GHSA-72vr-jr4v-55vf. - Add sanitize_sql_column() to lib/functions.php stripping all chars outside [a-zA-Z0-9_().] from user-supplied column names - Apply sanitize_sql_column() at every ORDER BY concatenation in api_automation.php and html_reports.php - Enforce direction to literal ASC/DESC in html_reports.php, api_automation.php, and html_utility.php (update_order_string, get_order_string) - Add tests/Unit/SafeSortTest.php with 22 stub-based regression tests Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(docs): move sanitize_sql_column above sanitize_search_string docblock The sanitize_search_string docblock was orphaned after sanitize_sql_column was inserted between it and the function body. Reorder so each function follows its own docblock directly. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(sqli): escape apostrophes in automation_templates and harden sort_direction in display_matching_hosts Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(sqli): restore allowlist guard, sanitize session keys, add sanitize_sql_column default Restore the preg_match allowlist guard in get_order_string() that was removed by the PR, re-opening the ORDER BY injection surface. When the request var is empty after sanitization, return '' rather than building a malformed ORDER BY clause. Sanitize sort_column before storing as SESSION array key in update_order_string() and add read-time guard in the inplace loop that rebuilds ORDER BY from stored keys. Add $default parameter to sanitize_sql_column() so callers get a safe fallback instead of an empty string that produces 'ORDER BY ASC'. Fix inconsistent INET_ATON substitution in display_matching_hosts(). Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Improve sanitize_sql_column function Fix regex to sanitize SQL column names properly. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: TheWitness <thewitness@cacti.net> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
7a1e9bbf92 |
harden CSV encoding and process execution handling (#7196)
* fix: harden CSV encoding and process execution handling Route CSV export cells through a shared cacti_csv_cell() helper so values with quotes, commas, or leading formula characters round-trip correctly, and add defensive guards on shell construction, unchecked proc_open handles, discarded exec exit codes, and gap values in nth-percentile fetch math. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: address review feedback on CSV and process hardening - escape poller_id in cmd_realtime proc_open command to match remote_agent Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: keep numeric CSV cells unquoted and guard snmptrap and rrdtool process failures - exempt numeric values from the CSV formula-injection prefix and preserve empty snmptrap positions while bailing out when the RRDtool process fails to spawn Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: ignore phpstan function.alreadyNarrowedType identifier Clears the level 6 alreadyNarrowedType error on lib/data_query.php:2591 that develop currently emits, matching the sibling narrowing identifiers already ignored. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
d9de6dff57 |
fix: logic and security bugs in 1.3.0 upgrade scripts (#7161)
* fix: correct logic and security bugs in upgrade scripts
install/upgrades/1_3_0.php:
- case 11 (hourly reports): was an unreachable duplicate case 10; fixes
all hourly report rows being silently skipped during migration
- case 11: add missing comma between enabled and recur_every in SET clause
- settings/settings_user.name: restore to varchar(255) to match final
schema; 1_2_31 widened it, 1_3_0 was incorrectly narrowing it to 75
- db_add_index -> db_install_add_key for all 5 index-creation calls so
that index failures surface in the upgrade status tracker instead of
being silently discarded
install/functions.php:
- install_unlink/install_rmdir/install_rmdir_recursive: add missing
return after the CACTI_PATH_BASE guard; without it the path-escape
log message was emitted but execution fell through to the destructive
unlink/rmdir anyway
- import_colors: replace string-interpolated SQL with prepared statements
and add hex-format validation (/^[0-9a-fA-F]{6}$/) to prevent SQL
injection from a modified colors.csv
cli/upgrade_database.php:
- use db_execute_prepared for the final version stamp to be consistent
with adjacent calls and eliminate the direct-concat pattern
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: use realpath() for canonical base-path boundary check in install helpers
str_contains($path, CACTI_PATH_BASE) is bypassable: a path like
CACTI_PATH_BASE.'/../other' contains the base as a substring but
resolves outside it. Replace with realpath() on both sides and
str_starts_with() on the resolved paths. Fail closed when realpath()
returns false (non-existent path).
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
|
||
|
|
f89c7d8254 |
fix: correct codespell-detected spelling errors in PHP source files (#6804)
* fix: correct codespell-detected spelling errors in PHP source files Fix 63 spelling errors identified by automated codespell scan across 23 files. All changes are in comments, docblocks, string literals, and one dead variable assignment ($flage → $flags in lib/database.php). No logic changes. Corrected words include: accommodate, authentication, complete, dependent, draggable, filtering, formatting, heartbeat, immediately, information, initialize, language, logging, parameter, please, resource, resetting, similarly, specified, SQLite, statistics, support, timeout, whether. Exclusions: parms/PARMS (Cacti CLI variable convention), tht/ot (SQL aliases), CurrOS/hasTables (PHP variables), pring (internal function). Fixes #6803 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: address Copilot review suggestions on PR #6804 Remove dead variable assignment activated by $postion -> $position typo fix in install/upgrades/1_0_0.php. The misspelled variable was never read, so fixing the name caused it to silently overwrite the correctly computed $position before the UPDATE. Removing the line restores the original (intended) behavior. Fix docblock possessive: "it's formatting" -> "its formatting" in lib/reports.php. Refs #6803 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * i18n: regenerate cacti.pot after msgid changes in global_settings.php "pre-selected" -> "preselected" and "re-using" -> "reusing" changed two msgid strings. Regenerate cacti.pot so translation tooling picks up the corrected English source strings. Refs #6803 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
bec0792bbb |
fix: add PHPStan level 8 type guards to install/functions.php (#6755)
* fix: add PHPStan level 8 type guards to install/functions.php
Add defensive type checks (is_array, !== false, int casts) to resolve
PHPStan level 8 errors. No behavioral changes for normal operation;
only error paths (db failure, file open failure) are affected.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: add 55 Pest tests for PHPStan type guard patterns
Cover 10 defensive guard categories with stub-based unit tests:
db result array|bool, fopen false, chown params, imagecolorallocate
clamping, session_name fallback, shell_exec cast, preg_match offsets,
version_to_bits float, pclose guard, foreach iterable guard.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: return false when fopen fails in install helpers
Ensure install_create_csrf_secret and remote_update_config_file
report failure when fopen returns false.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* revert: restore api_aggregate.php to develop state
Remove accidental deletion of aggregate_handle_stacked_lines() and
aggregate_get_data_sources() from pollution commit
|
||
|
|
3e1dc3c886 |
fix: replace strtolower/strtoupper with locale-independent wrappers (#6717)
* cleanup: remove unreachable defensive error logs in aggregate graph item validation * fix: replace strtolower/strtoupper with locale-independent wrappers PHP 8.5 deprecates locale-sensitive strtolower() and strtoupper(). Add cacti_strtolower() and cacti_strtoupper() wrappers that use mb_strtolower/mb_strtoupper with explicit UTF-8 encoding, then replace all 185 call sites across 65 files. Existing mb_strtolower() calls (email normalization) are left as-is since they already specify encoding explicitly. Includes 18 Pest tests covering ASCII, UTF-8, protocol names, config values, hex strings, NaN detection, and file extension patterns. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: correct docblock references and month_shift() strpos check Fix docblocks to reference strtolower()/strtoupper() (the deprecated PHP builtins) instead of the wrapper names. Fix month_shift() to use strpos() !== false instead of > 0 so 'month' at position 0 is matched. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * chore: drop unrelated lib/api_aggregate changes Keep PR scope focused on the issue for this branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
fcbcbdd1d7 |
refactor: replace strpos() boolean checks with str_contains/str_starts_with (#6645)
* refactor: replace strpos() boolean checks with str_contains/str_starts_with Completes the PHP 8.x string function migration started in lib/. Converts remaining legacy patterns across 81 files: - strpos($x, $y) !== false → str_contains($x, $y) - strpos($x, $y) === false → !str_contains($x, $y) - strpos($x, $y) != false → str_contains($x, $y) - bare if (strpos($x, $y)) → if (str_contains($x, $y)) - substr($x, 0, strlen($y)) == $y → str_starts_with($x, $y) Intentionally preserves strpos() calls that use the return value as an integer offset: - user_admin.php / user_group_admin.php (4 calls, ternary for position) - auth_profile.php (2 calls, offset for substr); guards fixed from bare if ($pos) to if ($pos !== false) Behavior fix: ss_esxi_vhosts.php:55 bare strpos() missed position-0 matches; str_contains() now correctly detects 'not installed' at any position. Adjacent line already used str_contains(). Ref: https://github.com/Cacti/cacti/issues/6641 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: correct dead-code elseif in plugin_display_compat() version comparison The if/elseif chain in plugin_display_compat() checked >=, >=, >, < — the second >= was unreachable dead code. Corrected to <= to complete the operator set (>=, <=, >, <). Without this fix, <= constraints (e.g. "<=1.2") fell through to the < branch and were rendered as "< =1.2" instead of "<= 1.2". This pre-existing bug was exposed when converting strpos() to str_contains(), which made PHPStan detect the always-false condition. Ref: https://github.com/Cacti/cacti/issues/6641 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: add limit to explode('=') in CLI argument parsers 17 CLI scripts used explode('=', $parameter) without a limit of 2, silently truncating values containing = (e.g. --note=a=b parsed as $value='a' instead of 'a=b'). The other 40 CLI scripts already had the correct limit. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
dfa068947b | broken cacti stats - cannot be installed (#6642) | ||
|
|
0e2a1971d9 |
Add stats servcheck (#6613)
* add servcheck stats * update |
||
|
|
27919c50d7 | Updating Copyrights to 2026 (#6578) | ||
|
|
835dc3ad87 |
1.3 repackage all (#6541)
* add interface speed * repackage all |
||
|
|
b993471579 |
Fix #6539 - Allow Standard Cacti Log Files to be Rotated on Demand,PHPdoc, and Typing (#6540)
* Fix #6539 - Allow Standard Cacti Log Files to be Rotated on Demand from the Cacti UI * QA: php-cs-fixer updates and documentation for lib/database.php * QA: Update .gitignore for composer files * QA: Update .gitignore for composer files * QA: Back off .gitignore change its duplicated * QA: Fix typing of db_has_permissions * QA: Fixing typing issue in db_format_index_create * Update lib/clog_webapi.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * QA: Fix additional code issues * QA: Applying more defaults to add device * QA: Fix at the database layer * QA: Fixes based upon automated testing * QA: Fix at the database layer * QA: After new SQL checking * QA: Skip MacTrack oui database download warnings * QA: Fixing type issue * QA: Recommendation from Copilot * QA: Recommendation from Copilot * QA: Recommendation from Copilot * QA: Recommendation from Copilot * QA: Some minor changes to avoid errors in testing * QA: Unused variable * QA: Recommendation from Copilot * QA: Recommendation from Copilot * QA: Recommendation from Copilot * QA: Minor introduced code defect * QA: Adding some unifying space around concatination * QA: Consistent comments and new function alias' * QA: Multi-line comment corrections * QA: Fix SQL Style * QA: Missed one function alias * QA: PHPStan Level 2 and PHPDoc fixes * QA: PHPStan Level 2 and PHPDoc Fixes * QA: PHPStan Level 6 Fixes * QA: Fix lib/xml.php to PHPStan Level 6 * QA: Wrong grep -v * PHPStan Level 6 for lib/vdef.php * PHPStan Level 8 for lib/time.php * QA: Raise PHPStan to Level 6 for lib/database.php * QA: Remove @deprecated tag from grv() * QA: Switch to htmle() alias over html_escape() * QA: More addressing PHPStan issues at Level 6 * QA: Change recommended by Copilot for Readability * QA: More PHPStan fixes * QA: More PHPStan fixes * QA: More PHPStan fixes for lib/utility.php * QA: More PHPStan fixes at Level 6 * QA: Raise PHPStan to Level 6 More Functions * QA: Raise PHPStan to Level 6 More Functions * QA: Raise PHPStan to Level 6 More Functions * QA: Raise PHPStan to Level 6 More Functions * QA: Raise PHPStan to Level 6 More Functions * QA: More PHPStan fixes at Level 6 * QA: Raise PHPStan to Level 6 More Functions * QA: Raise PHPStan to Level 6 More Functions * QA: More PHPStan fixes at Level 6 * QA: More PHPStan fixes at Level 6 * QA: Raise PHPStan to Level 6 More Functions * QA: Raise PHPStan to Level 6 More Functions * QA: More PHPStan fixes at Level 6 * QA: Raise PHPStan to Level 6 More Functions * QA: Raise PHPStan to Level 6 More Functions * QA: More PHPStan fixes at Level 6 * QA: Raise PHPStan to Level 6 More Functions * QA: Raise PHPStan to Level 6 More Functions * QA: More PHPStan fixes at Level 6 * QA: More PHPStan fixes at Level 6 * QA: More PHPStan fixes at Level 6 * QA: Raise PHPStan to Level 6 More Functions * QA: More PHPStan fixes at Level 6 * QA: Some phpseclib3 fixups to update to latest * QA: More fixups for graphing for magic strings * QA: More PHPStan fixes at Level 6 * QA: Fixing order string type on get_allowed_graphs() * QA: Fixing improper use of print * QA: Optimization of color_templates * QA: Copilot recommendation * QA: Copilot recommendation * QA: Copilot recommendation * QA: Copilot recommendation * QA: Copilot recommendation * QA: Copilot recommendation * QA: Copilot recommendation * QA: Copilot recommendation * QA: Raise PHPStan to Level 6 More Functions * QA: Issues insalling Cacti * QA: Issues insalling Cacti * QA: Issues insalling Cacti * QA: Issues insalling Cacti * QA: Issues insalling Cacti * QA: Issues testing Cacti * QA: Copilot recommendation * QA: More PHPStan fixes at Level 6 * QA: More PHPStan fixes at Level 6 * QA: More PHPStan fixes at Level 6 * QA: More PHPStan fixes at Level 6 * QA: More PHPStan fixes at Level 6 * QA: Installer failing * Undo ownership changes to remove tar errors * QA: Updates after php-cs-fixer * QA: Adding code checks to syntax checking * QA Change syntax flow language * QA: Reduce the number of cures * QA: Use sequentional option * Change runner to self-hosted for check job * Fix typo in OS version in workflow file * Add Git installation step in workflow Added step to install Git if not already installed. * Add '-y' flag to apt-get install and update commands Updated apt-get commands to include '-y' for automatic confirmation. * Change workflow OS version to 'ubuntu-latest' Updated the workflow to use 'ubuntu-latest' instead of 'ubuntu:latest' and modified the 'runs-on' configuration. * Remove sequential option from phpcsfixer script * Remove PHPCS fixer step from workflow Removed PHP coding standards check from workflow. * Move code checks to before plugins Moving these core checks to before the plugins are installed as we really want to check only the base Cacti here. * Add Composer validation and caching to start of workflow * QA: Addressing PHPStan additional * QA: Restore composer lock file * QA: Addressing PHPStan additional * QA: Addressing PHPStan additional * QA: Complete LDAP Project * QA: php-cs-fixer doing it's thing * QA: Syntax is already happening in PHPStan * QA: The md5 function no longer exists * QA: Update after ldap changes * QA: Undo that last password hack * QA: Attempting to switch to MariaDB 11.8 * QA: Attempting to switch to MariaDB 11.8 * QA: Resolving some issues with PHP8.4 * QA: Resolving some issues with PHP8.4 * QA: Add PHP8.5 to the matrix * QA: Removing PHP8.5 due to dependency issues * QA: Add the DB version to the testing output * QA: Switch back to MySQL for the moment * QA: More fidelity in reporting * QA: The pipe must be mixed * QA: Moving audit_schema.php changes from 1.2.x * QA: Moving audit_schema.php changes from 1.2.x * QA: Restore mysql monitoring * QA: php-cs-fixer issues * QA: Attempt to roll back to MariaDB 10.6 * QA: Attempt to fix check_all_pages.sh * QA: Make installer functional again * QA: Fix installer php-cs-fixer issues * QA: Type to a string * QA: Get the database version correctly * QA: Fix issue with reports in check_all_pages.sh * QA: Check the server version in the right place * Feature: Support SHA256 as a new hash for passwords * QA: Function definition and return typing audit * QA: Refinement of form types * QA: Refinement of form types * QA: Consistency of comments * QA: Issues with php-cs-fixer and more comments * QA: More typing issues * QA: php-cs-fixer on merge conflict resolution --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> |
||
|
|
15cad13fc4 | snmpv3 info and small css fix - icons are not visible in modern theme (#6512) | ||
|
|
54ac5e4953 |
add poller error graph to host stats (#6459)
* add poller error graph to host stats * add new graph * add host polling error |
||
|
|
629ee9bcf4 | incorrect parameter (#6498) | ||
|
|
1627c7a150 | Font Awesome Icons to Tabler Icons migration | ||
|
|
3d7cd822b8 | QA: Fixing additional files phpdoc specs | ||
|
|
b9334a15d9 | QA: Updates for files for php-cs-fixer and some php-doc changes | ||
|
|
b744b5e5f4 | add poller errors (#6458) | ||
|
|
d68b1364ac |
Fix:#6331 - Upgrading from prior to 1.0.5 generates errors
Upgrading from old versions of Cacti prior to 1_0_5 generates errors |
||
|
|
237f0ff1c4 | QA: Fixing #6331 - Incorrect calculation for profile_id | ||
|
|
6e7d95f8cb | QA: One more SSL issue | ||
|
|
48c520cd28 | QA: Correcting SSL Cert Verification Issues | ||
|
|
38d1139372 | QA: Update to latest 1.2.31 upgrader | ||
|
|
d6f403481f | QA: Brining things over from 1.2.x | ||
|
|
9ea7e73c2a | QA: Convert from input to buttons |