SQL Server User Permissions on Stored Procedure and Underlying Tables - sql

What would be the correct answer to the below? I would have thought EXECUTE permission is enough??
Thanks!
The database has a table named Customers owned by UserA and another table named Orders owned by UserB. You also have a stored procedure GetCustomerOrderInfo owned by UserB. GetCustomerOrderInfo selects data from both tables. You create a new user UserC. You need to ensure that UserC can call the GetCustomerOrderInfo stored procedure. You also need to assign only the minimum required permissions to UserC

Permissions on tables are not checked if the tables and the procedure have the the same owner. This is called ownership chaining.
Note that "ownership" in this context means "schema owner". For example, the table TestDB.Schema1.Table1 is owned by the user that owns of Schema1.
Because Orders has the same owner as GetCustomerOrderInfo, the stored procedure has implicit rights to read from Orders.
But Customers has a different owner, so you have to grant permission on that explicitly.
Here is a test script to demonstrate the issue:
use Test
go
if exists (select * from sys.syslogins where name = 'UserA')
drop login UserA
create login UserA with password = 'Welcome'
if exists (select * from sys.syslogins where name = 'UserB')
drop login UserB
create login UserB with password = 'Welcome'
if exists (select * from sys.syslogins where name = 'UserC')
drop login UserC
create login UserC with password = 'Welcome'
if exists (select * from sys.tables where name = 'Customers' and schema_name(schema_id) = 'SchemaA')
drop table SchemaA.Customers
if exists (select * from sys.schemas where name = 'SchemaA')
drop schema SchemaA
if exists (select * from sys.sysusers where name = 'UserA')
drop user UserA
if exists (select * from sys.tables where name = 'Orders' and schema_name(schema_id) = 'SchemaB')
drop table SchemaB.Orders
if exists (select * from sys.procedures where name = 'GetCustomerOrderInfo' and schema_name(schema_id) = 'SchemaB')
drop procedure SchemaB.GetCustomerOrderInfo
if exists (select * from sys.schemas where name = 'SchemaB')
drop schema SchemaB
if exists (select * from sys.sysusers where name = 'UserB')
drop user UserB
if exists (select * from sys.sysusers where name = 'UserC')
drop user UserC
create user UserA for login UserA
alter role db_owner add member UserA
go
create schema SchemaA authorization UserA
go
create user UserB for login UserB
alter role db_owner add member UserB
go
create schema SchemaB authorization UserB
go
create user UserC for login UserC
create table SchemaA.Customers (id int identity)
create table SchemaB.Orders (id int identity, CustomerId int)
go
create procedure SchemaB.GetCustomerOrderInfo
as
select *
from SchemaB.Orders o
join SchemaA.Customers c
on c.id = o.CustomerId
go
When we're all set up, we can test the procedure with different permissions. First we'll need execute permission on the stored procedure, then read permission on Customers. After that, the stored procedure works, even though we did not grant read access on Orders.
execute as login = 'UserC' -- Login as UserC
exec SchemaB.GetCustomerOrderInfo
-- The EXECUTE permission was denied on the object 'GetCustomerOrderInfo', database 'Test', schema 'SchemaB'
revert -- Revert back to our original login
grant execute on SchemaB.GetCustomerOrderInfo to UserC
execute as login = 'UserC'
exec SchemaB.GetCustomerOrderInfo
-- The SELECT permission was denied on the object 'Customers', database 'Test', schema 'SchemaA'.
revert
grant select on SchemaA.Customers to UserC
execute as login = 'UserC'
exec SchemaB.GetCustomerOrderInfo
-- (0 row(s) affected)
revert

As explained above the schema owner of SP and underlying objects should be the same.
Check schema owners by:
select name, USER_NAME(s.principal_id) AS Schema_Owner from sys.schemas s
To change the owner of an schema you can:
ALTER AUTHORIZATION ON SCHEMA::YOUR_SCHEMA TO YOUR_USER;
Examples:
ALTER AUTHORIZATION ON SCHEMA::Claim TO dbo
ALTER AUTHORIZATION ON SCHEMA::datix TO user1;
Finally if within your SP you are truncating a table or changing structure you may want to add WITH EXECUTE AS OWNER in your SP:
ALTER procedure [myProcedure]
WITH EXECUTE AS OWNER
as
truncate table etl.temp

