Display message to user on expired session when using wicket-auth-roles - session

Hi I have been unable to solve the following problem in Wicket 6.*:
In our webapp we are using wicket-auth-roles to manage authentication/authorization. When session expires, user should be redirected to a page set by getApplicationSettings().setPageExpiredErrorPage(SomePage.class) on his next action. However, if the user tries to access a page which doesn't allow guests, he is redirected to a login page skipping the PageExpiredPage altogether.
My question is - how can I display "Session has expired." message to the user?
Among other things, I have tried session.info("message") during onInvalidate phase of session's lifecycle, however the feedback message is then rendered on the first page after login (not on the login page).
Thank you for your anwsers.

You could use a RequestCycleListener to record when a PageExpiredException is thrown.
public class ExceptionMapperListener extends AbstractRequestCycleListener {
#Override
public IRequestHandler onException(RequestCycle cycle, Exception ex) {
if (ex instanceof PageExpiredException) {
// Record in session or request cycle
// OR
// Create a RenderPageRequestHandler yourself and add a page parameter
// See DefaultExceptionMapper#internalMap(Exception)
}
return null;
}
}
// In Application#init():
getRequestCycleListeners().add(new ExceptionMapperListener());
ORINAL ANSWER
(kept because it could still help...)
I haven't tried it myself since I don't use wicket-auth-roles, but try overriding the method AuthenticatedWebApplication#restartResponseAtSignInPage() with something like this:
if (isSessionExpired()) {
PageParameters params = new PageParameters();
params.add("showSessionExpired", true);
throw new RestartResponseAtInterceptPageException(getSignInPageClass(), params);
} else {
throw new RestartResponseAtInterceptPageException(getSignInPageClass());
}
And then in the SignInPageClass, display the desired message if the showSessionExpired page parameter is present.
I'm not sure how you implement isSessionExpired(), but you seem to have that part already covered.
OR
Depending on how you implemented isSessionExpired(), maybe you could do the following in your SignInPageClass:
if (sessionExpired()) {
session.info("message")
}

After bernie put me on the right path, I eventually figured out a solution to the problem:
First it is required to override RequestCycleListener:
public class SessionExpiredListener extends AbstractRequestCycleListener {
public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler) {
if (handler instanceof IPageRequestHandler) {
IPageRequestHandler pageHandler = (IPageRequestHandler) handler;
HttpServletRequest request = (HttpServletRequest) cycle.getRequest().getContainerRequest();
//check whether the requested session has expired
boolean expired = request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid();
//check whether the requested page can be instantiated with the current session
boolean authorized = Session.get().getAuthorizationStrategy().isInstantiationAuthorized(pageHandler.getPageClass());
if (expired && !authorized) {
throw new PageExpiredException("Session has expired!");
}
}
super.onRequestHandlerResolved(cycle, handler);
}
}
Check for authorized prevents the session-expired message from displaying on log-out or when accessing unprotected pages.
Finally, you must register your listener and PageRequestHandlerTracker in your WebApplication:
getRequestCycleListeners().add(new SessionExpiredListener());
getRequestCycleListeners().add(new PageRequestHandlerTracker());

Related

Invalid XSRF token at /oauth/token

