简体   繁体   中英

Downloading a file from a Remote File share throws access denied

I have the following code in ASP.Net Generic handler to download the file.

// generate you file
// set FilePath and FileName variables
string stFile = FilePath + FileName;
try {
    response.Clear();
    response.ContentType = "application/pdf";
    response.AppendHeader("Content-Disposition", "attachment; filename=" + FileName + ";");
    response.TransmitFile(stFile);
} catch (Exception ex) {
    // any error handling mechanism
} finally {
    response.End();
}

it works fine with local path - like "D:\Files\Sample.pdf" or "\localserver\Files\Sample.pdf".

but it throws and access denied error while trying to access the Network file share like "\anotherServer\Files\Sample.pdf".

Does it related to double hopping? If can I use spsecurity.runwithelevatedprivileges to fix this issue? or what are the other options?

As per this article https://weblogs.asp.net/owscott/iis-windows-authentication-and-the-double-hop-issue it seems to be a double hopping issue. How do I address this? All I want is that end user should be able to download remote resources by using asp.net generic handler.

I have to deal with something similar once and to get this to work by mapping network share to specific drive.

Solution to add credentials to your network share before trying to access it didn't work (but you can try it as a first attempt)

NetworkCredential nc = new NetworkCredential("<login>", "<pass>");
CredentialCache cache = new CredentialCache();
cache.Add(new Uri("<your network share>"), "Basic", nc);

If this doesn't work use the combined solution with mapping network share to drive. Use class NetworkDrive to make WinApi calls to WNetAddConnection2 and WNetCancelConnection2 :

public class NetworkDrive
{
    public enum ResourceScope
    {
        RESOURCE_CONNECTED = 1,
        RESOURCE_GLOBALNET,
        RESOURCE_REMEMBERED,
        RESOURCE_RECENT,
        RESOURCE_CONTEXT
    }

    public enum ResourceType
    {
        RESOURCETYPE_ANY,
        RESOURCETYPE_DISK,
        RESOURCETYPE_PRINT,
        RESOURCETYPE_RESERVED
    }

    public enum ResourceUsage
    {
        RESOURCEUSAGE_CONNECTABLE = 0x00000001,
        RESOURCEUSAGE_CONTAINER = 0x00000002,
        RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
        RESOURCEUSAGE_SIBLING = 0x00000008,
        RESOURCEUSAGE_ATTACHED = 0x00000010,
        RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
    }

    public enum ResourceDisplayType
    {
        RESOURCEDISPLAYTYPE_GENERIC,
        RESOURCEDISPLAYTYPE_DOMAIN,
        RESOURCEDISPLAYTYPE_SERVER,
        RESOURCEDISPLAYTYPE_SHARE,
        RESOURCEDISPLAYTYPE_FILE,
        RESOURCEDISPLAYTYPE_GROUP,
        RESOURCEDISPLAYTYPE_NETWORK,
        RESOURCEDISPLAYTYPE_ROOT,
        RESOURCEDISPLAYTYPE_SHAREADMIN,
        RESOURCEDISPLAYTYPE_DIRECTORY,
        RESOURCEDISPLAYTYPE_TREE,
        RESOURCEDISPLAYTYPE_NDSCONTAINER
    }

    [StructLayout(LayoutKind.Sequential)]
    private class NETRESOURCE
    {
        public ResourceScope dwScope = 0;
        public ResourceType dwType = 0;
        public ResourceDisplayType dwDisplayType = 0;
        public ResourceUsage dwUsage = 0;
        public string lpLocalName = null;
        public string lpRemoteName = null;
        public string lpComment = null;
        public string lpProvider = null;
    }

    [DllImport("mpr.dll")]
    private static extern int WNetAddConnection2(NETRESOURCE lpNetResource, string lpPassword, string lpUsername, int dwFlags);

    [DllImport("mpr.dll")]
    static extern int WNetCancelConnection2(string lpName, Int32 dwFlags, bool bForce);

    private const int CONNECT_UPDATE_PROFILE = 0x1;
    private const int NO_ERROR = 0;

    public int MapNetworkDrive(string unc, string drive, string user, string password)
    {
        NETRESOURCE myNetResource = new NETRESOURCE();
        myNetResource.lpLocalName = drive;
        myNetResource.lpRemoteName = unc;
        myNetResource.lpProvider = null;
        int result = WNetAddConnection2(myNetResource, password, user, 0);
        if (result == 0)
            return result;
        else
            throw new Win32Exception(Marshal.GetLastWin32Error());
    }

    public int UnmapNetworkDrive(string drive)
    {
        int result = WNetCancelConnection2(drive, CONNECT_UPDATE_PROFILE, true);
        if (result != NO_ERROR)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        return result;
    }
}

And use it like this:

NetworkDrive nd = new NetworkDrive();

try
{
    if (nd.MapNetworkDrive("<your network share>", "Z:", "<login>", "<pass>") == 0)
    {
        NetworkCredential nc = new NetworkCredential("<login>", "<pass>");
        CredentialCache cache = new CredentialCache();
        cache.Add(new Uri("<your network share>"), "Basic", nc);

        // access network share using UNC path (not your drive letter)
    }
}
finally
{
    nd.UnmapNetworkDrive("Z:");
}

You need to provide the file permissions through and once your work is done you need to revoke them. better to use try finally block in finally block you can revoke permissions

For file permissions example you can find in MSDN Link

File name should not contain spaces because it will corrupt download in mozilla browser

I ran into the same problem before, your code seems fine to me, what you need to check is the security and file permissions.

let's say that your handler is hosted under IIS, your application pool identity have to be set to a domain user, to run IIS process with that user's privileges.

so you have to give permission for that user to read/write the content of your Shared folder.

I normally do that to access the database and SAN storage for simple apps.

If you are using the default generated IIS app pool identity, your worker process will attempt to access network resources as the domain-registered machine account (Example: yourdomain\\machinename$). More details about how IIS operates in these situations can be found here .

As long as the server you are attempting to access is on the same domain, you just need to set both the "Share" permissions and the NTFS permissions (under Security for the shared folder) to allow this domain machine account to access the share.

NOTE: Machine accounts are not listed by default when selecting an account in the Security dialogue- you need to click "Object Types" when in the "Select Users and Groups" dialogue and add "Computers". More details about how IIS operates in these situations can be found here .

In my case it (finally) worked adding this line in the download procedure:
Response.AddHeader("Transfer-Encoding", "identity"); In my web application i also did this:

  • i download files using UNC path
  • i impersonated the web application to work under my user, i am admin
  • i also set for the app's pool my user

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM