SQL CREATE LOGON - can't use #parameter as username - sql

I'm a developer and I suck at SQL:) Please help me out here.
I'd like to create my own Stored Procedure that creates a Tenant in my SaaS database. In order to do this I need to create a new SQL Login for the Tenant and then add it to a predefined SQL Role.
I'm already stumped just trying to create the Login. Here is what I've tried...
CREATE PROCEDURE [MyScheme].[Tenants_InsertTenant]
#username nvarchar(2048),
#password nvarchar(2048)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
CREATE LOGIN #username WITH PASSWORD = #password
END
Msg 102, Level 15, State 1, Procedure Tenants_InsertTenant, Line 16
Incorrect syntax near '#username'.
Msg 319, Level 15, State 1, Procedure Tenants_InsertTenant, Line 16
Incorrect syntax near the keyword 'with'. If this statement is a common table expression, an xmlnamespaces clause or a change tracking context clause, the previous statement must be terminated with a semicolon.
I realize this should be straightforward but when your new to SQL and the SQL manager errors are as cryptic as they seem to be to me its better just to ask for help:)
Thanks,
Justin

Apparently CREATE LOGIN only accepts literals.
You could try wrapping it in an exec and building it as a string:
EXEC('CREATE LOGIN ' + quotename(#username) + ' WITH PASSWORD = ' + quotename(#password, ''''))
edit: added quotename for safety from sql injection attacks

Posible solution:
sp_addlogin #loginame = 'test', #passwd = 'test', #defdb = 'test'

Try this:
declare #t nvarchar(4000)
set #t = N'CREATE LOGIN ''''' + #username + ''''' WITH PASSWORD = ''''' + #password
exec sys.sp_executesql #t

Building on the answers from #codeulike and #Galkin I ended up doing this:
DECLARE #t nvarchar(4000)
SET #t = N'CREATE LOGIN ' + QUOTENAME(#username) + ' WITH PASSWORD = ' + QUOTENAME(#password, '''') + ', default_database = ' + QUOTENAME(#DatabaseName)
EXEC(#t)
I'm running SQL Server 2019 and combining EXEC(--> with QUOTENAME() inside <--) on the same line does not work.
If I understand the microsoft sql injection documentation using quotename to build a string then executing protects you from SQL injections.

Related

Create trigger in CREATE DATABASE stored procedure

We have a stored procedure that creates a database for each of our customers. This stored procedure runs in the context of master. A database name is passed in as a parameter to the stored procedure.
I am trying to modify the stored procedure to add a trigger to a table. I understand the stored procedure must switch to the new database to create triggers, so have appended the following to the stored procedure:
SET #str = ('USE ' + QUOTENAME (#db_name) + ' GO
CREATE TRIGGER ...')
EXEC (#str);
I get the error
Msg 102, Level 15, State 1, Line 3
Incorrect syntax near 'GO'.
Msg 111, Level 15, State 1, Line 4
'CREATE TRIGGER' must be the first statement in a query batch.
Now I assume the second error is a consequence of the first, but I am blowed if I can work out how to switch databases in the stored procedure in order to create the triggers.
We are using SQL Server 2019. How do I create triggers in a create database stored procedure?
Another approach is to execute statement one after another.
DECLARE #str VARCHAR(MAX)
DECLARE #db_name SYSNAME = 'YourDBName'
SET #str = 'USE ' + QUOTENAME (#db_name)
EXEC (#str)
SET #str = 'CREATE TRIGGER ...'
exec (#str);
For completeness, as mentioned by #DaleK, adding the approach mentioned by Aaron Bertnard in the Stackexchange link
DECLARE #str VARCHAR(MAX)
DECLARE #db_name SYSNAME = 'master'
SET #str = 'EXEC '+ #db_name + '..sp_executesql #stmt=N''CREATE PROCEDURE usp_test AS SELECT 1;'''
exec (#str);

SQL error - Incorrect syntax near the keyword 'Database'

I want to detect databases beginning with 'NAV'in a MS SQL DB. I tried it with this code:
DECLARE #DBName NVARCHAR(MAX);
SET #DBName = (SELECT name FROM master.dbo.sysdatabases where name LIKE '%NAV%');
EXECUTE ('USE' + #DBName);
But I got the error message:
Msg 156, Level 15, State 1, Line 1
Incorrect syntax near the keyword 'Database'.
Do know what is wrong there?
I'm not sure about the keyword database in the error message, but I do spot problems:
You concatenate USE and the databasename without a space: USENAV01 doesn't work. You should use 'USE ' + QUOTENAME(#DBName).
Secondly, I don't know what the intention is, but if you do EXECUTE ('USE ' + #DBName); followed by other (dynamic) queries, the following queries are executed on your current connection. In other words, the USE XXX doesn't matter for the following queries.
Thirdly, as mentioned by Jesse in below comment; if you have more than one database with a name like '%NAV%' (which your question suggests), your code is only executed for one of those databases. Which one that will be is unpredictable without an order by. If you want to execute code for all relevant databases, you have to loop through them.
Put a space after USE and put brackets around the database name:
DECLARE #DBName NVARCHAR(MAX);
SET #DBName = (SELECT name FROM master.dbo.sysdatabases where name LIKE '%NAV%');
EXECUTE ('USE [' + #DBName + ']');

Stored procedure for a login with user and password as parameter

create procedure createacc(
#loginnaam nvarchar(30),
#wachtwoord nvarchar(30))
AS
CREATE LOGIN #loginnaam
WITH PASSWORD = #wachtwoord
CREATE USER #loginnaam FOR LOGIN #loginnaam
ALTER ROLE db_datareader ADD #loginnaam
Go
This gives me syntax errors while it works fine outside of the Stored Procedure.
Msg 102, Level 15, State 1, Procedure createacc, Line 5
Incorrect syntax near '#loginnaam'.
Msg 319, Level 15, State 1, Procedure createacc, Line 6
Incorrect syntax near the keyword 'with'. If this statement is a common table expression, an xmlnamespaces clause or a change tracking context clause, the previous statement must be terminated with a semicolon.
Msg 156, Level 15, State 1, Procedure createacc, Line 8
Incorrect syntax near the keyword 'ADD'.
I would suggest using dynamic sql for this. Declare a nvarchar variable, put your statement inside it and execute that statement.
For example in your case:
DECLARE #statement NVARCHAR(MAX)
SET #statement = 'CREATE LOGIN ' + #loginnaam + ' WITH PASSWORD = '''+ #wachtwoord + ''' ;'
exec (#statement)
Above answer may be velnarable to SQL injection, it is concating SQL statement and then executing it.
Is there any way to pass password string as a parameter like example below
declare #query nvarchar(500)
declare #params nvarchar(500)
declare #passwordVal nvarchar(100)
set #passwordVal=N'Test#123'
set #query = 'Alter LOGIN [SqlUser] WITH password=#pass'
set #params = N'#pass NVARCHAR (100)';
EXECUTE sp_executesql #query,#params, #pass=#passwordVal
This query generates error
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near '#pass'.
Better alternative answer to parameter is here
I am trying to create a stored procedure to create a login and a database user?

Stored procedure, sysname and quotname()

New at this...
A provider (I assume it is their code) has set up a stored procedure to modify a user's SQL server password. They have used sysname types for the passed information: OLD, NEW and LOGINAME.
When they execute the command to change the password they use quotename() to bracket the text passed to the function.
Before that, while checking to see if the username exists, they pass the LOGINAME without any format control.
This has not been a problem in the past but since we have recently changed our username policy from initial+surname (FSURNAME) to firstname(dot)surname (FIRST.SURNAME) the routine crashes out. I think it is because of the lack of quotename()-style control over the username when passed to the see-if-they-exist function.
Code:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [sys].[sp_password]
#old sysname = NULL, -- the old (current) password
#new sysname, -- the new password
#loginame sysname = NULL -- user to change password on
as
-- SETUP RUNTIME OPTIONS / DECLARE VARIABLES --
set nocount on
declare #exec_stmt nvarchar(4000)
-- RESOLVE LOGIN NAME
if #loginame is null
select #loginame = suser_sname()
if #new is null
select #new = ''
-- DISALLOW USER TRANSACTION --
set implicit_transactions off
IF (##trancount > 0)
begin
raiserror(15002,-1,-1,'sys.sp_password')
return (1)
end
-- CHECK IT'S A SQL LOGIN --
if not exists (select * from master.dbo.syslogins where
loginname = #loginame and isntname = 0)
begin
raiserror(15007,-1,-1,#loginame)
return (1)
end
if #old is null
set #exec_stmt = 'alter login ' + quotename(#loginame) +
' with password = ' + quotename(#new, '''')
else
set #exec_stmt = 'alter login ' + quotename(#loginame) +
' with password = ' + quotename(#new, '''') + ' old_password = ' + quotename(#old, '''')
exec (#exec_stmt)
if ##error <> 0
return (1)
-- RETURN SUCCESS --
return (0) -- sp_password
I strongly suspect that the code here:
if not exists (select * from master.dbo.syslogins where
loginname = #loginame and isntname = 0)
is causing the issue as, as I understand it, the code is passing FIRST.LAST to the checking routine, which is then being interpreted it as an object rather than as text.
Is it possible to do the same thing but forcing text to be sent? Something like the quotename() functions being used elsewhere in the code?
EDIT:
The call that executes this stored procedure: sp_password NULL, abcdefgh, FIRST.LAST
Error received: Msg 102, Level 15, State 1, Line 1 Incorrect syntax near '.'.
When manually executed by adding the brackets (sp_password NULL, abcdefgh, [FIRST.LAST]) it of course works perfectly.
This is due to how the Sql Server handles parameter values of type sysname. Consider this.
create procedure TakeSysname
#Sysname sysname
as
--does nothing;
go
--This works.
exec TakeSysname JohnDoe;
print N'Worked';
go
--This works.
exec TakeSysname 'John.Doe';
print N'Worked'
go
--This does not.
exec TakeSysname John.Doe;
print N'Worked';
go
I'm still looking for some good explanation though.

Use database dynamically

This execution is giving me the following error:
Msg 102, Level 15, State 1, Line 5
Incorrect syntax near 'go'.
Msg 111, Level 15, State 1, Line 11
'CREATE/ALTER PROCEDURE' must be the first statement in a query batch.
If i remove the "GO" it gives me just the second one.
Any hints of what am I missing?
declare #dbname varchar(500)
set #dbname='master'
Exec ('
Use ' + #dbname + '
go
create PROCEDURE [dbo].[krijo_database] #dbname nvarchar(2000), #Direktoria varchar(4000)
AS
BEGIN
declare #stringu nvarchar(100)
set #stringu =
''CREATE DATABASE '' + #dbname
exec (#stringu)
End
')
Answer
declare #dbname varchar(500)
set #dbname='kontabel'
Exec(
'Use ' + #dbname +'
Exec (''
create PROCEDURE [dbo].[krijo_database] #dbname nvarchar(2000), #Direktoria varchar(4000)
AS
BEGIN
declare #stringu nvarchar(100)
set #stringu =
''''create DATABASE '''' + #dbname
exec (#stringu)
End
'')
')
Actually I tried like this and it worked but I had to change quotes.
The real procedure that I would like to use is more than 50000 lines and I can't go and manually change the quotes to everything.
Is there a better way?
Two issues:
Using "GO" is incorrect... there is no SQL keyword called "GO"... that's just a hack that SQL Server Management Studio is performing for you.
You need to the CREATE PROCEDURE command in it's own context... simple.
Here's the slight modification to your script:
declare #dbname varchar(500)
set #dbname='master'
Exec ('
Use ' + #dbname + '
EXECUTE(''create PROCEDURE [dbo].[krijo_database] #dbname nvarchar(2000), #Direktoria varchar(4000)
AS
BEGIN
declare #stringu nvarchar(100)
set #stringu =
''''CREATE DATABASE '''' + #dbname
exec (#stringu)
End'')
')
So the answer is to put another "EXECUTE" command inside the first EXECUTE command. I do this all the time, a lot of times in an "sp_msforeachdb". You can nest those bad boys as long as you want.
Sometime ago I had code which was updating database structure based on scripts.
I end up with split file by 'go' and execute separately each fragment. Can you try this?
So, first exec use statement, and than exec createprocedure.
Be sure to verify that it is created in proper database
My mistake, I didn't notice something.
maybe it's the Exec inside the Exec that's causing the error?
or because your'e assigning a nvarchar(2000) to a nvarchar(100)
"Msg 102, Level 15, State 1, Line 5 Incorrect syntax near 'go'. Msg 111, Level 15, State 1, Line 11 'CREATE/ALTER PROCEDURE' must be the first statement in a query batch. " If i remove the "GO" it gives me just the second one.
try this without any use or go: create PROCEDURE '+#dbname+'.[dbo].[krijo_database] #dbname nvarchar(2000)
You can't use GO like that
It isn't a SQL command
It tells SSMS to split the batch
If you remove it, then you'll get "first in batch" error which is expected
In this case, why not just do this...
Use master
GO
create PROCEDURE [dbo].[krijo_database] #dbname nvarchar(2000), #Direktoria varchar(4000)
AS
BEGIN
declare #stringu nvarchar(100)
set #stringu = 'CREATE DATABASE ' + #dbname
exec (#stringu)
End
GO
Why do need dynamic SQL to create a stored procedure?
USE master
GO
CREATE PROCEDURE dbo.create_database #name nvarchar(100)
AS
DECLARE #sql nvarchar(100)
SET #sql = 'CREATE DATABASE ' + QUOTENAME(#name)
EXEC (#sql)
GO
What you're after can't be done I don't think.
See this article for reference
The Real procedure that i would like to use it is a very big one, more dhan 50000 lines and i can't go on an changing the quotes to everything
Microsoft SQL Server has a maximum length of varchar of 8000 characters.
http://www.databasejournal.com/features/mssql/article.php/3788256/Data-Types-in-SQL-Server-2008.htm
you should create stored procedure with that portion with variable.
I used SQl DMO!
Great feature both for 32 and 64 bit,compatible with both SQL express and server!