Complete code for a Spring OAuth2 implementation of Multi-Factor Authentication has been uploaded to a file sharing site at this link. Instructions are given below to recreate the current problem on any computer in only a few minutes.
**CURRENT PROBLEM:**
Most of the authentication algorithm works correctly. The program does not break until the very end of the control flow shown below. Specifically, an `Invalid CSRF token found for http://localhost:9999/uaa/oauth/token` error is being thrown at the end of the **SECOND PASS** below. The app in the link above was developed by adding a custom `OAuth2RequestFactory`, `TwoFactorAuthenticationFilter` and `TwoFactorAuthenticationController` to the `authserver` app of this Spring Boot OAuth2 GitHub sample. **What specific changes need to be made to the code below in order to resolve this CSRF token error and enable 2-factor authentication?**
My research leads me to suspect that the `CustomOAuth2RequestFactory` (API at this link) might be the place to configure a solution because it defines ways for managing `AuthorizationRequest`s and `TokenRequest`s.
**This section of the official OAuth2 spec indicates that the `state` parameter of the request made to the authorization endpoint is the place where the `csrf` token is added.**
Also, the code in the link uses the Authorization Code Grant Type described at this link to the official spec, which would mean that Step C in the flow does not update the `csrf` code, thus triggering the error in Step D. (You can view the entire flow including Step C and Step D in the official spec.)
**CONTROL FLOW SURROUNDING THE CURRENT ERROR:**
The current error is being thrown during the **SECOND PASS** through `TwoFactorAuthenticationFilter` in the flowchart below. Everything works as intended until the control flow gets into the **SECOND PASS**.
The following flowchart illustrates the control flow of the two factor authentication process that is employed by the code in the downloadable app.
Specifically, the Firefox `HTTP` Headers for the sequence of `POST`s and `GET`s show that the same `XSRF` cookie is sent with every request in the sequence. The `XSRF` token values do not cause a problem until after the `POST /secure/two_factor_authentication`, which triggers server processing at the `/oauth/authorize` and `/oauth/token` endpoints, with `/oauth/token` throwing the `Invalid CSRF token found for http://localhost:9999/uaa/oauth/token` error.
To understand the relationship between the above control flow chart and the `/oauth/authorize` and `/oauth/token` endpoints, you can compare the above flowchart side by side with the chart for the single factor flow at the official spec in a separate browser window. The **SECOND PASS** above simply runs through the steps from the one-factor official spec a second time, but with greater permissions during the **SECOND PASS**.
**WHAT THE LOGS SAY:**
The HTTP Request and Response Headers indicate that:
1.) A POST to `9999/login` with the correct `username` and `password` submitted results in a redirect to `9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v` followed by a `GET 9999/secure/two_factor_authenticated`. One XSRF token remains constant across these exchanges.
2.) A POST to `9999/secure/two_factor_authentication` with the correct pin code sends the same `XSRF` token, and gets successfully re-directed to `POST 9999/oauth/authorize` and makes it into `TwoFactorAuthenticationFilter.doFilterInternal()` and proceeds to `request 9999/oauth/token`, but `9999/oauth/token` rejects the request because the same old XSRF token does not match a new `XSRF` token value, which was apparently created during the **FIRST PASS**.
One obvious difference between `1.)` and `2.)` is that the second `request 9999/oauth/authorize` in `2.)` does not contain the url parameters which are included in the first request to `9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v` in `1.)`, and also defined in the official spec. But it is not clear if this is causing the problem.
Also, it is not clear how to access the parameters to send a fully formed request from the `TwoFactorAuthenticationController.POST`. I did a SYSO of the `parameters` `Map` in the `HttpServletRequest` for the `POST 9999/secure/two_factor_authentication` controller method, and all it contains are the `pinVal` and `_csrf` variables.
You can read all the HTTP Headers and Spring Boot logs at a file sharing site by clicking on this link.
**A FAILED APPROACH:**
I tried #RobWinch's approach to a similar problem in the Spring Security 3.2 environment, but the approach does not seem to apply to the context of Spring OAuth2. Specifically, when the following `XSRF` update code block is uncommented in the `TwoFactorAuthenticationFilter` code shown below, the downstream request headers do show a different/new `XSRF` token value, but the same error is thrown.
if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
response.setHeader("XSRF-TOKEN"/*"X-CSRF-TOKEN"*/, token.getToken());
}
**This indicates that the `XSRF` configuration needs to be updated in a way that `/oauth/authorize` and `/oauth/token` are able to talk with each other and with the client and resource apps to successfully manage the `XSRF` token values.** Perhaps the `CustomOAuth2RequestFactory` is what needs to be changed to accomplish this. But how?
**RELEVANT CODE:**
The code for `CustomOAuth2RequestFactory` is:
public class CustomOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
public static final String SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME = "savedAuthorizationRequest";
public CustomOAuth2RequestFactory(ClientDetailsService clientDetailsService) {
super(clientDetailsService);
}
#Override
public AuthorizationRequest createAuthorizationRequest(Map authorizationParameters) {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession(false);
if (session != null) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
if (authorizationRequest != null) {
session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
return authorizationRequest;
}
}
return super.createAuthorizationRequest(authorizationParameters);
}
}
The code for `TwoFactorAuthenticationFilter` is:
//This class is added per: https://stackoverflow.com/questions/30319666/two-factor-authentication-with-spring-security-oauth2
/**
* Stores the oauth authorizationRequest in the session so that it can
* later be picked by the {#link com.example.CustomOAuth2RequestFactory}
* to continue with the authoriztion flow.
*/
public class TwoFactorAuthenticationFilter extends OncePerRequestFilter {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private OAuth2RequestFactory oAuth2RequestFactory;
//These next two are added as a test to avoid the compilation errors that happened when they were not defined.
public static final String ROLE_TWO_FACTOR_AUTHENTICATED = "ROLE_TWO_FACTOR_AUTHENTICATED";
public static final String ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED = "ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED";
#Autowired
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService);
}
private boolean twoFactorAuthenticationEnabled(Collection authorities) {
System.out.println(">>>>>>>>>>> List of authorities includes: ");
for (GrantedAuthority authority : authorities) {
System.out.println("auth: "+authority.getAuthority() );
}
return authorities.stream().anyMatch(
authority -> ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED.equals(authority.getAuthority())
);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("------------------ INSIDE TwoFactorAuthenticationFilter.doFilterInternal() ------------------------");
// Check if the user hasn't done the two factor authentication.
if (AuthenticationUtil.isAuthenticated() && !AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)) {
System.out.println("++++++++++++++++++++++++ AUTHENTICATED BUT NOT TWO FACTOR +++++++++++++++++++++++++");
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request));
/* Check if the client's authorities (authorizationRequest.getAuthorities()) or the user's ones
require two factor authenticatoin. */
System.out.println("======================== twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) is: " + twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) );
System.out.println("======================== twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) is: " + twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) );
if (twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) ||
twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities())) {
// Save the authorizationRequest in the session. This allows the CustomOAuth2RequestFactory
// to return this saved request to the AuthenticationEndpoint after the user successfully
// did the two factor authentication.
request.getSession().setAttribute(CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME, authorizationRequest);
// redirect the the page where the user needs to enter the two factor authentiation code
redirectStrategy.sendRedirect(request, response,
ServletUriComponentsBuilder.fromCurrentContextPath()
.path(TwoFactorAuthenticationController.PATH)
.toUriString());
return;
}
}
//THE NEXT "IF" BLOCK DOES NOT RESOLVE THE ERROR WHEN UNCOMMENTED
//if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
// CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
// this is the value of the token to be included as either a header or an HTTP parameter
// response.setHeader("XSRF-TOKEN", token.getToken());
//}
filterChain.doFilter(request, response);
}
private Map paramsFromRequest(HttpServletRequest request) {
Map params = new HashMap();
for (Entry entry : request.getParameterMap().entrySet()) {
params.put(entry.getKey(), entry.getValue()[0]);
}
return params;
}
}
**RE-CREATING THE PROBLEM ON YOUR COMPUTER:**
You can recreate the problem on any computer in only a few minutes by following these simple steps:
1.) Download the zipped version of the app from a file sharing site by clicking on this link.
2.) Unzip the app by typing: `tar -zxvf oauth2.tar(2).gz`
3.) launch the `authserver` app by navigating to `oauth2/authserver` and then typing `mvn spring-boot:run`.
4.) launch the `resource` app by navigating to `oauth2/resource` and then typing `mvn spring-boot:run`
5.) launch the `ui` app by navigating to `oauth2/ui` and then typing `mvn spring-boot:run`
6.) Open a web browser and navigate to `http : // localhost : 8080`
7.) Click `Login` and then enter `Frodo` as the user and `MyRing` as the password, and click to submit.
8.) Enter `5309` as the `Pin Code` and click submit. **This will trigger the error shown above.**
You can view the complete source code by:
a.) importing the maven projects into your IDE, or by
b.) navigating within the unzipped directories and opening with a text editor.
You can read all the HTTP Headers and Spring Boot logs at a file sharing site by clicking on this link.
One idea that popped to my head:
If session fixation is activated, a new session is created after the user authenticated successfully (see SessionFixationProtectionStrategy). This will also of course create a new csrf token if you use the default HttpSessionCsrfTokenRepository. Since you're mentioning the XSRF-TOKEN header I assume you use some JavaScript frontend. I could imagine that the original csrf token that was used for the login is stored and reused afterwards - which would not work because this csrf token is not valid anymore.
You may try disabling session fixation (http.sessionManagement().sessionFixation().none() or <session-management session-fixation-protection="none"/>) or re-get the current CSRF token after login.
Your CustomOAuth2RequestFactory is putting the previous request in-place of the current request. However, you are not updating the XSRF token in the old request when you make this switch. Here is what I would suggest for the updated CustomOAuth2Request:
#Override
public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession(false);
if (session != null) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
if (authorizationRequest != null) {
session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
//UPDATE THE STATE VARIABLE WITH THE NEW TOKEN. THIS PART IS NEW
CsrfToken csrf = (CsrfToken) attr.getRequest().getAttribute(CsrfToken.class.getName());
String attrToken = csrf.getToken();
authorizationRequest.setState(attrToken);
return authorizationRequest;
}
}
return super.createAuthorizationRequest(authorizationParameters);
}
I am revisiting this because my initial answer draft got downvoted. This version is further along the same path, which I believe is the right avenue of approach.

