Is it possible to style the Windows Identity Foundation postback page?
This is the page that comes across as blank after you successfully login and the url is similar to https://sts.address.com/?wa=wsignin1.0&wtrealm=https....
I read the following article, http://www.paraesthesia.com/archive/2011/01/31.aspx, which led me to using dotPeek on the Microsoft.IdentityModel assembly. Which shows me that all the ProcessSignInResponse message does, is the following:
public static void ProcessSignInResponse(SignInResponseMessage signInResponseMessage, HttpResponse httpResponse)
{
if (signInResponseMessage == null)
throw DiagnosticUtil.ExceptionUtil.ThrowHelperArgumentNull("signInResponseMessage");
else if (httpResponse == null)
{
throw DiagnosticUtil.ExceptionUtil.ThrowHelperArgumentNull("httpResponse");
}
else
{
signInResponseMessage.Write(httpResponse.Output);
httpResponse.Flush();
httpResponse.End();
}
}
The signInResponseMessage.Write method does the following:
public override void Write(TextWriter writer)
{
if (writer == null)
{
throw DiagnosticUtil.ExceptionUtil.ThrowHelperArgumentNull("writer");
}
else
{
this.Validate();
writer.Write(this.WriteFormPost());
}
}
As you can see, in essence, all that is performed, is to write the content of WriteFormPost to the response stream.
So I am, as we speak, changing my "ProcessSignIn" method to return the HTML to be output, instead of calling FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse.
So, I have changed my method essentially from this:
public static void ProcessSignIn(SignInRequestMessage signInRequest, HttpResponse httpResponse)
{
FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse(signInResponseMessage, httpResponse);
}
To:
public static string ProcessSignIn(SignInRequestMessage signInRequest)
{
return signInResponseMessage.WriteFormPost();
}
Of course, the SignInResponseMessage should have provided a cleaner method of returning just the "main" content of what you want to write to your form post, but getting the
HTML form as a string at least makes it easier to modify the result before returning it to the client with Response.Write(result).
I don't know if this is a documented feature, but I will suggest the following as a jumping-off point:
If your code looks anything at all like mine, you have a line of code that looks like:
FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse(responseMessage, HttpContext.Current.Response)
The second parameter to ProcesssignInResponse is an HttpResponse object. I tried, unsuccessfully, to find an answer to your question, by trying to pass in a custom HttpResponse message in order to capture the output so we can manipulate it however you like:
Dim myStringbuilder As New StringBuilder
Dim myStringWriter As New IO.StringWriter(myStringbuilder)
Dim myResponse As New Web.HttpResponse(myStringWriter)
If you pass in myResponse to ProcessSignInResponse, the following exception is thrown:
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.HttpResponse.End()
at Microsoft.IdentityModel.Web.FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse(SignInResponseMessage signInResponseMessage, HttpResponse httpResponse)
at Logon_App.LCLoginBase.issueTokenAndRedirect(logonParamsStruct& logonParams) in C:\Logon App\Logon App\Code\LCLogin\LCLoginBase.vb:line xxx
Related
I'm trying to add custom http headers to a webview client (for authorization).
It seems to work in some cases, I'am able to login to a webpage without entering username and password, and I get redirected to another page. But when the page is calling other resources to get elements populated with data an error is thrown and OnReceivedHttpError is invoked. The error I'm getting is 401 unauthorized and when i look through the headers on the IWebResourceRequest i can't see the authorization headers at all.
Am I missing something or have anyone had same problems ?
Using Xamarin Forms 2.3.3.180 and targeting API 21 (Android 5.0 Lollipop), compile with Android 7.1 Nougat.
I've tried in postman to add headers to request and it works perfectly.
Renderer:
public class MyWebViewRenderer : WebViewRenderer
{
private MyWebViewClient _webViewClient;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if(_webViewClient == null)
_webViewClient = new MyWebViewClient();
Control.SetWebViewClient(_webViewClient);
Control.LongClickable = false;
Control.HapticFeedbackEnabled = false;
Control.Settings.JavaScriptEnabled = true;
var data = Encoding.GetEncoding("ISO-8859-1").GetBytes("username:password");
var base64string = Base64.EncodeToString(data, Base64Flags.NoWrap);
var headers = new Dictionary<string, string>();
headers.Add("Authorization", $"Basic {base64string}")
Control.LoadUrl(Control.Url, headers);
}
}
WebViewClient:
public override bool ShouldOverrideUrlLoading(WebView view, string url)
{
WebView.SetWebContentsDebuggingEnabled(true);
var data = Encoding.GetEncoding("ISO-8859-1").GetBytes("username:password");
var base64string = Base64.EncodeToString(data, Base64Flags.NoWrap);
var headers = new Dictionary<string, string>();
headers.Add("Authorization", $"Basic {base64string}")
view.LoadUrl(url, headers);
return true;
}
public override WebResourceResponse ShouldInterceptRequest(WebView view, IWebResourceRequest urlResource)
{
//headers does not always contains authorization header, so let's add it.
if (!urlResource.RequestHeaders.ContainsKey("authorization") && !urlResource.RequestHeaders.ContainsKey("Authorization"))
{
var data = Encoding.GetEncoding("ISO-8859-1").GetBytes("username:password");
var base64string = Base64.EncodeToString(data, Base64Flags.NoWrap);
urlResource.RequestHeaders.Add("Authorization", $"{base64string}");
}
return base.ShouldInterceptRequest(view, urlResource);
}
public override void OnReceivedHttpError(WebView view, IWebResourceRequest request, WebResourceResponse errorResponse)
{
base.OnReceivedHttpError(view, request, errorResponse);
}
If you only need the headers on the get requests, the code below will work. However POST requests are a different issue. I needed to do a similar thing (with all requests, not just GET), and all I can say is that there's not straightforward solution, at least not one that I've found (and I've tried everything short of writing my own network driver). I've tried so many methods (ShouldOverrideUrlLoading, ShouldInterceptRequest, custom LoadUrl and PostUrl etc.) and none of them give a 100% solution. There is a lot of misinformation about this so I think some clarification is needed since I've spent two days on this without success.
So here's what I've learned:
If you only need the headers in the GET requests, that's trivial. Simply create an implementation of WebViewClient and override ShouldOverrideUrlLoading like this:
[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), typeof(App.Android.HybridWebViewRenderer))]
namespace App.Android
{
public class HybridWebViewRenderer : WebViewRenderer
{
public HybridWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
Control.SetWebViewClient(new CustomWebViewClient());
}
}
public class CustomWebViewClient : WebViewClient
{
public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, string url)
{
Dictionary<string, string> headers = new Dictionary<string, string>
{
["Name"] = "value"
};
view.LoadUrl(url, headers);
return true;
}
}
}
If, however, you need the headers in other requests (specifically POST requests) there really isn't a perfect solution. Many answers tell you to override ShouldInterceptRequest but this is unlikely to help. ShouldInterceptRequest provides an IWebResourceRequest which contains the URL of the request, the method (i.e. POST) and the headers. There are answers out there which state that adding the headers by doing request.Headers.Add("Name", "Value") is a viable solution but this is wrong. The IWebResourceRequest is not used by the WebView's internal logic so modifying it is useless!
You can write your own HTTP client in ShouldInterceptRequest which includes your own headers to perform the requests and return a WebResourceResponse object. Again, this works for GET requests, but the problem with this is that even though we can intercept a POST request, we cannot determine the content in the request as the request content is not included in the IWebResourceRequest object. As a result, we cannot accurately perform the request manually. So, unless the content of the POST request is unimportant or can somehow be fetched, this method is not viable.
An additional note on this method: returning null tells the WebView to handle the request for us. In other words 'I don't want to intercept the request'. If the return is not null however, the WebView will display whatever is in the WebResourceResponse object.
I also tried overriding the PostUrl and LoadUrl methods in the WebView itself. These methods are not called by the internal logic, so unless you are calling them yourself, this does not work.
So what can be done? There are a few hacky solutions (see github.com/KeejOow/android-post-webview) to get around this problem, but they rely on javascript and are not suitable in all cases (I have read that they don't work with forms). If you want to use them in Xamarin, you're going to need to adapt the code for C# anyway, and there is no guarantee that it will solve your problem.
I'm writing this so no one else has to waste countless hours finding a solution that doesn't really exist.
If only the Android devs had decided to include the POST content in the IWebResourceRequest object...
And apologies for the length, if you've read to this point, you're probably as desperate as I was.
I'm trying to write a global error handler for ASP.NET web api that is able to log the request details of requests that cause unhandled exception in my api. I've registered the below GlobalExceptionHandler class in my OWIN startup class, but I'm unable to retrieve the content of any data posted in the body of requests.
public class GlobalExecptionHander : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
var body = context.Request.Content.ReadAsStringAsync().Result;
//body here is an empty string
context.Result = new UnhandledErrorActionResult
{
Request = context.Request,
};
}
}
In my startup class
config.Services.Replace(typeof(IExceptionHandler), new GlobalExecptionHander());
Since I just came across this exact problem, I was amazed to find this question without an answer! I hope you've managed to solve the problem after all this time. I'd still like to answer this question regardless.
The thing is that by the time your GlobalExceptionHandler handles the exception, something (like Newtonsoft Json or any other request handler) has already read the contentstream of the HTTP request. When the stream is read, you cannot read it again, unless there was some way to reset that stream to its initial position...
public override void Handle(ExceptionHandlerContext context)
{
string requestContent = "";
using(System.IO.Stream stream = context.Request.Content.ReadAsStreamAsync().Result)
{
// Set the stream back to position 0...
if (stream.CanSeek)
{
stream.Position = 0;
}
// ... and read the content once again!
requestContent = context.Request.Content.ReadAsStringAsync().Result;
}
/* ..Rest of code handling the Exception.. */
}
The reason requestContent is outside that using block, is because the stream gets disposed after the block closes. You could also get rid of using and call stream.Dispose() after you've done reading the content.
I'm adding ASP.NET routing to an older webforms app. I'm using a custom HttpHandler to process everything. In some situations I would like to map a particular path back to an aspx file, so I need to just pass control back to the default HttpHandler for asp.net.
The closest I've gotten is this
public void ProcessRequest(HttpContext context) {
// .. when we decide to pass it on
var handler = new System.Web.UI.Page();
handler.ProcessRequest(context);
MemoryStream steam = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);
handler.RenderControl(htmlWriter);
// write headers, etc. & send stream to Response
}
It doesn't do anything, there's nothing output to the stream. MS's documentation for System.Web.UI.Page (as an IHttpHandler) say something to the effect of "do not call the ProcessRequest method. It's for internal use."
From looking around it seems like you can do this with MVC, e.g. : MvcHttpHandler doesn't seem to implement IHttpHandler
There is also this thing System.Web.UI.PageHandlerFactory which appears that it would just produce a Page handler for an aspx file, but it's internal and I can't use it directly.
This page: http://msdn.microsoft.com/en-us/library/bb398986.aspx refers to the "default asp.net handler" but does not identify a class or give any indication how one might use it.
Any ideas on how I can do this? Is it possible?
Persistence pays off! This actually works, and since this information seems to be available pretty much nowhere I thought I'd answer my own question. Thanks to Robert for this post on instantiating things with internal constructors, this is the key.
http://www.rvenables.com/2009/08/instantiating-classes-with-internal-constructors/
public void ProcessRequest(HttpContext context) {
// the internal constructor doesn't do anything but prevent you from instantiating
// the factory, so we can skip it.
PageHandlerFactory factory =
(PageHandlerFactory)System.Runtime.Serialization.FormatterServices
.GetUninitializedObject(typeof(System.Web.UI.PageHandlerFactory));
string newTarget = "default.aspx";
string newQueryString = // whatever you want
string oldQueryString = context.Request.QueryString.ToString();
string queryString = newQueryString + oldQueryString!="" ?
"&" + newQueryString :
"";
// the 3rd parameter must be just the file name.
// the 4th parameter should be the physical path to the file, though it also
// works fine if you pass an empty string - perhaps that's only to override
// the usual presentation based on the path?
var handler = factory.GetHandler(context, "GET",newTarget,
context.Request.MapPath(context,newTarget));
// Update the context object as it should appear to your page/app, and
// assign your new handler.
context.RewritePath(newTarget , "", queryString);
context.Handler = handler;
// .. and done
handler.ProcessRequest(context);
}
... and like some small miracle, an aspx page processes & renders completely in-process without the need to redirect.
I expect this will only work in IIS7.
I'm you're using Routing in webforms you should be able to just add an ignore route for the specific .aspx files you want. This will then be handled by the default HttpHandler.
http://msdn.microsoft.com/en-us/library/dd505203.aspx
Another option is to invert the logic by handling the cases in which you do NOT want to return the default response and remap the others to your own IHttpHandler. Whenever myCondition is false, the response will be the "default". The switch is implemented as an IHttpModule:
public class SwitchModule: IHttpModule
{
public void Init(HttpApplication context)
{
context.PostAuthenticateRequest += app_PostAuthenticateRequest;
}
void app_PostAuthenticateRequest(object sender, EventArgs e)
{
// Check for whatever condition you like
if (true)
HttpContext.Current.RemapHandler(new CustomHandler());
}
public void Dispose()
}
internal class CustomHandler: IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.Write("hallo");
}
public bool IsReusable { get; }
}
I searched the web but haven't found a real good answer for this question..
Let's say I have a form, on AddToList.aspx, and i want that after you hit send, it will direct you back to List.aspx, with a message "The Item was added to list" in a message box div.
do i need to send List.aspx?msg=my message, or is there another good way of doing it?
EDIT:
so i made this helper class:
public class MessageHelper : System.Web.UI.MasterPage
{
public void SetMessage(String message)
{
Session["Message"] = message;
}
public string GetMessage()
{
if (String.IsNullOrEmpty(Session["Message"]))
{
String temp = Session["Message"];
Session["Message"] = "";
return temp;
}
else
{
return "";
}
}
}
and got this error:
Error 32 The best overloaded method match for 'string.IsNullOrEmpty(string)' has some invalid arguments
Error 33 Argument '1': cannot convert from 'object' to 'string'
Error 34 Cannot implicitly convert type 'object' to 'string'. An explicit conversion exists (are you missing a cast?)
You need to convert to string. Session parameters are stored as objects.
It may also be userful to implement this as a extension method. This way it will be available on all page types (Master and UI)
public static class MessageHelper
{
public static void SetMessage(this Page page, String message)
{
Session["Message"] = message;
}
public static string GetMessage(this Page page)
{
var messageText = Session["Message"] as string;
if (!String.IsNullOrEmpty(messageText ))
{
Session["Message"] = "";
return messageText;
}
return "";
}
}
You could certainly use the query string to pass data to your List.aspx page, but be careful passing text that you're planning on writing out in the HTML - you'll need to protect against XSS attacks.
There are several other ways to do this. Chances are, you're going to have several places in your application where you want to redirect the user to another page, but also display a message that has something to do with what they did on the previous page (saved an item, deleted an item, etc.). It would be better to come up with more of a global scheme for this rather than a one-off just for this particular instance.
One idea is to use the Session for storing a message, then do your redirect.
Session("Message") = "Item was added to list."
Response.Redirect("List.aspx")
Then, on your pages (or a Master Page, perhaps), you check Session("Message") and if it's got something, you show that message to the user, then clear that variable.
If Session("Message") IsNot Nothing Then
Response.Write(CType(Session("Message"), String)) 'or set a label text, or show a pop up div, or whatever'
Session("Message") = Nothing
End If
If you use this approach, I recommend you write a helper class, and just use that to manage your messaging:
MessageHelper.SetMessage("Item added to list.")
and
MessageHelper.GetMessage()
would be the methods you would need.
I believe you could do it by setting the PostBackUrl of the button used to save the data to "List.aspx". Maybe set a variable to true/false on AddToList.aspx and then access it from List.aspx?
Not sure if it's better but it's an option.
I can't comment yet or I would have just commented this to your post. You need to cast your session variable like this: (string)Session["Message"]. So, code would look like this:
public class MessageHelper : System.Web.UI.MasterPage
{
public void SetMessage(String message)
{
Session["Message"] = message;
}
public string GetMessage()
{
if (String.IsNullOrEmpty((string)Session["Message"]))
{
String temp = (string)Session["Message"];
Session["Message"] = "";
return temp;
}
else
{
return "";
}
}
}
Actually there's a better way of writing that class: make it one property instead of two methods. It would look like this: (I also fixed your logic; GetMessage was always returning blank)
public class MessageHelper : System.Web.UI.MasterPage
{
public MessageHelper()
{
}
public string Message
{
set { Session["Message"] = value; }
get
{
if (String.IsNullOrEmpty((string)Session["Message"]))
{
Session["Message"] = "";
}
return (string)Session["Message"];
}
}
}
In your two respective files, you would set and get it like so:
//in AddToList.aspx
MessageHelper msg = new MessageHelper();
msg.Message = "The Item was added to your list.";
//and in List.aspx, assigned to an arbitrary Label named myMessageLabel
MessageHelper msg = new MessageHelper();
myMessageLabel.Text = msg.Message;
I have the pattern User/{domain}/{username} set up via Routing. Everything works except for one thing. I can't figure out how to get the domain and username variables passed to my redirected page. Below is my GetHttpHandler method from my IRouteHandler implementation.
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string basePath;
basePath = "~/UserPage.aspx";
string domain = requestContext.RouteData.GetRequiredString("domain");
string username = requestContext.RouteData.GetRequiredString("username");
string virtualPath =
string.Format(basePath + "?domain={0}&username={1}", domain, username);
return (Page)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page));
}
I get the error from the last line of code:
UserPage.aspx?domain=SOMEDOMAIN&username=SOMEUSER is not a valid virtual path.
So how are you supposed to pass variables to the target page? what am I missing?
I think I solved this one myself.
Found this loop
foreach (KeyValuePair<string, object> token in requestContext.RouteData.Values)
{
requestContext.HttpContext.Items.Add(token.Key, token.Value);
}
from http://www.codethinked.com/post/2008/08/20/Exploring-SystemWebRouting.aspx
Its like the 4th code sample down.
UPDATE:
Not sure if this will work... requestContext.HttpContext seems to be "readonly". Back to the drawing board.
UPDATE 2:
Looks like this will work if you add in a reference to System.Web.Abstractions
Started mucking around with things and saw the IHttpHandler interface provides the RequestContext to the GetHttpHandler method.
So, I modified my base page class (I always put a layer between System.Web.UI.Page and my own pages, calling it BasePage or similar just for the purpose). So I added a public property on PVBasePage to receive a RequestContext object.
public RequestContext RequestContext { get; set; }
Then, my Routing class code is as follows:
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
{
// create the page object as my own page...
var page = BuildManager.CreateInstanceFromVirtualPath(VirtualPath
, typeof(PVBasePage)) as PVBasePage;
// pass in the request context
page.RequestContext = requestContext;
// return this page in the form of a IHttpHandler
return page as IHttpHandler;
}
So instead of, as in the sample code, creating the instance directly as the IHttpHandler, I create it as my own page. Set the request context property, and then return the page to the caller AS a IHttpHandler.
Tested and it works. WOO HOO!
Then in the instance page, you can hit the RequestContext.GetValues collection to read out your passed in parameters.
HTH
#B.Tyndall
I just got this working with a solution similar to yours.
found at: http://msmvps.com/blogs/luisabreu/archive/2008/03/12/using-the-routing-mvc-api-with-classic-asp-net.aspx
foreach (var aux in requestContext.RouteData.Values)
{
HttpContext.Current.Items[aux.Key] = aux.Value;
}
So in effect you're no longer using the Request.QueryString but instead Context.Items collection
HttpContext.Current.Items["RouteName"]
or
Context.Items["RouteName"]
It appears as though other are also taking the route (no pun intended) of putting the parameters in the context Items collection.
http://bbits.co.uk/blog/archive/2008/05/19/using-asp.net-routing-independent-of-mvc---passing-parameters-to.aspx
I combined a couple of these approaches for pages that have a specific parameter, I created a UserNameRouteHandler for pages that accept that type of parameter. In my PageBase class I checked the context items for that parameter and then set a property so that my pages that inherit from PageBase can use it.
public class UserNameRouteHandler : IRouteHandler
{
#region Implementation of IRouteHandler
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string pageName = requestContext.RouteData.GetRequiredString("PageName");
string employeeUserName = requestContext.RouteData.GetRequiredString("UserName");
if(!string.IsNullOrEmpty(employeeUserName))
{
requestContext.HttpContext.Items["UserName"] = employeeUserName;
}
pageName = pageName.ToLower() == "home" ? "default" : pageName;
string virtualPath = string.Format("~/{0}.aspx", pageName);
return (Page)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page));
}
#endregion
}
And in my OnLoad of PageBase I set the property to pages that need it can have it...definitely looking for a more elegant solution though.
protected override void OnLoad(EventArgs e)
{
if (!IsPostBack)
{
if (Context.Items["UserName"] != null)
{
EmployeeUserName = Context.Items["UserName"].ToString();
}
}
base.OnLoad(e);
}