Middleware to Encrypt Query string - asp.net-mvc

I am writing a .Net Core Middleware to encrypt the query string parameters, where I want to user to see something like
?enc=VXzal017xHwKKPolDWQJoLACDqQ0fE//wGkgvRTdG/GgXIBDd1
while the code sees this
?user=123&account=456.
I encrypt the params using a IDataProtector. The Invoke() in my middleware looks like the below code
if (UriHelper.GetEncodedUrl(context.Request).Contains("?"))
{
string query = ExtractQuery((context.Request.GetEncodedUrl()));
int indexOfEnc = query.IndexOf(PARAMETER_NAME, StringComparison.OrdinalIgnoreCase);
if (indexOfEnc > -1)
{
var enc = context.Request.Query[PARAMETER_NAME];
enc = Decrypt(enc);
context.Request.Path = new PathString(context.Request.Path.Value + enc);
await _next.Invoke(context);
}
else if (context.Request.Method == "GET" || context.Request.Method == "POST")
{
// Encrypt the query string and redirects to the encrypted URL.
// Remove if you don't want all query strings to be encrypted automatically.
string encryptedQuery = Encrypt(query);
string tempRawUrl = UriHelper.GetEncodedUrl(context.Request).ToLower();
if (!(context.Request.Method == "POST" && tempRawUrl.Contains("ha")))
{
context.Response.Redirect(context.Request.Path.Value + "?" + PARAMETER_NAME + "=" + encryptedQuery);
}
}
}
else
{
await _next.Invoke(context);
}
The First time when I login and enter the user/pass, the code comes in to the elseif section above and gets encrypted fine. I look for the "enc" query param the next time and while it gets decrypted and the path looks good, the
**await _next.Invoke(context);**
in the if section does nothing. I am expecting it to go to the controller to validate the user/pass.
Bear with me here please, this is my first middleware and I am trying to replace the httphandlers in my legacy code.
Any help is appreciated. I have spent almost 5 hours on this and cant seem to figure it out.

You may take a look at the IQueryFeature and the IResponseFeature. In ASP.NET Core, features allow to override behaviours of basics objects
like HttpRequest & HttpResponse object.
You could simply wrap the existing IQueryFeature for transparent decryption.
And for the query encryption, wrap the existing IResponseFeature for transparent encryption.
Set the wrappers within the middleware.
httpContext.Features.Set<IQueryFeature>(new TransparentDecryptionQueryFeature(httpContext.Features.Get<IQueryFeature>));
httpContext.Features.Set<IResponseFeature>(new TransparentEncryptionResponseFeature(httpContext.Features.Get<IResponseFeature>));
By doing so, all middlewares executing after yours will use the "Transparent Feature".
public class TransparentDecryptionQueryFeature : IQueryFeature
{
privare readonly IQueryCollection _store;
public TransparentDecryptionQueryFeature(IQueryFeature feature)
{
_store = new TranparentDecryptionQueryCollection(feature.Query);
}
public IQueryCollection Query
{
get
{
return _store;
}
set
{
_store = new TransparentDecryptionQueryCollection(value);
}
}
}
public class TransparentDecryptionQueryCollection : IQueryCollection
{
private readonly IQueryCollection _inner;
public TransparentDecryptionQueryCollection(IQueryCollection inner)
{
var store = new Dictionary<string, StringValues>()
foreach (var item in inner)
{
if (item.Key == PARAMETER_NAME)
{
// TODO : Adds all the decrypted query parameters in the store
}
else
{
store.Add(item);
}
}
_inner = new QueryCollection(store);
}
// implement other methods by delegating with _inner object
}

I changed the code to.
if (indexOfEnc > -1)
{
var enc = context.Request.Query[PARAMETER_NAME];
enc = "?" + Decrypt(enc);
Microsoft.AspNetCore.Http.QueryString queryString = new Microsoft.AspNetCore.Http.QueryString(enc);
context.Request.QueryString = queryString;
await _next.Invoke(context);
}
and it does work now. I still think I am missing something here. Is there a better way to do this ?