How to redirect after successfully login to other class in gwt and how to set the session timeout and its response after session timeout in gwt?

can you please tell me how to redirect to next page after successfully login in GWT?
I have created the service interface and its implementions but dont know how to redirect the page after login authentication.
Share your views guyz.
and also tell me how to get sessiontimeout and then redirect automatically to login page in gwt.
Thanx
You can use Window.Location.replace(String newURL) to switch pages.
As for the session timeout, that depens on what kind of session management you are using.
The easiest would be to include the information "session expired" in every RPC call you make, for example with a custom exception:
Server
public String myRpcCall() throws SessionExpiredException {
if(!SessionManager.isSessionValid()) { // depends on your session management
throw new SessionExpiredException;
}
return some_stuff(); // whatever you want to do
}
Client
public doCall() {
AsyncCallback<String> cb = new AsyncCallback<String>() {
#Override
public void onFailure(Throwable caught) {
if(caught instanceof SessionExpiredException) {
// inform the user and redirect to login page
Window.Location.replace("login.html");
}
// handle other errors
}
// TODO onSuccess(String)
};
service.myRpcCall(cb); // your rpc call goes here
}

Authorize attribute is letting ActionResult code execute

I have a custom attribute that I am using for Ajax requests to prevent the default FormsAuthentication workflow from happening (since it doesn't make sense for an Ajax request).
This is the custom authorize attribute:
public class AjaxAuthorize : AuthorizeAttribute {
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) {
UrlHelper urlHelper;
if (filterContext.HttpContext.Request.IsAjaxRequest()) {
urlHelper = new UrlHelper(filterContext.RequestContext);
filterContext.HttpContext.Response.StatusCode = 401;
//Don't let IIS7 be mean.
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
//Return JSON which tells the client where the login page exists if the user wants to authenticate.
filterContext.HttpContext.Response.Write(new JavaScriptSerializer().Serialize(
new {
LoginUrl = string.Format("{0}?ReturnURL={1}", FormsAuthentication.LoginUrl, urlHelper.Encode(filterContext.HttpContext.Request.Url.PathAndQuery))
}
));
filterContext.HttpContext.Response.End();
} else {
base.HandleUnauthorizedRequest(filterContext);
}
}
}
I have my ActionResult decorated with the custom authorize attribute like so:
[HttpPut]
[ValidateInput(false)]
[AjaxAuthorize]
public ActionResult Comment(string id, string comment) {
//stuff happens here.
}
An exception is thrown inside of my action result because I try to access information about a user that would only exist if you are logged in. But, I don't understand why this code is even being executed because I have my authorization attribute protecting the action.
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs.
filterContext.Result = new HttpUnauthorizedResult();
}
This is default implementation of HandleUnauthorizedRequest. Probably you need to assign your custom ActionResult that contains JSON you need and valid status code. I'm not sure direct processing request object from HTTP context would work.
I ended up returning an HTTP 418 (I have been wanting to use this for awhile) so that the Forms Authentication module didn't intercept the request. Then in my script I look for that status code and handle it as an unauthorized request and do the proper redirect.
Try calling base.HandleUnauthorizedRequest(filterContext); inside the IF block. Or just remove the ELSE block and always call base.HandleUnauthorizedRequest(filterContext);.

