Create database user conditionally syntax error - database

I'm using the following to create a user if it doesn't already exist in the database:
use DBExample
GO
IF NOT EXISTS (SELECT * from sys.database_role_members WHERE USER_NAME(member_principal_id) = 'user1')
BEGIN
CREATE USER [user1] WITH PASSWORD = 'abc')
END
EXEC sp_addrolemember 'role1', 'user1'
GO
DBExample already has a user1, so when I try to run the script, SQL Server Management Studio complains about an 'Incorrect syntax near 'user1'. (in the create user line)
What am I missing to make this work?

I think you're confusing Logins with Users - in SQL Server 2008R2, at least, you can't have one without the other. I'd recommend having a quick look at Books Online for these concepts.
You're probably looking for something like:
IF NOT EXISTS (SELECT * from sys.server_principals WHERE name = 'user1')
BEGIN
CREATE LOGIN [user1] WITH PASSWORD = 'abc';
END
GO
USE DBExample
GO
IF NOT EXISTS
(
SELECT * from sys.database_principals dp
INNER JOIN sys.server_principals sp on dp.sid = sp.sid
WHERE dp.name = 'user1' or sp.name = 'user1'
)
BEGIN
CREATE USER [user1] FOR LOGIN [user1]
END
GO
IF NOT EXISTS (SELECT * from sys.database_role_members WHERE USER_NAME(member_principal_id) = 'user1')
BEGIN
EXEC sp_addrolemember 'role1', 'user1'
END
GO
This creates a Login if it doesn't exist, goes to the database then creates a User if it doesn't exist, then associates a User with a Role.

