AuthorizeAttribute with Roles but not hard-coding the Role values - asp.net-mvc-3

Is it possible to add the Roles but not hard-coding the values like:
[Authorize(Roles="members, admin")]
I would like to retrieve these roles from a database or configuration file where I wouldn't need to rebuild the application if I needed to add/remove Roles for a Controller Action.
I know with the enums it can be done...
http://www.vivienchevallier.com/Articles/create-a-custom-authorizeattribute-that-accepts-parameters-of-type-enum
but even this is still not flexible enough for my needs; it's still somewhat of a hard-code, even though it is cleaner.

You can create your custom authorization attribute, that will compare user roles and roles from your configuration.
public class ConfigAuthorizationAttribute: AuthorizeAttribute
{
private readonly IActionRoleConfigService configService;
private readonly IUserRoleService roleService;
private string actionName;
public ConfigAuthorizationAttribute()
{
configService = new ActionRoleConfigService();
roleService = new UserRoleService();
}
protected override void OnAuthorization(AuthorizationContext filterContext)
{
actionName = filterContext.ActionDescription.ActionName;
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var availableRoles = configService.GetActionRoles(actionName); // return list of strings
var userName = httpContext.User.Identity.Name;
var userRoles = roleService.GetUserRoles(userName); // return list of strings
return availableRoles.Any(x => userRoles.Contains(x));
}
}
I hope it helps you.

One solution would be to create an intermediate entity called "Group" where users are added to groups (eg: Admin, Support) and groups have set of Roles. (eg: Create users). This way you can hard code the Roles and configure the relationships between users and groups.
You would need to implement a custom Role Provider. Go through Implementing a Role Provider On MSDN
[Authorize(Roles="CreateUser")]
public ActionResult Create()
{
}

Related

ASP.NET Boilerplate: What is the best solution for Four eyes principle

