SQL Разрешение на вызов функции между базами данных Server 2019 - PullRequest
2 голосов
/ 13 июля 2020

Я столкнулся с интересным поведением в SQL Server 2019 - похоже, этого не было в более ранних версиях.

Если в базе данных 1 я вызываю функцию в той же базе данных, которая вызывает функция в базе данных2, которая ВЫБИРАЕТ таблицу в базе данных2, я получаю: «В разрешении SELECT было отказано для объекта '{TableName}', базы данных '{DbName}', схемы 'dbo'.»

Если вместо этого , Я вызываю функцию в базе данных2 напрямую (без использования функции в базе данных1), запрос выполняется успешно.

Схема архитектуры

Мой вопрос: что за лог c стоит за этим? Я не понимаю, почему мне разрешено читать таблицу в другой базе данных без разрешения SELECT через функцию, но не , когда я вызываю эту функцию с помощью функции в моем текущая база данных! Это связано с функцией, предотвращающей передачу разрешений? В настоящий момент я предполагаю, что это запланированное изменение, но я не понимаю логики c, стоящей за ним.

Ниже приведен код, демонстрирующий поведение простым способом.

/*******************************************
SET UP
*******************************************/
CREATE DATABASE TestDb1
GO
CREATE DATABASE TestDb2
GO

CREATE LOGIN [TestLogin] WITH PASSWORD = '123456a.'
GO

--Create users in each database and add to roles.
USE TestDb1
CREATE USER [TestUser] FOR LOGIN [TestLogin]
CREATE ROLE Db1Role
ALTER ROLE Db1Role ADD MEMBER [TestUser]

USE TestDb2
CREATE USER [TestUser] FOR LOGIN [TestLogin]
CREATE ROLE Db2Role
ALTER ROLE Db2Role ADD MEMBER [TestUser]

--Create table in db1, but do no GRANTs on it.
USE TestDb1
CREATE TABLE dbo._testDb1Table (Col1 INT)
GO

--Create a function in db1, and GRANT EXECUTE.
CREATE FUNCTION dbo._TestDb1Function()
RETURNS INT
AS
BEGIN
    DECLARE @Result INT = (SELECT TOP (1) Col1 FROM dbo._testDb1Table)
    RETURN @Result
END
GO
GRANT EXECUTE ON dbo._TestDb1Function TO Db1Role
GO

--Create a function in db2, and GRANT EXECUTE.
USE TestDb2
GO
CREATE FUNCTION dbo._TestDb2Function()
RETURNS INT
AS
BEGIN
    DECLARE @Result INT = (SELECT TestDb1.dbo._TestDb1Function())
    RETURN @Result
END
GO
GRANT EXECUTE ON dbo._TestDb2Function TO Db2Role
GO

/*******************************************
TESTS
*******************************************/
USE TestDb2

--Querying TestDb1 by calling the TestDb2 function directly works.
EXECUTE AS LOGIN = 'TestLogin'
SELECT TestDb1.dbo._TestDb1Function()
REVERT
GO

--Querying TestDb2 through a scalar function in db2 doesn't work.
--The SELECT permission was denied on the object '_testDb1Table', database 'TestDb1', schema 'dbo'.
EXECUTE AS LOGIN = 'TestLogin'
SELECT dbo._TestDb2Function()
REVERT
GO

/*******************************************
TIDY UP
*******************************************/
USE [master]
DROP LOGIN [TestLogin]
DROP DATABASE TestDb1
DROP DATABASE TestDb2

1 Ответ

1 голос
/ 15 июля 2020

Согласно полезным комментариям GSerg и Larnu, такое поведение, по-видимому, вызвано функцией встраивания скалярных UDF, добавленной в SQL Server 2019. встраивание на уровне базы данных, в определение функции или с помощью подсказки запроса.

Вот тот же код, что и в исходном вопросе, но с этими тремя возможными решениями, вставленными в соответствующие места ( закомментирован). Раскомментирование любого из этих трех решений позволяет сценарию работать без ошибок на SQL Server 2019.

/*******************************************
SET UP
*******************************************/
CREATE DATABASE TestDb1
CREATE DATABASE TestDb2
GO
--SOLUTION 1: Turn off scalar UDF inlining at the database level.
--USE TestDb2
--ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = OFF;
GO

CREATE LOGIN [TestLogin] WITH PASSWORD = '123456a.'
GO

--Create users in each database and add to roles.
USE TestDb1
CREATE USER [TestUser] FOR LOGIN [TestLogin]
CREATE ROLE Db1Role
ALTER ROLE Db1Role ADD MEMBER [TestUser]

USE TestDb2
CREATE USER [TestUser] FOR LOGIN [TestLogin]
CREATE ROLE Db2Role
ALTER ROLE Db2Role ADD MEMBER [TestUser]

--Create table in db1, but do no GRANTs on it.
USE TestDb1
CREATE TABLE dbo._testDb1Table (Col1 INT)
GO

--Create a function in db1, and GRANT EXECUTE.
CREATE FUNCTION dbo._TestDb1Function()
RETURNS INT
AS
BEGIN
    DECLARE @Result INT = (SELECT TOP (1) Col1 FROM dbo._testDb1Table)
    RETURN @Result
END
GO
GRANT EXECUTE ON dbo._TestDb1Function TO Db1Role
GO

--Create a function in db2, and GRANT EXECUTE.
USE TestDb2
GO
CREATE FUNCTION dbo._TestDb2Function()
RETURNS INT
--SOLUTION 2: Turn off scalar UDF inlining for the function.
--WITH INLINE = OFF
AS
BEGIN
    DECLARE @Result INT = (SELECT TestDb1.dbo._TestDb1Function())
    RETURN @Result
END
GO
GRANT EXECUTE ON dbo._TestDb2Function TO Db2Role
GO

/*******************************************
TESTS
*******************************************/
USE TestDb2

--Querying TestDb1 by calling the TestDb2 function directly works.
EXECUTE AS LOGIN = 'TestLogin'
SELECT TestDb1.dbo._TestDb1Function()
REVERT
GO

--Querying TestDb2 through a scalar function in db2 doesn't work.
--The SELECT permission was denied on the object '_testDb1Table', database 'TestDb1', schema 'dbo'.
EXECUTE AS LOGIN = 'TestLogin'
SELECT dbo._TestDb2Function()
--SOLUTION 3: Turn off scalar UDF inlining for the query which calls the function.
--OPTION (USE HINT('DISABLE_TSQL_SCALAR_UDF_INLINING')); --Added line
REVERT
GO

/*******************************************
TIDY UP
*******************************************/
USE [master]
DROP LOGIN [TestLogin]
DROP DATABASE TestDb1
DROP DATABASE TestDb2
...