Is there a version of IsLocalUrl for WebForms? - asp.net

I'm writing a mixed app using some MVC and some Webforms screens. I need to invoke a WebForms screen with a ReturnUrl in a hidden field. I'd like to validate the ReturnUrl before transferring back to it. MVC has an Url.IsLocalUrl function, but it doesn't seem to work on WebForm screens, so I use the UrlHelper class. But when I use it I get a NullReferenceException:
UrlHelper url = new UrlHelper();
if (url(validaddr)) <--- get NullReferenceException
{
}
Any ideas?

I use the below extension method to validate local url's in web forms. Hope this helps you too.
public static bool IsLocalURL(this string _url)
{
bool flag = false;
try
{
var url = new Uri(_url);
var ctx = HttpContext.Current;
if (url.Host.Equals(ctx.Request.Url.Host) && url.Port.Equals(ctx.Request.Url.Port))
flag = true;
}
catch { }
return flag;
}
This extension method is for string. You may create a similar for Uri class as well.

I came here trying to solve the same problem. I used RequestExtensions.IsUrlLocalToHost in System.Web.WebPages (available in nuget package Microsoft.AspNet.WebPages v3.2.6)
Doc here: https://learn.microsoft.com/en-us/dotnet/api/system.web.webpages.requestextensions.isurllocaltohost?view=aspnet-webpages-3.2
Assuming you have an HttpRequest to work with (you will probably need this anyway to compare the URL to the underlying host URL), you need to first convert your HttpRequest to HttpRequestBase:
var httpRequestBase = new HttpRequestWrapper(HttpContext.Current.Request) as HttpRequestBase;
Then you can perform:
httpRequestBase.IsUrlLocalToHost(myUrlString)

Code should be:
UrlHelper url = new UrlHelper();
if (url.IsLocalUrl(validaddr)) <--- get NullReferenceException
{
}

Related

ITempDataProvider in MVC 6 to use cookies for tempdata

I'm migrating a site over to use MVC 6. Currently I have tempdata store in cookies, but I can't find the set up of how to do this in the new MVC framework.
First, implement your ITempDataProvider. I did it this way, using JSON.Net.
public class CookieTempDataProvider : ITempDataProvider
{
readonly string CookieKey = "_tempdata";
public IDictionary<string,object> LoadTempData(HttpContext context)
{
var cookieValue = context.Request.Cookies[this.CookieKey];
if(string.IsNullOrWhiteSpace(cookieValue))
{
return new Dictionary<string, object>();
}
var decoded = Convert.FromBase64String(cookieValue);
var jsonAsString = Encoding.UTF8.GetString(decoded);
var dictionary = JsonConvert.DeserializeObject<IDictionary<string,object>>(jsonAsString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, TypeNameAssemblyFormat = FormatterAssemblyStyle.Full });
// The cookie really should be deleted when the SaveTempData() method is called with an empty dictionary
// but that does not seem to be working for some reason. Added this delete for now (maybe this is a beta issue)
// TODO: Revisit at next release
context.Response.Cookies.Delete(this.CookieKey);
return dictionary;
}
public void SaveTempData(HttpContext context, IDictionary<string,object> values)
{
if (values == null || values.Count == 0)
{
context.Response.OnStarting(() => Task.Run(() =>
{
context.Response.Cookies.Delete(this.CookieKey);
}));
return;
}
var jsonAsString = JsonConvert.SerializeObject(values, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, TypeNameAssemblyFormat = FormatterAssemblyStyle.Full });
var bytes = Encoding.UTF8.GetBytes(jsonAsString);
var encoded = Convert.ToBase64String(bytes);
context.Response.Cookies.Append(this.CookieKey, encoded);
}
}
Next, in Startup.cs, where services are wired up, replace the default ITempDataProvider with your custom version, like so:
public void ConfigureServices(IServiceCollection services)
{
// Replace Temp Data Provider
var existing = services.FirstOrDefault(x => x.ServiceType == typeof(ITempDataProvider));
services.Remove(existing);
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
}
EDIT
Since RC2 the original answer doesn't work any longer due to what seems like timing changes in the MVC request lifecycle...you'll receive an error about not being able to modify headers. I've updated the SaveTempData() method above to account for this.
I also had this need, so I've implemented a cookie-based TempData provider for ASP.NET Core MVC and published it on NuGet. It is available here.
If you think about TempData class for storing data for next request, there is some changes in MVC 6. You need to add additional package and configure it. Here are steps:
Remove "dnxcore50" from frameworks section in [project.json]. Session hasn't implementd yet in dnxcore50.
In the [project.json] add:
"Microsoft.AspNet.Session": "1.0.0-rc1-final"
Enable Caching and Session in class Startup.cs, method ConfigureServices, by adding next lines after services.AddMvc():
services.AddCaching();
services.AddSession();
Cinfigure it on class Startup.cs, method Configure, adding next line before app.UseMvc(...):
app.UseSession();
And that's it. But remember, you can store only primitive or serializable data types. If you need to store user defined data type, you need to serialized it. For that purpose we use "Newtonsoft.Json" lib. Here is example:
string json = JsonConvert.SerializeObject(myObject);
TempData["myKey"] = json;

