PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.1
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.1
4.11.1 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 week ago DBPermissions.php 1 week ago DataEncoder.php 1 week ago DatabaseOptions.php 1 week ago Env.php 1 week ago Escape.php 1 week ago Glob.php 1 week ago Hooks.php 1 week ago Math.php 6 days ago PluginInfo.php 1 week ago Sanitize.php 1 week ago ServerVars.php 1 week ago SlashMode.php 1 week ago Strings.php 1 week ago Times.php 1 week ago Urls.php 1 week ago Version.php 1 week ago WpDefaultDirectories.php 1 week 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