How to update the SSRS Users table after a username change? - sql-server

My user name has changed from jdoe to john.doe (for example). The reference in the [dbo].[Users] table shows my old user name, jdoe.
However, with my new user name, john.doe, the subscriptions fail to run and throw the error:
Failure sending mail: The user or group name 'ABCDE\jdoe' is not
recognized.Mail will not be resent.
The [dbo].[Subscriptions].[OwnerID] value references the [dbo].[Users].[UserID] for jdoe.
Can I simply change the [dbo].[Users].[UserName] value to my new username, leaving the [dbo].[Users].[UserID] and [dbo].[Users].[Sid] columns as is?

On your SQL Server, go to Security -> Logins. Then, rename the old username with the new username (just right click on it and select "Rename").
Next, go to the "Report Server" database, select the "Users" table, and update the "UserName" column for your old user.
This script should do the work:
UPDATE [ReportServer].[dbo].[Users] SET UserName='domain\newUser' WHERE UserName='domain\OldUser'
The SSRS Subscriptions and other SSRS features should now work properly.

I would use a MERGE statement for this. Check the transaction on a rollback to test with first. You'll also need UPDATE permission on the dbo.Users table.
SET XACT_ABORT ON
BEGIN TRANSACTION;
;WITH
users_list
AS
(
SELECT users_list.* FROM (VALUES
('DOMAIN\OldUser1', 'DOMAIN\New.User1')
, ('DOMAIN\OldUser2', 'DOMAIN\New.User2')
) users_list ([UserName], [NewUserName])
)
,
users_source -- don't add users that already exist to prevent duplicates
AS
(
SELECT
[UserName]
, [NewUserName]
FROM
users_list
WHERE
1=1
AND [NewUserName] NOT IN(SELECT [UserName] FROM [ReportServer].[dbo].[Users])
)
MERGE [ReportServer].[dbo].[Users] AS T
USING users_source AS S
ON T.[UserName] = S.[UserName]
WHEN MATCHED
THEN UPDATE SET
T.[UserName] = S.[NewUserName]
OUTPUT ##SERVERNAME AS [Server Name], DB_NAME() AS [Database Name], $action, inserted.*, deleted.*;
ROLLBACK TRANSACTION;
--COMMIT TRANSACTION;
GO

I have tried renaming the dbo.Users.UserName to the new DOMAIN/JDoe, but that still produces errors.
However, renaming the SQL Server's Security Username from DOMAIN/OldName to DOMAIN/NewName did the trick.

Related

What's the workaround to pass parameters to triggers?

I have a trigger in "Contracts" and I also have a table called "Audits" (self explanatory).
Everything is working fine. If I insert, edit or delete, a row is inserted into Audits table by the trigger...
The problem here is that Trigger does not accept parameters... and I have a table column called "TriggeredBy" inside of the Audits table... which is supposed to have the User's ID (whoever did the insert, delete or UPDATE).
Is there a workaround that I can use so I can pass that value to that trigger?
If you have the db connection opened for the duration of the application, you can keep track of who is associated with the current db session by having a table with session if, user id.
SessionId int,
UserId varchar(20)
At login time, use ##SPID to store the session ID and associated user.
The trigger can then use ##SPID and retrieve the user ID from the table and insert it into the log table.
Option 2:
Use an application role. Allow users to connect to SQL server database using Windows Integrated Security. Call sp_setapprole to set the role. Users should be given no access to any table. The app role should have insert update delete.
You can now determine the user in your trigger.
If the desktop application used Windows authentication, you could simply use ORIGINAL_LOGIN() or SUSER_SNAME() to get the end user account name in trigger code.
With a shared SQL login, one method is to store the end user name in SQL session context for use by the trigger. Session context allows you to store name/value pairs using the sp_set_session_context procedure and read current session values with the SESSION_CONTEXT function. Call sp_set_session_context with the current user name after opening a new SQL connection so that it can be used by triggers to identify the end user.
Example T-SQL code below. Also, see this answer for other methods to set/use session level values.
CREATE TRIGGER TR_YourTable
ON dbo.YourTable
FOR INSERT, UPDATE, DELETE
AS
DECLARE #TriggeredBy sysname = COALESCE(CAST(SESSION_CONTEXT(N'end-user-name') AS sysname), N'unknown');
IF EXISTS(SELECT 1 FROM inserted) AND EXISTS(SELECT 1 FROM deleted)
BEGIN
INSERT INTO dbo.YourAuditTable (Action, SomeColumn, TriggeredBy)
SELECT 'updated', SomeColumn, #TriggeredBy
FROM deleted;
END
ELSE
BEGIN
IF EXISTS(SELECT 1 FROM inserted)
BEGIN
INSERT INTO dbo.YourAuditTable (Action, SomeColumn, TriggeredBy)
SELECT 'inserted', SomeColumn, #TriggeredBy
FROM inserted;
END
ELSE
BEGIN
INSERT INTO dbo.YourAuditTable (Action, SomeColumn, TriggeredBy)
SELECT 'deleted', SomeColumn, #TriggeredBy
FROM deleted;
END;
END;
GO
--Example T-SQL usage. Queries should be parameterized in application code.
EXEC sp_set_session_context N'end-user-name', N'me';
INSERT INTO dbo.YourTable (SomeColumn) VALUES('example');
GO

