Unable to get proper referrer after AAD authentication in Application_PostAuthenticateRequest - asp.net-mvc-5

I have a scenario for redirection for certain users belonging to a DL. This is an AAD authenticated app.
If the user belongs to a certain DL, the application should load. Other wise the user should be redirected to another app("https://validreferrer"). And he has to click the link on the other app("https://validreferrer") to come to my app.
I am putting this check in post_authenticate event. I check the referrer . If this is a valid referrer("https://validreferrer") then proceed. If not check if the user belongs to the DL. If user doesnt belong to the DL then redirect the user to "https://validreferrer", else set the validated cookie and proceed with accessing the site.
If the user doesnot belong to the DL - The first time referrer comes as https://login.microsoftonline.com/72f98... as the request is authenticated from AD, so referrer becomes this and the referrer check fails. Then the user belonging to the DL check is performed and the user is redirected.
But, If the user first hits the other app - "https://validreferrer", and then clicks the link to come to my app, the referrer still comes as https://login.microsoftonline.com/72f98... instead of "https://validreferrer" and the user again is validated for belonging to the DL and then redirected. Now again if the user clicks on the external site("https://validreferrer") to come to my site, the referrer comes correctly.
How can I avoid this double redirection.
I tried to capture the referrer in session start, the first time the user comes via proper site("https://validreferrer"), but the referrer ("https://validreferrer") is populating only for Chrome. Not in the case of IE or Edge.
Is there any Request property which I can use to distinguish between the two calls? Or is there any other way to solve this problem?
Sample Code -
// This is capturing referrer only via chrome. Not working in IE and Edge
protected void Session_Start(object sender, EventArgs e)
{
if (Request.UrlReferrer != null &&
HttpContext.Current.Request.Cookies["UrlReferrer"] == null)
{
HttpContext.Current.Response.Cookies.Add(new HttpCookie("UrlReferrer", Request.UrlReferrer.ToString()));
}
}
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
if (this.Request.IsAuthenticated && HttpContext.Current.Request.Cookies["Validated"] == null )
{
bool userinDL= true;
//check if the referrer is correctly set do nothing
//else check if this is a ring zero user. If not, redirect
if ((Request.UrlReferrer == null || !Request.UrlReferrer.ToString().Contains("https://validreferrer")))
{
userinDL= CheckuserMembership();
if (!userinDL)
{
Response.Redirect("https://validreferrer");
}
else
{
HttpContext.Current.Response.Cookies.Add(new HttpCookie("Validated", "true"));
}
}
}
else
{
HttpContext.Current.Response.Cookies.Add(new HttpCookie("Validated", "true"));
}
}
}

AFAIK ,Request.UrlReferrer is unreliable approach as many proxy/firewall servers strip this
field from the request for security concerns .
If your requirement is just check whether the request is from another app , and you have access to "https://validreferrer" app and can modify the source , then a possible solution would be : you could try to append a querystring variable to the url link of another app , then in current app capture the querystring variable to distinguish the referrer. Related thread is for your reference .

Related

Suppress affinity cookie to force client to another Azure app node

