I have started developing an intranet website using ASP.NET Web Forms (I'm a total beginner) that uses Windows Authentication to identify users, but to control access to various pages, I'm looking to assign roles to users based on set criteria based on data within SQL tables (this data can change daily).
So far, I have the 'out of the box' ASP.NET Web Forms template with Windows Authentication that has a working connection to my (remote) SQL Server database.
I apologise if this has been answered elsewhere, but I can't seem to find a solution that fits my needs.
Using some basic IF logic, I will have the following roles: 'Admin', 'Moderator', 'HRA', 'Manager' and 'Employee'.
Looking up the logged-in user's data from a SQL table (3-4 fields max), set criteria will determine the user's role as follows:
if (UserRole === null) Then
If (ORG_ID === 30001000) Then
UserRole === 'Admin'
else if (ORG_ID === 30001001) Then
UserRole === 'Moderator'
else if (ORG_ID === 30001002) Then
UserRole === 'HRA'
else if (CHIEF === 'Chief') Then
UserRole === 'Manager'
else
UserRole === 'Employee'
End If
End if
I'm guessing that this would be worked into the Site.Master file that runs once per session but I'm stuck as to how this would work exactly and if anything needs to be added to the config file etc.
Thanks in advance, I understand how this would work with php but ASP.NET and how it works is completely new to me. If there is a better solution then great!
It's also worth noting that some parts of my site (e.g a Dashboards section) will allow some UserRoles to control custom access to dashboards controlled by an SQL table - but I can look at this in the future.
I thought I'd answer this myself just incase it's of any use to anyone else. I implemented my own Custom Role Provider and connecting to sql data to assign roles like this:
public class CustomRoleProvider : RoleProvider
{
public override bool IsUserInRole(string username, string roleName)
{
var roles = GetRolesForUser(username);
foreach (var role in roles)
{
if (role.Equals(roleName))
{
return true;
}
}
return false;
}
public override string[] GetRolesForUser(string username)
{
//create our List to hold our Roles
List<string> r = new List<string>();
r.Add("Employee");
//get our sap number of current user to look up against the database
var persno = Int32.Parse(10 + HttpContext.Current.User.Identity.Name.Substring(HttpContext.Current.User.Identity.Name.Length - 5));
//connect to our sql database
string strConnString = ConfigurationManager.ConnectionStrings["hrssportalConnectionString1"].ConnectionString;
string str;
SqlCommand com;
SqlConnection con = new SqlConnection(strConnString);
con.Open();
//SQL Query
str = "SELECT org_publisher.persno, org_publisher.record_type, org_publisher.org_string, map_user_roles.role_name FROM org_publisher LEFT JOIN users ON org_publisher.persno = users.persno LEFT JOIN map_user_roles ON users.role_id = map_user_roles.role_id WHERE org_publisher.persno = " + persno;
com = new SqlCommand(str, con);
//get our data
//SqlDataReader reader = com.ExecuteReader();
//reader.Read();
DataTable dt = new DataTable();
dt.Load(com.ExecuteReader());
//if we have rows returned do our checks
if (dt != null)
{
//get our data for checking
//string org_string = reader["org_string"].ToString();
//string line_manager = reader["record_type"].ToString();
string org_string = dt.Rows[0]["org_string"].ToString();
string line_manager = dt.Rows[0]["record_type"].ToString();
//Line Manager Role check
if (line_manager == "<ChiefPosition>")
{
r.Add("Manager");
}
//HRSS Role Check
if (org_string.Contains("30001803"))
{
r.Add("HRSS");
}
//HRA Role Check
if (org_string.Contains("30003237"))
{
r.Add("HRA");
}
//add all custom roles by cycling through rows
if (dt.Rows.Count > 0)
{
foreach (DataRow row in dt.Rows)
{
if (row["role_name"].ToString() != null)
{
r.Add(row["role_name"].ToString());
}
}
}
//close our sql objects
dt.Dispose();
con.Close();
//return List as an array
string[] rolesArray = r.ToArray();
return rolesArray;
}
else
{
//if no Rows returned from SQL, return only Employee role from List
string[] rolesArray = r.ToArray();
return rolesArray;
}
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
throw new System.NotImplementedException();
}
public override void CreateRole(string roleName)
{
throw new NotImplementedException();
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
throw new NotImplementedException();
}
public override bool RoleExists(string roleName)
{
throw new NotImplementedException();
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
throw new NotImplementedException();
}
public override string[] GetUsersInRole(string roleName)
{
throw new NotImplementedException();
}
public override string[] GetAllRoles()
{
throw new NotImplementedException();
}
public override string ApplicationName { get; set; }
}
Then in web.config:
<roleManager defaultProvider="CustomRoleProvider" enabled="true">
<providers>
<clear/>
<add name="CustomRoleProvider" type="ClassLibrary.CustomRoleProvider"
applicationName="WebApplication1" writeExceptionsToEventLog="false"/>
</providers>
</roleManager>
Related
So in my global.asax, I've got the following code:
Inventory.BusinessTier bt = new Inventory.BusinessTier();
string UserLogin = bt.ExtractLogin (Request.ServerVariables ["AUTH_USER"]);
Inventory.User myUser = new Inventory.User (UserLogin);
Session ["User"] = myUser;
It works just fine on one development PC, but using the same version of Visual Studio, it craps out on the third line with this error:
System.TypeInitializationException: 'The type initializer for
'Inventory.DataTier' threw an exception.'
Inner Exception
NullReferenceException: Object reference not set to an instance of an
object.
Other than a line adding impersonation in my web.config (it has to be there now), I haven't changed a single thing. Is there a way to get more info on this? I can't even trace it, because if I put a debug line in the User object constructor, it never hits it. I'm at a bit of a loss. Would appreciate any advice.
EDIT to answer questions below:
InventoryUser is a very simple user object that reads the current from the database and stores some basic user info in properties, such as UserID, Role, RoleID, and IsAdmin.
The DataTier class is a class that interacts with the database. It is used in multiple projects, so I'm quite sure it's not the problem. I tried to paste in the code anyway, but it exceeded the limit for a post.
I'm reasonably sure the problem is related to the user class. It's short, so I can paste it in here:
using System;
using System.Data;
// This is the user business object. It contains information pertaining to the current user of the application. Notably, it
// contains the department ID, which determines what inventory items the user will see when using the application. Only
// specified employees with admin access can see all items for all departments, and that is determined by a specific department ID.
namespace Inventory {
public class User {
private Guid _UserID;
private Guid _RoleID;
private Guid _UserDepartmentID;
private string _UserRole = "";
private string _UserName = "";
private bool _IsAuthorizedUser = false;
private bool _IsAdmin = false;
// Attribute declarations
public Guid UserID {
get {
return _UserID;
}
set {
_UserID = value;
}
}
public string UserRole {
get {
return _UserRole;
}
set {
_UserRole = value;
}
}
public Guid RoleID {
get {
return _RoleID;
}
set {
_RoleID = value;
}
}
public string UserName {
get {
return _UserName;
}
set {
_UserName = value;
}
}
public Guid UserDepartmentID {
get {
return _UserDepartmentID;
}
set {
_UserDepartmentID = value;
}
}
public bool IsAdmin {
get {
return _IsAdmin;
}
set {
_IsAdmin = value;
}
}
public bool IsAuthorizedUser {
get {
return _IsAuthorizedUser;
}
set {
_IsAuthorizedUser = value;
}
}
// -----------------
// - Constructor -
// -----------------
public User (string UserLogin) {
string ShortUserLogin = ExtractLogin (UserLogin);
GetUser (ShortUserLogin);
}
// ------------------
// - ExtractLogin -
// ------------------
public string ExtractLogin (string Login) {
// The domain and "\" symbol must be removed from the string, leaving only the user name.
int pos = Login.IndexOf (#"\");
return Login.Substring (pos + 1, Login.Length - pos - 1);
}
// -------------
// - GetUser -
// -------------
// This method is called to fill the user object based on the user's login. It ultimately gets authorized user data
// from the user table.
public void GetUser (string UserName) {
DataTier dt1 = new DataTier();
DataTable dt = dt1.GetUserInfo (UserName);
int RecordCount = dt.Rows.Count;
switch (RecordCount) {
case 1: // There is one user name match, as there should be. This is the likely situation.
DataRow dr = dt.Rows[0];
UserID = (Guid)dr ["UserID"];
UserRole = (string)dr ["UserRole"];
RoleID = (Guid)dr ["RoleID"];
this.UserName = UserName;
UserDepartmentID = (Guid)dr ["DepartmentID"];
IsAdmin = (bool)dr ["IsAdmin"];
IsAuthorizedUser = true;
break;
case 0: // There are no user name matches (unauthorized use).
IsAdmin = false;
IsAuthorizedUser = false;
break;
default: // There are multiple user name matches (problem!).
IsAdmin = false;
IsAuthorizedUser = false;
break;
}
}
}
}
In asp.net webform application, trying to save user to AspNetUsers after UPDATE-DATABASE command. the following code doesnt do that. solution ?
public Configuration()
{
AutomaticMigrationsEnabled = true;
}
protected override void Seed(MyApp.Models.ApplicationDbContext context)
{
if (!context.Users.Any(u => u.Email == "some#mail"))
{
var store = new UserStore<ApplicationUser>(context);
var manager = new UserManager<ApplicationUser>(store);
var user = new ApplicationUser { Email = "some#mail" };
manager.Create(user, "password");
}
}
In order to add ApplicationUser you must have the property Username initialized.
At present on login I am inserting a row for the user into an AccessSession table that keeps details of what roles the user has along with the ASP.NET_SessionId cookie.
My custom implementation of the GetRolesForUser method of this is:
public override string[] GetRolesForUser(string username)
{
List<string> roles = new List<string>();
string[] rolesArray;
char[] splitter = { '|' };
string sessionId = HttpContext.Current.Request.Cookies["ASP.NET_SessionId"].Value;
AccessSession sessionObject = AccessSession.Get(sessionId);
if (sessionObject != null)
{
rolesArray = sessionObject.Roles.Split(splitter);
foreach (string role in rolesArray)
{
if (!String.IsNullOrEmpty(role))
{
roles.Add(role);
}
}
}
return roles.ToArray();
}
The question I have is am I wrong using this approach? If cookies are disabled then there will be no HttpContext.Current.Request.Cookies["ASP.NET_SessionId"]. My alternative plan was to insert an AccessSession object in to Session but this always appears null when the custom RoleProvider tried to access it.
I could use cacheRolesInCookie=true but again that would be no better than the above approach as disabling cookies would break the functionality.
Thanks,
Richard
Well I managed to solve it in the end by getting the roles from the FormsAuthenticationTicket which held all my roles in already. Here is an example of the code:
public override string[] GetRolesForUser(string username)
{
List<string> roles = new List<string>();
string[] rolesArray = new string[] { };
char splitter = Advancedcheck.BLL.Common.Const.default_splitter;
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
rolesArray = ticket.UserData.Split(splitter);
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, rolesArray);
}
}
}
if (rolesArray.Length > 0)
{
foreach (string role in rolesArray)
{
if (!String.IsNullOrEmpty(role))
{
roles.Add(role.ToLower());
}
}
}
return roles.ToArray();
}
In Martin Fowler's "Patterns of Enterprise Application Architecture"
is described approach for organizing DAL like a set of mappers for entities. Each has it's own IdentityMap storing specific entity.
for example in my ASP.NET WebApplication:
//AbstractMapper - superclass for all mappers in DAL
public abstract class AbstractMapper
{
private readonly string _connectionString;
protected string ConnectionString
{
get { return _connectionString; }
}
private readonly DbProviderFactory _dbFactory;
protected DbProviderFactory DBFactory
{
get { return _dbFactory; }
}
#region LoadedObjects (IdentityMap)
protected Hashtable LoadedObjects = new Hashtable();
public void RegisterObject(long id, DomainObject obj)
{
LoadedObjects[id] = obj;
}
public void UnregisterObject(long id)
{
LoadedObjects.Remove(id);
}
#endregion
public AbstractMapper(string connectionString, DbProviderFactory dbFactory)
{
_connectionString = connectionString;
_dbFactory = dbFactory;
}
protected virtual string DBTable
{
get
{
throw new NotImplementedException("database table is not defined in class " + this.GetType());
}
}
protected virtual T Find<T>(long id, IDbTransaction tr = null) where T : DomainObject
{
if (id == 0)
return null;
T result = (T)LoadedObjects[id];
if (result != null)
return result;
IDbConnection cn = GetConnection(tr);
IDbCommand cmd = CreateCommand(GetFindStatement(id), cn, tr);
IDataReader rs = null;
try
{
OpenConnection(cn, tr);
rs = cmd.ExecuteReader(CommandBehavior.SingleRow);
result = (rs.Read()) ? Load<T>(rs) : null;
}
catch (DbException ex)
{
throw new DALException("Error while loading an object by id in class " + this.GetType(), ex);
}
finally
{
CleanUpDBResources(cmd, cn, tr, rs);
}
return result;
}
protected virtual T Load<T>(IDataReader rs) where T : DomainObject
{
long id = GetReaderLong(rs["ID"]);
T result = (T)LoadedObjects[id];
if (result != null)
return result;
result = (T)DoLoad(id, rs);
RegisterObject(id, result);
return result;
}
// another CRUD here ...
}
// Specific Mapper for entity Account
public class AccountMapper : AbstractMapper
{
internal override string DBTable
{
get { return "Account"; }
}
public AccountMapper(string connectionString, DbProviderFactory dbFactory) : base(connectionString, dbFactory) { }
public Account Find(long id)
{
return Find<Account>(id);
}
public override DomainObject DoLoad(long id, IDataReader rs)
{
Account account = new Account(id);
account.Name = GetReaderString(rs["Name"]);
account.Value = GetReaderDecimal(rs["Value"]);
account.CurrencyID = GetReaderLong(rs["CurrencyID"]);
return account;
}
// ...
}
The question is: where to store these mappers? How system services (entities) should call mappers?
I decided to create MapperRegistry containing all mappers. So services can call mappers like:
public class AccountService : DomainService
{
public static Account FindAccount(long accountID)
{
if (accountID > 0)
return MapperRegistry.AccountMapper.Find(accountID);
return null;
}
...
}
But where can I store MapperRegistry instance? I see following variants, but don't like any of them:
MapperRegistry is global for application (Singleton)
Not applicable because of necessity of synchronization in multi-thread ASP.NET application (at least Martin says that only mad can choose this variant)
MapperRegistry per Session
Seems not so good too. All ORMs (NHibernate, LINQ to SQL, EntityFramework) masters advise to use DataContext (NHibernateSession, ObjectContext) per Request and not to store context in Session.
Also in my WebApp almost all requests are AJAX-requests to EntityController.asmx (with attribute ScriptService) returning JSON. And session is not allowed.
MapperRegistry per Request
There are a lot of separate AJAX calls. In this case life cycle of MapperRegistry will be too small. So the data almost always will be retrieved from database, as a result - low performance.
Dear Experts, please help me with architectural solution.
I have an intranet that gets the current logged in user through active directory. When a user is locked out they get a windows prompt to enter their username and password. Is there a way for me to catch this and redirect them to a page where they are asked to enter their credentials again or tell them that their account might be locked out and to contact the help desk?
On your application once you grab the user that is logged in do the IsAccountLocked method below
public bool IsAccountLocked(string sUserName)
{
UserPrincipal oUserPrincipal = GetUser(sUserName);
return oUserPrincipal.IsAccountLockedOut();
}
public UserPrincipal GetUser(string sUserName)
{
PrincipalContext oPrincipalContext = GetPrincipalContext();
UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPrincipalContext, sUserName);
return oUserPrincipal;
}
public PrincipalContext GetPrincipalContext()
{
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain, sDomain, sDefaultOU, ContextOptions.SimpleBind, sServiceUser, sServicePassword);
return oPrincipalContext;
}
This is using System.DirectoryServices.AccountManagement for using only System.DirectoryServices you can do this
public bool IsAccountLocked(DirectoryEntry oDE)
{
return Convert.ToBoolean(oDE.InvokeGet("IsAccountLocked"));
}
public DirectoryEntry GetUser(string sUserName)
{
//Create an Instance of the DirectoryEntry
oDE = GetDirectoryObject();
//Create Instance fo the Direcory Searcher
oDS = new DirectorySearcher();
oDS.SearchRoot = oDE;
//Set the Search Filter
oDS.Filter = "(&(objectClass=user)(sAMAccountName=" + sUserName + "))";
oDS.SearchScope = SearchScope.Subtree;
oDS.PageSize = 10000;
//Find the First Instance
SearchResult oResults = oDS.FindOne();
//If found then Return Directory Object, otherwise return Null
if (oResults != null)
{
oDE = new DirectoryEntry(oResults.Path, sADUser, sADPassword, AuthenticationTypes.Secure);
return oDE;
}
else
{
return null;
}
}
private DirectoryEntry GetDirectoryObject()
{
oDE = new DirectoryEntry(sADPath, sADUser, sADPassword, AuthenticationTypes.Secure);
return oDE;
}
for a full implementation you can go to
http://anyrest.wordpress.com/2010/06/28/active-directory-c/
or
http://anyrest.wordpress.com/2010/02/01/active-directory-objects-and-c/