├── API.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── DOCUMENTATION.md ├── INSTALL.md ├── README.md ├── SECURITY.md ├── admin ├── admin-page.php └── api-keys-page.php ├── images ├── API_Keys.png ├── Media_Library.png └── Secure_Updates_Server_Settings01.png └── secure-updates-server.php /API.md: -------------------------------------------------------------------------------- 1 | # Secure Updates Server API Documentation 2 | 3 | The Secure Updates Server plugin provides several REST API endpoints for managing plugin updates securely. All endpoints are prefixed with `/wp-json/secure-updates-server/v1`. 4 | 5 | ## Authentication 6 | 7 | Most endpoints are public except for the Plugin List endpoint which requires authentication using a Bearer token. To authenticate: 8 | 9 | 1. Add your API key in the WordPress admin under Secure Updates Server > API Keys 10 | 2. Include the API key in requests using the Authorization header: 11 | ``` 12 | Authorization: Bearer your-api-key-here 13 | ``` 14 | 15 | ## Endpoints 16 | 17 | ### Download Plugin 18 | Get the latest version of a plugin package. 19 | 20 | ``` 21 | GET /download/{slug} 22 | ``` 23 | 24 | **Parameters:** 25 | - `slug` (path parameter): The plugin slug (e.g., "my-plugin") 26 | 27 | **Response:** 28 | - Success: Returns the plugin ZIP file for download 29 | - Error (404): Plugin not found or file doesn't exist 30 | ```json 31 | { 32 | "code": "plugin_not_found", 33 | "message": "Plugin not found or file does not exist.", 34 | "status": 404 35 | } 36 | ``` 37 | 38 | ### Get Plugin Information 39 | Retrieve metadata about a specific plugin. 40 | 41 | ``` 42 | GET /info/{slug} 43 | ``` 44 | 45 | **Parameters:** 46 | - `slug` (path parameter): The plugin slug 47 | 48 | **Response:** 49 | - Success: 50 | ```json 51 | { 52 | "name": "Plugin Name", 53 | "slug": "plugin-slug", 54 | "version": "1.0.0", 55 | "author": "Author Name", 56 | "homepage": "https://example.com", 57 | "download_link": "https://example.com/wp-json/secure-updates-server/v1/download/plugin-slug", 58 | "sections": { 59 | "description": "Plugin description here.", 60 | "installation": "Installation instructions here.", 61 | "changelog": "Changelog here." 62 | } 63 | } 64 | ``` 65 | - Error (404): Plugin not found 66 | ```json 67 | { 68 | "code": "plugin_not_found", 69 | "message": "Plugin not found", 70 | "status": 404 71 | } 72 | ``` 73 | 74 | ### Check Connection 75 | Test if the update server is accessible and functioning. 76 | 77 | ``` 78 | GET /connected 79 | ``` 80 | 81 | **Response:** 82 | ```json 83 | { 84 | "status": "connected" 85 | } 86 | ``` 87 | 88 | ### Verify Plugin File 89 | Check if a plugin file exists and get its verification information. 90 | 91 | ``` 92 | GET /verify_file/{slug} 93 | ``` 94 | 95 | **Parameters:** 96 | - `slug` (path parameter): The plugin slug 97 | 98 | **Response:** 99 | - Success: 100 | ```json 101 | { 102 | "status": "success", 103 | "message": "Plugin file is correctly hosted and accessible.", 104 | "file_exists": true, 105 | "file_path": "/path/to/plugin.zip", 106 | "checksum": "sha256-hash-of-file" 107 | } 108 | ``` 109 | - Error (404): Plugin or file not found 110 | ```json 111 | { 112 | "code": "plugin_not_found", 113 | "message": "Plugin not found.", 114 | "status": 404 115 | } 116 | ``` 117 | or 118 | ```json 119 | { 120 | "code": "file_not_found", 121 | "message": "Plugin file does not exist on the server.", 122 | "status": 404 123 | } 124 | ``` 125 | 126 | ### Manage Plugin List 127 | Add or update multiple plugins on the server. 128 | 129 | ``` 130 | POST /plugins 131 | ``` 132 | 133 | **Authentication Required**: Bearer token in Authorization header 134 | 135 | **Request Body:** 136 | ```json 137 | { 138 | "plugins": ["plugin-slug-1", "plugin-slug-2"] 139 | } 140 | ``` 141 | 142 | **Response:** 143 | - Success: 144 | ```json 145 | { 146 | "status": "success", 147 | "plugins": { 148 | "plugin-slug-1": "already mirrored", 149 | "plugin-slug-2": "mirrored successfully" 150 | } 151 | } 152 | ``` 153 | - Error (400): Invalid request 154 | ```json 155 | { 156 | "code": "invalid_request", 157 | "message": "Invalid plugin list.", 158 | "status": 400 159 | } 160 | ``` 161 | - Error (401): Unauthorized 162 | ```json 163 | { 164 | "code": "rest_forbidden", 165 | "message": "Sorry, you are not allowed to do that." 166 | } 167 | ``` 168 | 169 | ## Error Handling 170 | 171 | All endpoints return appropriate HTTP status codes: 172 | - 200: Successful request 173 | - 400: Bad request (invalid parameters) 174 | - 401: Unauthorized (invalid or missing API key) 175 | - 404: Resource not found 176 | - 500: Server error 177 | 178 | Error responses follow the WordPress REST API format: 179 | ```json 180 | { 181 | "code": "error_code", 182 | "message": "Human readable error message", 183 | "status": 400 184 | } 185 | ``` -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to the Secure Updates Server project will be documented in this file. 4 | 5 | ## [4.0] - 2024-11-05 6 | 7 | ### Added 8 | - API Key Management interface for secure client authentication 9 | - New REST API endpoint for plugin list handling 10 | - Direct plugin uploads with versioning support 11 | - Media Library integration for plugin storage 12 | - Enhanced admin dashboard with version histories 13 | - Improved logging and user notifications 14 | 15 | ### Changed 16 | - Updated scheduled tasks to hourly checks 17 | - Strengthened security measures across operations 18 | - Enhanced admin interface for better usability 19 | 20 | ### Security 21 | - Implemented rigorous input validation 22 | - Added nonce verification 23 | - Enhanced permission checks 24 | 25 | ## [3.0] 26 | 27 | ### Added 28 | - Direct plugin upload capability 29 | - Versioning and rollback features 30 | - Media Library storage integration 31 | - Enhanced logging and error handling 32 | 33 | ### Changed 34 | - Refined admin dashboard for better usability 35 | 36 | ## [2.0] 37 | 38 | ### Added 39 | - AWS S3 storage integration 40 | - On-demand plugin mirroring 41 | - Checksum verification 42 | - Enhanced admin interface 43 | 44 | ## [1.0] 45 | 46 | ### Added 47 | - Initial release 48 | - Basic plugin mirroring from WordPress.org 49 | - Admin interface for plugin management -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Secure Updates Server 2 | 3 | We welcome contributions from the community! This document outlines the process for contributing to the project. 4 | 5 | ## Getting Started 6 | 7 | 1. **Fork the Repository:** 8 | ```bash 9 | Click the 'Fork' button at the top of the repository page 10 | ``` 11 | 12 | 2. **Clone Your Fork:** 13 | ```bash 14 | git clone https://github.com/yourusername/secure-updates-server.git 15 | ``` 16 | 17 | 3. **Create a Branch:** 18 | ```bash 19 | git checkout -b feature/your-feature-name 20 | ``` 21 | 22 | 4. **Make Changes:** 23 | - Implement your feature or fix 24 | - Follow WordPress coding standards 25 | - Add tests where appropriate 26 | 27 | 5. **Commit and Push:** 28 | ```bash 29 | git add . 30 | git commit -m "Description of your changes" 31 | git push origin feature/your-feature-name 32 | ``` 33 | 34 | 6. **Submit a Pull Request:** 35 | - Go to the original repository 36 | - Submit a pull request from your branch 37 | - Provide a clear description of the changes 38 | 39 | ## Planned Enhancements 40 | 41 | 1. **Support for Additional Plugin Sources** 42 | - GitHub integration 43 | - Bitbucket integration 44 | - Custom source support 45 | 46 | 2. **Improved Rollback Functionality** 47 | - Automatic file replacements 48 | - Version transition handling 49 | 50 | 3. **Enhanced Error Handling** 51 | - Detailed error messages 52 | - Admin interface log viewer 53 | 54 | 4. **User Notifications** 55 | - Email notifications 56 | - Dashboard alerts 57 | 58 | 5. **API Enhancements** 59 | - Additional endpoints 60 | - Third-party integration support 61 | 62 | 6. **Testing & Quality Assurance** 63 | - Unit tests 64 | - Integration tests 65 | - Security audits 66 | - WordPress coding standards compliance 67 | 68 | ## Code Standards 69 | 70 | - Follow [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/) 71 | - Include proper documentation 72 | - Write meaningful commit messages 73 | - Add/update tests as needed 74 | 75 | ## Testing 76 | 77 | - Run existing tests before submitting PR 78 | - Add new tests for new features 79 | - Ensure all tests pass 80 | 81 | ## Questions? 82 | 83 | Open an issue for any questions about contributing. -------------------------------------------------------------------------------- /DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | # Secure Updates Server API Documentation 2 | 3 | The Secure Updates Server plugin provides several REST API endpoints for managing plugin updates securely. All endpoints are prefixed with `/wp-json/secure-updates-server/v1`. 4 | 5 | ## Authentication 6 | 7 | Most endpoints are public except for the Plugin List endpoint which requires authentication using a Bearer token. To authenticate: 8 | 9 | 1. Add your API key in the WordPress admin under Secure Updates Server > API Keys 10 | 2. Include the API key in requests using the Authorization header: 11 | ``` 12 | Authorization: Bearer your-api-key-here 13 | ``` 14 | 15 | ## Endpoints 16 | 17 | ### Download Plugin 18 | Get the latest version of a plugin package. 19 | 20 | ``` 21 | GET /download/{slug} 22 | ``` 23 | 24 | **Parameters:** 25 | - `slug` (path parameter): The plugin slug (e.g., "my-plugin") 26 | 27 | **Response:** 28 | - Success: Returns the plugin ZIP file for download 29 | - Error (404): Plugin not found or file doesn't exist 30 | ```json 31 | { 32 | "code": "plugin_not_found", 33 | "message": "Plugin not found or file does not exist.", 34 | "status": 404 35 | } 36 | ``` 37 | 38 | ### Get Plugin Information 39 | Retrieve metadata about a specific plugin. 40 | 41 | ``` 42 | GET /info/{slug} 43 | ``` 44 | 45 | **Parameters:** 46 | - `slug` (path parameter): The plugin slug 47 | 48 | **Response:** 49 | - Success: 50 | ```json 51 | { 52 | "name": "Plugin Name", 53 | "slug": "plugin-slug", 54 | "version": "1.0.0", 55 | "author": "Author Name", 56 | "homepage": "https://example.com", 57 | "download_link": "https://example.com/wp-json/secure-updates-server/v1/download/plugin-slug", 58 | "sections": { 59 | "description": "Plugin description here.", 60 | "installation": "Installation instructions here.", 61 | "changelog": "Changelog here." 62 | } 63 | } 64 | ``` 65 | - Error (404): Plugin not found 66 | ```json 67 | { 68 | "code": "plugin_not_found", 69 | "message": "Plugin not found", 70 | "status": 404 71 | } 72 | ``` 73 | 74 | ### Check Connection 75 | Test if the update server is accessible and functioning. 76 | 77 | ``` 78 | GET /connected 79 | ``` 80 | 81 | **Response:** 82 | ```json 83 | { 84 | "status": "connected" 85 | } 86 | ``` 87 | 88 | ### Verify Plugin File 89 | Check if a plugin file exists and get its verification information. 90 | 91 | ``` 92 | GET /verify_file/{slug} 93 | ``` 94 | 95 | **Parameters:** 96 | - `slug` (path parameter): The plugin slug 97 | 98 | **Response:** 99 | - Success: 100 | ```json 101 | { 102 | "status": "success", 103 | "message": "Plugin file is correctly hosted and accessible.", 104 | "file_exists": true, 105 | "file_path": "/path/to/plugin.zip", 106 | "checksum": "sha256-hash-of-file" 107 | } 108 | ``` 109 | - Error (404): Plugin or file not found 110 | ```json 111 | { 112 | "code": "plugin_not_found", 113 | "message": "Plugin not found.", 114 | "status": 404 115 | } 116 | ``` 117 | or 118 | ```json 119 | { 120 | "code": "file_not_found", 121 | "message": "Plugin file does not exist on the server.", 122 | "status": 404 123 | } 124 | ``` 125 | 126 | ### Manage Plugin List 127 | Add or update multiple plugins on the server. 128 | 129 | ``` 130 | POST /plugins 131 | ``` 132 | 133 | **Authentication Required**: Bearer token in Authorization header 134 | 135 | **Request Body:** 136 | ```json 137 | { 138 | "plugins": ["plugin-slug-1", "plugin-slug-2"] 139 | } 140 | ``` 141 | 142 | **Response:** 143 | - Success: 144 | ```json 145 | { 146 | "status": "success", 147 | "plugins": { 148 | "plugin-slug-1": "already mirrored", 149 | "plugin-slug-2": "mirrored successfully" 150 | } 151 | } 152 | ``` 153 | - Error (400): Invalid request 154 | ```json 155 | { 156 | "code": "invalid_request", 157 | "message": "Invalid plugin list.", 158 | "status": 400 159 | } 160 | ``` 161 | - Error (401): Unauthorized 162 | ```json 163 | { 164 | "code": "rest_forbidden", 165 | "message": "Sorry, you are not allowed to do that." 166 | } 167 | ``` 168 | 169 | ## Error Handling 170 | 171 | All endpoints return appropriate HTTP status codes: 172 | - 200: Successful request 173 | - 400: Bad request (invalid parameters) 174 | - 401: Unauthorized (invalid or missing API key) 175 | - 404: Resource not found 176 | - 500: Server error 177 | 178 | Error responses follow the WordPress REST API format: 179 | ```json 180 | { 181 | "code": "error_code", 182 | "message": "Human readable error message", 183 | "status": 400 184 | } 185 | ``` -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installation Guide 2 | 3 | ## Requirements 4 | 5 | - WordPress 5.0 or higher 6 | - PHP 7.4 or higher 7 | - ZIP extension enabled 8 | - Write permissions for wp-content/uploads 9 | 10 | ## Installation Steps 11 | 12 | 1. **Download the Plugin:** 13 | - Clone the repository or download the ZIP file from the [GitHub Repository](https://github.com/secure-updates/secure-updates-server) 14 | 15 | 2. **Install via WordPress Admin:** 16 | - Navigate to `Plugins` > `Add New` > `Upload Plugin` 17 | - Upload the `secure-updates-server.zip` file 18 | - Click `Install Now` and then `Activate` 19 | 20 | 3. **Configure the Plugin:** 21 | 22 | ### API Keys Setup 23 | - Go to `Secure Updates Server` > `API Keys` 24 | - Add a new API key that clients will use to authenticate 25 | - Ensure keys are securely stored and distributed 26 | 27 | ### Mirroring Plugins 28 | - Navigate to `Secure Updates Server` > `Secure Updates Server` 29 | - Enter plugin slug to mirror from WordPress.org 30 | - Click `Mirror Plugin` 31 | 32 | ### Uploading Plugins 33 | - Use the `Upload Your Plugin to the Server` section 34 | - Support for direct ZIP file uploads 35 | - Version management included 36 | 37 | ### Managing Plugins 38 | - View all plugins in the `Managed Plugins` section 39 | - Perform deletions or rollbacks as needed 40 | 41 | ## Usage Guide 42 | 43 | ### Managing API Keys 44 | 45 | 1. **Add a New API Key:** 46 | - Navigate to API Keys section 47 | - Enter secure key (16-64 characters) 48 | - Use only letters, numbers, underscores, hyphens 49 | 50 | 2. **Delete an API Key:** 51 | - Locate key in the list 52 | - Click Delete and confirm 53 | 54 | ### Plugin Management 55 | 56 | 1. **Mirroring Plugins:** 57 | - Enter plugin slug (e.g., `akismet`) 58 | - Click Mirror Plugin 59 | - Monitor progress in Managed Plugins table 60 | 61 | 2. **Direct Uploads:** 62 | - Use Upload section 63 | - Select ZIP file 64 | - Provide version information 65 | 66 | 3. **Version Management:** 67 | - View version history 68 | - Perform rollbacks 69 | - Delete outdated versions 70 | 71 | ## Client Configuration 72 | 73 | 1. Install Secure Updates Client plugin 74 | 2. Navigate to Settings > Secure Updates Client 75 | 3. Enter: 76 | - Custom Host URL (your server) 77 | - API key from server 78 | 4. Test connection 79 | 5. Enable updates 80 | 81 | ## Troubleshooting 82 | 83 | ### Common Issues 84 | 85 | 1. **Upload Failures:** 86 | - Check file permissions 87 | - Verify ZIP format 88 | - Confirm WordPress upload limits 89 | 90 | 2. **API Key Issues:** 91 | - Verify key format 92 | - Check client configuration 93 | - Confirm server settings 94 | 95 | 3. **Mirroring Problems:** 96 | - Check WordPress.org connectivity 97 | - Verify plugin slug 98 | - Monitor error logs 99 | 100 | ### Getting Help 101 | 102 | - Check the FAQ section 103 | - Review error logs 104 | - Open GitHub issue for support -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secure Updates Server 2 | 3 | A WordPress plugin that provides secure plugin updates by allowing direct uploads and mirroring from repositories with Media Library integration. 4 | 5 | **Contributors:** Secure Updates Foundation 6 | **Tags:** updates server, plugin mirror, WordPress, media library, plugin updates, API keys 7 | **Requires at least:** 5.0 8 | **Tested up to:** 6.6.2 9 | **Stable tag:** 4.0 10 | **License:** GPLv2 or later 11 | **License URI:** [https://www.gnu.org/licenses/gpl-2.0.html](https://www.gnu.org/licenses/gpl-2.0.html) 12 | 13 | ## Beta Status 14 | This plugin is in active development and while it's feature-complete and follows security best practices, it hasn't undergone extensive production testing across different environments. Test thoroughly in a staging environment before deploying to production. 15 | 16 | ![Plugin Settings Screenshot](images/Secure_Updates_Server_Settings01.png) 17 | 18 | ## Description 19 | 20 | **Secure Updates Server** is a comprehensive WordPress plugin designed to provide secure and controlled plugin updates. It caters to two primary user groups: 21 | 22 | 1. **Companies Managing Client Sites**: Mirror plugins from centralized repositories like WordPress.org to maintain greater control over plugin updates across multiple client sites. 23 | 24 | 2. **Plugin Authors**: Simplify the distribution of updates by directly uploading plugins to the Secure Updates Server. 25 | 26 | ## Features 27 | 28 | - **API Key Integration**: Secure client authentication using API keys 29 | - **Direct Plugin Uploads & Versioning**: Upload and manage plugin versions 30 | - **1-Click Mirroring from WordPress.org**: Easy plugin mirroring 31 | - **Media Library Integration**: Seamless cloud storage integration 32 | - **REST API Endpoints**: Secure communication infrastructure 33 | - **Automated & Scheduled Updates**: Hourly update checks 34 | - **Checksum Verification**: File integrity verification 35 | - **Enhanced Security Measures**: Comprehensive security features 36 | 37 | ![Mirrored / uploaded plugins stored in media library](images/Media_Library.png) 38 | 39 | 40 | 41 | ## Installation 42 | 43 | 1. **Download the Plugin:** 44 | - Clone the repository or download the ZIP file 45 | - Install via WordPress Admin > Plugins > Add New > Upload Plugin 46 | - Activate the plugin 47 | 48 | 2. **Configure the Plugin:** 49 | - Navigate to Secure Updates Server in admin menu 50 | - Set up API keys 51 | - Configure plugin mirroring or uploads 52 | - Manage plugin versions 53 | 54 | For detailed installation and configuration instructions, see [INSTALL.md](INSTALL.md). 55 | 56 | ## Documentation 57 | 58 | - [Installation Guide](INSTALL.md) - Detailed setup instructions 59 | - [API Documentation](API.md) - REST API endpoints and usage 60 | - [Contributing Guide](CONTRIBUTING.md) - Development and contribution guidelines 61 | - [Changelog](CHANGELOG.md) - Version history and updates 62 | - [Security Policy](SECURITY.md) - Security guidelines and reporting 63 | 64 | ## Related Tools 65 | 66 | - **[Secure Updates Client](https://github.com/secure-updates/secure-updates-client)**: Plugin for client sites 67 | - **[Secure Updates Library](https://github.com/secure-updates/secure-updates-library)**: Integration library for plugin authors 68 | 69 | ![API Keys Management](images/API_Keys.png) 70 | 71 | 72 | ## Requirements 73 | 74 | - WordPress 5.0 or higher 75 | - PHP 7.4 or higher 76 | - ZIP extension enabled 77 | - Write permissions for wp-content/uploads 78 | 79 | ## Support 80 | 81 | For bug reports and feature requests, please use the [GitHub issue tracker](https://github.com/secure-updates/secure-updates-server/issues). 82 | 83 | ## License 84 | 85 | This project is licensed under the GPL v2 or later - see [LICENSE](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html) for details. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 4.0 | :white_check_mark: | 8 | | 3.0 | :white_check_mark: | 9 | | 2.0 | :x: | 10 | | 1.0 | :x: | 11 | 12 | ## Security Features 13 | 14 | 1. **API Key Authentication** 15 | - Secure client authentication 16 | - Key management interface 17 | - Token-based authorization 18 | 19 | 2. **File Integrity** 20 | - SHA256 checksum verification 21 | - Secure file uploads 22 | - Version tracking 23 | 24 | 3. **Access Control** 25 | - WordPress capability checks 26 | - Nonce verification 27 | - Input sanitization 28 | 29 | 4. **Data Protection** 30 | - Secure storage practices 31 | - Media Library integration 32 | - Cloud storage compatibility 33 | 34 | ## Reporting a Vulnerability 35 | 36 | 1. **Do Not Create Public Issues** 37 | - Security issues should be reported privately 38 | 39 | 2. **Contact Process** 40 | - Email: security@secure-updates.org 41 | - Include detailed description 42 | - Provide steps to reproduce 43 | 44 | 3. **Response Timeline** 45 | - Initial response: 48 hours 46 | - Assessment: 1 week 47 | - Fix development: Based on severity 48 | 49 | ## Best Practices 50 | 51 | 1. **API Key Management** 52 | - Regular key rotation 53 | - Secure distribution 54 | - Access logging 55 | 56 | 2. **Server Configuration** 57 | - HTTPS enforcement 58 | - Access restrictions 59 | - Regular updates 60 | 61 | 3. **Monitoring** 62 | - Activity logging 63 | - Error tracking 64 | - Update notifications 65 | 66 | ## Security Recommendations 67 | 68 | 1. **For Administrators** 69 | - Regular backups 70 | - Access control review 71 | - Update monitoring 72 | 73 | 2. **For Developers** 74 | - Code review process 75 | - Security testing 76 | - Documentation maintenance 77 | 78 | 3. **For Users** 79 | - API key protection 80 | - Regular updates 81 | - Security monitoring -------------------------------------------------------------------------------- /admin/admin-page.php: -------------------------------------------------------------------------------- 1 | 12 |
13 |