Authorization_code grant flow on Owin.Security.OAuth: returns invalid_grant

I am trying to setup my authentication using the authorization_code grant flow. I had it previously working with grant_type=password, so I kind of know how the stuff is supposed to work. But when using grant_type=authorization_code, I couldn't make it return anything other than invalid_grant
Here is my setup:
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/auth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
Provider = new SampleAuthProvider()
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
AuthenticationType = "Bearer"
});
SampleAuthProvider is the following class: https://gist.github.com/anonymous/8a0079b705423b406c00
Basically, it's just logging every step and validating it. I tried the request:
POST http://localhost:12345/auth/token
grant_type=authorization_code&code=xxxxxx&client_id=xxxxx&redirect_uri=https://xxxx.com/
Content-Type: application/x-www-form-urlencoded
It's going through:
OnMatchEndpoint
OnValidateClientAuthentication
And that's all. I expected it to call OnValidateTokenRequest and OnGrantAuthorizationCodenext, but it just didn't. I have no idea why.
The xxxx's in the request aren't placeholders, I tried it like that. Maybe the middleware makes some checks on its own and rejects the request because of that? I tried variants of the redirect_uri with http, without any protocol, without trailing slash...
It also works properly with a custom grant_type. It so if I too desperate, I guess I can use that to simulate authorization_code, but I'd rather not have to do that.
TL;DR
My OAuthAuthorizationServerProvider returns {"error":"invalid_grant"}after OnValidateClientAuthentication when using grant_type=authorization_code.
Why is it stopping there?
How can I make the whole damn thing work?
Thanks for your help!
Edit
As pointed out by RajeshKannan, I made a mistake in my configuration. I didn't provide an AuthorizationCodeProvider instance. However, that didn't completely solve the problem, since in my case, the code is not issued by the AuthorizationCodeProvider, and I can't just deserialize it. I anwered with the workaround I got working.
Here is what I got working. I'm not completely comfortable with that solution, but it works and should help others to fix their issues.
So, the issue is that I didn't set the AuthorizationCodeProvider property. When a request with grant_type=authorization_code is received, the code must be validated by that code provider. The framework assumes that the code was issued by that code provider, but that's not my case. I get it from another server and have to send the code back to it for validation.
In the standard case, where you are also the one issuing the code, the link provided by RajeshKannan describes everything you have to do.
Here is where you have to set the property:
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString(Paths.TokenPath),
Provider = new SampleAuthProvider(),
AuthorizationCodeProvider = new MyAuthorizationCodeProvider ()
}
And the declaration of the MyAuthorizationCodeProvider class:
internal class MyAuthorizationCodeProvider : AuthenticationTokenProvider
{
public override async Task ReceiveAsync(
AuthenticationTokenReceiveContext context)
{
object form;
// Definitely doesn't feel right
context.OwinContext.Environment.TryGetValue(
"Microsoft.Owin.Form#collection", out form);
var redirectUris = (form as FormCollection).GetValues("redirect_uri");
var clientIds = (form as FormCollection).GetValues("client_id");
if (redirectUris != null && clientIds != null)
{
// Queries the external server to validate the token
string username = await MySsoService.GetUserName(context.Token,
redirectUris[0]);
if (!string.IsNullOrEmpty(username))
{
var identity = new ClaimsIdentity(new List<Claim>()
{
// I need the username in GrantAuthorizationCode
new Claim(ClaimTypes.NameIdentifier, username)
}, DefaultAuthenticationTypes.ExternalBearer);
var authProps = new AuthenticationProperties();
// Required. The request is rejected if it's not provided
authProps.Dictionary.Add("client_id", clientIds[0]);
// Required, must be in the future
authProps.ExpiresUtc = DateTimeOffset.Now.AddMinutes(1);
var ticket = new AuthenticationTicket(identity, authProps);
context.SetTicket(ticket);
}
}
}
}
I had the same error. Things I was missing:
Specify OAuthAuthorizationServerOptions.AuthorizationCodeProvider according to the documentation.
Specify the same client_id as a GET-parameter when making a request to the token endpoint as you did when you received the authorization_code.
Override OAuthAuthorizationServerProvider.ValidateClientAuthentication and in this method call context.TryGetFormCredentials. This sets the property context.ClientId to the value from the client_id GET-parameter. This property must be set, otherwise you'll get the invalid_grant error. Also, call context.Validated().
After doing all of the above, I could finally exchange the authorization_code to an access_token at the token endpoint.
Thanks scenario, My code was missing the following two required values. Posted here in case others find it useful:
// Required. The request is rejected if it's not provided
authProps.Dictionary.Add("client_id", clientIds[0]);
// Required, must be in the future
authProps.ExpiresUtc = DateTimeOffset.Now.AddMinutes(1);
Make sure that you have configured your authorization server options.
I think you should provide your authorize end point details:
AuthorizeEndpointPath = new PathString(Paths.AuthorizePath)
In the below link, the authorization code grant will be explained in detail and it lists the method which were involved in authorization code grant life cycle.
Owin Oauth authorization server
The answer by #dgn more or less worked for me. This is just an extension to that. As it turns out, you can supply whatever string you want to the ClaimsIdentity constructor. The following works just as well, and doubles up as a detailed code comment:
var identity = new ClaimsIdentity(
#"Katana - What a shitty framework/implementation.
Unintuitive models and pipeline, pretty much have to do everything, and the docs explain nothing.
Like what can go in here? WTF knows but turns out as long as _something_ is in here,
there is a client_id key in your AuthenticationProperties with the same value as
what's set inside your implementation for OAuthAuthorizationServerProvider.ValidateClientAuthentication, and
your AuthenticationProperties.ExpiresUtc is set to some time in the future, it works.
Oh and you don't actually need to supply an implementation for OAuthAuthorizationServerProvider.GrantAuthorizationCode...
but if you are using the resource owner grant type, you _do_ need to supply an implementation of
OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials. Hmm. Whatever.
Katana and IdenetityServer - two frameworks that are absolute garbage. In the amount of time it took me to
figure out all the observations in this paragraph, I could've written my own /token endpoint."
);
I solved this with the the following simplest example and would like to share it. Hope someone find it helpful.
--
It seems the middleware will check if the key redirect_uri exists in the dictionary of AuthenticationProperties, remove it and everything works fine(with validated context).
A simplified example of AuthorizationCodeProvider woubld be like so:
public class AuthorizationCodeProvider:AuthenticationTokenProvider {
public override void Create(AuthenticationTokenCreateContext context) {
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context) {
context.DeserializeTicket(context.Token);
context.Ticket.Properties.Dictionary.Remove("redirect_uri"); // <-
}
}
And don't forget to make the context validated in the overridden method OAuthAuthorizationServerProvider.ValidateClientAuthentication. Again, here's a simplified example which inherit from the ApplicationOAuthProvider class of the template project:
public partial class DefaultOAuthProvider:ApplicationOAuthProvider {
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) {
if(null!=context.RedirectUri) {
context.Validated(context.RedirectUri);
return Task.CompletedTask;
}
return base.ValidateClientRedirectUri(context);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) {
if(context.TryGetFormCredentials(out String clientId, out String clientSecret)) {
// Specify the actual expected client id and secret in your case
if(("expected-clientId"==clientId)&&("expected-clientSecret"==clientSecret)) {
context.Validated(); // <-
return Task.CompletedTask;
}
}
return base.ValidateClientAuthentication(context);
}
public DefaultOAuthProvider(String publicClientId) : base(publicClientId) {
}
}
Note that if you invoke context.Validated with a particular client id, then you will have to put the same client_id in the properties of the ticket, you can do that with the method AuthenticationTokenProvider.Receive

