Active Directory query from SQL Server 2012 trigger failing - sql

I have an OpenQuery call to Active Directory that gets the email address for a given Windows Domain account name. It works fine until I put it in an Insert trigger. The offending piece of T-SQL is:
set #sql = 'select mail from openquery(ADSI, '''
+ 'SELECT mail FROM ''''LDAP://DC=COMPANY,DC=LOCAL''''
WHERE objectClass=''''user'''' AND objectClass<>''''computer''''
AND samAccountName=''''username'''''') '
exec sp_executesql #sql, N'#recipients varchar(500) output', #recipients output
ADSI is a linked server and I have the security context set to always use a specific user account. I get the following error returned in my web app when adding a new record.
"The member with identity 'mail' does not exist in the metadata collection.
Parameter name: identity"
I have tried connecting as the same user that my app uses for connecting to the database and running the query from SQL Management Studio and I cannot replicate the problem outside of the Trigger. Can someone point me in the right direction? I suspect it is a security issue but I am lost and running out of ideas.

Related

SQL Trigger to grant 'dbowner' permissions upon new DB creation

I put together the following SQL script to create a trigger when a new DB is created, to grant db_owner permissions to a specific account:
CREATE TRIGGER ddl_trig_database
ON ALL SERVER
FOR CREATE_DATABASE
AS
DECLARE #DatabaseName NVARCHAR(128), #SQL NVARCHAR(4000)
SELECT #DatabaseName = EVENTDATA().value('(/EVENT_INSTANCE/DatabaseName)[1]','NVARCHAR(128)');
SET #SQL = '
USE ' + #DatabaseName + ';
EXEC sp_addrolemember N''db_owner'', N''[accountname]'';'
EXEC(#SQL)
However, I get the following error when I try to create a new DB to test this Trigger:
Message: User or role '[accountname]' does not exist in this database.
Could not find database ID 45, name '45'. The database may be offline.
Wait a few minutes and try again.
I put this together using some examples found on the web. It appears that the Trigger is occurring right when DB is being created, instead of running after DB has been created. Is there a way to delay it?
Few things to be mentioned here:
The trigger is executed AFTER the database is created as this is the default behaviour
Not being able to find the database may point to lack of required permissions. Make sure you have enough permission by impersonating as a user that has been granted enough permissions i.e. some database owner. Use the WITH EXECUSE AS clause.
Make sure you have the "[accountname]" existing at the new DB or at the server level (depends what kind of account you are trying to add). You can add a database user, database role, Windows login, or Windows group.
References:
https://msdn.microsoft.com/en-us/library/ms189799.aspx
https://msdn.microsoft.com/en-us/library/ms187750.aspx
SQL Server 2008 - Does a trigger run with the same permissions as the login/user?

SQL Server Agent Emails: Error Formatting Query

I was running into an issue when creating and implementing a new SQL server agent job to run and email the results of a simple query:
EXEC ms.dbo.sp_send_dbmail
#profile_name = 'Main DB Mail Profile',
#recipients = 'test#myemail.com',
#subject = 'Basket Report',
#query = N'Select Store, Date, Sum(Amount) as DailyTotal, COUNT(CAST(Trans as varchar(30))+CAST(Register as Varchar(30))) as DistinctTransactions
From BasketAnalysis
Where Date = dateadd(day,datediff(day,1,GETDATE()),0)
GROUP BY Store, Date
ORDER BY Store ASC;',
#attach_query_result_as_file = 1,
#query_attachment_filename = 'BasketReport.txt'
I kept running into a mysterious error message in the history log for the task.
Executed as user: NT AUTHORITY\NETWORK SERVICE. Error formatting
query, probably invalid parameters [SQLSTATE 42000] (Error 22050). The
step failed.
Has anyone had any luck getting more information on these errors and how to resolve them?
Digging around, I found a large number of different potential solutions, that I thought I would try to compile some of them here.
User Permissions: The SQL Server Agent user needs to have sufficient privileges to be able to execute and email. The user needs to have the DatabaseMailUserRole (under msdb user mapping) server role. The SQL Configuration Manager gives you the ability to view and adjust the Server Agent user if necessary, experimenting with a Server Agent exclusive user can be helpful!
Run the query in a new query window: Dumb as this is, I totally neglected to run the query outside of the job itself at first. I realized this, and this gave me some more error information than what was being logged in the Server Agent history.
Check your email parameters: This is my problem, but I was following this tutorial but was getting this error because I neglected to include the '#execute_query_database' parameter in my query. Adding that, pointing to my relevant database, was the missing piece.

CREATE USER in Dynamic SQL on SQL Azure

I want to write a stored procedure that checks the environment a database resides in (Based on the name) and creates the appropriate user and role for our application.
This would allow us to automate setting up permissions if we move a database between environments (Currently due to the limitations of Windows Azure SQL Database we have to manually run a script which is not ideal and prone to human error).
So the syntax we are using is:
DECLARE #UserToAdd VARCHAR(50) = (
SELECT CASE
WHEN #Environment = 'Development' THEN 'DevelopmentApplicationUser'
WHEN #Environment = 'Test' THEN 'TestingApplicationUser'
ELSE ''
END
)
IF (#UserToAdd != '')
BEGIN
EXEC ('CREATE USER [' + #UserToAdd + '] FOR LOGIN [' + #UserToAdd + '];')
EXEC ('EXEC sp_addrolemember N''WebUser'', N''' + #UserToAdd + ''';')
END
This works correctly on our development server (SQL Server 2008 R2) but in Windows Azure SQL Database we get the below error:
The CREATE USER statement must be the only statement in the batch
Now the MSDN documentation does state:
If the CREATE USER statement is the only statement in a SQL batch, Windows Azure SQL Database supports the FOR | FROM LOGIN clause. If the CREATE USER statement is not the only statement in a SQL batch or is executed in dynamic SQL, the FOR | FROM LOGIN clause is not supported.
However this means that we cannot automate our permissions whatsoever.
Has anyone got around this issue and been able to produce dynamic sql that creates a user? Alternatively is there a way around this in a stored procedure?
I opened a support case with Microsoft to see if this is possible and the response was that if you want to check the existence of a login or a user and then create that login or user, you MUST use separate connections for the check and then the creation. It is NOT possible to check and create if not exists in the same transaction or batch. In Sql Azure.
Hth,
Oli
I speak under correction here.
you can create the user without FOR LOGIN
then use sp_change_users_login to map the user to a login after the fact