14 | 15 | 16 | 17 |
18 |

19 |
20 | 21 |
22 |

23 | 38 |

39 |
40 | 41 |
42 |

43 |
44 | 45 |
46 |

47 |
48 | 49 | 50 | 51 |

52 |
53 | 57 | 58 | 59 | 60 | 63 | 67 | 68 |
61 | 62 | 64 | 65 |

66 |
69 | 70 |
71 | 72 | 73 |
74 | 75 | 76 |

77 |
78 | 82 | 83 | 84 | 85 | 86 | 90 | 91 |
87 | 88 |

89 |
92 | 93 |
94 | 95 |
96 | 97 | 98 |

99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | $plugin): ?> 112 | 113 | 114 | 115 | 122 | 131 | 143 | 144 | 145 | 146 | 147 | 150 | 151 | 152 | 153 |
116 | '; 119 | } 120 | ?> 121 | 123 | 127 | 128 | 129 | 130 | 132 | 133 |
134 | 138 | 139 | 140 | "return confirm('" . esc_attr__('Are you sure you want to delete this plugin?', 'secure-updates-server') . "');"]); ?> 141 |
142 |
148 | 149 |
154 |
155 | 156 | 157 | -------------------------------------------------------------------------------- /admin/api-keys-page.php: -------------------------------------------------------------------------------- 1 | 7 |
8 |