I used the answer found here: https://stackoverflow.com/a/6159882 to make use of variables to substitute the user name to get around the 'There is already login/user named xxx in the database' error SSMS was complaining about. The code looks like this at the end:
USE DBExample
GO
DECLARE #userName varchar(100)
SET #userName = 'user1'
IF NOT EXISTS (SELECT * from sys.server_principals WHERE name = #userName)
BEGIN
DECLARE #LoginSQL varchar(200);
SET #LoginSQL = 'CREATE LOGIN ' + #userName + ' WITH PASSWORD = abc';
EXEC (#LoginSQL);
END
IF NOT EXISTS (SELECT * from sys.database_principals WHERE name = #userName)
BEGIN
DECLARE #UserSQL varchar(200);
SET #UserSQL = 'CREATE USER ' + #userName + ' FOR LOGIN ' + #userName;
EXEC (#UserSQL);
END
IF NOT EXISTS (SELECT * from sys.database_role_members WHERE USER_NAME(member_principal_id) = #userName)
BEGIN
EXEC sp_addrolemember 'role1', #userName
END
GO

Related

How to restrict user access to tables, but not to SPs and Views in SQL Server 2008

Prerequisites:
Let's say I have 2 tables.
Table A
With columns A, B and C.
Table B with columns A, B and C.
I also have a stored procedure to update both tables
and I have a simple View that joins the tables.
I also have a user, let's call him... "Bob".
Question:
Now;
I want "Bob" to only have access to the view and the stored procedure.
"Bob" cannot gain read/write to either table, only to the view and the stored procedure.
How would I achieve this?
Old question, but ownership chaining in SQL is the answer. The comments to the question had the answer to the SP issue, but OP implied an issue with running select against the view.
For the SP, you need to specify the execute as owner clause
CREATE PROCEDURE dbo.YourProc
WITH EXECUTE AS OWNER
AS
--Your code here
GO
GRANT EXEC ON dbo.YourProc TO Bob;
GO
And if the owner of the SP has access to those tables, this will work.
For the view, same idea. I stumbled because I created a new view/schema to limit access, I gave the user select permission on the view, but the owner of the schema did NOT have access to the underlying tables, so when the user executed the select, I got an error about not having permissions to run select against the underlying table. Changing the owner of the schema to dbo (which owned the tables) fixed this for me.
I put together a script to stage this a bit, and gives the user select permissions on the schema instead of just the view (as I know this won't be the only view they'll want)
DECLARE #Schema varchar(128) = 'Reporting', #Username varchar(255) = 'ReportingUser', #dbName varchar(255)
SELECT #dbName=db_name(dbid) FROM master.dbo.sysprocesses WHERE spid=##spid
IF NOT EXISTS (SELECT * FROM master.sys.server_principals where name = #Username)
BEGIN
Print 'User must be created/added to SQL before being given database permissions'
GOTO TheEnd
END
IF NOT EXISTS (SELECT * FROM sysusers where name = #Username)
BEGIN
EXEC sp_grantdbaccess #Username,#Username
END
IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = #SCHEMA)
BEGIN
EXEC('CREATE SCHEMA ' + #Schema)
END
EXEC ('ALTER USER '+#Username+' WITH DEFAULT_SCHEMA = ' + #Schema)
EXEC ('GRANT SELECT ON SCHEMA :: '+ #Schema +' TO '+ #Username)
TheEnd:
And to save some folks to stumble over this too some time, here are some helpful queries to identify and troubleshoot ownership:
SELECT s.name AS SchemaName,
s.schema_id,
u.name AS schema_owner
FROM sys.schemas s
INNER JOIN sys.sysusers u ON u.uid = s.principal_id
ORDER BY s.name;
This will show you schema owners
exec sp_tables #table_type = "'table', 'view'"
This will show you owner of the views/tables

Can I use a variable of type varchar with possible dots in SQL Server database?

I'm creating a procedure to add users logins to a specific database. I call this procedure inside a trigger when a user is inserted in my database (I want to have different logins for each user in my site and in the access to the database).
Here's my trigger:
ALTER TRIGGER [dbo].[sys_users_insert]
ON [dbo].[sys_users]
AFTER INSERT
AS
BEGIN
DECLARE #USERNAME varchar(MAX)
SELECT #USERNAME = INSERTED.username
FROM INSERTED
EXEC CreateUser #USERNAME
END
The CreateUser is my procedure to create the logins in the database. The procedure is working. When I call it from a query editor:
EXEC CreateUser 'patricia.santos'
The user is created with success. But when I insert a user in the table and trigger runs the user isn't created nor is inserted in the table. And I found out that if I insert a user without dots in the username everything goes ok. So I believe is something with the dot but I would like to know if there's some workaround to accept usernames with dots.
UPDATE
Here's my procedure for creating logins in my database:
USE mydatabase
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[CreateUser]
#USERNAME AS VARCHAR(MAX) = ''
AS
BEGIN
declare #sql nvarchar(MAX)
If NOT EXISTS(select loginname from master.dbo.syslogins where name = #USERNAME)
BEGIN
set #sql = 'CREATE LOGIN ' + #USERNAME +
' WITH
PASSWORD = ''something'',
CHECK_POLICY = OFF'
exec(#sql)
set #sql = 'ALTER SERVER ROLE sysadmin ADD MEMBER ' + #username
exec(#sql)
IF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = #USERNAME)
BEGIN
-- Creates a database user for the login created above.
set #sql = 'CREATE USER [' + #USERNAME + '] FOR LOGIN [' + #USERNAME + ']'
exec(#sql)
EXEC sp_addrolemember 'db_owner', #USERNAME
END
END
END
Thank you :)
I think the issue may be with your CreateUser procedure. Ensure that the “CREATE USER” clause and ALTER SERVER ROLE clause wraps the user name in square brackets. Please see updated procedure below:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[CreateUser]
#USERNAME AS VARCHAR(MAX) = ''
AS
BEGIN
declare #sql nvarchar(MAX)
If NOT EXISTS(select loginname from master.dbo.syslogins where name = #USERNAME)
BEGIN
set #sql = 'CREATE LOGIN [' + #USERNAME + '] ' +
' WITH
PASSWORD = ''something'',
CHECK_POLICY = OFF'
exec(#sql)
set #sql = 'ALTER SERVER ROLE sysadmin ADD MEMBER [' + #username + ']'
exec(#sql)
IF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = #USERNAME)
BEGIN
-- Creates a database user for the login created above.
set #sql = 'CREATE USER [' + #USERNAME + '] FOR LOGIN [' + #USERNAME + ']'
exec(#sql)
EXEC sp_addrolemember 'db_owner', #USERNAME
END
END
END
Let me know if this helps at all.

Can osql.exe or sqlcmd.exe be used to change the default password policy of instance?

Can osql.exe or sqlcmd.exe be used to change password policy of instance? I have a batch script that creates databases and users but , as you know, the "sp_addlogin" stored procedure doesn't seem to have an option to allow less than 8 char passwords when creating the user. How can I use a batch script, calling osql, to lower this policy requirement before using "sp_addlogin" to create the login?
Here is how I do it so far:
-- CREATE USER 2005/2008 sql generated from batch file
-- where username and db name are the same
IF NOT EXISTS (SELECT * FROM master.dbo.syslogins WHERE loginname = N'teee')
BEGIN
declare #loginlang nvarchar(132)
SELECT #loginlang = N'us_english'
IF #loginlang IS NULL OR (NOT EXISTS (SELECT * FROM master.dbo.syslanguages WHERE name = #loginlang) and #loginlang != N'us_english')
SELECT #loginlang = ##language
EXEC sp_addlogin N'teee', N'PassMe', N'TEEE', #loginlang
ALTER LOGIN teee WITH PASSWORD = 'PassMe' UNLOCK, CHECK_POLICY = OFF, CHECK_EXPIRATION = OFF
END
GO
IF NOT EXISTS (SELECT * FROM dbo.sysusers WHERE name = N'teee' and status != 0)
EXEC sp_grantdbaccess N'teee', N'teee'
GO
EXEC sp_addrolemember N'db_datareader', N'teee'
GO
EXEC sp_addrolemember N'db_datawriter', N'teee'
GO
EXEC sp_addrolemember N'db_owner', N'teee'
GO
The error I get when I run this is:
Msg 15116, Level 16, State 1, Server U0163499, Line 1
Password validation failed. The password does not meet Windows policy requirements because it is too short.
Msg 15151, Level 16, State 1, Server U0163499, Line 10
Cannot alter the login 'teee', because it does not exist or you do not have permission.
Obviously, if I use a password greater than or equal to 8 characters when creating the user, it works fine. I need a solution that also works on SQL 2012.
Your Code
EXEC sp_addlogin N'teee', N'PassMe', N'TEEE', #loginlang
ALTER LOGIN teee WITH PASSWORD = 'PassMe' UNLOCK, CHECK_POLICY = OFF,
CHECK_EXPIRATION = OFF
Replace with above line with Below line:
USE [master]
GO
CREATE LOGIN [teee] WITH PASSWORD=N'PassMe',
DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
GO
This code work for me
IF NOT EXISTS (SELECT * FROM master.dbo.syslogins WHERE loginname = N'teee')
BEGIN
declare #loginlang nvarchar(132)
SELECT #loginlang = N'us_english'
IF #loginlang IS NULL OR (NOT EXISTS (
SELECT * FROM master.dbo.syslanguages WHERE name = #loginlang)
and #loginlang != N'us_english')
SELECT #loginlang = ##language
--EXEC sp_addlogin N'teee', N'PassMe', N'TEEE', #loginlang
--ALTER LOGIN teee WITH PASSWORD = 'PassMe' UNLOCK,
--CHECK_POLICY = OFF, CHECK_EXPIRATION = OFF
CREATE LOGIN [teee] WITH PASSWORD=N'PassMe',
DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
END
GO
IF NOT EXISTS (SELECT * FROM dbo.sysusers WHERE name = N'teee' and status != 0)
EXEC sp_grantdbaccess N'teee', N'teee'
GO
EXEC sp_addrolemember N'db_datareader', N'teee'
GO
EXEC sp_addrolemember N'db_datawriter', N'teee'
GO
EXEC sp_addrolemember N'db_owner', N'teee'
GO

The database owner SID recorded in the master database differs from the database owner SID

When I try to install tSQLt onto an existing database i get the following error:
The database owner SID recorded in the master database differs from
the database owner SID recorded in database ''. You should correct
this situation by resetting the owner of database '' using the ALTER
AUTHORIZATION statement.
This problem can arise when a database restored from a backup and the SID of the database owner does not match the owners SID listed in the master database. Here is a solution that uses the "ALTER AUTHORIZATION" statement recommended in the error message:
DECLARE #Command VARCHAR(MAX) = 'ALTER AUTHORIZATION ON DATABASE::[<<DatabaseName>>] TO
[<<LoginName>>]'
SELECT #Command = REPLACE(REPLACE(#Command
, '<<DatabaseName>>', SD.Name)
, '<<LoginName>>', SL.Name)
FROM master..sysdatabases SD
JOIN master..syslogins SL ON SD.SID = SL.SID
WHERE SD.Name = DB_NAME()
PRINT #Command
EXEC(#Command)
Added this to the top of the tSQLt.class.sql script
declare #user varchar(50)
SELECT #user = quotename(SL.Name)
FROM master..sysdatabases SD inner join master..syslogins SL
on SD.SID = SL.SID
Where SD.Name = DB_NAME()
exec('exec sp_changedbowner ' + #user)
Apply the below script on database you get the error:
EXEC sp_changedbowner 'sa'
ALTER DATABASE [database_name] SET TRUSTWORTHY ON
The simplest way to change DB owner is:
EXEC SP_ChangeDBOwner 'sa'
Necromaning:
If you don't want to use the SQL-Server 2000 views (deprecated), use this:
-- Restore sid when db restored from backup...
DECLARE #Command NVARCHAR(MAX)
SET #Command = N'ALTER AUTHORIZATION ON DATABASE::<<DatabaseName>> TO <<LoginName>>'
SELECT #Command = REPLACE
(
REPLACE(#Command, N'<<DatabaseName>>', QUOTENAME(SD.Name))
, N'<<LoginName>>'
,
QUOTENAME
(
COALESCE
(
SL.name
,(SELECT TOP 1 name FROM sys.server_principals WHERE type_desc = 'SQL_LOGIN' AND is_disabled = 'false' ORDER BY principal_id ASC )
)
)
)
FROM sys.databases AS SD
LEFT JOIN sys.server_principals AS SL
ON SL.SID = SD.owner_sid
WHERE SD.Name = DB_NAME()
PRINT #command
EXECUTE(#command)
GO
Also prevents bug on oddly named database or user, and also fixes bug if no user is associated (uses sa login).

How to find out whether sql server login has any user mapped to using SMO

I need to write a function to delete the login in the database if it does not have any users to map to using SQL Server Management Objects (SMO). How can I achieve this ?
Just like to add that when using the login.EnumDatabaseMappings(),when there are no users mappped to the login , will return null.So you can not use something like login.EnumDatabaseMappings().Length rather you should use
mylogin = server.Logins(loginName)
If Not mylogin Is Nothing Then
If Not mylogin.EnumDatabaseMappings() Is Nothing Then
mylogin.Drop()
End If
End If
How about this:
Server server = new Server("your server name");
foreach (Login login in server.Logins)
{
DatabaseMapping[] mappings = login.EnumDatabaseMappings();
}
Should work and give you what you're looking for.
Give this a go,
If you comment out the code about the cursor and look at the result of the select statement you can see what logins it wants to drop.
USE MASTER;
GO
DECLARE #loginName varchar(max)
DECLARE #SQL varchar(max)
CREATE TABLE #dbusers (
sid VARBINARY(85))
EXEC sp_MSforeachdb
'insert #dbusers select sid from [?].sys.database_principals where type != ''R'''
DECLARE loginCursor CURSOR FOR
SELECT name
FROM sys.server_principals
WHERE sid IN (SELECT sid
FROM sys.server_principals
WHERE TYPE != 'R'
AND name NOT LIKE ('##%##')
EXCEPT
SELECT DISTINCT sid
FROM #dbusers)
AND type_desc = 'SQL_LOGIN'
OPEN loginCursor
FETCH NEXT FROM loginCursor into #loginName
WHILE ##FETCH_STATUS=0
BEGIN
SET #SQL = 'DROP LOGIN '+#loginName
EXEC sp_executesql #SQL
END
CLOSE loginCursor
DEALLOCATE loginCursor
GO
DROP TABLE #dbusers

Resources