Restricting access to certain views - Beginner - grails

I have a Domain class and Controller called Person. This Controller has 4 views. create.gsp, remove.gsp, show.gsp, showdetail.gsp.
What i want to do is to allows read permission for showdetail.gsp to all viewers and restrict access to create.gsp, remove.gsp and show.gsp views. Only the Administrator should be able to access these pages. How can i do this in Grails?

You can use security filters to do that or you can use Spring Security Core grails plugin.
If you can want to use security filters, you can do the following:
Create session for each user at the time of login.
session["user"] = "rohit" // user name
Add security Filters:
PersonFilters(controller: 'person', action: 'create') {
before = {
// code to be change
if(!session["user"]) {
//flash.message = "Your session has been expired. Please login to continue."
redirect(controller: "login", action:"index")
return false;
} else if(session["user"] && session["user"] != "admin"){
// redirect to some action
// redirect(controller: "", action:"index")
return false;
}
}
}
Similarly, you can add security filter for those actions that you don't want to show to any user except the admin.

First of all, user would need to login and after successful authentication, session should be maintained properly. And then in controller filter, you could write an interceptor which can allow access to certain actions based on authorization rules.
All these and much more is already done along with UI part in nimble plugin, it is very easy.
And read this document:
http://snimavat.github.io/nimble/guide/leverage.html#accesscontrol

Related

2 factor authentication in Grails

I want a way to implement 2 factor authentication. When the user enters their username and password it moves them to another page where they are granted a temporary Role and then they are asked to enter a security token which will be sent to them via email. authtoken image
login image
I have been there and I discovered that its pretty easy to implement this mostly in custom code if you are in conventional old school web app. I think there is something in spring that allows you to do this, but depending on your application it might be just so much easier to do this by yourself and not even that difficult.
One way is to create grails filter that covers all routes that are affected by this. For example all except login page and the second page after that. This filter will check the temp role and if found, it will redirect to the second auth page. Otherwise it lets the request pass. Could be something like this:
twoFactorCheck(controller: '*', controllerExclude: 'privacyPolicy|login|logout|2factor', action: '*') {
before = {
if (process2factorAuthNeed()) {
return true;
}
redirect(controller: "2factor", action: "index");
return false;
}
}
Then another thing you need is an implementation of custom UserDetailsService where you can capture the first normal authentication and set the temp role for the user. This is covered here: https://grails-plugins.github.io/grails-spring-security-core/2.0.x/guide/userDetailsService.html if you are on grails 2.* but your spring security plugins version user guide will have this entry as well.

Allow a user to login/signin between actions