Related

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!

How to allow a view to read from multiple schemas

I have two tables in different schemas, one owned by dbo, the other not. I have a third schema where I'm trying to create a view that reads from those two tables. My understanding is that if dbo is the owner of the view, because it has grant options to all objects in the database, users with only access to the view should be able to query it. But that isn't working, even when I explicitly grant dbo SELECT WITH GRANT to the underlying table in the schema it doesn't own.
I've read a few other posts, but don't see an answer that works: https://stackoverflow.com/a/4134892/1499015, Ownership Chaining and Tutorial: Ownership Chains and Context Switching
I assume I'm doing something wrong with permission chaining, but I can't figure out what the problem is.
--use master
--GO
--create login testuser with password='******************'
-- principal3 will own schema3
--create login principal3 with password='*****************'
--use testingDB
--GO
create user testuser for login testuser
create user principal3 for login principal3
GO
-- testrole that will only have select permissions on schema2
CREATE ROLE testrole
exec sp_addrolemember 'testrole', 'testuser'
GO
-- the first schema is owned by dbo
CREATE SCHEMA schema1 authorization dbo
GO
-- the schema where the view will live, also owned by dbo
create schema schema2 authorization dbo
GO
-- schema3 owned by principal3
create schema schema3 authorization principal3
go
-- tables in schema1 and schema3
CREATE TABLE schema1.testtable1 (
id int
, testvalue sysname
)
GO
CREATE TABLE schema3.testtable3 (
id int
, testvalue sysname
)
GO
-- some base data
INSERT schema1.testtable1
SELECT 1, 'a'
UNION SELECT 2, 'b'
UNION SELECT 3, 'c'
INSERT schema3.testtable3
SELECT 1, 'hot dogs rule'
UNION SELECT 2, 'pizza sucks'
UNION SELECT 3, 'haw just kidding that''s obviously backwards'
GO
-- view in schema2 that queries tables in schema1 and schema3, owned by dbo
CREATE VIEW schema2.testview
AS
SELECT t1.testvalue as v1
, t3.testvalue as v3
FROM schema1.testtable1 t1
INNER JOIN schema3.testtable3 t3 ON t1.id = t3.id
GO
GRANT SELECT ON schema::schema1 TO dbo WITH GRANT OPTION
GRANT SELECT ON schema::schema3 TO dbo WITH GRANT OPTION
ALTER AUTHORIZATION ON schema2.testview TO dbo
-- give testrole permission to query the view
GRANT SELECT ON schema2.testview to testrole
GO
EXECUTE AS USER = 'testuser';
SELECT USER_NAME();
BEGIN TRY
select top 100 * from schema2.testview
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH
REVERT;
SELECT USER_NAME();
When I run this code, I get
The SELECT permission was denied on the object 'testtable3', database 'testingDb', schema 'schema3'.
I've also tried to create a schema owner user for the schema that the view lives in, and granting that user select with grant to the underlying tables, but it's still not working.
Any ideas what I'm doing wrong?
Thanks!
The documentation says:
When an object is accessed through a chain, SQL Server first compares
the object's owner to the owner of the calling object (the previous
link in the chain). If both objects have the same owner, permissions
on the referenced object are not checked. Whenever an object accesses
another object that has a different owner, the ownership chain is
broken and SQL Server must check the caller's security context.
In this case, when the object testtable3 is accessed, SQL Server first compares testtable3 owner to the owner of the testview. If both objects have the same owner, permissions on the referenced object are not checked. It does not matter if you GRANT dbo rights on testtable3, even WITH GRANT (he this right anyway). It matters that the owner of testtable3 is the same as the owner of the testview.
For example, try:
ALTER AUTHORIZATION ON SCHEMA::schema3 TO dbo
And it will work.
If the owners are different, the ownership chain is broken and SQL Server must check the caller's security context. In this case: does testuser have rights to select from testtable3?.
Therefore, if the owners are different you need to do this to make it work:
GRANT SELECT ON schema3.testtable3 TO testuser