Determing the physical path of a user friendly url

I have been asked to look at updating an old ASP.net Web Forms application to ASP.net 4.5; specifically to implement Microsoft's 'User Friendly' routing mechanism (NuGet Package Microsoft.AspNet.FriendlyUrls).
Generally, the upgrade was straightforward, but I am left with one problem.
The original developer attached/associated 'meta data' XML files to many of the web pages.. For example, /Products/Tables/Oak.aspx might also have the following meta file /Products/Tables/Oak.aspx.meta
When the page loads, it 'looks' for the meta file and loads it. In a non-rewritten URL environment, this was easy...
string metaUrl = Page.Request.Url.AbsolutePath + ".meta";
string metaPath = Page.Server.MapPath(metaUrl);
If (System.IO.File.Exists(metaPath)) {
LoadMetaFile(metaPath);
}
In a 'Friendly URL' environment, this is not so easy as the original URL might be rewritten to /Products/Tables/Oak or maybe even rewritten completely via a custom MapPageRoute() definition.
Does anyone know if there a way that I can find/determine the 'true' path of the page?
The solution posted by Petriq ASP.NET WebForms: Request.GetFriendlyUrlFileVirtualPath() returns empty string works perfectly in my scenario.
For reference, here is Petriq's code for his HttpRequest extension method:
using System.Web;
using System.Web.Routing;
using Microsoft.AspNet.FriendlyUrls;
namespace Utils.Extensions
{
public static class HttpRequestExtensions
{
public static string GetFileVirtualPathFromFriendlyUrl(this HttpRequest request)
{
string ret = string.Empty;
ret = request.GetFriendlyUrlFileVirtualPath();
if (ret == string.Empty)
{
foreach (RouteBase r in RouteTable.Routes)
{
if (r.GetType() == typeof(Route))
{
Route route = (Route)r;
// Following line modified for case-insensitive comparison
if (String.Compare("/" + route.Url, request.Path, true) == 0)
{
if (route.RouteHandler.GetType() == typeof(PageRouteHandler))
{
PageRouteHandler handler = (PageRouteHandler)route.RouteHandler;
ret = handler.VirtualPath;
}
break;
}
}
}
}
return ret;
}
}
}

