MySQL: Check if the user exists and drop it - mysql

There’s not standard way to check if a MySQL user exists and based on that drop it. Are there any workarounds for this?
Edit: I need a straight way to run this without throwing up an error
e.g.
DROP USER test#localhost; :

This worked for me:
GRANT USAGE ON *.* TO 'username'#'localhost';
DROP USER 'username'#'localhost';
This creates the user if it doesn't already exist (and grants it a harmless privilege), then deletes it either way. Found solution here: http://bugs.mysql.com/bug.php?id=19166
Updates: #Hao recommends adding IDENTIFIED BY; #andreb (in comments) suggests disabling NO_AUTO_CREATE_USER.

Since MySQL 5.7 you can do a DROP USER IF EXISTS test
More info: http://dev.mysql.com/doc/refman/5.7/en/drop-user.html

To phyzome's answer (most highly voted one), it seems to me that if you put "identified by" at the end of the grant statement, the user will be created automatically. But if you don't, the user is not created. The following code works for me,
GRANT USAGE ON *.* TO 'username'#'localhost' IDENTIFIED BY 'password';
DROP USER 'username'#'localhost';
Hope this helps.

Found the answer to this from one of the MySQL forums. We’ll need to use a procedure to delete the user.
User here is “test” and “databaseName” the database name.
SET #OLD_SQL_MODE=##SQL_MODE, SQL_MODE='ANSI';
USE databaseName ;
DROP PROCEDURE IF EXISTS databaseName.drop_user_if_exists ;
DELIMITER $$
CREATE PROCEDURE databaseName.drop_user_if_exists()
BEGIN
DECLARE foo BIGINT DEFAULT 0 ;
SELECT COUNT(*)
INTO foo
FROM mysql.user
WHERE User = 'test' and Host = 'localhost';
IF foo > 0 THEN
DROP USER 'test'#'localhost' ;
END IF;
END ;$$
DELIMITER ;
CALL databaseName.drop_user_if_exists() ;
DROP PROCEDURE IF EXISTS databaseName.drop_users_if_exists ;
SET SQL_MODE=#OLD_SQL_MODE ;
CREATE USER 'test'#'localhost' IDENTIFIED BY 'a';
GRANT ALL PRIVILEGES ON databaseName.* TO 'test'#'localhost'
WITH GRANT OPTION

