Rhino.Security: second-level cache is never hit for DetachedCriteria - asp.net-mvc

I've implemented a solution which involves Rhino.Security to manage user/roles/permissions.
Since I want to check if a user is authorized to access a controller action, I've implemented a custom action filter:
public class AuthorizationAttribute : ActionFilterAttribute
{
CustomPrincipal currentPrincipal = (CustomPrincipal)filterContext.HttpContext.User;
var actionName = filterContext.ActionDescriptor.ActionName;
var controllerName = filterContext.Controller.GetType().Name;
var operation = string.Format("/{0}/{1}", controllerName, actionName);
if (!securityService.CheckAuthorizationOnOperation(currentPrincipal.Code, operation))
{
filterContext.Controller.TempData["ErrorMessage"] = string.Format("You are not authorized to perform operation: {0}", operation);
filterContext.Result = new HttpUnauthorizedResult();
}
}
CheckAuthorizationOnOperation calls Rhino.Security to check if the user is allowed to the operation specified:
AuthorizationService.IsAllowed(user, operation);
Everything works properly but I've noticed that the second-level cache is never hit when the query called by IsAllowed is executed.
I've investigated and I've seen that the framework (Rhino.Security) uses a DetachedCriteria. These are the 2 procedures called:
public Permission[] GetGlobalPermissionsFor(IUser user, string operationName)
{
string[] operationNames = Strings.GetHierarchicalOperationNames(operationName);
DetachedCriteria criteria = DetachedCriteria.For<Permission>()
.Add(Expression.Eq("User", user)
|| Subqueries.PropertyIn("UsersGroup.Id",
SecurityCriterions.AllGroups(user).SetProjection(Projections.Id())))
.Add(Expression.IsNull("EntitiesGroup"))
.Add(Expression.IsNull("EntitySecurityKey"))
.CreateAlias("Operation", "op")
.Add(Expression.In("op.Name", operationNames));
return FindResults(criteria);
}
private Permission[] FindResults(DetachedCriteria criteria)
{
ICollection<Permission> permissions = criteria.GetExecutableCriteria(session)
.AddOrder(Order.Desc("Level"))
.AddOrder(Order.Asc("Allow"))
.SetCacheable(true)
.List<Permission>();
return permissions.ToArray();
}
As you can see FindResults uses SetCacheable.
Everytime I refresh a page my action filter executes the procedures and the query is executed again, ignoring the cache (second-level).
Since I use extensively the cache and all the other calls work properly I would like to understand why this one doesn't work as expected.
Doing some research I've noticed that the second-level cache is used only if I call the function twice:
SecurityService.CheckAuthorizationOnOperation(currentPrincipal.Code, "/Users/Edit");
SecurityService.CheckAuthorizationOnOperation(currentPrincipal.Code, "/Users/Edit");
It seems that the cache for this particular situation only works if I am using the same session (nHibernate).
Is there anybody who can try to help me to figure out what's happening?
UPDATE:

Make sure you are doing the work inside a transaction
Verify that the Permission entity is cached too

There's an issue with this framework.
I opened a question on Google Groups, everyone knows about it, but it seems that the framework has been forgotten.

Related

How can I set a global variable in Razor Pages of ASP.NET Core?

I wanna to check if the browser is IE and do something in razor page.
I just made a function in razor page to do that.
However, I think use the function to check if the browser is IE in every razor page is redundant. For independent user, I just need to check this only one time and set a global variable that IsIE=true/false . And other page will easily know that if it is IE.
The question is how can I get/set a global variable in razor page?
Thank you.
————————————————
To #Neville Nazerane ,here is the function which to check if is IE:
#{
Boolean IsIE = false;
string UA = Context.Request.Headers["User-Agent"].ToString();
if (UA.Contains("Trident") || UA.Contains("MSIE"))
{
IsIE = true;
}
else
{
IsIE = false; ;
}
if (IsIE == true)
{
}
else
{
}
}
HTTP requests work by clients sending a request (with header and body) to your server. Your server can then access this info and send a response. This doesn't create any persistent (ongoing) connection between the server and client. This means there is no permanent link between your server and each client. Any global variable you declare will be global for your server's web application and will be common for every client.
What you are trying to do here is create variables isolated from each client's connection. Normally this is done with the help of Session or Cookie variable. But in this case, I don't see how this will improve any performance over the code you have written. In your code, you are trying to access the Http Headers from the request. Cookies and session variables are also accessed in a very similar way. If anything fetching directly from headers must have a slightly better performance. If you are trying to clean up your code so you don't have to write this on every page, services could be quite helpful.
You can create a class for service something like this:
public class AgentChecker
{
public bool IsIE { get; set; }
// makes sure check is done only when object is created
public AgentChecker(IHttpContextAccessor accessor)
{
string UA = accessor.HttpContext.Request.Headers["User-Agent"].ToString();
if (UA.Contains("Trident") || UA.Contains("MSIE"))
{
IsIE = true;
}
else
{
IsIE = false;
}
}
// optional to simplify usage further.
public static implicit operator bool(AgentChecker checker) => checker.IsIE;
}
In your startup class add the following:
// to access http context in a service
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// makes sure object is created once per request
services.AddScoped<AgentChecker>();
Once this is set up, in your view you can use:
#inject AgentChecker checker
#* if you didn't create the implicit operator, you can use if (checker.IsIE) *#
#if (checker)
{
<div>Is ie</div>
}
else
{
<div>not ie</div>
}
The inject goes at the top of any view page you would like to use this in. While this still creates a new object each request, it is cleaner to use and only creates one object no matter how many partial views you are using.

