5LyS ddlZddlZddlZddlmZddlmZmZddlm Z ddl m Z m Z ddl mZejeZedZded ee fd Zd ejd eefd Zd ee fd Zd ee fdZdee d dfdZded ee fdZde ded dfdZ d+dee d ee fdZde ded dfdZ de d dfdZ! d,de"dzde"dzde"d e#e"ee ffdZ$d ee fdZ%d eed ee fd!Z&d"Z'e ed#d$d%e'&de d e"fd'Z(d ee fd(Z)d ee fd)Z*de d dfd*Z+dS)-N)Path)SqliteDatabaseOperationalError)retry_on)WPSite WordpressSite) PLUGIN_SLUGzD/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3pathreturnc<ts;tdt tt St td|d}d|DS)a Get a list of WordPress sites that match the given path. Args: path: The path to search for WordPress sites. Note: The same WordPress site (real_path) can appear in multiple reports if it was scanned directly and also as part of a parent folder scan. We use only the entry from the latest report for each real_path. Returns: A list of WPSite objects that match the path. -App detector database '%s' couldn't be found.a WITH latest_reports AS ( SELECT id, uid, domain, dir FROM report WHERE id IN ( SELECT MAX(id) FROM report WHERE domain IS NOT NULL AND domain != '' GROUP BY dir ) ), -- Get all WordPress sites with their report IDs all_wp_sites AS ( SELECT wp.real_path, lr.domain, lr.uid, lr.id as report_id FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE 'a%' AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' ) -- For each real_path, keep only the entry from the latest report SELECT real_path, domain, uid FROM all_wp_sites WHERE (real_path, report_id) IN ( SELECT real_path, MAX(report_id) FROM all_wp_sites GROUP BY real_path ) c pg|]3}t|d|dt|d4Sr)docrootdomainuidrint.0rows ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/site_repository.py z%get_sites_by_path..II     s1vc!f#c!f++>>>   ) COMPONENTS_DB_PATHexistsloggererrorstrlistr execute_sqlfetchall)r cursors rget_sites_by_pathr's  $ $ & & ; " # #   vv . / / ; ; ()-)   !!FD  ??$$   r user_infoctr|;tdt tt S|(tdt St td|jd}d| DS)aq Get a set of paths to WordPress sites belonging to a particular user. Paths are sorted by their length to make sure that the main site is the last one in the list. The data is pulled from the app-version-detector database. Args: user_info: The user info with ID to get sites for. Returns: A list of paths to WordPress sites. Nr z'No user info provided for getting sitesz WITH latest_reports AS ( SELECT MAX(id) as id, dir FROM report WHERE uid = a GROUP BY dir ) SELECT wp.real_path FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' GROUP BY wp.real_path ORDER BY length(wp.real_path) DESC cg|] }|d S)rrs rrz&get_sites_for_user..zs 0 0 0sCF 0 0 0r) rrr r!r"r#rr$pw_uidr%)r(r&s rget_sites_for_userr-Os  $ $ & &)*; ; " # #   vv  5   vv . / / ; ; '-    F& 1 0foo// 0 0 00rcbts;tdt tt St tdtj ddd}d| DS)a Get a set of wp sites where imunify-security plugin is not installed. The data is pulled from the app-version-detector database. Note: The same WordPress site (real_path) can appear in multiple reports if it was scanned directly and also as part of a parent folder scan. We use only the entry from the latest report for each real_path. Returns: A set of WPSite objects where the plugin is not installed. r a WITH latest_reports AS ( SELECT id, uid, domain, dir FROM report WHERE id IN ( SELECT MAX(id) FROM report WHERE domain IS NOT NULL AND domain != '' GROUP BY dir ) ), -- Get all WordPress sites with their report IDs. -- The real_path LIKE guard filters out orphaned apps rows -- left behind when AVD rescans and rebuilds the report table. all_wp_sites AS ( SELECT wp.id as wp_id, wp.real_path, lr.domain, lr.uid, lr.id as report_id FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' ), -- For each real_path, keep only the entry from the latest report latest_wp_sites AS ( SELECT wp_id, real_path, domain, uid, report_id FROM all_wp_sites WHERE (real_path, report_id) IN ( SELECT real_path, MAX(report_id) FROM all_wp_sites GROUP BY real_path ) ) SELECT real_path, domain, uid FROM latest_wp_sites lws WHERE NOT EXISTS ( SELECT 1 FROM apps AS plugin WHERE plugin.parent_id = lws.wp_id AND plugin.title = 'wp_plugin_-_' ) c ph|]3}t|d|dt|d4Srrrs r z+get_sites_without_plugin..rr rrr r!r"setrr$r replacer%r&s rget_sites_without_pluginr8}  $ $ & & ; " # #   uu . / / ; ;+ R0;/B3/L/LS+ + + --F\  ??$$   rct}dtjtjDfd|DS)a Get a set of WordPress sites where we need to install the plugin. This is determined by finding sites that don't have the plugin installed and are not already tracked in our database. Returns: A set of WPSite objects where the plugin needs to be installed. ch|] }|j Sr+rrrs rr3z'get_sites_to_install..s' rc&h|] }|jv |Sr+r<)rsexisting_docrootss rr3z'get_sites_to_install..s-   19.sH  +<x<'+    r)r insert_manyexecute)rEs rinsert_installed_sitesrNsP     giiiiirlatest_versionc|stdgSdtjtjtj|kDS)a Get a list of WordPress sites that have outdated plugin versions. Args: latest_version: The latest available plugin version to compare against. Returns: A list of WPSite objects that have versions older than latest_version. z8Cannot get outdated sites without a valid latest versionc6g|]}tj|Sr+rfrom_wordpress_siter=s rrz&get_outdated_sites..3     "1%%   r)r r!rrBwhererIis_nullrH)rOs rget_outdated_sitesrWs  F      %''--  - 5 5 7 7  !^ 3     rrK timestampctd||tj|tj|jkdS)z Mark a WordPress site as manually deleted in the database. Args: site: The WPSite object to mark as deleted timestamp: The timestamp when the site was deleted z:Mark site %s as manually deleted at %s (WP-Plugin removed)rINr inforupdaterUrrM)rKrXs rmark_site_as_manually_deletedr^ s\ KKD  ;;; }$ 4 5 5 rfreshly_installed_sitesc2t}d|D}dtjtjD|t z}|r|d|Dz}fd|DS)a Get a set of WordPress sites that should be marked as manually deleted. These are sites that are in our database but no longer have the plugin installed. Args: freshly_installed_sites: Optional set of sites that were just installed and should be excluded from being marked as manually deleted to avoid race conditions. Returns: set[WPSite]: A set of WordPress sites that should be marked as manually deleted ch|] }|j Sr+r<rr@s rr3z8get_sites_to_mark_as_manually_deleted..-sGGGQqyGGGrcBi|]}|jtj|Sr+)rrrSr=s r z9get_sites_to_mark_as_manually_deleted..0s7  6-a00rch|] }|j Sr+r<rbs rr3z8get_sites_to_mark_as_manually_deleted..<sHHH1QYHHHrc h|] }| Sr+r+)rdactive_db_sitess rr3z8get_sites_to_mark_as_manually_deleted..>s 9 9 91OA  9 9 9r)r8rrBrUrIrVr5)r_rCdocroots_without_plugindocroots_to_markrhs @r%get_sites_to_mark_as_manually_deletedrks455GG2FGGG%''--  - 5 5 7 7  O/_1E1EEIHH0GHHHH 9 9 9 9(8 9 9 99rrHctj|tj|jkdS)z Update the version of a WordPress site in the database. Args: site: The WPSite object to update version: The new version to set )rHN)rr]rUrrM)rKrHs rupdate_site_versionrmAsA)))//- giiiiirctj|j|jtj|jkdS)z Update the domain and uid of a WordPress site in the database to match what AVD currently reports. Args: site: The WPSite object with the current domain and uid from AVD. )rrN)rr]rrrUrrMrKs rupdate_site_identityrpNsG :::@@- giiiiirrlimitoffsetc~tjtjd}|#|tj|k}|}|||}|dkr||}d|D}||fS)a/ Get active installed WordPress sites with optional filtering and pagination. Args: uid: Optional user ID to filter sites by owner limit: Maximum number of sites to return offset: Number of sites to skip Returns: Tuple of (total_count, paginated_sites) TNrc6g|]}tj|Sr+rRrJs rrz1get_installed_sites_paginated..xs# @ @ @$V ' - - @ @ @r) rrBrUrIrVrcountrqrr)rrqrrquery total_countrEs rget_installed_sites_paginatedrx[s  " " ( ()11$77  E  M-455++--K  E"" zz V$$ @ @% @ @ @E  rc(t\}}|S)z Get a list of active installed WordPress sites. These are sites that haven't been marked as manually deleted. Returns: A list of WPSite objects representing non-deleted sites. )rx)r0rEs rget_installed_sitesrz|s-..HAu Lrdomainsc|sgSdtjtjdtj|DS)z Get active installed WordPress sites filtered by domain names. Args: domains: List of domain names to filter by Returns: List of WPSite objects matching the given domains c6g|]}tj|Sr+rRr=s rrz2get_installed_sites_by_domains..rTrT)rrBrUrIrVrin_)r{s rget_installed_sites_by_domainsrss    %''--  - 5 5d ; ;  $ $W - -     rc>Ktjdd{VdS)Ng?)asynciosleep) exceptionattempts rsleep_on_errorrs. -  rTF) max_triessilentlogon_errorctjtj|jkS)a" Delete a WordPress site from the database with retry logic. Will retry up to 3 times on database operational errors with 0.5s delay between attempts. Args: site: The WPSite object to delete Returns: The number of rows affected by the delete operation )rdeleterUrrMros r delete_siters5&  }$ 4 5 5 rcbts;tdt tt St tdtj ddd}d| DS)a Get a set of WordPress sites where the imunify-security plugin is installed. The data is pulled from the app-version-detector database. Note: The same WordPress site (real_path) can appear in multiple reports if it was scanned directly and also as part of a parent folder scan. We use only the entry from the latest report for each real_path. Returns: A set of WPSite objects where the plugin is installed. r a WITH latest_reports AS ( SELECT id, uid, domain, dir FROM report WHERE id IN ( SELECT MAX(id) FROM report WHERE domain IS NOT NULL AND domain != '' GROUP BY dir ) ), -- Get all WordPress sites with their report IDs. -- The real_path LIKE guard filters out orphaned apps rows -- left behind when AVD rescans and rebuilds the report table. all_wp_sites AS ( SELECT wp.id as wp_id, wp.real_path, lr.domain, lr.uid, lr.id as report_id FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' ), -- For each real_path, keep only the entry from the latest report latest_wp_sites AS ( SELECT wp_id, real_path, domain, uid, report_id FROM all_wp_sites WHERE (real_path, report_id) IN ( SELECT real_path, MAX(report_id) FROM all_wp_sites GROUP BY real_path ) ) SELECT real_path, domain, uid FROM latest_wp_sites lws WHERE EXISTS ( SELECT 1 FROM apps AS plugin WHERE plugin.parent_id = lws.wp_id AND plugin.title = 'wp_plugin_r/r0r1c ph|]3}t|d|dt|d4Srrrs rr3z(get_sites_with_plugin..rrr4r7s rget_sites_with_pluginrr9rct}dtjtjtjdDfd|DS)aI Get a set of WordPress sites that should be adopted. These are sites where the plugin is installed but either: - Not tracked in our database (e.g., copied/migrated sites) - Flagged as manually removed (from past bugs or manual reinstall) Returns: A set of WPSite objects that should be adopted. ch|] }|j Sr+r<r=s rr3z%get_sites_to_adopt..s*  rTc&h|] }|jv |Sr+r<)rr@tracked_docrootss rr3z%get_sites_to_adopt..s& N N N!AI=M,M,MA,M,M,Mr)rrrBrrUrIrV)sites_with_pluginrs @rget_sites_to_adoptrs.//%m&;<<BB  - 5 5d ; ;   O N N N( N N NNrctd|tjdtj|jkdS)z Clear the manually_deleted_at flag for a WordPress site. This is used when adopting a site that was previously marked as manually deleted. Args: site: The WPSite object to clear the flag for zrs 33333333******AAAAAAAA;;;;;;  8 $ $TJ :C:DL::::z+1#"3+1S +1+1+1+1\E#f+EEEEPc&k$#f+$2stF|25T*,0!:!: [!:[!:!:!:!:H f s t     v $     t :  3V  B T&\    DI$v,,    f$Es6{EEEEPOCKOOOO.fr