How to secure a link to be downloaded only by specific users? - c#

I have the following case , and i wanna to ask what's the best solution ?
I have a specific file i wanna specific users(according to some permissions) to download this file .
so i show this file only for the authorized users, but what if someone(not authorized) recognize the file link(knows the link url) and download it !!
How to allow this file to be downloaded only by the authorized users .

Put the file into a directory which is not served by the web server and implement a handler for the "virtual url" which in turn checks for permissions etc. - a possible way would be an ASHX handler (see here for sample code and here for MSDN reference).

My answer would be:
Dont use direct links!
Create a Download.aspx and have the links for downloads post to Download.aspx?params
The params should be encrypted/hashed containing the filepath+name to download and session_id.
On Download.aspx validate that the session_id is valid and active on the browser.
This should allow you to allow downloads to the correct folks only:
If you add to the params also the user_id or the user_type you can deny/permit download on the onLoad of Download.aspx

The following link provides details on Authorization Rules in iis and asp.net, it seems pertinent to your question.
Firstly you want to ensure ASP.NET handles request for your specified file type. You can configure this in IIS (see link below).
Secondly, you will then need to update your web.config to deny anonymous users from reaching your url, providing that you are using rolemanager :
<roleManager defaultProvider="SqlProvider" enabled="true" cacheRolesInCookie="false"
cookieName=".ASPROLES" cookieTimeout="30" cookiePath="/" cookieRequireSSL="false"
cookieSlidingExpiration="true" cookieProtection="All">
<providers>
<add name="SqlProvider" type="System.Web.Security.SqlRoleProvider"
connectionStringName="membership" applicationName="yourApplication"/>
</providers>
</roleManager>
<location path="path/file.extension">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
IIS 6 ignores Web.config authorization settings

The best way would be to add httphandlers and check whether the requested file have special permissions or not, an example for what I said would be:
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class MyHTTPHandler : IHttpHandler, IRequiresSessionState
{
string myFile;
public bool IsReusable {
get { return true; }
}
public void ProcessRequest(System.Web.HttpContext context)
{
myFile = context.Request.Path;
if (myFile.ToLower().Contains("members private files") || myFile.ToLower().Contains("members%20private%20files")) {
if (System.Web.HttpContext.Current.Session["Login"] == null) {
context.Response.Redirect("~/NotAuthorized.aspx");
} else {
if (myFile.ToLower().Contains("privatefiles")) {
StartDownload(context, myFile);
} else {
if (IsMemberAuthoraizedToDownloadFile(context)) {
StartDownload(context, myFile);
} else {
context.Response.Redirect("~/NotAuthorized.aspx");
}
}
}
} else {
StartDownload(context, myFile);
}
}
private void StartDownload(HttpContext context, string downloadFile)
{
context.Response.Buffer = true;
context.Response.Clear();
context.Response.AddHeader("content-disposition", "attachment; filename=" + downloadFile);
context.Response.ContentType = "application/pdf";
context.Response.WriteFile(downloadFile);
}
// just my own function to check if user is valid
private bool IsMemberAuthoraizedToDownloadFile(HttpContext context)
{
GroupMembersControl MyGroupMemberc = new GroupMembersControl();
System.Collections.Generic.List<GroupMembers> MemberGroupsL = MyGroupMemberc.GetMemberGroups(System.Web.HttpContext.Current.Session["Login"]);
MemberGroupControl MyGroupC = new MemberGroupControl();
MemberGroup MyGroup = default(MemberGroup);
foreach (GroupMembers groupmember in MemberGroupsL) {
MyGroup = MyGroupC.GetMemberGroup(groupmember.GroupID);
if (myFile.ToLower().Contains(MyGroup.Name.ToLower)) {
return true;
}
}
return false;
}
}

Related

Unable to set path ASP.NET_sessionid cookie

