PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.0
4.11.0 4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / Utils / DBPermissions.php
wp-staging / Framework / Utils Last commit date
Cache 1 day ago DBPermissions.php 1 day ago DataEncoder.php 1 day ago DatabaseOptions.php 1 day ago Env.php 1 day ago Escape.php 1 day ago Glob.php 1 day ago Hooks.php 1 day ago Math.php 1 day ago PluginInfo.php 1 day ago Sanitize.php 1 day ago ServerVars.php 1 day ago SlashMode.php 1 day ago Strings.php 1 day ago Times.php 1 day ago Urls.php 1 day ago Version.php 1 day ago WpDefaultDirectories.php 1 day ago
DBPermissions.php
218 lines
1 <?php
2
3 namespace WPStaging\Framework\Utils;
4
5 use wpdb;
6 use ArrayIterator;
7 use WPStaging\Framework\Security\Auth;
8
9
10
11
12
13 class DBPermissions
14 {
15
16 protected $wpdb;
17
18
19 private $auth;
20
21 public function __construct(wpdb $wpdb, Auth $auth)
22 {
23 $this->wpdb = $wpdb;
24 $this->auth = $auth;
25 }
26
27
28
29
30 public function ajaxCheckDBPermissions()
31 {
32 if (!$this->auth->isAuthenticatedRequest()) {
33 return;
34 }
35
36 $type = isset($_POST['type']) ? sanitize_text_field($_POST['type']) : '';
37 $grantsToCheck = ['CREATE', 'UPDATE', 'INSERT', 'DROP'];
38 if ($type === 'push') {
39 $grantsToCheck[] = 'ALTER';
40 }
41
42 if ($this->isAllowed(['ALL PRIVILEGES']) || $this->isAllowed($grantsToCheck)) {
43 wp_send_json_success();
44 }
45
46 $action = !empty($type) ? $type : 'restore';
47 $permissions = $action === 'push' ? 'CREATE, UPDATE, ALTER, INSERT, DROP' : 'CREATE, UPDATE, INSERT, DROP';
48
49 $message = sprintf(
50 __("The database user might not have sufficient permissions to use the %s action. Continue the process anyway by clicking the 'Proceed' button or change the user's DB permissions and resume the process.<br/><br/> Required permissions are: %s.", 'wp-staging'),
51 $action,
52 $permissions
53 );
54
55
56 $message = '<span id="wpstg-permission-info-output">' . $message . '</span>';
57 $message .= '<span id="wpstg-permission-info-data">' . $this->getDebugInfo() . '</span>';
58 $message .= '<br/><button type="button" id="wpstg-db-permission-show-full-message" class="wpstg-link-btn wpstg-blue-primary">' . __("Show Full Message", "wp-staging") . '</button>';
59
60 wp_send_json_error([
61 'message' => wp_kses_post($message),
62 ]);
63 }
64
65
66
67
68
69
70
71 public function isAllowed(array $grantsToCheck): bool
72 {
73 $grants = $this->wpdb->get_results("SHOW GRANTS;");
74 if (empty($grants) || $this->wpdb->last_error) {
75 return false;
76 }
77
78 $hasGranted = array_filter($grants, function ($grant) use ($grantsToCheck) {
79 $grantString = (new ArrayIterator((array)$grant))->current();
80
81
82 if (!$this->hasGrantForCurrentDatabase($grantString)) {
83 return false;
84 }
85
86
87 if ($this->hasAllPrivileges($grantString)) {
88 return true;
89 }
90
91
92 return $this->hasRequiredPermissions($grantString, $grantsToCheck);
93 });
94
95 return !empty($hasGranted);
96 }
97
98
99
100
101 public function getDebugInfo(): string
102 {
103 $dbUser = empty($_POST['databaseUser']) ? DB_USER : sanitize_text_field($_POST['databaseUser']);
104 $dbName = empty($_POST['databaseDatabase']) ? DB_NAME : sanitize_text_field($_POST['databaseDatabase']);
105 $dbHost = empty($_POST['databaseServer']) ? DB_HOST : sanitize_text_field($_POST['databaseServer']);
106
107 $data = '<textarea class="wpstg-permission-info-output wpstg-textbox" readonly="readonly" name="wpstg-permission-info" title="' . __('Please copy and paste this message and report it to us!', 'wp-staging') . '">';
108 $data .= PHP_EOL . __('DB Name: ', 'wp-staging') . $dbName;
109 $data .= PHP_EOL . __('DB User: ', 'wp-staging') . $dbUser;
110 $data .= PHP_EOL . __('DB Host: ', 'wp-staging') . $dbHost;
111
112 $grants = $this->wpdb->get_results("SHOW GRANTS;");
113 if (empty($grants) || $this->wpdb->last_error) {
114 $data .= PHP_EOL . __('wpdb query error: ', 'wp-staging') . $this->wpdb->last_error;
115 return wp_kses_post($data);
116 }
117
118 $grantsHtml = '';
119 foreach ($grants as $grant) {
120 $grantString = (new ArrayIterator((array)$grant))->current();
121 $grantString = preg_replace('/IDENTIFIED BY PASSWORD\s+([\'"])(?:\\\\.|(?!\1).)*\1/', "IDENTIFIED BY PASSWORD '********'", $grantString);
122 $grantString = preg_replace('/IDENTIFIED BY\s+([\'"])(?:\\\\.|(?!\1).)*\1/', "IDENTIFIED BY '********'", $grantString);
123 $grantsHtml .= PHP_EOL . $grantString . ';';
124 }
125
126 $data .= PHP_EOL . __('User Grants: ', 'wp-staging') . $grantsHtml;
127 $data .= '</textarea>';
128
129 return wp_kses_post($data);
130 }
131
132
133
134
135
136 public function setDB(wpdb $wpdb)
137 {
138 $this->wpdb = $wpdb;
139 }
140
141
142
143
144
145 private function hasGrantForCurrentDatabase(string $grantString): bool
146 {
147 $dbName = $this->wpdb->dbname;
148
149 if (stripos($grantString, '*.*') !== false) {
150 return true;
151 }
152
153
154
155
156 $escapedDbName = str_replace('_', '\\_', $dbName);
157
158 $patterns = [
159 '/\bON\s+\*\.\*/i',
160 '/\bON\s+`' . preg_quote($dbName, '/') . '`\.\*/i',
161 '/\bON\s+`' . preg_quote($escapedDbName, '/') . '`\.\*/i',
162 '/\bON\s+"' . preg_quote($dbName, '/') . '"\.\*/i',
163 '/\bON\s+"' . preg_quote($escapedDbName, '/') . '"\.\*/i',
164 '/\bON\s+' . preg_quote($dbName, '/') . '\.\*/i',
165 '/\bON\s+' . preg_quote($escapedDbName, '/') . '\.\*/i',
166 '/`' . preg_quote($dbName, '/') . '`\.\*/i',
167 '/`' . preg_quote($escapedDbName, '/') . '`\.\*/i',
168 '/"' . preg_quote($dbName, '/') . '"\.\*/i',
169 '/"' . preg_quote($escapedDbName, '/') . '"\.\*/i',
170 '/\b' . preg_quote($dbName, '/') . '\.\*/i',
171 '/\b' . preg_quote($escapedDbName, '/') . '\.\*/i',
172 ];
173 foreach ($patterns as $pattern) {
174 if (preg_match($pattern, $grantString)) {
175 return true;
176 }
177 }
178
179 return false;
180 }
181
182
183
184
185
186 private function hasAllPrivileges(string $grantString): bool
187 {
188 return preg_match('/\bGRANT\s+ALL(\s+PRIVILEGES)?\b/i', $grantString) === 1;
189 }
190
191
192
193
194
195
196 private function hasRequiredPermissions(string $grantString, array $grantsToCheck): bool
197 {
198 if (!preg_match('/GRANT\s+(.*?)\s+ON\s+/i', $grantString, $matches)) {
199 return false;
200 }
201
202 $permissionsString = strtoupper(trim($matches[1]));
203 $permissionsString = preg_replace('/\s*,\s*/', ',', $permissionsString);
204 $grantedPermissions = array_filter(array_map('trim', explode(',', $permissionsString)));
205 $grantedPermissionsAssoc = array_flip($grantedPermissions);
206
207
208 foreach ($grantsToCheck as $requiredPermission) {
209 $requiredPermission = strtoupper(trim($requiredPermission));
210 if (!in_array($requiredPermission, $grantedPermissions, true)) {
211 return false;
212 }
213 }
214
215 return true;
216 }
217 }
218