Create user with roles inside stored procedure - sql

I want to create users with roles inside stored procedure. I tried plain query it didn't work, probably because of GO, then I tried to put that inside stored procedure, that didn't wok because of GO either. I tried to put new line character inside dynamic query, that didn't work either. Can anyone help?
create PROCEDURE dbo.CreateUser
AS
BEGIN
/*** Creating User ***/
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = '
CREATE USER [PRODUCTION\user1] FOR LOGIN [PRODUCTION\user1] WITH DEFAULT_SCHEMA=[dbo]
GO
sp_addrolemember #rolename = ''db_owner'', #membername = ''PRODUCTION\user1''
GO
CREATE USER [PRODUCTION\user2] FOR LOGIN [PRODUCTION\user2] WITH DEFAULT_SCHEMA=[dbo]
GO
sp_addrolemember #rolename = ''db_owner'', #membername = ''PRODUCTION\user2''
GO'
/*** Creating User ***/
EXEC(#SQL)
END

Personally, I would do this:
CREATE PROCEDURE dbo.CreateUser
AS
BEGIN
/*** Creating User ***/
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = N'CREATE USER [PRODUCTION\user1] FOR LOGIN [PRODUCTION\user1] WITH DEFAULT_SCHEMA=[dbo];' + NCHAR(13) + NCHAR(10) +
N'CREATE USER [PRODUCTION\user2] FOR LOGIN [PRODUCTION\user2] WITH DEFAULT_SCHEMA=[dbo];';
--PRINT #SQL; --Your debugging best friend
EXEC sp_executesql #SQL;
SET #SQL = N'ALTER ROLE db_owner ADD MEMBER [PRODUCTION\user1];' + NCHAR(13) + NCHAR(10) +
N'ALTER ROLE db_owner ADD MEMBER [PRODUCTION\user2];';
--PRINT #SQL; --Your debugging best friend
EXEC sp_executesql #SQL;
END
This gets rid of the sp_addrolemember procedure, which is deprecated. It also splits the creation and memberships into 2 separate batches, to ensure that the users have indeed been created.

Related

Is it possible to assign all schemas to a single login without specifying?

I work with a DB where schemas get added and removed regularly, and preferably I would be able to assign all schemas to a single user with a script.
but as far as I can see, I always have to specify the schema like
ALTER AUTHORIZATION ON SCHEMA::schema_name TO myUser;
Is there a workaround?
DECLARE #sql nvarchar(max) = N'',
#base nvarchar(max) = N'ALTER AUTHORIZATION ON SCHEMA::$s$ TO myUser;';
SELECT #sql += REPLACE(#base, N'$s$', QUOTENAME(name))
FROM sys.schemas;
PRINT #sql;
-- EXEC sys.sp_executesql;

How to fix that procedure in sql

I created procedure which count not null rows in the column, but query throws errors: #tableName is not declared and invalid object name tempTable. I don't know why code throws that errors, because all variables are declared.
Msg 1087, Level 16, State 1, Procedure getLenCol, Line 7 [Batch Start Line 0]
Must declare the table variable "#tableName".
Msg 208, Level 16, State 1, Line 11
Invalid object name 'tempTable'.
CREATE OR ALTER PROC getLenCol
#tableName varchar(255),
#colName varchar(255)
as
DECLARE #tempTable Table(smth varchar(255));
DECLARE #query varchar(255)
insert into #tempTable(smth) select #colName from #tableName where #colName is not null
exec (#query)
select ##ROWCOUNT
GO
exec getLenCol 'users','name'
Also when I make that program in another way, that code throw
Msg 1087, Level 15, State 2, Line 11
error.
Must declare the table variable "#tempTable".
CREATE OR ALTER PROC getLenCol
#tableName varchar(255),
#colName varchar(255)
as
DECLARE #tempTable Table(smth varchar(255))
DECLARE #query varchar(255)
SET #query = concat('insert into #tempTable(smth) select ',#colName,' from ',#tableName,' where ',#colName,' is not null');/*#colName from #tableName where #colName is not NULL*/
exec (#query)
select ##ROWCOUNT
GO
exec getLenCol 'users','name'
Is it a way to fix that error?
Obviously, your code is subject to SQL injection attacks -- as the comments on the question have explained.
But your issue is the scoping rules around your table variable. You can fix that by using:
set #query = concat('select ', #colName, ' from ', #tableName, ' where ', #colName,' is not null');
insert into #tempTable (smth)
exec(#query);
I don't think there is any way around the SQL injection vulnerabilities for the logic you have suggested. However, your code is so non-sensical that I doubt that it is really representative of your actual code.
As it seems that many are not aware of the dangers of SQL Injection, including Gordon, I wanted to expand on that first. Let's, take the accepted answer (at time of writing), which gives the following:
CREATE OR ALTER PROC getLenCol
#tableName varchar(255),
#colName varchar(255)
as
DECLARE #query varchar(255)
DECLARE #tempTable Table(smth varchar(255))
set #query = concat('select ', #colName, ' from ', #tableName, ' where ', #colName,' is not null');
insert into #tempTable (smth)
exec(#query);
GO
Now, let's be someone malicious:
EXEC dbo.getLenCol #colName = N'1; CREATE LOGIN NewLogin WITH PASSWORD = ''1'', CHECK_POLICY = OFF;/*',
#tableName =N'*/ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;--';
So, what does the above, in the dynamic SQL run? Let's find out by adding PRINT #query; to the SP's definition:
select 1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/* from */ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;-- where 1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/* is not null
And, with a little formatting for ease of reading:
select 1;
CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;
/* from */
ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;
-- where 1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/* is not null
OH. OHHHHHHHHHHH. Congratulations you are the new proud owner of a SQL Server that has a new sysadmin LOGIN!
NEVER, inject unsanitised string into a string in SQL. NEVER.
Rather than repeating myself, I'm going to link to my article Dos and Don'ts of Dynamic SQL, however, you can easily make the above query secure with a few of uses of QUOTENAME:
CREATE OR ALTER PROC getLenCol
#schemaName sysname = N'dbo', --You should define the schema too
#tableName sysname, --An object can't be longer than 128 characters, so sysname is best
#colName sysname
AS
BEGIN
DECLARE #query nvarchar(MAX);
DECLARE #tempTable Table(smth varchar(255));
SET #QUERY = CONCAT(N'SELECT ', QUOTENAME(#colName),N' FROM ', QUOTENAME(#schemaName), N'.', QUOTENAME(#tableName), N' WHERE ', QUOTENAME(#colName), N' IS NOT NULL;');
PRINT #query;
INSERT INTO #tempTable (smth)
EXEC sys.sp_executesql #query;
END;
GO
And what happens if we run the above EXEC statement before? Well you get the statement below (with added formatting):
SELECT [1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/*]
FROM [dbo].[*/ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;--]
WHERE [1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/*] IS NOT NULL;
And no surprised, that generated the error
Invalid object name 'dbo.*/ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;--'.
Now your dynamic statement is safe from injection.
I would highly recommend against this approach, firstly calling this procedure is as much, if not more typing that just doing a count. Compare the two
EXECUTE dbo.getLenCol #tableName = 'dbo.SomeTable', #colName = 'ID';
SELECT COUNT(ID) FROM dbo.SomeTable;
Even with the shortened exec, and not using named parameters it is longer:
EXEC dbo.getLenCol dbo.SomeTable', 'ID';
It is very, very rare that a catch all query like this, with object names being passed as parameters is going to be the correct approach. There are some maintenance queries where it is useful, but these are the exception, not the rule.
If you must do this though, you should do a little bit of validation first, and check that both the table name and column name are valid before executing any dynamic SQL by using COL_LENGTH(#tableName, #ColName). e.g
CREATE OR ALTER PROC getLenCol #tableName SYSNAME, #colName SYSNAME
AS
BEGIN
IF COL_LENGTH(#tableName, #ColName) IS NOT NULL
BEGIN
DECLARE #SQL NVARCHAR(MAX) = CONCAT('SELECT COUNT(', #colName, ') FROM ', #tableName, ';');
EXECUTE sp_executesql #SQL;
RETURN;
END
-- TABLE OR COLUMN WAS NOT VALID RETURN -1 TO INDICATE THAT
SELECT -1;
END

SQL Grant other user db_owner when creating database

I would like to set up a MS SQL trigger (or some other mechanism) that would automatically grant some group db_owner permission.
I don't want our users to have db_admin on the SQL Servers, instead I would like to grant them only db_creator but whenever someone creates a database, other users should have access to this database as well.
I have tried creating a SQL trigger:
USE master
GO
CREATE TRIGGER trg_DDL_CreateDatabase
ON ALL SERVER
FOR CREATE_DATABASE
AS
DECLARE #databaseName varchar(max)
SET #databaseName = (SELECT EVENTDATA().value('(/EVENT_INSTANCE/DatabaseName)[1]', 'VARCHAR(255)'))
EXEC ('USE [' + #databaseName + ']' )
IF IS_MEMBER ('DOMAIN\GROUP') = 1
EXEC sp_addrolemember N'db_owner', N'DOMAIN\SOMEGROUP'
ELSE
EXEC sp_addrolemember N'db_owner', N'DOMAIN\SOMEOTHERGROUP'
GO
Since I can not use USE statement in the trigger I've tried using the EXEC ('USE [' + #databaseName + ']' ) approach - however it doesn't work either.
Any suggestions please?
edit: It would be awesome if I can grant different permissions based on the group membership. For example user is member of group A then A is being added to the db_owner, if B then B,C and E is being added etc.
Every database gets created as a copy of [model]. Any users, groups and grants you apply in [model] will be automatically copied into any newly created database. Note that this does not address databases that are attached.
If you want to do it via DDL triggers you need to use dynamic sql for the entire batch:
CREATE TRIGGER trg_DDL_CreateDatabase
ON ALL SERVER
FOR CREATE_DATABASE
AS
DECLARE #databaseName sysname;
SET #databaseName = (SELECT EVENTDATA().value('(/EVENT_INSTANCE/DatabaseName)[1]', 'sysname'));
declare #sql nvarchar(max);
set #sql = N'USE ' + quotename(#databaseName) + N';
IF IS_MEMBER (''DOMAIN\GROUP'') = 1
EXEC sp_addrolemember N''db_owner'', N''DOMAIN\SOMEGROUP''
ELSE
EXEC sp_addrolemember N''db_owner'', N''DOMAIN\SOMEOTHERGROUP'';';
exec sp_executesql #sql;
As good practices use QUOTENAME() to properly escape names. Use sysname for database name type, both in extraction (xml method) and in storage (variable type). As it were your code could had been easily exploited by a db_creator to elevate himself to sysadmin via SQL injection (create an appropriate db name and pwn you).

Granting access to one db to users/roles of another

Short version: Can I grant access to external databases to a role?
Long version:
I am working on reports using Crystal which is retrieving data from an applications SQL Server Instance (database1).
The application is running the reports and overwriting the connection in the report, I have no access to the applications code.
I have added a new DB to the server (database2) which is collecting information from a telephone exchange and I want to join some of this information to the applications data (database1).
I can join the data and the reports work when run within the designer (logged in as SA) but when the reports are run externally through the application they fail with a fairly generic error (Failed to retrieve data).
I am assuming the error is being caused by the new DB permissions as if I log into the application as SA the error goes away.
The Application has a special DB Role for users that run reports, when adding a table/view/sp to the application db (database1) I can simply grant select/execute to this role to allow the reports to access the object.
Now I have object in a different db however the role isn't easily accessible.
Is there any way I can reference the second db (database2) through the existing role?
eg:
USE [database1]
GRANT EXECUTE ON [database2].[dbo].[CUSTOM_PROCEDURE] TO [applicationrole1]
OR
USE [database2]
GRANT EXECUTE ON [dbo].[CUSTOM_PROCEDURE] TO [database1].[dbo].[applicationrole1]
Ideally I want to be able to link to the Role somehow rather than re-creating a new role as the role is updated by the application regularly when new users are added/configured.
(Not tagged with Crystal-Reports as this isn't related to the problem)
Edit:
Is there any way to do something like:
INSERT INTO Database2.sys.database_principals
SELECT * FROM Database1.sys.database_principals
WHERE [type] = 'S'
To copy over the Users (not logins) and then add the role members?
Presumably, you'd be using a login that has access to both databases (such as the case with SA). You'd create the appropriate role and grant rights to each database, then create the user (linked to the login you're using) in both, adding each to the role you created.
The T-SQL will look something like this:
use master
go
create login testuser with password = 'mypassword123'
go
use test
go
create role reporting
grant select on something to reporting -- grant your permissions here
create user testuser for login testuser
exec sp_addrolemember 'reporting', 'testuser'
go
use test2
go
create role reporting
grant select on something2 to reporting -- grant your permissions here
create user testuser for login testuser
exec sp_addrolemember 'reporting', 'testuser'
go
Now I can connect to test and execute
select * from something
select * from test2.dbo.something2
Of course, you'd change your grants to EXECUTE on the desired stored procedures, but it looks like you've already got that covered.
After that, it's just about executing a simple script to create logins, users, and add them to the role.
declare #sql nvarchar(max), #username nvarchar(50), #password nvarchar(50)
-- ########## SET PARAMETERS HERE
SET #username = N'testguy'
SET #password = N'test123'
-- ########## END SET PARAMETERS
set #sql = N'USE master; CREATE LOGIN [' + #username + N'] WITH PASSWORD = N''' + #password + N'''; USE database1; CREATE USER [' + #username + N'] FOR LOGIN [' + #username + N']; EXEC sp_addrolemember ''reporting'', ''' + #username + N'''; USE database2; CREATE USER [' + #username + N'] FOR LOGIN [' + #username + N']; EXEC sp_addrolemember ''reporting'', ''' + #username + N''';'
exec sp_executesql #sql
Syncing logins, users, and roles automatically
This script will find all SQL logins (you can change this to whatever makes sense to you; windows AND SQL accounts, accounts that contain a certain string, whatever), ensure the user has been created in database1 and database2, and ensures they are both added to the reporting role. You will need to ensure the reporting role is created on both databases, but you only need to do this once.
After that, you can run this script periodically, either manually, or using a SQL Agent job. All you need to do is create the login for the server; when the script runs it will do the rest.
declare #login nvarchar(50), #user1 nvarchar(50), #user2 nvarchar(50), #sql nvarchar(max), #rolename nvarchar(50)
SET #rolename = 'reporting'
declare c cursor for
select sp.name as login, dp1.name as user1, dp2.name as user2 from sys.server_principals as sp
left outer join database1.sys.database_principals as dp1 on sp.sid = dp1.sid
left outer join database2.sys.database_principals as dp2 on sp.sid = dp2.sid
where sp.type = 'S'
and sp.is_disabled = 0
open c
fetch next from c into #login, #user1, #user2
while ##FETCH_STATUS = 0 begin
-- create user in db1
if (#user1 is null) begin
SET #sql = N'USE database1; CREATE USER [' + #login + N'] FOR LOGIN [' + #login + N'];'
EXEC sp_executesql #sql
end
-- ensure user is member of role in db1
SET #sql = N'USE database1; EXEC sp_addrolemember '''+ #rolename + ''', ''' + #login + N''';'
EXEC sp_executesql #sql
-- create user in db2
if (#user2 is null) begin
SET #sql = N'USE database2; CREATE USER [' + #login + N'] FOR LOGIN [' + #login + N'];'
EXEC sp_executesql #sql
end
-- ensure user is member of role in db2
SET #sql = N'USE database2; EXEC sp_addrolemember '''+ #rolename + ''', ''' + #login + N''';'
EXEC sp_executesql #sql
fetch next from c into #login, #user1, #user2
end
close c
deallocate c
You will want to add a transaction and error handling to roll off incomplete changes, but I'll leave that up to you.
Set the stored procedure to execute as owner.
http://msdn.microsoft.com/en-us/library/ms188354.aspx
Set trustworthy on the database where the stored procedure is located.
http://technet.microsoft.com/en-us/library/ms187861.aspx
Ensure that you have the same owner on both databases.
http://msdn.microsoft.com/en-us/library/ms188676(v=sql.105).aspx
Grant execute permissions on the stored procedure to the appropriate user or role on the database with the procedure.

How to rename users, add a role etc, using script in t-sql

I have to
Rename users (from yyyyyy\xxxx to xxxx)
Add a role to the users
See the priviliges of stored procedures granted to a specified role (I found a table with the information regarding tables, but not stored procedure)
All in t-sql. I know how to do it mannualy, but with 400+ users, I hope to script me out of the problems.
Can anyone help?
What you need to do is loop over the users to modify and execute the commands to make the changes you need. You can do this by querying the syslogins table and creating a cursor with the results.
I have added the statement to rename the user, but adding the role is as simple as adding in a second statement and exec with sp_addrolemember
DECLARE #Login as varchar(50);
DECLARE LoginsCrsr CURSOR FOR
SELECT name
FROM syslogins
WHERE name like '%John%'; --Whatever critera you need
OPEN LoginsCrsr;
FETCH NEXT FROM LoginsCrsr
INTO #Login;
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE #TSQL as varchar(255)
DECLARE #NewLogin as varchar(50)
SELECT #NewLogin = #Login -- Do your own thing here
SELECT #TSQL = 'ALTER LOGIN [' + #Login + '] WITH NAME=[' + #NewLogin + ']'
PRINT #TSQL
EXEC (#TSQL)
--Whatever else you need to do
FETCH NEXT FROM LoginsCrsr
INTO #Login
END
GO
CLOSE LoginsCrsr;
DEALLOCATE LoginsCrsr;
GO