Acesso de linha a linha do SQL Server

10

Eu tenho uma tabela estruturada da seguinte maneira (simplificada)

Name, EMail, LastLoggedInAt

Eu tenho um usuário no SQL Server (RemoteUser) que só pode ver dados (por meio de uma consulta de seleção) em que o campo LastLoggdInAt não é nulo.

Parece que eu posso fazer isso? É possível?

LiamB
fonte
Aqui está o tópico dos Manuais Online para Segurança em Nível de Linha: docs.microsoft.com/en-us/sql/relational-databases/security/…
David Browne - Microsoft

Respostas:

32

O modelo de segurança do SQL Server permite conceder acesso a uma exibição sem conceder acesso às tabelas subjacentes.

Como o código de exemplo é uma ótima maneira de mostrar um conceito, considere o seguinte, com uma LoginDetailstabela e a visualização correspondente:

CREATE TABLE dbo.LoginDetails
(
    Username nvarchar(100) NOT NULL
    , EmailAddress nvarchar(256) NOT NULL
    , LastLoggedInAt datetime NULL
);
GO

CREATE VIEW dbo.LoginDetailsView
AS
SELECT ld.Username
    , ld.EmailAddress
    , ld.LastLoggedInAt
FROM dbo.LoginDetails ld
WHERE ld.LastLoggedInAt IS NOT NULL;
GO

Criaremos um logon e um usuário, em seguida, atribuiremos a esse usuário os direitos para selecionar linhas da exibição, sem ter direitos para exibir a tabela em si.

CREATE LOGIN RemoteUser 
WITH PASSWORD = '2q1345lkjsadfgsa0(*';

CREATE USER RemoteUser
FOR LOGIN RemoteUser
WITH DEFAULT_SCHEMA = dbo;

GRANT SELECT ON dbo.LoginDetailsView TO RemoteUser;

Agora, vamos inserir duas linhas de teste:

INSERT INTO dbo.LoginDetails(Username, EmailAddress, LastLoggedInAt)
VALUES ('user x', '[email protected]', NULL)
    , ('user y', '[email protected]', GETDATE());

Isso testa o modelo de segurança. A primeira SELECTinstrução é bem-sucedida, pois é selecionada na visualização, enquanto a segunda SELECTinstrução falha porque o usuário não tem acesso direto à tabela.

EXECUTE AS LOGIN = 'RemoteUser';

SELECT *
FROM dbo.LoginDetailsView;
╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nome de usuário ║ EndereçoEmail ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ user y ║ [email protected] -15 2018-02-15 07: 36: 54.490 ║
╚══════════╩══════════════╩═══════════════════════ ══╝
SELECT *
FROM dbo.LoginDetails;

REVERT

Observe que os resultados da exibição excluem a linha em que o LastLoggedInAtvalor está NULL, conforme necessário em sua pergunta.

A segunda SELECTinstrução na tabela subjacente retorna um erro:

Msg 229, Nível 14, Estado 5, Linha 28
A permissão SELECT foi negada no objeto 'LoginDetails', banco de dados 'tempdb', esquema 'dbo'.

Limpar:

DROP USER RemoteUser;
DROP LOGIN RemoteUser;
DROP VIEW dbo.LoginDetailsView;
DROP TABLE dbo.LoginDetails;

Como alternativa, se você tiver o SQL Server 2016 ou mais recente, poderá usar um predicado de segurança no nível da linha para impedir que certos usuários vejam linhas com um LastLoggedInAtvalor NULL .

Primeiro, criamos a tabela, um logon, um usuário para esse logon e concedemos acesso à tabela:

CREATE TABLE dbo.LoginDetails
(
    Username nvarchar(100) NOT NULL
    , EmailAddress nvarchar(256) NOT NULL
    , LastLoggedInAt datetime NULL
);
GO

CREATE LOGIN RemoteUser 
WITH PASSWORD = '2q1345lkjsadfgsa0(*';

CREATE USER RemoteUser
FOR LOGIN RemoteUser
WITH DEFAULT_SCHEMA = dbo;

GRANT SELECT ON dbo.LoginDetails TO RemoteUser;

