Downloading a file from a Remote File share throws access denied
Asked Answered
D

5

6

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.

Downtrodden answered 17/3, 2018 at 12:57 Comment(15)
The identity of the application pool (typically depending on your site configuration) is the identity that will try to access that share. What is the app pool identity and does it have read access?Lacreshalacrimal
Yes, the application pool has full control.Downtrodden
It works fine on local folders but fails on remote server resources (file share)Downtrodden
How do I make sure that the application pool account is used to access the remote resourcesDowntrodden
That it works on local folders does not surprise me. Check the event logs on the remote server for security events.Lacreshalacrimal
What is the identity? Is it one of the built in accounts like Network Service or Local System? Are you on a windows domain? You may need to create and or use a domain user for the app pool.Lacreshalacrimal
It is hosted on SharePoint uses domain account for application pool and the application pool has full control on the remote share.Downtrodden
I did see that user account is being used to request the resources however somehow it is not working when the resources are in remote serverDowntrodden
OK, then I don't think I can help. I'm not familiar with accessing Sharepoint resources but I doubt you would do so with file share permissions.Lacreshalacrimal
It is not a sharepoint resource but asp.net generic handler is hosted on sharePoint webapplication. ASP.Net Generic handler is used to download the file from the remote server(using file share - \\anotherServer\Files\xxx.xx). Anyway, Thank you, Crowcoder.Downtrodden
Permissions to files on shares are set in two places - permissions for share and “classic” permissions to files. I very often fall into the trap of forgetting of one of the places ;) Did you checked these two places?Geniagenial
Not really an answer, but perhaps you could work around this limitation through impersonation (support.microsoft.com/en-us/help/306158/…)? Just for testing, to see if that works (perhaps same user as app pool)Hotshot
@AndrzejMartyna, yes, everyone has read access in both classic and sharing permissions.Downtrodden
@KarthikeyanVijayakumar did you checked my answer?Wacky
@AlexandruClonțea, I was able to get this work by using SPSecurity.RunWithElevatedPrivileges(delegate () {context.Response.TransmitFile(string.Format(@"{0}\{1}", mediaCenterImageFolder, fileName));}); will try impersonation if I come across any issue.Downtrodden
W
4

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:");
}
Wacky answered 21/3, 2018 at 12:46 Comment(2)
Thank you, @tinamou. Somehow I was able to get this work by using SPSecurity.RunWithElevatedPrivileges(delegate () {context.Response.TransmitFile(string.Format(@"{0}\{1}", mediaCenterImageFolder, fileName));});Downtrodden
Glad to hear that :)Wacky
W
0

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

Widthwise answered 21/3, 2018 at 7:40 Comment(1)
Everyone is granted access on both classic folder level permission and sharing permissions.Downtrodden
E
0

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.

Entry answered 22/3, 2018 at 7:2 Comment(1)
As mentioned earlier, Everyone including the application pool account is granted access on both classic folder level permission and sharing permissions.Downtrodden
B
0

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.

Bors answered 25/3, 2018 at 2:35 Comment(0)
C
0

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
Criticaster answered 27/9, 2021 at 13:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.