ASP.NET MVC - user authorize in controller - entity-framework

I have table in my database with names of users (with domains, for example: Domain1\user1). My project has Windows Authentication. I have two controllers - one for all logged in users and second for specific user. My table has 3 columns: (Id, Name, Extra), where "Extra" is only fill for user, who is admin (it has varchar: "admin").
I want to create such authorization, where only admin will have access to site with second controller. How to write it?
For any suggestions I will be very appreciate.
Thanks in advance for help. ;)
Monic
====Edit====
from example: ASP.NET MVC 4 Custom Authorize Attribute with Permission Codes (without roles)
In my main controller:
[AuthorizeUser(AccessLevel = "Extra")]
public class SecureController : Controller
{
(...)
}
public class AuthorizeUserAttribute : AuthorizeAttribute
{
public string AccessLevel { get; set; }
private Report_DBEnt REn = new Report_DBEnt();
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (!isAuthorized)
{
return false;
}
string privilegeLevels = string.Join("", REn.Users.Where(u => u.Extra.Equals("admin")).FirstOrDefault());
if (privilegeLevels.Contains(this.AccessLevel))
{
return true;
}
else
{
return false;
}
}
}
I've tried use it sth like this, but I have no access to my site.

Try this:
[AuthorizeUser(AccessLevel = "admin")]
public class SecureController : Controller
{
(...)
}
Hope it will help
Update
public class AuthorizeUserAttribute : AuthorizeAttribute
{
public string AccessLevel { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if(httpContext.User.Identity.IsAuthenticated)
{
string privilegeLevels = string.Join("",GetUserRights(httpContext.User.Identity.Name.ToString());
if (privilegeLevels.Contains(this.AccessLevel))
{
return true;
}
else
{
return false;
}
}
else
return false;
}
}
private string GetUserRights(string userName)
{
private Report_DBEnt REn = new Report_DBEnt();
return REn.Users.Where(u => u.UserName== userName).Select(u=>u.Extra);
}

Related

Get current user id in dbcontext

I am working on an ASP.NET Core web application. This application works with a SQL Server database and I am using Entity Framework 6.
I want to automatically add, on each entity, a last modification date and user id.
My question is: how can I automatically set the user id to each entity?
I have tried to override SaveChanges() in the DbContext. It works fine, but I can't access the Microsoft identity classes there...
Thanks
(Apologies in advance if the method signatures look a bit odd, I'm one of the rare breed that uses explicit interface implementation to help avoid stale code dust-bunnies littering code as I re-factor)
An example on accessing a session state to get the user from within a DbContext using a session token from a Forms Authentication web application:
[Serializable]
public class UserDetails
{
public int UserId { get; set; }
public string DisplayName { get; set; }
}
public interface ISessionHelper
{
UserDetails CurrentUser { get; set; }
void Clear();
bool IsAuthenticated();
}
public class SessionHelper : ISessionHelper
{
private const string UserDetailsKey = "YourUniqueSessionIdKey";
private ISessionHelper This => this;
UserDetails ISessionHelper.CurrentUser
{
get
{
try
{
var token = (UserDetails)HttpContext.Current.Session[UserDetailsKey];
return token;
}
catch
{
throw new ApplicationException("The current session could not be resolved.");
}
}
set
{
try
{
HttpContext.Current.Session[UserDetailsKey] = value;
}
catch
{
throw new ApplicationException("The current session state could not be set.");
}
}
}
/// <summary>
/// <see cref="ISessionHelper.Clear"/>
/// </summary>
void ISessionHelper.Clear()
{
HttpContext.Current.Session.Clear();
}
/// <summary>
/// <see cref="ISessionHelper.IsAuthenticated(string)"/>
/// </summary>
bool ISessionHelper.IsAuthenticated()
{
try
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var userDetails = This.CurrentUser;
return (userDetails != null && userDetails.UserId != 0);
}
catch
{
return false;
}
}
}
public interface ICurrentUserLocator
{
bool IsUserLoggedIn { get; }
int CurrentUserId { get; }
string CurrentUserDisplayName { get; }
}
public sealed class FormsAuthenticationUserLocator : ICurrentUserLocator
{
private const string UnauthorizedAccessExceptionMessage = "No user is currently logged in.";
private readonly ISessionHelper _sessionHelper = null;
[ExcludeFromCodeCoverage]
private ICurrentUserLocator This => this;
private UserDetails _userDetails = null;
[ExcludeFromCodeCoverage]
private UserDetails CurrentUser
{
get { return _userDetails ?? (_userDetails = SessionHelper.UserDetails); }
}
[ExcludeFromCodeCoverage]
bool ICurrentUserLocator.IsUserLoggedIn => CurrentUser != null;
[ExcludeFromCodeCoverage]
int ICurrentUserLocator.CurrentUserId => CurrentUser?.UserId ?? throw new UnauthorizedAccessException(UnauthorizedAccessExceptionMessage);
[ExcludeFromCodeCoverage]
string ICurrentUserLocator.CurrentUserDisplayName => CurrentUser?.DisplayName ?? throw new UnauthorizedAccessException(UnauthorizedAccessExceptionMessage);
public FormsAuthenticationUserLocator(ISessionHelper sessionHelper)
{
_sessionHelper = sessionHelper ?? throw new ArgumentNullException("sessioNHelper");
}
}
Assuming you are using dependency injection with a container, register the two classes against their respective interfaces in the container on a per-request lifespan. Then add a dependency on ICurrentUserLocator into your DbContext:
public class AppDbContext : DbContext
{
private readonly ICurrentUserLocator _currentUserLocator = null;
public AppDbContext(ICurrentUserLocator currentUserLocator)
{
_currentUserLocator = currentUserLocator ?? throw new ArgumentNullException("currentUserLocator");
}
}
Now in your overridden SaveChanges you can fetch the current user ID:
var currentUser = Users.Single(x => x.UserId == _currentUserLocator.CurrentUserId);
var updatedEntities = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Modified)
.Select(x => x.Entity)
.Cast<EditableEntityBase>();
foreach (var entity in updatedEntities)
{
entity.LastModifiedBy = currentUser;
entity.LastModifiedAt = DateTime.Now;
}
var insertedEntities = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added)
.Select(x => x.Entity)
.Cast<EditableEntityBase>();
foreach (var entity in insertedEntities)
{
entity.CreatedBy = entity.LastModifiedBy = currentUser;
entity.CreatedAt = entity.LastModifiedAt = DateTime.Now;
}
If you only expose the FKs rather than user navigation properties then you can just set the ID. If your app has a Log Out feature be sure to call the SessionHelper.Clear() method. In this example I modified the session helper I typically use to just focus on the current user details to keep it simple. It normally tracks a few other details, hence why the Clear() method wipes the entire session. It's a standardized wrapper for handling known session state rather than littering code with Session["somestring"] everywhere.