Hi I needed a serious help
i have tried everything but am not able to change the path of ASP.NET_sessionid cookie
its path is always set to "/" , i want to set it to a folder or directory
this issue need to be solved as it was raised by app security team
have tried , iis rewrite rule , custom session id manager
any help much aprreciated
As #iamdln said, you need to create your own SessionIDManager but you also need to config it on your Web.config.
It worked for me.
Your SessionIdManager class,
public class MySessionIDManager : SessionIDManager, ISessionIDManager
{
void ISessionIDManager.SaveSessionID(HttpContext context, string id, out bool redirected, out bool cookieAdded)
{
base.SaveSessionID(context, id, out redirected, out cookieAdded);
if (cookieAdded)
{
var name = "ASP.NET_SessionId";
var cookie = context.Response.Cookies[name];
cookie.Path = "/yourPath";
}
}
}
Web.config, replace namespace and class for yours.
This goes inside <system.web> section.
<sessionState sessionIDManagerType = "Namespace.MySessionIDManager"></sessionState>
Original links:
ASP.NET Forum - Explains how to override path
StackOverFlow - Explains how to override domain
Both are quite similar anyway.
You will need to create your own SessionIDManager which is inherited from ISessionIDManager and change the cookie.Path to whatever you want.
static HttpCookie CreateSessionCookie(String id) {
HttpCookie cookie;
cookie = new HttpCookie(Config.CookieName, id);
cookie.Path = "/";
cookie.SameSite = Config.CookieSameSite;
// VSWhidbey 414687 Use HttpOnly to prevent client side script manipulation of cookie
cookie.HttpOnly = true;
return cookie;
}

HttpPostedFileBase not accepting file