I have a requirement, where we allow a user to access a URL without logging in until a certain point.
For example:
OnlineBooking/Services. They can select the services, this populates the viewmodel and then brings up a confirm view OnlineBooking/Confirm allowing a user to add an email address etc. Which then generates a ViewModel.
My question is, how can I check the user exists, if it does. Redirect to the login view (Account Controller - Login Action), allow them to login, then redirect back to this action without losing the viewmodel in this action? This may not even be possible, if not how can I achieve this?
Thanks for any advice.
Example:
public async Task<IActionResult> Confirm(BookingViewModel bookingViewModel)
{
try
{
var matchedUser = await _userManager.FindByEmailAsync(bookingViewModel.Email);
if (matchedUser == null) //User does not have an existing account, so register them.
{
//This is fine
}
else
{
//Need to redirect to login, then back to here without losing the viewmodel
}
}
My question is, how can I check the user exists, if it does. Redirect to the login view (Account Controller - Login Action), allow them to login, then redirect back to this action without losing the viewmodel in this action
My personal preference would be to not even redirect the user. If you need them to login, popup a dialog asking them to login. Once the user is logged in (ajax), enable the button to continue..
Not sure if it's the best practice, but what about storing the View Model in the session before you redirect to the login page? Then at the beginning of the confirm action, check if that session variable exists.
Session variables can be set like this:
Session["MyViewModel"] = viewModel;
and retrieved like so:
MyViewModel viewModel = (MyViewModel)Session["MyViewModel"]

How to perform one time action on first login using ASP.NET MVC5 Windows Authentication

I am creating web application using ASP.NET MVC5 framework with 'windows authentication' - i.e. I am creating an intranet application authenticating users against active directory.
When new user is defined in company's active directory, I'd need to catch his first login and redirect him onto profile page, where user is prompted to fill some info.
I am able to catch user's first login by simple look into the database, if the user has here his own record in some table. If not, the user is here first time and I can create him such record.
Here comes the deal - after extensive searching through possibilities it seems the only "reasonable" way how to do it is via custom AuthenticationFilter - specifically to put the DB check logic into
OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
method.
The reason why I put "reasonable" into the quotes is that on one hand, this approach fits into the MVC philosophy - by which I mean that it is not some 'hack' approach.
On the other hand - since I am using windows authentication, there is effectively no Login action in any controller. User can type whatever 'www.mysite.com/controller/action' url and if not logged in, there is no redirect to login page, the windows security box just appears prompting for credentials. Which means that I have to register my custom authentication filter globally to cover all controller/action patterns. That would mean that the DB check would be performed each and every request - I don't like this. I am not sure how much performance hit this could make, but also it doesn't seem right from design point of view.
The last thing I've tried was to use my authentication filter to catch 'unauthenticated' users and redirect them to some 'Login' action - but here I found that the windows security box is appearing before even the authentication filter is fired, so technically my custom authentication filter never catches an unauthenticated user.
So my question is - is there a better approach how to step in the logging process with one time action? Or I can use what I have - i.e. globally registered authentication filter performing DB check every request ?
Thanks
Finally I have some working solution for this.
The problem:
MVC5 APS.NET intranet application using windows authentication. After successful login against active directory we want to find out if the user is here for the first time and if so, create him record in application database.
The solution:
Since I am at the end of the day interested only in authenticated and authorized users I've created a globally registered action filter i.e. filter which will be applied to every single controller/action combination after successful authentication/authorization.
Inside this filter I am checking if the current session has flag IsNewSession set to true. If so, I am performing that check against application DB. That way even if the action filter is invoked each request I am doing roundtrip into database only once - during user's first request.
The implementation:
public class DbCheckFilter : ActionFilterAttribute
{
private AppDbContext db = new AppDbContext();
//we are overriding OnActionExecuting method since this one
//is executed prior the controller action method itself
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//is this a new session
if (filterContext.HttpContext.Session.IsNewSession)
{
//we are storing users in db based on their active directory
//Guid - therefore we need to get 'UserPrincipal' object
//instead of 'WindowsPrincipal' provided by filterContext
using (var principalContext = new PrincipalContext(ContextType.Domain))
{
var principal = UserPrincipal.FindByIdentity(principalContext, filterContext.HttpContext.User.Identity.Name);
if (principal != null)
{
//finally we perform the DB check itself
if (!CreateUserInDbIfNew(principal.Guid.Value, principal.DisplayName))
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
else
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
base.OnActionExecuting(filterContext);
}
I believe this is what you're looking for. Your db check will only happen once at this point. You can add this method to your Global.asax like so, which works with Windows Auth once authorized...
protected void Application_AuthorizeRequest(object sender, EventArgs e)
{
// Do your check here
// Do something
}

Enforcing a choice prior to viewing MVC and Web Forms pages

I'm working on a system that needs to know a user's choice before they enter a site. Up till now the choice has been stored in a cookie and checked by JavaScript on the page load - if the cookie doesn't exist then a dialog is shown and the user makes the choice.
Although we'd normally expect the user to arrive at the homepage of the application, they can legally follow a URL to any page within the application, so the JavaScript to check the choice exists on every page.
This has caused problems (almost always fixed by clearing cookies) so we're switching to store the choice in the database. What we need is a neat way of making sure that all pages (MVC and Web Forms) check that the choice has been made and, if it hasn't, either display a dialog or redirect to a page where the choice can be made.
The main thing bothering me is that to cause a redirect using MVC, I need to return a RedirectResult, and this can only be done from an Action. I don't want every action to have code regarding this check - it seems like the kind of thing that should be possible from a base controller (in the same way a base page could cause a Response.Redirect.
Can anyone suggest a good way for all pages to perform a check on the database and then either cause a redirect or show a dialog?
The main thing bothering me is that to cause a redirect using MVC, I
need to return a RedirectResult, and this can only be done from an
Action.
Oh not at all. You could also redirect from a custom action filters.
For example you could write a custom IAuthorizationFilter that will check whether the user made the necessary choice and if not redirect to some given page. The check could be done against a cookie, database or wherever you decide to persist this information:
public class EnsureChoiceHasBeenMadeAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// get the current user
var user = filterContext.HttpContext.User;
if (user.Identity.IsAuthenticated && !UserMadeAChoice(user.Identity.Name))
{
// if the current user is authenticated and he didn't made a choice
// redirect him to some page without even attempting to execute
// the controller action that he requested
var values = new RouteValueDictionary(new
{
controller = "home",
action = "index"
});
filterContext.Result = new RedirectToRouteResult(values);
}
}
private bool UserMadeAChoice(string username)
{
throw new NotImplementedException();
}
}
Now you have different possibilities:
You decorate the controllers/actions that you want to perform this check with the [EnsureChoiceHasBeenMade] attribute
You register the action filter as a global action filter so that it applies to absolutely all actions
You write a custom filter provider in order to dynamically apply the action filter to some actions based on some dynamic values (you have access to the HttpContext).

How do you define the login page you want unauthorized user to be redirected to

I have decorated my controller with an Authorize attribute, as so:
[Authorize(Roles="ExecAdmin")]
If I try to go to that controller after logging in as a user who is not ExecAdmin, it does appear to be attempting to redirect to a login page. BUT, the page it is attempting to redirect to is not my login page, it is a view called LogOnUserControl.ascx. This is a partial view that is not displayed by my login page.
I have no idea why it is doing this -- or maybe it is trying to redirect to some other page altogether, one which does display LogOnUserControl.ascx. Or maybe it is looking for anything with "LogOn" in the name? (Though the name of my login view is LogOn.aspx...)
How can I tell it what page to redirect to?
UPDATE: I do have this in the global.asax
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == "")
{
return;
}
FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
return;
}
string[] roles = authTicket.UserData.Split(new char[] { ';' });
//Context.ClearError();
if (Context.User != null)
{
Context.User = new System.Security.Principal.GenericPrincipal(Context.User.Identity, roles);
}
}
... since I am using a non-standard way of defining roles; i.e., I am not using ASP.NET membership scheme (with role providers defined in web.config, etc.). Instead I am setting roles this way:
// get user's role
string role = rc.rolesRepository.GetUserType(rc.loginRepository.GetUserID(userName)).ToString();
// create encryption cookie
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
userName,
DateTime.Now,
DateTime.Now.AddMinutes(120),
createPersistentCookie,
role //user's role
);
// add cookie to response stream
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
System.Web.HttpCookie authCookie = new System.Web.HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);
(This is called after the user has been validated.)
Not sure how this could be impacting the whole thing, though ...
UPDATE: Thanks to Robert's solution, here's how I solved it -- extend AuthorizeAttribute class:
public class AuthorizeAttributeWithMessage : AuthorizeAttribute
{
private string _message = "";
public string Message
{
get {
return _message;
}
set {
_message = value;
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
// user is logged in but wrong role or user:
filterContext.Controller.TempData.Add("Message", Message);
}
base.HandleUnauthorizedRequest(filterContext);
}
}
Then in the LogOn view:
<%
if (HttpContext.Current.Request.IsAuthenticated)
{
// authenticated users should not be here
Response.Redirect("/Home/Index");
}
%>
And in the home page view:
<% if (TempData != null && TempData.Count > 0 && TempData.ContainsKey("Message"))
{ %>
<div class="largewarningtext"><%= TempData["Message"]%></div>
<% } %>
And atop the affected controllers:
[AuthorizeAttributeWithMessage(Roles = "Consultant,ExecAdmin", Message = "You do not have access to the requested page")]
This has the advantage of ALWAYS redirecting any authenticated user who ends up on Logon.aspx -- authenticated users should not be there. If there is a message in the TempData, it will print it out on the home page; if not, it will at least have done the redirect.
Login page is configured within web.config file.
But you probably already know that. The real problem here is a bit more complicated. I guess you're onto something very interesting here, since Login page barely authenticates a user. It doesn't check its authorization for a particular resource (which is your case here where authorization fails) so this shouldn't redirect to login page in the first place.
Checking AuthorizeAttribute source code, you should get a 401: Unauthorize Request response from the server. It doesn't redirect you to the login page (as I anticipated in the previous paragraph, since login is too stupid for that. So there most be something else in your code that doesn't work as it should.
Edit
As this page states:
If the site is configured to use ASP.NET forms authentication, the 401 status code causes the browser to redirect the user to the login page.
Based on this information it's actually forms authentication that sees this 401 and redirects to login (configured as you described in the comment).
But. It would be nice to present some message to the user why they were redirected to login page in the first place. No built-in functionality for that... Still this knowledge doesn't solve your problem, does it...
Edit 2
There are two patterns you can take that actually look very similar to the user, but work diferently on the server.
Simpler one
Write your own authorization attribute (simply inherit from the existing one and add an additional public property Message to it), where you can also provide some sort of a message with attribute declaration like ie.
[AuthorizeWithMessage(Role = "ExecAdmin", Message = "You need at least ExecAdmin permissions to access requested resource."]
Your authorization attribute should populate TempData dictionary with the provided message (check documentation about TempData that I would use in this case) and then call into base class functionality.
change your login view to check for the message in the TempData dictionary. If there is one, you can easily present it to the already authenticated user (along with a link to some homepage that they can access), so they will know why they are presented with a login.
Complex one
create your own authorization filter (not inheriting from the original) and provide your own redirection to some authorization login view that would serve the login in case user has insufficient rights.
create your custom login view that can in this case be strong type. Your authorization filter could populate it with the correct model. This model will include the message string and it can also provide the route link to a page where a user can go.
custom configuration classes that serve this configuration of custom login page.
You could as well configure various different route definitions based on user rights. So for some rights they'd be presented with some page, but if they have some other rights, their route would point to a different route.
Which one to choose?
Go with the simpler one if it satisfies your needs, but if you want more control of the whole process I'd rather go with the complex one. It's not so complicated and it would give you full control of the insufficient login process. You could make it a much better experience for the users.

Resources