Can ASP Identity handle multitenancy, I have collisions in role names in ASP Identity web app - security

In a public facing web app multi tenant app, I'm using ASP Identity 2.x.
I let 3rd party's e.g. non-profits/comps self-register, create and populate their own roles and users. The code below is fine, if its an intranet scenarios where everyone belongs to the same company, it does not for work multiple non-profits
The non-profits registrants (understandably so) are naming roles with the same names, i.e. Managers and Employees etc. which are common/same across the database.
How can I extend ASP Identity to separate the roles per organization in a multi-tenant fashion, can you help me understand the design and how extend this, do I need a sub-role? i.e.
what do I do to ensure roles are scoped per organization at the database, so that different org's can have the same role names?
and, what do I do at the middle tier, i.e. usermanager, role manager objects level (middle tier)
//Roles/Create
[HttpPost]
public ActionResult Create(FormCollection form)
{
try
{
context.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
{ \\ Question - can I add another level here like company??
Name = form["RoleName"]
});
context.SaveChanges();
ViewBag.ResultMessage = "Role created successfully";
return RedirectToAction("RoleCreated");
}
catch
{
return View();
}
}`
Question- When adding a role, how do I separate the role to know which Role is from Which company when I add to the user?
public ActionResult RoleAddToUser(string UserName, string RoleName)
{
ApplicationUser user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var account = new AccountController();
account.UserManager.AddToRole(user.Id, RoleName);
ViewBag.ResultMessage = "Role created successfully !";
// prepopulat roles for the view dropdown
var list = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
ViewBag.Roles = list;
return View("ManageUserRoles");
}
how do I get the list of Roles and Users for that non-profit?
public ActionResult ManageUserRoles()
{
var list = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
ViewBag.Roles = list;
return View();
}`

I'm guessing you do have some sort of TenantId or CompanyId that is an identifier to the tenant you are working with.
IdentityRole and IdentityUser are framework objects that is recommended to inherit to add your own properties. You should do just that:
public class MyApplicationRole : IdentityRole
{
public int CompanyId { get; set; }
}
public class MyApplicationuser : IdentityUser
{
public int CompanyId { get; set; }
}
You can also add reference to a company object here and link it as a foreign key. This might make your life easier retrieving company objects related to the user.
Based on the objects above I'll try answering your questions:
When roles are created you can append a CompanyId to the name as a prefix. Something like <CompanyId>#CustomerServiceRole. This will avoid name clashes. At the same time add CompanyId to the MyApplicationRole class. Prefixed identifier will avoid clashes, identifier in the object will make the roles discoverable by the company. Alternatively you can implement RoleValidator and do the validation based on unique role name within a company. But then you will have to change the code that creates user identity when users are logged in, as role ids are not stored into the cookie.
I think this is self explanatory from the previous answer:
context.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
{
CompanyId = companyId, // TODO Get the company Id
Name = companyId.ToString() + "#" + form["RoleName"]
});
Self explanatory, see code snippets above.
Depending how you link your roles to companies the query will be different. Basically you'll need to do a join between companies and matching roles based on CompanyId and then filter companies by the non-profit flag.

Related

Razor pages assign roles to users