Related

How can i validate email confirmation token?

In my asp.net mvc application, when a user registers, I send a email to their account with a link for validation before they can use the app. See code snippet below.
var emailActionLink = Url.Action("ValidateAccount", "Register",
new { Token = registeredUserViewModel.Id, Username = registeredUserViewModel.Username },
Request.Url.Scheme);
The snippet above is what they will click on which will then call an action with the route values,
Validate Account Action
public ActionResult ValidateAccount(string token, string username)
{
try
{
if (!string.IsNullOrEmpty(token) && !string.IsNullOrEmpty(username))
{
var user = _userServiceClient.IsUserNameAvailable(username);
if (!user.HasValue) throw new NullReferenceException("This account does not exist");
var userContract = user.Value;
userContract.EmailVerified = true;
if (_userServiceClient.UpdateUser(userContract) == null) throw new Exception("Something has gone wrong");
return View("ValidationCompleted");
}
else
{
ViewBag.RegisteredUser = null;
}
}
catch (Exception exception)
{
throw;
}
return View();
}
The problem is, this method is not verifiying the token, what will happen if someone changes the value of token in the uri, this will still pass and very the account. What will be the right approach in improving this.
In this case, token is the user's Id which is a Guid, but it is encoded and there is no way of comparing the user's Id in my database with this encoded token. I think this is encoded in the action link.
Rather than using your Id, you would probably be better off having a Token field in the table (nullable, so that it can be cleared out after validation). Generate a URL-safe token (I use hex strings, which don't use any special characters), and then look for that token in the database in the validation action.
Here's an example of a token generator:
public class TokenGenerator
{
public static string GenerateToken(int size = 32)
{
var crypto = new RNGCryptoServiceProvider();
byte[] rbytes = new byte[size / 2];
crypto.GetNonZeroBytes(rbytes);
return ToHexString(rbytes, true);
}
private static string ToHexString(byte[] bytes, bool useLowerCase = false)
{
var hex = string.Concat(bytes.Select(b => b.ToString(useLowerCase ? "x2" : "X2")));
return hex;
}
}
Then, add the appropriate method to your service class:
public YourUserType GetUserForToken(string token, string userName)
{
return YourDbContext.Users
.SingleOrDfault(user => user.Token.Equals(token, StringComparison.OrdinalIgnoreCase)
&& user.UserName.Equals(userName, StringComparison.OrdinalIgnoreCase));
}
Obviously, this makes some assumptions on your table structure and data access code.

Include Entity navigation properties using a Service Reference

I am using a WCF Data Services class that exposes an entity framework model via the OData protocol like so:
public class Service : EntityFrameworkDataService<MyEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.UseVerboseErrors = true;
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
}
}
I consume this service through a service reference in a web solution. I am having problems including all the navigation properties for the entity. I cannot use the following syntax because I do not know what type of entity the user may be requesting:
I CANNOT USE
MyEntities.Customer.Expand("Address");
or
MyEntities.Customer.Include("Address");
What I am currently doing is building a URI string with the $expand=Entity1,Entity2 syntax and then executing that against my service as follows:
public static QueryOperationResponse<object> GetList(string entitySetName, params string[] preloads)
{
StringBuilder stringBuilder = new StringBuilder();
string queryString = string.Empty;
object result = null;
Uri dataAccessURI;
stringBuilder.Append(ServiceReferenceURI.AbsoluteUri);
stringBuilder.Append(entitySetName);
if (preloads != null)
{
for (int i = 0; i <= preloads.Length - 1; i++)
{
queryString = i == 0 ? "?$expand=" : ",";
stringBuilder.AppendFormat("{0}{1}", queryString, preloads[i]);
}
}
dataAccessURI = new Uri(stringBuilder.ToString());
try
{
result = TitanEntities.Execute<object>(dataAccessURI, "GET", true);
}
catch (Exception ex)
{
// log any errors to the console
WriteConsoleMessage(ex.Message, DataAccessEventType.Error);
}
return (QueryOperationResponse<object>)result;
resulting URI string is similar to this:
http://192.168.0.196/Service.svc/AliquotPreparation?$expand=Aliquot,AliquotPrepBatch,AnalysisPreparationMethod,Unit,Employee,Unit,PreparationMethod,State
To me this is a crappy implementation. It is all I could come up with right now though. The problem is, if there are A LOT of navigation properties the $expand command gets too long and the URI reaches it's character limit!
So how can I implement this through a service reference? I would greatly appreciate someone's help!!!

RavenDB and IDocumentStoreListener

I am trying to add an autoincrement to a simple model via an IDocumentStoreListener. I have found that the documentation regarding implementation of this feature is fairly sparse (any pointers would be greatly appreciated). I have been trying to follow this blog post but it appears to be out of date. When i try to implement
store = new EmbeddableDocumentStore
{
RunInMemory = true
}
.RegisterListener(new AuditableEntityListener(() => "Test User"))
.Initialize();
I get a build error stating "Cannot convert lambda expression to type Raven.Client.IDocumentStore because it is not a delegate type".
I managed to get it to build by using this code
store = new EmbeddableDocumentStore
{
RunInMemory = true
}
.RegisterListener(new AuditableEntityListener(store ))
.Initialize();
The code for the listener is as follows
public class PublicIdStoreListener : IDocumentStoreListener
{
HiLoKeyGenerator generator;
IDocumentStore store;
public PublicIdStoreListener(IDocumentStore store)
{
this.store = store;
generator = new HiLoKeyGenerator(store, "verifications", 1024);
}
public void AfterStore(string key, object entityInstance, RavenJObject metadata)
{
throw new NotImplementedException();
}
public bool BeforeStore(string key, object entityInstance, RavenJObject metadata)
{
var verification = entityInstance as VerifyAccountModel;
if (verification.PublicId == "0")
{
verification.PublicId = generator.GenerateDocumentKey(store.Conventions, entityInstance);
}
return false;
}
}
However, when i run the application it hits the PublicIdStoreListener when any document is stored, not just the VerifyAccountModel, which causes the application to throw an exception.
I was wondering if anyone can point me in the right direction on this as I am confused as to how this is actually supposed to be implemented. Thanks in advance.
EDIT
I updated the documentlistener to the following
public bool BeforeStore(string key, object entityInstance, RavenJObject metadata)
{
if (entityInstance.GetType() == new VerifyAccountModel().GetType())
{
var verification = entityInstance as VerifyAccountModel;
if (verification.PublicId == "0")
{
verification.PublicId = generator.GenerateDocumentKey(store.Conventions, entityInstance);
}
}
return true;
}
UPDATE
I figured out that i cant attach the store via RegisterListener in the same line that it is instantiated. It has to be done afterwards otherwise the store is still null when passed in. Thank you for your help.
I am not sure if there's a way to register the listener to only fire for certain types, but you can certainly structure your code to only process VerifyAccountModel entities.
var verification = entityInstance as VerifyAccountModel;
if (verification == null)
return false; // We can't do anything, just let it pass through
Also, my understanding is that you should return true when you make a change, false if no change was made. This determines whether the entity needs to be re-serialized. If that is correct, the whole thing might be restructured as follows.
var verification = entityInstance as VerifyAccountModel;
if (verification != null && verification.PublicId == "0")
{
verification.PublicId = generator.GenerateDocumentKey(store.Conventions, entityInstance);
return true; // change made, re-serialize
}
return false; // no change made

How do I convert an HttpRequestBase into an HttpRequest object?

inside my ASP.NET MVC controller, I've got a method that requires an HttpRequest object. All I have access to is an HttpRequestBase object.
Is there anyway I can somehow convert this?
What can/should I do??
You should always use HttpRequestBase and HttpResponseBase in your application as opposed to the concrete versions which are impossible to test (without typemock or some other magic).
Simply use the HttpRequestWrapper class to convert as shown below.
var httpRequestBase = new HttpRequestWrapper(Context.Request);
Is it your method, so you can re-write it to take HttpRequestBase? If not, you can always get the current HttpRequest from HttpContext.Current.HttpRequest to pass on. However, I often wrap access to the HttpContext inside a class like mentioned in ASP.NET: Removing System.Web Dependencies for better unit testing support.
You can just use
System.Web.HttpContext.Current.Request
The key here is that you need the full namespace to get to the "correct" HttpContext.
I know it's been 4 years since this question was asked, but if this will help somebody, then here you go!
(Edit: I see that Kevin Hakanson already gave this answer...so hopefully my response will help those people who just read answers and not comments.) :)
To get HttpRequest in ASP.NET MVC4 .NET 4.5, you can do the following:
this.HttpContext.ApplicationInstance.Context.Request
Try to use/create a HttpRequestWrapper using your HttpRequestBase.
Typically when you need to access the HttpContext property in a controller action, there is something you can do better design wise.
For example, if you need to access the current user, give your action method a parameter of type IPrincipal, which you populate with an Attribute and mock as you wish when testing. For a small example on how, see this blog post, and specifically point 7.
There is no way to convert between these types.
We had a similar case. We rewrote our classes/web services methods so that they use HttpContextBase, HttpApplicationStateBase, HttpServerUtilityBase, HttpSessionStateBase... instead of the types of close name without the "Base" suffix (HttpContext, ... HttpSessionState). They are a lot easier to handle with home-made mocking.
I feel sorry you couldn't do it.
This is an ASP.Net MVC 3.0 AsyncController which accepts requests, converts the inbound HttpRequestBase MVC object to a System.Web.HttpWebRequest. It then sends the request asynchronously. When the response comes back, it converts the System.Web.HttpWebResponse back into an MVC HttpResponseBase object which can be returned via the MVC controller.
To answer this question explicitly, I guess you'd only be interested in the BuildWebRequest() function. However, it demonstrates how to move through the whole pipeline - converting from BaseRequest > Request and then Response > BaseResponse. I thought sharing both would be useful.
Through these classes, you can have an MVC server which acts as a web proxy.
Hope this helps!
Controller:
[HandleError]
public class MyProxy : AsyncController
{
[HttpGet]
public void RedirectAsync()
{
AsyncManager.OutstandingOperations.Increment();
var hubBroker = new RequestBroker();
hubBroker.BrokerCompleted += (sender, e) =>
{
this.AsyncManager.Parameters["brokered"] = e.Response;
this.AsyncManager.OutstandingOperations.Decrement();
};
hubBroker.BrokerAsync(this.Request, redirectTo);
}
public ActionResult RedirectCompleted(HttpWebResponse brokered)
{
RequestBroker.BuildControllerResponse(this.Response, brokered);
return new HttpStatusCodeResult(Response.StatusCode);
}
}
This is the proxy class which does the heavy lifting:
namespace MyProxy
{
/// <summary>
/// Asynchronous operation to proxy or "broker" a request via MVC
/// </summary>
internal class RequestBroker
{
/*
* HttpWebRequest is a little protective, and if we do a straight copy of header information we will get ArgumentException for a set of 'restricted'
* headers which either can't be set or need to be set on other interfaces. This is a complete list of restricted headers.
*/
private static readonly string[] RestrictedHeaders = new string[] { "Accept", "Connection", "Content-Length", "Content-Type", "Date", "Expect", "Host", "If-Modified-Since", "Range", "Referer", "Transfer-Encoding", "User-Agent", "Proxy-Connection" };
internal class BrokerEventArgs : EventArgs
{
public DateTime StartTime { get; set; }
public HttpWebResponse Response { get; set; }
}
public delegate void BrokerEventHandler(object sender, BrokerEventArgs e);
public event BrokerEventHandler BrokerCompleted;
public void BrokerAsync(HttpRequestBase requestToBroker, string redirectToUrl)
{
var httpRequest = BuildWebRequest(requestToBroker, redirectToUrl);
var brokerTask = new Task(() => this.DoBroker(httpRequest));
brokerTask.Start();
}
private void DoBroker(HttpWebRequest requestToBroker)
{
var startTime = DateTime.UtcNow;
HttpWebResponse response;
try
{
response = requestToBroker.GetResponse() as HttpWebResponse;
}
catch (WebException e)
{
Trace.TraceError("Broker Fail: " + e.ToString());
response = e.Response as HttpWebResponse;
}
var args = new BrokerEventArgs()
{
StartTime = startTime,
Response = response,
};
this.BrokerCompleted(this, args);
}
public static void BuildControllerResponse(HttpResponseBase httpResponseBase, HttpWebResponse brokeredResponse)
{
if (brokeredResponse == null)
{
PerfCounters.ErrorCounter.Increment();
throw new GriddleException("Failed to broker a response. Refer to logs for details.");
}
httpResponseBase.Charset = brokeredResponse.CharacterSet;
httpResponseBase.ContentType = brokeredResponse.ContentType;
foreach (Cookie cookie in brokeredResponse.Cookies)
{
httpResponseBase.Cookies.Add(CookieToHttpCookie(cookie));
}
foreach (var header in brokeredResponse.Headers.AllKeys
.Where(k => !k.Equals("Transfer-Encoding", StringComparison.InvariantCultureIgnoreCase)))
{
httpResponseBase.Headers.Add(header, brokeredResponse.Headers[header]);
}
httpResponseBase.StatusCode = (int)brokeredResponse.StatusCode;
httpResponseBase.StatusDescription = brokeredResponse.StatusDescription;
BridgeAndCloseStreams(brokeredResponse.GetResponseStream(), httpResponseBase.OutputStream);
}
private static HttpWebRequest BuildWebRequest(HttpRequestBase requestToBroker, string redirectToUrl)
{
var httpRequest = (HttpWebRequest)WebRequest.Create(redirectToUrl);
if (requestToBroker.Headers != null)
{
foreach (var header in requestToBroker.Headers.AllKeys)
{
if (RestrictedHeaders.Any(h => header.Equals(h, StringComparison.InvariantCultureIgnoreCase)))
{
continue;
}
httpRequest.Headers.Add(header, requestToBroker.Headers[header]);
}
}
httpRequest.Accept = string.Join(",", requestToBroker.AcceptTypes);
httpRequest.ContentType = requestToBroker.ContentType;
httpRequest.Method = requestToBroker.HttpMethod;
if (requestToBroker.UrlReferrer != null)
{
httpRequest.Referer = requestToBroker.UrlReferrer.AbsoluteUri;
}
httpRequest.UserAgent = requestToBroker.UserAgent;
/* This is a performance change which I like.
* If this is not explicitly set to null, the CLR will do a registry hit for each request to use the default proxy.
*/
httpRequest.Proxy = null;
if (requestToBroker.HttpMethod.Equals("POST", StringComparison.InvariantCultureIgnoreCase))
{
BridgeAndCloseStreams(requestToBroker.InputStream, httpRequest.GetRequestStream());
}
return httpRequest;
}
/// <summary>
/// Convert System.Net.Cookie into System.Web.HttpCookie
/// </summary>
private static HttpCookie CookieToHttpCookie(Cookie cookie)
{
HttpCookie httpCookie = new HttpCookie(cookie.Name);
foreach (string value in cookie.Value.Split('&'))
{
string[] val = value.Split('=');
httpCookie.Values.Add(val[0], val[1]);
}
httpCookie.Domain = cookie.Domain;
httpCookie.Expires = cookie.Expires;
httpCookie.HttpOnly = cookie.HttpOnly;
httpCookie.Path = cookie.Path;
httpCookie.Secure = cookie.Secure;
return httpCookie;
}
/// <summary>
/// Reads from stream into the to stream
/// </summary>
private static void BridgeAndCloseStreams(Stream from, Stream to)
{
try
{
int read;
do
{
read = from.ReadByte();
if (read != -1)
{
to.WriteByte((byte)read);
}
}
while (read != -1);
}
finally
{
from.Close();
to.Close();
}
}
}
}
It worked like Kevin said.
I'm using a static method to retrieve the HttpContext.Current.Request, and so always have a HttpRequest object for use when needed.
Here in Class Helper
public static HttpRequest GetRequest()
{
return HttpContext.Current.Request;
}
Here in Controller
if (AcessoModel.UsuarioLogado(Helper.GetRequest()))
Here in View
bool bUserLogado = ProjectNamespace.Models.AcessoModel.UsuarioLogado(
ProjectNamespace.Models.Helper.GetRequest()
);
if (bUserLogado == false) { Response.Redirect("/"); }
My Method UsuarioLogado
public static bool UsuarioLogado(HttpRequest Request)

