How can I create a custom parameter binding for a claim? - c#

I want to be able to provide a claim from the current user directly in the parameters of a controller. So that I can write unit tests without touching the ClaimPrincipal magic.
Like the [FromUri] or [FromBody], maybe [FromClaim]?
I tried implementing a CustomModelProvider as specified in this documentation from Microsoft: https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-2.2
But I do not know how can I provide the ClaimsPrincipal or List.
Also the ValueProvider returns a string, so I am unsure that this is actually feasible.
This is my attempt of a ClaimModelBinder
public class ClaimModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
var modelName = bindingContext.ModelName;
// Try to fetch the value of the argument by name
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None) return Task.CompletedTask;
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
var value = valueProviderResult.FirstValue;
// TODO: Unsure, how to continue after this.
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value)) return Task.CompletedTask;
int id = 0;
if (!int.TryParse(value, out id))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
modelName,
"Author Id must be an integer.");
return Task.CompletedTask;
}
// Model will be null if not found, including for
// out of range id values (0, -3, etc.)
bindingContext.Result = ModelBindingResult.Success(null);
return Task.CompletedTask;
}
}

Can you provide a source for "constructing a ClaimsPrincipal for testing is far easier and more correct than what you are trying to do"?
The source is me. As to why I said it, it's based on the understanding of how the ASP NET Core framework is written, as I demonstrate below.
To answer your question, Controller has a User property to access claims, there is no need to write a Model Binder to access claims when there is already a User property, unless of course you cannot access claims from that User property due to your claims logic being different. But you haven't made such mentions.
"I want to be able to provide a claim from the current user directly in the parameters of a controller. So that I can write unit tests without touching the ClaimPrincipal magic."
I interpreted this as,
"I want to write unit tests for my controller that has logic involving the Claims Principal but I do not know how to provide a fake Claims Principal so I'm going to avoid that and pass a method parameter instead"
The ClaimsPrincipal can be unmagiced as follows.
Controller has a User property but it is Get only. Magic
HttpContext has a User property that is Get and Set (Nice) but Controller.HttpContext is Get only (Not So Nice)
Controller has a ControllerContext property which is Get and Set, ControllerContext has a HttpContext property which is Get and Set. Jackpot!
This is the source code of ControllerBase which is what Controller and ApiController derive from,
public abstract class ControllerBase
{
/* simplified below */
public ControllerContext ControllerContext
{
get => _controllerContext;
set => _controllerContext = value;
}
/* ... */
public HttpContext HttpContext => ControllerContext.HttpContext;
/* ... */
public ClaimsPrincipal User => HttpContext?.User;
}
As you see here, the User you access is a convenience Getter that ultimately accesses ControllerContext.HttpContext.User. Knowing this information, you can unit test a controller that uses a ClaimsPrincipal as follows.
// Create a principal according to your requirements, following is exemplary
var principal = new ClaimsPrincipal(new ClaimsIdentity(new []
{
// you might have to use ClaimTypes.Name even for JWTs issued as sub.
new Claim(JwtRegisteredClaimNames.Sub, "1234"),
new Claim(JwtRegisteredClaimNames.Iss, "www.example.com"),
}, "Bearer"));
var httpContext = new DefaultHttpContext();
httpContext.User = principal;
// Fake anything you want
httpContext.Request.Headers = /* ... */
var controller = new ControllerUnderTest(...);
controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = httpContext;
// Test the action, no need to pass claims as parameters because the User property is set
var result = controller.ActionThatUsesUserClaims(...);
Assert.Something(result, expected);
This is how ASP NET Core works every time a a real web request is received. It literally does the above to make the controller functional and ready for you to use.
All of the above are part of the public ASP NET Core api and are not subject to breaking changes without a major version bumb so they are safe to use. In fact, this is one of the things that set ASP Net Core apart from the old ASP NET MVC which was a nightmare to test as it did not expose any of the above publicly.
Having said all this, for some reason I have overlooked, if you really need to write a model binder to provide claims, inject the HTTPContextAccessor. But that requires you to check the type of the method parameters and branch execution. One branch would bind properties from the value provider while the other binds from the HttpContext. But why bother when you can do the above with 0 refactoring?