I am wondering how to create and assign roles in Razor Pages 2.1. application.
I have found how to make them for MVC application (How to create roles in asp.net core and assign them to users and http://hishambinateya.com/role-based-authorization-in-razor-pages), however it does not work for razor pages as I have no IServicesProvider instance.
What I want is just to create admin role and assign it to seeded administrator account. Something similar has been done in this tutorial https://learn.microsoft.com/en-us/aspnet/core/security/authorization/secure-data?view=aspnetcore-2.1, but it seems be sutied for MVC and does not work properly after I applied it to my application. Please help me to understand how to create and seed roles in Razor Pages.
Will be very greatfull for help!
I handle the task next way. First, I used code proposed by Paul Madson in How to create roles in asp.net core and assign them to users. Abovementioned method I have inserted into Startup.cs. It creates administrator role and assigned it to seeded user.
private void CreateRoles(IServiceProvider serviceProvider)
{
var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
Task<IdentityResult> roleResult;
string email = "someone#somewhere.com";
//Check that there is an Administrator role and create if not
Task<bool> hasAdminRole = roleManager.RoleExistsAsync("Administrator");
hasAdminRole.Wait();
if (!hasAdminRole.Result)
{
roleResult = roleManager.CreateAsync(new IdentityRole("Administrator"));
roleResult.Wait();
}
//Check if the admin user exists and create it if not
//Add to the Administrator role
Task<ApplicationUser> testUser = userManager.FindByEmailAsync(email);
testUser.Wait();
if (testUser.Result == null)
{
ApplicationUser administrator = new ApplicationUser
{
Email = email,
UserName = email,
Name = email
};
Task<IdentityResult> newUser = userManager.CreateAsync(administrator, "_AStrongP#ssword!123");
newUser.Wait();
if (newUser.Result.Succeeded)
{
Task<IdentityResult> newUserRole = userManager.AddToRoleAsync(administrator, "Administrator");
newUserRole.Wait();
}
}
}
Then, in the same file in Configure method I add argument (IServiceProvider serviceProvider), so you should have something like Configure(..., IServiceProvider serviceProvider). In the end of Configure method I add
CreateRoles(serviceProvider).
To make this code work create ApplicationUser class somwhere, for example in Data folder:
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sobopedia.Data
{
public class ApplicationUser: IdentityUser
{
public string Name { get; set; }
}
}
Finally, inside ConfigureServices method substitute
services.AddIdentity<ApplicationUser>()
.AddEntityFrameworkStores<SobopediaContext>()
.AddDefaultTokenProviders();
with
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<SobopediaContext>()
.AddDefaultTokenProviders();
As a result, after programm starts in table AspNetRoles you will get a new role, while in table AspNetUsers you will have a new user acuiering administrator role.
Unfortunatelly, after you add the following code
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<SobopediaContext>()
.AddDefaultTokenProviders();
pages Login and Registration stop working. In order to handle this problem you may follow next steps:
Scaffold Identity following (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-2.1&tabs=visual-studio).
Then substitute IdentityUser for ApplicationUser in entire solution. Preserv only IdentityUser inheritance in ApplicationUser class.
Remove from Areas/identity/Pages/Account/Register.cs all things related to EmailSernder if you have no its implementation.
In order to check correctness of the roles system you may do as follows. In the end of ConfigureServices method in Startup.cs add this code:
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator"));
});
services.AddMvc().AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Contact","RequireAdministratorRole");
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
If it does not worki then just add [Authorize(Roles = "Administrator")] to Contact Page model, so it will look something like this:
namespace Sobopedia.Pages
{
[Authorize(Roles = "Administrator")]
public class ContactModel : PageModel
{
public string Message { get; set; }
public void OnGet()
{
Message = "Your contact page.";
}
}
}
Now, in order to open Contact page you should be logged in with login someone#somewhere.com and password _AStrongP#ssword!123.

Extending Identity 2.1 to assign user to company and roles to users

