Олицетворение и объединение SQL Server

Мне задали задачу написать веб-интерфейс для устаревшей базы данных, где у всех пользователей есть учетные записи баз данных и соответственно назначены роли (у нас есть триггеры по всему месту записи, когда пользователи делают определенные вещи, все на основе user_name()).

Чтобы использовать что-нибудь удаленно современное и чтобы не хранить пароль пользователя в текстовом формате, я подключаюсь к учетной записи уровня App, у которой есть привилегии олицетворения для каждого пользователя, и я пытаюсь запустить Execute As [email protected] и Revert установить и reset контекст выполнения до и после запуска любого SQL.

К сожалению, вызов пула соединений reset_connection отключает w/my Connection, и он задерживает выбросы некоторых неприятных ошибок в отношении физического подключения, которое недействительно...

Я могу обойти эту ошибку, не используя пул соединений. Но тогда мой пользователь приложения нуждается в безумном количестве привилегий, чтобы фактически выполнить олицетворение. Кроме того, уничтожение пула соединений является обломком...

Как я могу сделать это без ущерба для безопасности или производительности? Имейте в виду, что я не могу изменить тот факт, что у моих пользователей есть логины базы данных, и я действительно не в восторге от того, что пользовательские пароли восстанавливаются. Является ли мой единственный вариант обхода пула соединений, чтобы я мог выдавать себя (и использовать пользователя sa, чтобы у меня было достаточно прав для фактического олицетворения кого-то)?

Ответ 1

Чтобы реализовать своего рода "фальшивую" делегацию без огромных изменений в коде приложения/базы данных, я предлагаю использовать context_info() для переноса текущего пользователя в базу данных и замените вызовы на user_name() на вызовы dbo.fn_user_name().

Пример создания этого решения

Создать функцию fn_user_name()

Я бы создал функцию fn_user_name, которая будет извлекать имя пользователя из контекста_info() в соединении или возвращать имя_пользователя(), когда нет доступной информации контекста. обратите внимание, что контекст соединения представляет собой 128-байтовый двоичный код. Все, что вы там наложите, будет дополнено нулевыми символами, чтобы обойти это. Я наполняю значения пробелами.

create function dbo.fn_user_name()
returns sysname
as
begin
    declare @user sysname = rtrim(convert(nvarchar(64), context_info()))
    if @user is null 
        return user_name()
    return @user
end
go

Теперь вы можете заменить все вызовы на user_name() в своем коде и заменить их на эту функцию.

Вставить контекст в свои вызовы db в .net