I want to add 4 eyes principle to ASP.NET Boilerplate framework. That means every change on Role, User,.. need to be approved (by another admin) before applied to the system. I have searched for some time but no answer. So what is the best solution for this flow?
Can I create the same tables with Abp tables (dbo.AbpUser_Temp, etc) and the all the changes will be stored in these tables? Is there any better solution?
Example: In the application, Admin1 has created a user named User1. But this user cannot login to the application until he was approved by Admin2.
Simple Workflows
Example: In the application, Admin1 has created a user named User1. But this user cannot login to the application until he was approved by Admin2.
Simple workflows like these can be appropriately handled by a property and a method:
public class User : AbpUser<User>
{
public bool IsApproved { get; set; }
public void Approve(User approver)
{
if (approver.Id != CreatorUserId)
{
IsApproved = true;
}
}
}
Complex Workflows
Complex workflows like "every change" can do this instead of _Temp tables:
public abstract class ChangeBase : Entity<long>, IExtendableObject
{
public string EntityTypeAssemblyQualifiedName { get; set; }
public string EntityIdJsonString { get; set; }
public long ProposerUserId { get; set; }
public long? ApproverUserId { get; set; }
public string ExtensionData { get; set; }
}
public class Change : ChangeBase
{
[NotMapped]
public Type EntityType => Type.GetType(EntityTypeAssemblyQualifiedName);
[NotMapped]
public object EntityId => JsonConvert.DeserializeObject(EntityIdJsonString, EntityHelper.GetPrimaryKeyType(EntityType));
[NotMapped]
public bool IsApproved => ApproverUserId.HasValue && ApproverUserId != ProposerUserId;
[NotMapped]
public IDictionary<string, string> ChangedPropertyValuePairs => JObject.Parse(ExtensionData).ToObject<Dictionary<string, string>>();
public Change(EntityIdentifier changedEntityIdentifier, long proposerUserId, IDictionary<string, string> changedPropertyValuePairs)
{
EntityTypeAssemblyQualifiedName = changedEntityIdentifier.Type.AssemblyQualifiedName;
EntityIdJsonString = changedEntityIdentifier.Id.ToJsonString();
ProposerUserId = proposerUserId;
ExtensionData = JObject.FromObject(changedPropertyValuePairs).ToString(Formatting.None);
}
public bool Approve(long approverUserId)
{
if (approverUserId != ProposerUserId)
{
ApproverUserId = approverUserId;
return true;
}
return false;
}
}
Usage:
public class UserAppService // ...
{
private readonly IRepository<Change, long> _changeRepository;
public UserAppService(
IRepository<User, long> repository,
IRepository<Change, long> changeRepository) // : base(repository)
{
_changeRepository = changeRepository;
}
public void ChangeUserName(long userId, string newUserName)
{
// Validation, etc.
var changedPropertyValuePairs = new Dictionary<string, string> {
{ nameof(User.UserName), newUserName }
};
var change = new Change(
new EntityIdentifier(typeof(User), userId),
AbpSession.GetUserId(),
changedPropertyValuePairs
);
_changeRepository.Insert(change);
}
public void ApproveChange(long changeId)
{
// Validation, etc.
var change = _changeRepository.Get(changeId);
if (change.EntityType == typeof(User) && change.Approve(AbpSession.GetUserId()))
{
var user = Repository.Get((long)change.EntityId);
var changedPropertyValuePairs = change.ChangedPropertyValuePairs;
foreach (var changedProperty in changedPropertyValuePairs.Keys)
{
switch (changedProperty)
{
case nameof(User.UserName):
user.UserName = changedPropertyValuePairs[changedProperty];
break;
// ...
default:
break;
}
}
}
}
For development
Separate staging and production environments. Develop on one box, test it, get it reviewed and then deploy to a production box. Simple, effective and language agnostic advice.
Since ASP.NET Boilerplate framework included Entity Framework. You could also leverage migrations.
After you do your development work, and requires you to "update-database", then your SOP should be to have the admin review the (relatively simple) migrations that will be committed.
I hope that helps.
For application flow
There are probably quite a few ways to actually implement this so I'll cover a simple one get your idea's flowing, but keep in mind: The way you need to implement two person integrity must fit how your operating procedures should work, and not the other way around. Development doesn't drive business operations, business use-cases drive development.
Extending existing Identity* classes. Example: The ApplicationUser class (it may be named differently, but it derives from IdentityUser
Create 2 flags (boolean fields) that must be, and can only be turned 'on' by an administrator
a single administrator can only turn on 1 flag. (Which means you also have to store which administrator turned on which flag.)
The flags can be stored in the existing Abp* tables, or you can create a new table
Add logic so that the user is not allowed to log in unless those 2 flags are both on.
Example: default IdentityUserRole has identified and registered, but can not log in. Once both admin's switch the flags on, elevate the users IdentityUserRole to a role that is allowed to log in.

ASP NET Boilerplate, Login saved in ABPSession

I'm new on the asp net boilerplate framework, and i created a new mvc project multipage web application, without module zero.
I would like to use the AbpSession class that from what I understand has inside the user id that is taken over Thread.CurrentPrincipal.
However, I do not understand how to do after login, to save the user id in the Thread.CurrentPrincipal.
I've searched in the network and found several solutions, but in the AbpSession class the user id is always null.
The most optimal solution I found was this:
IList<Claim> claimCollection = new List<Claim>
{
new Claim(AbpClaimTypes.UserId, "5")
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claimCollection);
var principal = new ClaimsPrincipal(claimsIdentity);
Thread.CurrentPrincipal = principal;
It's the first time I use principal and identity and despite being documented I did not quite understand how to use them with asp net boilerplate, and I did not find sample codes.
Do you know how to tell me the right way or tell me where to find some functional codes?
Thanks
Start expanding AbpSession
The last section has cleared the way of thinking. Let's roll up our sleeves and expand in this section.
AbpSession attributes have been injected into three base classes: Application Service, AbpController and ABP ApiController.
So we need to extend AbpSession at the domain level, which is the project at the end of. Core.
Now suppose we need to extend an Email attribute.
Extending IAbpSession
Locate the project at the end of. Core, add the Extensions folder, and then add the IAbpSession Extension interface inherited from IAbpSession:
namespace LearningMpaAbp.Extensions
{
public interface IAbpSessionExtension : IAbpSession
{
string Email { get; }
}
}
Implementing IAbpSession Extension
Add the AbpSession Extension class, which is based on Claims AbpSession and implements the IAbpSession Extension interface.
namespace LearningMpaAbp.Extensions
{
public class AbpSessionExtension : ClaimsAbpSession, IAbpSessionExtension, ITransientDependency
{
public AbpSessionExtension(
IPrincipalAccessor principalAccessor,
IMultiTenancyConfig multiTenancy,
ITenantResolver tenantResolver,
IAmbientScopeProvider<SessionOverride> sessionOverrideScopeProvider) :
base(principalAccessor, multiTenancy, tenantResolver, sessionOverrideScopeProvider)
{}
public string Email => GetClaimValue(ClaimTypes.Email);
private string GetClaimValue(string claimType)
{
var claimsPrincipal = PrincipalAccessor.Principal;
var claim = claimsPrincipal?.Claims.FirstOrDefault(c => c.Type == claimType);
if (string.IsNullOrEmpty(claim?.Value))
return null;
return claim.Value;
}
}
}
UserClaimsPrincipalFactory.cs
//Override CreateAsync method to add your custom claim
public override async Task<ClaimsPrincipal> CreateAsync(User user)
{
var claim = await base.CreateAsync(user);
claim.Identities.First().AddClaim(new Claim(ClaimTypes.Email, user.EmailAddress));
return claim;
}
Replace the injected AbbSession attribute
First replace the injected ABP Session in AbpController
Locate. ApplicationxxxControllerBase:AbpController. CS and inject IAbpSession Extension with attributes. Add the following code:
//AbpSession Hiding Parent Class
public new IAbpSessionExtension AbpSession { get; set; }
Replace the injected ABP Session in Application Service
Locate. ApplicationxxxAppServiceBase.cs. Introduce IAbpSession Extension with attributes, and add the following code as well:
//AbpSession Hiding Parent Class
public new IAbpSessionExtension AbpSession { get; set; }
Chaneg the injected ABP Session in Views AbpRazorPage
Locate. ApplicationxxxRazorPage.cs. Introduce IAbpSession Extension with attributes, and add the following code as well:
[RazorInject]
public IAbpSessionExtension AbpSession { get; set; }
Altough the question is very general, i would like to share you some code about how to add custom field to AbpSession in ASP.NET Core.
MyAppSession.cs
//Define your own session and add your custom field to it
//Then, you can inject MyAppSession and use it's new property in your project.
public class MyAppSession : ClaimsAbpSession, ITransientDependency
{
public MyAppSession(
IPrincipalAccessor principalAccessor,
IMultiTenancyConfig multiTenancy,
ITenantResolver tenantResolver,
IAmbientScopeProvider<SessionOverride> sessionOverrideScopeProvider) :
base(principalAccessor, multiTenancy, tenantResolver, sessionOverrideScopeProvider)
{
}
public string UserEmail
{
get
{
var userEmailClaim = PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == "Application_UserEmail");
if (string.IsNullOrEmpty(userEmailClaim?.Value))
{
return null;
}
return userEmailClaim.Value;
}
}
}
UserClaimsPrincipalFactory.cs
//Override CreateAsync method to add your custom claim
public override async Task<ClaimsPrincipal> CreateAsync(User user)
{
var claim = await base.CreateAsync(user);
claim.Identities.First().AddClaim(new Claim("Application_UserEmail", user.EmailAddress));
return claim;
}

Where can I load the user information to the session in ASP.NET MVC 5 with windows authentication?

I want to use the ASP.NET MVC 5 for my web app. I need use the windows authentication.
If I use the windows authentication where is the best place for reading user information (userid and roles) and store its to the Session?
I have the method for getting the user information by username from the database like this:
public class CurrentUser
{
public int UserId { get; set; }
public string UserName { get; set; }
public Roles Roles { get; set; }
}
public enum Roles
{
Administrator,
Editor,
Reader
}
public class AuthService
{
public CurrentUser GetUserInfo(string userName)
{
var currentUser = new CurrentUser();
//load from DB
return currentUser;
}
}
You've asked two questions (1) the best place to obtain user information and (2) how to store it in the Session. I'll answer (1) and in so doing perhaps show that you need not put any additional information in the session.
You've stated that your application is using Windows Authentication, so that means the hard work of authenticating the user has already been done by IIS/HttpListener before your app receives the request. When you receive the request there will be a WindowsPrincipal in HttpContext.User. This will have the windows username and AD roles already established, but you wish to use additional roles stored in the database...
You could access your AuthService from anywhere in your application, but probably the best approach is to register an IAuthorizationFilter and do the work there. By following this approach, the additional roles and other information you fetch from the database will be available in your controller methods and, perhaps more importantly, from any additional library code that needs to check user credentials.
Prior to .Net 4.5, if you wanted to add additional information to the WindowsPrincipal I think your only choice was to replace the system-provided User with another object that implemented the IPrincipal interface. This approach is still available (and what I recommend), but since the introduction of Windows Identity Foundation (WIF) in .Net 4.5, WindowsPrincipal is derived from  System.Security.Claims.ClaimsIdentityClaimsIdentity, which supports adding additional roles (and other useful information) to the system-provided principal. However, as several people have found, there is a bug/feature in Windows which can cause an exception The trust relationship between the primary domain and the trusted domain failed to be thrown when checking roles that have been added programmatically. We have found that a simple and reliable way to avoid this is to replace the User with a GenericPrincipal.
Steps required:
(1) create an IAuthorizationFilter.
class MyAuthorizationFilter : IAuthorizationFilter
{
AuthService _authService;
public MyAuthorizationFilter(AuthService authService)
{
_authService = authService;
}
public void OnAuthorization(AuthorizationContext filterContext)
{
var principal = filterContext.HttpContext.User;
if (principal.Identity != null && principal.Identity.IsAuthenticated)
{
// Add username (and other details) to session if you have a need
filterContext.HttpContext.Session["User"] = principal.Identity.Name;
// get user info from DB and embue the identity with additional attributes
var user = _authService.GetUserInfo(principal.Identity.Name);
// Create a new Principal and add the roles belonging to the user
GenericPrincipal gp = new GenericPrincipal(principal.Identity, user.RoleNames.ToArray());
filterContext.HttpContext.User = gp;
}
}
}
(2) Register your filter. This can be registered at the controller level or globally. Typically you will do this in App_Start\FilterConfig.cs:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyAuthorizationFilter(new AuthService()));
}
}
(3) Use the provided GenericPrincipal in your application code to answer questions about the user identification and other credentials. e.g. in your controller method you can access the username or any other "claims" (e.g. email address) stored in the GenericPrincipal by your filter.
public ActionResult Index()
{
ViewBag.Name = HttpContext.User.Identity.Name;
if(HttpContext.User.IsInRole("Administrator"))
{
// some role-specific action
}
return View();
}
Because you've used the built-in mechanism to record Principal roles, you can access user details from anywhere using HttpContext.User or System.Threading.Thread.CurrentPrincipal. Also you can use the AuthorizeAttribute in you controller methods to declare which actions are available to certain roles or users. e.g.
public class HomeController : Controller
{
[Authorize(Roles = "Administrator")]
public ActionResult Admin()
{
return View();
}
See MSDN for further details about ClaimsIdentity
I hope this helps
-Rob
First and foremost: never, never, never store user details in the session. Seriously. Just don't do it.
If you're using Windows Auth, the user is in AD. You have use AD to get the user information. Microsoft has an MSDN article describing how this should be done.
The long and short is that you create a subclass of UserIdentity and extend it with the additional properties you want to return on the user:
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("inetOrgPerson")]
public class InetOrgPerson : UserPrincipal
{
// Inplement the constructor using the base class constructor.
public InetOrgPerson(PrincipalContext context) : base(context)
{
}
// Implement the constructor with initialization parameters.
public InetOrgPerson(PrincipalContext context,
string samAccountName,
string password,
bool enabled)
: base(context,
samAccountName,
password,
enabled)
{
}
InetOrgPersonSearchFilter searchFilter;
new public InetOrgPersonSearchFilter AdvancedSearchFilter
{
get
{
if ( null == searchFilter )
searchFilter = new InetOrgPersonSearchFilter(this);
return searchFilter;
}
}
// Create the mobile phone property.
[DirectoryProperty("mobile")]
public string MobilePhone
{
get
{
if (ExtensionGet("mobile").Length != 1)
return null;
return (string)ExtensionGet("mobile")[0];
}
set
{
ExtensionSet( "mobile", value );
}
}
...
}
In the example code above, a property is added to bind to the AD's user's mobile field. This is done by implementing the property as shown utilizing ExtensionSet, and then annotating the property with the DirectoryProperty attribute to tell it what field it binds to.
The DirectoryRdnPrefix and DirectoryObjectClass attributes on the class need to line up with how your AD is set up.
Once this is implemented, then you will be able to get at the values simply by referencing them off User.Identity. For example, User.Identity.MobilePhone would return the mobile field from AD for the user.