I have a API method that needs to accept files. I am using Postman chrome plugin to call the api and attach a file.
I added API functionality to my MVC project after creating just an initial MVC web site. don't know if I missed some config but my other API calls work just this one not getting any files.
Here's the code
[Route("~/api/mediaitems/{token}/{eventId}")]
public async Task<HttpResponseMessage> MediaItems(string token, int eventId, HttpPostedFileBase upload)
{
if (upload.ContentLength > 0)
{
string _targetFolder = HttpContext.Current.Server.MapPath(ConfigurationManager.AppSettings["FilePath"]);
string _targetPath = Path.Combine(_targetFolder, Guid.NewGuid() + Path.GetFileName(upload.FileName));
upload.SaveAs(_targetPath);
var mediaItem = new MediaItem
{
MediaFilePath = _targetPath,
FileType = upload.FileName,
EventId = eventId,
CreatedDate = DateTime.Now.Date
};
//Save mediaItem
_repo.SaveMediaItem(mediaItem);
return Request.CreateResponse(HttpStatusCode.Created);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
http://localhost:24727/api/mediaitems/12341254234/1
that's the URL and then I attach the .jpg to the postman body.
When I run the api request it never has the file therefore it can never save.
You can start with something like this:
[Route("~/api/mediaitems/{token}/{eventId}")]
public async Task<HttpResponseMessage> MediaItems(string token, int eventId)
{
byte[] data = await this.Request.Content.ReadAsByteArrayAsync();
//data will contain the file content, you can save it here
return Request.CreateResponse(HttpStatusCode.Created);
}
Notice how I removed the HttpPostedFileBase upload parameter.
You can do some validation based on this.Request.Content if you want. For example, you might want to check the content length.
Is that an ApiController ? There is not HttpPostedFile in Web API.
You have to use another approach: How to post file to ASP.NET Web Api 2
Maybe help this...Add following to web.config. You must setup max accepted size of file in bytes: 15728640 = 15MB
<configuration>
<location path="Custommer/edit">
<system.web>
<httpRuntime maxRequestLength="15728640"/>
</system.web>
</location>
</configuration>

asp.net custom user authentication with master page

I am working on a small Inventory management system. I have almost created it on my local machine. It works as it is designed. I just moved it to my hosting account and now I am facing some problems.
1, After login, when user goes to different pages, open and closes forms, after some time it redirects to login page. I don't know why. Seems like some sort of exception was occurred or the session had gone empty. How to handle this condition?
2, Is this the right way to keep a check on the user login using a check in Page_Load event of master page?
My site uses a master page that has separate layout for top navigation menu and bottom body area. When the first time user lands on the site it login into the system and upon successful login I store his information in session. I am heavily using Session in all pages for ADD, DELETE, UPDATE purposes. When an add record is performed, I pass success of failure message in session to show after post back. Code behind of login page is given below:
protected void loginForm_OnAuthenticate(object sender, AuthenticateEventArgs e)
{
string error = "";
sMethodName = "loginForm_OnAuthenticate";
_objLoginBLL = new LoginBLL();
int iRetVal = _objLoginBLL.ValidateUser(loginForm.UserName, loginForm.Password, ref error);
if (iRetVal >= 0)
{
Session.Clear(); //Remove all stored Session variables.
Session[Constant.Session.LOGGED_IN_DATETIME] = DateTime.Now.ToString("yyyyMMddHHmmssfff");
Session[Constant.Session.LOGIN_USERNAME] = loginForm.UserName;
Session[Constant.Session.LOGIN_USER_ID] = iRetVal;
Session[Constant.Session.LOGIN_COMPANY] = ddlCompanies.SelectedValue;
Session[Constant.Session.LOGIN_FISCAL_YEAR] = ddlFiscalYear.SelectedValue;
Session[Constant.Session.IS_DIRECT_ACCESS] = "NO";
FormsAuthentication.RedirectFromLoginPage(loginForm.UserName, loginForm.RememberMeSet);
}
else
{
Logger.Log("User validation failed.", sClassName, sMethodName, DEBUG);
switch (iRetVal)
{
case -1:
loginForm.FailureText = Constant.Messages.INCORRECT_USER_OR_PASSWORD;
loginForm.Focus();
break;
case -2:
loginForm.FailureText = Constant.Messages.ACCOUNT_LOCKED;
loginForm.Focus();
break;
//case -3:
//TODO: Account doesn't exists
default:
var randToken = new Random().Next(1000);
Session[Constant.Session.TOKEN] = randToken;
var myHashtable = new Hashtable
{
{Constant.Session.TOKEN, randToken},
{Constant.Fields.ERROR_KEY, iRetVal}
};
Response.Redirect(WebFunctions.CreateQueryString(Constant.Urls.Error, myHashtable));
break;
}
}
}
I am continuously checking if the session doesn't contain any user id then redirect it to the login page. The code behind of my master page is given below:
protected void Page_Load(object sender, EventArgs e)
{
if (Session[Constant.Session.LOGIN_USER_ID] == null)
{
FormsAuthentication.RedirectToLoginPage();
return;
}
CheckDBConnection();
Initialize();
}
Any help or tips will be appreciated.
You can view the site here: www.paracha.net (I can share guest account credentials in private message if anyone is interested)
First of all, keep in mind that the session cookie is not encrypted, so you should not be using the session to store any confidential information.
Secondly, you should not be checking the authentication on every Page_Load. Instead, you should configure the page access in web.config:
<configuration>
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
This will protect all your pages so only authenticated (i.e. logged in) users will see the page, while all others will be redirected to the login page.
If you have some pages (e.g. a splash page) or folders (e.g. the images folder) that you want them to be accessible to all users, then add a section for each page or folder:
<configuration>
<location path="splash.aspx">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
<location path="images">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
</configuration>
In order for this to work, you should be using forms authentication. Here are the settings in web.config:
<configuration>
<system.web>
<authentication mode="Forms">
<forms
name=".YOURNAME_AUTH"
loginUrl="login"
defaultUrl="/"
protection="All"
timeout="30"
path="/"
requireSSL="true"
slidingExpiration="true"
cookieless="UseCookies"
domain=""
enableCrossAppRedirects="false">
</forms>
</authentication>
</system.web>
</configuration>
Obviously, you will need a login.aspx page, and when you click the Log in button, you need to authenticate the user like this:
protected void btnLogIn_Click(object sender, EventArgs e) {
string Username = txtUsername.Text;
string Password = txtPassword.Text;
try {
if (ValidateUser(Username, Password)) {
FormsAuthentication.RedirectFromLoginPage(Username, false);
}
else {
lblMessage.Text = "Incorrect Credentials.";
lblMessage.ForeColor = Color.Red;
}
}
catch {
lblMessage.Text = "Login Failed.";
lblMessage.ForeColor = Color.Red;
}
}
The function ValidateUser() can do anything you want for authentication. You can validate the credentials against your database if you like.
If you use FormsAuthentication, you do not need to check Session[Constant.Session.LOGIN_USER_ID] manually. It will redirect to Login page automatically for which you can configure in web.config.
Another thought
It is not directly related to your question. It is just an alternative approach.
Instead of creating multiple session states, you can create custom Context to keep track of the current logged-in user's information
E.g. You can store Company and Fiscal Year properties inside MyUser class.
void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null &&
HttpContext.Current.User.Identity.IsAuthenticated)
{
MyContext.Current.MyUser =
YOUR_BLL.GetUserByUsername(HttpContext.Current.User.Identity.Name);
}
}
public class MyContext
{
private MyUser _myUser;
public static MyContext Current
{
get
{
if (HttpContext.Current.Items["MyContext"] == null)
{
MyContext context = new MyContext();
HttpContext.Current.Items.Add("MyContext", context);
return context;
}
return (MyContext) HttpContext.Current.Items["MyContext"];
}
}
public MyUser MyUser
{
get { return _myUser; }
set { _myUser = value; }
}
}
}
Addition
C# is strongly type language, so you should not encode the variable name with type of variable. E.g. objLoginBLL and iRetVal. Please read C# Design Guideline or Essential C# 6.0 (Page 7).

