Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@
"db search",
"db tables",
"db size",
"db columns"
"db columns",
"db users",
"db users create"
]
},
"autoload": {
Expand Down
3 changes: 2 additions & 1 deletion db-command.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
require_once $wpcli_db_autoloader;
}

WP_CLI::add_command( 'db', 'DB_Command' );
WP_CLI::add_command( 'db', DB_Command::class );
WP_CLI::add_command( 'db users', \WP_CLI\Db\DB_Users_Command::class );
60 changes: 60 additions & 0 deletions features/db-users.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
@skip-sqlite
Feature: Manage database users

Scenario: Create database user without privileges
Given an empty directory
And WP files
And wp-config.php

When I try `wp db create`
And I run `wp db users create testuser localhost --password=testpass123`
Comment thread
swissspidy marked this conversation as resolved.
Then STDOUT should contain:
"""
Success: Database user 'testuser'@'localhost' created.
"""

When I run `wp db query "SELECT User, Host FROM mysql.user WHERE User='testuser'"`
Then STDOUT should contain:
"""
testuser
"""

Scenario: Create database user with privileges
Given an empty directory
And WP files
And wp-config.php

When I try `wp db create`
And I run `wp db users create appuser localhost --password=secret123 --grant-privileges`
Then STDOUT should contain:
"""
created with privileges on database
"""
And STDOUT should contain:
"""
appuser
"""

Scenario: Create database user with custom host
Given an empty directory
And WP files
And wp-config.php

When I try `wp db create`
And I run `wp db users create remoteuser '%' --password=remote123`
Then STDOUT should contain:
"""
Success: Database user 'remoteuser'@'%' created.
"""

Scenario: Create database user with no password
Given an empty directory
And WP files
And wp-config.php

When I try `wp db create`
And I run `wp db users create nopassuser localhost`
Then STDOUT should contain:
"""
Success: Database user 'nopassuser'@'localhost' created.
"""
3 changes: 0 additions & 3 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@
<rule ref="WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound">
<exclude-pattern>*/src/DB_Command\.php$</exclude-pattern>
</rule>
<rule ref="WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedTraitFound">
<exclude-pattern>*/src/DB_Command_SQLite\.php$</exclude-pattern>
</rule>

<exclude-pattern>/tests/phpstan/scan-files</exclude-pattern>
</ruleset>
5 changes: 2 additions & 3 deletions src/DB_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
use WP_CLI\Formatter;
use WP_CLI\Utils;
use cli\table\Column;

require_once __DIR__ . '/DB_Command_SQLite.php';
use WP_CLI\Db\DB_Command_SQLite;

/**
* Performs basic database operations using credentials stored in wp-config.php.
Expand Down Expand Up @@ -2071,7 +2070,7 @@ private static function is_text_col( $type ) {
*
* @phpstan-return ($idents is string ? string : array)
*/
private static function esc_sql_ident( $idents ) {
protected static function esc_sql_ident( $idents ) {
$backtick = static function ( $v ) {
// Escape any backticks in the identifier by doubling.
return '`' . str_replace( '`', '``', $v ) . '`';
Expand Down
4 changes: 4 additions & 0 deletions src/DB_Command_SQLite.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<?php

namespace WP_CLI\Db;

use WP_CLI\Formatter;
use WP_CLI\Utils;
use WP_CLI;
use Exception;

/**
* SQLite-specific database operations for DB_Command.
Expand Down
114 changes: 114 additions & 0 deletions src/DB_Users_Command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

namespace WP_CLI\Db;

use DB_Command;
use WP_CLI;
use WP_CLI\Utils;

/**
* Manages MySQL database users.
*
* ## EXAMPLES
*
* # Create a new database user with privileges.
* $ wp db users create myuser myhost --password=mypass --grant-privileges
* Success: Database user 'myuser'@'myhost' created with privileges on database 'wp_database'.
*
* @when after_wp_config_load
*/
class DB_Users_Command extends DB_Command {

/**
* Creates a new database user with optional privileges.
*
* Creates a MySQL database user account and optionally grants full privileges
* to the current database specified in wp-config.php.
*
* ## OPTIONS
*
* <username>
* : MySQL username for the new user account.
*
* [<host>]
* : MySQL host for the new user account.
* ---
* default: localhost
* ---
*
* [--password=<password>]
* : Password for the new user account. If not provided, MySQL will use no password.
*
* [--grant-privileges]
* : Grant full privileges on the current database to the new user.
*
* [--dbuser=<value>]
* : Username to connect as (privileged user). Defaults to DB_USER.
*
* [--dbpass=<value>]
* : Password to connect with (privileged user). Defaults to DB_PASSWORD.
*
* [--defaults]
* : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
*
* ## EXAMPLES
*
* # Create a user without privileges.
* $ wp db users create myuser localhost --password=mypass
* Success: Database user 'myuser'@'localhost' created.
*
* # Create a user with full privileges on the current database.
* $ wp db users create appuser localhost --password=secret123 --grant-privileges
* Success: Database user 'appuser'@'localhost' created with privileges on database 'wp_database'.
*/
public function create( $args, $assoc_args ) {
list( $username, $host ) = array_pad( $args, 2, 'localhost' );

$password = Utils\get_flag_value( $assoc_args, 'password', '' );
$grant_privileges = Utils\get_flag_value( $assoc_args, 'grant-privileges', false );

// MySQL account names in CREATE USER / GRANT ... TO use string literals, not identifiers.
$username_escaped = $this->esc_sql_string( $username );
$host_escaped = $this->esc_sql_string( $host );
$user_identifier = $username_escaped . '@' . $host_escaped;

// Create user
$create_query = "CREATE USER {$user_identifier}";
if ( ! empty( $password ) ) {
$password_escaped = $this->esc_sql_string( $password );
Comment thread
swissspidy marked this conversation as resolved.
$create_query .= " IDENTIFIED BY {$password_escaped}";
}
$create_query .= ';';

parent::run_query( $create_query, $assoc_args );

// Grant privileges if requested
if ( $grant_privileges ) {
$database = DB_NAME;
$database_escaped = (string) self::esc_sql_ident( $database );
$grant_query = 'GRANT ALL PRIVILEGES ON ' . $database_escaped . '.* TO ' . $user_identifier . ';';
parent::run_query( $grant_query, $assoc_args );

// Flush privileges
parent::run_query( 'FLUSH PRIVILEGES;', $assoc_args );

WP_CLI::success( "Database user '{$username}'@'{$host}' created with privileges on database '{$database}'." );
} else {
WP_CLI::success( "Database user '{$username}'@'{$host}' created." );
}
}

/**
* Escapes a string for use in a SQL query.
*
* Uses SQL-standard single-quote escaping (`'` => `''`) so the generated
* literal remains valid even when MySQL is running with
* `NO_BACKSLASH_ESCAPES`.
*
* @param string $value String to escape.
* @return string Escaped string, wrapped in single quotes.
*/
private function esc_sql_string( $value ) {
return "'" . str_replace( "'", "''", $value ) . "'";
}
}
Loading