File Response With Permissions?

We have an API that needs to accept an Id and return the corresponding file to the user assuming the business rules allow the user access to that file. We have all the checking correct, find the file in our lookup table, but are having an issue when we go to return the file. We are following the code example you provided here:

but are getting back the following response:

Error (NotFound): abc5d9142952466b9d94a83000acd706_Application_Details_Original.pdf was not found

Handler for Request not found (404):

Request.HttpMethod: GET
Request.PathInfo: /Attachments
Request.QueryString: AttachmentId=3
Request.RawUrl: /Attachments?AttachmentId=3

Of specific concern is that in your example, you said the user can just link directly to the file via URL:

You can also access the text file directly (i.e. without a Service):

http://test.servicestack.net/textfile.txt

which is exactly what we don’t want here since we only want the file to be accessible if the request clears our business rules. How can we return the file/stream via our API but not expose the file to the world?

Code:

            Document doc = await Db.SingleByIdAsync<Document>(request.AttachmentId).ConfigureAwait(false);
            if ((doc != null))
            {

IAuthSession session = GetSession();

            MyUser myUser = MyUserHelper.GetUser(session.UserName);
            Users.ValidateUserRelavence(myUser, originalLoan);
                return new HttpResult(
                    new FileInfo(("~/" + doc.FilePath.Replace("\\", "/").RightPart("Files/")).MapHostAbsolutePath()),
                    asAttachment: request.AsAttachment);
            }

File structure:
API location: C:\Deploy\MyAPI\bin
File Storage location: C:\Files\Storage\ (shared via IIS as a Virtual Directory under MyAPI as \MyAPI\Storage)
Example file location C:\Files\Storage\BRB\12345\abc5d9142952466b9d94a83000acd706_Application_Details_Original.pdf

thanks!

The recommended solution is not adding files you don’t want to be accessible in your WebRoot, add them to a non-servable directory outside of your WebRoot. File sources under VirtualFileSources are serveable directly. Otherwise if you want to prevent direct access to to files in WebRoot you can add the folder to Config.ForbiddenPaths or register a custom RawRequestHandlers or PreRequestFilters to verify and short-clrcuit access before allowing download.

For the 404 the resolved file path doesn’t exist, generally we recommend using the VirtualFileSources to resolve files, e.g:

return new HttpResult(VirtualFileSources.GetFile(doc.FilePath), 
    asAttachment: request.AsAttachment);

Otherwise if you want to use FileInfo we recommend resolving files using HostContext.AppHost.MapProjectPath("~/path/to.file.doc") which is also overridable in your AppHost.

Thanks for the response. Absolutely no problem to not have C:\Files\Storage\ mapped as a virtual directory in the WebSite - was only doing that because I thought it was needed for this.
As far as using

return new HttpResult(VirtualFileSources.GetFile(doc.FilePath), asAttachment: request.AsAttachment);
doesn’t that imply that I need to register that path as a virtual file source in my AppHost (which would open up that whole path to the world)?

That was assuming you want to keep your files servable otherwise you can assign AppHost.VirtualFiles to a different Content Root directory (e.g. where you want uploaded files to go) and use that to access those files:

return new HttpResult(VirtualFiles.GetFile(doc.FilePath), asAttachment: request.AsAttachment);

Something isn’t right.

Testing your code I get:

System.ArgumentException: Second path fragment must not be a drive or UNC name.\r\nParameter name: path2\r\n at System.IO.Path.InternalCombine(String path1, String path2)\r\n at System.IO.FileSystemEnumerableIterator1.GetFullSearchString(String fullPath, String searchPattern)\r\n at System.IO.FileSystemEnumerableIterator1…ctor(String path, String originalUserPath, String searchPattern, SearchOption searchOption, SearchResultHandler1 resultHandler, Boolean checkHost)\r\n at System.IO.DirectoryInfo.InternalGetDirectories(String searchPattern, SearchOption searchOption)\r\n at ServiceStack.VirtualPath.FileSystemVirtualDirectory.EnumerateDirectories(String dirName)\r\n at ServiceStack.VirtualPath.FileSystemVirtualDirectory.GetDirectoryFromBackingDirectoryOrDefault(String dName)\r\n at ServiceStack.VirtualPath.AbstractVirtualDirectoryBase.GetFile(Stack1 virtualPath)\r\n at ServiceStack.VirtualPath.AbstractVirtualPathProviderBase.GetFile(String virtualPath)\r\n at System.Linq.Enumerable.WhereSelectListIterator2.MoveNext()\r\n at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable1 source, Func`2 predicate)\r\n at Arix.API.AttachmentController.<Get>d__11.MoveNext() in

Also, we seem to be talking past each other the last few posts. Again, as you see from my OP, the location of the files is NOT in the website root. I prefer it NOT to be in the website root. I do NOT want the files to be publicly accessible, but only through calling my secured API.

You should be using a virtual path from the source when using the VFS, e.g. VirtualFiles.GetFile("path/to/file.doc").

This is exactly where I’m tripping up. I don’t understand how to create a path relative to the website without creating a virtual path under the website, which you suggested I not do.

The string that GetFile takes should be a path relative to what?

I do or do not need to register a VirtualFileSource in my AppHost?

You can configure VirtualFiles in your AppHost to be any path, i.e. outside of your WebRoot:

VirtualFiles = new FileSystemVirtualFiles("c:\\path\\to\\files");

OK - I added this:

VirtualFiles = new FileSystemVirtualFiles(“C:\Files\Storage”);

return new HttpResult(VirtualFileSources.GetFile(doc.FilePath.RightPart(“Storage\”)), asAttachment: request.AsAttachment);

but now I’m getting this:

System.NullReferenceException: Object reference not set to an instance of an object.\r\n at ServiceStack.HttpResult…ctor(IVirtualFile fileResponse, Boolean asAttachment)\r\n at My.API.AttachmentController.<Get>d__11.MoveNext()

The user context that the API is running under has full permissions to that folder tree.

Is VirtualFileSources.GetFile(doc.FilePath.RightPart("Storage")) returning a file or null? If it’s null the path is wrong.

null. I think we’re still missing on what the relative path should be relative too. I gave above the specific code I’m using - how I’m setting the relative path and and I’m referencing it given the specific file location.

In your example above of

VirtualFiles = new FileSystemVirtualFiles(“c:\path\to\files”);

Should the call to GetFile have a string that starts with files or the folder name within files? (my code does the latter but maybe that’s not correct)?

Make sure the path is properly escaped:

VirtualFiles = new FileSystemVirtualFiles("c:\\path\\to\\files");

If the file is at c:\path\to\files\dir\file.doc then the virtual path will be:

return new HttpResult(VirtualFileSources.GetFile("dir/file.doc"), asAttachment: request.AsAttachment);

It looks like the VirtualFiles property isn’t staying set and that’s causing the issue.

When I break on the VirtualFileSources.GetFile and inspect the VirtualFileSources, none of the values are the one I set in the AppHost with the line:
VirtualFiles = new FileSystemVirtualFiles(“C:\Files\Storage”);

That line is getting hit and the value of VirtualFiles is correct immediately after, but when the API gets called it seems to not have kept it. This is causing it not to be able to find the relative path…

If it helps, I don’t need it set globally, it’s fine to set it the line before I access the file if that’s doable.

You should be using VirtualFiles.GetFile() not the public VirtualFileSources.GetFile().

Working now - thanks!

1 Like