Related

IdentityServer4 custom AuthorizeInteractionResponseGenerator

Sadly documentation on the implementation of a custom AuthorizeInteractionResponseGenerator in IdentityServer4 is sorely lacking.
I'm trying to implement my own AuthorizeInteractionResponseGenerator because I need a further step of user interaction (after authentication). My scenario is that a single identity (email) can be associated with multiple tenants. So after logon, I need the user to be presented with a list of associated tenants, so that they can choose one.
I have evaluated the source code, and have come up with the the following custom AuthorizeInteractionResponseGenerator:
public class AccountChooserResponseGenerator : AuthorizeInteractionResponseGenerator
{
public AccountChooserResponseGenerator(ISystemClock clock,
ILogger<AuthorizeInteractionResponseGenerator> logger,
IConsentService consent, IProfileService profile)
: base(clock, logger, consent, profile)
{
}
public override async Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request, ConsentResponse consent = null)
{
var response = await base.ProcessInteractionAsync(request, consent);
if (response.IsConsent || response.IsLogin || response.IsError)
return response;
return new InteractionResponse
{
RedirectUrl = "/Organization"
};
}
}
It inherits from the base AuthorizeInteractionResponseGenerator built into IdentityServer4, so that the standard Logon and Consent pages can show. This happens, and then the user is correctly redirected to the /Organization url to select an organization (tenant).
But what then? With the lack of documentation and examples, I'm really struggling to figure out the following two questions:
1) How do I now, having selected a Tenant, indicate to my custom AccountChooserResponseGenerator that my interaction is complete, and that the user can now be redirected back to the Client?
Edit:
Answer to 1: To indicate that the interaction is complete, you I have to return an empty new InteractionResponse(). In my case, a check for the existence of the TenantId claim sufficed, as follows:
if (!request.Subject.HasClaim(c=> c.Type == "TenantId" && c.Value != "0"))
return new InteractionResponse
{
RedirectUrl = "/Organization"
};
return new InteractionResponse();
2) And how can I get information about the selected Tenant to be added to the identity token that IdentityServer4 passes back to the Client?
Edit: Answer to 2: In the Controller Action method that gets executed after selecting a Tenant, I called :
await HttpContext.SignInAsync(User.Claims.Single(r=> r.Type == "sub").Value,
new System.Security.Claims.Claim("TenantId", tenant.Id.ToString()));
return Redirect(ReturnUrl);
...which is an IdentityServer4-provided Extension to HttpContext.

Unit Test Web Api Controller fake ApiController.User with custom Identity doesn't work

I'm currently working with Web API v5.2.2 and I'm writing Unit Test code for one of the controllers. The problem I encountered was happening in ApiController.User part.
I have a custom Identity for the user implemented IIdentity Interface:
public class CustomIdentity : IIdentity
{
//Constructor and methods
}
The CustomIdentity was set in the HttpRequest in normal usage. But since I'm only testing the query functionalities in the Unit Test, I just called the controller and its methods instead of sending requests.
Thus, I have to insert the User Identity into the Thread, and I tried with following ways:
var controller = new AccountsController(new AccountUserContext());
First try:
controller.User = new ClaimsPrincipal(new GenericPrincipal(new CustomIdentity(user), roles.Distinct().ToArray()));
And second try:
IPrincipal principal = null;
principal = new GenericPrincipal(new CustomIdentity(user), roles.Distinct().ToArray());
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
However, I got this error from both attempts:
Object reference not set to an instance of an object.
And I found the User identity remains null in the thread.
Anyone tried this method before? Thank you for the advice!
You said
The CustomIdentity was set in the HttpRequest in normal usage.
Are you attaching a request to the controller when assembling your test.
Check the examples here
Unit Testing Controllers in ASP.NET Web API 2
[TestMethod]
public void QueryAccountControllerTest()
{
// Arrange
var user = "[Username Here]"
var controller = new AccountsController(new AccountUserContext());
//Set a fake request. If your controller creates responses you will need tis
controller.Request = new HttpRequestMessage {
RequestUri = new Uri("http://localhost/api/accounts")
};
controller.Configuration = new HttpConfiguration();
controller.User = new ClaimsPrincipal(new CustomIdentity(user));
// Act
... call action
// Assert
... assert result
}