REST API - CreatedAtRoute method doesn't return a value

I'm building some REST API server in .NET Core and using Postman software to test it. I have a problem with POST method which doesn't return me any value ("Could not get any response") when I try to perform second Add operation on my DBContext class inside CreateUser method. My code :
UsersController :
[Produces("application/json")]
[Route("api/[controller]")]
public class UsersController : Controller
{
private readonly DBContext _context;
#region CONSTRUCTOR
public UsersController(DBContext context)
{
_context = context;
}
#endregion
#region HTTP GET
// GET: api/users || api/users?cardnr=xxx
[HttpGet]
public async Task<IActionResult> GetUsers(string cardNr)
{
if (String.IsNullOrEmpty(cardNr))
{
try
{
var users = await _context.Users.ToListAsync();
if (users.Any())
{
return Json(users);
}
else
{
return NotFound();
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
else
{
try
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.Cards.Any(c => c.CardNumber.Equals(cardNr)));
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
}
//GET: api/users/1
[HttpGet("{id}", Name = "GetUserByID")]
public async Task<IActionResult> GetUserByID(Int32 id)
{
try
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.IDUser == id);
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
#endregion
#region HTTP POST
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] Models.User userToCreate, string userGroupID)
{
if (userToCreate == null)
{
return BadRequest();
}
else
{
try
{
_context.Users.Add(userToCreate);
int parsingResult;
// if user passed userGroupID
if (userGroupID != null)
{
// parsing if userGroupID is a number
if (!int.TryParse(userGroupID, out parsingResult))
{
return BadRequest();
}
else
{
// if client want to assign a new user to some group
if (parsingResult > 0)
{
// creating new record in UserGroup table - assigning a user to group
var userGroup = new Models.UserGroup();
_context.Entry(userGroup).Property("IDGroup").CurrentValue = parsingResult;
_context.Entry(userGroup).Property("IDUser").CurrentValue = userToCreate.IDUser;
_context.UserGroups.Add(userGroup); // NOTE HERE
}
}
}
await _context.SaveChangesAsync();
return CreatedAtRoute("GetUserByID", new { id = userToCreate.IDUser }, userToCreate);
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
}
#endregion
}
User model :
public class User
{
[Key]
public int IDUser { get; set; }
[Required]
public string Name { get; set; }
public List<UserGroup> UsersGroups { get; set; }
}
UserGroup model :
public class UserGroup
{
public Group Group { get; set; }
public User User { get; set; }
}
DBContext class :
public class DBContext : DbContext
{
public DBContext(DbContextOptions<DBContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// shadow property - foreign key
modelBuilder.Entity<UserGroup>()
.Property<int>("IDUser");
// shadow property - foreign key
modelBuilder.Entity<UserGroup>()
.Property<int>("IDGroup");
modelBuilder.Entity<UserGroup>()
.HasKey( new string[]{ "IDUser", "IDGroup" });
modelBuilder.Entity<UserGroup>()
.HasOne(ug => ug.Group)
.WithMany(g => g.UsersGroups)
.HasForeignKey("IDGroup");
modelBuilder.Entity<UserGroup>()
.HasOne(ug => ug.User)
.WithMany(u => u.UsersGroups)
.HasForeignKey("IDUser");
base.OnModelCreating(modelBuilder);
}
public DbSet<Group> Groups { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserGroup> UserGroups { get; set; }
}
The problem lies in HttpPost method in UsersController.
When I do "normal" POST and pass JSON object which contain a user to add without assigning it to group (empty userGroupID parameter) everything is ok - user gets added to the DataBase and Postman returns me a user with its ID.
screen :
and when I try to add a new user but with adding it to specific group I always get an error :
screen :
Even despite that error new user gets properly added to DB and associated with its group (record gets added to UserGroup table; UserGroup is join table between Users and Groups table). So I have proper data in my DB but I always get this error and I can't return new added user to client who called API and can't get his ID. Am I doing something wrong in my CreateUser method ?
UPDATE :
I have added a comment line in "NOTE HERE" in CreateUser method in UsersController. If I comment whole this line I don't get an error from Postman but obviously I don't get my User associated with its group (I don't get new record added to UserGroup join table). So it seems like another Add method on context object causing an error ... Does it make sense ?
Did you try to debug it?
Set a breakpoint on the row:
if (userToCreate == null)
Send again the request with Postman and debug your app. There you can see what and where it goes wrong.
Please let me know how it is going so I know how can I help you:)

ASP MVC EF6 Multi Tenant based on host

Sorry, another multi tenancy post. I can't find a good solution to site, I have read tons of great posts on multi tenancy for ASP MVC but I still need some good advice.
I have an ASP MVC Entity Framework 6 Code First web application. This app has to work for many different clients using a single database for all of them.
I have an entity for all the clients, and each client can have different hosts.
public class Client
{
public int ClientId { get; set; }
public string Name { get; set; }
...
public ICollection<ClientHost> Hosts { get; set; }
}
public class ClientHost
{
public int ClientId { get; set; }
public Client Client { get; set; }
public string Name { get; set; }
}
I have added a column "ClientId" to all the entities I need to filter, so I can separate data from different clients.
public class SomeEntity
{
public int Id { get; set; }
...
public int ClientId { get; set; }
}
First thing I need is, base on the host, retrieve the ClientId to work with.
private static int GetClientId()
{
var currentClient = Convert.ToInt32(HttpRuntime.Cache[CacheClient]);
if (currentClient != null) return currentClient;
lock (Synclock)
{
using (var dataContext = new MyDataContext())
{
var urlHost = HttpContext.Current.Request.Url.Host;
currentClient = dataContext.Clients
.FirstOrDefault(p => p.Hosts.Any(h => h.Name == urlHost));
if (currentClient == null) return null;
HttpRuntime.Cache.Insert(CacheClient, currentClient, null, Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(0), CacheItemPriority.Default, null);
return currentClient;
}
}
}
QUESTION 1
As you see I get the clientId from DB and store it in cache, so I don't have to call DB every time I need it.
I don't know if there is a better approach to get the client Id or, better, to store it.
EDIT
After investigation I have created a variable in DbCOntext and initialize it in the Startup.cs file.
public class MyDataContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public static string ClientId { get; set; }
public MyDataContext() : base("MyDataBase") { }
public static MyDataContext Create()
{
return new myDataContext();
}
....
}
In Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
MyDataContext.ClientId = ClientConfiguration.GetCurrentClientId();
ConfigureAuth(app);
}
}
QUESTION 2
Once I have the ClientId, I need to add a filter to every query that needs it. Doing this manually can take you to make many errors or forget to do it in some places.
I need a way that the application can add the filter to all queries automatically (only those entities that need it), so I don't have to worry about a client getting other client's data. Also I need to add the ClientId to all the Insert and Update commands.
I have read about filtering and/or use EF Interceptors, but after reading some posts about that I can't figure out how to do it. Need some help here.
Thanks in advance.
EDIT
In order to solve QUESTION 2 I have followed this great post by Xabikos:
http://xabikos.com/2014/11/17/Create-a-multitenant-application-with-Entity-Framework-Code-First-Part-1/
I have changed it a little bit, since I don't use Users to get the current tenant and instead I use the host. This is part of the program I don't know yet how I'm going to solve but, assuming I already have the ClientId I can add filters to all the queries without realizing that is happening:
I have replaced all the user logic:
private static void SetTenantParameterValue(DbCommand command)
{
if (MyDataContext.ClientId == 0) return;
foreach (DbParameter param in command.Parameters)
{
if (param.ParameterName != TenantAwareAttribute.TenantIdFilterParameterName)
continue;
param.Value = MyDataContext.ClientId;
}
}
Same in all the places...
Than I only have to mark the entities that have to filter with TenantAware, indicating the property. In this case I do in my base class and then apply that base class to all the entities I need.
[TenantAware("ClientId")]
public abstract class ClientEntity : Entity, IClientEntity
{
public int ClientId { get; set; }
public Client Client { get; set; }
}
Here are a couple of things I have done in the past that might help.
Question 1:
I am not a big fan of session as the web is supposed to be stateless. However, it is sometimes necessary. Your approach is reasonable. You could also use cookies as well. What I use are Json Web Tokens (JWT) via my authentication provider (Auth0.com). For each request as it is authenticated, I look for this client id. Here is an example. This is MVC 6 as well. You could do the same type of things w/ cookies.
public class Auth0ClaimsTransformer : IClaimsTransformer
{
private string _accountId = AdminClaimType.AccountId.DefaultValue;
private string _clientId = AdminClaimType.ClientId.DefaultValue;
private string _isActive = AdminClaimType.IsActive.DefaultValue;
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
foreach (var claim in context.Principal.Claims)
{
switch (claim.Type)
{
case "accountId":
_accountId = claim.Value ?? _accountId;
break;
case "clientId":
_clientId = claim.Value ?? _clientId;
break;
case "isActive":
_isActive = claim.Value ?? _isActive;
break;
}
}
((ClaimsIdentity)context.Principal.Identity)
.AddClaims(new Claim[]
{
new Claim(AdminClaimType.AccountId.DisplayName, _accountId),
new Claim(AdminClaimType.ClientId.DisplayName, _clientId),
new Claim(AdminClaimType.IsActive.DisplayName, _isActive)
});
return Task.FromResult(context.Principal);
}
Then in my Startup.cs Configure method I plug in my claims transformer.
app.UseJwtBearerAuthentication(options);
app.UseClaimsTransformation(new ClaimsTransformationOptions
{
Transformer = new Auth0ClaimsTransformer()
});
Next I use a base authentication controller that parses out my claims into properties I can use in my controller.
[Authorize]
[Route("api/admin/[controller]")]
public class BaseAdminController : Controller
{
private long _accountId;
private long _clientId;
private bool _isActive;
protected long AccountId
{
get
{
var claim = GetClaim(AdminClaimType.AccountId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _accountId);
return _accountId;
}
}
public long ClientId
{
get
{
var claim = GetClaim(AdminClaimType.ClientId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _clientId);
return _clientId;
}
}
public bool IsActive
{
get
{
var claim = GetClaim(AdminClaimType.IsActive);
if (claim == null)
return false;
bool.TryParse(claim.Value, out _isActive);
return _isActive;
}
}
public string Auth0UserId
{
get
{
var claim = User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
return claim == null ? string.Empty : claim.Value;
}
}
private Claim GetClaim(AdminClaimType claim)
{
return User.Claims.FirstOrDefault(x => x.Type == claim.DisplayName);
}
Finally in my controller it is trivial to extract which tenant is making the call. e.g.
public FooController : BaseController
{
public async Task<IActionResult> Get(int id)
{
var foo = await _fooService.GetMultiTenantFoo(ClientId, id);
return Ok(foo);
}
}
Question 2:
One of the ways I have used in the past is create a BaseMultiTenant class.
public class BaseMultiTenant
{
public int ClientId {get;set;}
public virtual Client Client {get;set;}//if you are using EF
}
public class ClientHost : BaseMultiTenant
{
public string Name {get;set;}
//etc
}
Then simply create an extension method for multi-tenant based entities. I know this doesn't "do it automatically" but it is an easy way to ensure each multi-tenant entity is being called only by its owner.
public static IQueryable<T> WhereMultiTenant<T>(this IQueryable<T> entity, int clientId, Expression<Func<T, bool>> predicate)
where T : BaseMultiTenant
{
return entity.Where(x => x.ClientId == clientId)
.Where(predicate);
}
Then when someone calls for their resource you can:
var clientHost = _myContext.ClientHosts
.WhereMultiTenant(ClientId,
x => x.Name == "foo")
.FirstOrDefault();
Hope this is helpful.
Also found a similar example using an interface.

Automapper : Mapping to an Interface

I am using AutoMapper to map between an entity and an Interface
First I created my mapping and checked it is valid.
AutoMapper.Mapper.CreateMap<User, IUserViewModel>();
AutoMapper.Mapper.AssertConfigurationIsValid();
Then I created a method that uses this mapping:
public IUserViewModel GetUser(int id)
{
var user= _userRepository.GetByKey(id);
var currentUser = Mapper.Map<User, IUserViewModel>(user);
return currentUser;
}
I am using this method in another place of my code
IUserViewModel myUser = XXXXX.GetUser(3);
This issue is this is myUser is always null.
However, when I debug my method and stop inside it juste before returning , I can see that my object currentSupplier is created and filled up correctly.
But when the method returns I get a null value.
I guess this has to do with the fact my object currentSupplier is created as
Proxy<....>
Any help?
Thank you.
Adding a full copy of my code that proves that the above works for me - couldnt fit it into the comments.
class Program
{
static void Main(string[] args)
{
Program program = new Program();
IUserViewModel myUser = program.GetUser(3);
Console.WriteLine(myUser.Name); // Prints Frank
Console.Read();
}
public Program()
{
Mapper.CreateMap<User, IUserViewModel>();
Mapper.AssertConfigurationIsValid();
}
private UserRepo _userRepository = new UserRepo();
public IUserViewModel GetUser(int id)
{
var user = _userRepository.GetByKey(id);
var currentUser = Mapper.Map<User, IUserViewModel>(user);
return currentUser;
}
}
public class UserRepo
{
public User GetByKey(int id)
{
return new User { Name = "Frank" };
}
}
public interface IUserViewModel
{
string Name { get; set; }
}
public class User
{
public string Name { get; set; }
}
Could you add some additional content to show where this is failing?

What should I return from my service layer? DotNetOpenAuth

I am wondering what should I return in this case. My Ui and service layer are in different projects.
This is what happens
-> User Comes to Site -> User chooses openId provider and hits login -> Post back to controller
[HttpPost]
public ActionResult Login(LoginViewModel loginViewModel)
{
var test = accountService.SendOpenIdRequest(loginViewModel.OpenId);
}
public class LoginViewModel
{
public OpenId OpenId { get; set; }
}
So I take in a ViewModel that contains my domain class.
public class OpenId
{
public string Url { get; set; }
}
So far in my SendOpenIdRequest
public ? SendOpenIdRequest(OpenId openId)
{
var openIdRelyingParty = new OpenIdRelyingParty();
var response = openIdRelyingParty.GetResponse();
Identifier id;
if (Identifier.TryParse(openId.Url, out id))
{
try
{
var req = openIdRelyingParty.CreateRequest(openId.Url);
return req.RedirectingResponse
}
catch (ProtocolException ex)
{
}
}
return null;
}
Now this is where I get lost since their are so many things I could return.
I could return
return req.RedirectingResponse.AsActionResult()
However I think this would be bad as now I am depending on asp.net mvc ActionResult and if I say use this service layer for some other project(maybe I have a webservice that connects to a mobile application). It won't won't work to well.
I could return OutgoingWebResponse but I am not really sure what to do with it once I get it back.
I could also return the IAuthenticationRequest what is generated from CreateRequest()
Finally I could return my Domain Object(OpenId) with one of the ones I listed above in it.
You could return an OutgoingWebResponse:
public OutgoingWebResponse SendOpenIdRequest(OpenId openId)
{
using (var openIdRelyingParty = new OpenIdRelyingParty())
{
var response = openIdRelyingParty.GetResponse();
Identifier id;
if (Identifier.TryParse(openId.Url, out id))
{
try
{
var req = openIdRelyingParty.CreateRequest(openId.Url);
return req.RedirectingResponse
}
catch (ProtocolException ex)
{
}
}
return null;
}
}
and then in your controller:
[HttpPost]
public ActionResult Login(LoginViewModel loginViewModel)
{
var response = accountService.SendOpenIdRequest(loginViewModel.OpenId);
if (response == null)
{
ModelState.AddModelError(
"openid_identifier",
"The specified login identifier is invalid"
);
return View();
}
return response.AsActionResult();
}