Process html files with HttpModule to catch 404 errors on IIS7

I am having another problem with my HttpModule that handles exceptions. (cfr. my previous post: Custom HttpModule for IIS 7 for integrated)
All works well, but only for aspx pages.
The main reason we wanted to use this HttpModule is to handle 404 exceptions that occur when someone tries to go to a html page that doesn't exist. But my HttpModule only works for .aspx pages and it isn't triggered when a html file doesn't exist.
This is the configuration that I have set up in my web.conf file:
<system.webServer>
<modules>
<add name="AspExceptionHandler"
type="Company.Exceptions.AspExceptionHandler, Company.Exceptions"
preCondition="managedHandler" />
</modules>
</system.webServer>
I have also tried adding 'runAllManagedModulesForAllRequests="true"' to the module node, and 'preCondition="managedHandler"' to the "add" node, but this also didn't work.
I have set the Application pool on which my web application is running to "Integrated" mode, because I found this a lot on google.
Is there another way to make my HttpModule handle exceptions that occur when visiting a non existing html page?
Thanks!
Having done a little research based on the answer of Alexander, I implemented IHttpHandler in my AspExceptionHandler as well.
My class now looks like:
public class AspExceptionHandler : IHttpModule, IHttpHandler
{
public void Dispose() { }
public void Init(HttpApplication context)
{
context.Error += new EventHandler(ErrorHandler);
}
private void ErrorHandler(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
try
{
// Gather information
Exception currentException = application.Server.GetLastError(); ;
String errorPage = "http://companywebsite.be/error.aspx";
HttpException httpException = currentException as HttpException;
if (httpException == null || httpException.GetHttpCode() != 404)
{
application.Server.Transfer(errorPage, true);
}
//The error is a 404
else
{
// Continue
application.Server.ClearError();
String shouldMail404 = true;
//Try and redirect to the proper page.
String requestedFile = application.Request.Url.AbsolutePath.Trim('/').Split('/').Last();
// Redirect if required
String redirectURL = getRedirectURL(requestedFile.Trim('/'));
if (!String.IsNullOrEmpty(redirectURL))
{
//Redirect to the proper URL
}
//If we can't redirect properly, we set the statusCode to 404.
else
{
//Report the 404
}
}
}
catch (Exception ex)
{
ExceptionCatcher.FillWebException(HttpContext.Current, ref ex);
ExceptionCatcher.CatchException(ex);
}
}
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
if (!File.Exists(context.Request.PhysicalPath))
{
throw new HttpException(404, String.Format("The file {0} does not exist", context.Request.PhysicalPath));
}
else
{
context.Response.TransmitFile(context.Request.PhysicalPath);
}
}
}
In the ProcessRequest method (required by the IHttpHandler) I check if the file exists.
If it does not exist, I throw an HttpException that is catched by the HttpModule part of my class.
The system.webServer node in my web.config now looks like this:
<modules runAllManagedModulesForAllRequests="true">
<add name="AspExceptionHandler" type="Company.Exceptions.AspExceptionHandler, Company.Exceptions" preCondition="managedHandler" />
</modules>
<handlers>
<add name="AspExceptionHandler" type="Company.Exceptions.AspExceptionHandler, Company.Exceptions" verb="*" path="*.html" />
</handlers>
I found the answer in in this post: HttpHandler fire only if file doesn't exist
you can try this
<httpHandlers>
<add type="Company.Exceptions.AspExceptionHandler, Company.Exceptions" verb="*" path="*"/>
....
</httpHandlers>
this help you to catch all request, but if reqest is made for existing page you need to pass it to standart handler