Recommended way to create an ActionResult with a file extension

I need to create an ActionResult in an ASP.NET MVC application which has a .csv filetype.
I will provide a 'do not call' email list to my marketing partners and i want it to have a .csv extension in the filetype. Then it'll automatically open in Excel.
http://www.example.com/mailinglist/donotemaillist.csv?password=12334
I have successfully done this as follows, but I want to make sure this is the absolute best and recommended way of doing this.
[ActionName("DoNotEmailList.csv")]
public ContentResult DoNotEmailList(string username, string password)
{
return new ContentResult()
{
Content = Emails.Aggregate((a,b)=>a+Environment.NewLine + b),
ContentType = "text/csv"
};
}
This Actionmethod will respond to the above link just fine.
I'm just wondering if there is any likelihood of any unexpected conflict of having the file extension like this with any different version of IIS, any kind of ISAPI filter, or anything else I cant think of now.
I need to be 100% sure because I will be providing this to external partners and don't want to have to change my mind later. I really cant see any issues, but maybe theres something obscure - or another more "MVC" like way of doing this.
I used the FileContentResult action to also do something similar.
public FileContentResult DoNotEmailList(string username, string password)
{
string csv = Emails.Aggregate((a,b)=>a+Environment.NewLine + b);
byte[] csvBytes = ASCIIEncoding.ASCII.GetBytes( csv );
return File(csvBytes, "text/csv", "DoNotEmailList.csv");
}
It will add the content-disposition header for you.
I think your Response MUST contain "Content-Disposition" header in this case. Create custom ActionResult like this:
public class MyCsvResult : ActionResult {
public string Content {
get;
set;
}
public Encoding ContentEncoding {
get;
set;
}
public string Name {
get;
set;
}
public override void ExecuteResult(ControllerContext context) {
if (context == null) {
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = "text/csv";
if (ContentEncoding != null) {
response.ContentEncoding = ContentEncoding;
}
var fileName = "file.csv";
if(!String.IsNullOrEmpty(Name)) {
fileName = Name.Contains('.') ? Name : Name + ".csv";
}
response.AddHeader("Content-Disposition",
String.Format("attachment; filename={0}", fileName));
if (Content != null) {
response.Write(Content);
}
}
}
And use it in your Action instead of ContentResult:
return new MyCsvResult {
Content = Emails.Aggregate((a,b) => a + Environment.NewLine + b)
/* Optional
* , ContentEncoding = ""
* , Name = "DoNotEmailList.csv"
*/
};
This is how I'm doing something similar. I'm treating it as a download:
var disposition = String.Format(
"attachment;filename=\"{0}.csv\"", this.Model.Name);
Response.AddHeader("content-disposition", disposition);
This should show up in the browser as a file download with the given filename.
I can't think of a reason why yours wouldn't work, though.
The answer you accepted is good enough, but it keeps the content of the output in memory as it outputs it. What if the file it generates is rather large? For example, when you dump a contents of the SQL table. Your application could run out of memory. What you do want in this case is to use FileStreamResult. One way to feed the data into the stream could be using pipe, as I described here

Resources