Programmatically give access to databases - sql-server

Scenario: Instructor of a database course wants to do the following for each of 100 students every semester: (a) create login from windows, (b) create database for each student where they are db_owner, (c) each student has read access to a database.
Here is the script I have so far:
DECLARE #DBname varchar(10) = 'aa1833'
DECLARE #Lname varchar(20) = 'STUDENTS'+'\'+#DBname
DECLARE #CreateDB varchar(max) = 'CREATE DATABASE '+#DBname
EXEC(#CreateDB)
DECLARE #CreateLogin varchar(max) = 'CREATE LOGIN ['+#Lname+ '] FROM WINDOWS'
EXEC(#CreateLogin)
USE sample_database
EXEC sp_addrolemember 'db_datareader', #Lname
EXEC sp_droprolemember 'db_owner', #Lname
DECLARE #dbRights varchar(max) = 'USE '+#DBname
EXEC(#dbRights)
EXEC sp_addrolemember 'db_owner', #Lname
I would appreciate help with following issues:
The above script is able to create logins and user's database. However, access rights (to individual database and sample database) are incorrect.
To manage server space, I would like to delete student accounts and their databases at the end of each semester. Can we have database names such as students\f21\001\aa1833?
Any other comments to improve this set up (students cannot access others' work and edit contents) would be appreciated.
SQL Server 15

The database context reverts back to the outer context (sample_database) after the dynamic SQL USE statement runs so the subsequent sp_addrolemember runs in sample_database instead of aa1833 as intended.
Execute USE and sp_addrolemember in the same dynamic SQL batch to avoid the issue:
DECLARE #dbRights varchar(max) = 'USE ' + QUOTENAME(#DBname) + N';EXEC sp_addrolemember ''db_owner'', #Lname';
sp_executesql #dbRights, N'#Lname sysname', #Lname = #Lname;

Related

Execute a script/stored prodedure when specific Error_Number is encountered

I've created a stored procedure (sp_dropUser) to loop through and drop a user from all databases.
But many times I encounter error 15138 when a user owns a schema.
Hence I created another procedure (sp_altAuthorization) to alter the authorization of the schemas owned by the database user.
What I want to do is, whenever I encounter the specific error while executing sp_dropUser, then execute sp_altAuthorization, and then return to the first procedure and continue.
Something like:
CREATE sp_dropUser
#userName
--Cursor to DROP USER from each #dbName
IF ERROR_NUMBER() = 15138
EXEC sp_altAuthorization #userName, #dbName
FETCH NEXT FROM cursor INTO #user
GO
And sp_altAuthorization will accept the parameters and alter the authorization.
CREATE sp_altAuthorization
#userName, #dbName
--build dynamic sql query
SET #dsql = N'USE ' + #dbName + N'
ALTER AUTHORIZATION ON SCHEMA:: '+#schema + ' TO dbo'
EXEC(#dsql)
RETURN --not sure!
I was thinking TRY CATCH block could be used, but not sure how.

Grant Server roles SQL server 2008R2

I have a tool which updates my sql table 'eptrack' with the following information
Server, Instance, userid, access, startdate and expirydate.
every time this table is updated a trigger will initiate a job which in turn connects to the respective server/Instance and grant the requested server roles.
I am able to grant the role via the following query.
exec sp_addsrvrolemember'na\admin_Test1', 'sysadmin'
However when i try to pick this info from the table via the below query,I get an error.
EXEC sp_addrolemember '(select userid from eptrack)' , '(select access from eptrack)'
Could I get a help for a query on granting the server role picked from the table for the userid in the same table
According to sp_addrolemember,this accepts variables as well
-- Syntax for SQL Server and Azure SQL Database
sp_addrolemember [ #rolename = ] 'role',
[ #membername = ] 'security_account'
so you can try something like below
declare #rolename sysname,
#membername sysname
select #rolename='db_Datareader',#membername='test'
EXEC sp_addrolemember #rolename,#membername
The above select can be from your table,if there are multiple results from select this will not work as expected, you may want to have a method which tracks/get only one row at a time
You should use a cursor for going through your table, get 1 row at a time, assign 2 variables and pass them to sp_addsrvrolemember like this:
declare #eptrack table (userid sysname, access sysname);
insert into #eptrack values ('na\admin_Test1', 'sysadmin');
declare #rolename sysname, #membername sysname;
select #rolename = access, #membername = userid
from #eptrack;
exec sp_addsrvrolemember #membername, #rolename;
Thank you for the assistance. I tried the above query as mentioned below
declare #rolename sysname,
#membername sysname
select #rolename=(select access from eptrack),#membername= (select userid from eptrack)
EXEC sp_addrolemember #rolename,#membername
I see an error the user id does not exist. I just inserted the information in the table and executed the query after that.
This worked, thank you. however we are entering the userid and access manually and we need those info picked from the table
declare #eptrack table (userid sysname, access sysname);
insert into #eptrack values ((select userid from eptrack), (select access from eptrack));
declare #rolename sysname, #membername sysname;
select #rolename = access, #membername = userid
from #eptrack;
exec sp_addsrvrolemember #membername, #rolename;
I tried the above one and see an error user id is not a valid login.
wanted to check if I am entering the select command right?
The table will have new rows inserted frequently and the query needs to pick the userid and access from the table every time a new value is inserted.

sp_addrolemember with WHERE clause - SQL Server

Is there a way to add a WHERE clause within the sp_addrolemember script so that I don't have to create the stored procedure in every single database?
For example in my stored procedure:
BEGIN
DECLARE #SQL NVARCHAR(MAX)
DECLARE #GrantSql INT
EXEC #GrantSql = sp_addrolemember 'db_owner', #LoginName WHERE DatabaseName = 'DBName'
IF #GrantSQL = 0
BEGIN
INSERT INTO TableName....
END
END
In documentation:
Adds a database user, database role, Windows login, or Windows group
to a database role in the CURRENT database
But you can try:
EXEC #GrantSql = DBName..sp_addrolemember 'db_owner', #LoginName
If DBName is a parameter, you should use parametrized dynamic sql
DECLARE #Sql NVARCHAR(MAX) =
N'EXEC #GrantSql = ' + QUOTENAME(#DBName) + '..sp_addrolemember #RoleName, #LoginName'
EXEC sp_executesql #Sql, N'#RoleName NVARCHAR(MAX), #LoginName NVARCHAR(255)', #RoleName, #LoginName
P.S.
Here is a good article about dynamic sql:
http://www.sommarskog.se/dynamic_sql.html
I think everyone who are about to write dynamic sql MUST read it

Security for requesting temporary logins

I need to set up a system which will allow developers to request an emergency ID for a database. They will be assigned to a role called 'analyst' which will provide them a drop down box with the databases they can gain access to. They will submit the request and a temporary SQL Login will be generated and displayed on screen. The login will have some elevated privs. The login will be removed after 12 hours.
I've got the whole thing working myself as an SA on ASP.net, but now I'm working on modifying the procedures to work using a SQL Login in the application connection string.
I've tried a few things to get it working, but have run into a roadblock.
Here's my procedure that does the real work.
USE [SQLEmergencyLoginRequest]
GO
/****** Object: StoredProcedure [dbo].[SQLELR_Login_CREATE] Script Date: 12/08/2009 14:48:29 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[SQLELR_Login_CREATE]
#SERVER VARCHAR(50),
#DATABASE VARCHAR(50),
#NTLOGIN VARCHAR(50),
#IR INT,
#LOGIN VARCHAR(50) OUTPUT,
#PWD VARCHAR(20) OUTPUT,
#NotifyDBA INT
WITH EXECUTE AS OWNER
AS
/*
Emergency_Access_Login_CREATE: Create Login/PWD, Create User, Create Role, Add User to Role, return Login/PWD.
*/
DECLARE #Random_Login_Extension VARCHAR(20)
DECLARE #sql VARCHAR(1000)
SET #Database = QUOTENAME(#Database);
BEGIN TRANSACTION
--CREATE LOGIN/PWD
EXEC dbo.random_password #Random_Login_Extension OUTPUT;
EXEC dbo.random_password #PWD OUTPUT;
SET #LOGIN = 'Emergency_Login_' + #Random_Login_Extension;
SET #sql= 'CREATE LOGIN [' + #LOGIN + ']' +
'WITH PASSWORD= ''' + #PWD + ''', DEFAULT_DATABASE=[master], ' +
'CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF';
EXEC(#sql);
--CREATE USER
DECLARE #User_Cmd VARCHAR(1000);
SET #User_Cmd = 'USE ' + #DATABASE + ';' +
'CREATE USER [' + #LOGIN + '] FOR LOGIN [' + #LOGIN + '];' +
'EXEC sp_addrolemember N''db_datareader'',''' + #LOGIN + ''';' +
'EXEC sp_addrolemember N''db_datawriter'',''' + #LOGIN + ''';' +
'EXEC sp_addrolemember N''db_ddladmin'',''' + #LOGIN + ''';';
EXEC (#User_Cmd);
INSERT INTO dbo.SQLELR_Emergency_Logins
([CreationTime]
,[NTLogin]
,[IR]
,[SERVER]
,[DATABASE]
,SQLLoginCreated)
VALUES
(GETDATE()
,#NTLOGIN
,#IR
,#SERVER
,#DATABASE
,#LOGIN)
DECLARE #MYBODY VARCHAR(500)
SET #MYBODY = #NTLOGIN + ' has created a temporary login in the ' + #Database + ' Database. The login name is ' + #LOGIN;
DECLARE #MYSUBJECT VARCHAR(500)
SET #MYSUBJECT = 'Emergency Login Creation ON server ' + ##SERVERNAME;
IF #NotifyDBA = 1
BEGIN
EXEC msdb.dbo.sp_notify_operator
#profile_name = 'SQLDBA',
#name = 'SQLDBA',
#subject = #MYSUBJECT,
#body = #MYBODY;
END
COMMIT TRANSACTION
I don't want the application account to be highly privileged in every DB, so I created another account which will go into every db and have db_owner. Evidently the sp_addrolemember using fixed db roles needs a db_owner to work, which is why the acct is db_owner. I'd prefer security admin, but it seems it's not possible.
Back to the problem - using EXECUTE AS with dynamic code does not work.
Is the only way to get this done by creating a stored procedure in every database which creates the user?
We're doing this because we'd like to crank down security on this server and take away db_owner from developers which has been the norm for years. Creating this mechanism will satisfy their only remaining complaint about not having access. They are afraid we won't answer a page and they will be unable to resolve an issue, so this will take care of that.
Of course, any advice on security holes here would be appreciated as well.
The EXECUTE AS clause on the work procedure puts you into the 'execute as' cage, see Extending Database Impersonation by Using EXECUTE AS. Because the EXECUTE AS of the procedure is an database principal, the execute as context will be trusted only inside the database.
There are two workarounds, the 500lb sledge hammer of ALTER DATABASE [SQLEmergencyLoginRequest] SET TRUSTWORTHY ON or the surgical precission tool of code signing, see Call a procedure in another database from an activated procedure for an example. I highly recommend the code signing approach:
craete a certificate in SQLEmergencyLoginRequest
sign the procedure
drop the private key of the certificate to prevent future use for signing
export the certificate
import the certificate in master
create a login derived from the certificate
grant AUTHENTICATE on SERVER to the certificate derived login
grant all other priviledges needed for the procedure to this derived login
This would ensure that the procedure has all the needed priviledges to do its work, in any database. You have to redo the whole signing procedure every time you alter it.

Script SQL Server login with Windows authentication without machine name

I want to write a SQL 2005 script to create a new login that uses Windows authentication. The Windows user is a local account (not a domain one). A local account with the same name exists on many SQL Server machines and I want to run the same script on all of them.
It seemed simple enough:
CREATE LOGIN [MyUser]
FROM WINDOWS
However, that doesn't work! SQL returns an error, saying Give the complete name: <domain\username>.
Of course, I can do that for one machine and it works, but the same script will not work on other machines.
Looks like sp_executesql is the answer, as beach posted. I'll post mine as well, because ##SERVERNAME doesn't work correctly if you use named SQL instances, as we do.
DECLARE #loginName SYSNAME
SET #loginName = CAST(SERVERPROPERTY('MachineName') AS SYSNAME) + '\MyUser'
IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE [name] = #loginName)
BEGIN
DECLARE #sql NVARCHAR(1000)
SET #sql = 'CREATE LOGIN [' + #loginName + '] FROM WINDOWS'
EXEC sp_executesql #sql
END
This just worked for me:
DECLARE #sql nvarchar(1000)
SET #SQL = 'CREATE LOGIN [' + ##SERVERNAME + '\MyUser] FROM WINDOWS'
EXEC sp_executeSQL #SQL

Resources