/
opt
/
cloudlinux
/
venv
/
lib
/
python3.11
/
site-packages
/
clwpos
/
Upload Filee
HOME
""" This module is the only one that is allowed to be imported from other tools. Please, keep list of "external" methods here, so we can keep them backwards-compatible. The purpose is to reduce number of places in code which randomly are dependencies of other tools (e.g. x-ray). Don't user this module in clwpos code. Maybe one day we will make proper api :) """ import json import logging import os import pwd import subprocess from typing import List, Optional from typing_extensions import TypedDict from clwpos.billing import get_or_create_unique_identifier from clwpos.cl_wpos_exceptions import WposError from clwpos.constants import SMART_ADVISE_USER_UTILITY, WPOS_DAEMON_SOCKET_FILE from clwpos.daemon import WposDaemon from clwpos.data_collector_utils import php_info as _php_info from clwpos.feature_suites import PremiumSuite, CDNSuitePro from clwpos.feature_suites.configurations import get_visible_modules, get_allowed_modules, get_allowed_features_dict from clwpos.optimization_features import Feature, OBJECT_CACHE_FEATURE, CDN_FEATURE, convert_feature_list_to_interface from clwpos.user.config import LicenseApproveStatus, UserConfig from clwpos.utils import ( is_wpos_supported as _is_wpos_supported, daemon_communicate, drop_permissions_if_needed, get_subscription_status, run_in_cagefs_if_needed, get_server_wide_options ) class HostInfo(TypedDict): vhost: str account: str version: str handler: str documentroot: str def php_get_vhost_versions(user: str) -> List[HostInfo]: """ Get information about vhosts in the following format: [ { 'vhost': 'username.zone', 'account': 'username', 'version': 'ea-php80', 'handler': 'php-fpm', 'documentroot': '/home/username/public_html' } ] """ return WposDaemon._php_get_vhost_versions(user) class HostInfoExtended(HostInfo): php_binary: str def php_get_vhost_versions_user() -> List[HostInfoExtended]: """ Get information about vhosts in the following format: [ { 'vhost': 'username.zone', 'account': 'username', 'version': 'ea-php80', 'php_binary': '/opt/php/php', 'handler': 'php-fpm', 'documentroot': '/home/username/public_html' } ] Unlike the php_get_vhost_versions, it automatically detects current user and also performs php version change inside the cagefs, providing additional information about php binary. """ result = _php_info() return [ HostInfoExtended( vhost=php_data['vhost'], account=php_data['account'], version=php_data['version'].identifier, php_binary=php_data['version'].bin, handler=php_data['handler'], documentroot=php_data['documentroot'], ) for php_data in result ] def is_wpos_supported() -> bool: """ Determines whether wpos is supported in current environment """ return _is_wpos_supported() def is_feature_visible(feature: str, username: str) -> Optional[bool]: """ Determines whether feature is visible for username. Return False in case if feature is unknown. Return None in case if user is missing. """ try: pw = pwd.getpwnam(username) except KeyError: return None return Feature(feature).NAME in ( item.NAME for item in get_visible_modules(uid=pw.pw_uid) ) def is_feature_hidden_server_wide(feature: str): return Feature(feature).optimization_feature() in get_server_wide_options().hidden_features def is_feature_allowed(feature: str, username: str) -> Optional[bool]: """ Determines whether feature is allowed for username to be activated. Return False in case if feature is unknown. Return None in case if user is missing. """ try: pw = pwd.getpwnam(username) except KeyError: return None return Feature(feature).NAME in ( item.NAME for item in get_allowed_modules(uid=pw.pw_uid) ) def is_subscription_pending(feature: str, username: str) -> Optional[bool]: """ Determines whether feature is allowed for username to be activated. """ try: pw = pwd.getpwnam(username) except KeyError: return None try: is_pending = daemon_communicate({ "command": WposDaemon.DAEMON_GET_UPGRADE_ATTEMPT_STATUS, "feature": feature, "uid": pw.pw_uid })["pending"] except WposError: return False return is_pending def get_subscription_upgrade_url(feature: str, username: str) -> Optional[str]: """ Determines whether feature is allowed for username to be activated. """ try: pw = pwd.getpwnam(username) except KeyError: return None try: upgrade_url = daemon_communicate({ "command": WposDaemon.DAEMON_GET_UPGRADE_LINK_COMMAND, "uid": pw.pw_uid, "feature": feature })["upgrade_url"] except WposError: return None return upgrade_url def _get_cdn_usage_statistics(username): """Wrapper for easy mocking""" with drop_permissions_if_needed(username): get_usage_command = [SMART_ADVISE_USER_UTILITY, 'get-cdn-usage'] output = run_in_cagefs_if_needed(get_usage_command, check=True).stdout try: return json.loads(output)['data'] except KeyError: raise subprocess.CalledProcessError( returncode=0, cmd=get_usage_command, output=output) def get_subscriptions_info(username: str): """ Backwards compatibility for the third party tools. Use get_subscriptions_info_2 instead. """ try: user_pw = pwd.getpwnam(username) except Exception: logging.error('Cannot get uid for user: %s, subscription info will be empty', username) return {} return get_subscriptions_by_pw(user_pw) def get_subscriptions_by_pw(user_pw: pwd.struct_passwd): subscriptions = {} allowed_features_dict = get_allowed_features_dict(user_pw.pw_uid) converted_allowed_features = {feature_set: convert_feature_list_to_interface(features) for feature_set, features in allowed_features_dict.items()} for feature, suite_name in ( (OBJECT_CACHE_FEATURE.NAME, PremiumSuite.name), (CDN_FEATURE.NAME, CDNSuitePro.name) ): subscriptions[feature.lower()] = subscription = dict( status=get_subscription_status( converted_allowed_features, suite_name, feature.lower()) ) if feature == CDN_FEATURE.NAME: usage = None if is_feature_allowed(feature.lower(), user_pw.pw_name): try: info = _get_cdn_usage_statistics(user_pw.pw_name) except Exception as e: logging.warning("Error during obtaining usage, error: %s", str(e)) usage = None else: usage = calculate_cdn_usage(info) subscription['usage'] = usage return subscriptions def calculate_cdn_usage(info): warning = f"Content Delivery Network: You have reached your " \ f"{info['limit_bytes'] // 1024 ** 3}GB limit. " \ f"Please upgrade your subscription." \ f"" if info['total_bytes_used'] >= info['limit_bytes'] else None bytes_used = info['total_bytes_used'] bytes_limit = info['limit_bytes'] # no usage ---> no warning if bytes_used == 0 or bytes_limit == 0: warning = None usage = { "bandwidth": { "usage": bytes_used, "limit": bytes_limit }, "warning": warning } return usage def get_user_auth_key(username: str): """ Reads configuration and gets identifier of user that he can use to auth on provisioning server """ return get_or_create_unique_identifier(username) def get_license_approve_status(feature: str, username: str) -> LicenseApproveStatus: """ Provides easy way for 3rd party tools to get information about license approve status """ with drop_permissions_if_needed(username): uc = UserConfig(username) feature = Feature(feature) return uc.get_license_approve_status(feature) def approve_license_agreement(feature: str, username: str): """ Provides easy way for 3rd party tools to approve license """ with drop_permissions_if_needed(username): uc = UserConfig(username) feature = Feature(feature) uc.approve_license_agreement(feature) def get_license_agreement_text(feature: str, username: str): """ Returns text of the license agreement for the given feature or None if feature is not required to approve agreement """ with drop_permissions_if_needed(username): feature = Feature(feature) if not feature.HAS_LICENSE_TERMS: return None return open(feature.LICENSE_TERMS_PATH).read() def is_wpos_visible(username: str) -> Optional[bool]: """ Determines whether at least one feature is allowed for username to be activated. """ try: pw = pwd.getpwnam(username) except KeyError: return None return bool(get_visible_modules(uid=pw.pw_uid)) def is_smart_advice_notifications_disabled_server_wide() -> Optional[bool]: """ Returns whether WordPress SmartAdvice notifications are disabled. """ return get_server_wide_options().disable_smart_advice_notifications def is_smart_advice_wordpress_plugin_disabled_server_wide() -> Optional[bool]: """ Returns whether WordPress SmartAdvice plugin is disabled on this server. """ return get_server_wide_options().disable_smart_advice_wordpress_plugin def is_smart_advice_reminders_disabled_server_wide() -> Optional[bool]: return get_server_wide_options().disable_smart_advice_reminders