I have a C# web application that uses a component (Progress Telerik Sitefinity CMS) that takes a long time (2 minutes) to initialize. A user who visits the site while in this stage, will be redirected to a page that polls the state every second, until initialization is complete. (This is built-in Sitefinity behavior).
I'm hosting my application within an Azure App Service. If I increase the number of instances (scale up), some of my users end up on the new node while it's still initializing. Problem is, due to the affinity cookie Azure adds, they stay on this node.
I want affinity, except when the site is initializing. In that case, I want to drop the cookie and poll. In that case I get assigned a random node, so an initialzed node is found within seconds.
Question is: how do I achieve this? Much of what happens is handled within Sitefinity, so I resorted to altering content in my global.asax. It doesn't work. I tried to put this in my global.asax.cs:
protected void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
var path = HttpContext.Current.Request.Url.AbsolutePath;
// "/sitefinity/status" is the page the client is redirected to
// "/appstatus" is used to poll initialization status
if (path == "/appstatus" || path == "/sitefinity/status")
{
// "ARRAffinity" is the Azure affinity cookie
Response.Cookies.Remove("ARRAffinity");
// just removing the cookie didn't work so i tried to override it
Response.Cookies.Add(new HttpCookie("ARRAffinity", "-") { HttpOnly = true });
// reportedly, this suppresses cookie adding by Azure
Response.Headers.Add("ARR-Disable-Session-Affinity", "true");
};
}
How do I force my client to a different node?
EDIT
I think I found (part of) the problem here.
First, "/" is requested. This returns a 302 redirect, but also the ARRAffinity cookie.
Then, "/sitefinity/status" is requested. Both the ARR-Disable-Session-Affinity and the cookie are stripped. This means, the cookie is not cleared on the client.
While polling, the client already has the cookie. So the user is never redirected to another node.
So that's might be the problem. Now to solve it...
EDIT
I followed Vesselin Vassilevs suggestion and added this to my sites configuration file:
<appSettings>
<add key="sf:AppStatusPageResponseCode" value="503" />
</appSettings>
But because I still incidentally reach the initializing node, I also suppressed the affinity cookie , by altering my global.asax.cs:
protected void Application_EndRequest(object sender, EventArgs e)
{
var httpCode = Response.StatusCode;
var isRedirectBackFromStatusPage = httpCode == 302 && Request.Url.AbsolutePath == "/sitefinity/status";
var isFinalSitefinityStatusPoll = httpCode == 404 && Request.Url.AbsolutePath == "/appstatus";
if (isRedirectBackFromStatusPage || isFinalSitefinityStatusPoll)
{
var cookie = Request.Cookies["ARRAffinity"];
if (cookie != null) Response.Cookies.Add(cookie);
return;
}
if (httpCode != 200 || !Response.ContentType.StartsWith("text/html"))
{
Response.Headers.Add("ARR-Disable-Session-Affinity", "true");
};
}
Why not disable the arr affinity cookie altogether?
Sitefinity backend works fine with no arr cookie and with multiple instances.
EDIT: We need to tell Azure that the site is not yet ready during the Sitefinity initialization. The problem with that is, that the appStatus page (shown by Sitefinity during init) returns status code 302 and even 200, which makes Azure to believe the site is running ok. I've wrote about this here: https://sitefinitydevelopment.com/blog/sitefinity's-application-status-page-can-cause-you-big-problems.html
Depending on your Sitefinity version, you can either implement the custom solution there (manually returning http code 503 during system restart) or set the following setting in the web.config (Sitefinity 9+)
<add key="sf:AppStatusPageResponseCode" value="503" />

Azures Arr Affinity response cookie

Once you consume and set Azure ARRAffinity response cookie and send it back to Azure, are you supposed to get it back with next response ?
I just completed bit of code what brings Azure response cookie all the way to browser, sets it as a session cookie and then I pass it back to Azure in request as a cookie. To my surprise I am not getting this cookie back, I see it only the first time. However I have a feeling this might be expected behaviour - I could find anything in the documentation. When I try to change the cookie to some made up value, the correct cookie is returned with the next response.
public class RestRequestWithAffinity : RestRequest
{
public RestRequestWithAffinity(string resource, IRequestWithAffinity request)
: base(resource)
{
if (!string.IsNullOrEmpty(request.AffinityValue))
{
AddCookie("ARRAffinity", request.AffinityValue);
}
}
}
var request = new RestRequestWithAffinity(url, feedRequest)
{
Method = Method.GET
};
// cookie doesn't come back when already in request
IRestResponse response = await _client.ExecuteTaskAsync(request);
Yes, you supposed to get it back with next response. You can take a look on the following link:
http://azure.microsoft.com/blog/2013/11/18/disabling-arrs-instance-affinity-in-windows-azure-web-sites/
if you create the cookie, than choose a different name and everything will be fine! ARRAffinity is a reserved name by the IIS ARR Module. And that's why you may see this misbehavior.
Also pay attention that if you use the public Microsoft provided domains (i.e. yourdomain.cloudapp.net or yourdomain.azurewebsites.net) you cannot set the cookie at top domain level - i.e. you cannot set cookie for the cloudapp.net domain or for the azurewebsites.net domain. You shall always use the full domain, including any subdomains to set the cookie - i.e. yourdomain.azurewebsites.net.
Take a read here for more information about that issue: https://publicsuffix.org/learn/