Logon Trigger for auditing

Using SQL Server 2008R2
I am working on a LOGON trigger to capture users who have logged in. I have created a trigger, but it is capturing the SQL Agent account and is inserting it like mad. I would like to exclude it from the list, but I'm not sure how to modify the trigger to make it so. The different attempts locked me out and I had to use DAC to drop the trigger.
The original code:
USE B_DBA;
GO
CREATE TRIGGER LogonTrigger ON ALL SERVER FOR LOGON
AS
BEGIN
IF SUSER_SNAME() <> 'sa'
INSERT INTO B_DBA.dbo.LogonAudit (UserName, LogonDate, spid)
VALUES (SUSER_SNAME(), GETDATE(), ##SPID)
END;
GO
ENABLE TRIGGER LogonTrigger ON ALL SERVER;
What I'd like to add is basically a where clause, but I haven't been able to add it successfully and it would not work on the IF section.
Where not in (Select service_account from sys.dm_server_services)
Any help would be appreciated.
Untested, but it seems like you should be able to modify your IF statement to check sys.dm_server_services:
USE B_DBA;
GO
CREATE TRIGGER LogonTrigger ON ALL SERVER FOR LOGON
AS
BEGIN
IF SUSER_SNAME() <> 'sa'
AND NOT EXISTS (SELECT service_account FROM sys.dm_server_services WHERE service_account = SUSER_SNAME())
INSERT INTO B_DBA.dbo.LogonAudit (UserName, LogonDate, spid)
VALUES (SUSER_SNAME(), GETDATE(), ##SPID)
END;
GO
ENABLE TRIGGER LogonTrigger ON ALL SERVER;

Encrypted Password corruption - some saved as encrypted, some not encrypted, some not saved

I'm programming a legacy ColdFusion application that uses a 2008 SQL Server database. I know very little about how the SQL Server database was set up, but I'm hoping if I share some of the symptoms, someone might have some suggestions for what to check.
The database uses a Symmetric Key to secure users passwords. I have a Users table with username, password, etc. as fields. Password is encrypted.
Most legacy users in the database work correctly. Users can login using the website, change their passwords, etc. without issues. For records used for testing, I've changed the passwords with SQL in SQL Server, not through the website: "update users set password = "fluffy" where userID in (6543, 7654, 8765)" etc.
When I've done that, a few things happen:
I can never log into the website the first time using USERID 6543
and PASSWORD "fluffy" -- but it always works the second time.
When I run my Stored Procedure exec get_user_unencrypt_by_id 6543,
the results return "NULL" as the password.
When I run a query select * from Users I see the expected
symbols/gibberish in most of the password fields, but for the users
6543, 7654, and 8765, I see "fluffy".
When I run a query select * from users where password is null I get
no results.
What I've done to try to resolve the problem:
I ran the following SQL to open and reset the Master Key:
OPEN MASTER KEY DECRYPTION BY PASSWORD = ''
ALTER MASTER KEY ADD ENCRYPTION BY SERVICE MASTER KEY
Close Master Key
GO
This seemed to have no effect.
I tried updating the corrupted passwords using SQL
update users set password = EncryptByKey(Key_GUID('PASS_Key_01'), 'fluffy')
where userID in (6543, 7654, 8765)"
When I tried this, those users were locked out when using the password 'fluffy.'
I've tried resetting the passwords through the website. This appears to work correctly only for records where the passwords are not corrupted. If I do this with one of the corrupted passwords, it appears to work temporarily, but later (the next day), the password is corrupted again.
My SP named get_user_unencrypt_by_id is this:
OPEN SYMMETRIC KEY PASS_Key_01
DECRYPTION BY CERTIFICATE UserPasswords0324
SELECT userid, username, CONVERT (nvarchar,
DecryptByKey([password])) as 'password', [role], firstname, lastname,
Add1, Add2, City, [State], Zip, Phone, Fax,
FROM users
I'm not sure what else to try, so I'd be happy for any suggestions or ideas. Thanks.
Edited to add more detail. In continuing to investigate, I learned there is a trigger on the table. This is the trigger.
/****** Object: Trigger [dbo].[encrypt_password_on_update]
Script Date: 4/1/2015 8:55:44 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[encrypt_password_on_update]
ON [dbo].[USERS]
after update
AS
BEGIN
/***The purpose of this trigger is to encrypt a password that was
update by the user. When the update statement updates the password,
this encrypts it before storing it in the db***/
DECLARE #updatecount int
DECLARE #userid int
DECLARE #password nvarchar(50)
DECLARE #temp_encryt_password nvarchar(50)
select #updatecount = (select count(userid) from inserted)
if (#updatecount = '1')
BEGIN
SELECT #userid = (SELECT userid FROM Inserted)
OPEN SYMMETRIC KEY PASS_Key_01
DECRYPTION BY CERTIFICATE UserPasswords0324
if (#userid != '' and #userid is not null)
BEGIN
select #temp_encryt_password = (select
EncryptByKey(Key_GUID('PASS_Key_01'), [password]) from users where
userid = #userid)
/***If the password is already encrypted (if the update was for something else other than the password) we don't want to reencrypt***/
if ( CONVERT (nvarchar, DecryptByKey(#temp_encryt_password)) is not null)
BEGIN
update USERS
set [password] = EncryptByKey(Key_GUID('PASS_Key_01'), [password])
where userid = #userid
END
END
END
END
GO
I believe I've solved my own problem. The issue was occurring in passwords that I reset directly within the database using a query like
update users set password = "fluffy" where userID in (6543, 7654, 8765)
Then the trigger that is actually encrypting the passwords looks for a single record:
select #updatecount = (select count(userid) from inserted)
if (#updatecount = '1')
BEGIN
...
So the passwords were being stored in the database natively, and never encrypted because I was updating multiple records at the same time.
Then, when a user attempted to log in to the site, the authentication would fail -- the SP that returned the decrypted password would return NULL. That failure would trigger an update on the User's database to increment the number of failed login attempts. THAT query would trigger the password encryption, and the second time the user attempted to log in, the authentication would work. So, for testing purposes, the key to what I needed was to reset passwords in the database using a series of queries like this:
update users set password = "fluffy" where userID = 6543
GO
update users set password = "fluffy" where userID = 7654
GO
update users set password = "fluffy" where userID = 8765
GO

DDL Trigger For Logon Insert data to a table

I have a below DDL Trigger on my server
CREATE TRIGGER [DDLForLogin] ON ALL SERVER
FOR LOGON
AS BEGIN
DECLARE #data XML
SET #data = EVENTDATA()
IF EXISTS(SELECT * FROM sys.Databases WHERE NAME = 'DatabaseMaintenance') Begin
INSERT INTO DatabaseMaintenance.dbo.MyTable (UserName, HostName, ApplicationName, EventDataValue)
VALUES (CURRENT_USER, HOST_Name(), APP_NAME(),#Data)
END
END;
a login as Windows Authentication insert one row on MyTable but a login as SQL Server Authentication raised below error :
Logon failed for login 'xxx' due to trigger execution.
Changed database context to 'master'.
Changed language setting to us_english. (Microsofr SQL Server, Error: 17892)
EDIT
I GRANT INSERT on my table to PUBLIC.
but no change on raised error.
EDIT2
I change my trigger and add With Execute AS 'sa' on the trigger
but no change on raised error.
Try adding an appropriate EXECUTE AS clause to your trigger - the default is CALLER, so unless that user has permissions to insert into your audit table1, the trigger will fail.
Also, then use ORIGINAL_LOGIN() in the trigger to get the correct login information
1 Which you normally don't want - because otherwise every user can forge entries alleging that other users have logged in.
Must change trigger to below code:
CREATE TRIGGER [DDLForLogin] ON ALL SERVER
WITH EXECUTE AS 'sa'
FOR LOGON
AS BEGIN
DECLARE #data XML
SET #data = EVENTDATA()
DECLARE #IsPooled int
SET #IsPooled = #data.value('(/EVENT_INSTANCE/IsPooled)[1]', 'int')
IF EXISTS(SELECT * FROM sys.Databases WHERE NAME = 'DatabaseMaintenance')AND (#IsPooled=0) Begin
insert into DatabaseMaintenance.dbo.Login (UserName, HostName, ApplicationName, EventDataValue)
values (ORIGINAL_LOGIN(), HOST_Name(), APP_NAME(),#Data)
END
END;

Checking if a SQL Server login already exists

I need to check if a specific login already exists on the SQL Server, and if it doesn't, then I need to add it.
I have found the following code to actually add the login to the database, but I want to wrap this in an IF statement (somehow) to check if the login exists first.
CREATE LOGIN [myUsername] WITH PASSWORD=N'myPassword',
DEFAULT_LANGUAGE=[us_english],
CHECK_EXPIRATION=OFF,
CHECK_POLICY=OFF
GO
I understand that I need to interrogate a system database, but not sure where to start!
Here's a way to do this in SQL Server 2005 and later without using the deprecated syslogins view:
IF NOT EXISTS
(SELECT name
FROM master.sys.server_principals
WHERE name = 'LoginName')
BEGIN
CREATE LOGIN [LoginName] WITH PASSWORD = N'password'
END
The server_principals view is used instead of sql_logins because the latter doesn't list Windows logins.
If you need to check for the existence of a user in a particular database before creating them, then you can do this:
USE your_db_name
IF NOT EXISTS
(SELECT name
FROM sys.database_principals
WHERE name = 'Bob')
BEGIN
CREATE USER [Bob] FOR LOGIN [Bob]
END
From here
If not Exists (select loginname from master.dbo.syslogins
where name = #loginName and dbname = 'PUBS')
Begin
Select #SqlStatement = 'CREATE LOGIN ' + QUOTENAME(#loginName) + '
FROM WINDOWS WITH DEFAULT_DATABASE=[PUBS], DEFAULT_LANGUAGE=[us_english]')
EXEC sp_executesql #SqlStatement
End
As a minor addition to this thread, in general you want to avoid using the views that begin with sys.sys* as Microsoft is only including them for backwards compatibility. For your code, you should probably use sys.server_principals. This is assuming you are using SQL 2005 or greater.
You can use the built-in function:
SUSER_ID ( [ 'myUsername' ] )
via
IF [value] IS NULL [statement]
like:
IF SUSER_ID (N'myUsername') IS NULL
CREATE LOGIN [myUsername] WITH PASSWORD=N'myPassword',
DEFAULT_LANGUAGE=[us_english],
CHECK_EXPIRATION=OFF,
CHECK_POLICY=OFF
GO
https://technet.microsoft.com/en-us/library/ms176042(v=sql.110).aspx
In order to hande naming conflict between logins, roles, users etc. you should check the type column according to Microsoft sys.database_principals documentation
In order to handle special chacters in usernames etc, use N'<name>' and [<name>] accordingly.
Create login
USE MASTER
IF NOT EXISTS (SELECT 1 FROM master.sys.server_principals WHERE
[name] = N'<loginname>' and [type] IN ('C','E', 'G', 'K', 'S', 'U'))
CREATE LOGIN [<loginname>] <further parameters>
Create database user
USE [<databasename>]
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE
[name] = N'<username>' and [type] IN ('C','E', 'G', 'K', 'S', 'U'))
CREATE USER [<username>] FOR LOGIN [<loginname>]
Create database role
USE [<databasename>]
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE
[name] = N'<rolename>' and Type = 'R')
CREATE ROLE [<rolename>]
Add user to role
USE [<databasename>]
EXEC sp_addrolemember N'<rolename>', N'<username>'
Grant rights to role
USE [<databasename>]
GRANT SELECT ON [<tablename>] TO [<rolename>]
GRANT UPDATE ON [<tablename>] ([<columnname>]) TO [<rolename>]
GRANT EXECUTE ON [<procedurename>] TO [<rolename>]
The SQL is tested on SQL Server 2005, 2008, 2008 R2, 2014, 2016, 2017, 2019
Try this (replace 'user' with the actual login name):
IF NOT EXISTS(
SELECT name
FROM [master].[sys].[syslogins]
WHERE NAME = 'user')
BEGIN
--create login here
END
This is for Azure SQL:
IF (EXISTS(SELECT TOP 1 1 FROM sys.sql_logins WHERE [name] = '<login>'))
DROP LOGIN [<login>];
Source: How to check whether database user already exists in Azure SQL Database
This works on SQL Server 2000.
use master
select count(*) From sysxlogins WHERE NAME = 'myUsername'
on SQL 2005, change the 2nd line to
select count(*) From syslogins WHERE NAME = 'myUsername'
I'm not sure about SQL 2008, but I'm guessing that it will be the same as SQL 2005 and if not, this should give you an idea of where t start looking.
what are you exactly want check for login or user ?
a login is created on server level and a user is created at database level so a login is unique in server
also a user is created against a login, a user without login is an orphaned user and is not useful as u cant carry out sql server login without a login
maybe u need this
check for login
select 'X' from master.dbo.syslogins where loginname=<username>
the above query return 'X' if login exists else return null
then create a login
CREATE LOGIN <username> with PASSWORD=<password>
this creates a login in sql server .but it accepts only strong passwords
create a user in each database you want to for login as
CREATE USER <username> for login <username>
assign execute rights to user
GRANT EXECUTE TO <username>
YOU MUST HAVE SYSADMIN permissions or say 'sa' for short
you can write a sql procedure for that on a database
create proc createuser
(
#username varchar(50),
#password varchar(50)
)
as
begin
if not exists(select 'X' from master.dbo.syslogins where loginname=#username)
begin
if not exists(select 'X' from sysusers where name=#username)
begin
exec('CREATE LOGIN '+#username+' WITH PASSWORD='''+#password+'''')
exec('CREATE USER '+#username+' FOR LOGIN '+#username)
exec('GRANT EXECUTE TO '+#username)
end
end
end
Starting SQL 2016:
DROP USER IF EXISTS [userName]
CREATE USER [userName] FOR LOGIN [loginName]
First you have to check login existence using syslogins view:
IF NOT EXISTS
(SELECT name
FROM master.sys.server_principals
WHERE name = 'YourLoginName')
BEGIN
CREATE LOGIN [YourLoginName] WITH PASSWORD = N'password'
END
Then you have to check your database existence:
USE your_dbname
IF NOT EXISTS
(SELECT name
FROM sys.database_principals
WHERE name = 'your_dbname')
BEGIN
CREATE USER [your_dbname] FOR LOGIN [YourLoginName]
END

Resources