@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Improve schema upgrade workflow for unprivileged users

Summary:
In a basically reasonable configuration where you connect
with a non-privileged user from the web workflow, upgrade_schema.php
won't have enough privileges. Allow the user to override the normal
auth with -u and -p.

Test Plan:
Tried to do a schema upgrade with an underprivileged user,
got a useful error message instead of garbage.

Reviewed By: Girish
Reviewers: Girish, davidrecordon, jungejason, tuomaspelkonen, aran
CC: aran, epriestley, Girish
Differential Revision: 191

+181 -99
+2
resources/sql/patches/033.privtest.sql
··· 1 + # This is a no-op, just testing the upgrade_schema.php script. 2 + SELECT 1;
+120 -92
scripts/sql/upgrade_schema.php
··· 25 25 26 26 define('SCHEMA_VERSION_TABLE_NAME', 'schema_version'); 27 27 28 - if (isset($argv[1]) && !is_numeric($argv[1])) { 29 - print 30 - "USAGE: ./update_schema.php [first_patch_version]\n\n". 31 - "run './update_schema.php 12' to apply all patches starting from ". 32 - "version 12.\n". 33 - "run './update_schema.php' to apply all patches that are new since\n". 34 - "the last time this script was run\n\n"; 35 - exit(0); 28 + $options = getopt('v:u:p:') + array( 29 + 'v' => null, 30 + 'u' => null, 31 + 'p' => null, 32 + ); 33 + 34 + if ($options['v'] && !is_numeric($options['v'])) { 35 + usage(); 36 36 } 37 37 38 38 echo phutil_console_wrap( ··· 45 45 } 46 46 47 47 // Use always the version from the commandline if it is defined 48 - $next_version = isset($argv[1]) ? (int)$argv[1] : null; 48 + $next_version = isset($options['v']) ? (int)$options['v'] : null; 49 49 50 - // Dummy class needed for creating our database 51 - class DummyUser extends PhabricatorLiskDAO { 52 - public function getApplicationName() { 53 - return 'user'; 54 - } 50 + if ($options['u']) { 51 + $conn_user = $options['u']; 52 + $conn_pass = $options['p']; 53 + } else { 54 + $conn_user = PhabricatorEnv::getEnvConfig('mysql.user'); 55 + $conn_pass = PhabricatorEnv::getEnvConfig('mysql.pass'); 55 56 } 57 + $conn_host = PhabricatorEnv::getEnvConfig('mysql.host'); 56 58 57 - // Class needed for setting up the actual SQL connection 58 - class PhabricatorSchemaVersion extends PhabricatorLiskDAO { 59 - public function getApplicationName() { 60 - return 'meta_data'; 61 - } 62 - } 59 + $conn = new AphrontMySQLDatabaseConnection( 60 + array( 61 + 'user' => $conn_user, 62 + 'pass' => $conn_pass, 63 + 'host' => $conn_host, 64 + 'database' => null, 65 + )); 63 66 64 - // Connect to 'phabricator_user' db first to create our db 65 - $conn = id(new DummyUser())->establishConnection('w'); 66 - $create_sql = <<<END 67 - CREATE DATABASE IF NOT EXISTS `phabricator_meta_data`; 67 + try { 68 + 69 + $create_sql = <<<END 70 + CREATE DATABASE IF NOT EXISTS `phabricator_meta_data`; 68 71 END; 69 - queryfx($conn, $create_sql); 72 + queryfx($conn, $create_sql); 70 73 71 - // 'phabricator_meta_data' database exists, let's connect to it now 72 - $conn = id(new PhabricatorSchemaVersion())->establishConnection('w'); 73 - $create_sql = <<<END 74 - CREATE TABLE IF NOT EXISTS phabricator_meta_data.`schema_version` ( 75 - `version` INTEGER not null 76 - ); 74 + $create_sql = <<<END 75 + CREATE TABLE IF NOT EXISTS phabricator_meta_data.`schema_version` ( 76 + `version` INTEGER not null 77 + ); 77 78 END; 78 - queryfx($conn, $create_sql); 79 + queryfx($conn, $create_sql); 80 + 81 + // Get the version only if commandline argument wasn't given 82 + if ($next_version === null) { 83 + $version = queryfx_one( 84 + $conn, 85 + 'SELECT * FROM phabricator_meta_data.%T', 86 + SCHEMA_VERSION_TABLE_NAME); 79 87 80 - // Get the version only if commandline argument wasn't given 81 - if ($next_version === null) { 82 - $version = queryfx_one( 83 - $conn, 84 - 'SELECT * FROM %T', 85 - SCHEMA_VERSION_TABLE_NAME); 88 + if (!$version) { 89 + print "*** No version information in the database ***\n"; 90 + print "*** Give the first patch version which to ***\n"; 91 + print "*** apply as the command line argument ***\n"; 92 + exit(-1); 93 + } 86 94 87 - if (!$version) { 88 - print "*** No version information in the database ***\n"; 89 - print "*** Give the first patch version which to ***\n"; 90 - print "*** apply as the command line argument ***\n"; 91 - exit(-1); 95 + $next_version = $version['version'] + 1; 92 96 } 93 97 94 - $next_version = $version['version'] + 1; 95 - } 96 - 97 - // Find the patch files 98 - $patches_dir = $root.'/resources/sql/patches/'; 99 - $finder = id(new FileFinder($patches_dir)) 100 - ->withSuffix('sql'); 101 - $results = $finder->find(); 98 + // Find the patch files 99 + $patches_dir = $root.'/resources/sql/patches/'; 100 + $finder = id(new FileFinder($patches_dir)) 101 + ->withSuffix('sql'); 102 + $results = $finder->find(); 102 103 103 - $patches = array(); 104 - foreach ($results as $r) { 105 - $matches = array(); 106 - if (preg_match('/(\d+)\..*\.sql$/', $r, $matches)) { 107 - $patches[] = array('version' => (int)$matches[1], 108 - 'file' => $r); 109 - } else { 110 - print 111 - "*** WARNING : File {$r} does not follow the normal naming ". 112 - "convention. ***\n"; 104 + $patches = array(); 105 + foreach ($results as $r) { 106 + $matches = array(); 107 + if (preg_match('/(\d+)\..*\.sql$/', $r, $matches)) { 108 + $patches[] = array('version' => (int)$matches[1], 109 + 'file' => $r); 110 + } else { 111 + print 112 + "*** WARNING : File {$r} does not follow the normal naming ". 113 + "convention. ***\n"; 114 + } 113 115 } 114 - } 115 116 116 - // Files are in some 'random' order returned by the operating system 117 - // We need to apply them in proper order 118 - $patches = isort($patches, 'version'); 117 + // Files are in some 'random' order returned by the operating system 118 + // We need to apply them in proper order 119 + $patches = isort($patches, 'version'); 119 120 120 - $patch_applied = false; 121 - foreach ($patches as $patch) { 122 - if ($patch['version'] < $next_version) { 123 - continue; 124 - } 121 + $patch_applied = false; 122 + foreach ($patches as $patch) { 123 + if ($patch['version'] < $next_version) { 124 + continue; 125 + } 125 126 126 - print "Applying patch {$patch['file']}\n"; 127 + print "Applying patch {$patch['file']}\n"; 127 128 128 - $path = Filesystem::resolvePath($patches_dir.$patch['file']); 129 + $path = Filesystem::resolvePath($patches_dir.$patch['file']); 129 130 130 - $user = PhabricatorEnv::getEnvConfig('mysql.user'); 131 - $pass = PhabricatorEnv::getEnvConfig('mysql.pass'); 132 - $host = PhabricatorEnv::getEnvConfig('mysql.host'); 131 + list($stdout, $stderr) = execx( 132 + "mysql --user=%s --password=%s --host=%s < %s", 133 + $conn_user, 134 + $conn_pass, 135 + $conn_host, 136 + $path); 133 137 134 - list($stdout, $stderr) = execx( 135 - "mysql --user=%s --password=%s --host=%s < %s", 136 - $user, $pass, $host, $path); 138 + if ($stderr) { 139 + print $stderr; 140 + exit(-1); 141 + } 142 + 143 + // Patch was successful, update the db with the latest applied patch version 144 + // 'DELETE' and 'INSERT' instead of update, because the table might be empty 145 + queryfx( 146 + $conn, 147 + 'DELETE FROM phabricator_meta_data.%T', 148 + SCHEMA_VERSION_TABLE_NAME); 149 + queryfx( 150 + $conn, 151 + 'INSERT INTO phabricator_meta_data.%T VALUES (%d)', 152 + SCHEMA_VERSION_TABLE_NAME, 153 + $patch['version']); 137 154 138 - if ($stderr) { 139 - print $stderr; 140 - exit(-1); 155 + $patch_applied = true; 141 156 } 142 157 143 - // Patch was successful, update the db with the latest applied patch version 144 - // 'DELETE' and 'INSERT' instead of update, because the table might be empty 145 - queryfx($conn, 'DELETE FROM %T', SCHEMA_VERSION_TABLE_NAME); 146 - queryfx( 147 - $conn, 148 - 'INSERT INTO %T values (%d)', 149 - SCHEMA_VERSION_TABLE_NAME, 150 - $patch['version']); 158 + if (!$patch_applied) { 159 + print "Your database is already up-to-date.\n"; 160 + } 151 161 152 - $patch_applied = true; 162 + } catch (AphrontQueryAccessDeniedException $ex) { 163 + echo 164 + "ACCESS DENIED\n". 165 + "The user '{$conn_user}' does not have sufficient MySQL privileges to\n". 166 + "execute the schema upgrade. Use the -u and -p flags to run as a user\n". 167 + "with more privileges (e.g., root).". 168 + "\n\n". 169 + "EXCEPTION:\n". 170 + $ex->getMessage(). 171 + "\n\n"; 172 + exit(1); 153 173 } 154 174 155 - if (!$patch_applied) { 156 - print "Your database is already up-to-date.\n"; 175 + function usage() { 176 + echo 177 + "usage: upgrade_schema.php [-v version] [-u user -p pass]". 178 + "\n\n". 179 + "Run 'upgrade_schema.php -v 12' to apply all patches starting from ". 180 + "version 12.\n". 181 + "Run 'upgrade_schema.php -u root -p hunter2' to override the configured ". 182 + "default user.\n"; 183 + exit(1); 157 184 } 185 +
+2
src/__phutil_library_map__.php
··· 45 45 'AphrontPageView' => 'view/page/base', 46 46 'AphrontPagerView' => 'view/control/pager', 47 47 'AphrontPanelView' => 'view/layout/panel', 48 + 'AphrontQueryAccessDeniedException' => 'storage/exception/accessdenied', 48 49 'AphrontQueryConnectionException' => 'storage/exception/connection', 49 50 'AphrontQueryConnectionLostException' => 'storage/exception/connectionlost', 50 51 'AphrontQueryCountException' => 'storage/exception/count', ··· 509 510 'AphrontPageView' => 'AphrontView', 510 511 'AphrontPagerView' => 'AphrontView', 511 512 'AphrontPanelView' => 'AphrontView', 513 + 'AphrontQueryAccessDeniedException' => 'AphrontQueryRecoverableException', 512 514 'AphrontQueryConnectionException' => 'AphrontQueryException', 513 515 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 514 516 'AphrontQueryCountException' => 'AphrontQueryException',
+2 -1
src/docs/configuration_guide.diviner
··· 119 119 = Upgrading Schema = 120 120 121 121 After you have configured Phabricator, you need to upgrade the database 122 - schema, see @{article:Upgrading Schema} 122 + schema, see @{article:Upgrading Schema}. You'll also need to do this after you 123 + update the code in the future.
+9 -3
src/docs/upgrade_schema.diviner
··· 14 14 If you are doing this for the first time to a freshly installed MySQL database, 15 15 run the following command: 16 16 17 - PHABRICATOR_ENV=<your_config> php path/to/phabricator/scripts/sql/upgrade_schema.php 0 17 + PHABRICATOR_ENV=<your_config> path/to/phabricator/scripts/sql/upgrade_schema.php -v 0 18 18 19 19 This will install all the patches starting from 0. Running this script will 20 20 store the information of the latest installed patch in the Phabricator database. 21 21 Next time you want to upgrade your schema, just run: 22 22 23 - PHABRICATOR_ENV=<your_config> php path/to/phabricator/scripts/sql/upgrade_schema.php 23 + PHABRICATOR_ENV=<your_config> path/to/phabricator/scripts/sql/upgrade_schema.php 24 24 25 25 This will install all the patches that are new since the last time you ran 26 26 this script. 27 27 28 + If your configuration uses an unprivileged user to connect to the database, you 29 + may have to override the default user so the schema changes can be applied with 30 + root or some other admin user: 31 + 32 + PHABRICATOR_ENV=<your_config> path/to/phabricator/scripts/sql/upgrade_schema.php -u <user> -p <pass> 33 + 28 34 If you need to upgrade the schema starting from a specific patch, just run: 29 35 30 - PHABRICATOR_ENV=<your_config> php path/to/phabricator/scripts/sql/upgrade_schema.php <patch_number> 36 + PHABRICATOR_ENV=<your_config> path/to/phabricator/scripts/sql/upgrade_schema.php -v <patch_number> 31 37 32 38 However, this isn't usually needed.
+10 -3
src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php
··· 144 144 "{$error}."); 145 145 } 146 146 147 - $ret = @mysql_select_db($database, $conn); 148 - if (!$ret) { 149 - $this->throwQueryException($conn); 147 + if ($database !== null) { 148 + $ret = @mysql_select_db($database, $conn); 149 + if (!$ret) { 150 + $this->throwQueryException($conn); 151 + } 150 152 } 151 153 152 154 $end = microtime(true); ··· 249 251 // portable to parse the key out of the error and attach it to the 250 252 // exception. 251 253 throw new AphrontQueryDuplicateKeyException("{$errno}: {$error}"); 254 + case 1044: // Access denied to database 255 + case 1045: // Access denied (auth) 256 + case 1142: // Access denied to table 257 + case 1143: // Access denied to column 258 + throw new AphrontQueryAccessDeniedException("#{$errno}: {$error}"); 252 259 default: 253 260 // TODO: 1064 is syntax error, and quite terrible in production. 254 261 throw new AphrontQueryException("#{$errno}: {$error}");
+1
src/storage/connection/mysql/__init__.php
··· 8 8 9 9 phutil_require_module('phabricator', 'aphront/console/plugin/services/api'); 10 10 phutil_require_module('phabricator', 'storage/connection/base'); 11 + phutil_require_module('phabricator', 'storage/exception/accessdenied'); 11 12 phutil_require_module('phabricator', 'storage/exception/base'); 12 13 phutil_require_module('phabricator', 'storage/exception/connection'); 13 14 phutil_require_module('phabricator', 'storage/exception/connectionlost');
+23
src/storage/exception/accessdenied/AphrontQueryAccessDeniedException.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2011 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + /** 20 + * @group storage 21 + */ 22 + class AphrontQueryAccessDeniedException 23 + extends AphrontQueryRecoverableException { }
+12
src/storage/exception/accessdenied/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('phabricator', 'storage/exception/recoverable'); 10 + 11 + 12 + phutil_require_source('AphrontQueryAccessDeniedException.php');