Returning binary file from controller in ASP.NET Web API

I'm working on a web service using ASP.NET MVC's new WebAPI that will serve up binary files, mostly .cab and .exe files.
The following controller method seems to work, meaning that it returns a file, but it's setting the content type to application/json:
public HttpResponseMessage<Stream> Post(string version, string environment, string filetype)
{
var path = #"C:\Temp\test.exe";
var stream = new FileStream(path, FileMode.Open);
return new HttpResponseMessage<Stream>(stream, new MediaTypeHeaderValue("application/octet-stream"));
}
Is there a better way to do this?
Try using a simple HttpResponseMessage with its Content property set to a StreamContent:
// using System.IO;
// using System.Net.Http;
// using System.Net.Http.Headers;
public HttpResponseMessage Post(string version, string environment,
string filetype)
{
var path = #"C:\Temp\test.exe";
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return result;
}
A few things to note about the stream used:
You must not call stream.Dispose(), since Web API still needs to be able to access it when it processes the controller method's result to send data back to the client. Therefore, do not use a using (var stream = …) block. Web API will dispose the stream for you.
Make sure that the stream has its current position set to 0 (i.e. the beginning of the stream's data). In the above example, this is a given since you've only just opened the file. However, in other scenarios (such as when you first write some binary data to a MemoryStream), make sure to stream.Seek(0, SeekOrigin.Begin); or set stream.Position = 0;
With file streams, explicitly specifying FileAccess.Read permission can help prevent access rights issues on web servers; IIS application pool accounts are often given only read / list / execute access rights to the wwwroot.
For Web API 2, you can implement IHttpActionResult. Here's mine:
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
class FileResult : IHttpActionResult
{
private readonly string _filePath;
private readonly string _contentType;
public FileResult(string filePath, string contentType = null)
{
if (filePath == null) throw new ArgumentNullException("filePath");
_filePath = filePath;
_contentType = contentType;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(File.OpenRead(_filePath))
};
var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
return Task.FromResult(response);
}
}
Then something like this in your controller:
[Route("Images/{*imagePath}")]
public IHttpActionResult GetImage(string imagePath)
{
var serverPath = Path.Combine(_rootPath, imagePath);
var fileInfo = new FileInfo(serverPath);
return !fileInfo.Exists
? (IHttpActionResult) NotFound()
: new FileResult(fileInfo.FullName);
}
And here's one way you can tell IIS to ignore requests with an extension so that the request will make it to the controller:
<!-- web.config -->
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
For those using .NET Core:
You can make use of the IActionResult interface in an API controller method, like so.
[HttpGet("GetReportData/{year}")]
public async Task<IActionResult> GetReportData(int year)
{
// Render Excel document in memory and return as Byte[]
Byte[] file = await this._reportDao.RenderReportAsExcel(year);
return File(file, "application/vnd.openxmlformats", "fileName.xlsx");
}
This example is simplified, but should get the point across. In .NET Core this process is so much simpler than in previous versions of .NET - i.e. no setting response type, content, headers, etc.
Also, of course the MIME type for the file and the extension will depend on individual needs.
Reference: SO Post Answer by #NKosi
While the suggested solution works fine, there is another way to return a byte array from the controller, with response stream properly formatted :
In the request, set header "Accept: application/octet-stream".
Server-side, add a media type formatter to support this mime type.
Unfortunately, WebApi does not include any formatter for "application/octet-stream". There is an implementation here on GitHub: BinaryMediaTypeFormatter (there are minor adaptations to make it work for webapi 2, method signatures changed).
You can add this formatter into your global config :
HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));
WebApi should now use BinaryMediaTypeFormatter if the request specifies the correct Accept header.
I prefer this solution because an action controller returning byte[] is more comfortable to test. Though, the other solution allows you more control if you want to return another content-type than "application/octet-stream" (for example "image/gif").
For anyone having the problem of the API being called more than once while downloading a fairly large file using the method in the accepted answer, please set response buffering to true
System.Web.HttpContext.Current.Response.Buffer = true;
This makes sure that the entire binary content is buffered on the server side before it is sent to the client. Otherwise you will see multiple request being sent to the controller and if you do not handle it properly, the file will become corrupt.
The overload that you're using sets the enumeration of serialization formatters. You need to specify the content type explicitly like:
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
You could try
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
You can try the following code snippet
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
Hope it will work for you.