How to redirect to the login page when the session expires?

I have three JSF 2.0 web modules and I need to redirect to the login page when the session expires.
I have tried it using a HttpSessionListener, it is calling the sessionDestroyed() event method, but I am not able to forward/redirect the request in there. I think it is becasue there are no HttpServletRequest and HttpServletResponse objects.
I also tried it using a PhaseListener, but it results in a "too many redirects" error in the webbrowser.
public class SessionListener implements PhaseListener {
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
public void beforePhase(PhaseEvent event) {
if (!FacesContext.getCurrentInstance().isPostback()) {
try {
System.out.println("Session Destroyed");
FacesContext.getCurrentInstance().getExternalContext().redirect("login.jsf");
}
catch (Exception e) {
System.out.println("error" + e);
}
}
}
public void afterPhase(PhaseEvent event) {
try {
System.out.println("Session Created");
}
catch (Exception e) {
System.out.println("error" + e);
}
}
}
Why do those attempts not work and how can I solve it the best?
It's not possible to send a redirect at exactly the moment when the session is expired. The client has namely not sent any HTTP request at that moment which you could then respond with a redirect.
You should just keep your existing authentication mechanism which redirects to the login page when the user is not logged-in anymore. You can at best improve it by adding a check if the user is been redirected to the login page because the session has been expired, or just because he has never logged in before (i.e. it's a fresh new request).
You can check for that by if HttpServletRequest#getRequestedSessionId() doesn't return null (which means that the client has sent a session cookie and thus assumes that the session is still valid) and HttpServletRequest#isRequestedSessionIdValid() returns false (which means that the session has been expired at the server side). You can do that in the very same filter where you're checking for the logged-in user (you do already have one, right? or are you using container managed authentication?).
User user = (User) session.getAttribute("user");
if (user == null) {
String loginURL = request.getContextPath() + "/login.jsf";
if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
response.sendRedirect(loginURL + "?expired=true");
} else {
response.sendRedirect(loginURL);
}
} else {
chain.doFilter(request, response);
}
And then in the login.xhtml page check for it
<h:panelGroup rendered="#{param.expired}">
<p>You have been redirected to the login page, because your session was expired.</p>
</h:panelGroup>
Your phase listener approach makes by the way no sense. It indeed sends a redirect on every single request causing it to run in an infinite loop. The restore view phase has absolutely nothing to do with the session expiration.
Try to use
FacesContext.getCurrentInstance().getApplication().getNavigationHandler().
handleNavigation(context, null, "LoginForm");
But note that you should use Servlet Filter for these purposes, it's better to do not any redirection from PhaseListener because it's really error prone.

