ASP.Net Web API partial response base on a role - asp.net-core

I know this is an old question, but could not find an answer for ASP.Net Core. I have ASP.Net Core web api application with role-based membership. Suppose we have an enitity from database with such fields:
public class Transaction
{
public int Id { get; set;}
public string UserId { get; set; }
public string ManagerId { get; set; }
public decimal Amount { get; set; }
}
And controller action:
[Authorize(Roles = "Admin,Manager,RegularUser")]
public async Task<IActionResult> Transactions(TransactionsRequest request)
{
var response = await _repository.Find(request, User);
return Ok(response);
}
My goal is to query and return partial result based on a role:
For Admin role I need to return all transations with all fields
For Manager role I need to return trasactions for current manager (based on managerId) without UserId field
For RegularUser role I need to return trasactions for current user (based on userId) without ManagerId field
I think it is simple to make three different actions, each one for specific role. But what if my role number will grow?

Related

API endpoint that depends of the Authenticated user

I have the following entities:
public class Company {
public Int32 CompanyId { get; set; }
public String Name { get; set; }
}
public class Project {
public Int32 ProjectId { get; set; }
public String Name { get; set; }
public virtual Company { get; set; }
}
public class ProjectUser {
public Int32 UserId { get; set; }
public String ProjectId { get; set; }
}
public class User {
public Int32 UserId { get; set; }
public String Name { get; set; }
}
I need to create an Api endpoint to get a Company's Projects:
companies/{companyId}/projects
This url makes sense but returning projects should be restricted to:
Projects, in provided company, where current authenticated user is associated (ProjectUser);
OR
All projects, in provided company, if current authenticated user is system admin.
Question
So this request depends on the authenticated user ...
Should I send the UserId in the request and then check its permissions in the API?
If yes, how would the url, e.g. companies/{companyId}/projects, become?
Or should I simply get the authenticated User and checks its permissions in the API without sending the UserId in the Request?
The latter. Your request should be authorized, most likely via sending the Authorization header with a previously obtained access token. You should not nor do you need to send the user id along with the request. That would be obtained via the user principal, specifically via the NameIdentifier claim. As for your resource-level authorization, you'll use the user id obtained from the principal to ensure that the authenticated user has access to said resource, most normally via a foreign key on the resource itself. The docs have a more extensive guide.

using EF Core IdentityContext and DbContext both for order management

I am working on creating an ecommerce website on ASP MVC Core 2. I inherited my user from IdentityUser and inherited context from IdentityDbContext for working with user data and inherited a different context from DbContext for working with products and orders etc.
Now, I want to link an order or shopping cart to a particular user and can not wrap my head around how to refer to the user in order table as they are in different contexts. I am also using the default guid created by EF as primary key in both the tables.
Should I ditch DbContext and use IdentityDbContext only? Does doing this causes problems with async methods in identity and other usual non async methods.
Here are some code snippets from my classes
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace shophe1.Models
{
public enum Gender { Male, Female, Other}
public class User : IdentityUser
{
public string FullName { get; set; }
public Gender Gender { get; set; }
public string ReferralID { get; set; }
public DateTime RegistrationDateTime { get; set; }
public string ActivationDateTime { get; set; }
public string UserType { get; set; }
public Wallet Wallet {get;set;}
public virtual ICollection<UserBankDetail> BankDetails { get; set; }
public virtual ICollection<UserAddress> AddressDetails { get; set; }
//public ShoppingCart Cart { get; set; }
//public ICollection<Order> Orders { get; set; }
}
}
Order class
using System.Collections.Generic;
namespace shophe1.Models
{
public class Order
{
public string OrderId { get; set; }
public ICollection<product> CartProducts { get; set; }
//public User User { get; set; }
public decimal OrderTotal { get; set; }
public decimal ShippingCharges { get; set; }
}
}
The issue is if I add user in order model and a collection of orders in user class, both contexts get mixed up and when migrating, all user and product related models get clubbed in same migration whether I use DbContext or IdentityContext in migrations --context option. This is because both user and orders are now interrelated.
Please advice.
Inherit your context from IdentityDbContext. You should have one context per database, ideally - especially if you want to relate the entities to each other.
If you actually want separate databases, such that the Identity tables reside in one, while your application tables reside in another, then you won't be able to directly relate the entities with each other. However, you can still create a pseudo-foreign key, where you simply store the id of a particular user in a column on one of your entities. You'd then merely need to issue a separate query on the other context with this id to fetch the user manually. For example:
var order = await appContext.Orders.FindAsync(orderId);
var user = await identityContext.Users.FindAsync(order.UserId);

Get current user logged in in Custom Authentication MVC 4

