I would like to use SpEL to handle method level security. I run into an issue where the data passed to the method is not enough to determine if a user has access. Here is an example
#RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
#ResponseStatus(HttpStatus.NO_CONTENT)
#PreAuthorize("#securityService.isAllowedAccessByCurrentUser(#.id)")
public void delete(#PathVariable("id") final Long id) {
service.delete(id);
}
Here, the id variable is the ID of some object. To get the owner ID of the object (which is what i want to pass in the spel expression), I would need to do something like:
service.findOne(id).getUser().getId();
How can I get that ID and use it in the SpEL expression?
Why need to so complicated? You can simply create another method to specifically check if the current user can delete an object given that object Id and make the #PreAuthorize refer to this method:
#Service
public class SecurityService{
#Autowired
private Service service
boolean isAllowToDeleteSomeObject(Long objectId){
SomeObject anObject = service.findOne(objectId);
//You should able to get the current user Id by SecurityContextHolder.getContext().getAuthentication().
//getCurrentUserId() simply encapsulate such codes for convenient.
Long currentUserId = getCurrentUserId();
if(anObject.getUser().getId().equals(currentUserId)){
return true;
}else{
return false;
}
}
}
Then you can refer to this method in SpEL:
#PreAuthorize("#securityService.isAllowToDeleteSomeObject(#.id)")
public void delete(#PathVariable("id") final Long id) {
}
Related
I'm looking for recommendations on how to have multiple authorize attributes on an action.
eg:
[AuthorizePermission(PermissionName.SectionOne, PermissionLevel.Two)]
[AuthorizePermission(PermissionName.SectionTwo, PermissionLevel.Three)]
public ActionResult Index(int userId = 0){
}
If the user has access to SectionOne OR SectionTwo with the required PermissionLevel then they should be allowed in.
The problem i'm facing is how do I check both attributes before deciding they aren't allowed in (as they are separate attributes)? If the first one fails then it will never get to the second one.
I can not pass both permission sets to one attribute as they need to be paired together.
Does anyone have any suggestions?
I can not pass both permission sets to one attribute as they need to be paired together.
Yes, you can.
There is no reason why you can't include all the permissions in a single attribute. Something like this:
[AuthorizePermission(new Permission[]{
new Permission(PermissionName.SectionOne, PermissionLevel.Two),
new Permission(PermissionName.SectionTwo, PermissionLevel.Three)}]
This would pass an array of Permission objects, which you can then evaluate in your method with OR logic.
public class AuthorizePermissionAttribute : AuthorizeAttribute
{
private Permission[] _permissions = null;
public AuthorizePermissionAttribute(Permission[] permissions)
{
_permissions = permissions;
}
}
You could even get fancy and add a parameter that tells whether to AND or OR them...
The only way that I know is something like this
public class CustomRolesAttribute : AuthorizeAttribute
{
public CustomRolesAttribute(params string[] roles)
{
Roles = String.Join(",", roles);
}
}
Usage:
[CustomRoles("members", "admin")]
I have a method which looks like the one below
public List<Rajnikanth> GetRajnis()
{
string username = Utility.Helpers.GetLoggedInUserName();
return _service.GetRajni(username);
}
Utility.Helper is a static class,
public static class Helpers
{
public static String GetLoggedInUserName()
{
string username = "";
if (System.Web.HttpContext.Current.User.Identity.IsAuthenticated)
{
username = ((System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity).Ticket.Name;
}
return username;
}
}
I want to test : GetRajnis()
I want to mock : GetLoggedInUserName()
So my test method looks something like...
[TestMethod]
public void TestGetRajnis()
{
SomeController s = new SomeController(new SomeService());
var data = s.GetRajnis();
Assert.IsNotNull(data);
}
how do I mock the static method GetLoggedInUserName() ?
The Simplest Approach: Override the return value
If you are looking to mock a return value, then this is very simple. You can modify the Utility.Helper class to include a property called OverrideLoggedInUserName. When someone calls GetLogedInUserName(), if the override property is set, it is returned, otherwise the normal code to get the value from the HttpContext is used to get the return value.
public static class Helper
{
// Set this value to override the return value of GetLoggedInUserName().
public static string OverrideLoggedInUserName { get; set; };
public static string GetLoggedInUserName()
{
// Return mocked value if one is specified.
if ( !string.IsNullOrEmpty( OverrideLoggedInUserName ) )
return OverrideLoggedInUserName;
// Normal implementation.
string username = "";
if ( System.Web.HttpContext.Current.User.Identity.IsAuthenticated )
{
username = ( (System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity ).Ticket.Name;
}
return username;
}
}
This will effectively allow you to override the return value, which technically isn't a mock--it's a stub (according to the excellent article Mocks Aren't Stubs by Martin Fowler). This allows you to stub a return value, but won't allow you to assert whether the method was called or not. Anyhow as long as you only want to manipulate the return value this works fine.
Here is how you would use this in a test.
[ TestMethod ]
public void TestGetRajnis()
{
// Set logged in user name to be "Bob".
Helper.OverrideLoggedInUserName = "Bob";
SomeController s = new SomeController( new SomeService() );
var data = s.GetRajnis();
// Any assertions...
}
This design does have one drawback. Because it's a static class, if you set the override value, it remains set until you un-set it. So you must remember to re-set it to null.
A Better Approach: Inject the dependency
A better approach may be to create a class that retrieves the logged in user name, and pass it into the constructor of SomeController. We call this dependency injection. This way, you can inject a mocked instance into it for testing, but pass the real instance (that gets the user from the HttpContext) when not testing. This is a much cleaner and clearer approach. Plus, you can leverage all the power of whatever mocking framework you are using, since they are designed specifically to handle this approach. Here is what that would look like.
// Define interface to get the logged in user name.
public interface ILoggedInUserInfo
{
string GetLoggedInUserName();
}
// Implementation that gets logged in user name from HttpContext.
// This class will be used in production code.
public class LoggedInUserInfo : ILoggedInUserInfo
{
public string GetLoggedInUserName()
{
// This is the same code you had in your example.
string username = "";
if ( System.Web.HttpContext.Current.User.Identity.IsAuthenticated )
{
username = ( (System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity ).Ticket.Name;
}
return username;
}
}
// This controller uses the ILoggedInUserInfo interface
// to get the logged in user name.
public class SomeController
{
private SomeService _service;
private ILoggedInUserInfo _userInfo;
// Constructor allows you inject an object that tells it
// how to get the logged in user info.
public SomeController( SomeService service, ILoggedInUserInfo userInfo )
{
_service = service;
_userInfo = userInfo;
}
public List< Rajnikanth > GetRajnis()
{
// Use the injected object to get the logged in user name.
string username = _userInfo.GetLoggedInUserName();
return _service.GetRajni( username );
}
}
And here is a test using Rhino Mocks to inject a stub object into the controller.
[ TestMethod ]
public void TestGetRajnis()
{
// Create a stub that returns "Bob" as the current logged in user name.
// This code uses Rhino Mocks mocking framework...
var userInfo = MockRepository.GenerateStub< ILoggedInUserInfo >();
userInfo.Stub( x => x.GetLoggedInUserName() ).Return( "Bob" );
SomeController s = new SomeController( new SomeService(), userInfo );
var data = s.GetRajnis();
// Any assertions...
}
The disadvantage here is that you can't just call Helper.GetLoggedInUserName() from anywhere in your code, because it's no longer static. However, you no longer have the need to reset the stubbed username every time you finish a test. Because it's not static, it it automatically reset. You just recreate it for the next test and set a new return value.
I hope this helps.
Get rid of the static class if you are looking for testability. A simple fix for now would be to create a wrapper around the static class. Unless you use something like TypeMock or something equally as powerful, then you cannot alter the logic of a static class. Nor do I suggest it. If you have to stub a static class, it probably should not be a static class.
public class StaticWrapper
{
public virtual String GetLoggedInUserName()
{
Utility.Helpers.GetLoggedInUserName();
}
}
I am unit-testing my controller.
In one of my controller methods I am setting Session variables:
public void Index()
{ Session["foo"] = "bar";
return View();
}
How can I unit-test this? The problem is that the Session property is null when testing. Injecting is not possible because the Session property is readonly.
I don't want to use any third-party tool or mocking.
Simply dont use things like Session["foo"] in your controller methods. Best practice is keep action methods unaware of any context-like global objects. Everything your action method needs should be given to her in form of arguments. Note that built-in mechanism of model binding works exactly like that - you dont use Request.Form[], you let "somebody behind the scene" pass it to your action as argument.
Now for the session you can do the same - write you very simple ValueProvider which will know how to recognize arguments you want to fill from session, and you are done. In production your actions will work with session, in test you cant simply pass them any values you want as arguments.
For inspiration look at this http://www.prideparrot.com/blog/archive/2012/7/how_to_create_a_custom_session_value_provider
Injecting is not possible because the Session property is readonly.
This means you cannot use setter injection, but could you use constructor injection, ie add a constructor for your controller that is something like:
MyController(Session session)
{
m_session = session;
// then call your main constructor
}
Session getSession()
{
return m_session;
}
You can then use this separate constructor during testing.
I agree with #rouen. do not directly use Session["foo"]. But I think having ValueProvider ans might not be a practical solution, as we only store very few variables, and these values may be and most likely not ur full model.
So my approach is something similar to what Vic Smith suggests but a much more IOC (and Mock) friendly.
I would create a provider (i.e a service) to retrieve the session variables
public class SessionVariableProvider : ISessionVariableProvider
{
public object GetSessionValue(string key)
{
if (!HttpContext.Current.Session.IsNewSession
&& HttpContext.Current.Session[key] != null)
{
return HttpContext.Current.Session[key];
}
throw new ArgumentNullException(key);
}
public void SetSessionValue(string key, object value)
{
HttpContext.Current.Session[key] = value;
}
}
public interface ISessionVariableProvider
{
object GetSessionValue(string key);
void SetSessionValue(string key, object value);
}
Modify your Controller expect ISessionVariableProvider as a parameter.
public class TestController: Controller
{
protected readonly ISessionVariableProvider _sessionVariableProvider;
protected InowiaControllerBase(ISessionVariableProvider sessionVariableProvider)
{
Guard.ArgumentNotNull(sessionVariableProvider, "sessionVariableProvider");
this._sessionVariableProvider = sessionVariableProvider;
}
public ActionResult Index()
{
_sessionVariableProvider.SetSessionValue("foo", "bar");
var foo2 = (string)_sessionVariableProvider.GetSessionValue("foo2");
return View();
}
}
when testing create your own test implementation of ISessionVariableProvider and pass it to the controller.
I'm try to build a static property on a static class that will basically return a cookie value, to be used across my MVC site (MVC 3, if it matters). Something like this:
public static class SharedData
{
public static string SomeValue
{
get
{
if (HttpContext.Current.Request.Cookies["SomeValue"] == null)
{
CreateNewSomeValue();
}
return HttpContext.Current.Request.Cookies["SomeValue"].Value.ToString();
}
}
}
I need to access this from within controller actions, global.asax methods, and action filters. But the problem is, when action filters run, HttpContext is not available. Right now, I have to have a separate static method just to pull the cookie from the filter context that I pass in, which seems awkward.
What is the best solution for building such a static method for retrieving a cookie value like this that works from both controller actions and action filters? Or is there a better approach for doing something like this?
Thanks in advance.
The call to the static HttpContext.Current is not good design. Instead, create an extension method to access the cookie from an instance of HttpContext and HttpContextBase.
I wrote a little helper for you. You can use it to perform your functionality from within an action filter.
public static class CookieHelper
{
private const string SomeValue = "SomeValue";
public static string get_SomeValue(this HttpContextBase httpContext)
{
if(httpContext.Request.Cookies[SomeValue]==null)
{
string value = CreateNewSomeValue();
httpContext.set_SomeValue(value);
return value;
}
return httpContext.Request.Cookies[SomeValue].Value;
}
public static void set_SomeValue(this HttpContextBase httpContext, string value)
{
var someValueCookie = new HttpCookie(SomeValue, value);
if (httpContext.Request.Cookies.AllKeys.Contains(SR.session))
{
httpContext.Response.Cookies.Set(someValueCookie);
}
else
{
httpContext.Response.Cookies.Add(someValueCookie);
}
}
}
Note: You could easily make these methods work on HttpContext instead just by replacing the HttpContextBase parameter with HttpContext.
As JohnnyO pointed out above, I had access to HttpContext from within my action filter all along. At least, in the particular action filter method where this was needed. There may have been some other filter/method that did not have access at one point, but for now, this is working as I need it to.
Is ther any way to pass a non static value to a actionfilter parameter like below?
public class ProcuctController : Controller
{
private int userID = 1;
[TestFilter(x=userID)]
public ActionResult Index()
{
}
}
No, there isn't. Attributes are baked into the metadata of the resulting assembly and all values should be known at compile time. You can pass only constant values as attribute properties. And that's .NET limitation, not MVC.
depending on the situation, you could always pass in some kind of key to the filter, and then inside the filter you could do a lookup for the corresponding value in whichever datasource you prefer. this would allow you to get non-static data in your method, though it would be nice if it were as simple as your example :-)
You can't pass it in but you could do something like:
public class TestFilterAttribute : ActionFilterAttribute
{
public string UserId
{
get
{
return AppSettings["UserId"];
}
}
}