Ok so i am really lost now,
I have an application that works great, i have groups of data that are available to a company subscription, When the user logs in they can access the information from the data that their roles allow, If they have multiple roles they have access to multiple areas of reports, if they only subscribe to one role they only have access to that one area. now all the data is the same and updated daily, so if 100 users are subscribed to roles 1,3 and 5 they can access that data. for the last decade the users of the companies would use one login to access the data ( one login supplied to company for George, But Lisa, john, Jerry and bob all use George's credentials to download reports). Note the data does not change per company and the site does not change per company all data and themes are part of the one site they have access to.
Now the Issue.
I have been asked by many of my users (Companies that access my data) that they would like to be able to to add users to their account so they can monitor who is downloading what report as i charge subscription by 500, 1000, and 2000 reports. My application records reports downloaded and the user can view these reports from their user portal and the id that downloaded it. the issue is that the companies want to be able to administer their own users, sort of like an account admin. I have looked into multi tenant and this does not really suit my needs, but from what i am looking at i feel i need to add a company_id to the user and company_id user roles so each company can have an admin role and a user role. but not sure as i study and read more on this i see allot of different ideas. but dont really see one that works for me.
here is an example.
Each user (company) has many users, each user in that company has roles of Admin or user. Users (companies) have access to many areas that roles allow them and the users of that company would have the same access to reports tha company roles provide. (or see aggregate data across all groups they belong to).
Thinking of adding this to IdentityModels:
public class ApplicationUserRole : IdentityUserRole<string>
{
public string CompanyId { get; set; }
}
public class ApplicationUser : IdentityUser<string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>//, IAppUser
{
public ApplicationUser()
{
this.Id = Guid.NewGuid().ToString();
}
public virtual string CompanyId { get; set; }
public virtual List<CompanyEntity> Company { get; set; }
public DateTime CreatedOn { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(ApplicationUserManager manager, string authenticationType)
{
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
return userIdentity;
}
}
add to IdentityConfig:
public string GetCurrentCompanyId(string userName)
{
var user = this.FindByName(userName);
if (user == null)
return string.Empty;
var currentCompany = string.Empty;
if (user.Claims.Count > 0)
{
currentCompany = user.Claims.Where(c => c.ClaimType == ConcordyaPayee.Core.Common.ConcordyaClaimTypes.CurrentCompanyId).FirstOrDefault().ClaimValue;
}
else
{
currentCompany = user.CurrentCompanyId;
}
return currentCompany;
}
public override Task<IdentityResult> AddToRoleAsync(string userId, string role, string companyId)
{
return base.AddToRoleAsync(userId, role);
}
Now im not sure about this, do i need to create a new user store. and how would authentication work, will i need to write a new Authorize Attribute. should i be using the claims. or maybe ACL system. or do i need to find a different identity provider.
Any help would be appreciated greatly. as i am getting very confused.

Relation to users when these are stored in an external Identity provider service

I'm trying to create an API and a website client for it. Lately I've been reading a lot about OAuth2 as a security mechanism and companies that offers authentication as a service such as auth0.com or even Azure active Directory and I can see the advantages in using them
Because I'm used to always having the users in the same database and tables with relationships to the Users table in the form of One to Many such as below
public class User
{
public string subjectId { get; set; }
public virtual List<Invoice> Invoices { get; set; }
/*
More properties in here
*/
}
public class Invoice
{
public int InvoiceId { get; set; }
public string PaymentNumber { get; set; }
public DateTime Date { get; set; }
public double Amount { get; set; }
public string Description { get; set; }
public virtual User User { get; set; }
}
My questions is then.
If the users are stored in an external authentication service such as Auth0.com,
How the Invoice class will handle the relation to the user?
Would it be just adding a new property subjectId in the Invoice table and this will take the value of whatever id the authentication service assigned?
In the latter case, would the class Invoice be something like below?
public class Invoice
{
public int InvoiceId { get; set; }
public string PaymentNumber { get; set; }
public DateTime Date { get; set; }
public double Amount { get; set; }
public string Description { get; set; }
public string SubjectId{get;set;}
}
Also, if the users are stored someplace else, how do you make a query like,
Select * from Users u inner join Invoices i where Users.Name='John Doe' and i.Date>Somedate.
Since you have mentioned Auth0 as your Identity provider there are multiple ways to achieve the user table in your database.
1. Authenticating/ registering the user with Auth0 will send a response with Profile Object which will have all the basic profile information you need. Post this profile object back to your own API to save it to database. This API endpoint should be secured with the access token you received along with the profile object from Auth0.
2. You can create a custom rule in Auth0 that posts the user information back to your api. This rule gets executed on Auth0 server so this is a secure call.
3. Identity providers (Auth0 in our case) are required to expose an API endpoint that gives us user profile data (ex: https://yourdoamin.auth0.com/userinfo). You can make a call to this endpoint from your API to receive the user information.
When user Registers to your application, please use one of these techniques to establish a User profile information table in your database. It is always a good idea to treat the Identity Provider as a service responsible for authenticating the resource owner (the user of your application) and providing an access token for securely accessing your API/ application. If you have the profile of the user in your database, you do not have to depend on the Identity Provider once the user is authenticated.
Please let me know if you have any further questions.
Thank you,
Soma.
We have a similar setup for our website. We use Passport for our user database and our website doesn't have a user table at all. This makes life much simpler than having a a bunch of duplicate data between Passport and our website. I'll use our code as an example of what you are doing and hopefully it makes sense.
Our website has a License object that looks like this (Java not C#, but they are similar):
public class License {
public String companyName;
public List<User> users;
}
The License table looks like this (trimmed down):
CREATE TABLE licenses (
id UUID NOT NULL,
company_name VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
The License identifies the users that are associated with it via a join table like this (Passport uses UUIDs for user ids making life simple again):
CREATE TABLE users_licenses (
users_id UUID NOT NULL,
licenses_id UUID NOT NULL,
PRIMARY KEY (users_id, licenses_id),
CONSTRAINT users_licenses_fk_1 FOREIGN KEY (licenses_id) REFERENCES licenses (id)
);
Then we can select in either direction. If we know the user id, we can ask for all their licenses like this:
select * from licenses where users_id = ?
Or if we know the license id, we can ask for all the users that have access to the license:
select * from users_licenses where licenses_id = ?
Once we have one or more user ids, we can call the Passport /api/user endpoint or the /api/user/search endpoint to retrieve one or more user objects. We are actually using the Passport Java Client (https://github.com/inversoft/passport-java-client) which makes the API call for us and then returns a List<User>. This is what is stored in the License class from above. That code looks like this:
License license = licenseMapper.retrieveById(licenseId);
List<UUID> userIds = licenseMapper.retrieveUserIdsFor(licenseId);
ClientResponse<SearchResponse, Errors> clientResponse = passportClient.searchUsers(userIds);
license.users = clientResponse.successResponse.users;
LicenseMapper is a MyBatis interface that executes the SQL and returns the License objects. C# ORMs use LINQ, but it would be similar.
The nice thing about this setup is that we don't have a user database table in our website database that we have to keep in sync. Everything is loaded from Passport via the API. We aren't ever concerned about performance either. Passport is on-premise and can do thousands of user lookups each second, so we always load the data instead of caching it.
The only piece of your question that requires additional code is handling the joins when you are searching for arbitrary users like name='John Doe'. The only way to handle this is to query your user database first, retrieve all the IDs, then load their invoices. This seems like it could be dangerous if you have a large user database, but still doable.
That could would look like this in our situation:
UserSearchCriteria criteria = new UserSearchCriteria().withName("John Doe");
ClientResponse<SearchResponse, Errors> clientResponse = passportClient.searchUsersByQueryString(criteria);
List<User> users = clientResponse.successResponse.users;
Set<License> licenses = new HashSet<>();
for (User user : users) {
licenses.addAll(licenseMapper.retrieveByUserId(user.id));
}

MVC2 :: How do I *USE* a Custom IIdentity Class?

I am trying to store a whole truckload of information about a user from a webservice. As this is information about the currently authenticated user, I thought it would make sense to store that information in a custom IIdentity implementation.
The custom MagicMembershipProvider.GetUser(string id, bool userIsOnline) calls the webservice and returns a MagicMembershipUser instance with all the fields populated (department, phone number, other employee info).
The custom membership provider and custom membership user both work fine.
What and where is the best way to put the membership user information into the IPrincipal User object that is accessible in every controller?
I have been trying to wrap my brain around the program flow of security with IIdentity, IPrincipal and Role authorization in an MVC2 application -- but I'm really struggling here and could use some mentoring. There a Internet Ton of articles about the parts, but not much about the whole.
Edit
My best guess so far is to assign the HttpContext.Current.User in the FormsAuthenticationService:
public void SignIn(string userName, bool createPersistentCookie)
{
if (String.IsNullOrEmpty(userName))
throw new ArgumentException("Value cannot be null or empty.", "userName");
try
{
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
MagicMembershipUser magicUser = _provider.GetUser("", false)
as MagicMembershipUser;
MagicIdentity identity = new MagicIdentity(userName, magicUser);
GenericPrincipal principal = new GenericPrincipal(identity, null);
HttpContext.Current.User = principal;
}
catch (Exception)
{
throw;
}
}
What and where is the best way to put the membership user information into the IPrincipal User object that is accessible in every controller?
In a custom [Authorize] filter implementation. You could override the AuthorizeCore method and call the base method and if it returns true query your membership provider and inject the custom magic identity into the context.
Example:
public class MagicAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
var username = httpContext.User.Identity.Name;
var magicUser = _provider.GetUser(username, false) as MagicMembershipUser;
var identity = new MagicIdentity(username, magicUser);
var principal = new GenericPrincipal(identity, null);
httpContext.User = principal;
}
return isAuthorized;
}
}
Now all that's left is decorate your base controller with the [MagicAuthorize] attribute.

Fba roles with SharePoint user groups

I have built custom Membership and Role providers. Users are some clients that belong to the company and I am using Company as a Role.
I would like to create SharePoint Group and add more companies to it (for example type of industry) and then do redirecting and security by the SPGroup.
How do I retrieve SPGroup for the current logged in user ?
I would like to this in my custom Login page so another problem is how do I retrieve SPUser or SPGroup knowing login name ?
This is what I have now:
private List GetGroupsForUser(List roleAccounts)
{
List groups = new List();
SPSecurity.RunWithElevatedPrivileges(
delegate()
{
using (SPSite site = new SPSite(SPContext.Current.Web.Site.ID))
{
SPUserCollection users = site.RootWeb.SiteUsers;
foreach (string account in roleAccounts)
{
SPGroupCollection accGroups = users[account].Groups;
foreach (SPGroup spg in groups)
{
groups.Add(spg);
}
}
}
}
);
return groups;
}
private string GetRoleManagerName()
{
foreach (KeyValuePair setting in SPContext.Current.Site.WebApplication.IisSettings)
{
if (string.IsNullOrEmpty(setting.Value.RoleManager) == false)
return setting.Value.RoleManager.ToLower();
}
return null;
}
private List GetSpAccounts()
{
List roleAccounts = new List();
string roleProviderName = GetRoleManagerName();
foreach (string role in Roles.GetRolesForUser(login.UserName))
{
roleAccounts.Add(roleProviderName + ":" + role.ToLower());
}
return roleAccounts;
}
// and now I can use it
List roleAccounts = GetSpAccounts();
List groups = GetGroupsForUser(roleAccounts);
But I have a felling that I should not have to do this manually like this. How will Target Audience work if only role is added to the group ?
Use the OwnedGroups property of the SPUser class to return the collection of groups owned by a user.
Update misunderstood the question:
Get the currently logged in user: SPContext.Current.Web.CurrentUser
Add that use to a group: SPGroup.Users.Add([username],[email],[name],[notes]);
Update, third times the charm. So you want to find out what group the user is in based on the roles they have?
It's a bit of a combination of the above two attempts at answering it:
var matched = from r in SPContext.Current.Web.CurrentUser.Roles
where r.Name = [Role]
select r.Groups;
Important note that the Roles property won't work in the next version of SharePoint!
Personally I think SPContext.Current.Web.CurrentUser.Groups would be an easier way to figure out what groups the user is in, but it ignores your role requirementment.

Resources