9 | 10 | 15 |
16 |

17 |
18 | 21 |
22 |

23 |
24 | 28 | 29 | 30 |
31 |

32 |
33 | 34 | 35 | 36 | 37 | 40 | 53 | 54 |
38 | 39 | 41 | 49 |

50 | 51 |

52 |
55 | 56 |
57 |
58 | 59 | 60 |
61 |

62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 76 | 88 | 89 | 90 | 91 | 92 | 95 | 96 | 97 | 98 |
74 | 75 | 77 |
78 | 79 | 80 | 81 | 86 |
87 |
93 | 94 |
99 |
100 | 101 | 102 |
103 |

104 |
105 |

106 | 107 |

108 |
    109 |
  1. 110 | 111 |
    Authorization: Bearer YOUR_API_KEY
    112 |
  2. 113 |
  3. 114 | 115 |
    curl -X POST \
    116 |    \
    117 |   -H 'Authorization: Bearer YOUR_API_KEY' \
    118 |   -H 'Content-Type: application/json' \
    119 |   -d '{"plugins": ["plugin-slug-1", "plugin-slug-2"]}'
    120 |
  4. 121 |
122 |

123 |
124 | 125 |

126 |
127 |
128 |
129 | 130 | -------------------------------------------------------------------------------- /images/API_Keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secure-updates/secure-updates-server/3ed2dd1f9e3edd076c94a7198956409717ae0def/images/API_Keys.png -------------------------------------------------------------------------------- /images/Media_Library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secure-updates/secure-updates-server/3ed2dd1f9e3edd076c94a7198956409717ae0def/images/Media_Library.png -------------------------------------------------------------------------------- /images/Secure_Updates_Server_Settings01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secure-updates/secure-updates-server/3ed2dd1f9e3edd076c94a7198956409717ae0def/images/Secure_Updates_Server_Settings01.png -------------------------------------------------------------------------------- /secure-updates-server.php: -------------------------------------------------------------------------------- 1 | setup_hooks(); 28 | } 29 | 30 | /** 31 | * Load plugin textdomain for translations 32 | */ 33 | public function load_textdomain() 34 | { 35 | load_plugin_textdomain('secure-updates-server', false, dirname(plugin_basename(__FILE__)) . '/languages'); 36 | } 37 | 38 | /** 39 | * Setup WordPress hooks 40 | */ 41 | private function setup_hooks() 42 | { 43 | // Admin Menu 44 | add_action('admin_menu', [$this, 'setup_admin_menu']); 45 | 46 | // Handle Direct Plugin Upload 47 | add_action('admin_post_upload_plugin_to_server', [$this, 'upload_plugin_to_server']); 48 | 49 | // Handle Mirror Plugin via standard form submission 50 | add_action('admin_post_mirror_plugin', [$this, 'mirror_plugin']); 51 | 52 | // Handle Delete Plugin via standard form submission 53 | add_action('admin_post_delete_plugin', [$this, 'delete_plugin']); 54 | 55 | // Scheduled Updates 56 | add_action('secure_updates_server_check_updates', [$this, 'check_for_updates']); 57 | 58 | // REST API Endpoints 59 | add_action('rest_api_init', [$this, 'register_rest_routes']); 60 | 61 | // Activation and Deactivation Hooks 62 | register_activation_hook(__FILE__, [$this, 'activate_plugin']); 63 | register_deactivation_hook(__FILE__, [$this, 'deactivate_plugin']); 64 | } 65 | 66 | /** 67 | * Activate the plugin and schedule update checks 68 | */ 69 | public function activate_plugin() 70 | { 71 | if (!wp_next_scheduled('secure_updates_server_check_updates')) { 72 | wp_schedule_event(time(), 'hourly', 'secure_updates_server_check_updates'); 73 | } 74 | } 75 | 76 | /** 77 | * Deactivate the plugin and clear scheduled hooks 78 | */ 79 | public function deactivate_plugin() 80 | { 81 | wp_clear_scheduled_hook('secure_updates_server_check_updates'); 82 | } 83 | 84 | /** 85 | * Setup the admin menu in WordPress dashboard 86 | */ 87 | public function setup_admin_menu() 88 | { 89 | add_menu_page( 90 | __('Secure Updates Server', 'secure-updates-server'), 91 | __('Secure Updates Server', 'secure-updates-server'), 92 | 'manage_options', 93 | 'secure-updates-server', 94 | [$this, 'admin_page'], 95 | 'dashicons-update', 96 | 6 97 | ); 98 | 99 | add_submenu_page( 100 | 'secure-updates-server', 101 | __('API Keys', 'secure-updates-server'), 102 | __('API Keys', 'secure-updates-server'), 103 | 'manage_options', 104 | 'secure-updates-server-api-keys', 105 | [$this, 'api_keys_page'] 106 | ); 107 | } 108 | 109 | /** 110 | * Display the admin page for managing plugins 111 | */ 112 | public function admin_page() 113 | { 114 | // Include the admin page content 115 | include plugin_dir_path(__FILE__) . 'admin/admin-page.php'; 116 | } 117 | 118 | /** 119 | * Register REST API routes 120 | */ 121 | public function register_rest_routes() 122 | { 123 | // Existing Download Endpoint 124 | register_rest_route('secure-updates-server/v1', '/download/(?P[a-zA-Z0-9-]+)', [ 125 | 'methods' => 'GET', 126 | 'callback' => [$this, 'handle_download_request'], 127 | 'permission_callback' => '__return_true', 128 | ]); 129 | 130 | // Existing Info Endpoint 131 | register_rest_route('secure-updates-server/v1', '/info/(?P[a-zA-Z0-9-]+)', [ 132 | 'methods' => 'GET', 133 | 'callback' => [$this, 'handle_info_request'], 134 | 'permission_callback' => '__return_true', 135 | ]); 136 | 137 | // Existing Connected Endpoint 138 | register_rest_route('secure-updates-server/v1', '/connected', [ 139 | 'methods' => 'GET', 140 | 'callback' => [$this, 'handle_connected_request'], 141 | 'permission_callback' => '__return_true', 142 | ]); 143 | 144 | // Existing Verify File Endpoint 145 | register_rest_route('secure-updates-server/v1', '/verify_file/(?P[a-zA-Z0-9-]+)', [ 146 | 'methods' => 'GET', 147 | 'callback' => [$this, 'handle_verify_file_request'], 148 | 'permission_callback' => '__return_true', 149 | ]); 150 | 151 | // New Plugin List Endpoint 152 | register_rest_route('secure-updates-server/v1', '/plugins', [ 153 | 'methods' => 'POST', 154 | 'callback' => [$this, 'handle_plugin_list_request'], 155 | 'permission_callback' => [$this, 'verify_client_request'], 156 | ]); 157 | } 158 | 159 | /** 160 | * Verify client API key 161 | */ 162 | public function verify_client_request($request) 163 | { 164 | $headers = $request->get_headers(); 165 | if (isset($headers['authorization'])) { 166 | $auth = $headers['authorization'][0]; 167 | if (strpos($auth, 'Bearer ') === 0) { 168 | $api_key = substr($auth, 7); 169 | $valid_api_keys = get_option('secure_updates_valid_api_keys', []); 170 | if (in_array($api_key, $valid_api_keys, true)) { 171 | return true; 172 | } 173 | } 174 | } 175 | return false; 176 | } 177 | 178 | /** 179 | * Handle REST API request for plugin download 180 | */ 181 | public function handle_download_request($request) 182 | { 183 | $plugin_slug = sanitize_text_field($request['slug']); 184 | 185 | // Get the latest version of the plugin 186 | $secure_updates_plugins = get_option('secure_updates_plugins', []); 187 | if (isset($secure_updates_plugins[$plugin_slug])) { 188 | $latest_version = $secure_updates_plugins[$plugin_slug]['latest_version']; 189 | $attachment_id = $secure_updates_plugins[$plugin_slug]['versions'][$latest_version]['attachment_id']; 190 | $file_path = get_attached_file($attachment_id); 191 | 192 | if ($file_path && file_exists($file_path)) { 193 | // Serve the file for download 194 | header('Content-Description: File Transfer'); 195 | header('Content-Type: application/zip'); 196 | header('Content-Disposition: attachment; filename="' . basename($file_path) . '"'); 197 | header('Content-Length: ' . filesize($file_path)); 198 | header('Pragma: public'); 199 | flush(); 200 | readfile($file_path); 201 | exit; 202 | } 203 | } 204 | 205 | return new WP_Error('plugin_not_found', __('Plugin not found or file does not exist.', 'secure-updates-server'), ['status' => 404]); 206 | } 207 | 208 | /** 209 | * Handle REST API request for plugin information 210 | */ 211 | public function handle_info_request($request) 212 | { 213 | $plugin_slug = sanitize_text_field($request['slug']); 214 | 215 | // Get plugin information 216 | $secure_updates_plugins = get_option('secure_updates_plugins', []); 217 | if (isset($secure_updates_plugins[$plugin_slug])) { 218 | // Retrieve plugin data 219 | $plugin_info = [ 220 | 'name' => ucfirst($plugin_slug), 221 | 'slug' => $plugin_slug, 222 | 'version' => $secure_updates_plugins[$plugin_slug]['latest_version'], 223 | 'author' => 'Your Name', 224 | 'homepage' => home_url(), 225 | 'download_link' => rest_url('secure-updates-server/v1/download/' . $plugin_slug), 226 | 'sections' => [ 227 | 'description' => __('Plugin description here.', 'secure-updates-server'), 228 | 'installation' => __('Installation instructions here.', 'secure-updates-server'), 229 | 'changelog' => __('Changelog here.', 'secure-updates-server'), 230 | ], 231 | ]; 232 | 233 | return rest_ensure_response($plugin_info); 234 | } 235 | 236 | return new WP_Error('plugin_not_found', __('Plugin not found', 'secure-updates-server'), ['status' => 404]); 237 | } 238 | 239 | /** 240 | * Handle REST API request to test connection 241 | */ 242 | public function handle_connected_request($request) 243 | { 244 | return rest_ensure_response(['status' => 'connected']); 245 | } 246 | 247 | /** 248 | * Handle REST API request to verify plugin file 249 | */ 250 | public function handle_verify_file_request($request) 251 | { 252 | $plugin_slug = sanitize_text_field($request['slug']); 253 | 254 | // Get the plugin information 255 | $secure_updates_plugins = get_option('secure_updates_plugins', []); 256 | if (isset($secure_updates_plugins[$plugin_slug])) { 257 | $attachment_id = $secure_updates_plugins[$plugin_slug]['attachment_id']; 258 | $file_path = get_attached_file($attachment_id); 259 | 260 | if ($file_path && file_exists($file_path)) { 261 | // Calculate checksum 262 | $checksum = isset($secure_updates_plugins[$plugin_slug]['versions'][$secure_updates_plugins[$plugin_slug]['latest_version']]['checksum']) 263 | ? $secure_updates_plugins[$plugin_slug]['versions'][$secure_updates_plugins[$plugin_slug]['latest_version']]['checksum'] 264 | : ''; 265 | 266 | return rest_ensure_response([ 267 | 'status' => 'success', 268 | 'message' => __('Plugin file is correctly hosted and accessible.', 'secure-updates-server'), 269 | 'file_exists' => true, 270 | 'file_path' => $file_path, 271 | 'checksum' => $checksum, 272 | ]); 273 | } else { 274 | return new WP_Error('file_not_found', __('Plugin file does not exist on the server.', 'secure-updates-server'), ['status' => 404]); 275 | } 276 | } 277 | 278 | return new WP_Error('plugin_not_found', __('Plugin not found.', 'secure-updates-server'), ['status' => 404]); 279 | } 280 | 281 | /** 282 | * Handle plugin list request 283 | */ 284 | public function handle_plugin_list_request($request) 285 | { 286 | $params = $request->get_json_params(); 287 | if (empty($params['plugins']) || !is_array($params['plugins'])) { 288 | return new WP_Error('invalid_request', __('Invalid plugin list.', 'secure-updates-server'), ['status' => 400]); 289 | } 290 | 291 | $plugin_slugs = array_map('sanitize_text_field', $params['plugins']); 292 | $responses = []; 293 | 294 | foreach ($plugin_slugs as $slug) { 295 | if ($this->is_plugin_mirrored($slug)) { 296 | $responses[$slug] = 'already mirrored'; 297 | } else { 298 | $result = $this->mirror_plugin_by_slug($slug); 299 | if ($result) { 300 | $responses[$slug] = 'mirrored successfully'; 301 | } else { 302 | $responses[$slug] = 'failed to mirror'; 303 | } 304 | } 305 | } 306 | 307 | return rest_ensure_response(['status' => 'success', 'plugins' => $responses]); 308 | } 309 | 310 | /** 311 | * Mirror a plugin (Handles standard form submission) 312 | */ 313 | public function mirror_plugin() 314 | { 315 | // Verify nonce 316 | if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'mirror_plugin_nonce')) { 317 | wp_redirect(admin_url('admin.php?page=secure-updates-server&status=error&message=nonce_failed')); 318 | exit; 319 | } 320 | 321 | // Capability check 322 | if (!current_user_can('manage_options')) { 323 | wp_redirect(admin_url('admin.php?page=secure-updates-server&status=error&message=insufficient_permissions')); 324 | exit; 325 | } 326 | 327 | $plugin_slug = isset($_POST['plugin_slug']) ? sanitize_text_field($_POST['plugin_slug']) : ''; 328 | 329 | if (empty($plugin_slug)) { 330 | wp_redirect(admin_url('admin.php?page=secure-updates-server&status=error&message=empty_slug')); 331 | exit; 332 | } 333 | 334 | $this->log_message("Mirroring plugin: $plugin_slug"); 335 | 336 | if ($this->mirror_plugin_by_slug($plugin_slug)) { 337 | wp_redirect(admin_url('admin.php?page=secure-updates-server&status=success')); 338 | } else { 339 | wp_redirect(admin_url('admin.php?page=secure-updates-server&status=error&message=mirror_failed')); 340 | } 341 | exit; 342 | } 343 | 344 | /** 345 | * Delete a mirrored or uploaded plugin (Handles standard form submission) 346 | */ 347 | public function delete_plugin() 348 | { 349 | // Verify nonce 350 | if (!isset($_POST['delete_plugin_nonce_field']) || !wp_verify_nonce($_POST['delete_plugin_nonce_field'], 'delete_plugin_nonce')) { 351 | wp_redirect(admin_url('admin.php?page=secure-updates-server&status=error&message=nonce_failed')); 352 | exit; 353 | } 354 | 355 | // Capability check 356 | if (!current_user_can('manage_options')) { 357 | wp_redirect(admin_url('admin.php?page=secure-updates-server&status=error&message=insufficient_permissions')); 358 | exit; 359 | } 360 | 361 | $plugin_slug = isset($_POST['plugin_slug']) ? sanitize_text_field($_POST['plugin_slug']) : ''; 362 | 363 | if (empty($plugin_slug)) { 364 | wp_redirect(admin_url('admin.php?page=secure-updates-server&status=error&message=empty_slug')); 365 | exit; 366 | } 367 | 368 | // Delete plugin data from the database 369 | $secure_updates_plugins = get_option('secure_updates_plugins', []); 370 | if (isset($secure_updates_plugins[$plugin_slug])) { 371 | // Delete all attachments related to the plugin 372 | foreach ($secure_updates_plugins[$plugin_slug]['versions'] as $version_info) { 373 | wp_delete_attachment($version_info['attachment_id'], true); 374 | } 375 | 376 | unset($secure_updates_plugins[$plugin_slug]); 377 | update_option('secure_updates_plugins', $secure_updates_plugins); 378 | 379 | $this->log_message("Deleted plugin: $plugin_slug"); 380 | 381 | do_action('secure_updates_plugin_deleted', $plugin_slug); 382 | 383 | wp_redirect(admin_url('admin.php?page=secure-updates-server&status=deleted')); 384 | exit; 385 | } 386 | 387 | $this->log_message("Plugin not found: $plugin_slug"); 388 | wp_redirect(admin_url('admin.php?page=secure-updates-server&status=error&message=plugin_not_found')); 389 | exit; 390 | } 391 | 392 | /** 393 | * Handle Direct Plugin Upload 394 | */ 395 | public function upload_plugin_to_server() 396 | { 397 | // Verify nonce 398 | if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'upload_plugin_nonce')) { 399 | wp_die(__('Nonce verification failed', 'secure-updates-server')); 400 | } 401 | 402 | // Capability check 403 | if (!current_user_can('manage_options')) { 404 | wp_die(__('Insufficient permissions.', 'secure-updates-server')); 405 | } 406 | 407 | // Check for file upload 408 | if (empty($_FILES['plugin_zip_file']) || $_FILES['plugin_zip_file']['error'] !== UPLOAD_ERR_OK) { 409 | wp_die(__('Please select a valid ZIP file to upload.', 'secure-updates-server')); 410 | } 411 | 412 | // Handle file upload 413 | $file = $_FILES['plugin_zip_file']; 414 | $upload = wp_handle_upload($file, ['test_form' => false]); 415 | 416 | if (isset($upload['error']) && !empty($upload['error'])) { 417 | wp_die(__('Error uploading file: ', 'secure-updates-server') . esc_html($upload['error'])); 418 | } 419 | 420 | // Insert the uploaded file into the WordPress Media Library 421 | $filetype = wp_check_filetype(basename($upload['file']), null); 422 | $attachment = [ 423 | 'guid' => $upload['url'], 424 | 'post_mime_type' => $filetype['type'], 425 | 'post_title' => preg_replace('/\.[^.]+$/', '', basename($upload['file'])), 426 | 'post_content' => '', 427 | 'post_status' => 'inherit' 428 | ]; 429 | 430 | $attachment_id = wp_insert_attachment($attachment, $upload['file']); 431 | require_once(ABSPATH . 'wp-admin/includes/image.php'); 432 | $attach_data = wp_generate_attachment_metadata($attachment_id, $upload['file']); 433 | wp_update_attachment_metadata($attachment_id, $attach_data); 434 | 435 | // Extract plugin slug from file name 436 | $plugin_slug = sanitize_title(preg_replace('/\.[^.]+$/', '', basename($upload['file']))); 437 | 438 | // Extract version from plugin data if possible 439 | $plugin_data = $this->get_plugin_data_from_zip($upload['file']); 440 | $version = isset($plugin_data['Version']) ? sanitize_text_field($plugin_data['Version']) : '1.0.0'; 441 | 442 | // Calculate checksum 443 | $checksum = $this->calculate_checksum($upload['file']); 444 | 445 | // Add plugin slug to served plugins list 446 | $secure_updates_plugins = get_option('secure_updates_plugins', []); 447 | if (!isset($secure_updates_plugins[$plugin_slug])) { 448 | $secure_updates_plugins[$plugin_slug] = [ 449 | 'attachment_id' => $attachment_id, 450 | 'type' => 'direct_upload', 451 | 'versions' => [ 452 | $version => [ 453 | 'version' => $version, 454 | 'date' => current_time('mysql'), 455 | 'attachment_id' => $attachment_id, 456 | 'checksum' => $checksum, 457 | ] 458 | ], 459 | 'latest_version' => $version, 460 | ]; 461 | } else { 462 | // Handle versioning for existing plugin 463 | $secure_updates_plugins[$plugin_slug]['versions'][$version] = [ 464 | 'version' => $version, 465 | 'date' => current_time('mysql'), 466 | 'attachment_id' => $attachment_id, 467 | 'checksum' => $checksum, 468 | ]; 469 | $secure_updates_plugins[$plugin_slug]['latest_version'] = $version; 470 | $secure_updates_plugins[$plugin_slug]['attachment_id'] = $attachment_id; 471 | } 472 | 473 | update_option('secure_updates_plugins', $secure_updates_plugins); 474 | 475 | $this->log_message("Uploaded plugin: $plugin_slug version: $version"); 476 | 477 | do_action('secure_updates_plugin_uploaded', $plugin_slug, $version); 478 | 479 | // Redirect back with success message 480 | wp_redirect(admin_url('admin.php?page=secure-updates-server&status=upload_success')); 481 | exit; 482 | } 483 | 484 | /** 485 | * Check for updates (both mirrored and uploaded plugins) 486 | */ 487 | public function check_for_updates() 488 | { 489 | $secure_updates_plugins = get_option('secure_updates_plugins', []); 490 | 491 | foreach ($secure_updates_plugins as $plugin_slug => $plugin_info) { 492 | if ($plugin_info['type'] === 'mirror') { 493 | $this->check_mirrored_plugin_update($plugin_slug, $plugin_info); 494 | } 495 | // For direct uploads, you might implement different update mechanisms if needed 496 | } 497 | 498 | $this->log_message('Checked for plugin updates.'); 499 | } 500 | 501 | /** 502 | * Check and update a mirrored plugin 503 | */ 504 | private function check_mirrored_plugin_update($plugin_slug, $plugin_info) 505 | { 506 | $plugin_data = $this->fetch_plugin_data($plugin_slug); 507 | 508 | if ($plugin_data && version_compare($plugin_data['version'], $plugin_info['latest_version'], '>')) { 509 | $version = sanitize_text_field($plugin_data['version']); 510 | $attachment_id = $this->upload_plugin_zip_to_media_library($plugin_slug, $plugin_data['download_link'], $version); 511 | 512 | if ($attachment_id) { 513 | // Calculate checksum 514 | $file_path = get_attached_file($attachment_id); 515 | $checksum = $this->calculate_checksum($file_path); 516 | 517 | // Update plugin information 518 | $this->update_plugin_info($plugin_slug, $version, $attachment_id, 'mirror', $checksum); 519 | 520 | $this->log_message("Updated mirrored plugin: $plugin_slug to version: $version"); 521 | 522 | do_action('secure_updates_plugin_updated', $plugin_slug, $version); 523 | } 524 | } 525 | } 526 | 527 | /** 528 | * Check if a plugin is already mirrored 529 | */ 530 | private function is_plugin_mirrored($slug) 531 | { 532 | $secure_updates_plugins = get_option('secure_updates_plugins', []); 533 | return isset($secure_updates_plugins[$slug]); 534 | } 535 | 536 | /** 537 | * Mirror a plugin by slug 538 | */ 539 | private function mirror_plugin_by_slug($slug) 540 | { 541 | $plugin_data = $this->fetch_plugin_data($slug); 542 | 543 | if ($plugin_data && isset($plugin_data['download_link'])) { 544 | $version = sanitize_text_field($plugin_data['version']); 545 | $attachment_id = $this->upload_plugin_zip_to_media_library($slug, $plugin_data['download_link'], $version); 546 | 547 | if ($attachment_id) { 548 | $file_path = get_attached_file($attachment_id); 549 | $checksum = $this->calculate_checksum($file_path); 550 | 551 | $this->update_plugin_info($slug, $version, $attachment_id, 'mirror', $checksum); 552 | 553 | $this->log_message("Successfully mirrored plugin: $slug version: $version"); 554 | 555 | do_action('secure_updates_plugin_mirrored', $slug, $version); 556 | 557 | return true; 558 | } 559 | } 560 | 561 | $this->log_message("Failed to mirror plugin: $slug"); 562 | return false; 563 | } 564 | 565 | /** 566 | * Display API keys management page 567 | */ 568 | public function api_keys_page() 569 | { 570 | // Handle form submission 571 | if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { 572 | if (!wp_verify_nonce($_POST['_wpnonce'], 'manage_api_keys')) { 573 | wp_die(__('Security check failed', 'secure-updates-server')); 574 | } 575 | 576 | $valid_api_keys = get_option('secure_updates_valid_api_keys', []); 577 | 578 | if ($_POST['action'] === 'add_key' && isset($_POST['new_api_key'])) { 579 | $new_key = sanitize_text_field($_POST['new_api_key']); 580 | if (!empty($new_key)) { 581 | $valid_api_keys[] = $new_key; 582 | update_option('secure_updates_valid_api_keys', array_unique($valid_api_keys)); 583 | } 584 | } elseif ($_POST['action'] === 'delete_key' && isset($_POST['api_key'])) { 585 | $key_to_delete = sanitize_text_field($_POST['api_key']); 586 | $valid_api_keys = array_diff($valid_api_keys, [$key_to_delete]); 587 | update_option('secure_updates_valid_api_keys', $valid_api_keys); 588 | } 589 | } 590 | 591 | // Display the page 592 | $valid_api_keys = get_option('secure_updates_valid_api_keys', []); 593 | include plugin_dir_path(__FILE__) . 'admin/api-keys-page.php'; 594 | } 595 | 596 | /** 597 | * Fetch plugin data from WordPress.org 598 | */ 599 | private function fetch_plugin_data($slug) 600 | { 601 | $transient_key = 'plugin_data_' . $slug; 602 | $plugin_data = get_transient($transient_key); 603 | 604 | if ($plugin_data === false) { 605 | $response = wp_remote_get("https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request[slug]=$slug"); 606 | 607 | if (is_wp_error($response)) { 608 | $this->log_message("Error fetching data for plugin $slug: " . $response->get_error_message()); 609 | return false; 610 | } 611 | 612 | $plugin_data = json_decode(wp_remote_retrieve_body($response), true); 613 | set_transient($transient_key, $plugin_data, HOUR_IN_SECONDS); 614 | } 615 | 616 | return $plugin_data; 617 | } 618 | 619 | /** 620 | * Upload plugin ZIP to Media Library 621 | */ 622 | private function upload_plugin_zip_to_media_library($slug, $url, $version) 623 | { 624 | $tmp = download_url($url); 625 | 626 | if (is_wp_error($tmp)) { 627 | $this->log_message("Error downloading plugin $slug: " . $tmp->get_error_message()); 628 | return false; 629 | } 630 | 631 | $file_array = [ 632 | 'name' => $slug . '-' . $version . '.zip', 633 | 'tmp_name' => $tmp, 634 | ]; 635 | 636 | // Handle the upload using WordPress's media library functions 637 | $attachment_id = media_handle_sideload($file_array, 0); 638 | 639 | // Check for errors during upload 640 | if (is_wp_error($attachment_id)) { 641 | $this->log_message("Error uploading plugin $slug to media library: " . $attachment_id->get_error_message()); 642 | @unlink($tmp); // Delete the temporary file 643 | return false; 644 | } 645 | 646 | // Delete the temporary file 647 | @unlink($tmp); 648 | 649 | return $attachment_id; 650 | } 651 | 652 | /** 653 | * Handle logging of messages 654 | */ 655 | private function log_message($message) 656 | { 657 | if (defined('WP_DEBUG') && WP_DEBUG) { 658 | error_log('[Secure Updates Server] ' . $message); 659 | } 660 | } 661 | 662 | /** 663 | * Calculate SHA256 checksum of a file 664 | */ 665 | private function calculate_checksum($file_path) 666 | { 667 | return hash_file('sha256', $file_path); 668 | } 669 | 670 | /** 671 | * Update plugin information in the database 672 | */ 673 | private function update_plugin_info($slug, $version, $attachment_id, $type, $checksum = '') 674 | { 675 | $secure_updates_plugins = get_option('secure_updates_plugins', []); 676 | if (!isset($secure_updates_plugins[$slug])) { 677 | $secure_updates_plugins[$slug] = [ 678 | 'slug' => $slug, 679 | 'type' => $type, 680 | 'versions' => [], 681 | ]; 682 | } 683 | 684 | $secure_updates_plugins[$slug]['versions'][$version] = [ 685 | 'version' => $version, 686 | 'date' => current_time('mysql'), 687 | 'attachment_id' => $attachment_id, 688 | 'checksum' => $checksum, 689 | ]; 690 | 691 | $secure_updates_plugins[$slug]['latest_version'] = $version; 692 | $secure_updates_plugins[$slug]['attachment_id'] = $attachment_id; 693 | 694 | update_option('secure_updates_plugins', $secure_updates_plugins); 695 | } 696 | 697 | /** 698 | * Extract plugin data from ZIP file 699 | */ 700 | private function get_plugin_data_from_zip($file_path) 701 | { 702 | $plugin_data = []; 703 | $zip = new ZipArchive(); 704 | if ($zip->open($file_path) === true) { 705 | // Look for main plugin file (assuming it has the same name as the folder) 706 | $plugin_slug = basename($file_path, '.zip'); 707 | $main_plugin_file = "$plugin_slug.php"; 708 | 709 | if ($zip->locateName($main_plugin_file)) { 710 | $plugin_content = $zip->getFromName($main_plugin_file); 711 | if ($plugin_content !== false) { 712 | $plugin_data = $this->parse_plugin_header($plugin_content); 713 | } 714 | } 715 | $zip->close(); 716 | } 717 | return $plugin_data; 718 | } 719 | 720 | /** 721 | * Parse plugin header to get version and other data 722 | */ 723 | private function parse_plugin_header($plugin_content) 724 | { 725 | $plugin_data = []; 726 | $headers = [ 727 | 'Version' => 'Version', 728 | 'Plugin Name' => 'Name', 729 | // Add more headers if needed 730 | ]; 731 | 732 | foreach ($headers as $field => $regex_field) { 733 | if (preg_match('/' . $field . ':\s*(.+)/i', $plugin_content, $matches)) { 734 | $plugin_data[$field] = trim($matches[1]); 735 | } 736 | } 737 | 738 | return $plugin_data; 739 | } 740 | } 741 | 742 | // Initialize the plugin 743 | new Secure_Updates_Server(); 744 | } --------------------------------------------------------------------------------