Failing to delete record from linked server

I've spent the past two days searching everywhere for a solution to my problem but without any luck.
I have this query that deletes record from a remote server:
delete from OPENROWSET('SQLNCLI', 'Server=AB01S\SQLEXPRESS;Database=ShopData;Trusted_Connection=yes', 'Select receipt_n,action_in, action_ty, action_field_name,action_field_type,action_field_data, terminal from tblData where receipt_n= 1 and terminal = 1');
and I am getting this error:
OLE DB provider "SQLNCLI" for linked server "(null)" returned message "Multiple-step OLE DB operation generated errors. Check each OLE DB status value, if available. No work was done.".
Msg 7202, Level 11, State 1
It also suggests to use sp_addlinkedserver to add a linked server.
Some notes:
This happens only on this specific computer + remote server. On 2 different stations (Computer+Server) it worked just fine.
Insert to OpenRowSet is working OK.
Select * from OpenRowSet is working OK.
=> Changing the delete to select * works OK.
Ad hok is enabled on the computer that queries
Remote connection is enabled on the computer being queried via openrowset
I can ping the server (AB01S)
So far I have tried adding linked setup via GUI and sp_addlinkserver.
I found the problem!!
The server name (for some reason) was incorrect.
##servername returned AB01S_88
The solution was:
running the following code and then restarting the service:
exec sp_dropserver ##servername
exec sp_addserver 'AB01S', local
exec sp_serveroption 'AB01S', 'data access' , 'true'

The server principal "XYuser" is not able to access the database "Ydb" under the current security context