AuthorizationCodeWebApp.AuthResult result is NULL randomly! after AJAX call in MVC app

I need an advice. I wrote an MVC app to access Google Gmail API (using Google OAuth 2 Authentication), as explained in Google's Tutorial: https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc
The idea is - Index action initializes
private static AuthorizationCodeWebApp.AuthResult result;
after it performs Google OAuth2 authentication. That result object has Credentials.Token property that contains issued AccessToken and RefreshToken. Next, Index action returns a vew (rendered HTML) that has a DIV where I want to load Gmail messages asynchronously. When the page loads, there is a Javascript function that fires on page load even in a browser. Simple. That Javascript function makes an AJAX call to Gmail() action in HomeController. It makes these calls with a 1 minute interval (js timer). Since result object is marked as static it should be available to pass Credentials to Gmail API Service method that is implemented in GmailManager helper class:
return PartialView(GmailManager.GetGmail(result.Credential));
The problem is sometimes the result object is null inside the Gmail() action and the NullReferenceException is thrown
I dont understand why this is happening if the result was initialized in Index action and it is static, so it should be alive by the time the call is made to Gmail() action. It should never be null. If it was constantly null I would understand and try to debug and fix it, but it is random and I cannot understand the logic.
If someone understands what is happening please advice.
Below is my HomeController code:
public class HomeController : Controller
{
private static AuthorizationCodeWebApp.AuthResult result;
public async Task<ActionResult> Index(CancellationToken cancellationToken)
{
result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
if (result.Credential == null)
return new RedirectResult(result.RedirectUri);
if (!string.IsNullOrEmpty(result.Credential.Token.RefreshToken))
SaveRefreshTocken(result.Credential.Token.RefreshToken);
return View();
}
public ActionResult Gmail()
{
result.Credential.Token.RefreshToken = WebConfigurationManager.AppSettings["RefreshToken"];
return PartialView(GmailManager.GetGmail(result.Credential));
}
private static void SaveRefreshTocken(string refreshToken)
{
Configuration config = WebConfigurationManager.OpenWebConfiguration("~");
config.AppSettings.Settings["RefreshToken"].Value = refreshToken;
config.Save();
}
}

MVC3: Session_Start Fires twice when testing for Roles

I need to do some authentication for a web app with MVC3. The customer would like there to be a generic page to show if they do not have any of the role groups in windows AD that are allowed to use the app. I found a pretty simple way to do it, but just curious if it is a valid way or if there is something better out there.
Basically in the Session_Start in the global I am checking for User.IsInRole() and if that returns false then I do a Response.Redirect(). This question is: after it his the code in the IF statement and hits the Response.Redirect() code then it hits the session one more time before it goes to the AccessDenied page in the root of the app. Is this okay? Will it cause any issues If they are valid and does not enter the If to do the response.redirect?
//if (!User.IsInRole("test_user"))
//{
// Response.Redirect("~/AccessDenied.aspx", true);
//}
I would recommend you to write your Authorization filter for MVC3 and do this type of logic there:
public class RoleFilter: AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext filterContext)
{
if (!User.IsInRole("test_user"))
{
filterContext.HttpContext.Response.StatusCode = 302;
filterContext.Result = new RedirectResult("~/AcessDenied.aspx");
}
}
}
Also I wouldn't recommend you to use Response.Redirect because it aborts current thread.

