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" />
Related
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 .
I've added Azure Active Directory Authentication to my function app, but as soon as I set "Action to take when request is not authenticated" to "Login with Azure Active Directory", the development interface for the function app yields this message:
Error:
We are unable to reach your function app. Your app could be having a temporary issue or may be failing to start. You can check logs or try again in a couple of minutes.
Session Id: 23a5880ec94743f5a9d3ac705515b294
Timestamp: 2016-11-16T08:36:54.242Z
Presumably adding the authentication requirement breaks access to the function app in some fashion... though I am able to make changes in the code editor, and they do take effect, I no longer see updates in the log panel: no compilation output messages, for example.
Does anyone know a work-around for this?
So far, I've tried just leaving the auth option to "Allow anonymous requests (no action)" and using this following code:
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
var user = "Anonymous";
var claimsPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal;
if (claimsPrincipal != null && claimsPrincipal.Identity.IsAuthenticated)
{
user = claimsPrincipal.Identity.Name;
log.Info($"Hello {user}");
}
return req.CreateResponse(HttpStatusCode.OK, "Hello " + user);
}
However, this (rightly) doesn't redirect to the authentication provider... I would prefer to have the app take care of all that gunge for me, but if doing so means I can't see compilation messages / log messages, it makes it pretty hard to see what's going on.
Nathan,
Unfortunately, this is a limitation at the moment and we're tracking it here: https://github.com/projectkudu/AzureFunctionsPortal/issues/794
Your approach, to allow anonymous and validate in the function is what we recommend at the moment. To extend your workaround, you can add the following code to initiate a login redirect when you detect an anonymous user (the code below assumes you are using AAD).
else
{
log.Info("Received an anonymous request! Redirecting...");
var res = req.CreateResponse(HttpStatusCode.Redirect);
res.Headers.Location = new Uri(req.RequestUri, $"/.auth/login/aad?post_login_redirect_uri={req.RequestUri.AbsolutePath}&token_mode=session");
return res;
}
We understand that isn't ideal and appreciate your patience while we work to improve this.
Thanks!
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/
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
We have developed a portal using the code from the customer portal. But we haven't used the customer portal solution on the CRM server. Everything is working fine except caching prevents updates to show on the portal.
In CRM 4 I used this solution http://pogo69.wordpress.com/2010/11/05/caching-revisited-crm-4-0-sdk-advanced-developer-extensions/. But this doesn't work in CRM 2011 because Microsoft.Xrm.Client.Caching is different. How do I clear the cache for 2011?
Any help or thoughts would be greatly appreciated.
Thanks!
Make sure that you have a cache invalidation URL configured. The PRM portal has a service that invalidates the cache with any update/create event (managed by a plugin registered on all entities).
Go To Settings -> Web Notification URL, either update the URL to the Cache.axd file or create a new entry.
If this is all in place, I would make sure that the plugin that takes care of calling the cache invalidation is working/registered as it should be.
Hope that helps
See the following article for more details on the Web Notification URL.
Set Up Cache Invalidation to Refresh Changes on the Website
I found that removing the preloadcache parameter & value from URLs bypasses CRM's cache. Not ideal but it worked. Your mileage may vary...
<script type="text/javascript">
bypassCrmPreloadCache(); // DE_WR_15706 Bypass 30sec cache to get latest version #
function bypassCrmPreloadCache() {
var ParentURL = window.parent.location.href;
var nStartPreloadcache = ParentURL.indexOf("preloadcache");
if (nStartPreloadcache > 0) {
// Parent URL is cached
var nEnd = ParentURL.indexOf("&", nStartPreloadcache);
if (nEnd == -1) { // Special case: no ampersand => preloadcache is last argument.
nEnd = ParentURL.length; // End of URL is end of preloadcache's value.
}
var strPreloadCacheParamAndValue = ParentURL.substr(nStartPreloadcache, 1 + nEnd - nStartPreloadcache);
// Remove preloadcache-parameter from URL
ParentURL = ParentURL.replace(strPreloadCacheParamAndValue, "");
if (ParentURL.charAt(ParentURL.length-1) == '&')
ParentURL = ParentURL.slice(0, -1); // Ensure URL not terminated by an unnecessary '&'.
// Load URL in parent Window, bypassing the cache
window.open(ParentURL, "_parent");
}
}
</script>