asp.net webapi dynamic authorization

I am new to webapi and mvc and I am struggling to find a best practice for handling authorizations dynamically based on roles and ownership of the resource. For example an account page that should allow employee admins, employee call center or the owning client to Get, Post, Put or Delete account information. So an admin and call center employee should be able to Get, Post, Put or Delete any request for any userid, but a client should only be able to perform these actions on resources owned by them.
For example Tom is UserID 10 and Jerry is UserID 20.
/api/Account/10 should be accessible by any admin, call center or Tom. Jerry should be kicked out.
/api/Account/20 should be accessible by any admin, call center or Jerry. Tom should be kicked out.
In webforms the typical solution is to just check if the user is a client and verify their id against the request. (I know AuthorizeAttribute is not in webforms, but showing as an example of what it would covert to in webapi/mvc.)
[Authorize(Roles = "Administrator, CallCenter, Client")]
public string Get(int userID)
{
if (Thread.CurrentPrincipal.IsInRole("Client") && Thread.CurrentPrincipal.Identity.userID != userID)
{
//Kick them out of here.
}
return "value";
}
This will work, but it seems like the check for ownership should happen in a single location before it reaches the controller and should be reusable throughout an application. I am guessing the best place would either be a custom AuthorizationFilterAttribute or a custom AuthorizeAttribute and maybe create a new role ClientOwner.
[Authorize(Roles = "Administrator, CallCenter, ClientOwner")]
public string Get(int userID)
{
return "value";
}
Custom AuthorizeAttribute
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
//If user is already authenticated don't bother checking the header for credentials
if (Thread.CurrentPrincipal.Identity.IsAuthenticated) { return; }
var authHeader = actionContext.Request.Headers.Authorization;
if (authHeader != null)
{
if (authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) &&
!String.IsNullOrWhiteSpace(authHeader.Parameter))
{
var credArray = GetCredentials(authHeader);
var userName = credArray[0];
var password = credArray[1];
//Add Authentication
if (true)
{
var currentPrincipal = new GenericPrincipal(new GenericIdentity(userName), null);
var user = GetUser(userName);
foreach (var claim in user.Cliams)
{
currentPrincipal.Identities.FirstOrDefault().AddClaim(new Claim(ClaimTypes.Role, claim);
}
//**************Not sure best way to get UserID below from url.***********************
if (user.userTypeID = UserTypeID.Client && user.userID == UserID)
{
currentPrincipal.Identities.FirstOrDefault().AddClaim(new Claim(ClaimTypes.Role, "ClientOwner"));
}
Thread.CurrentPrincipal = currentPrincipal;
return;
}
}
}
HandleUnauthorizedRequest(actionContext);
}}
Can someone point me in the right direction as to the best place to handle the authorization of the individual user? Should this still be done in the controller or should I move it to a custom AuthorizationFilterAttribute or a custom AuthorizationAttribute or is there somewhere else this should be handled? If the proper place is in a custom attribute, then what is the best way to get the userID and should I create a new role like the example above or should I do something different?
This is a common scenario and I am very surprised that I have struggled to find examples of the above scenario. This leads me to believe that either everyone is doing the check in the controller or there is another term I am not aware of so I am not getting good google results.
I think you may be getting authorization and permissions confused. "Dynamic authorization" isn't something you ever do.
Authorization is the act of verifying an author.
Request claims it is being sent from Alice.
Request presents a password or authorization token that proves the requester is Alice.
Server verifies that the password or authorization token matches its records for Alice.
Permissions are the business logic that specifies who can do what in your system.
Request is already authorized, and we know it came from Alice.
Alice is requesting to delete an important resource.
Is Alice an administrator? If not, tell her she can't do that because she doesn't have permission. (403 Forbidden)
The built-in [Authorize] attribute lets you optionally specify Roles that are permitted to access a resource. That option to specify permissions as part of authorization is slightly misplaced, in my opinion.
My advice would be to leave authorization as purely the process of verifying the author of a request. The BasicAuthHttpModule described here is close to what you want already.
Non-trivial permissions logic needs to be handled inside of your action body. Here's an example:
//Some authorization logic:
// Only let a request enter this action if the author of
// the request has been verified
[Authorize]
[HttpDelete]
[Route("resource/{id}")]
public IHttpActionResult Delete(Guid id)
{
var resourceOwner = GetResourceOwner(id);
//Some permissions logic:
// Only allow deletion of the resource if the
// user is both an admin and the owner.
if (!User.IsInRole("admin") || User.Identity.Name != resourceOwner)
{
return StatusCode(HttpStatusCode.Forbidden);
}
DeleteResource(id);
return StatusCode(HttpStatusCode.NoContent);
}
In this example, it would be difficult to convey the permissions logic as an attribute on the action, because the portion of the permissions that compares the current user to the resource owner can only be evaluated after you have actually gotten the resource owner info from your backend storage device.