Facebook C# SDK - Problem with Iframe app and Safari Cookies

I'm building an Iframe canvas application for Facebook. I'm not using the Javascript SDK.
This is the code I'm using, and it works well in all browsers except for Safari.
protected FacebookApp app;
protected CanvasAuthorizer cauth;
Response.AddHeader("p3p", "CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"");
app = new FacebookApp();
cauth = new CanvasAuthorizer(app);
if (!cauth.IsAuthorized())
{
myAuth auth = new myAuth();
myAuth.Authorize(app, Request, Response, perms);
}
if (cauth.Authorize())
{
// Do my app stuff here
}
public class myAuth
{
public static void Authorize(FacebookApp fbApp, System.Web.HttpRequest request, System.Web.HttpResponse response, string perms)
{
Authorize(fbApp, request, response, perms, null);
}
public static void Authorize(FacebookApp fbApp, System.Web.HttpRequest request, System.Web.HttpResponse response, string perms, string redirectUrl)
{
Uri url = fbApp.GetLoginUrl();
NameValueCollection nvc = System.Web.HttpUtility.ParseQueryString(url.Query);
if (!string.IsNullOrEmpty(perms))
nvc.Add("req_perms", perms);
if (!string.IsNullOrEmpty(redirectUrl))
nvc["next"] = GetAppRelativeUrl(redirectUrl);
else if (request.QueryString.Count > 0)
nvc["next"] = GetAppRelativeUrl(request.Path.Replace(request.ApplicationPath, string.Empty).Replace(request.ApplicationPath.ToLower(), string.Empty) + "?" + request.QueryString);
else
nvc["next"] = GetAppRelativeUrl(request.Path.Replace(request.ApplicationPath, string.Empty).Replace(request.ApplicationPath.ToLower(), string.Empty));
UriBuilder ub = new UriBuilder(url);
ub.Query = nvc.ToString();
string content = CanvasUrlBuilder.GetCanvasRedirectHtml(ub.Uri);
response.ContentType = "text/html";
response.Write(content);
response.End();
}
public static string GetAppRelativeUrl(string url)
{
return CanvasSettings.Current.CanvasPageUrl.ToString();
}
}
I read about Safari not allowing third party cookies, and I figure that's where the problem lies. My question is wheter there's a way to handle this using the SDK, or what my options are.
Regards,
Anders Pettersson
I've had some problems with Safari changing the case of data sent in HTTP headers... make sure any parsing/comparing you are doing is case insensitive.
See here: Facebook Iframe App with multiple pages in Safari Session Variables not persisting
here I am getting same problem but now I got solution for safari..
Just change validation mode in web.config
<system.web>
<pages enableViewState="true" validateRequest="false" />
<httpRuntime requestValidationMode="2.0"/>
<!--
Enable this code if you get still problem
<sessionState cookieless="true" regenerateExpiredSessionId="true" />-->

Categories

Resources