diff --git a/pluginHolder/discordWebhooks.zip b/pluginHolder/discordWebhooks.zip new file mode 100644 index 000000000..f7cc151de Binary files /dev/null and b/pluginHolder/discordWebhooks.zip differ diff --git a/pluginHolder/fail2ban.zip b/pluginHolder/fail2ban.zip new file mode 100644 index 000000000..da2df290e Binary files /dev/null and b/pluginHolder/fail2ban.zip differ diff --git a/pluginHolder/patreon_verifier.py b/pluginHolder/patreon_verifier.py new file mode 100644 index 000000000..42fd5cfed --- /dev/null +++ b/pluginHolder/patreon_verifier.py @@ -0,0 +1,245 @@ +# -*- coding: utf-8 -*- +""" +Patreon Verifier for CyberPanel Plugins +Verifies Patreon membership status for paid plugins +""" + +import urllib.request +import urllib.error +import json +import os +import sys + +# Patreon API configuration +PATREON_API_BASE = 'https://www.patreon.com/api/oauth2/v2' +PATREON_MEMBERSHIP_TIER = 'CyberPanel Paid Plugin' # The membership tier name to check + +class PatreonVerifier: + """ + Verifies Patreon membership status for CyberPanel users + """ + + def __init__(self): + """Initialize Patreon verifier""" + # Try to import from Django settings first, then fallback to environment + try: + from django.conf import settings + self.client_id = getattr(settings, 'PATREON_CLIENT_ID', os.environ.get('PATREON_CLIENT_ID', '')) + self.client_secret = getattr(settings, 'PATREON_CLIENT_SECRET', os.environ.get('PATREON_CLIENT_SECRET', '')) + self.creator_id = getattr(settings, 'PATREON_CREATOR_ID', os.environ.get('PATREON_CREATOR_ID', '')) + self.membership_tier_id = getattr(settings, 'PATREON_MEMBERSHIP_TIER_ID', os.environ.get('PATREON_MEMBERSHIP_TIER_ID', '27789984')) + self.creator_access_token = getattr(settings, 'PATREON_CREATOR_ACCESS_TOKEN', os.environ.get('PATREON_CREATOR_ACCESS_TOKEN', '')) + except: + # Fallback to environment variables only + self.client_id = os.environ.get('PATREON_CLIENT_ID', '') + self.client_secret = os.environ.get('PATREON_CLIENT_SECRET', '') + self.creator_id = os.environ.get('PATREON_CREATOR_ID', '') + self.membership_tier_id = os.environ.get('PATREON_MEMBERSHIP_TIER_ID', '27789984') + self.creator_access_token = os.environ.get('PATREON_CREATOR_ACCESS_TOKEN', '') + + # Cache for membership checks (to avoid excessive API calls) + self.cache_file = '/home/cyberpanel/patreon_cache.json' + self.cache_duration = 300 # Cache for 5 minutes + + def get_user_patreon_token(self, user_email): + """ + Get stored Patreon access token for a user + This should be stored when user authorizes via Patreon OAuth + """ + # In a real implementation, you'd store this in a database + # For now, we'll check if there's a stored token file + token_file = f'/home/cyberpanel/patreon_tokens/{user_email}.token' + if os.path.exists(token_file): + try: + with open(token_file, 'r') as f: + token_data = json.load(f) + return token_data.get('access_token') + except: + return None + return None + + def check_membership_cached(self, user_email): + """ + Check membership with caching + """ + import time + cache_data = self._load_cache() + cache_key = f"membership_{user_email}" + + if cache_key in cache_data: + cached_result = cache_data[cache_key] + if time.time() - cached_result.get('timestamp', 0) < self.cache_duration: + return cached_result.get('has_membership', False) + + # Check membership via API + has_membership = self.check_membership(user_email) + + # Update cache + cache_data[cache_key] = { + 'has_membership': has_membership, + 'timestamp': time.time() + } + self._save_cache(cache_data) + + return has_membership + + def check_membership(self, user_email): + """ + Check if user has active Patreon membership for 'CyberPanel Paid Plugin' + + Args: + user_email: User's email address + + Returns: + bool: True if user has active membership, False otherwise + """ + access_token = self.get_user_patreon_token(user_email) + + if not access_token: + return False + + try: + # Get user's identity + user_info = self._get_user_identity(access_token) + if not user_info: + return False + + member_id = user_info.get('id') + if not member_id: + return False + + # Get user's memberships + memberships = self._get_memberships(access_token, member_id) + if not memberships: + return False + + # Check if user has the required membership tier + # First try to match by tier ID (more accurate) + for membership in memberships: + tier_id = membership.get('id', '') + tier_name = membership.get('attributes', {}).get('title', '') + + # Check by tier ID first (most accurate) + if tier_id == self.membership_tier_id: + # Check if membership is active + status = membership.get('attributes', {}).get('patron_status', '') + if status in ['active_patron', 'former_patron']: + # Check if currently entitled + entitled = membership.get('attributes', {}).get('currently_entitled_amount_cents', 0) + if entitled > 0: + return True + + # Fallback: Check by tier name (for compatibility) + if PATREON_MEMBERSHIP_TIER.lower() in tier_name.lower(): + # Check if membership is active + status = membership.get('attributes', {}).get('patron_status', '') + if status in ['active_patron', 'former_patron']: + # Check if currently entitled + entitled = membership.get('attributes', {}).get('currently_entitled_amount_cents', 0) + if entitled > 0: + return True + + return False + + except Exception as e: + import logging + logging.writeToFile(f"Error checking Patreon membership for {user_email}: {str(e)}") + return False + + def _get_user_identity(self, access_token): + """ + Get user identity from Patreon API + """ + url = f"{PATREON_API_BASE}/identity" + + try: + req = urllib.request.Request(url) + req.add_header('Authorization', f'Bearer {access_token}') + + with urllib.request.urlopen(req) as response: + data = json.loads(response.read().decode()) + return data.get('data', {}) + except Exception as e: + import logging + logging.writeToFile(f"Error getting Patreon identity: {str(e)}") + return None + + def _get_memberships(self, access_token, member_id): + """ + Get user's memberships from Patreon API + """ + url = f"{PATREON_API_BASE}/members/{member_id}?include=currently_entitled_tiers" + + try: + req = urllib.request.Request(url) + req.add_header('Authorization', f'Bearer {access_token}') + + with urllib.request.urlopen(req) as response: + data = json.loads(response.read().decode()) + + # Parse included tiers + memberships = [] + included = data.get('included', []) + + for item in included: + if item.get('type') == 'tier': + # Include tier ID in the membership data + tier_data = { + 'id': item.get('id', ''), + 'type': item.get('type', ''), + 'attributes': item.get('attributes', {}) + } + memberships.append(tier_data) + + return memberships + except Exception as e: + import logging + logging.writeToFile(f"Error getting Patreon memberships: {str(e)}") + return [] + + def _load_cache(self): + """Load cache from file""" + if os.path.exists(self.cache_file): + try: + with open(self.cache_file, 'r') as f: + return json.load(f) + except: + return {} + return {} + + def _save_cache(self, cache_data): + """Save cache to file""" + try: + os.makedirs(os.path.dirname(self.cache_file), exist_ok=True) + with open(self.cache_file, 'w') as f: + json.dump(cache_data, f) + except Exception as e: + import logging + logging.writeToFile(f"Error saving Patreon cache: {str(e)}") + + def verify_plugin_access(self, user_email, plugin_name): + """ + Verify if user can access a paid plugin + + Args: + user_email: User's email address + plugin_name: Name of the plugin to check + + Returns: + dict: { + 'has_access': bool, + 'is_paid': bool, + 'message': str + } + """ + # Check if plugin is paid (this will be checked from meta.xml) + # For now, we'll assume the plugin system will pass this info + + # Check membership + has_membership = self.check_membership_cached(user_email) + + return { + 'has_access': has_membership, + 'is_paid': True, # This will be determined by plugin metadata + 'message': 'Access granted' if has_membership else 'Patreon subscription required' + } diff --git a/pluginHolder/plugin_access.py b/pluginHolder/plugin_access.py new file mode 100644 index 000000000..f460e0768 --- /dev/null +++ b/pluginHolder/plugin_access.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +""" +Plugin Access Control +Checks if user has access to paid plugins +""" + +from .patreon_verifier import PatreonVerifier +import logging + +def check_plugin_access(request, plugin_name, plugin_meta=None): + """ + Check if user has access to a plugin + + Args: + request: Django request object + plugin_name: Name of the plugin + plugin_meta: Plugin metadata dict (optional, will be loaded if not provided) + + Returns: + dict: { + 'has_access': bool, + 'is_paid': bool, + 'message': str, + 'patreon_url': str or None + } + """ + # Default response for free plugins + default_response = { + 'has_access': True, + 'is_paid': False, + 'message': 'Access granted', + 'patreon_url': None + } + + # If plugin_meta not provided, try to load it + if plugin_meta is None: + plugin_meta = _load_plugin_meta(plugin_name) + + # Check if plugin is paid + if not plugin_meta or not plugin_meta.get('is_paid', False): + return default_response + + # Plugin is paid - check Patreon membership + if not request.user or not request.user.is_authenticated: + return { + 'has_access': False, + 'is_paid': True, + 'message': 'Please log in to access this plugin', + 'patreon_url': plugin_meta.get('patreon_url') + } + + # Get user email + user_email = getattr(request.user, 'email', None) + if not user_email: + # Try to get from username or other fields + user_email = getattr(request.user, 'username', '') + + if not user_email: + return { + 'has_access': False, + 'is_paid': True, + 'message': 'Unable to verify user identity', + 'patreon_url': plugin_meta.get('patreon_url') + } + + # Check Patreon membership + verifier = PatreonVerifier() + has_membership = verifier.check_membership_cached(user_email) + + if has_membership: + return { + 'has_access': True, + 'is_paid': True, + 'message': 'Access granted', + 'patreon_url': None + } + else: + return { + 'has_access': False, + 'is_paid': True, + 'message': f'This plugin requires a Patreon subscription to "{plugin_meta.get("patreon_tier", "CyberPanel Paid Plugin")}"', + 'patreon_url': plugin_meta.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership') + } + +def _load_plugin_meta(plugin_name): + """ + Load plugin metadata from meta.xml + + Args: + plugin_name: Name of the plugin + + Returns: + dict: Plugin metadata or None + """ + import os + from xml.etree import ElementTree + + installed_path = f'/usr/local/CyberCP/{plugin_name}/meta.xml' + source_path = f'/home/cyberpanel/plugins/{plugin_name}/meta.xml' + + meta_path = None + if os.path.exists(installed_path): + meta_path = installed_path + elif os.path.exists(source_path): + meta_path = source_path + + if not meta_path: + return None + + try: + tree = ElementTree.parse(meta_path) + root = tree.getroot() + + # Extract paid plugin information + paid_elem = root.find('paid') + patreon_tier_elem = root.find('patreon_tier') + patreon_url_elem = root.find('patreon_url') + + is_paid = False + if paid_elem is not None and paid_elem.text and paid_elem.text.lower() == 'true': + is_paid = True + + return { + 'is_paid': is_paid, + 'patreon_tier': patreon_tier_elem.text if patreon_tier_elem is not None and patreon_tier_elem.text else 'CyberPanel Paid Plugin', + 'patreon_url': patreon_url_elem.text if patreon_url_elem is not None else 'https://www.patreon.com/c/newstargeted/membership' + } + except Exception as e: + logging.writeToFile(f"Error loading plugin meta for {plugin_name}: {str(e)}") + return None diff --git a/pluginHolder/templates/pluginHolder/plugin_help.html b/pluginHolder/templates/pluginHolder/plugin_help.html new file mode 100644 index 000000000..40b340c67 --- /dev/null +++ b/pluginHolder/templates/pluginHolder/plugin_help.html @@ -0,0 +1,351 @@ +{% extends "baseTemplate/index.html" %} +{% load i18n %} +{% block title %}{% trans "Plugin Help - " %}{{ plugin_name }} - CyberPanel{% endblock %} + +{% block header_scripts %} + +{% endblock %} + +{% block content %} +{% load static %} + +
+ {{ plugin_description }} +
+ {% endif %} + +| {% trans "Icon" %} | {% trans "Plugin Name" %} | -{% trans "Author" %} | {% trans "Version" %} | +{% trans "Pricing" %} | {% trans "Modify Date" %} | -{% trans "Status" %} | {% trans "Action" %} | -{% trans "Active" %} | {% trans "Help" %} | {% trans "About" %} |
|---|