Logged in user can only access 1 page?

Using Orchard 1.6 Iv created a new role 'FactoryWorker'. When this user logs in from the front end I want them to be navigated to one page only.
OrchardLocal/System/ManufacturedProducts
I have set this page to be a print screen of the order details so the factory worker will know what products to get ready for ship out & they wont be able to navigate as no menu appears, but also need the other pages blocked incase the user decides to enter the URL of a page they arnt allowed access to.
This is the only page I want this particular user to be able to access(after they login), and I have added a logout button, which logs out the user and returns them to the home page.
So iv been looking through editing a role, with permissions and content etc...but this all seems to be applying to forms and content in general. where the user can access any content type etc...
So can someone advise me on how to do this?
thanks for any replies
UPDATE
I forgot to mention that this is not a content type, item or part I am talking about.
I have created my own controller & View & VM which is accessible from the dash board (using the AdminMenu, which brings the admin user to OrchardLocal/System/ManufacturedProducts)
I have looked at Orchard.ContentPermissions Feature but it only seems to allow me to 1)Grant permissions for others or 2)Grant permission for own content
any ideas?
You can use a Request Filter, (I do not know if it is the best way) :
FilterProvider – defines the filter applied to each request. Resembles the way default ASP.NET MVC action filters work with the difference that it’s not an attribute. All FilterProvider objects are injected into the request pipeline and are applied to all requests (so you need to check if the current request is suitable for your filter at the beginning of an appropriate method).
From : http://www.szmyd.com.pl/blog/most-useful-orchard-extension-points
So you could implement something like this
public class Filter : FilterProvider, IAuthorizationFilter {
private readonly IAuthenticationService _authenticationService;
public Filter(IAuthenticationService authenticationService) {
_authenticationService = authenticationService;
}
public void OnAuthorization(AuthorizationContext filterContext) {
//If route is the restricted one
if (filterContext.HttpContext.Request.Url.AbsoluteUri.Contains("OrchardLocal/System/ManufacturedProducts")) {
//Get the logged user
IUser loggedUser = _authenticationService.GetAuthenticatedUser();
if (loggedUser == null)
return filterContext.Result = new HttpUnauthorizedResult();
//Get the Roles
var roles = loggedUser.As<IUserRoles>().Roles;
if (!roles.Contains("FactoryUser")) {
//User is not authorized
return filterContext.Result = new HttpUnauthorizedResult();
}
}
}
}
Note: Untested code!
EDIT: Also you could invert the logic and check if the logged user has the role 'FactoryUser' and restrict its access to every page except the one they should see.
Your module can create a new permission (look at one of the permissions.cs files for examples), then create a role that has only that permission. Have your controller action check that permission (again, many examples found by finding usage of the permissions defined in one of the permissions.cs).
You can use the Content Permissions module. Using this module you can attach a content item permission part to a content type. This part allows you to choose which roles can see the content when you create it.

WebClient with credentials still not downloading file