Update
As of MySQL 5.7 you can directly use DROP USER IF EXISTS statement.
https://dev.mysql.com/doc/refman/5.7/en/drop-user.html
DROP USER IF EXISTS 'user'#'localhost';
FYI (and for older version of MySQL), this is a better solution...!!!
The following SP will help you to remove user 'tempuser'#'%' by executing CALL DropUserIfExistsAdvanced('tempuser', '%');
If you want to remove all users named 'tempuser' (say 'tempuser'#'%', 'tempuser'#'localhost' and 'tempuser'#'192.168.1.101') execute SP like CALL DropUserIfExistsAdvanced('tempuser', NULL); This will delete all users named tempuser!!! seriously...
Now please have a look on mentioned SP DropUserIfExistsAdvanced:
DELIMITER $$
DROP PROCEDURE IF EXISTS `DropUserIfExistsAdvanced`$$
CREATE DEFINER=`root`#`localhost` PROCEDURE `DropUserIfExistsAdvanced`(
MyUserName VARCHAR(100)
, MyHostName VARCHAR(100)
)
BEGIN
DECLARE pDone INT DEFAULT 0;
DECLARE mUser VARCHAR(100);
DECLARE mHost VARCHAR(100);
DECLARE recUserCursor CURSOR FOR
SELECT `User`, `Host` FROM `mysql`.`user` WHERE `User` = MyUserName;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET pDone = 1;
IF (MyHostName IS NOT NULL) THEN
-- 'username'#'hostname' exists
IF (EXISTS(SELECT NULL FROM `mysql`.`user` WHERE `User` = MyUserName AND `Host` = MyHostName)) THEN
SET #SQL = (SELECT mResult FROM (SELECT GROUP_CONCAT("DROP USER ", "'", MyUserName, "'#'", MyHostName, "'") AS mResult) AS Q LIMIT 1);
PREPARE STMT FROM #SQL;
EXECUTE STMT;
DEALLOCATE PREPARE STMT;
END IF;
ELSE
-- check whether MyUserName exists (MyUserName#'%' , MyUserName#'localhost' etc)
OPEN recUserCursor;
REPEAT
FETCH recUserCursor INTO mUser, mHost;
IF NOT pDone THEN
SET #SQL = (SELECT mResult FROM (SELECT GROUP_CONCAT("DROP USER ", "'", mUser, "'#'", mHost, "'") AS mResult) AS Q LIMIT 1);
PREPARE STMT FROM #SQL;
EXECUTE STMT;
DEALLOCATE PREPARE STMT;
END IF;
UNTIL pDone END REPEAT;
END IF;
FLUSH PRIVILEGES;
END$$
DELIMITER ;
Usage:
CALL DropUserIfExistsAdvanced('tempuser', '%'); to remove user 'tempuser'#'%'
CALL DropUserIfExistsAdvanced('tempuser', '192.168.1.101'); to remove user 'tempuser'#'192.168.1.101'
CALL DropUserIfExistsAdvanced('tempuser', NULL); to remove all users named 'tempuser' (eg., say 'tempuser'#'%', 'tempuser'#'localhost' and 'tempuser'#'192.168.1.101')

Um... Why all the complications and tricks?
Rather then using DROP USER... You can simply delete the user from the mysql.user table (which doesn't throw an error if the user does not exist), and then flush privileges to apply the change.
DELETE FROM mysql.user WHERE User = 'SomeUser' AND Host = 'localhost';
FLUSH PRIVILEGES;
-- UPDATE --
I was wrong. It's not safe to delete the user like that. You do need to use DROP USER. Since it is possible to have mysql options set to not create users automatically via grants (an option I use), I still wouldn't recommend that trick. Here's a snipet from a stored procedure that works for me:
DECLARE userCount INT DEFAULT 0;
SELECT COUNT(*) INTO userCount FROM mysql.user WHERE User = userName AND Host='localhost';
IF userCount > 0 THEN
SET #S=CONCAT("DROP USER ", userName, "#localhost" );
PREPARE stmt FROM #S;
EXECUTE stmt;
SELECT CONCAT("DROPPED PRE-EXISTING USER: ", userName, "#localhost" ) as info;
END IF;
FLUSH PRIVILEGES;

DROP USER IF EXISTS 'user'#'localhost' ;
that works for me without throwing any errors in Maria DB, it should work for u too

Regarding #Cherian's answer, the following lines can be removed:
SET #OLD_SQL_MODE=##SQL_MODE, SQL_MODE='ANSI';
...
SET SQL_MODE=#OLD_SQL_MODE;
...
This was a bug pre 5.1.23. After that version these are no longer required. So, for copy/paste convenience, here is the same with the above lines removed. Again, for example purposes "test" is the user and "databaseName" is the database; and this was from this bug.
DROP PROCEDURE IF EXISTS databaseName.drop_user_if_exists ;
DELIMITER $$
CREATE PROCEDURE databaseName.drop_user_if_exists()
BEGIN
DECLARE foo BIGINT DEFAULT 0 ;
SELECT COUNT(*)
INTO foo
FROM mysql.user
WHERE User = 'test' and Host = 'localhost';
IF foo > 0 THEN
DROP USER 'test'#'localhost' ;
END IF;
END ;$$
DELIMITER ;
CALL databaseName.drop_user_if_exists() ;
DROP PROCEDURE IF EXISTS databaseName.drop_users_if_exists ;
CREATE USER 'test'#'localhost' IDENTIFIED BY 'a';
GRANT ALL PRIVILEGES ON databaseName.* TO 'test'#'localhost'
WITH GRANT OPTION

I wrote this procedure inspired by Cherian's answer.
The difference is that in my version the user name is an argument of the procedure ( and not hard coded ) . I'm also doing a much necessary FLUSH PRIVILEGES after dropping the user.
DROP PROCEDURE IF EXISTS DropUserIfExists;
DELIMITER $$
CREATE PROCEDURE DropUserIfExists(MyUserName VARCHAR(100))
BEGIN
DECLARE foo BIGINT DEFAULT 0 ;
SELECT COUNT(*)
INTO foo
FROM mysql.user
WHERE User = MyUserName ;
IF foo > 0 THEN
SET #A = (SELECT Result FROM (SELECT GROUP_CONCAT("DROP USER"," ",MyUserName,"#'%'") AS Result) AS Q LIMIT 1);
PREPARE STMT FROM #A;
EXECUTE STMT;
FLUSH PRIVILEGES;
END IF;
END ;$$
DELIMITER ;
I also posted this code on the CodeReview website ( https://codereview.stackexchange.com/questions/15716/mysql-drop-user-if-exists )

DROP USER 'user'#'localhost';
The above command will drop the user from the database, however, it is Important to know if the same user is already using the database, that session will not end until the user closes that session. It is important to note that dropped user will STILL access the database and perform any operations.
DROPPING THE USER DOES NOT DROP THE CURRENT USER SESSION

Combining phyzome's answer (which didn't work right away for me) with andreb's comment (which explains why it didn't) I ended up with this seemingly working code that temporarily disables NO_AUTO_CREATE_USER mode if it is active:
set #mode = ##SESSION.sql_mode;
set session sql_mode = replace(replace(#mode, 'NO_AUTO_CREATE_USER', ''), ',,', ',');
grant usage on *.* to 'myuser'#'%';
set session sql_mode = #mode;
drop user 'myuser'#'%';

in terminal do:
sudo mysql -u root -p
enter the password.
select user from mysql.user;
now delete the user 'the_username'
DROP USER the_unername;
replace 'the_username' with the user that you want to delete.

In case you have a school server where the pupils worked a lot. You can just clean up the mess by:
delete from user where User != 'root' and User != 'admin';
delete from db where User != 'root' and User != 'admin';
delete from tables_priv;
delete from columns_priv;
flush privileges;

If you mean you want to delete a drop from a table if it exists, you can use the DELETE command, for example:
DELETE FROM users WHERE user_login = 'foobar'
If no rows match, it's not an error.

Related

How to write stored procedure to check Grant in MySql?

I want to create a stored procedure to check grant. I have tried by following way but I don't what I am missing here.
//simple Query "SHOW GRANTS FOR testuser #'192.168.1.180'" - It is working
CREATE DEFINER=`abc`#`localhost` PROCEDURE `SP_GetGrantAllPriviledge`(
IN Username TEXT,
IN Hostname TEXT
)
BEGIN
SHOW GRANTS FOR Username #Hostname; //doesn't work
END
Can anybody suggest what I am missing?
You can use the TABLE_PRIVILEGES in the INFORMATION_SCHEMA.
SELECT *
FROM INFORMATION_SCHEMA.TABLE_PRIVILEGES
WHERE `GRANTEE` = CONCAT('''', Username, '''#''', Hostname, '''')
#Hostname; is the source of issue
Update your SP with prepare statement:
CREATE PROCEDURE `SP_GetGrantAllPriviledge`(
IN Username TEXT,
IN Hostname TEXT
)
BEGIN
SET #sql = CONCAT('SHOW GRANTS FOR ',Username,' #',Hostname,'; ');
PREPARE stmt FROM #sql;
EXECUTE stmt;
END

How to restrict other user drop root user in MySQL?

I created a user and granted "CREATE USER" privileges. I found that user can drop any other user include root. How can I prevent that happen?
Actually we are implementing Mysql as a Service just like AWS's RDS. We will create a super user which will be used by system management. Our customer will have another user which has most of privileges including "CREATE USER", so that they can manage account themselves. I want to find out a approach other than Mysql normal privileges to get that
If you have AWS RDS, you will find RDS has a 'rdsadmin' user account. If you run DROP USER 'rdsadmin'#'localhost' you will get a error: ERROR 1396 (HY000): Operation DROP USER failed for 'rdsadmin'#'localhost'. I'm curious how to implement that.
I tried add a trigger on mysq.user table to throw a error when user delete 'rdsadmin' from table. but the trigger only work for DELETE FROM USER ... sql not for DROP USER. Do you know the reason, or is there any way to fix it?
It is not possible to grant privileges to specified users, CREATE USER is a global privilege.
I'd suggest you to separate MySQL users, for example - there are can be some of them: for administrating (with root privileges), for developers (to access and change database objects and tables data), and so on...
You can use this work around by creating an database API.
The SQL code should help you.
CREATE TABLE mysql.`created_users` (
`user_name` varchar(255) DEFAULT NULL,
`owner` varchar(255) DEFAULT NULL
)
The table hold the usernames and what user created them.
Note create the Procedures with your root account
Procedure to create an mysql user.
DROP PROCEDURE IF EXISTS mysql.createUser;
DELIMITER //
CREATE PROCEDURE mysql.createUser(IN userName VARCHAR(255), IN userPassword VARCHAR(255))
BEGIN
SET #createUserQuery = CONCAT('
CREATE USER "',userName,'"#"localhost" IDENTIFIED BY "',userPassword,'" '
);
PREPARE stmt FROM #createUserQuery;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #createcreatedUserQuery = CONCAT('
INSERT INTO mysql.created_users (user_name, owner) VALUE("',userName,'", "',USER(),'")'
);
PREPARE stmt FROM #createcreatedUserQuery;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
Procedure to drop and with check on created_users table to make sure the user exists and delete right check.
DROP PROCEDURE IF EXISTS mysql.dropUser;
DELIMITER //
CREATE PROCEDURE mysql.dropUser(IN userName VARCHAR(255))
BEGIN
SET #canDeleteUser = 0;
SET #createCountUserQuery = CONCAT('
SELECT COUNT(*) FROM mysql.created_users WHERE user_name = "',userName,'" AND owner = "',USER(),'" INTO #canDeleteUser'
);
PREPARE stmt FROM #createCountUserQuery;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
IF #canDeleteUser = 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'The user is not known on the server or you dont have rights to delete this user';
END IF;
IF #canDeleteUser = 1 THEN
SET #createDropUserQuery = CONCAT('
DROP USER "',userName,'"#"localhost"'
);
PREPARE stmt FROM #createDropUserQuery;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #createDeleteUserQuery = CONCAT('
DELETE FROM created_users WHERE user_name = "',userName,'" AND owner = "',USER(),'"'
);
PREPARE stmt FROM #createDeleteUserQuery;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END //
DELIMITER ;
And to give rights to execute these
GRANT EXECUTE ON PROCEDURE mysql.createUser TO '[user]'#'localhost';
GRANT EXECUTE ON PROCEDURE mysql.dropUser TO '[user]'#'localhost';
And you may want to give the user select priv on mysql.proc so they can see the source code behind the procedures and know the parameters
You can use the database API like this.
CALL mysql.createUser('user', 'password');
CALL mysql.dropUser('user');
Note that root account can only remove users with mysql.dropUser that have the owner root#localhost
As Devart said, You cannot change the privileges of CREATE USER.
However, what it appears like you are doing is provisioning mysql instances out to users to control for themselves. To that end, how about a server management tool. There's many out there, to include froxlor Server Management Studio (which is free and open source): https://www.froxlor.org/
It's just a thought.

