I'm using WindowsTokenRoleProvider to determine Active Directory group membership in an ASP.NET web application.
My problem is that performance is not good, especially when a user is in many groups. As an example, I am in 253(!) groups, and WindowsTokenRoleProvider is taking around 150 seconds to determine what groups I am in.
I know I can use caching so that this isn't done on subsequent requests for a user, but obviously it isn't acceptable to take that long on the first hit.
What are my options? Can I force WindowsTokenRoleProvider to only consider certain groups? (I'm only interested in 5).
Some testing has revealed that my problem is that calling:
Roles.IsUserInRole(groupName)
is accessing the method GetRolesForUser in the RoleProvider - which is retrieving details of every role the user is a member of.
But calling:
Roles.Provider.IsUserInRole(groupName)
determines whether or not the user is in the group - without retrieving the details of every role the user is in.
Weird, but it looks like using Roles.Provider.IsUserInRole will solve my problem.
* UPDATE *
It turns out that this is just a partial workaround; if I use imperative permission checks, or 'allow' and 'deny' in web.comfig, then WindowsTokenRoleProvider still goes and slowly gets details of every group the user is a member of :o(
So my question still stands...
* UPDATE *
I solved this by creating a class that extends from WindowsTokenRoleProvider and overriding GetRolesForUser so it only checks for membership of roles specified in the configuration. It includes caching too:
/// <summary>
/// Retrieve the list of roles (Windows Groups) that a user is a member of
/// </summary>
/// <remarks>
/// Note that we are checking only against each system role because calling:
/// base.GetRolesForUser(username);
/// Is _very_ slow if the user is in a lot of AD groups
/// </remarks>
/// <param name="username">The user to check membership for</param>
/// <returns>String array containing the names of the roles the user is a member of</returns>
public override string[] GetRolesForUser(string username)
{
// Will contain the list of roles that the user is a member of
List<string> roles = null;
// Create unique cache key for the user
string key = String.Concat(username, ":", base.ApplicationName);
// Get cache for current session
Cache cache = HttpContext.Current.Cache;
// Obtain cached roles for the user
if (cache[key] != null)
{
roles = new List<string>(cache[key] as string[]);
}
// Was the list of roles for the user in the cache?
if (roles == null)
{
roles = new List<string>();
// For each system role, determine if the user is a member of that role
foreach (SystemRoleElement role in WebConfigSection.Settings.SystemRoles)
{
if (base.IsUserInRole(username, role.Name))
{
roles.Add(role.Name);
}
}
// Cache the roles for 1 hour
cache.Insert(key, roles.ToArray(), null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
}
// Return list of roles for the user
return roles.ToArray();
}
Related
Is it possible to get the user in a RouteProviderInterface implementation?
My RouteProviderInterface implementation loads a number of new Routes, nothing special. But I want to customize the Routes based on a user setting, if a user is logged in.
If I inject the TokenStorage, the user is not loaded and null:
public function __construct(TokenStorage $tokenStorage) {
$this->user = $tokenStorage->getToken()->getUser(); // is null
}
Is there another way to get the user?
Some edit based on comments:
I am trying this with a authenticated user. I also dump the user in the actual controller being used and the user does exists there
All firewalls have "stateless: true" in the config
New MVC4 application created UserProfile table :
UserId(int) | UserName(nvarchar)
In controller :
string currentUser = User.Identity.Name; // returns UserName
var mu1 = Membership.GetUser(); // returns null
var mu2 = Membership.GetUser(currentUser); // returns null as well
I read a lot of threads and they all talk about getting Guid, which I don't even have in user and membership tables.
Is there a way to get the UserId (int) of currently logged in User ?
Make sure you add [InitializeSimpleMembership] to the top of your controller so that it initializes the simple membership via the file InitializeSimpleMembershipAttribute.cs in the Filters directory.
Then you can access the user id in several different ways:
int mu1 = (int)WebSecurity.CurrentUserId;
int mu2 = (int)Membership.GetUser().ProviderUserKey;
You can get UserId as int with
WebSecurity.GetUserId(User.Identity.Name);
Additional Information: You should add [InitializeSimpleMembership] on top of controller class if you use another controller than AccountController.
mu1 and mu2 will contain an instance of MembershipUser if the user has successfully authenticated. MembershipUser has a ProviderUserKey property of type object that will contain the identifier for a user.
Membership.GetUser() requires a MembershipProvider to be hooked up for the application and for the user to be authenticated; since you are seeing null returned, this would suggest that one of the aforementioned conditions is not met.
You can get UserId
if (Request.IsAuthenticated)
{
var membership = (SimpleMembershipProvider)Membership.Provider;
int idUser = membership.GetUserId(User.Identity.Name);
}
I'm building an ASP.NET MVC3 Website with EF and DB First Approach. I need to come up with a reliable mechanism for database context switching in runtime for users. I've got several databases (same schema) that are used in remote "workshops" and application users in company headquaters need to have the ability to switch between databases at any time.
First I have implemented a base controller, that had ChangeDbContext(string dbname). It was persisting selected dbName to Session, and then I was retrieving from Session in OnActionExecuting method. However it turned out to be not reliable because session behaved unpredicatble (random expiration etc.) So I'm looking a smart way to replace Session with something else.
I could use advices on :
- where to put EntityFramework object initialization (BaseController Constructor ?)
- are there any additional changes that I should do to utilize Impersonation with WindowsAuth for DB connection ?
First, you need to insure your application session can survive restarts and app pool recycles. See this
Second, you need to inject the connection string for your DBContext based on the authenticated user request.
I assume you’ve got a database full of users so what you need to do is save a list of possible connection strings in a SQL table and relate them back to their associated user accounts. Once you’ve authenticated the user you need to retrieve the connection string associated with the user account. You don't want to store your connection string in a session or any other mechanism that could potentially expose sensitive data to a web client. So in summary this what you need to do.
You will want to retrieve your connection string for each request base on the authenticated user.
Inject the connection string into your DBContext.
Make your database calls as necessary.
Money!
Injecting strings into entity is easy.
If you're using EF 4.1 Code first your DBContext would look something like this.
EF 4.1 accepts normal ADO.NET connection strings.
public class ExampleProvider : DbContext, IExampleProvider
{
private readonly string _nameOrStringConnection;
public ExampleProvider()
{
}
public ExampleProvider(string nameOrStringConnection)
: base(nameOrStringConnection)
{
_nameOrStringConnection = nameOrStringConnection;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Example>().ToTable("example");
modelBuilder.Entity<Example>().HasKey(x => x.ExampleId);
}
public DbSet<Example> Examples { get; set; }
}
If you're using EF.edmx you will need to make sure that your injected connection string includes the edmx metadata files info like this...
..."metadata=res:///ExampleModel.csdl|res:///ExampleModel.ssdl|res://*/ExampleModel.msl;...
If you look in the edmx designer file you will see your DBContext has several constructor overloads. Use the second or third overload per your needs.
#region Contexts
/// <summary>
/// No Metadata Documentation available.
/// </summary>
public partial class Entities : ObjectContext
{
#region Constructors
/// <summary>
/// Initializes a new Entities object using the connection string found in the 'Entities' section of the application configuration file.
/// </summary>
public Entities() : base("name=Entities", "Entities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
/// <summary>
/// Initialize a new Entities object.
/// </summary>
public Entities(string connectionString) : base(connectionString, "Entities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
/// <summary>
/// Initialize a new Entities object.
/// </summary>
public Entities(EntityConnection connection) : base(connection, "Entities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
#endregion
/// incomplete file
Good luck!
Cookies can be persisted for a long expiry, well longer than a session anyway. You could also look at a hidden page variable or mangled URL.
1) Sessions doesn't expire randomply...but after the time you set in the we.config...default is 10 min. Seesion MUST expire because there is no way to know that an user left our web site...so if they stop accessing pages for, say 10 min, we ASSUME, they went away...You can increase this time but the problem remain.
2) Tou can store directly the information in a cookie. Now since the cookie only waste resources on the browser (very little space), you can make the cookie persistent...so that it never expire
3) As an alternative to cookies you can store this information together with the credential information of users (login name etc.) You can use the Profile provider to define a property DBChosen.
Is this possible to set different timeout for different session in ASP.Net?
Edited
I mean that in the same page i have 2 session variable Session["ss1"] and Session["ss2"], is there possible to set timeout for each session? Or is there anyway to do the same like save session to cookie and set expire?
Sry im just new to ASP.Net
I wrote a very simple extender class that does that. You can find the source code here
Usage:
//store and expire after 5 minutes
Session.AddWithTimeout("key", "value", TimeSpan.FromMinutes(5));
Set any timeout at login time, you can set different timeout for different users...
HttpContext.Current.Session.Timeout = 540;
If you are talking about session timeout for different users then
You can use Global.asax in this you can use Session_Start event and in this event you can set session timeout differently for different users
The answer is no the session timeout applies to ALL session variables per user. You can however use the cache or a cookie which both support timeout on an individua(per key) level.
But hang on those solutions don't come without some major drawbacks. If you use the cache you lose the privacy the session provides and if you use the cookie you are constrained with file size and serialization issues.
One workaround for this is to use the cache and make sure you include the user's session id in every key you use. This way you'll end up with a cache storage that mimics the session itself.
If you want further functionality and don't want to bother about implementing this however you can use the API from this little project on CodePlex:
http://www.univar.codeplex.com
The version 2.0 offers many storage type options out of the box including a session bound cache.
/// <summary>
/// this class saves something to the Session object
/// but with an EXPIRATION TIMEOUT
/// (just like the ASP.NET Cache)
/// (c) Jitbit 2011. MIT license
/// usage sample:
/// Session.AddWithTimeout(
/// "key",
/// "value",
/// TimeSpan.FromMinutes(5));
/// </summary>
public static class SessionExtender
{
public static void AddWithTimeout(
this HttpSessionState session,
string name,
object value,
TimeSpan expireAfter)
{
session[name] = value;
session[name + "ExpDate"] = DateTime.Now.Add(expireAfter);
}
public static object GetWithTimeout(
this HttpSessionState session,
string name)
{
object value = session[name];
if (value == null) return null;
DateTime? expDate = session[name + "ExpDate"] as DateTime?;
if (expDate == null) return null;
if (expDate < DateTime.Now)
{
session.Remove(name);
session.Remove(name + "ExpDate");
return null;
}
return value;
}
}
Usage:
//store and expire after 5 minutes
Session.AddWithTimeout("key", "value", TimeSpan.FromMinutes(5));
//get the stored value
Session.GetWithTimeout("key");
by Alex. CEO, founder https://www.jitbit.com/alexblog/196-aspnet-session-caching-expiring-values/
I'm working on a web-based project that users will access after having been authenticated by Active Directory. My boss controls access to Active Directory, and wants to use groups to handle authentication to the application I'm writing. He's also provided me with a class to connect to pull the information I need from AD (logon name and active directory groups), so that's not a concern here.
Here's my problem: most users belong to more than 20 AD groups. I've never worked with AD before, so I have no idea if this is abnormally high, but I do know that it takes 5-6 seconds for AD to respond to my request for user group lists, so I really want to minimize the number of times I have to request groups, especially since peak use will involve about 200-300 users hitting the page within a few hours.
This application has three separate control groups: users, reviewers, and administrators. Each group has their own collection of pages in their respective folders of the website. Each folder has one entry-point page (i.e., the others will redirect to this page if no pertinent data are found in the Session). This page checks for valid a AD group only if IsPostback == false, and reads from an entry in the Session object to make sure that the user has the proper access.
So (finally), here's my question: Am I handling this in the most efficient way possible, or have I overlooked some simple alternative here?
For your issue above, yes AD sometimes is a bit slow depends on the load but rather than concentrating on that why not change your logic rather than ennumerating all the users groups why not check whether a user is a group member of. To implement it here is the code
/// <summary>
/// Checks if user is a member of a given group
/// </summary>
/// <param name="sUserName">The user you want to validate</param>
/// <param name="sGroupName">The group you want to check the membership of the user</param>
/// <returns>Returns true if user is a group member</returns>
public bool IsUserGroupMember(string sUserName, string sGroupName)
{
UserPrincipal oUserPrincipal = GetUser(sUserName);
GroupPrincipal oGroupPrincipal = GetGroup(sGroupName);
if (oUserPrincipal == null || oGroupPrincipal == null)
{
return oGroupPrincipal.Members.Contains(oUserPrincipal);
}
else
{
return false;
}
}
Or even better if you still want to prefer to use the ennumeration part, why not ennumerate only the groups on a specific OU rather than the whole directory like such
/// <summary>
/// Gets a list of the users group memberships
/// </summary>
/// <param name="sUserName">The user you want to get the group memberships</param>
/// <param name="sOU">The OU you want to search user groups from</param>
/// <returns>Returns an arraylist of group memberships</returns>
public ArrayList GetUserGroups(string sUserName, string sOU)
{
ArrayList myItems = new ArrayList();
UserPrincipal oUserPrincipal = GetUser(sUserName);
PrincipalSearchResult<Principal> oPrincipalSearchResult = oUserPrincipal.GetGroups(GetPrincipalContext(sOU));
foreach (Principal oResult in oPrincipalSearchResult)
{
myItems.Add(oResult.Name);
}
return myItems;
}
/// <summary>
/// Gets the principal context on specified OU
/// </summary>
/// <param name="sOU">The OU you want your Principal Context to run on</param>
/// <returns>Retruns the PrincipalContext object</returns>
public PrincipalContext GetPrincipalContext(string sOU)
{
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain, sDomain, sOU, ContextOptions.SimpleBind, sServiceUser, sServicePassword);
return oPrincipalContext;
}
Finally as a note if you value security more than speed then I would not suggest IsPostback == false so that if there are any changes on a Security Group Membership of a certain user then you will be able to capture it better on the next process.
For a full implementation of AD Methods please refer here
if you are using .Net 2.0
http://anyrest.wordpress.com/2010/02/01/active-directory-objects-and-c/
or if you are using .Net 3.5 or 4.0
http://anyrest.wordpress.com/2010/06/28/active-directory-c/