Здесь есть 2 варианта. Или вы создаете свой собственный класс SqlConnection или создаете метод factory, который возвращает открытое соединение sql, как показано ниже. Метод factory имеет такую ​​проблему, что каждый запрошенный вами запрос будет иметь 2 дБ вызовов. Это наименьший код для записи.

    public SqlConnection CreateConnection(string connectionString, string user)
    {
        var conn = new SqlConnection(connectionString);
        using (var cmd = new SqlCommand(
            @"declare @a varbinary(128) = convert(varbinary(128), @user + replicate(N' ', 64 - len(@user)))
              set context_info @a", conn))
        {
            cmd.Parameters.Add("@user", SqlDbType.NVarChar, 64).Value = user;
            conn.Open();
            cmd.ExecuteNonQuery();
        }
        return conn;
    }

вы использовали бы это как:

using(var conn = CreateConnection(connectionString, user))
{
   var cmd = new SqlCommand("select 1", conn);
   return conn.ExecuteScalar()
}

Для альтернативной версии SqlConnection вам нужно будет перегрузить DbConnection и реализовать все методы SqlConnection. Методы выполнения предваряют запрос ниже и передают имя пользователя в качестве дополнительного параметра.

declare @a varbinary(128) = convert(varbinary(128), @user + replicate(N' ', 64 - len(@user)))
set context_info @a 

этот класс будет затем использоваться как:

using(var conn = new SqlContextInfoConnection(connectionString, user))
{
   var cmd = new SqlCommand("select 1", conn);
   conn.open;
   return conn.ExecuteScalar()
}

Я бы лично реализовал вариант 2, поскольку он будет ближе к нормальному функционированию SqlConnection.

Ответ 2

Я знаю, что это старо, однако этот пост является единственным полезным ресурсом, который я нашел, и я решил поделиться своим решением, которое строится на Filip De Vos ответ.

У нас также есть устаревшее приложение VB6, в котором используется sp_setapprole (я ценю, что это не совсем соответствует оригинальной записи OPs). Наши компоненты .NET, которые используют одну и ту же базу данных (и по существу являются частью платформы приложения), в значительной степени основаны на Linq to SQL.

Настройка соответствия для соединения с данными datacontext оказалась затруднительной, учитывая количество открытий и закрытие соединения.

Мы закончили тем, что использовали простую оболочку, как было предложено выше. Единственными переопределенными методами являются Open() и Close(), где approle устанавливается и не устанавливается.

Public Class ManagedConnection
    Inherits Common.DbConnection

    Private mCookie As Byte()
    Private mcnConnection As SqlClient.SqlConnection

    Public Sub New()
        mcnConnection = New SqlClient.SqlConnection()
    End Sub

    Public Sub New(connectionString As String)
        mcnConnection = New SqlClient.SqlConnection(connectionString)
    End Sub

    Public Sub New(connectionString As String, credential As SqlClient.SqlCredential)
        mcnConnection = New SqlClient.SqlConnection(connectionString, credential)
    End Sub

    Public Overrides Property ConnectionString As String
        Get
            Return mcnConnection.ConnectionString
        End Get
        Set(value As String)
            mcnConnection.ConnectionString = value
        End Set
    End Property

    Public Overrides ReadOnly Property Database As String
        Get
            Return mcnConnection.Database
        End Get
    End Property

    Public Overrides ReadOnly Property DataSource As String
        Get
            Return mcnConnection.DataSource
        End Get
    End Property

    Public Overrides ReadOnly Property ServerVersion As String
        Get
            Return mcnConnection.ServerVersion
        End Get
    End Property

    Public Overrides ReadOnly Property State As ConnectionState
        Get
            Return mcnConnection.State
        End Get
    End Property

    Public Overrides Sub ChangeDatabase(databaseName As String)
        mcnConnection.ChangeDatabase(databaseName)
    End Sub

    Public Overrides Sub Close()

        Using cm As New SqlClient.SqlCommand("sp_unsetapprole")
            cm.Connection = mcnConnection
            cm.CommandType = CommandType.StoredProcedure
            cm.Parameters.Add("@cookie", SqlDbType.VarBinary, 8000).Value = mCookie

            cm.ExecuteNonQuery()
        End Using

        mcnConnection.Close()
    End Sub

    Public Overrides Sub Open()
        mcnConnection.Open()

        Using cm As New SqlClient.SqlCommand("sp_setapprole")
            cm.Connection = mcnConnection
            cm.CommandType = CommandType.StoredProcedure
            cm.Parameters.Add("@rolename", SqlDbType.NVarChar, 128).Value = "UID"
            cm.Parameters.Add("@password", SqlDbType.NVarChar, 128)Value = "PWD"
            cm.Parameters.Add("@fCreateCookie", SqlDbType.Bit).Value = True
            cm.Parameters.Add("@cookie", SqlDbType.VarBinary, 8000).Direction = ParameterDirection.InputOutput
            cm.ExecuteNonQuery()

            mCookie = cm.Parameters("@cookie").Value
        End Using
    End Sub

    Protected Overrides Function BeginDbTransaction(isolationLevel As IsolationLevel) As DbTransaction
        Return mcnConnection.BeginTransaction(isolationLevel)
    End Function

    Protected Overrides Function CreateDbCommand() As DbCommand
        Return mcnConnection.CreateCommand()
    End Function
End Class

До:

Using dc As New SystemOptionDataContext(sConnectionString)
    intOption= dc.GetIntegerValue("SomeDatabaseOption")
End Using

После:

Using dc As New SystemOptionDataContext(New ManagedConnection(strConnectionString))
    intOption= dc.GetIntegerValue("SomeDatabaseOption")
End Using

Надеюсь, что это поможет другим.