Identify Schema for table depending on User Access

I have a scenario where there are two database
schemas: Schema1 and Schema2 and a table: Table1.
Same Table1 exisits in both the schemas like Schema1.Table1 and Schema2.Table1.
Now we have some stored procedures which will be in another Schema say Schema3.
CREATE PROCEDURE SCHEMA3.GETDETAILS (
#AS_CODE_TYPE VARCHAR(1) ,
#AS_OUT_FIELD1 VARCHAR(50) OUT ,
#AS_RETURN_VAL INTEGER OUT
)
AS
BEGIN
SELECT #AS_OUT_FIELD1 = [EXTERNALREFKEY] FROM TABLE1 WHERE CODE_TYPE = #AS_CODE_TYPE
IF #AS_OUT_FIELD1 <> ' '
BEGIN
SET #AS_RETURN_VAL = 1 ;
END
ELSE
BEGIN
SET #AS_RETURN_VAL = - 1 ;
END
END
Now My question:
How do i get the schema details for a given user .
Do i need to modify the SP to dynamically append the schema to table depending on the user access to a specific schema.
Please help
When creating a user, if you don't specify it's schema, it will has the default schema 'dbo'.
You can get the user schema by running the bellow query in master DB:
USE master;
SELECT s.name user, s.default_schema_name user_schema FROM sys.database_principals s
WHERE s.name='user'
GO
For example:
You also can alter the user schema if you have the permission.
For more details, Changing the default schema of a user:
ALTER USER Mary51 WITH DEFAULT_SCHEMA = Purchasing;
GO
Hope this helps

GRANT Permissions seem not to work on a VIEW

I have the following view (called view3)
I created a role and granted it SELECT and UPDATE rights just on two columns.
CREATE ROLE Testrole
GRANT SELECT (Doc_ID, [Total Attentions]) ON view3 TO Testrole
Then I assigned the role to a user (test) already created
ALTER ROLE Testrole ADD MEMBER test
But when the transaction checking if all is ok is executed, all the columns are shown instead of the two required (the same image above).
This is the script
CREATE LOGIN logtest
WITH PASSWORD = 'logtest'
CREATE USER test
FOR LOGIN logtest
CREATE ROLE Testrole
GRANT SELECT (Doc_ID, [Total Attentions]) ON view3 TO Testrole
ALTER ROLE Testrole ADD MEMBER test
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
BEGIN TRANSACTION
EXECUTE AS USER = 'test'
SELECT * /*This should give an error*/
FROM view3
SELECT Doc_ID, [Total Attentions] /*This should work just fine*/
FROM view3
REVERT
ROLLBACK
GRANT permissions are cumulative. These symptoms suggest that view-level (all columns) permissions exist, inherited from this or other roles. Run the query below to see if this is the case.
SELECT
permission_name
, OBJECT_NAME(major_id) AS ObjectName
, CASE WHEN c.name IS NULL THEN 'All Columns' ELSE c.name END AS ColumnName
, USER_NAME(grantee_principal_id) AS Gratee
FROM sys.database_permissions AS p
LEFT JOIN sys.columns AS c ON
c.object_id = p.major_id
AND c.column_id = p.minor_id
WHERE
major_id = OBJECT_ID(N'view3');

How do I determine if a database role exists in SQL Server?

I'm trying to figure out how I can check if a database role exists in SQL Server. I want to do something like this:
if not exists (select 1 from sometable where rolename='role')
begin
CREATE ROLE role
AUTHORIZATION MyUser;
end
What table/proc should I use here?
SELECT DATABASE_PRINCIPAL_ID('role')
--or
IF DATABASE_PRINCIPAL_ID('role') IS NULL
USER_ID is deprecated and could break. CREATE ROLE indicates SQL 2005+ so it's OK
if not exists (select 1 from sys.database_principals where name='role' and Type = 'R')
begin
CREATE ROLE role
AUTHORIZATION MyUser;
end