How do I get ASP.NET WebForms Routing to route .asmx JSON calls properly?

I am attempting to implement multi-tenancy in a legacy ASP.NET WebForms app. I want the URL to indicate the proper client, like so:
http://example.com/client_name/Default.aspx
http://example.com/client_name/MyWebService.asmx
However, I cannot get it to route the .asmx's properly. This routing rule picks up all incoming urls just fine:
routes.Add("ClientSelector", new System.Web.Routing.Route
(
"{client}/{*path}",
routeHandler: new ClientRoute()
));
But I am having issues with handling .asmx calls. Here's my IRouteHandler, below. The error I get is:
A first chance exception of type 'System.Web.Services.Protocols.SoapException' occurred in System.Web.Services.dll
Additional information: Unable to handle request without a valid action parameter. Please supply a valid soap action.
It's supposed to be JSON, but for some reason it's not working. I am setting the content-type - if I send this same exact request without routing, it works fine.
public class ClientRoute : System.Web.Routing.IRouteHandler
{
private string m_Path;
private string m_Client;
public ClientRoute() { }
public bool IsReusable { get { return true; } }
public IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
this.m_Path = (string)requestContext.RouteData.Values["path"];
this.m_Client = (string)requestContext.RouteData.Values["client"];
string virtualPath = "~/" + this.m_Path;
bool shouldValidate = false;
if (shouldValidate && !UrlAuthorizationModule.CheckUrlAccessForPrincipal(
virtualPath, requestContext.HttpContext.User,
requestContext.HttpContext.Request.HttpMethod))
{
requestContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
requestContext.HttpContext.Response.End();
return null;
}
else
{
HttpContext.Current.RewritePath(virtualPath);
HttpContext.Current.Items.Add("Client", this.m_Client);
if (virtualPath.EndsWith(".aspx"))
return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page));
else
{
var asmxPos = virtualPath.IndexOf(".asmx", StringComparison.OrdinalIgnoreCase);
if (asmxPos >= 0)
{
// What goes here? This isn't working...
var asmxOnlyVirtualPath = virtualPath.Substring(0, asmxPos + 5);
return new System.Web.Services.Protocols.WebServiceHandlerFactory().GetHandler(
HttpContext.Current, HttpContext.Current.Request.HttpMethod, asmxOnlyVirtualPath, HttpContext.Current.Server.MapPath(asmxOnlyVirtualPath));
}
else
return new StaticRoute();
}
}
}
}
Relevant links:
Getting ScriptHandlerFactory handler
The open source http://www.teamlab.com project is built with ASP.NET Webforms, and uses a multitenant/saas model. I noticed you posted another question inquiring about multitenancy.
Perhaps you can look into their code for reference ideas.
I tried my best, ended up failing, and converted all my web services to WCF .svc services instead.

Resources