Prevent download files from URL - asp.net-core

I've Blazor Application (Blazor Server) with side menu. When you click on one of these menus, you will open PDF file based on specific privilege (when clicks on href).
My question :- what if someone changes the URL manually and replace it by the file URL, how I can get this URL or prevent unauthorized user from downloading this file ??

It's better to create a controller to download file, so you can control the download before it starts.
Something like:
My File
In this case the FilesController will have a Download method and inside this method you can check the authorization process.

public FileResult DownloadFile()
{
// logic to allow/disallow users from downloading
byte[] fileBytes = System.IO.File.ReadAllBytes(#"pathtofile"); // or any other source
string fileName = "nameWithExtension";
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}
While link to this method in controller can be presented to end users

Related

Task Module call from Ms Teams in Bot Framework

I am looking to open a task module (Pop up - iframe with audio/video) in my bot that is connected to Teams channel. I am having issues following the sample code provided on the GitHub page.
I have tried to follow the sample and incorporate to my code by did not succeed.
In my bot.cs file I am creating card action of invoke type:
card.Buttons.Add(new CardAction("invoke", TaskModuleUIConstants.YouTube.ButtonTitle, null,null,null,
new Teams.Samples.TaskModule.Web.Models.BotFrameworkCardValue<string>()
{
Data = TaskModuleUIConstants.YouTube.Id
}));
In my BotController.cs that inherits from Controller
[HttpPost]
public async Task PostAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await _adapter.ProcessAsync(Request, Response, _bot);
}
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity.Type == ActivityTypes.Invoke)
{
return HandleInvokeMessages(activity);
}
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
private HttpResponseMessage HandleInvokeMessages (Activity activity)
{
var activityValue = activity.Value.ToString();
if (activity.Name == "task/fetch")
{
var action = Newtonsoft.Json.JsonConvert.DeserializeObject<Teams.Samples.TaskModule.Web.Models.BotFrameworkCardValue<string>>(activityValue);
Teams.Samples.TaskModule.Web.Models.TaskInfo taskInfo = GetTaskInfo(action.Data);
Teams.Samples.TaskModule.Web.Models.TaskEnvelope taskEnvelope = new Teams.Samples.TaskModule.Web.Models.TaskEnvelope
{
Task = new Teams.Samples.TaskModule.Web.Models.Task()
{
Type = Teams.Samples.TaskModule.Web.Models.TaskType.Continue,
TaskInfo = taskInfo
}
};
return msg;
}
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
There is more code as per the GitHub sample but I won't paste it here. Can someone point me into the correct direction ?
I have got to the stage that it is displaying a pop up window but the content and title comes from manifest file instead of creating actual iframe also no video is rendering. My goal is to render video within my teams using iframe container.
The important part from the sample:
This sample is deployed on Microsoft Azure and you can try it yourself by uploading Task Module CSharp.zip to one of your teams and/or as a personal app. (Sideloading must be enabled for your tenant; see step 6 here.) The app is running on the free Azure tier, so it may take a while to load if you haven't used it recently and it goes back to sleep quickly if it's not being used, but once it's loaded it's pretty snappy.
So,
Your Teams Admin MUST enable sideloading
Your bot MUST be sideloaded into Teams
The easiest way to do this would be download the sample manifest, open it in App Studio, then edit your bot information in. You then need to make sure Domains and permissions > Valid Domains are set for your bot. Also ensure you change the Tabs URLs to your own.
You also need to make sure that in your Tasks, the URLs they call ALL use https and not http. If anywhere in the chain is using http (like if you're using ngrok and http://localhost), it won't work.

ABCpdf - Download PDF with .NET Core 2.1 - HttpContext/HttpResponse

I'm creating a web page that will allow the user to download a report as a PDF using ABCpdf. But reading the documentation, the only options I see are by using doc.Save("test.pdf") (which saves the file on the server that is hosting the application) or using 'HttpContext.Current.ApplicationInstance.CompleteRequest();' (which saves on the client side, which is what I want, but HttpContext.Current is not available on .NET Core.
The band-aid solution I have is with the doc.Save(), I would save the file on the server then send a link to the view which then downloads it from the server. A potential risk I can think of is making sure to 'clean up' after the download has commenced on the server.
Is there a alternative/.NET Core equivalent for HttpContext.Current and also HttpResponse?
Here is the code that I'd like to make work:
byte[] theData = doc.GetData();
Response.ClearHeaders();
Response.ClearContent();
Response.Expires = -1000;
Response.ContentType = "application/pdf";
Response.AddHeader("content-length", theData.Length.ToString());
Response.AddHeader("content-disposition", "attachment; filename=test.pdf");
Response.BinaryWrite(theData);
HttpContext.Current.ApplicationInstance.CompleteRequest();
Errors I get (non-verbose)
'HttpResponse' does not contain a definition for 'ClearHeaders'
'HttpResponse' does not contain a definition for 'ClearContent'
'HttpResponse' does not contain a definition for 'Expires'
'HttpResponse' does not contain a definition for 'AddHeader'
'HttpResponse' does not contain a definition for 'BinaryWrite'
'HttpContext' does not contain a definition for 'Current'
I've updated this answer to something that actually works!
GetStream does what you need, however to facilitate a file download in .NET Core it would be far easier if you create a controller as described in https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-2.1.
Then you can create a route controller to serve the file from the stream as shown in Return PDF to the Browser using Asp.net core.
So your controller would look something like:
[Route("api/[controller]")]
public class PDFController : Controller {
// GET: api/<controller>
[HttpGet]
public IActionResult Get() {
using (Doc theDoc = new Doc()) {
theDoc.FontSize = 96;
theDoc.AddText("Hello World");
Response.Headers.Clear();
Response.Headers.Add("content-disposition", "attachment; filename=test.pdf");
return new FileStreamResult(theDoc.GetStream(), "application/pdf");
}
}
}
Out of curiosity I just mocked this up and it does work - serving the PDF direct to the browser as download when you go to the URL localhost:port/api/pdf. If you make the content-disposition "inline; filename=test.pdf" it will show in the browser and be downloadable as test.pdf.
More information on the GetStream method here: https://www.websupergoo.com/helppdfnet/default.htm?page=source%2F5-abcpdf%2Fdoc%2F1-methods%2Fgetstream.htm

Secure pdf files and Xamarin Forms

I have a forms app which requires authentication. One of my pages is a webview, and I use cookies to send the authentication status through the webview to the server. On the webpage is a link to a .pdf file which also requires authentication. When this link is tapped I grab the url and attempt to open it using Device.OpenUri, but the authentication fails. Is there a way to send the authentication cookies along with the url to OpenUri, or another way to make the device's browser aware that the user is authenticated? Or is there another way to download the pdf file in the webview?
Because Device.OpenUri, will just open another app with the url, there is no way to pass authentication through. What you need to do is download the PDF to local storage, then open the PDF from your local storage. You will need to place the file in shared storage on your mobile device to allow the other application to have access to it.
If that is a security risk, you will need to add an internal PDF Viewer to your app, so you can view the PDF in your app internally.
Now, if you want to download the PDF, you will need to use HttpClient. In iOS and UWP, the HttpClient and WebView share the same cookie container, which means if you authenticated on the WebView, it will already be authenticated in the HttpClient. For Android, you need to copy the authentication cookies across.
First establish a cookie container you can read.
CookieContainer _cookieContainer = new CookieContainer();
System.Net.Http.HttpClient _client = new System.Net.Http.HttpClient(new HttpClientHandler() { CookieContainer = _cookieContainer });
Then depending upon which way you want to copy, you can do
private void CopyCookies(HttpResponseMessage result, Uri uri)
{
foreach (var header in result.Headers)
if (header.Key.ToLower() == "set-cookie")
foreach (var value in header.Value)
_cookieManager.SetCookie($"{uri.Scheme}://{uri.Host}", value);
foreach (var cookie in GetAllCookies(_cookieContainer))
_cookieManager.SetCookie(cookie.Domain, cookie.ToString());
}
or
public void ReverseCopyCookies(Uri uri)
{
var cookie = _cookieManager.GetCookie($"{uri.Scheme}://{uri.Host}");
if (!string.IsNullOrEmpty(cookie))
_cookieContainer.SetCookies(new Uri($"{uri.Scheme}://{uri.Host}"), cookie);
}
I go into more detail in Cookie Sharing with WebView and the Http Client.

ASP MVC4 how to send header in action method of controller to force pdf file download?

I have a controller on server side, and the method
public ActionResult GetBulletin()
{
return File(#"E:\Fileserver\022015.pdf", "application/pdf");
}
this is triggered when a user clicks on a link on the webpage, the document then is opened in a browser window.
the link is
Download
What i want to do is force the download, I read that we shall use the content disposition header, my question is how to have the server send this header?
Response.AppendHeader("Content-Disposition", #"attachment; filename=E:\Fileserver\022015.pdf");
return View() ;
doesn't work of course.thanks.
You should set only file name in your Response.AppendHeader("Content-Disposition", #"attachment; filename=E:\Fileserver\022015.pdf"); statement so it becomes:
Response.AppendHeader("Content-Disposition", #"attachment; filename=022015.pdf");.
Then you can use the File(<file-bytes>, <content-type>) to send the file to the client. In your case this could be done like this:
return File(System.IO.File.ReadAllBytes(#"E:\Fileserver\022015.pdf"), "application/pdf")

Why are cookies unrecognized when a link is clicked from an external source (i.e. Excel, Word, etc...)

I noticed that when a link is clicked externally from the web browser, such as from Excel or Word, that my session cookie is initially unrecognized, even if the link opens up in a new tab of the same browser window.
The browser ends up recognizing its cookie eventually, but I am puzzled as to why that initial link from Excel or Word doesn't work. To make it even more challenging, clicking a link works fine from Outlook.
Does anybody know why this might be happening? I'm using the Zend Framework with PHP 5.3.
This is because MS Office is using Hlink.dll component to lookup if the link is Office document or something else. MS Office expect to open the document linked within documents without the aid of external browser (using Hlink.dll component of IE6).
If session cookie protects website Hlink naturally is being redirected to login page and having reached HTML page and not able to "understand" it opens it in external browser. Note that it opens not original URL (expected behavior) but the result of redirect, even if it was 302 redirect.
Microsoft has that bug in unsupported component (Hlink.dll), instead of recognizing the bug they turn it over to our head (trying to convince us that it is flaw of SSO system we use, i.e. session cookies) and refuses to upgrade it. It offers workaround that turns off the lookup functionality of MS Office:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
Office\9.0\Common\Internet\ForceShellExecute:DWORD=1
Or offer us to workaround serverside, to avoid HTTP redirects and change into Javascript redirects or META REFRESH redirects (i.e. to have Hlink get text/html page on original URL and make it run external browser to handle it).
Server side this worked for me in IIS (using a rewrite rule)
<rule name="WordBypass" enabled="true" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{HTTP_USER_AGENT}" pattern="Word|Excel|PowerPoint|ms-office" />
</conditions>
<action type="CustomResponse" statusCode="200" statusReason="Refresh" statusDescription="Refresh" />
</rule>
We had this same problem and wrote an open source gem to help those using rails: https://github.com/spilliton/fix_microsoft_links
You can use the same approach we used on any framework though:
Detect if the user agent is from a Microsoft product
Render a blank html page with a meta refresh tag that will cause the browser to refresh the page with the correct cookies
Example code here: https://github.com/spilliton/fix_microsoft_links/blob/master/lib/fix_microsoft_links.rb
PHP solution:
This prevents the MS product recognising the redirect. MS therefore launches a browser from the required link.
if (isset($_SERVER['HTTP_USER_AGENT']))
{
$http_user_agent = $_SERVER['HTTP_USER_AGENT'];
if (preg_match('/Word|Excel|PowerPoint|ms-office/i', $http_user_agent))
{
// Prevent MS office products detecting the upcoming re-direct .. forces them to launch the browser to this link
die();
}
}
.. redirect after this code
Fix for VB.NET:
Dim userAgent As String = System.Web.HttpContext.Current.Request.UserAgent
If userAgent.Contains("Word") Or userAgent.Contains("Excel") Or userAgent.Contains("PowerPoint") Or userAgent.Contains("ms-office") Then
System.Web.HttpContext.Current.Response.Clear()
System.Web.HttpContext.Current.Response.Write("<html><head><meta http-equiv='refresh' content='0'/></head><body></body></html>")
System.Web.HttpContext.Current.Response.End()
End If
It basically forces the browser to refresh the page, so the request comes in with the user agent of the browser and all the correct cookies.
Here is a solution for C# ASP.NET based on spilliton's answer above. In Global.asax.cs, add the following:
private static string MSUserAgentsRegex = #"[^\w](Word|Excel|PowerPoint|ms-office)([^\w]|\z)";
protected void Application_OnPostAuthenticateRequest(object sender, EventArgs e)
{
if (System.Text.RegularExpressions.Regex.IsMatch(Request.UserAgent, MSUserAgentsRegex))
{
Response.Write("<html><head><meta http-equiv='refresh' content='0'/></head><body></body></html>");
Response.End();
}
}
Here is an example of the fix using a dotnet core middleware:
public class MicrosoftOfficeLinksHandlingMiddleware
{
private static readonly Regex MsUserAgentsRegex = new Regex(#"[^\w](Word|Excel|PowerPoint|ms-office)([^\w]|\z)");
private readonly RequestDelegate _next;
public MicrosoftOfficeLinksHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
string userAgent = context.Request.Headers["User-Agent"].FirstOrDefault();
if (userAgent != null && MsUserAgentsRegex.IsMatch(userAgent))
{
// just return an empty response to the office agent
return;
}
await _next(context);
}
}
1.From excel/word point to http://example.com/from_excel.php
2.In "from_excel.php" redirect to page where you use session
<script>document.location.href = "http://example.com/page_with_session.php"; </script>
Here is how to workaround this with Java and Spring via a Filter:
/**
* To see why this is necessary, check out this page:
* https://support.microsoft.com/en-gb/help/899927.
*/
public class MicrosoftFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(final HttpServletRequest request,
final HttpServletResponse response,
final FilterChain filterChain) throws ServletException, IOException {
//Serve up a blank page to anything with a Microsoft Office user agent, forcing it to open the
//URL in a browser instead of trying to pre-fetch it, getting redirected to SSO, and losing
//the path of the original link.
if (!request.getHeader("User-Agent").contains("ms-office")) {
filterChain.doFilter(request, response);
}
}
}
/**
* Security configuration.
*/
#Configuration
public class SecurityConfiguration {
#Bean
public FilterRegistrationBean microsoftFilterRegistrationBean() {
FilterRegistrationBean<MicrosoftFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MicrosoftFilter());
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
}
We are seeing a problem that TWO Chrome tabs are opened when clicking an URL from MS Word, and the page to open has JavaScript redirection: window.location.href=blabla
By debugging from the servers side, we confirmed that there are requests sent from Office app, besides Chrome. This is so wierd.
But anyway, by checking the request header "User-Agent", and returning an empty page to Office apps, our TWO tabs issue got resolved. That's definitely the right thing to do!
Here is my solution for this in WordPress. Add this to functions.php in your theme or another plugin file.
This may be helpful if your system, like WP, sends logged out users to a login page with a redirect to the page they were trying to access. Word was sending users to this page, but then WP wasn't properly handling the case where a user was already logged in. This code checks if there is a current user and a redirect_to param passed. If so, it redirects to the redirect_to location.
function my_logged_in_redirect_to()
{
global $current_user;
if($current_user->ID && $_REQUEST['redirect_to'])
{
wp_redirect($_REQUEST['redirect_to']);
exit;
}
}
add_action('wp', 'my_logged_in_redirect_to');
Here's a VBA fix, for Excel. The same concept can be applied for Microsoft Word. Basically, rather than firing off the link from within Excel, the code executes the link from within a shell.
Here's the code:
Private Sub Worksheet_FollowHyperlink(ByVal objLink As Hyperlink)
Application.EnableEvents = False
Dim strAddress As String
strAddress = "explorer " & objLink.TextToDisplay
Dim dblReturn As Double
dblReturn = Shell(strAddress)
Application.EnableEvents = True
End Sub
For the Excel sheet that contains the links, right-click the sheet tab and click View Code. The VBA editor appears.
Paste the code into the window, and close the editor.
Modify each link in the page so it simply points back to the cell that it is in. To do so:
Right-click the link, and click Edit Hyperlink. An Edit Hyperlink window appears.
Click Place In This Document.
Click the sheet name.
For Type the cell reference, enter a cell reference (e.g. A4).
Click OK.
A couple of notes:
You will need to save the spreadsheet as a macro-enabled spreadsheet
(.xlsm). When users open the spreadsheet, they will be asked to
enable macros. If they answer No, the links will not work.
These instructions are based on Excel 2010. Presumably later versions are similar.
I can't believe they call this a feature.
However, here's a featurefix for Apache:
RewriteEngine On
# Send a 200 to MS Office so it just hands over control to the browser
# It does not use existing session cookies and would be redirected to the login page otherwise
# https://www.wimpyprogrammer.com/microsoft-office-link-pre-fetching-and-single-sign-on/
RewriteCond %{HTTP_USER_AGENT} ;\sms-office(\)|;)
RewriteRule .* - [R=200,L]
Might not be best performance wise, as the whole page gets sent instead of an empty response, but I did not want to add another Apache modules just for fixing such an idio^H^H^H^H feature.
NGINX solution below:
if ($http_user_agent ~* Word|Excel|PowerPoint|ms-office) {
return 200 '<html><head><meta http-equiv="refresh" content="0"/></head><body></body></html>';
}
You can put it in the server or location block.
Works like charm.
I had to solve this issue for an ASP.NET site but I only wanted to use javascript/ jQuery:
var isCoBrowse = ('<%= Session["user"].ToString().ToLower() %>' != '0');
if (isCoBrowse && window.location.href.indexOf('ReturnUrl=') >= 0 && window.location.href.indexOf('dllCheq') == -1) {
//redirect to the ReturnUrl & add dllCheq to the URI
var toRedirect = decodeURIComponent(gup('ReturnUrl', window.location.href)) + '&dllCheq';
window.location = toRedirect;
}
I got the gup function from:
How to get the value from the URL parameter?
Use fix provided by microsoft given link below. https://support.microsoft.com/en-us/kb/218153
And in ColdFusion / Lucee
<cfif cgi.HTTP_USER_AGENT contains "Excel" OR cgi.HTTP_USER_AGENT contains "ms-office">
<cfabort/>
</cfif>
I suspect this is a matter of how you are setting the cookie(s).
Due to the nature of how the web was created, example.com is not seen as the same domain as www.example.com; hence: you can be logged in at www.example.com and not logged in at example.com.
So in other words, check the URL in your word or excel file - is it the same domain as how you are logged in within your browser?
There are two fixes/solutions to this cookie inconsistency:
1. redirect anyone who tries to load your site without the www. to the same page with the www. (or vice versa), or
2. when you are setting the cookie, make sure to specify the domain argument as ".example.com". The leading dot indicates the cookie should be valid on all subdomains of that domain as well.
I suspect the reason the browser eventually recognizes it is because you probably eventually end up landing on a URL with the same domain structure as how you are logged in.
Hope this helps.