Getting the Id of an error in Elmah after calling .Raise()

I'm working on an MVC3 application and I'm using Elmah to handle my error logging. What I want in my application is to carry the Elmah Id onto the custom error page as I will provide a link which allows a user to specifically report it in the event that it is a repeat error (in their opinion).
Now, I've read similar questions on here and they suggest adding the following code (or similar) to the Global.asax.cs file:
void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args)
{
string sessionId = Session.SessionID;
Session["ElmahId_" + sessionId] = args.Entry.Id;
}
This is what I'm using at the moment, with the SessionID allowing for added flexibility in making the Session stored object unique. However, this may still cause issues if more than one error occurs at (virtually) the same time.
Instead, I decided to work on my own HandleErrorAttribute that looks something like this:
public class ElmahHandleErrorAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
throw new ArgumentNullException("filterContext");
if (filterContext.IsChildAction && (!filterContext.ExceptionHandled
&& filterContext.HttpContext.IsCustomErrorEnabled))
{
Elmah.ErrorSignal.FromCurrentContext().Raise(filterContext.Exception);
// get error id here
string errorId = null;
string areaName = (String)filterContext.RouteData.Values["area"];
string controllerName = (String)filterContext.RouteData.Values["controller"];
string actionName = (String)filterContext.RouteData.Values["action"];
var model = new ErrorDetail
{
Area = areaName,
Controller = controllerName,
Action = actionName,
ErrorId = errorId,
Exception = filterContext.Exception
};
ViewResult result = new ViewResult
{
ViewName = "Error",,
ViewData = new ViewDataDictionary<ErrorDetail>(model),
TempData = filterContext.Controller.TempData
};
filterContext.Result = result;
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
}
}
where ErrorDetail is a custom model which just has the public properties that are being set here as strings. This data can then be shown in the model for admin's at a quick glance and the errorId can be used to create the 'Report Error' link.
So my question is does anyone know of a way of getting the Id after the line
Elmah.ErrorSignal.FromCurrentContext().Raise(filterContext.Exception)
without using the Logged event in the global.asax.cs?
Any thoughts are much appreciated.
After reading Dupin's comments it seems logical that it isn't quite possible. I tried digging around the Elmah source code and came up with a couple of alternatives that might be worth sharing.
The obvious alternative is stick with my original option of using the Logged event:
void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args)
{
string sessionId = Session.SessionID;
Session["ElmahId_" + sessionId] = args.Entry.Id;
}
For a more direct solution it is possible to manually log the error with the following:
string errorId = Elmah.ErrorLog.GetDefault(HttpContext.Current)
.Log(new Elmah.Error(filterContext.Exception));
However, using this approach won't hit your filters or mail module and so on.
After doing a bit of thinking and a little more searching, I came up with a new compromise. Still using the logged event but I've found a way to create a new unique key that can be passed to the view, by adding my own data to the exception.
string loggingKey = "ElmahId_" + Guid.NewGuid().ToString();
filterContext.Exception.Data.Add("LoggingKey", loggingKey);
This way I can pass the exception in my view model, which has this key value in the Data collection. The logged event would be changed to something like this:
void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args)
{
string key = args.Entry.Error.Exception.Data["LoggingKey"].ToString();
Session[key] = args.Entry.Id;
}
Then in the view I get the key from the model to then pull the Id from the Session collection.
Maybe not very helpful but I suspect you can't get the error id at that point and you will need to use the logged event.
When you call
Elmah.ErrorSignal.FromCurrentContext().Raise(filterContext.Exception)
You're just raising the error. Depending on how you've configured ELMAH you might be logging the error or you might just send an email or a tweet.
There's no direct link between a raised error and an Id. That will only come with logging which, if you're feeling funny, you could be doing in multiple places and so creating multiple ids.
http://code.google.com/p/elmah/issues/detail?id=148#c3 is an identical request and a proposed patch on the Elmah project site
The solution above only works only if there is a Session object (website scenario). We needed it to work in an Azure WorkerRole, or a console / desktop app type setup. This solution will also work for web and save some session memory. There isn't a perfect solution, but one that worked for us to be able to log the error and retrieve the stored ID AND fire off an email is to:
Store the error using ErrorLog.Log(error) (see: Using ELMAH in a console application)
Raise the error skipping the logging (SQL or otherwise)
For the second part, we used the implementation of ElmahExtension given here: https://stackoverflow.com/a/2473580/476400
and REMOVED the following lines adding the logging:
(ErrorLog as IHttpModule).Init(httpApplication);
errorFilter.HookFiltering(ErrorLog); //removed!
The entire call from our client code looks like this:
ErrorLog errorLog = ErrorLog.GetDefault(null);
errorLog.ApplicationName = "YourAppName";
Error error = new Error(ex);
string errorResult = errorLog.Log(error);
Guid errorId = new Guid(errorResult);
ex.LogToElmah(); //this is just going to send the email
You might want to call that extention method something else, like RaiseToElmahNoStorage(), or something to indicate it is skipping the storage component.

