I have inherited a database that contains a lot of stored procedures that create a local temporary table, calls a procedure that uses the temp table, and then deletes the temp table. Like this:
CREATE PROCEDURE procSelectFromTable
AS
BEGIN
SELECT *
FROM #myTable
END;
GO
CREATE PROCEDURE procMakeTable
AS
BEGIN
SELECT 1 AS [ID]
,'NestedProcedure' AS [Message]
INTO #myTable;
EXEC procSelectFromTable
DROP TABLE #myTable;
END;
GO
EXEC procMakeTable;
GO
--Clean up
IF OBJECT_ID('tempdb..#myTable') IS NOT NULL
DROP TABLE #myTable;
DROP PROCEDURE procMakeTable;
DROP PROCEDURE procSelectFromTable
I have not seen procedures written in this manner before. Is it safe for me to assume this is ok because the nested procedure will always be called in the same spid and will always be able to access the temp table?
Yes, the nested procedure will have access to the local temp table. This was common use before the introduction of table-valued parameters or when the procedures exist in different databases in the same instance. The procedures are tightly coupled and might be a problem to test the 'child' procedure, but it has the advantage that it can be called from multiple procedures.
I am trying to insert the data of a stored procedure into a temp table like below
CREATE TABLE #CustomTable3HTML
(
ItemId varchar(30),
ItemId1 varchar(30)
)
INSERT INTO #CustomTable3HTML
EXEC SalesDeals.dbo.prGetDealProposalDetail 17100102, 1
but I am getting this error
Msg 8164, Level 16, State 1, Procedure prGetDealProposalDetail, Line 138 [Batch Start Line 1]
An INSERT EXEC statement cannot be nested.
I figured this is because the stored procedure already has an insert into clause defined and I found out that it can be used only once in the calling chain.
So I started looking for other options and found out about OpenRowSet which I am using as below
SELECT *
INTO #CustomTable3HTML
FROM OPENROWSET('SQLOLEDB','Server=Demo\Demo;Trusted_Connection=Yes;Database=SalesDeals',
'SET NOCOUNT ON;SET FMTONLY OFF;EXEC SalesDeals.dbo.prGetDealProposalDetail 17100102,1')
I am getting an error when I run this SQL command
Access to the remote server is denied because no login-mapping exists.
It works fine when I use a higher level account like sysadmin but fails with the other account which is a normal db owner on the database where I am running this SQL.
There is work around of this. It's not beautiful, but it will work.
In our outer query define a table:
CREATE TABLE #CustomTable3HTML
(
ItemId varchar(30),
ItemId1 varchar(30)
)
Change the procedure adding the following code at the end:
IF OBJECT_ID('tempdb..#CustomTable3HTML')
BEGIN
INSERT INTO #CustomTable3HTML
SELECT ....
END
ELSE
BEGIN
SELECT ....
END
After executing the stored procedure you will have the data in table.
I'm accustomed to doing this:
use MyDb
go
create type table_list as table (name sysname)
go
create proc rebuild_indexes
#ls table_list readonly
as
[...]
but now I want to create the proc in master and use it in other databases:
use master
go
create type table_list as table (name sysname)
go
create proc sp_rebuild_indexes
#ls table_list readonly
as
[...]
...it builds fine but I can't use it!
use MyDb
go
declare #ls table_list
yields:
Msg 2715, Level 16, State 3, Line 1 Column, parameter, or variable #1:
Cannot find data type table_list. Parameter or variable '#ls' has an
invalid data type.
so I tried creating a synonym, which doesn't complain during creation but doesn't help:
create synonym table_list for master.dbo.table_list
thoughts anyone?
Table types declared in different databases, even if they share the same name and structure, are not treated as being the same by SQL Server:
create database DB1
go
use DB1
go
create type TT1 as table (ID int not null)
go
create procedure Echo
#T TT1 readonly
as
select * from #T
go
create database DB2
go
use DB2
go
create type TT1 as table (ID int not null)
go
declare #T TT1
insert into #T(ID) values (1)
exec DB1..Echo #T
Result:
(1 row(s) affected)
Msg 206, Level 16, State 2, Procedure Echo, Line 0
Operand type clash: TT1 is incompatible with TT1
So far as I'm aware, there is no way to declare a variable in a database using a table type definition from another database. (e.g. anywhere where you see a user defined table type can be used, it can only be named as <table type> or <schema>.<table type>. 3 or 4 part names are not allowed)
(The above is true for 2008 and 2012; obviously, future versions may do something to address this)
As a work-around, you can do it the "poor man's" way - have your master defined stored procedure work against a temp table, rather than a user defined table type:
use Master
go
create procedure sp_Echo
as
select DB_NAME(),* from #t
go
create database DB1
go
use DB1
go
create table #t (ID int not null)
insert into #t (ID) values (1),(2)
exec sp_Echo
Results:
------------- -----------
DB1 1
DB1 2
AFAIK, user defined types can't be used outside the context of the database they were created in.
You may have to create the UDT in each database, but leave the sProc in master.
Is there a specific reason you're using the sp_ naming convention?
Peet
I have two tables CrossDBTrigTest_1 and CrossDBTrigTest_2 on same SQL Server instance.
The databases both have a table called Employee.
I wrote the following trigger on the Employee table CrossDBTrigTest_1 db:
Create Trigger [dbo].[CrossDBInsert] on [dbo].[employee] after insert
AS
Begin
Set nocount on
Insert into CrossDBTrigTest_2.employee(FirstName, LastName, Date)
SELECT inserted.FirstName, inserted.LastName, getdate()
FROM inserted
End
but the Insert statement fails with message:
Msg 208, Level 16, State 1, Procedure CrossDBInsert, Line 5
Invalid object name 'CrossDBTrigTest_2.employee'.
How do I enable cross database triggers in situations like this??
Shouldn't
CrossDBTrigTest_2.employee(FirstName,LastName,Date)
be
CrossDBTrigTest_2.dbo.employee(FirstName,LastName,Date)
???
Use
CrossDBTrigTest_2..employee
as table name. Note two dots instead of one.
If I have a user that only has limited permissions - just db_datareader and db_datawriter, which should only permit the user to query data and insert/edit/delete data, without allowing the user to add/modify/delete tables in the database.
There may be a need for the user to be able to execute stored procedures. If the user is given execute permissions (via the following sql: "GRANT EXECUTE TO UserName"), will the previous limitations (datareader and datawriter) still be enforced on what the user tries to execute through stored procedures? Or do Execute privileges really open up a pandora's box of other security holes (and if so, what)?
If the owner of the stored procedure has the rights to select, insert, update or delete against a table then select, insert, update and delete statements inside the stored procedure will execute as long as the caller has execute rights on the stored procedure, even if the caller does not have rights to directly perform select, insert, update or delete against the table.
However a stored procedure can not perform DDL unless the caller has rights to perform DDL even if the owner of the stored procedure has DDL rights. Note this also applies to truncate table.
Answer: In your case granting db_datareader and db_datawriter to a user already gives the user full DML on all tables. Granting execute on any stored procedure will not give any additional rights.
Stored procedures can be used to increase data integrity by providing a gate through which all external programs must go. Do not grant insert, delete or update, but create SPs that do the work and enforce the appropriate rules about the data. (Above and beyond what can be done with constraints.) And as Joe Kuemerle points out, stored procedures can be used to increase security.
I have observed this behavior while developing an application on SQL Server 2000 and this even re-tested on SQL Server 2008 and found the same behavior. I have not been able to find documentation on this behavior.
Logged in as DBO and SA create a table:
create table dbo.SO (PK int identity constraint SO_PK primary key
, SomeData varchar(1000)
)
Then create some stored procedures for basic DML:
create procedure dbo.InsertSO (#SomeData varchar(1000)) as
begin
insert into dbo.SO (SomeData) values (#SomeData)
return SCOPE_IDENTITY()
end
go
create procedure dbo.SelectSO (#PK int=null) as
begin
if #PK is not null
select PK, SomeData from dbo.SO where PK = #PK
else
select PK, SomeData from dbo.SO
end
go
create procedure dbo.CountSO as
begin
select COUNT(*) as CountSO from SO
end
go
create procedure dbo.DeleteSO (#PK int=null ) as
begin
if #PK is not null
delete dbo.SO where PK = #PK
else
delete dbo.SO
end
go
create procedure dbo.UpdateSO (#PK int, #NewSomeData varchar(1000)) as
begin`
update dbo.SO
set SomeData = #NewSomeData
where PK = #PK
end
go
create procedure dbo.TruncateSO as
begin
truncate table dbo.SO
end
go
As dbo, we can run the following SQL statements:
declare #PK_to_update int
insert into dbo.SO (SomeData) values ('Hello world!')
set #PK_to_update = SCOPE_IDENTITY()
declare #PK_to_delete int
insert into dbo.SO (SomeData) values ('Goodbye cruel world!')
set #PK_to_delete = SCOPE_IDENTITY()
insert into dbo.SO (SomeData) values ('Four score and seven years ago...')
select PK, SomeData
from dbo.SO
delete dbo.so
where PK = #PK_to_delete
update dbo.SO
set SomeData = 'Hello Milky Way!'
where PK = #PK_to_update
select PK, SomeData
from dbo.SO
truncate table dbo.SO
select COUNT(*) as CountSO from dbo.SO
Or do the equivalent via the stored procedures
go
declare #PK_to_update int
exec #PK_to_update = dbo.InsertSO 'Hello world!'
declare #PK_to_delete int
exec #PK_to_delete = dbo.InsertSO 'Goodbye cruel world!'
exec dbo.InsertSO 'Four score and seven years ago...'
exec dbo.SelectSO
exec dbo.DeleteSO #PK_to_delete
exec dbo.UpdateSO #PK_to_update, 'Hello Milky Way!'
exec dbo.SelectSO
exec dbo.TruncateSO
exec dbo.CountSO
Now, create a DDL stored procedure and test:
create procedure dbo.DropSO as
begin
drop table dbo.SO
end
go
begin transaction
select TABLE_NAME from INFORMATION_SCHEMA.TABLES
where TABLE_NAME = 'SO'
exec dbo.DropSO
select TABLE_NAME from INFORMATION_SCHEMA.TABLES
where TABLE_NAME = 'SO'
rollback transaction
And now create another user and grant execute rights to all the stored procedure. Do not grant any other rights. (Assumes public does not have extra rights and mixed mode authentication. Mixed mode authentication is not recommended, but makes testing how rights are handled easier.)
exec sp_addlogin #loginame = 'SoLogin' , #passwd = 'notsecure', #defdb = 'Scratch'
exec sp_adduser #loginame = 'SoLogin', #name_in_db = 'SoUser'
go
grant execute on dbo.InsertSo to SoUser
grant execute on dbo.InsertSO to SoUser
grant execute on dbo.SelectSO to SoUser
grant execute on dbo.CountSO to SoUser
grant execute on dbo.DeleteSO to SoUser
grant execute on dbo.UpdateSO to SoUser
grant execute on dbo.TruncateSO to SoUser
grant execute on dbo.DropSO to SoUser
Login in as SoLogin. Try the DML:
declare #PK_to_update int
insert into dbo.SO (SomeData) values ('Hello world!')
set #PK_to_update = SCOPE_IDENTITY()
declare #PK_to_delete int
insert into dbo.SO (SomeData) values ('Goodbye cruel world!')
set #PK_to_delete = SCOPE_IDENTITY()
insert into dbo.SO (SomeData) values ('Four score and seven years ago...')
select PK, SomeData
from dbo.SO
delete dbo.so
where PK = #PK_to_delete
update dbo.SO
set SomeData = 'Hello Milky Way!'
where PK = #PK_to_update
select PK, SomeData
from dbo.SO
truncate table dbo.SO
go
select COUNT(*) as CountSO from dbo.SO
go
drop table dbo.so
Nothing but errors:
Msg 229, Level 14, State 5, Line 2
The INSERT permission was denied on the object 'SO', database 'Scratch', schema 'dbo'.
Msg 229, Level 14, State 5, Line 6
The INSERT permission was denied on the object 'SO', database 'Scratch', schema 'dbo'.
Msg 229, Level 14, State 5, Line 9
The INSERT permission was denied on the object 'SO', database 'Scratch', schema 'dbo'.
Msg 229, Level 14, State 5, Line 11
The SELECT permission was denied on the object 'SO', database 'Scratch', schema 'dbo'.
Msg 229, Level 14, State 5, Line 14
The SELECT permission was denied on the object 'SO', database 'Scratch', schema 'dbo'.
Msg 229, Level 14, State 5, Line 14
The DELETE permission was denied on the object 'SO', database 'Scratch', schema 'dbo'.
Msg 229, Level 14, State 5, Line 17
The SELECT permission was denied on the object 'SO', database 'Scratch', schema 'dbo'.
Msg 229, Level 14, State 5, Line 17
The UPDATE permission was denied on the object 'SO', database 'Scratch', schema 'dbo'.
Msg 229, Level 14, State 5, Line 21
The SELECT permission was denied on the object 'SO', database 'Scratch', schema 'dbo'.
Msg 1088, Level 16, State 7, Line 24
Cannot find the object "SO" because it does not exist or you do not have permissions.
Msg 229, Level 14, State 5, Line 1
The SELECT permission was denied on the object 'SO', database 'Scratch', schema 'dbo'.
Msg 3701, Level 14, State 20, Line 2
Cannot drop the table 'SO', because it does not exist or you do not have permission.
Try the basic DML stored procedures:
declare #PK_to_update int
exec #PK_to_update = dbo.InsertSO 'Hello world!'
declare #PK_to_delete int
exec #PK_to_delete = dbo.InsertSO 'Goodbye cruel world!'
exec dbo.InsertSO 'Four score and seven years ago...'
exec dbo.SelectSO
exec dbo.DeleteSO #PK_to_delete
exec dbo.UpdateSO #PK_to_update, 'Hello Milky Way!'
exec dbo.SelectSO
They work, because the owner of the SPs have the right rights, even though SoUser does not.
Try the truncate or drop stored procedure:
exec dbo.TruncateSO
go
exec dbo.DropSO
Errors again:
Msg 1088, Level 16, State 7, Procedure TruncateSO, Line 4
Cannot find the object "SO" because it does not exist or you do not have permissions.
Msg 3701, Level 14, State 20, Procedure DropSO, Line 4
Cannot drop the table 'SO', because it does not exist or you do not have permission.
Execute permissions do not open up any extra security holes. In my opinion a larger hole is the fact that users have direct read/write access to the tables.
Since SQL Server implements ownership chaining you can provide controllable, auditable access to data by revoking datareader/datawriter permissions and providing all data access through stored procedures where users only have execute permissions. This will ensure that someone cannot arbitrarily insert/update/delete from tables. It will also provide another layer in a defense in depth strategy as in the event that an application that uses the database is vulnerable to a SQL Injection attack the attacker cannot read from/write to any table they want to.
The only caveat with doing this is if you are using an ORM it may take some additional development effort to use sprocs rather than letting the ORM dynamically generate the SQL.
The concept you want is "ownership chaining"
Basically, permissions are not checked on objects in the same schema (say dbo) used by stored procedures. Except: deny is always checked.
So if stored proc dbo.uspDoStuff uses table dbo.Parent and dbo.Child, no permissions are needed on the tables and it just works. Unless you have run "DENY SELECT ON dbo.Parent to MyUser".
Note: You'd normally do "CREATE ROLE MyRole", add the user to the role, and grant permissions on the role. db_datareader is just a special, reserved role for example.
Granting execute permissions will allow that person to do anything which that stored procedure does in the context of that stored procedure (so if the sproc drops a table, the user will be able to execute the sproc to drop the table).
Edit, I just checked and I was wrong. Deny access does not revoke the ability to execute an action in a stored procedure.
Here is the article on MSDN which specifies that denying access does not affect a stored procedure.
http://msdn.microsoft.com/en-us/library/bb669058.aspx
UPDATE:
What you might be able to do is execute a drop table command through sp_executeSQL in the stored procedure and deny the user the ability drop tables. That should prevent the stored procedure from being able to successfully execute the command (unless the user has the permissions to do so), since to use sp_executesql the user needs the permissions to perform the sql action and not just access to the stored procedure.