I have a custom authorize program called CustomAuthorize that inherits AuthorizeAttribute that simply restricts access to certain controllers and resources based on various factors specific to the user. However, I get an error on the following line:
The line:
Protected Overrides Function AuthorizeCore(httpContext As
HttpContextBase) As Boolean
The error:
An unhandled exception of type 'System.StackOverflowException'
occurred in MyBlog.DLL
Here's my whole code:
Public Class CustomAuthorize
Inherits AuthorizeAttribute
Protected Overrides Function AuthorizeCore(httpContext As HttpContextBase) As Boolean
Dim authorized = AuthorizeCore(httpContext)
' if user is not authorized, restrict access
If (authorized = False) Then
Return False
End If
' get user name
Dim username = httpContext.User.Identity.Name
' get user
Dim user = Membership.GetUser(username, True)
' get user's profile
Dim db As UserProfileDbContext = New UserProfileDbContext
Dim profile = db.UserProfiles.Where(Function(x) x.UserId = user.ProviderUserKey).Single
' TODO: if user doesn't have a profile, return false
' get route
Dim routeData = httpContext.Request.RequestContext.RouteData
' get controller
Dim controller = routeData.Values("controller").ToString
' get id
Dim id = routeData.Values("id").ToString
' if no id is set, check to see if the user owns the requested entity (company or blog)
If String.IsNullOrEmpty(id) = True Then
If controller.ToLower = "blog" Or controller.ToLower = "article" Then
If profile.IsCompanyOwner Or profile.IsBlogOwner = True Then
' if user is owner of a blog with no specified id, then it will default to their own blog
Return True
End If
End If
Else
' if controller = blog
' check for blog id
If controller.ToLower = "blog" Then
' check to see if the user owns the company to which the blog belongs
If profile.IsCompanyOwner Then
' get company from blog id
Dim db1 As BlogDbContext = New BlogDbContext
Dim blog = db1.Blogs.Where(Function(b) b.BlogId = id).Single()
If blog.CompanyId = profile.CompanyId Then
Return True
End If
ElseIf profile.IsBlogOwner Then
' if user's blog id is the blog being requested, grant access
If profile.BlogId = id Then
Return True
End If
End If
End If
' if controller = article
' check for article blog id
If controller.ToLower = "article" Then
Dim db2 As ArticleDbContext = New ArticleDbContext
Dim article = db2.Articles.Where(Function(a) a.ArticleId = id).Single
Dim articleBlogId = article.BlogId
' check to see if the user owns the company to which the blog belongs
If profile.IsCompanyOwner Then
' get company from blog id
Dim db1 As BlogDbContext = New BlogDbContext
Dim blog = db1.Blogs.Where(Function(b) b.BlogId = articleBlogId).Single()
If blog.CompanyId = profile.CompanyId Then
Return True
End If
ElseIf profile.IsBlogOwner Then
' if user's blog id is the blog being requested, grant access
If profile.BlogId = articleBlogId Then
Return True
End If
End If
End If
End If
' if we got this far, then the user shouldn't have access
Return False
End Function
Protected Overrides Sub HandleUnauthorizedRequest(filterContext As AuthorizationContext)
Dim result = New ViewResult()
result.ViewName = "Error"
result.ViewBag.ErrorMessage = "oops, you are not allowed"
filterContext.Result = result
End Sub
End Class
How can I fix this error? Thank you.
I think you want to call the MyBase.AuthorizeCore.
So you want to change this line
Dim authorized = AuthorizeCore(httpContext)
to
Dim authorized = MyBase.AuthorizeCore(httpContext)
The first line of your function is Dim authorized = AuthorizeCore(httpContext)
This line will call your method again, and the first line of that new call will do the same, ad infinitum. This causes a StackOverflowException.
Related
Can anyone give me solution for this issue (or article, keyword ):
One user create a product, and back-end will be send mail for admin to approve the product, this mail has contained the link:
http://localhost:11260/#/productHandle/116796
I want admin don't need login in system, and can access this link.
because the current code in Global.asax check cookies:
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie ck = Request.Cookies[FormsAuthentication.FormsCookieName];
if (ck != null && !String.IsNullOrEmpty(ck.Value))
{
FormsAuthenticationTicket fat = FormsAuthentication.Decrypt(ck.Value);
UserProfile profile = JsonConvert.DeserializeObject<UserProfile>(fat.UserData);
MyPrincipal myPrincipal = new MyPrincipal(profile.Username);
myPrincipal.UsrProfile = profile;
HttpContext.Current.User = myPrincipal;
}
}
---after access successfully, i intended to encrypt this link. but step above don't work..
thank for help me!
We have a similar application that involves a user requesting access to our document imaging system, and then an email is sent to our application admins for approval. The general idea looks like this:
[HttpPost]
public ActionResult RegisterNewUser(NewUser model)
{
if (ModelState.IsValid)
{
var pwSalt = repository.CreateSalt();
var newUser = User();
newUser.Username = model.Username;
newUser.Email = model.Email;
newUser.PasswordSalt = pwSalt;
newUser.Password = repository.CreatePasswordHash(model.Password, pwSalt);
newUser.IsApproved = false;
newUser.RegisterDate = DateTime.Now;
db.Users.Add(newUser);
db.SubmitChanges();
ConfirmationEmail(model.Username);
return RedirectToAction("RegistrationSuccess");
}
}
The above code is the post-action for a new user who's just registered for our application. It adds the user to the db table. The model contains fields like name, username, email, etc (I also have a function "CreatePasswordHash" which uses the user's password and a generated SALT to created an encrypted password). One thing to note is that the Users table contains a column "IsApproved", which sets to "false" by default. Without this value changing to "true", the user won't be able to use the application. Within this action is another function named "ConfirmationEmail" which is where we send an email to one of the admins for approval.
public void ConfirmationEmail(string username)
{
var user = db.Users.Single(u => u.Username == username);
string mailBody = "A new user requires approval for document imaging.\n\n"
+ "Name: " + user.Name + "\n\n"
+ "\n\n"
+ "If you approve, follow the link below: \n\n"
+ "http://example.com/Imaging/Account/Approval/" + user.UserId;
MailMessage msg = new MailMessage();
msg.Priority = MailPriority.High;
msg.To.Add(admin#example.com);
msg.Subject = "New User registration approval";
msg.Body = mailBody;
msg.IsBodyHtml = false;
SmtpClient smtp = new SmtpClient();
smtp.Send(msg);
msg.Dispose();
}
The above function takes a username param from the original registration action, which we use to find the user record (note: Username in the Users table is a UNIQUE column, so it will fail validation if it's not unique). I create the body of the email using plain text, and attach the URL of the approval action for the admin to confirm approval. Everything else is standard mail code (SMTP settings are stored in my web.config file).
Account controller -- Approval action:
public ActionResult Approval(int id)
{
var user = db.Users.Find(id);
user.IsApproved = true;
db.SubmitChanges();
EmailApproval(id);
return View(user);
}
Simple action that changes the "IsApproved" flag to true, which grants the user access to the application. The View that is returned contains basic user information displayed in a nice confirmation window. Lastly, there's a function "EmailApproval" which sends an email to the user about being approved. It uses the same emailing practices as the other function above.
Hope this helps.
You can make action insecure ([AllowAnonymous] will help you) and implement security for this action via IP address.
In this case you need to implement you own Authorization filter (read there how to do it) and check that it is called from predefined admin's IP and call is local (use HttpRequest.IsLocal for checking this).
Frankly, I never will do something insecure in my projects so you have to think twice before doing something similar for this.
From other hand you can extend your session timeout.
While the MVC4 template provided by Microsoft is useful, I feel there are a few scenarios that should be covered to help out users trying to log in.
Allow the user to log in with their email address instead of their user name (they can still choose to use their user name). The former is generally easier to remember.
If they don't have a local account and try to use one, check if they have previously used an external provider (such as google) to log in and let them know to use it instead.
If they have registered an account locally but have not yet confirmed their email, let them know. The current template just warns that the username or password is wrong.
Maybe I'm over-thinking it, but I want to provide the user every opportunity to successfully log in and use the site. Here is the code I wrote to add these functions. I wrote it in VB, but am including C# tag since the majority of MVC user's here seem to favor it and VB is easy to read. Is this the correct approach to add these options? Are there any glaring errors with this code, aside from the fact I can probably refactor it? Thank you.
<HttpPost()> _
<AllowAnonymous()> _
<ValidateAntiForgeryToken()> _
Public Function Login(ByVal model As LoginModel, ByVal returnUrl As String) As ActionResult
If ModelState.IsValid Then
If IsEmail(model.UserName) Then
'the username is an email address
Dim username = GetUserNamebyEmail(model.UserName)
If username IsNot Nothing Then
If WebSecurity.Login(username, model.Password, persistCookie:=model.RememberMe) Then
Return RedirectToLocal(returnUrl)
End If
'check if there is a local account
Dim localID = GetUserIDbyEmail(model.UserName)
If localID Is Nothing Then
'no local account means the username is wrong
ModelState.AddModelError("", "The user name or password provided is incorrect.")
Else
If Not OAuthWebSecurity.HasLocalAccount(localID) Then
'registered via external provider
ModelState.AddModelError("", "Please login with the External Provider you have previously used.")
Else
If Not WebSecurity.IsConfirmed(model.UserName) Then
'has a local account, but hasn't confirmed email
ModelState.AddModelError("", "You have not yet confirmed your email.")
Else
'password is wrong
ModelState.AddModelError("", "The user name or password provided is incorrect.")
End If
End If
End If
Else
ModelState.AddModelError("", "The email you entered is incorrect.")
End If
Else
'must be the regular user name, so log in as normal
If WebSecurity.Login(model.UserName, model.Password, persistCookie:=model.RememberMe) Then
Return RedirectToLocal(returnUrl)
End If
'check if there is a local account
Dim localID = GetUserIDbyUserName(model.UserName)
If localID Is Nothing Then
'no local account means the username is wrong
ModelState.AddModelError("", "The user name or password provided is incorrect.")
Else
If Not OAuthWebSecurity.HasLocalAccount(localID) Then
'registered via external provider
ModelState.AddModelError("", "Please login with the External Provider you have previously used.")
Else
If Not WebSecurity.IsConfirmed(model.UserName) Then
'has a local account, but hasn't confirmed email
ModelState.AddModelError("", "You have not yet confirmed your email.")
Else
'password is wrong
ModelState.AddModelError("", "The user name or password provided is incorrect.")
End If
End If
End If
End If
End If
Return View(model)
End Function
'check if input is an email address
Public Function IsEmail(ByVal input As String) As Boolean
Return Regex.IsMatch(input, "\A(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\Z")
End Function
Public Function GetUserNamebyEmail(ByVal email As String) As String
Dim username As String = Nothing
Dim conn As SqlConnection = New SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings("DefaultConnection").ConnectionString)
Dim cmd As SqlCommand = New SqlCommand("select username from user_info where Email = #Email", conn)
cmd.Parameters.Add(New SqlParameter("#Email", System.Data.SqlDbType.NVarChar))
cmd.Parameters("#Email").Value = email
conn.Open()
Dim reader As SqlDataReader = cmd.ExecuteReader()
Try
While reader.Read
username = reader(0)
End While
Finally
reader.Close()
End Try
conn.Close()
Return username
End Function
Public Function GetUserIDbyEmail(ByVal email As String) As Integer?
Dim userID As Integer?
Dim conn As SqlConnection = New SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings("DefaultConnection").ConnectionString)
Dim cmd As SqlCommand = New SqlCommand("select UserID from user_info where Email = #Email", conn)
cmd.Parameters.Add(New SqlParameter("#Email", System.Data.SqlDbType.NVarChar))
cmd.Parameters("#Email").Value = email
conn.Open()
Dim reader As SqlDataReader = cmd.ExecuteReader()
Try
While reader.Read
userID = reader(0)
End While
Finally
reader.Close()
End Try
conn.Close()
Return userID
End Function
Public Function GetUserIDbyUserName(ByVal username As String) As Integer?
Dim userID As Integer?
Dim conn As SqlConnection = New SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings("DefaultConnection").ConnectionString)
Dim cmd As SqlCommand = New SqlCommand("select UserID from user_info where UserName = #username", conn)
cmd.Parameters.Add(New SqlParameter("#username", System.Data.SqlDbType.NVarChar))
cmd.Parameters("#username").Value = username
conn.Open()
Dim reader As SqlDataReader = cmd.ExecuteReader()
Try
While reader.Read
userID = reader(0)
End While
Finally
reader.Close()
End Try
conn.Close()
Return userID
End Function
I have following two actions that keeps calling each other and goes in infinite loop. what am i doing wrong?
Public Overrides Sub OnAuthorization(filterContext As System.Web.Mvc.AuthorizationContext)
'This calls the AuthorzeCore function and also makes sure that the browser does not cache this function
MyBase.OnAuthorization(filterContext)
If Not IsNothing(filterContext.Result) Then
Return
End If
'Gets the calling Controller
Dim controllerName As String = filterContext.Controller.GetType().Name
'Gets the calling action
Dim actionName As String = filterContext.ActionDescriptor.ActionName
'Checks whether the logged in user has access to the action of the controller
Dim canAccess As test.Security.Permissions.PermissionTypes
canAccess = test.ApplicationSecurity.GetSecurityObject().GetAccess(controllerName & "." & actionName)
If canAccess = Security.Permissions.PermissionTypes.DISABLE Then
'User has access to the application but not to the action they are trying to access, so throw a Unauthorised exception
filterContext.HttpContext.Response.StatusCode = 403
HandleUnauthorizedRequest(filterContext)
End If
End Sub
Protected Overrides Sub HandleUnauthorizedRequest(filterContext As System.Web.Mvc.AuthorizationContext)
''To make sure that we throw a not authorised error rather not authenticated message
'If filterContext.HttpContext.Request.IsAuthenticated Then
' 'filterContext.Result = New HttpStatusCodeResult(CType(System.Net.HttpStatusCode.Forbidden, Int32))
' filterContext.Result = New RedirectToRouteResult(
'Else
' MyBase.HandleUnauthorizedRequest(filterContext)
'End If
If (filterContext.HttpContext.Request.IsAjaxRequest()) Then
Dim urlHelper As UrlHelper = New UrlHelper(filterContext.RequestContext)
filterContext.Result = New JsonResult With {.Data = New With {.Error = "NotAuthorized", .URL = urlHelper.Action("UnAuthorized", "Error")}, _
.JsonRequestBehavior = JsonRequestBehavior.AllowGet}
ElseIf filterContext.HttpContext.Response.StatusCode = 403 Then
filterContext.Result = New ViewResult With {.ViewName = "UnAuthorized"}
Else
filterContext.Result = New ViewResult With {.ViewName = "UnAuthenticated"}
End If
End Sub
You shouldn't be calling HandleUnauthorizedRequest from inside OnAuthorization, this method is called automatically when the request can't be authorized.
From the docs:
Authorization is denied in the following cases:
• The request is not associated with any user.
• The user is not authenticated.
• The user is authenticated but is not in the authorized group of Userss (if defined), or if the user is not in any
of the authorized Roles (if defined).
If authorization is denied then this method will invoke
HandleUnauthorizedRequest(HttpActionContext) to process the
unauthorized request.
i have created new domain in grails and from a controller i've tried to save but nothing get saved in the database.. the code is as follow
controller
def register={
String name = params.name
String email = params.email
String pass = params.password
boolean signedIn = params.signedIn
System.out.println(name + " " + email +" "+ pass+" " + signedIn)
def rUser = new Registered(params)
rUser.signedIn = signedIn
System.out.println(rUser)
rUser.save(flush:true)
}
domain
class Registered {
String name;
String email;
String password;
boolean signedIn =false;
static constraints = {
}
}
and i'm trying to save by this url
http://localhost:8080/egypths/apps/register?name=hegab&email=eio#gmail.com&password=tom&signedIn=false
so what am i doing wrong ... putting in mind that there's no error in the stack trace
I would start by wrapping this in an integration test that would look like this:
import groovy.util.GroovyTestCase
import org.junit.Test
public class RegisterControllerTests extends GroovyTestCase {
#Test
void saveAction() {
def controller = new RegisterController() //or whatever the controller name is
controller.params.name = "SomethingUnique"
controller.params.email = "example#example.com"
controller.params.password = "password"
controller.params.signedIn = "false"
controller.register()
def registered = Registered.findByName("SomethingUnique")
assert "example#example.com" == registered.email
assert "password" == registered.password
assert false == registered.signedIn
}
}
Then I would start by making your controller action as simple as possible:
def register={
String name = params.name
String email = params.email
String pass = params.password
boolean signedIn = params.signedIn
def rUser = new Registered()
rUser.name = name
rUser.email = email
rUser.password = pass
rUser.signedIn = signedIn
rUser.save(flush:true, failOnError:true) //I would remove the failOnError after you identify the issue.
}
This way you can quickly repeat your test and figure out where your problem is. Adding the failOnError:true to the save call will cause an exception to be thrown if it doesn't pass validation. If this simple example works start working back towards a more elegant solution to identify where you're issue resides.
I have an ASP.NET MVC 2 application in which I am creating a custom action filter. This filter sits on the controllers in the application and verifies from the database whether that function is currently available.
Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
Try
' Check controller name against database.
Dim controllerName = filterContext.Controller.GetType().Name
controllerName = controllerName.Remove(controllerName.Length - 10)
' Look up availability.
Dim available As Boolean = _coreService.GetControllerAvailability(controllerName)
If Not available Then
' Redirect to unavailable notice.
filterContext.Result = New RedirectResult("/Home/Unavailable/")
End If
Catch ex As Exception
_eventLogger.LogWarning(ex, EventLogEntryType.Error)
Throw
End Try
End Sub
My problem is that depending on the action that has been requested I need to redirect the user to an action that returns either a view, partial views or JSON.
Given the ActionExecutingContext can I find out what the return type of the originally requested action is?
EDIT:
Ok, I'm getting closer but have another problem.
Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
Try
' Check controller name against database.
Dim controllerName = filterContext.Controller.GetType().Name
Dim shortName = controllerName.Remove(controllerName.Length - 10)
' Look up availability.
Dim available As Boolean = _coreService.GetControllerAvailability(shortName)
If Not available Then
' find out what type is expected to be returned
Dim actionName As String = filterContext.ActionDescriptor.ActionName
Dim controllerType = Type.GetType("Attenda.Stargate.Web." & controllerName)
Dim actionMethodInfo = controllerType.GetMethod(actionName)
Dim actionReturnType = actionMethodInfo.ReturnType.Name
Select Case actionReturnType
Case "PartialViewResult"
filterContext.Result = New RedirectResult("/Home/UnavailablePartial/")
Case "JsonResult"
filterContext.Result = New RedirectResult("/Home/UnavailableJson/")
Case Else
filterContext.Result = New RedirectResult("/Home/Unavailable/")
End Select
End If
Catch ex As Exception
_eventLogger.LogWarning(ex, EventLogEntryType.Error)
Throw
End Try
End Sub
I can use reflection to find the return type of the action method. My problem is if I have the following methods on a controller:
Public Function Create() As ViewResult
Return View()
End Function
<AcceptVerbs(HttpVerbs.Post)>
Public Function Create(values as FormCollection) As ViewResult
' Do stuff here
End Function
I get an AmbiguousMatchException thrown.
With the information I have in the OnActionExecuting method, is there anyway of being more precise with determining the overload that is being called?
I created an AuthenticationFilterAttribute based on this which returns different results based on type:
/// <summary>
/// Access to the action will be blocked if the user is not logged in.
/// Apply this to the controller level or individual actions as an attribute.
/// </summary>
public class AuthenticationFilterAttribute : ActionFilterAttribute
{
protected const string InvalidAccess = "Invalid access";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Find out if the user is logged in:
Controller controller = (Controller)filterContext.Controller;
if (!controller.User.Identity.IsAuthenticated)
{
switch (GetExpectedReturnType(filterContext).Name)
{
case "JsonResult":
var jsonResult = new JsonResult();
jsonResult.Data = new { Error = true, ErrorMessage = InvalidAccess };
jsonResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
filterContext.Result = jsonResult;
break;
// Assume same behaviour as ActionResult
default:
var actionResult = new ContentResult();
actionResult.Content = InvalidAccess;
filterContext.Result = actionResult;
break;
}
}
}
private Type GetExpectedReturnType(ActionExecutingContext filterContext)
{
// Find out what type is expected to be returned
string actionName = filterContext.ActionDescriptor.ActionName;
Type controllerType = filterContext.Controller.GetType();
MethodInfo actionMethodInfo = default(MethodInfo);
try
{
actionMethodInfo = controllerType.GetMethod(actionName);
}
catch (AmbiguousMatchException ex)
{
// Try to find a match using the parameters passed through
var actionParams = filterContext.ActionParameters;
List<Type> paramTypes = new List<Type>();
foreach (var p in actionParams)
{
paramTypes.Add(p.Value.GetType());
}
actionMethodInfo = controllerType.GetMethod(actionName, paramTypes.ToArray());
}
return actionMethodInfo.ReturnType;
}
}
Ok, this is the solution I have come up with.
Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
Try
' Check controller name against database.
Dim controllerName = filterContext.Controller.GetType().Name
Dim shortName = controllerName.Remove(controllerName.Length - 10)
' Look up availability.
Dim available As Boolean = _coreService.GetControllerAvailability(shortName)
If Not available Then
' find out what type is expected to be returned
Dim actionName As String = filterContext.ActionDescriptor.ActionName
Dim controllerType = Type.GetType("Attenda.Stargate.Web." & controllerName)
Dim actionMethodInfo As MethodInfo
Try
actionMethodInfo = controllerType.GetMethod(actionName)
Catch ex As AmbiguousMatchException
' Try to find a match using the parameters passed through
Dim actionParams = filterContext.ActionParameters
Dim paramTypes As New List(Of Type)
For Each p In actionParams
paramTypes.Add(p.Value.GetType())
Next
actionMethodInfo = controllerType.GetMethod(actionName, paramTypes.ToArray)
End Try
Dim actionReturnType = actionMethodInfo.ReturnType.Name
Select Case actionReturnType
Case "PartialViewResult"
filterContext.Result = New RedirectResult("/Home/UnavailablePartial/")
Case "JsonResult"
filterContext.Result = New RedirectResult("/Home/UnavailableJson/")
Case Else
filterContext.Result = New RedirectResult("/Home/Unavailable/")
End Select
End If
Catch ex As Exception
_eventLogger.LogWarning(ex, EventLogEntryType.Error)
Throw
End Try
End Sub
If the Type.GetMethod(string) call fails to identify the method requested, I fetch the parameters collection from the ActionExecutingContext.ActionParameters collection and build an array of the types of the parameters passed in the request. I can then use the Type.GetMethod(string,type()) overload to be more specific about my request.
Some nice answers above, but in MVC Core, I notice you can just get to the method info by casting to a ControllerActionDescriptor, which would clean the above answers some. (I'm using this in a Blazor Webassembly Web Api backend in Preview 6)
((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)filterContext.ActionDescriptor).MethodInfo
So getting the Return type could become:
public static class FilterHelper
{
public static Type GetReturnType(this ActionExecutingContext filterContext)
{
return ((ControllerActionDescriptor)filterContext.ActionDescriptor).MethodInfo.ReturnType;
}
}
And be used like:
Type t = actionContext.GetReturnType();
Will update with an updated full solution for 6 Preview
By the time OnActionExecuting is invoked, the action method has not been executed yet, so there's no way you know whether that action method is going to return which subclass of ActionResult. So, unless you can go with CIL analysis implementation (which I think can get ugly very quickly), I don't think what you want to do is possible.
That said, isn't the fact that you redirect users to a view when the controller isn't available enough? I mean, I don't understand why you want to redirect users to a JSON result or a partial view.