Create a user on database A with a procedure from database B (with EXECUTE AS)

Posted on

Question :

I have 2 logins :

  • LoginA: role dbcreator + CONNECT ANY DATABASE
  • LoginB: role sysadmin

I have 2 databases :

  • dbA: everyone has all rights on it
  • dbB: only LoginB has rights on it, LoginA is only the owner of that database but no more

I’m wondering if it is possible to create a user (linked to LoginA) in dbB from a stored procedure of dbA.
Considering this stored procedure has to be executed by LoginA.
Maybe some kind of “EXECUTE AS” mechanism could help ?
Also I have issues about how to use a “USE [dbB]” in a stored procedure.

The solution can have some rights modification,
but these can’t give securityadmin nor sysadmin roles.

Here is what is done for now but clearly there is a lot of debug and functionalities to go, but the spirit of what I wanna do is in here :

CREATE PROCEDURE [dbB_UserAfterRestore] 
AS
BEGIN
    SET NOCOUNT ON;

    USE [dbB]
    CREATE USER [HOSTNAMELoginA] FOR LOGIN [HOSTNAMELoginA]
    ALTER ROLE [db_datareader] ADD MEMBER [HOSTNAMELoginA]
    ALTER ROLE [db_datawriter] ADD MEMBER [HOSTNAMELoginA]
END
GO

Answer :

Since LoginA is not member of sysadmin server role, the login must need IMPERSONATE permissions to use EXECUTE AS i.e.

USE dbB;
CREATE USER LoginA for login LoginA; -- This will create user under "public" database role
GRANT IMPERSONATE ON USER::LoginB to LoginA; -- This is required to use "EXECUTE AS"

Once LoginA got IMPERSONATE access, you can create procedure as follows in dbA where LoginA got full access (db_owner)

use dbA
go

CREATE PROCEDURE [UserAfterRestore] 
    @TargetDBName varchar(128),
    @GranteeLogin varchar(128),
    @ExecuteAs varchar(128)
AS
BEGIN
    SET NOCOUNT ON;
    Declare @TSQL varchar(2000);

    SELECT @TSQL = 'USE [' + @DBName + ']; 
                    Execute as user = '''+ @ExecuteAs +'''; 
                    IF NOT EXISTS (SELECT name FROM SYS.database_principals WHERE name = '''+ @GranteeLogin +''')
                        BEGIN
                            CREATE USER [' + @GranteeLogin + '] FOR LOGIN ['+ @GranteeLogin + '];
                        END
                        ALTER ROLE [db_datareader] ADD MEMBER ['+ @GranteeLogin +'];
                        ALTER ROLE [db_datawriter] ADD MEMBER ['+ @GranteeLogin +'];'
    Print 'Executing: ' + @TSQL

    EXEC (@TSQL);
END
GO

Call procedure as follows:

exec dbA.dbo.[UserAfterRestore] 
        @TargetDBName = dbB, 
        @GranteeLogin = 'LoginA', 
        @ExecuteAs = 'LoginB'; 


If the target is multiple databases, then LoginA must be either member of sysadmin server role or must be user of all databases (under public role) and GRANTED for IMPERSONATE on respective database’s db_owner

Leave a Reply

Your email address will not be published. Required fields are marked *