Access Thread.CurrentPrincipal when binding with NInject in Owin context

I need to access my claims when binding my services, but appears that Ninject bind occurs before HttpMessageHandlerAdapter sets the Thread.CurrentPrincipal so I cannot access my claims when I'm binding my services. I tried this
kernel.Bind<IMyService>().ToMethod(x => GetMyService());
private static IMyService GetMyService()
{
var principal = Thread.CurrentPrincipal;
var userId = [GetUserIdFromPrincipalClaims]
return new MyService(userId);
}
But the claims list is empty, if I access Thread.CurrentPrincipal claims list from any action I can get an userId. How can I access my claims list when binding?
Since identity claims are a run-time value, I would say it can't be done (easily). However, one approach involving the decorator pattern and an abstract factory might look like this:
class MyServiceDecoraptor : IMyService
{
private readonly IMyServiceFactory _myServiceFactory;
public MyServiceDecoraptor(IMyServiceFactory myServiceFactory)
{
_myServiceFactory = myserviceFactory
}
public void MyServiceMethod()
{
var principal = Thread.CurrentPrincipal;
var userid = GetUserId(principal);
_myServiceFactory.Create(userid).MyServiceMethod();
}
//more code here
}

Access action method parameters from custom Authorize attribute in MVC 3

I am writing an MVC 3 app where users will be able to log in and manage their data. I want to prevent users from viewing or tampering with other user's data. My first instinct was to just verify access to the relevant object in each action method like this:
public ActionResult ShowDetails(int objectId)
{
DetailObject detail = _repo.GetById(objectId);
if (detail.User.UserID != (Guid)Membership.GetUser().ProviderUserKey)
{
return RedirectToAction("LogOff", "Account");
}
}
This works fine, but I thought it might be better to put the object authorization code into a custom Authorize attribute derived from AuthorizeAttribute, which I could then apply to the controller. Unfortunately, I have not been able to find a way to access the action method parameters from within my custom Authorize attribute. Instead, the only way I have found to access the incoming objectId is by examining httpContext.Request or filterContext.RequestContext.RouteData.Values:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private int _objectId = 0;
private IUnitOfWork _unitOfWork;
public MyAuthorizeAttribute(IUnitOfWork uow)
{
_unitOfWork = uow;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
int.TryParse((string) filterContext.RequestContext.RouteData.Values["id"], out _objectId);
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
int objectId = 0;
if (httpContext.Request.Params.AllKeys.Contains("id", StringComparer.InvariantCultureIgnoreCase))
{
int.TryParse(httpContext.Request[idKey], out objectId);
}
if (objectId != 0)
{
if (!IsAuthorized(objectId, httpContext.User.Identity.Name))
{
return false;
}
}
if (_objectId != 0)
{
if (!IsAuthorized(objectId, httpContext.User.Identity.Name))
{
return false;
}
}
return base.AuthorizeCore(httpContext);
}
private bool IsAuthorized(int objectId, string userName)
{
DetailObject detail;
detail = _unitOfWork.ObjectRepository.GetById(objectId);
if (detail == null)
{
return false;
}
if (userName != detail.User.UserName)
{
return false;
}
return true;
}
}
I find this approach to be very clunky. I really don't want to have to poke around in the RouteData or Request objects; it would be much cleaner to be able to access the action method parameters since model binding would have already pulled out the relevant data from the RouteData and Request.
I know I can access action method parameters from a custom Action Filter (as detailed here), but shouldn't data authorization code be placed in an Authorize Filter? The more examples I see of Authorize filters, the more I get the impression that they are intended only to handle roles.
My main question is: How do I access action method parameters from my custom Authorize Attribute?
Answer to your main question: no, unfortunately AuthorizationContext does not provide access to action parameters.
First off, you could use ValueProvider to not have to deal with whether the id is part of the route or a query parameter or HTTP posted, as follows:
public override void OnAuthorization(AuthorizationContext filterContext)
{
string id = filterContext.Controller.ValueProvider.GetValue("id").AttemptedValue;
...
}
This works for simple data types and introduces little overhead. However once you start using custom model binders for your action parameters, you have to inherit your filter from ActionFilterAttribute to avoid double binding:
[MyFilter]
public ActionResult MyAction([ModelBinder(typeof(MyModelBinder))] MyModel model)
{
...
}
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = filterContext.ActionParameters["model"] as MyModel;
...
}
}
While semantically inheriting from AuthorizeAttribute for authorization purposes sounds better, there are no other reasons for doing this. Moreover, I find using ActionFilterAttribute easier, as all you have to do is override only one method, not keeping a state for subsequent methods.

Resources