I am trying to download files from a website with username/password. You need to pay for a registered account in order to download files - which we have done. I am attempting to pass in the username/password and download a file as follows:
if (docUrl != null)
{
if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
this.WebClientInstance.Credentials = new NetworkCredential(username, password);
fileData = this.WebClientInstance.DownloadData(docUrl);
this.WebClientInstance.Dispose();
isDataDownloaded = true;
}
WebClientInstance is a System.Net.WebClient. I debugged and verified that it is hitting the line to set credentials. Instead of downloading the PDF, I end up with an HTML page that prompts me to log in to get access to the file. I have verified that the username/password is correct. I use the same credentials to scrape the website with WatiN.
Is there something else that I'm supposed to be doing here?
UPDATE
Okay, I've done some sniffing around and found some useful info on this issue. I still haven't gotten it to work, but I think I'm closer. First, you need to create a cookie aware WebClient that extends the WebClient class, as follows:
public class CookiesAwareWebClient : WebClient
{
public CookieContainer CookieContainer { get; private set; }
public CookiesAwareWebClient()
{
this.CookieContainer = new CookieContainer();
}
protected override WebRequest GetWebRequest(Uri address)
{
var webRequest = base.GetWebRequest(address);
if (webRequest is HttpWebRequest)
(webRequest as HttpWebRequest).CookieContainer = this.CookieContainer;
return webRequest;
}
}
Next is to use the WebClient.UploadValues() method to upload the login info to the target website. The full process of authenticating and downloading the target resource is as follows:
using (var webClient = new CookiesAwareWebClient())
{
var postData = new NameValueCollection()
{
{ "userId", username },
{ "password", password }
};
webClient.UploadValues(docUrl, postData);
fileData = webClient.DownloadData(docUrl);
}
I was wrong about the site using forms auth. It is a JSP website and uses a JSESSIONID. I have verified that I am getting a cookie back with what appears to be a valid 32-byte JSESSIONID value.
However, when I call WebClient.DownloadData() it is still only returning the redirected login page. I've tried to fix this by setting the AllowAutoRedirect property on the HttpWebRequest to false, but then it returns 0 bytes.
Is there something else that I need to do so it won't redirect and will take me to the resource once I have authenticated?
(Answered in a question edit. Converted to a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
The OP wrote:
Solved. So the problem was between my ears. I was passing in the URL for the secure resource to the .UploadValues() method, knowing that it would redirect to the login page. However, I really needed to pass in the URL from the login form (where it goes upon submitting) - not the login page itself. Once I did that, it worked correctly. I think I'm going to go find a career in food service now.
LINKS
There were already a few questions posted on SO that addressed this issue. I just didn't know what I was looking for at first so I didn't see those... Anywhere here are a couple good resources that I came across when working on this issue:
how to maintaine cookies in between two Url's in asp.net
Trying to get authentication cookie(s) using HttpWebRequest

Redirect loop when liferay integrated with OpenSSO

My softwares are:
Liferay 6.0.6 with Tomocat 6.0.29, OpenSSO 9.5.2_RC1 Build 563 with tomcat 6.0.35, CentOS 6.2 Operating system
Setup:
I have setup both liferay and opensso on the same CenOS machine, making sure that both of its tomcat run on very different port, I have installed and configured OpenSSO with Liferay as per the guidelines availaible on liferay forums
Problem:
when i hit my application URL i get redirected to Opensso login page which is what i want, when i login with proper authentication details it trys to redirect to my application which is exactly how it should behave, however this redirect goes in a loop and i don't see my application dashboard. The conclusion i come to is that the redirect is trying to authenticate in liferay but somehow it does not get what it is looking for and goes back to opensso and this repeats infinitely. I can find similar issues been reported here. Unfortunetly, it did not work.
Later i decided to debug the liferay code and i put a break point on com.liferay.portal.servlet.filters.sso.opensso.OpenSSOUtil and com.liferay.portal.servlet.filters.sso.opensso.OpenSSOFilter. The way i understand this code is written is it first goes to the OpenSSOUtil.processFilter() method which get's the openSSO setting information that i have configured on liferay and later checks if it is authenticated by calling the method OpenSSOUtil.isAuthenticated(). This particular implementation basically reads the cookie information sent and tries to set the cookie property on liferay by calling the method OpenSSOUtil._setCookieProperty(). This is where it fails, it tries to read the cookie with name [iPlanetDirectoryPro] from the liferay class com.liferay.util.CookieUtil using the HttpServletRequest object but all it get's a NULL. this value set's the authenticate status to false and hence the loop executes.
Following is the code from class com.liferay.util.CookieUtil
public static String get(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
if (cookies == null) {
return null;
}
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies;
String cookieName = GetterUtil.getString(cookie.getName());
if (cookieName.equalsIgnoreCase(name)) {
return cookie.getValue();
}
}
return null;
}
Can anyone please let me know why liferay is not able to find the cookie that opensso sent. If its related to Opensso setting about enable cookie value, then i have done that already which is here
In OpenSSO go to: Configuration -> Servers and Sites -> -> Security -> Cookie -> check Encode Cookie Value (set to Yes)
What works:
when this loop is executing i open another tab and login to my application explicitly, from my application when i signout it get's signout from opensso also. This is strange to me.
For more information, while this redirect loop happens, following URL's give me these set of information
http://opensso.ple.com:9090/openam/identity/getCookieNameForToken
string=iPlanetDirectoryPro
http://opensso.ple.com:9090/openam/identity/isTokenValid
boolean=true

Resources