Role Management in ASP MVC 5 (Microsoft.AspNet.Identity)

in ASP MVC5 RC I didn't get the role system to work.
My database has all needs tables an role exist but proofing if user is in role always return false (no SQL exception or something)!?
Did I need to activate role system for IPrincipal somewhere?
Test code:
AccountController accCont = new AccountController();
// check role exist : result = true
var roleExist = await accCont.IdentityManager.Roles.RoleExistsAsync("61c84919-72e2-4114-9520-83a3e5f09de1");
// try find role by name : result = role object
var role = await accCont.IdentityManager.Roles.FindRoleByNameAsync("ProjectAdministrator");
// check with AccountController instance : result = true
var exist = await accCont.IdentityManager.Roles.IsUserInRoleAsync(User.Identity.GetUserId(), role.Id);
// check if current user is in role : result (both) = false????
var inRole = User.IsInRole(role.Id);
var inRole2 = User.IsInRole(role.Name);
I also try to build an custom extenuation like the IIdentity.GetUserId() extension method from Microsoft.AspNet.Identity.Owin Namespace.
namespace Microsoft.AspNet.Identity
{
public static class IdentityExtensions
{
public static string IsUserInRole(this IIdentity identity)
{
if (identity == null)
{
throw new ArgumentNullException("identity");
}
ClaimsIdentity identity2 = identity as ClaimsIdentity;
if (identity2 != null)
{
var result = identity2.FindFirstValue(IdentityConfig.Settings.GetAuthenticationOptions().RoleClaimType);
return null; // later result
}
return null;
}
}
}
But the result for claim Type RoleClaimType is always null :(
I'm really stuck with this.
Thank you for your help! Steffen
I'm trying to understand how to use roles in MVC 5 myself, which is what brought me here. I can't answer your question, but check out this link. The downloaded solution works right out of the box and I've already been able to cut-and-paste some of the code and get it working in my own app. Now I'm trying to fully understand what it's doing.
http://www.typecastexception.com/post/2013/11/11/Extending-Identity-Accounts-and-Implementing-Role-Based-Authentication-in-ASPNET-MVC-5.aspx
It may not answer your question but at least it's a fully working solution that actually does work as described without a lot of hassle, so it's a good starting point.
User.IsInRole is basically looking at the claims for the currently signed in user. What does your sign in logic look like? That is what is responsible for minting the cookie that turns into the User identity. That needs to have the Role claim set properly for the IsInRole method to work correctly.

Why mocking asp.net membership role fails to restrict access with Authorize attribute

I have successfully setup up mocking to test asp.net forms authorization but I am seeing some unexpected behavior with the Role membership and the Authorize attribute. Specifically when the ChangePassword method is called as shown below I would expect that I would get a Unauthorized Access redirect to the Logon screen however I am able to step all the way through the ChangePassword method and receive a change password success. Can anyone help direct me in what I am doing wrong?
I have tested that in the ChangePassword method calling the IsUserInRole method does work as expected and I can redirect to the Logon screen in that condition but that seems burdensome to test for that condition in all my methods. Thanks in advance. I have also tried not assigning the user to a role (instead of returning false with mock) but the result is the same, change password succeeds.
[TestMethod]
public void TestProfile()
{
string testUserName = "userName", password = "password1", newPassword = "newPassword1";
var prov = new Mock<IMembershipProvider>();
prov.Setup(v => v.ValidateUser(testUserName, password)).Returns(true);
var user = new Mock<MembershipUser>();
var frmAuth = new Mock<IFormsAuthentication>();
user.Setup(v => v.ChangePassword(password, newPassword)).Returns(true);
prov.Setup(v => v.GetUser(testUserName, true)).Returns(user.Object);
AccountController ctrl = new AccountController(prov.Object, frmAuth.Object);
var ctrlCtx = new Mock<ControllerContext>();
ctrlCtx.SetupGet(x => x.HttpContext.User.Identity.Name).Returns(testUserName);
ctrlCtx.SetupGet(x => x.HttpContext.User.Identity.IsAuthenticated).Returns(true);
//with this line I would expect to see a redirect to unauhorized
ctrlCtx.Setup(x => x.HttpContext.User.IsInRole("RoleToTest")).Returns(false);
ctrl.ControllerContext = ctrlCtx.Object;
ctrl.Url = Moq.Mock.Of<IUrlHelper>(x => x.IsLocalUrl(It.IsAny<string>()) == true);
ChangePasswordModel changePass = new ChangePasswordModel() { NewPassword = newPassword, OldPassword = password, ConfirmPassword = password };
var result = ctrl.ChangePassword(changePass) as ViewResult;
string expectedViewName = "Logon";
Assert.AreEqual(result.ViewName, expectedViewName, true /* ignoreCase */,
string.Format("The expected view '{0}' was not returned. Did change password succeed?", expectedViewName));
}
[Authorize(Roles="RoleToTest")]
[HttpPost]
public ActionResult ChangePassword(ChangePasswordModel model)
{
if (ModelState.IsValid)
{
// ChangePassword will throw an exception rather
// than return false in certain failure scenarios.
bool changePasswordSucceeded;
try
{
MembershipUser currentUser = membershipProvider.GetUser(User.Identity.Name, true /* userIsOnline */);
changePasswordSucceeded = currentUser.ChangePassword(model.OldPassword, model.NewPassword);
}
catch (Exception e)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(e);
changePasswordSucceeded = false;
}
if (changePasswordSucceeded)
{
return View("ChangePasswordSuccess");
}
else
{
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
}
}
// If we got this far, something failed, redisplay form
return View("ChangePassword", model);
}
Specifically when the ChangePassword method is called as shown below I
would expect that I would get a Unauthorized Access redirect to the
Logon screen however I am able to step all the way through the
ChangePassword method and receive a change password success.
Your expectation is wrong. Attributes such as [Authorize] are just metadata baked into the assembly at compile-time. If there's nothing to interpret them, well, nothing will happen at all at runtime.
The thing is that the [Authorize] attribute is used by the ASP.NET MVC request processing pipeline. In your unit test you are doing a simple call of the controller action. Nothing more. There's no code to maker any sense of this attribute.
So you don't need to unit test that your controller action is redirecting to the LogOn page if the user is not authenticated. What you have to unit test is that your controller action is decorated with the Authorize attribute. The fact that this attribute will redirect to the Logon page when placed on a controller action is something that the ASP.NET MVC team already extensively unit tested during the development of the framework so you don't need to repeat their job. Just trust them.
So here's how a typical unit test could look like:
[TestMethod]
public void ChangePassword_Action_Should_Be_Accessible_Only_To_Users_Belonging_To_The_RoleToTest_Role()
{
Expression<Func<AccountController, ActionResult>> changePwdEx =
x => x.ChangePassword(null);
var authorize = (changePwdEx.Body as MethodCallExpression)
.Method
.GetCustomAttributes(typeof(AuthorizeAttribute), true)
.OfType<AuthorizeAttribute>()
.First();
Assert.AreEqual("RoleToTest", authorize.Roles);
}
Alright, now you have unit tested that this controller action is only accessible to users belonging to the RoleToTest role.
In your next unit test you assume that a user belongs to this role (by mocking the corresponding classes) and you assert that the body of the controller action executes as expected.

Categories

Resources