using System;
using System.Data;
using System.Data.Odbc;
using System.Configuration;
using System.Diagnostics;
using System.Web;
using System.Linq;
using System.Threading;
using System.Globalization;
using System.Web.Security;
using System.Configuration.Provider;
using System.Collections.Specialized;
using System.Collections.Generic;
#if DynamicFileSys
using CryptoGateway.RDB.Data.DynamicFileSystem;
#elif MembershipPlus
using CryptoGateway.RDB.Data.MembershipPlus;
#else
using CryptoGateway.RDB.Data.AspNetMember;
#endif
//using log4net;
namespace Archymeta.Web.Security
{
/// <summary>
/// Provider for Asp.Net hierarchic role management using the Membership Data Service.
/// </summary>
/// <remarks>
/// Hierarchic role system make it possible for more refined user access control.
/// It allows the following logic to be implemented:
/// namely a user having a role has also all the roles corresponding to all (if any)
/// of the direct or indirect parent roles of the said role. For example, a user having
/// role "Administrators.System" can not only access resources
/// requiring "Administrators.System" role but also the
/// ones requiring "Administrators" role. However, a user having
/// "Administrators" role can not access more restrictive resources
/// requiring "Administrators.System" role.
/// </remarks>
public sealed class AspNetRoleProvider : RoleProvider
{
#if TEST
public CallContext cntx
{
get
{
return _cctx;
}
}
public Application_ App
{
get { return app; }
}
#endif
#if DynamicFileSys
private DynamicFileSystemServiceProxy svc = new DynamicFileSystemServiceProxy();
#elif MembershipPlus
private MembershipPlusServiceProxy svc = new MembershipPlusServiceProxy();
#else
private AspNetMemberServiceProxy svc = new AspNetMemberServiceProxy();
#endif
private Application_ app
{
get { return AspNetMembershipProvider.app; }
set { AspNetMembershipProvider.app = value; }
}
private CallContext _cctx
{
get
{
return AspNetMembershipProvider._cctx;
}
set
{
AspNetMembershipProvider._cctx = value;
}
}
/// <summary>
/// The name of the current application
/// </summary>
public override string ApplicationName
{
get { return pApplicationName; }
set { pApplicationName = value; }
}
private string pApplicationName;
/// <summary>
/// ..
/// </summary>
public bool WriteExceptionsToEventLog
{
get { return pWriteExceptionsToEventLog; }
set { pWriteExceptionsToEventLog = value; }
}
private bool pWriteExceptionsToEventLog = false;
/// <summary>
/// Initialize the Role Provider.
/// </summary>
/// <param name="name">Name of the provider.</param>
/// <param name="config">The configuraton values</param>
public override void Initialize(string name, NameValueCollection config)
{
//
// Initialize values from web.config.
//
if (config == null)
throw new ArgumentNullException("config");
if (name == null || name.Length == 0)
name = "AspNetRoleServiceProvider";
if (String.IsNullOrEmpty(config["description"]))
{
config.Remove("description");
config.Add("description", "Asp.Net Role Service Provider");
}
// Initialize the abstract base class.
base.Initialize(name, config);
if (config["applicationName"] == null || config["applicationName"].Trim() == "")
{
pApplicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;
}
else
{
pApplicationName = config["applicationName"];
}
if (config["writeExceptionsToEventLog"] != null)
{
if (config["writeExceptionsToEventLog"].ToUpper() == "TRUE")
{
pWriteExceptionsToEventLog = true;
}
}
lock (AspNetMembershipProvider.syncRoot)
{
if (_cctx == null)
_cctx = svc.SignInService(new CallContext(), null);
CallContext cctx = _cctx.CreateCopy();
// must use direct access since all three providers is trying to create the app at the same time.
cctx.DirectDataAccess = true;
Application_ServiceProxy apprepo = new Application_ServiceProxy();
List<Application_> apps = apprepo.LoadEntityByNature(cctx, ApplicationName);
if (apps == null || apps.Count == 0)
{
//
// when the application is registerred the first time, membership, role, and profile provider could try to add the application at the same time,
// setting cctx.OverrideExisting = true will prevent concurrent add exception for new items.
// set it back if you do not want to accidentally overwriting an existing items by a new one
//
cctx.OverrideExisting = true;
var tuple = apprepo.AddOrUpdateEntities(cctx, new Application_Set(), new Application_[] { new Application_ { Name = ApplicationName } });
app = tuple.ChangedEntities.Length == 1 && AspNetMembershipProvider.IsValidUpdate(tuple.ChangedEntities[0].OpStatus) ? tuple.ChangedEntities[0].UpdatedItem : null;
}
else
app = apps[0];
}
if (app == null)
throw new Exception("User Role provider initialization failed.");
}
/// <summary>
/// Adds a new role to the data source for the configured applicationName.
/// </summary>
/// <param name="rolename">The full name of the role to create.</param>
/// <remarks>
/// <para>
/// The current service supports hierarchic roles. A role's full name is the full path of a role
/// where nodes in the path is separated by ".".
/// </para>
/// <para>
/// For example, the role "System" under "Administrators" has a full name of "Administrators.System".
/// </para>
/// </remarks>
public override void CreateRole(string rolename)
{
CallContext cctx = _cctx.CreateCopy();
RoleServiceProxy rsvc = new RoleServiceProxy();
try
{
Role last;
if (findRole(rolename, out last) == null)
{
int lev = -1;
string[] rolepath = rolename.Trim('.').Split('.');
if (last != null)
{
lev = 0;
var x = last;
while (x.UpperRef != null)
{
lev++;
x = x.UpperRef;
}
}
RoleSet rs = new RoleSet();
for (int i = lev + 1; i < rolepath.Length; i++)
{
Role r = new Role();
r.ApplicationID = app.ID;
r.RoleName = rolepath[i];
r.DisplayName = rolepath[i];
r.Description = "";
r.ParentID = last == null ? new int?() : last.ID;
var _r = rsvc.AddOrUpdateEntities(cctx, rs, new Role[] { r });
r = _r.ChangedEntities[0].UpdatedItem;
last = r;
}
}
}
catch (Exception e)
{
if (WriteExceptionsToEventLog)
{
WriteToEventLog(e, "CreateRole");
}
throw e;
}
finally
{
}
}
/// <summary>
/// Removes a role from the data source for the configured applicationName.
/// </summary>
/// <param name="rolename">The full name of the role to delete.</param>
/// <param name="throwOnPopulatedRole">
/// Whether or not throw an exception if roleName has one or more members and do not delete roleName.
/// </param>
/// <returns>
/// Whether or not the role was successfully deleted.
/// </returns>
/// <remarks>
/// <para>
/// The current service supports hierarchic roles. A role's full name is the full path of a role
/// where nodes in the path is separated by ".".
/// </para>
/// <para>
/// For example, the role "System" under "Administrators" has a full name of "Administrators.System".
/// </para>
/// </remarks>
public override bool DeleteRole(string rolename, bool throwOnPopulatedRole)
{
CallContext cctx = _cctx.CreateCopy();
RoleServiceProxy rsvc = new RoleServiceProxy();
try
{
Role r = findRole(rolename);
if (r != null)
{
if (!throwOnPopulatedRole)
rsvc.DeleteEntities(cctx, new RoleSet(), new Role[] { r });
else
{
var rus = GetUsersInRole(rolename);
if (rus == null || rus.Length == 0)
rsvc.DeleteEntities(cctx, new RoleSet(), new Role[] { r });
else
throw new ProviderException("Cannot delete a populated role.");
}
}
return true;
}
catch (Exception e)
{
if (WriteExceptionsToEventLog)
{
WriteToEventLog(e, "DeleteRole");
}
throw e;
}
finally
{
}
}
/// <summary>
/// Adds the specified user names to the specified roles for the configured applicationName.
/// </summary>
/// <param name="usernames">A string array of user names to be added to the specified roles. </param>
/// <param name="rolenames">A string array of the full role names to add the specified user names to.</param>
/// <remarks>
/// <para>
/// The current service supports hierarchic roles. A role's full name is the full path of a role
/// where nodes in the path is separated by ".".
/// </para>
/// <para>
/// For example, the role "System" under "Administrators" has a full name of "Administrators.System".
/// </para>
/// </remarks>
public override void AddUsersToRoles(string[] usernames, string[] rolenames)
{
foreach (string username in usernames)
{
if (username.Contains(","))
throw new ArgumentException("User names cannot contain commas.");
}
CallContext cctx = _cctx.CreateCopy();
RoleServiceProxy rsvc = new RoleServiceProxy();
try
{
List<Role> roles = new List<Role>();
foreach (string rolename in rolenames)
{
var r = findRole(rolename);
if (r == null)
throw new ProviderException("Role name '" + rolename + @"' is not found.");
roles.Add(r);
}
UserServiceProxy usvc = new UserServiceProxy();
UsersInRoleServiceProxy ursvc = new UsersInRoleServiceProxy();
foreach (string un in usernames)
{
List<User> ul = usvc.LoadEntityByNature(cctx, un);
if (ul == null || ul.Count == 0)
throw new ArgumentException("User '" + un + "' is not found.");
User u = ul[0];
List<UsersInRole> l = new List<UsersInRole>();
foreach (Role r in roles)
{
UsersInRole uir = new UsersInRole();
uir.RoleID = r.ID;
uir.UserID = u.ID;
l.Add(uir);
}
ursvc.AddOrUpdateEntities(cctx, new UsersInRoleSet(), l.ToArray());
}
}
catch (Exception e)
{
if (WriteExceptionsToEventLog)
{
WriteToEventLog(e, "AddUsersToRoles");
}
else
{
throw e;
}
}
finally
{
}
}
/// <summary>
/// Removes the specified user names from the specified roles for the configured applicationName.
/// </summary>
/// <param name="usernames">A string array of user names to be removed from the specified roles. </param>
/// <param name="rolenames">A string array of full role names to remove the specified user names from.</param>
/// <remarks>
/// <para>
/// The current service supports hierarchic roles. A role's full name is the full path of a role
/// where nodes in the path is separated by ".".
/// </para>
/// <para>
/// For example, the role "System" under "Administrators" has a full name of "Administrators.System".
/// </para>
/// </remarks>
public override void RemoveUsersFromRoles(string[] usernames, string[] rolenames)
{
CallContext cctx = _cctx.CreateCopy();
try
{
List<User> users = new List<User>();
foreach (string n in usernames)
{
User u = findUser(n);
if (u != null)
users.Add(u);
}
List<Role> roles = new List<Role>();
foreach (string n in rolenames)
{
Role r = findRole(n);
if (r != null)
roles.Add(r);
}
UsersInRoleServiceProxy uisvc = new UsersInRoleServiceProxy();
List<UsersInRole> l = new List<UsersInRole>();
foreach (User u in users)
{
foreach (Role r in roles)
{
UsersInRole uir = uisvc.LoadEntityByKey(cctx, r.ID, u.ID);
if (uir != null)
l.Add(uir);
}
}
if (l.Count > 0)
{
uisvc.DeleteEntities(cctx, new UsersInRoleSet(), l.ToArray());
}
}
catch (Exception e)
{
if (WriteExceptionsToEventLog)
{
WriteToEventLog(e, "RemoveUsersFromRoles");
}
throw e;
}
finally
{
}
}
/// <summary>
/// Gets a list of all the roles for the configured applicationName.
/// </summary>
/// <returns>
/// A string array containing the full names of all the roles stored in the data source for the configured applicationName.
/// </returns>
public override string[] GetAllRoles()
{
CallContext cctx = _cctx.CreateCopy();
Application_ServiceProxy asvc = new Application_ServiceProxy();
try
{
var lapr = asvc.MaterializeAllRoles(cctx, app);
List<string> lrns = new List<string>();
foreach (Role r in lapr)
lrns.Add(rolePath(r));
return lrns.ToArray();
}
finally
{
}
}
/// <summary>
/// Gets a list of the roles that a specified user is in for the configured applicationName.
/// </summary>
/// <param name="username">The user's user name to return a list of roles for.</param>
/// <returns>
/// A string array containing the names of all the roles that the specified user is in for the configured applicationName.
/// </returns>
/// <remarks>
/// <para>
/// The current service supports hierarchic roles. If a user has a explicit role R, then the same user implicitly has all the roles
/// that the are parents of R, if any.
/// </para>
/// <para>
/// For example, if a user has the role "System" under "Administrators", then he/she also has the "Administrators" role implicitly.
/// </para>
/// </remarks>
public override string[] GetRolesForUser(string username)
{
CallContext cctx = _cctx.CreateCopy();
try
{
User u = findUser(username);
if (u == null)
return new string[] { };
RoleServiceProxy rsvc = new RoleServiceProxy();
QueryExpresion qexpr = new QueryExpresion();
qexpr.OrderTks = new List<QToken>(new QToken[] { new QToken { TkName = "RoleName" } });
qexpr.FilterTks = new List<QToken>(new QToken[]{
new QToken { TkName = "ApplicationID" },
new QToken { TkName = "==" },
new QToken { TkName = "\"" + app.ID + "\"" },
new QToken { TkName = "&&" },
new QToken { TkName = "UsersInRole." },
new QToken { TkName = "UserID" },
new QToken { TkName = "==" },
new QToken { TkName = "\"" + u.ID + "\"" }
});
var roles = rsvc.QueryDatabase(cctx, new RoleSet(), qexpr);
List<string> lrns = new List<string>();
foreach (Role r in roles)
{
//
// if a user is in a role, then he/she is in the parent roles (if any) of that role as well, this rule is also applied to the parent role ....
//
if (r.ParentID != null)
{
Stack<Role> srs = new Stack<Role>();
Role pr = r;
while (pr != null)
{
srs.Push(pr);
var p = rsvc.MaterializeUpperRef(cctx, pr);
pr.UpperRef = p;
pr = p;
}
while (srs.Count > 0)
{
string rp = rolePath(srs.Pop());
if (!lrns.Contains(rp))
lrns.Add(rp);
}
}
else
{
string rp = rolePath(r);
if (!lrns.Contains(rp))
lrns.Add(rp);
}
}
return lrns.ToArray();
}
finally
{
}
}
/// <summary>
/// Gets a list of users in the specified role for the configured applicationName.
/// </summary>
/// <param name="rolename">The full name of the role to get the list of users for.</param>
/// <returns>
/// A string array containing the names of all the users who are members of the specified role for the configured applicationName.
/// </returns>
/// <remarks>
/// <para>
/// The current service supports hierarchic roles. A role's member includes not only its direct members, but also the members of
/// its child role sub-trees, if any.
/// </para>
/// <para>
/// For example, if role "Administrators" has a child node "System", then the members of "Administrators" includes users in
/// "Administrators" and those of users in "System" role as well.
/// </para>
/// </remarks>
public override string[] GetUsersInRole(string rolename)
{
CallContext cctx = _cctx.CreateCopy();
try
{
Role r = findRole(rolename);
if (r == null)
return new string[] { };
RoleServiceProxy rsvc = new RoleServiceProxy();
var ra = rsvc.LoadEntityHierarchyRecurs(cctx, r, 0, -1);
//for a given role, the users in it also include the ones in all its child roles, recursively (see above), in addition to its own ...
List<string> luns = new List<string>();
_getUserInRole(cctx, ra, luns);
return luns.ToArray();
}
finally
{
}
}
private void _getUserInRole(CallContext cctx, EntityAbs<Role> ra, List<string> usersinrole)
{
UserServiceProxy usvc = new UserServiceProxy();
QueryExpresion qexpr = new QueryExpresion();
qexpr.OrderTks = new List<QToken>(new QToken[] { new QToken { TkName = "Username" } });
qexpr.FilterTks = new List<QToken>(new QToken[]{
new QToken { TkName = "UsersInRole." },
new QToken { TkName = "RoleID" },
new QToken { TkName = "==" },
new QToken { TkName = "" + ra.DataBehind.ID + "" }
});
var users = usvc.QueryDatabase(cctx, new UserSet(), qexpr);
foreach (User u in users)
usersinrole.Add(u.Username);
if (ra.ChildEntities != null)
{
foreach (var c in ra.ChildEntities)
_getUserInRole(cctx, c, usersinrole);
}
}
/// <summary>
/// Gets a value indicating whether the specified user is in the specified role for the configured applicationName.
/// </summary>
/// <param name="username">The user name to search for.</param>
/// <param name="rolename">The full role name to search in.</param>
/// <returns>
/// Whether or not the specified user is in the specified role for the configured applicationName.
/// </returns>
/// <remarks>
/// <para>
/// The current service supports hierarchic roles. A role's member includes not only its direct members, but also the members of
/// its child role sub-trees, if any.
/// </para>
/// <para>
/// For example, if role "Administrators" has a child node "System", then the members of "Administrators" includes users in
/// "Administrators" and those of users in "System" role as well.
/// </para>
/// </remarks>
public override bool IsUserInRole(string username, string rolename)
{
CallContext cctx = _cctx.CreateCopy();
try
{
User u = findUser(username);
if (u == null)
return false;
Role r = findRole(rolename);
if (r == null)
return false;
UsersInRoleServiceProxy uisvc = new UsersInRoleServiceProxy();
UsersInRole x = uisvc.LoadEntityByKey(cctx, r.ID, u.ID);
if (x != null)
return true;
else
{
var urs = GetUsersInRole(rolename);
return (from d in urs where d == username select d).Any();
}
}
finally
{
}
}
/// <summary>
/// Gets a value indicating whether the specified role name already exists in the role data source for the configured applicationName.
/// </summary>
/// <param name="rolename">The full name of the role to search for in the data source.</param>
/// <returns>
/// Whether or not the role name already exists in the data source for the configured applicationName.
/// </returns>
public override bool RoleExists(string rolename)
{
return findRole(rolename) != null;
}
/// <summary>
/// Gets an array of user names in a role where the user name contains the specified user name to match.
/// </summary>
/// <param name="rolename">The full name of the role to search in.</param>
/// <param name="usernameToMatch">The user name to search for.</param>
/// <returns>
/// A string array containing the names of all the users where the user name matches usernameToMatch and the user is a member of the specified role.
/// </returns>
/// <remarks>
/// Note since this is a query, this method does not drill into the child roles. It only return having explicit role with foll name <paramref name="rolename"/>.
/// </remarks>
public override string[] FindUsersInRole(string rolename, string usernameToMatch)
{
CallContext cctx = _cctx.CreateCopy();
try
{
Role r = findRole(rolename);
if (r == null)
return new string[] { };
UserServiceProxy usvc = new UserServiceProxy();
QueryExpresion qexpr = new QueryExpresion();
qexpr.OrderTks = new List<QToken>(new QToken[] { new QToken { TkName = "Username" } });
qexpr.FilterTks = new List<QToken>(new QToken[]{
new QToken { TkName = "UsersInRole." },
new QToken { TkName = "RoleID" },
new QToken { TkName = "==" },
new QToken { TkName = "" + r.ID + "" },
new QToken { TkName = "&&" },
new QToken { TkName = "Username" },
new QToken { TkName = "contains" },
new QToken { TkName = "\"" + usernameToMatch + "\"" }
});
var users = usvc.QueryDatabase(cctx, new UserSet(), qexpr);
List<string> luns = new List<string>();
foreach (User u in users)
luns.Add(u.Username);
// since this is a query, we do not drill into the child roles, like what it is done above ....
return luns.ToArray();
}
finally
{
}
}
private Role findRole(string rolename)
{
Role last;
return findRole(rolename, out last);
}
private Role findRole(string rolename, out Role last)
{
last = null;
if (string.IsNullOrEmpty(rolename))
return null;
CallContext cctx = _cctx.CreateCopy();
string[] rolepath = rolename.Trim('.').Split('.');
RoleServiceProxy rsvc = new RoleServiceProxy();
QueryExpresion qexpr = new QueryExpresion();
qexpr.OrderTks = new List<QToken>(new QToken[] { new QToken { TkName = "RoleName" } });
qexpr.FilterTks = new List<QToken>(new QToken[]{
new QToken { TkName = "ApplicationID" },
new QToken { TkName = "==" },
new QToken { TkName = "\"" + app.ID + "\"" },
new QToken { TkName = "&&" },
new QToken { TkName = "ParentID" },
new QToken { TkName = "is null" }
});
var rrts = rsvc.QueryDatabase(cctx, new RoleSet(), qexpr);
foreach (var rr in rrts)
{
if (rr.RoleName == rolepath[0])
{
if (rolepath.Length > 1)
{
var rtree = rsvc.LoadEntityFullHierarchyRecurs(cctx, rr);
last = rtree.DataBehind;
var r = findMatch(rtree, rolepath, 1, ref last);
return r;
}
else
{
last = rr;
return rr;
}
}
}
return null;
}
private Role findMatch(EntityAbs<Role> ra, string[] path, int lev, ref Role last)
{
if (ra.ChildEntities != null)
{
foreach (var c in ra.ChildEntities)
{
if (c.DataBehind.RoleName == path[lev])
{
c.DataBehind.UpperRef = last;
last = c.DataBehind;
if (lev == path.Length - 1)
return c.DataBehind;
else
return findMatch(c, path, lev + 1, ref last);
}
}
}
return null;
}
private User findUser(string name)
{
UserServiceProxy usvc = new UserServiceProxy();
var usrs = usvc.LoadEntityByNature(_cctx.CreateCopy(), name);
if (usrs == null || usrs.Count == 0)
return null;
return usrs[0];
}
private string rolePath(Role r)
{
RoleServiceProxy rsvc = null;
string rpath = r.RoleName;
while (r.ParentID != null)
{
if (r.UpperRef == null)
{
if (rsvc == null)
rsvc = new RoleServiceProxy();
r.UpperRef = rsvc.MaterializeUpperRef(_cctx.CreateCopy(), r);
}
rpath = r.UpperRef.RoleName + "." + rpath;
r = r.UpperRef;
}
return rpath;
}
private void WriteToEventLog(Exception e, string action)
{
CallContext cctx = _cctx.CreateCopy();
string message = "An exception occurred communicating with the data source.\n\n";
message += "Action: " + action;
Trace.Write(message);
Debug.Write(message);
/*
if (log.IsErrorEnabled)
log.Error("[" + cctx.InVokePath + "]: " + message, e);
*/
}
}
}