Create procedure ERROR 1046 (3D000): No database selected

I am trying to create a simple procedure which would create a new database.
complete code which i am trying to run over mysql is :
SET #DB_NAME := "mydb";
SET #DB_CREATE:= "CREATE DATABASE ";
DELIMITER //
drop procedure if exists create_db //
create procedure create_db(name TEXT)
BEGIN
DECLARE temp TEXT;
DECLARE user TEXT;
SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = name INTO temp;
if temp = name then
SIGNAL SQLSTATE '45002' SET MESSAGE_TEXT = 'This database already exist';
else
SELECT USER() INTO user;
SET #s = CONCAT('CREATE DATABASE ', name);
PREPARE stmt_create FROM #s;
EXECUTE stmt_create;
DEALLOCATE PREPARE stmt_create;
SET #s = CONCAT('GRANT ALL PRIVILEGES ON ', name, '.* TO ', user, ' WITH GRANT OPTION');
PREPARE stmt_grant FROM #s;
EXECUTE stmt_grant;
DEALLOCATE PREPARE stmt_grant;
END IF;
END //
DELIMITER ;
call create_db(#DB_NAME);
I took the help of how do I use a variable in create database statement for creating this procedure.
While running this procedure over mysql i am getting error :
ERROR 1046 (3D000): No database selected
I have googled it a lot but is unable to fix it.
While trying different things i tried to execute the above procedure by first executing:
mysql> USE mysql;
Using that, error is gone. And
mysql> show databases;
is displaying all the databases along with the newly created database. Could somebody tell me whether using mysql as default database is correct or not.
If somebody has another method please tell it to me.
Use the :: use database to select the database to which you want the proc to be created or
try using databasename.
create procedure databasename.create_db(name TEXT)
I don't believe you can have functions that are "GLOBAL" i.e. outside the scope of a database, so not selecting a database before calling your function is not possible

Delete all users in MySQL with specific name

I want to delete all users from my MySQL database with specified name regardless to the host parameter. Here is what I wrote:
DELIMITER ;;
## CREATING SCHEMA
DROP SCHEMA IF EXISTS rms;;
CREATE SCHEMA rms;;
USE rms;;
## DROP USER
DROP PROCEDURE IF EXISTS PREPARE_USERS;;
CREATE PROCEDURE PREPARE_USERS()
BEGIN
DECLARE V_RECORD_NOT_FOUND INTEGER DEFAULT 0;
DECLARE V_USER_HOST CHAR(60);
DECLARE C_HOSTS_CURSOR CURSOR FOR
SELECT host FROM mysql.user WHERE user='rms';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET V_RECORD_NOT_FOUND = 0;
OPEN C_HOSTS_CURSOR;
READ_LOOP: LOOP
FETCH C_HOSTS_CURSOR INTO V_USER_HOST;
IF V_RECORD_NOT_FOUND != 0 THEN
LEAVE READ_LOOP;
END IF;
SET #V_EXEC=CONCAT(CONCAT('DROP USER \'rms\'#\'',V_USER_HOST),'\';;');
PREPARE V_STMT FROM #V_EXEC;
EXECUTE V_STMT;
DEALLOCATE PREPARE V_STMT;
END LOOP;
CLOSE C_HOSTS_CURSOR;
FLUSH PRIVILEGES;
CREATE USER 'rms'#'127.0.0.1' IDENTIFIED BY 'rms123';
GRANT ALL ON rms.* TO 'rms'#'127.0.0.1'
WITH MAX_USER_CONNECTIONS 250;
END;;
CALL PREPARE_USERS();;
DROP PROCEDURE IF EXISTS PREPARE_USERS;;
DELIMITER ;
But it gives me errors and I don't know why :/ When there are no users with 'rms' name, it won't even run, but if there are any, MySQL claims that they can't be dropped even though they are.
OK, I have the answer:
SET #users = NULL;
SELECT GROUP_CONCAT('\'',user, '\'#\'', host, '\'') INTO #users FROM mysql.user WHERE user = 'rms';
SET #users = CONCAT('DROP USER ', #users);
PREPARE stmt1 FROM #users;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;

How to generically/dynamically create/drop users

(Related to, but separate from Syntax error with emulating "create user if not exists".)
Is it possible to achieve the functionality of generically/dynamically adding a user (i.e. emulating the sp_adduser system procedure included with other DBMSs) in MySQL?
MySQL doesn't support the following if [not] exists syntax, see http://bugs.mysql.com/bug.php?id=15287:
create user if not exists 'foo'#'%' identified by password 'bar';
It also doesn't support this:
drop procedure if exists create_user_if_not_exists;
delimiter ||
create procedure create_user_if_not_exists
( sUser varchar(60),
sHost varchar(16),
sPassword varchar(255) )
begin
-- ensure user does not yet exist
if (select ifnull((select 1
from mysql.user
where User = sUser
and Host = sHost), 0) = 0) then
set #createUserText = concat('create user ''', sUser, '''#''', sHost, ''' identified by ''', sPassword, ''';');
prepare createUserStatement FROM #createUserText;
execute createUserStatement;
deallocate prepare createUserStatement;
end if;
end ||
delimiter ;
because if you try to call said procedure:
call create_user_if_not_exists ( 'foo', '%', 'bar' );
you get the lovely message:
This command is not supported in the prepared statement protocol yet
The following works, but obviously is not particularly reusable:
drop procedure if exists create_user_if_not_exists;
delimiter ||
create procedure create_user_if_not_exists
( )
begin
if (select ifnull((select 1
from mysql.user
where User = 'foo'
and Host = '%'), 0) = 0) then
create user 'foo'#'%' identified by password 'bar';
end if;
end ||
delimiter ;
Oh sorry i've just twigged you are talking about db users. not application users.
You might like the INSERT INTO ...... ON DUPLICATE KEY UPDATE ......=VALUE(.....) statement.
I use that in my general saving object method, using this i don't have to care if the user exists or not it'll be in there (and up-to-date) after i commit.