I have created a simple login table without simplemembership because in my project i don't need to have a register, the users need to be created automatically in the database. (Made following this tutorial).
I have a model Login:
public class Login
{
public virtual int UserId { get; set; }
public virtual string Username { get; set; }
public virtual string Password { get; set; }
public virtual List<Enrollment> Enrollments { get; set; }
}
and Enrollments:
public class Enrollment
{
public virtual int EnrollmentId { get; set; }
public virtual string Name { get; set; }
public virtual List<Login> Users { get; set; }
}
And i have a table Enrollment_Login because of the many-to-many relationship.
I need to create a view where i show a list of enrollments that the user logged in is "registered".
I have this query:
var query= from name in db.Enrollments
where name.Logins.Any(c=>c.LoginId==??)
select name;
If this query is right, how can i get the current user logged in?
You can't get the LoginId (which I think is a foreign key of your Login entity in Enrollement entity)
When user is authenticated the the name of that user is stored in this.User.Identity.Name if you're in your action method.
The name in your case is the UserName property in Login entity.
So you must write the following code :
var currentLogin = db.Logins.Single(u => u.UserName = this.User.Identity.Name);
var query= from name in db.Enrollments
where name.Logins.Any(c=>c.LoginId==currentLogin.Id)
select name;
Or in one query:
var query= from enrollement in db.Enrollments
join login in db.Logins on enrollement.LoginId equals login.UserId
where login.UserName == this.User.Identity.Name
select enrollement;

Pass an array of model items to view

I have a controller
public ActionResult UsersInRoles()
{
using (var ctx1 = new UsersModel())
{
var model = new UsersInRolesViewModel();
model.UserProfiles = ctx1.UserProfiles.ToList();
model.UserRoles = ctx1.UserRoles.ToList();
return View(model);
}
}
I am Passing both of those models through to my View:
UserRoles Contains all of my possible roles in the system. Now I need to get an array in my controller of all the roles that a User Is registered for, and pass that through to me view as well, So that I can know not to display roles in a box of available roles which the user is already registered for.
How can I do this?
I see two options:
Some how using linq statement to try and get the data in one go,
or
To pass the full model of roles through and and array of roles that the user is registered for and then I am able to display either or etc in the view?
AUX Classes related to the Usermodel:
public class UsersModel : DbContext
{
public UsersModel()
: base("name=UsersConnection")
{}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Roles> UserRoles { get; set; }
public DbSet<UsersInRoles> UsersInUserRoles { get; set; }
}
and
public class UsersInRolesViewModel
{
public IList<UserProfile> UserProfiles { get; set; }
public IList<Roles> UserRoles { get; set; }
public IList<ut_GMUTempData> GMUTempData { get; set; }
}
This is a recurring question.
Look at my answer to this SO question: Getting values of check-box from formcollection in asp.net mvc
If that's not clear enough, please, leave a comment.
If you just need roles of the logged in user and you use the default simple membership of Internet Application template, you doesn't need model binding at all. You can retrieve roles of the logged in user in your view like the following:
#{
// r will be a string[] array that contains all roles of the current user.
var r = Roles.GetRolesForUser();
}
and if you want to retrieve roles of any other user, just put the username into that method:
#{
// r will be a string[] array that contains all roles of the entered username.
var r = Roles.GetRolesForUser("username");
}
in this case, you just need to send the username to your model.
Update:
And if you have UserID, you can retrieve username as the following:
#{
SimpleMembershipProvider provider = new SimpleMembershipProvider();
string uname = provider.GetUserNameFromId(id);
// r will be a string[] array that contains all roles of the entered username.
var r = Roles.GetRolesForUser(uname);
}

ASP.Net MVC 4 Simple Membership specify Schema

I'm trying to figure out how to setup ASP.Net MVC 4's Simple Membership. The tables that house users in my current database have a different schema then 'dbo'. How can I specify the schema when initializing my database connection:
WebSecurity.InitializeDatabaseConnection
(
connectionStringName: "GuessAListConnection",
userTableName: "UserProfile",
userIdColumn: "UserId",
userNameColumn: "UserName",
autoCreateTables: false
);
There's not an option for schema. I've tried appending schema to the userTableName but that produces an error. Any suggestions?
Thanks
Tom
WebSecurity will use whatever the default schema of the database user. No way to specify a different schema that i'm aware of.
I found another solution if you have your user/authentication tables in a different schema than dbo (or whatever the default schema of the database user) and that's to use a view.
In my database, my user/authentication tables are scoped to a "Security" schema. I also have a Person schema where the Person table lives. I store just FirstName, LastName in Person table.
I created a view that pulls all of the tables together for authentication and am returning the following fields:
UserId, UserName, PersonId, FirstName, LastName, AuthenticationType, LastLoginDate, IsLockedOut
In my application, I can authenticate a person a number of different ways: Form, Windows, Facebook, Twitter, etc. The UserId, UserName relate to the different authentication type.
Here's my UserProfile class in ASP.Net MVC:
[Table("__vwRegisteredUser")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public String UserName { get; set; }
public int PersonId { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public String AuthenticationTypeName { get; set; }
public Boolean IsLockedOut { get; set; }
public DateTime LastLoginDate { get; set; }
public int Id
{
get
{
return PersonId;
}
}
public String Name
{
get
{
return String.Format("{0} {1}", FirstName, LastName);
}
}
}
Notice I use a view for the Table attribute. I'm also returning PersonId as an Id property and concatenating FirstName and LastName together as a Name property. The UserName is the value coming back from my Authentication Type (Windows user name, email address if I'm using Forms authentication, or whatever Facebook or Twitter returns). So, there's no consistency for UserName from my Security.Authentication table.
Initializing database connection still requires a table in order for SimpleMembership to generate membership tables. There maybe a way to have a custom membership table, but I haven't figured that out yet.