Where to store logged user information on ASP.NET MVC using Forms Authentication?

I'm using ASP.NET MVC and Forms Authentication on my application. Basically I use FormsAuthentication.SetAuthCookie to login and FormsAuthentication.SignOut to logout.
In the HttpContext.Current.User.Identity I have stored the user name but I need more info about the logged user. I don't want to store my entire User obj in the Session because it might be big and with much more infomation than I need.
Do you think it's a good idea to create like a class called LoggedUserInfo with only the attributes I need and then add it to the Session variable? Is this a good approach?
Or do you have better ideas?
I use this solution:
ASP.NET 2.0 Forms authentication - Keeping it customized yet simple
To summarize: I created my own IPrincipal implementation. It is stored in HttpContext.Current.Cache. If it is somehow lost, I have username from client side authorization cookie and can rebuild it. This solution doesn't rely on Session, which can be easily lost.
EDIT
If you want to use your principal in your controller and make it testable, you can do this:
private MyPrincipal _myPrincipal;
MyPrincipal MyPrincipal
{
get
{
if (_myPrincipal == null)
return (MyPrincipal)User;
return _myPrincipal;
}
set
{
_myPrincipal = value;
}
}
In your test, you will set object prepared for testing. Otherwise it will be taken from HttpContext. And now I started thinking, why do I use Ninject to do it?
Store it server side in the session.
Eg.
// Make this as light as possible and store only what you need
public class UserCedentials
{
public string Username { get; set; }
public string SomeOtherInfo { get; set; }
// etc...
}
Then when they sign in just do the following to save the users info:
// Should make typesafe accessors for your session objects but you will
// get the point from this example
Session["UserCredentials"] = new UserCredentials()
{ Username = "SomeUserName", SomeOtherInfo = "SomeMoreData" };
Then whenever you need it fetch it:
UserCredentials user = (UserCredentials)(Session["UserCredentials"]);
I have written a couple of question/answers regarding doing custom authorization in MVC:
How to implement authorization checks in ASP.NET MVC based on Session data?
How does the Authorize tag work? - Asp.net Mvc
I actually like to use a CustomPrincipal and CustomIdentity which I set in the logon action method like
if (!String.IsNullOrEmpty(username) && !String.IsNullOrEmpty(password) && _authService.IsValidLogin(username, password))
{
User objUser = _userService.GetUserByName(username);
if (objUser != null)
{
//** Construct the userdata string
string userData = objUser.RoleName + "|" + objUser.DistrictID + "|" + objUser.DistrictName + "|" + objUser.ID + "|" + objUser.DisplayName;
HttpCookie authCookie = FormsAuthentication.GetAuthCookie(username, rememberMe.GetValueOrDefault());
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, userData);
authCookie.Value = FormsAuthentication.Encrypt(newTicket);
Response.Cookies.Add(authCookie);
return RedirectToAction("Index", "Absence");
}
else
{
return RedirectToAction("LogOn", "Account");
}
}
else
{
return RedirectToAction("LogOn", "Account");
}
Then in the custom principal you can have methods that access specific information you passed in to the constructor like
((CustomIdentity)((CustomPrincipal)HttpContext.Current.User).Identity).DisplayName;
where the DisplayName property is declared in the CustomIdentity class.
Well you will have to store these somewhere. Two main possible places though:
The server
You can either put them into Session. I suggest you do create a separate class that will hold only data that you actually need to avoid of wasting too much memory. Or you can also store into Cache that can end up in having many DB calls when there are huge amounts of concurrent users.
The client
In this case if you can limit the amount of data with a separate class, to that and use whatever way to serialize it and send it to the client. Either in a cookie or in URI (if length permits and cookies are disabled)...
Outcome of these thoughts:
the main thing here would be to create a separate class if you gain much memory resources this way. So that's the first thing you should do.

Resources