Em seguida, inserimos algumas linhas de amostra. Uma linha com um valor nulo LastLoggedInAte outra com um valor não nulo para essa coluna.

INSERT INTO dbo.LoginDetails(Username, EmailAddress, LastLoggedInAt)
VALUES ('user x', '[email protected]', NULL)
    , ('user y', '[email protected]', GETDATE());

Aqui, estamos criando uma função com valor de tabela vinculada ao esquema que retorna uma linha com 0 ou 1, dependendo do valor das variáveis @LastLoggedInAte @usernameque são passadas para a função. Essa função será usada por um predicado de filtro para eliminar as linhas que queremos ocultar de certos usuários.

CREATE FUNCTION dbo.fn_LoginDetailsRemoteUserPredicate
(
    @LastLoggedInAt datetime
    , @username sysname
)  
RETURNS TABLE  
WITH SCHEMABINDING  
AS  
    RETURN SELECT 1 AS fn_securitypredicate_result   
    WHERE (@username = N'RemoteUser' AND @LastLoggedInAt IS NOT NULL)
        OR @username <> N'RemoteUser';  
GO

Este é o filtro de segurança que elimina as linhas das SELECTinstruções executadas na dbo.LoginDetailstabela:

CREATE SECURITY POLICY LoginDetailsRemoteUserPolicy
ADD FILTER PREDICATE dbo.fn_LoginDetailsRemoteUserPredicate(LastLoggedInAt, USER_NAME())
ON dbo.LoginDetails
WITH (STATE=ON);

O filtro acima usa a dbo.fn_LoginDetailsRemoteUserPredicatefunção passando o nome do usuário atual, juntamente com os valores de cada linha da LastLoggedInAtcoluna da dbo.LoginDetailstabela.

Se consultarmos a tabela como um usuário normal:

SELECT *
FROM dbo.LoginDetails

vemos todas as linhas:

╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nome de usuário ║ EndereçoEmail ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ usuário x ║ [email protected] ║ NULL ║
║ user y ║ [email protected] -15 2018-02-15 13: 53: 42.577 ║
╚══════════╩══════════════╩═══════════════════════ ══╝

No entanto, se testarmos como RemoteUser:

EXECUTE AS LOGIN = 'RemoteUser';

SELECT *
FROM dbo.LoginDetails

REVERT

vemos apenas linhas "válidas":

╔══════════╦══════════════╦═══════════════════════ ══╗
║ Nome de usuário ║ EndereçoEmail ║ LastLoggedInAt ║
╠══════════╬══════════════╬═══════════════════════ ══╣
║ user y ║ [email protected] -15 2018-02-15 13: 42: 02.023 ║
╚══════════╩══════════════╩═══════════════════════ ══╝

E limpamos:

DROP SECURITY POLICY LoginDetailsRemoteUserPolicy;
DROP FUNCTION dbo.fn_LoginDetailsRemoteUserPredicate;
DROP USER RemoteUser;
DROP LOGIN RemoteUser;
DROP TABLE dbo.LoginDetails;

Esteja ciente de que a ligação de um esquema a uma função dessa tabela impossibilita modificar a definição da tabela sem primeiro descartar o predicado do filtro e a dbo.fn_LoginDetailsRemoteUserPredicatefunção.

Max Vernon
fonte
Resposta brilhante - obrigado! Qual é a implicação de desempenho desses 2 métodos? Descobrimos que nosso aplicativo Web é 5 vezes mais lento quando usamos a função. Precisa olhar para o método View.
LiamB 13/0318
A função de segurança no nível da linha é avaliada para cada linha lida na tabela; Eu esperaria que ele diminuísse significativamente o acesso a essa tabela. A exibição, por outro lado, deve ter um impacto insignificante no desempenho, supondo que você crie um índice útil na LastLoggedInAtcoluna.
Max Vernon
Isso faz sentido - vou olhar para a vista agora, parece estar funcionando bem! Se desejássemos que o usuário pudesse editar apenas dados do usuário para essas linhas que correspondam aos critérios, isso seria possível com a visualização?
LiamB 14/0318
É lindo, tudo funcionando - obrigado pela ajuda com isso #
LiamB
Sim, você pode editar linhas por meio da visão
Max Vernon