How can I show Authenticated but UNAUTHORIZED users an unauthorized page MVC 3?

I have an application where some users belong to a Role, but may not actually have access to certain data within a URL. For instance the following url is open to all users
/Library/GetFile/1
However, some users may not have access to file1, but I can't use the Authorize attribute to detect that. I want instead to redirect those users to an unauthorized or accessdenied page. I'm using Forms Authentication and my config is set up like this
<authentication mode="Forms">
<forms loginUrl="~/Home/Index" timeout="2880" />
</authentication>
my custom errors block is like this
<customErrors mode="On" defaultRedirect="Error" redirectMode="ResponseRewrite" >
<error statusCode="401" redirect="Unauthorized"/>
</customErrors>
I am attempting to return the HttpUnauthorizedResult if the user does not have access, but I just get redirected to the login page, which isn't valid here because the User is Authenticated already.
It appears that the HttpUnauthorizedResult is setting the HTTP Response Code to 401 which Forms Authentication is hijacking and sending the user to the Login page.
Throwing the UnauthorizedAccessException doesn't seem to work either always redirecting the user to an IIS Error page even though I've updated my RegisterGlobalFilters to
filters.Add(new HandleErrorAttribute
{
ExceptionType = typeof(UnauthorizedAccessException),
View = "Unauthorized",
Order = 3
});
If I change UnauthorizedAccessException to a custom Exception the redirect works and for now that's what I've done.
Your solution is similar to mine except that I did this:
Create a custom exception, UnauthorizedDataAccessException.
Create a custom exception filter (so that it could log the invalid access attempt).
Register my custom exception attribute as a global filter in App_start.
Create a marker interface, ISecureOwner and added it to my entity.
Add a secure 'Load' extension method to my repository, which throws the exception if the current user is not the owner of the entity that was loaded. For this to work, entity has to implement ISecureOwner that returns the id of the user that saved the entity.
Note that this just shows a pattern: the details of how you implement GetSecureUser and what you use to retrieve data will vary. However, although this pattern is okay for a small app, it is a bit of hack, since that kind of security should be implemented deep down at the data level, using ownership groups in the database, which is another question :)
public class UnauthorizedDataAccessException : Exception
{
// constructors
}
public class UnauthorizedDataAccessAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception.GetType() == Typeof(UnauthorizedDataAccessException))
{
// log error
filterContext.ExceptionHandled = true;
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Error", action = "UnauthorizedDataAccess" }));
}
else
{
base.OnException(filterContext);
}
}
// marker interface for entity and extension method
public interface ISecureOwner
{
Guid OwnerId { get; }
}
// extension method
public static T SecureFindOne<T>(this IRepository repository, Guid id) where T : class, ISecureOwner, new()
{
var user = GetSecureUser();
T entity = repository.FindOne<T>(id);
if (entity.OwnerId != user.GuidDatabaseId)
{
throw new UnauthorizedDataAccessException(string.Format("User id '{0}' attempted to access entity type {1}, id {2} but was not the owner. The real owner id is {3}.", user.GuidDatabaseId, typeof(T).Name, id, entity.OwnerId));
}
return entity;
}
// Register in global.asax
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
var filter = new UnauthorizedDataAccessAttribute { ExceptionType = typeof(UnauthorizedDataAccessException) };
filters.Add(filter);
filters.Add(new HandleErrorAttribute());
}
// Usage:
var ownedThing = myRepository.SecureFindOne<myEntity>(id))
You can restrict access to certain roles. If an unauthorized role tries to access a resource you can redirect them to a specific url.
Look at this other SO question: attribute-for-net-mvc-controller-action-method, there are good answers there.
You can check in your code if a user belongs to a role:
User.IsInRole("RoleToTest");
you can also apply attributes to your controllers/action methods. Anyhow it is all explained in the link I specified above.
* EDIT *
You could override OnException in your base Controller. Implement a custom exception, e.g., AccessNotAuthorizedAccessException.
In OnExcepton, if you detect your custom exception, just redirect to a friendly url that shows the 'Not authorized...' message.

Resources