System Specifications
Microsoft SQL Server Management Studio 9.00.4035.00
Microsoft Analysis Services Client Tools 2005.090.4035.00
Microsoft Data Access Components (MDAC) 2000.085.1132.00
(xpsp.080413-0852)
Microsoft MSXML 2.6 3.0 4.0 5.0 6.0
Microsoft Internet Explorer 7.0.5730.13
Microsoft .NET Framework 2.0.50727.1433
Operating System 5.1.2600
On an SQL Server 2005 called BHAVMSQL02, I have two databases Mattercentre_dev and CMSNET_DEV. The Mattercentre_dev has a stored procedure that builds a list from a table in CMSNET_DEV. The stored procedure looks
like this...
USE [Mattercentre_dev]
GO
/****** Object: StoredProcedure [dbo].[UDSPRBHPRIMBUSTYPE]
Script Date:02/12/2009 10:18:10 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROCEDURE [dbo].[UDSPRBHPRIMBUSTYPE] WITH EXECUTE AS 'Readuser' AS
DECLARE #SERVERNAME nvarchar(30)
DECLARE #DBASE nvarchar(30)
DECLARE #SQL nvarchar(2000)
SET #SERVERNAME = Convert(nvarchar,
(SELECT spData FROM dbSpecificData WHERE spLookup = 'CMSSERVER'))
SET #DBASE = Convert(nvarchar,
(SELECT spData FROM dbSpecificData WHERE spLookup = 'CMSDBNAME'))
SET #SQL =
'SELECT
null as Code
, ''(not specified)'' as Description
UNION SELECT
clnt_cat_code as Code
, clnt_cat_desc as Description
FROM '
+ #SERVERNAME + '.' + #DBASE + '.dbo.hbl_clnt_cat
WHERE
inactive = ''N''
ORDER BY Description'
PRINT #SQL
EXECUTE sp_executeSQL #SQL
#SERVERNAME == 'BHAVMSQL02'
#DBASE == 'CMSNET_DEV'
When the stored procedure was executed the following error message appeared...
The server principal "ReadUser" is not able to access the database "CMSNET_DEV" under the current security context.
After googling the error message, I carried out the following fix...
Deleted the user ReadUser from
BHAVMSQL02 -> Databases ->
Mattercentre_dev -> Security -> Users
Set Up ReadUser from BHAVMSQL02 ->
Security -> Logins with the following
settings...
General
Login Name - readUser
Password - xxxxxxxxxxxx
Confirm - xxxxxxxxxxxx
Default db - master
default lg - British English
Everything Else - Unset
Server Roles
Only Public Set
User Mappings
CMSNET_DEV - ReadUser - dbo
Database Role Membership - db_owner, public
Mattercentre_dev - ReadUser - dbo
Database Role Membership - db_owner, public
I then ran the following script...
ALTER DATABASE CMSNET_DEV SET TRUSTWORTHY ON
GO
ALTER DATABASE mattercentre_dev SET TRUSTWORTHY ON
GO
I re-ran the stored procedure and executed it again and I still have the same
error message.
I have looked this question up in Stack Overflow and the suggested solutions
are similar to my own.
You cannot use ownership chaining when your stored procedure contains dynamic SQL, i.e doing so breaks the ownership chain.
In order for this to work you will need to use a certificate to sign your stored procedures.
Below is a brilliant article that contains instructions for signing stored procedures.
http://www.sommarskog.se/grantperm.html
Looking at this in further detail, the fact that you are using the “execute as clause” should negate the fact that the ownership chain is broken as a result of incorporating dynamic SQL.
With this in mind, the likely hood is that for some reason, the login “ReadUser” does not have appropriate read access to the databases in question, this should not be the case however, given that the login is a member of the db_owner role in both databases. That said, if the database roles have been altered from their original state then this may not hold true.
To test that the issue is not isolated to the “ReadUser” login I would suggest creating a new SQL Server Login, and mapping the login to both databases (there by creating database logins of the same name) with appropriate read access. Then modify the stored procedure to execute as the new login.