My goal here is to have an attachments manager that (1) effectively uses SQL FILESTREAM as it was designed, (2) restricts access to the SQL FileTable and its underlying FILESTREAM Windows directory to one of two Active Directory accounts (creds provided by a web/app.config file), and (3) effectively disregards the current Windows Auth user for whatever site or app in which this library is included.
I've seen a number of posts about this issue already, and I believe I've tried nearly every suggestion I've found across them all, still with little-to-no success.
I have:
"Server={server};Database=AttachmentsGlobal;User ID={domain}\\{user};Password={password};Trusted_Connection=False;Integrated Security=true;"
filestream
column.SqlFileStream
object (see below).It works whenever I disable the using(Impersonator){}
block in SaveFile
below, but it fails otherwise. It also seems to ignore the connection string when I debug locally: my user account has access to the attachments folder because I'm an admin, but when I change the connection string to a user that explicitly does NOT have access, it still uploads fine. I'm assuming this is the "Integrated Security" setting that's passing the current application user's creds to the server and ignoring the provided user. I'm not sure how to test this well, short of running Visual Studio as another user (which I have tried, and it fails).
Please tell me that I'm doing something silly and dumb, and that I'm reasonably close to a solution. This incredibly vague "access is denied" error is driving me nuts! Thanks.
// SqlFileItem is just a simple model mapping all the columns of the file
// table (except the filestream itself)
public SqlFileItem UploadFile(Stream fileStream, string fileName,
SqlFileItem parent, AttachmentType restricted) {
const string uploadSproc = "InsertAttachment";
const string metadataSproc = "GetMetadata";
SqlFileItem rt;
using (TransactionScope ts = new TransactionScope()) {
using (SqlConnection conn = GetConnection(restricted)) {
conn.Open();
string serverPath;
byte[] serverXfer;
Guid streamIdOfUploadedFile;
using (SqlCommand cmd = conn.CreateCommand()) {
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = uploadSproc;
cmd.Parameters.Add("@fName", SqlDbType.VarChar).Value =
fileName;
cmd.Parameters.Add("@parent", SqlDbType.VarChar).Value =
parent.path_locator.ToString();
using (SqlDataReader rdr = cmd.ExecuteReader()) {
rdr.Read();
serverPath = rdr.GetSqlString(0).Value;
serverXfer = rdr.GetSqlBinary(1).Value;
streamIdOfUploadedFile = rdr.GetGuid(2);
rdr.Close();
}
}
// See below for SaveFile
SaveFile(fileStream, serverPath, serverXfer, exportRestriction);
using (SqlCommand cmd = conn.CreateCommand()) {
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = metadataSproc;
cmd.Parameters.Add("@streamId",
SqlDbType.UniqueIdentifier).Value =
streamIdOfUploadedFile;
using (SqlDataReader rdr = cmd.ExecuteReader()) {
rt =
DataReaderToList<SqlFileItem>(rdr)
.FirstOrDefault();
}
}
}
ts.Complete();
}
return rt;
}
// Performs the actual streaming/saving operation.
private static void SaveFile(Stream src, string serverPath,
byte[] serverXfer, AttachmentType restricted) {
const int blockSize = 1024*512;
// My attempt at impersonating
Impersonator imp = restricted == AttachmentType.Open
? new Impersonator(@"{openActiveDirectoryUser}", @"{domain}",
@"{password}")
: new Impersonator(@"{restrictedActiveDirectoryUser}", @"{domain}",
@"{password}");
using (imp) {
using (src) {
using (
// The line below is where it breaks with a Win32Exception.
// The full text of it from VS2015 is provided below.
SqlFileStream dest = new SqlFileStream(serverPath,
serverXfer, FileAccess.Write)) {
byte[] buffer = new byte[blockSize];
int bytesRead;
while ((bytesRead = src.Read(buffer, 0, buffer.Length)) >
0) {
dest.Write(buffer, 0, bytesRead);
dest.Flush();
}
dest.Close();
}
src.Close();
}
}
}
ALTER PROCEDURE [dbo].[InsertAttachment]
@fileName varchar(255),
@parent hierarchyid
AS
BEGIN
SET NOCOUNT ON;
DECLARE @returns TABLE (stream_id UNIQUEIDENTIFIER)
INSERT INTO Attachments(file_stream, name, is_directory, path_locator)
OUTPUT inserted.stream_id INTO @returns
VALUES (0x, @fileName, 0, dbo.GetCustomPathLocator(@parent.ToString()))
SELECT file_stream.PathName()
,GET_FILESTREAM_TRANSACTION_CONTEXT()
,aft.stream_id
FROM Attachments att
INNER JOIN @returns rt on att.stream_id = rt.stream_id
END
ALTER PROCEDURE [dbo].[GetMetadata]
@streamId uniqueidentifier
AS
BEGIN
SELECT stream_id
-- omit file_stream varbinary(max)
,name
,path_locator
,parent_path_locator
,file_type
,cached_file_size
,creation_time
,last_write_time
,last_access_time
,is_directory
-- omit remaining is_* flags
FROM Attachments
WHERE stream_id = @streamId
END
System.ComponentModel.Win32Exception was unhandled
ErrorCode=-2147467259
HResult=-2147467259
Message=Access is denied
NativeErrorCode=5
Source=System.Data
StackTrace:
at System.Data.SqlTypes.SqlFileStream.OpenSqlFileStream(String path, Byte[] transactionContext, FileAccess access, FileOptions options, Int64 allocationSize)
at System.Data.SqlTypes.SqlFileStream..ctor(String path, Byte[] transactionContext, FileAccess access, FileOptions options, Int64 allocationSize)
at System.Data.SqlTypes.SqlFileStream..ctor(String path, Byte[] transactionContext, FileAccess access)
at FileTableAttachmentLibrary.AttachmentManager.SaveFile(Stream src, String serverPath, Byte[] serverXfer, AttachmentScope exportRestricted) in C:\_projects\FileTableAttachmentLibrary\FileTableAttachmentLibrary\FileTableAttachmentLibrary\AttachmentManager.cs:line 498
at FileTableAttachmentLibrary.AttachmentManager.UploadFile(Stream fileStream, String fileName, SqlFileItem parent, AttachmentScope exportRestriction) in C:\_projects\FileTableAttachmentLibrary\FileTableAttachmentLibrary\FileTableAttachmentLibrary\AttachmentManager.cs:line 210
at TestAttachments.Program.AttachmentManagerTest() in C:\_projects\FileTableAttachmentLibrary\FileTableAttachmentLibrary\TestAttachments\Program.cs:line 69
at TestAttachments.Program.Main(String[] args) in C:\_projects\FileTableAttachmentLibrary\FileTableAttachmentLibrary\TestAttachments\Program.cs:line 26
InnerException: null