Changes to sysusers and sysxlogins in SQL 2008 - sql

I am currently updating a MS SQL 2000 server to SQL 2008. One of the issues highlighted by the Upgrade advisor is that the undocumented table sysxlogins has been removed.
I currently have a procedure that is run by a user 'foo' to determine if the user 'bar' exists in the database blah. If the user exists the user's password is compared to the password that was passed in to the procedure in order to determine if bar is allowed to log in to an application, it looks like this:
#UserName Varchar(50),
#Password Varchar(50)
As
Set NoCount On
------------------------------------------------------------------------------------
-- Check username
------------------------------------------------------------------------------------
If Exists
(
select top 1 name
from blah.dbo.sysusers With (NoLock)
where name = #UserName
)
Begin
------------------------------------------------------------------------------------
-- Check Password
------------------------------------------------------------------------------------
If Not Exists
(
Select *
From master.dbo.sysxlogins With (NoLock)
Where srvid IS NULL
And name = #Username
And ( ((#Password is null) or (#Password = '') and password is null)
Or (pwdcompare(#Password, password, (CASE WHEN xstatus&2048 = 2048 THEN 1 ELSE 0 END)) = 1))
)
Begin
Return 2
End
Else
Begin
------------------------------------------------------------------------------------
-- Check Role
------------------------------------------------------------------------------------
Select usg.name
From blah.dbo.sysusers usu
left outer join (blah.dbo.sysmembers mem inner join blah.dbo.sysusers usg on mem.groupuid = usg.uid) on usu.uid = mem.memberuid
left outer join syslogins lo on usu.sid = lo.sid
where usu.name = #Username
and usg.name not like 'db_%'
Return 0 -- Username and password correct
End
End
Else
Begin
Return 1 -- Username incorrect
End
This all works fine under SQL 2000, yet I must now pay the price of using undocumented system tables and make it work under 2008.
There are two problems with this, the first problem is that foo can no longer see all of the database users when executing:
select * from blah.dbo.sysusers
or Microsoft's recommended alternative:
select * from blah.sys.database_principals
I understand that this is due to the fact that members of the public role no longer have access to object meta data unless they are a member of sysadmin or have the View Definition permission on the object.
It is not possible for foo to be a member of sysadmin, so as far as I understand I need to grant foo the View Definition permission, but on which object? I don't think I do it on the system view, so do I do it on every single user?
Secondly, and similarly, I need to change my reference to sysxlogins to sys.sql_logins. Again foo can only see itself and sa when executing
select * from sys.sql_logins
How can I get foo to see all of the server logins in this list?
There will no doubt be similar problems when accessing sysmembers and syslogins later on in the code but hopefully an understanding of the two examples above will help me to sort the rest out.
Thanks in advance,

You can grant the SELECT right directly on sys.database_principals, as long as the login has a user in the master database. For example:
use master
create user MyUser for login MyUser
grant select on sys.database_principals to MyUser
Then, in SQL Server 2008, passwords are encrypted, even for the administrator. You can, however, verify a password by trying to change it. The change procedure will give an error if the old password is incorrect.
declare #rc int
begin try
exec #rc = sp_password 'welcome', 'welcome', 'MyUser'
end try
begin catch
set #rc = ERROR_NUMBER()
end catch
-- Should be 0 on success
select #rc
For this to work, you have to disable Enforce password policy in the Login Properties dialog. Otherwise, the policy would prevent you from changing your password too often.

I think GRANT SELECT ON... is more troublesome as one have to add the user to the master database. The below was the solution for me:
USE master
GRANT VIEW ANY DEFINITION TO foo
If you have an app that works on various versions of SQL you need to check if the server version is higher then 8 (GRANT VIEW ANY DEFINITION works from SQL 2005 though it seemes not be needed there).

Related

Better way to check if a SQL login exists

I was going over older stored procedure updating code and in one of them it was looking if a login exists.
I started to change it to the way I've been using but wanted to check online to make sure. Is what is here Checking if a SQL Server login already exists better
IF NOT EXISTS
(SELECT name
FROM sys.database_principals
WHERE name = 'Bob')
BEGIN
CREATE USER [Bob] FOR LOGIN [Bob]
END
or would this work just the same, better, worse!?
IF (ISNULL(Is_srvrolemember ('public', 'boguslogin'), 2) = 2) --any number not 1 or 0
BEGIN
CREATE LOGIN [boguslogin] WITH PASSWORD = N'password'
END
I've been using the Is_srvrolemember one for a while now. I wanted to do this in one line without select and found this to be the easiest way.
Is_srvrolemember returns a 1 if is a public login, a 0 if not and a NULL if login doesn't exist.
This is also assuming a login was not explicitly removed from the public role, which isn't common and we've never done on our servers.
Just curious what others think, is there any reason not to do it like this?
Now I'm wondering if I need to go back and change my code to the select statement. None of mine actually create a login but go on to do other things like send an admin email after a login fails if login does not actually exist.
DECLARE #this_login VARCHAR(200)
IF (ISNULL(Is_srvrolemember ('public', #this_login ), 2) = 2) --any number not 1 or 0
BEGIN
EXEC msdb.dbo.Sp_send_dbmail
#profile_name = #profile_name,
#recipients = #recipients,
#body = #body,
#subject = #subject,
#exclude_query_output = 0
END
I think the issue is your "I wanted to do this in one line" bit, but this is T-SQL, and that's a bad stance to take. Go with the sys.database_principals if you can. It's much cleaner.

Cross Database Trigger with EXECUTE AS not working - permission problem

I have 2 Databases. Database A belongs to an ERP System and Database B is my own database.
I've created an AFTER INSERT DML Trigger in a table which belongs to Database A.
Every single ERP User is associated with a DB User.
At the moment I copy all the users from Database A to Database B to grant permission for my tables.
It was the only solution I could find to make it work but it's bad because a new ERP User will get an Error Message when there's no user in Database B.
So now I tried it once again to use WITH EXECUTE AS .... I tried OWNER and different DB users but nothing works and I have absolutely no idea what's wrong.
I even granted all permissions for the user on the tables I use in the trigger.
Let's say I have a user called "triggerUser" when I do WITH EXECUTE AS 'triggerUser' it doesn't work but when I remove the EXECUTE AS .... and use the triggerUser for the ERP Login it works so the user got the permissions to write my tables.
Here's my trigger. Maybe you have an idea what I'm missing.
CREATE TRIGGER [dbo].[TR_manAddAutomatikQueueForLager_AfterInsert]
ON [SL_MWAWI].[dbo].[LAGERPROTOKOLL]
WITH EXECUTE AS 'moep'
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
DECLARE #Mandant int
SET #Mandant = (SELECT MANDANT_ID FROM SL_Daten.dbo.MANDANT WHERE Datenbankname = 'SL_MWAWI')
-- insert
IF EXISTS (SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted)
BEGIN
INSERT INTO maniacSellerGen2.dbo.manAutomatikQueue
SELECT #Mandant, ISNULL(wsa.WebShopId, wsav.WebShopId), ISNULL(wsav.VaterArtikelnummer, wsa.Artikelnummer), 'Lager', GETDATE()
FROM inserted
LEFT JOIN maniacSellerGen2.dbo.manWebShopArtikel wsa
ON wsa.Artikelnummer COLLATE DATABASE_DEFAULT = inserted.Artikelnummer COLLATE DATABASE_DEFAULT
AND #Mandant = wsa.Mandant
LEFT JOIN maniacSellerGen2.dbo.manWebShopArtikelVarianten wsav
ON wsav.Artikelnummer COLLATE DATABASE_DEFAULT = inserted.Artikelnummer COLLATE DATABASE_DEFAULT
AND #Mandant = wsav.Mandant
WHERE ((wsa.Artikelnummer IS NULL AND wsav.Artikelnummer IS NOT NULL)
OR (wsa.Artikelnummer IS NOT NULL AND wsav.Artikelnummer IS NULL))
END
END
Here's a screenshot from the permissions for the user in every database it has db_datareader and db_datawriter.
Permissions
I also did tried this:
GRANT INSERT ON dbo.manAutomatikQueue TO moep
GRANT DELETE ON dbo.manAutomatikQueue TO moep
GRANT UPDATE ON dbo.manAutomatikQueue TO moep
GRANT SELECT ON dbo.manAutomatikQueue TO moep
Thanks a lot!

check for commonly used passwords on SQL Server

NCSC has published a list of the 100.000 most used passwords, see 100K passwords
I wondered if any of my users had used one of these passwords, so I wrote a check, inspired by an idea from bp_check.
Am I doing it the correct way?
I had problems with UniCode, but that should have been solved by now.
Here is my code to run such a password check:
-- script to check if any of the passwords on your SQL Server is amongst the 100.000 most used passwords that Troy Hunt and NCSC released
-- see https://www.ncsc.gov.uk/blog-post/passwords-passwords-everywhere
--drop table dbo.PwnedPasswordTop100k
create table dbo.PwnedPasswordTop100k ( pw nvarchar(500) collate Latin1_General_CS_AS not null)
go
bulk insert dbo.PwnedPasswordTop100k
FROM 'c:\temp\PwnedPasswordTop100k.txt'
WITH
(
FIELDTERMINATOR = ',',
ROWTERMINATOR = '\n'
, codepage=65001
)
go
SELECT sl.name , ppt.pw
from sys.sql_logins sl
cross join dbo.PwnedPasswordTop100k ppt
where PWDCOMPARE(ppt.pw, sl.password_hash) = 1
union all
SELECT s.name, 'password is NULL' FROM sys.sql_logins s -- password is null (from idea from BP_Check http://aka.ms/BPCheck;)
where password_hash is null
AND exists(SELECT * FROM fn_my_permissions(NULL, 'SERVER') where permission_name='CONTROL SERVER')
and name NOT IN ('MSCRMSqlClrLogin','##MS_SmoExtendedSigningCertificate##','##MS_PolicySigningCertificate##','##MS_SQLResourceSigningCertificate##','##MS_SQLReplicationSigningCertificate##','##MS_SQLAuthenticatorCertificate##','##MS_AgentSigningCertificate##','##MS_SQLEnableSystemAssemblyLoadingUser##')
union all
SELECT s.name, s.Name FROM sys.sql_logins s -- password the same as login (from idea from BP_Check http://aka.ms/BPCheck;)
where PWDCOMPARE(s.name, s.name) = 1
union all
select 'not SYSADM', 'You do not have CONTROL SERVER permissions, and cannot see any password_hashes'
where not exists(SELECT * FROM fn_my_permissions(NULL, 'SERVER') where permission_name='CONTROL SERVER')
--select top (10000) * from dbo.PwnedPasswordTop100k where pw like N'пїЅпїЅпїЅпїЅ'
begin try
drop table dbo.PwnedPasswordTop100k
end try
begin catch
end catch
The PwnedPasswordTop100k.txt file must be copied to c:\temp on the SQL Server, and the account that runs SQL Server, must have access to the file. Alternatively, change the path to a place which the SQL Server can see.
Not everyone has access to a share that SQL Server can see. So I've also written a script with all the passwords:
Script with 100.000 passwords
It is a SELECT Statement that is 1500 kB. Quite big, but you can run it in SQL Server Management Studio.
My SQL Server can check about 6 users per minute, so the script is not very fast.

Find all databases where particular user exists and its role

I have a huge instance containing 1000+ databases. I need to find a way to query entire instance and find databases that contain particular user and what role this user has. I am not interested whether the user is orphanded. I just want to know which databases have this user and which do not.
Lets say that my user is called TestUser. Databases that do not contain this user should return NULL.
I would like the results in the following format:
Column1 - Database Name
Column2 - UserName (if exists or else NULL)
Column3 - UserRole (if exists or else NULL)
Under the assumption that you are not looking for issuing 1000+ selects, one (extremely ugly) solution would be:
SELECT 'DB_1' , UserName , UserRole
FROM DB_1.UsersTable
WHERE Username = 'TestUser'
UNION
SELECT 'DB_2' , UserName , UserRole
FROM DB_2.UsersTable
WHERE Username = 'TestUser'
:
:
Another solution is to use DYNAMIC SQL:
Collect the list of all the DBs that you to check,
Build a string hosting a select statement like the one above,
Execute the statement.
Again, both methods are shameful.
create table #temp
(
dbname sysname,
dbrole sysname,
dbuser sysname
)
Exec sp_msforeachdb '
if db_id()>4
Begin
insert into #temp
select db_name(), rp.name as database_role, mp.name as database_user
from sys.database_role_members drm
join sys.database_principals rp on (drm.role_principal_id = rp.principal_id)
join sys.database_principals mp on (drm.member_principal_id = mp.principal_id)
End
'
The Roles Part is referenced from here:
Get list of all database users with specified role

How can i create view using the SYSTEM_USER's permission path in query

I am attempting to make a view of a table that uses the current user's user name (System_user) with their log in information ([permission path]) which tells me which subset of the data they are allowed to see in the view. Views cannot be created with variables or temporary tables so made an attempt at user defined functions to return a table but it appears to be a bit beyond my current skills.
It works as a Query
DECLARE #user as varchar(50)
SELECT #user = SYSTEM_USER
CREATE TABLE LoginInformation(
[account name] varchar(50),
[type] varchar(50),
[privilege] varchar(50),
[mapped login name] varchar(50),
[permission path] varchar(50)
)
INSERT LoginInformation
EXEC xp_LoginInfo #AcctName = #user, #Option = 'all';
SELECT * FROM SomeTableName
WHERE Permission IN (SELECT [permission path] FROM LoginInformation)
When creating a view I get stuck because I cannot declare a variable:
DECLARE #user as varchar(50)
SELECT #user = SYSTEM_USER
When I tried user defined functions i got held up because I could not Insert an Executed statement
INSERT LoginInformation
EXEC xp_LoginInfo #AcctName = #user, #Option = 'all';
I also do not appear to be able to do the simplest solution:
INSERT LoginInformation
EXEC xp_LoginInfo #AcctName = SYSTEM_USER, #Option = 'all';
Ideally i would have a function that took System_user as a parameter and gave me a table of just that user's [permission path]. I could then use that in my view's creation.
WHERE Permission IN (MyFunctionName(SYSTEM_USER))
xp_LoginInfo is a stored procedure. You can see its underlying code by running the following T-SQL statement:
sp_helptext xp_LoginInfo
Which produces the output:
create procedure sys.xp_logininfo
#acctname sysname = null, -- IN: NT login name
#option varchar(10) = null, -- IN: 'all' | 'members' | null
#privilege varchar(10) = 'Not wanted' OUTPUT -- OUT: 'admin' | 'user' | null
as
set nocount on
etc ...
My suggestion is to take the underlying SQL, tweak it if needed and use it in your view.
While doing additional research I found a much easier and different way of accomplishing my goal. I went with the Is_Member function. It accesses the permission path without having to work with SYSTEM_USER. I have done a few tests and it appears to do what I want.
CREATE VIEW [dbo].[vw_SomeTableName]
SELECT * FROM SomeTableName
where WHERE Is_Member( UserName ) = 1 )
The Is_Member function indicates whether the current user is a member of the specified Microsoft Windows group or SQL Server database role.
IS_MEMBER ( { 'group' | 'role' } )
Return value:
Current user is not a member of group or role, returns 0.
Current user is a member of group or role, returns 1.
Either group or role is not valid. When queried by a SQL Server login or a login using an application role, returns NULL for a Windows group.
For more on Is_Member check out msdn.microsoft.com on Is_Member or look at some other stack overflow questions.