I am using Angular in an ASP.NET Core with ASP.NET Identity application.
I have the following controller action
[HttpGet("users/{userId:int:min(1)}/notes"), Authorize]
public async Task<IActionResult> GetNotesBy(userId) {
var data = _service.getNotesBy(userId);
return Ok(data);
} // GetNotesBy
I would like to restrict the access to the API so:
If a user is authenticated than it can only access its notes.
I want to prevent an authenticate user with ID=X to access the notes of a user with ID=Y. How can I block an user in this situation?
This is what resource based authorization is aimed at.
As resource based authorization requires the actual resource it needs to happen imperatively, inside your controller.
The following is for ASP.NET Core RC1.
So, let's assume your getNotesBy returns a Notes class, and you have a few operations, read, write, update, delete.
First we need to define the operations. There's a suitable base class in Microsoft.AspNet.Authorization.Infrastructure, OperationAuthorizationRequirement. So we'd do something like this.
public static class Operations
{
public static OperationAuthorizationRequirement Create =
new OperationAuthorizationRequirement { Name = "Create" };
public static OperationAuthorizationRequirement Read =
new OperationAuthorizationRequirement { Name = "Read" };
public static OperationAuthorizationRequirement Update =
new OperationAuthorizationRequirement { Name = "Update" };
public static OperationAuthorizationRequirement Delete =
new OperationAuthorizationRequirement { Name = "Delete" };
}
So now we have our operations, we think about how we handle authorization. You have two ways operations can succeed, if the current user owns the notes, or the current user is an admin. This equates to two handlers for a single requirement/operation.
The admin one is easy, it would look something like this;
public class AdminAuthorizationHander :
AuthorizationHandler<OperationAuthorizationRequirement, Notes>
{
protected override void Handle(AuthorizationContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
var isSuperUser = context.User.FindFirst(c => c.Type == "Superuser" &&
c.Value == "True");
if (isSuperUser != null)
{
context.Succeed(requirement);
return;
}
}
}
Here we're looking for a Superuser claim with a value of True. If that's present we succeed the requirement. You can see from the method signature we're taking the OperationAuthorizationRequirement and a resource, the Notes class. This handler doesn't limit itself to a single operation, admins have rights to every operation.
Now we can write the handler which looks for the actual user.
public class NotesAuthorizationHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Notes>
{
protected override void Handle(AuthorizationContext context,
OperationAuthorizationRequirement requirement,
Notes resource)
{
if (context.User.Name == resource.Owner)
{
context.Succeed(requirement);
}
}
}
Here we are writing something that will work for all resources, and checks an Owner property on the resource against the name of the current user.
So we have two handlers now for a single requirement, the OperationAuthorizationRequirement.
Now we need to register our handlers. In startup.cs you register handlers in DI in the ConfigureServices() method. After the call to services.AddAuthorization() you need to put your handlers into DI. You would do this like so;
services.AddSingleton<IAuthorizationHandler, AdminAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, NotesAuthorizationHandler>();
You can adjust the scope from Singleton to whatever you like if you are taking things like a DbContext.
Finally we're almost ready to call this, but first you need to change your controller constructor to take an instance of IAuthorizationService. Once you have that you can call AuthorizeAsync() and away you go.
[Authorize]
public class NotesController : Controller
{
IAuthorizationService _authorizationService;
public NotesController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
[HttpGet("users/{userId:int:min(1)}/notes"), Authorize]
public async Task<IActionResult> GetNotesBy(userId)
{
var resource = _service.getNotesBy(userId);
if (await authorizationService.AuthorizeAsync(User, resource, Operations.Read))
{
return Ok(data);
}
return new ChallengeResult();
}
}
So what you are doing is getting your resource, and then authorizing the current user against it and the operation. When this happens all handlers which can handle that resource and operation will get called. As there are multiple handlers any one can succeed and allow access.
Related
Note: Cross-posted to ServerFault, based on comments.
Intro
I need to password protect some actions in my application, such as loading/saving files, clicking check-boxes, etc. This is a standard C# .Net 4.0, WinForms application which will run on Windows 7 in a corporate network.
I was about to roll my own very basic system (read obfuscation with wide open backdoors) with a text file of users/passwords/permissions (hashed and salted) until after some searching I found what looks like a
tantalizingly simple approach , but I'm having trouble finding a good tutorial on Roles that isn't about ASP.NET.
Question
So does anyone know of one or more tutorials that show me how to:
Create a Windows User/Group and give that User/Group a Role or Permission.
Note that I'm testing this from my company's networked laptop, but will deploy it on the customer's corporate network (Not sure if this is an issue, or how tricky this will get).
Create winforms/console app sample with even just a single method that prints "Hello World" if I'm authenticated or throws an exception if I'm not?
I've never done Network Admin or anything related and I keep reading about Active Directory and Local Users Vs Networked Users... I was hoping for an approach where I could build to an Interface and just ask Windows if the current user has permission ABC and not care too much about how Windows figured that out. Then I can make a concrete implementation for each Local/Network/ActiveDirectory/etc. use case as required (or if required... as I don't even know that right now).
Background
- read if interested, but not required to answer question
Just to make sure I'm going in the right direction here, basically I need/want to test this on my development PC to make sure it's going to have a good end-user experience for my customer. The problem is that currently they run an Auto-login script for each computer that runs my application and there are several different operators that use my application throughout the day. The customer wants password protection on certain features of my app and only provide that to certain operators. I have no problem fitting this in, as I've expected the request for a while, I just haven't ever programmed authentication before.
I think it's worthwhile to convince my customer to give each operator their own network account and assign whatever permissions they want to that operator or group, in case they need to fire somebody, change permissions, etc. It also means I just open several options for them and they can group those permissions however they see fit based on internal corporate policies, which I really shouldn't have to be worried about (but will be if I have to roll my own, as they're IT department knows almost nothing of my application).
From what I can tell it also makes my life a lot easier by not having to deal with hashing passwords and encryption, etc. and just handle which Role is required to click this or that button.
First of all, you'd have to determine, if you really want a simple role-based-authentication (you may want to read: http://lostechies.com/derickbailey/2011/05/24/dont-do-role-based-authorization-checks-do-activity-based-checks/)
If you're sure it's absolutely sufficient, you're already on the right way with the SO link you provided in your question. It's kind of confusing that there is no support of 'roles' by default in Windows, but there are groups. Groups can be local or remote (e.g. ActiveDirectory), so an admin could assign users to certain groups, that are specific for your application (for an example look here: http://msdn.microsoft.com/en-us/library/ms731200(v=vs.110).aspx)
One key is: You have to prepare your application's central principal, hence fill it with roles, supported for the current user.
Therefore, On the very startup of your application you then check the current active user and set your application wide principal and role(s). This may look like this (just a very simple example):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Security.Principal;
using System.Text;
using System.Threading;
namespace WindowsPrincipalTrial
{
public class Program
{
// you could also move these definitions to a config file
private static IDictionary<string, string> _groupRoleMappings = new Dictionary<string, string>()
{
{"MYAPPUSERGRP", MyRoles.Standard},
{"MYAPPSUPPORTGRP", MyRoles.Extended},
{"MYAPPADMINGRP", MyRoles.Admin},
};
private static void Main(string[] args)
{
var windowsId = WindowsIdentity.GetCurrent();
if (windowsId != null)
{
var allRoleNames = getGroupCorrespondingRoles(windowsId);
var newPrincipal = new GenericPrincipal(windowsId, allRoleNames);
Thread.CurrentPrincipal = newPrincipal;
}
else
{
throw new NotSupportedException("There must be a logged on Windows User.");
}
}
private static string[] getGroupCorrespondingRoles(WindowsIdentity id)
{
// you also could do this more elegant with LINQ
var allMappedRoleNames = new List<string>();
string roleName;
foreach (var grp in id.Groups)
{
var groupName = grp.Translate(typeof(NTAccount)).Value.ToUpper();
if (_groupRoleMappings.TryGetValue(groupName, out roleName))
{
allMappedRoleNames.Add(roleName);
}
}
return allMappedRoleNames.ToArray();
}
}
public static class MyRoles
{
public const string Standard = "standard_role";
public const string Extended = "extended_role";
public const string Admin = "admin_role";
}
}
Then your Application-Principal is set up.
Now you could check access in your code like this:
public void DoSomethingSpecial()
{
if (Thread.CurrentPrincipal.IsInRole(MyRoles.Extended))
{
// do your stuff
}
else
{
// maybe display an error
}
}
Or more drastically:
public void DoSomethingCritical()
{
var adminPermission = new PrincipalPermission(null, MyRoles.Admin);
adminPermission.Demand();
// do stuff
}
what is possible even declarative, as known from ASP.NET:
[PrincipalPermission(SecurityAction.Demand, Role=MyRoles.Admin)]
public void DoSomethingMoreCritical()
{
// do stuff
}
The ugly thing with the latter two examples is, that they throw exceptions, when the right role isn't hit.
So the mapping between roles and groups you have to do quite at the start of your app, according to the systems you want to use (local groups, AD groups, LDAP groups etc.).
If you, however, prefer authentication with actions and roles, after all, have a look at Windows Identity Foundation and Claims Based Authorization! There are already some ready-to-use frameworks out there (e.g. https://github.com/thinktecture/Thinktecture.IdentityModel).
UPDATE:
When it comes to activity based and thereby claims based authorization, I will try in short, how you could achieve it, by using Thinktecture's IdentityModel.
Generally that approach still uses roles internally, but has a kind of translation layer in between. Thinktecture already encapsulates many things needed. Authorization checks in code are then done via claim permissions. They are technically kind of request for an access to a certain resource. For the sake of simplicity I limit my example for actions only, by using one single default resource (since ClaimPermission doesn't accept an empty resource).
If you want to use action#resource pairs, you'd have to modify the code respectively.
At first you need a ClaimsAuthorizationManager
public class MyClaimsAuthorizationManager : ClaimsAuthorizationManager
{
private IActivityRoleMapper _actionToRolesMapper;
public MyClaimsAuthorizationManager(IActivityRoleMapper mapper)
{
_actionToRolesMapper = mapper;
}
public override bool CheckAccess(AuthorizationContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
try
{
var action = getActionNameFromAuthorizationContext(context);
var sufficientRoles = _actionToRolesMapper.GetRolesForAction(action)
.Select(roleName => roleName.ToUpper());
var principal = context.Principal;
return CheckAccessInternal(sufficientRoles, principal);
}
catch (Exception ex)
{
return false;
}
}
protected virtual bool CheckAccessInternal(IEnumerable<string> roleNamesInUpperCase, IClaimsPrincipal principal)
{
var result = principal.Identities.Any(identity =>
identity.Claims
.Where(claim => claim.ClaimType.Equals(identity.RoleClaimType))
.Select(roleClaim => roleClaim.Value.ToUpper())
.Any(roleName => roleNamesInUpperCase.Contains(roleName)));
return result;
}
// I'm ignoring resources here, modify this, if you need'em
private string getActionNameFromAuthorizationContext(AuthorizationContext context)
{
return context.Action
.Where(claim => claim.ClaimType.Equals(ClaimPermission.ActionType))
.Select(claim => claim.Value)
.FirstOrDefault();
}
}
As you may have guessed, IActivityRoleMapper is an interface for a class, that returns the names of all roles, that include permission for a given action.
This class is very individual and I guess you'll find your way implementing it, because it's not the point here. You could do it by hardcoding, loading from xml or from a database. Also you would have to change/extend it, if you wanted to you action#resource pairs for permission requests.
Then you'd have to change the code in main() method to:
using Thinktecture.IdentityModel;
using Thinktecture.IdentityModel.Claims;
using Microsoft.IdentityModel.Web;
private static void Main(string[] args)
{
var windowsId = WindowsIdentity.GetCurrent();
if (windowsId != null)
{
var rolesAsClaims = getGroupCorrespondingRoles(windowsId)
.Select(role => new Claim(ClaimTypes.Role, role))
.ToList();
// just if you want, remember the username
rolesAsClaims.Add(new Claim(ClaimTypes.Name, windowsId.Name));
var newId = new ClaimsIdentity(rolesAsClaims, null, ClaimTypes.Name, ClaimTypes.Role);
var newPrincipal = new ClaimsPrincipal(new ClaimsIdentity[] { newId });
AppDomain.CurrentDomain.SetThreadPrincipal(newPrincipal);
var roleMapper = new ActivityRoleMapper(); // you have to implement
// register your own authorization manager, so IdentityModel will use it per default
FederatedAuthentication.ServiceConfiguration.ClaimsAuthorizationManager = new MyClaimsAuthorizationManager(roleMapper);
}
else
{
throw new NotSupportedException("There must be a logged on Windows User.");
}
}
Finally you can check access this way:
public const string EmptyResource = "myapplication";
public void DoSomethingRestricted()
{
if (!ClaimPermission.CheckAccess("something_restricted", EmptyResource))
{
// error here
}
else
{
// do your really phat stuff here
}
}
Or again, with exceptions:
private static ClaimPermission RestrictedActionPermission = new ClaimPermission(EmptyResource, "something_restricted");
public void DoSomethingRestrictedDemand()
{
RestrictedActionPermission.Demand();
// play up, from here!
}
Declarative:
[ClaimPermission(SecurityAction.Demand, Operation = "something_restricted", Resource = EmptyResource)]
public void DoSomethingRestrictedDemand2()
{
// dostuff
}
Hope this helps.
I'm building a Silverlight Web Resource which is intended to integrate into a form, and it needs to know all of the following information:
id of the current user
id of teams current user belongs to
id of current user's security roles
I'm working in an early-bound kind of way, added a Service Reference to the OData endpoint (http://server/org/XRMservices/2011/OrganizationData.svc) which in turn provides me with the context (let's name it cmtestcontext, which is its actual name in code).
I access data through this class (I didn't create it, I just googled it out of the net some time ago: this is a stripped-down, keep-it-short version)
public class QueryInterface
{
//NOTE: ServiceReference1 is the name of the OData service reference
//Add Service Reference -> point to CRM OData url
public ServiceReference1.cmtextcontext CrmContext;
public QueryInterface()
{
var crmServerUrl = (string)GetContext().Invoke("getServerUrl");
if (crmServerUrl.EndsWith("/")) crmServerUrl = crmServerUrl.Substring(0, crmServerUrl.Length - 1);
Uri ODataUri = new Uri(crmServerUrl + "/xrmservices/2011/organizationdata.svc/", UriKind.Absolute);
CrmContext = new cmtestContext(ODataUri) { IgnoreMissingProperties = true };
}
}
The class allows me to sort of fetch in one line, as follows (actual code snippet enveloped in a dummy method to make it copy-pastable):
void RetrieveAllInformationFromCRM()
{
QueryInterface qi = new QueryInterface();
List<Guid> allData = new List<Guid>();
//NOTE: STEP 1 - USER ID
//NOTE: Since this is a web resource, I can cheat and use Xrm.Page.context.getUserId()
//NOTE: Remove the extra '{}' from the result for it to be parsed!
allData.Add(new Guid(qi.GetContext().Invoke("getUserId").ToString().Substring(1,36)));
//NOTE: STEP 2a - TEAM MEMBERSHIP FOR USER
//NOTE: TeamMembership entity links users to teams in a N:N relationship
qi.crmContext.TeamMembershipSet.BeginExecute(new AsyncCallback((result) =>
{
var teamMemberships = qi.crmContext.TeamMembershipSet.EndExecute(result)
.Where(tm => tm.TeamId.HasValue && (tm.SystemUserId ?? Guid.Empty) == userId)
.Select(tm => tm.TeamId.Value);
//NOTE: STEP 2b - TEAMS RELATED TO TEAMMEMBERSHIPS
qi.crmContext.TeamSet.BeginExecute(new AsyncCallback((result2) =>
{
var teamDetails = qi.crmContext.TeamSet.EndExecute(result2)
.Where(t => teamMemberships.Contains(t.TeamId));
foreach (var team in teamDetails)
allData.Add(team.TeamId);
//NOTE: FINAL STEP - allData is filled and ready to be used.
}), null);
}), null);
}
In the code above, my FINAL STEP picks up allData and processes it, and the flow goes on. My concern is, if/when I'll need to modify this "reader" method I'll have to cut and paste the "final" code around to ensure it's placed after all the reads. I'd like it way better if I could just make the reads follow one another, so I could do this:
void MyReaderMethod()
{
ReadEverything();
ProcessData();
}
Basically, can you just wait for a request to finish ? Hanging UI is a non-issue, I'd just wrap the code in a BackgroundWorker along with a "Please Wait" splash.
The nicest (IMO) is to convert the Async method calls (a requirement of Silverlight) into Task based calls.
With tasks you can easily seperate the query from the result action.
Then using the Async BCL's (via nuget) you can use async/await (if you are not using VS2012, then Tasks are still nicer to work with, you will just have to use continuations)
This example is for late bound, but you can modify it for your needs
public Task<OrganizationResponse> ExecuteAsync(OrganizationRequest request)
{
return Task.Factory.FromAsync<OrganizationResponse>(
(callback, state) => Begin(() => service.BeginExecute(request, callback, state)),
service.EndExecute,
null);
}
Then you can use it like
async void MyReaderMethod()
{
//TODO:wrap in try/catch
var result = await ExecuteAsync( ... );
ProcessData(result);
}
Or for VS 2010
void MyReaderMethod()
{
ExecuteAsync( ... ).ContinueWith(task =>{
//TODO: Error handling
ProcessData(task.Result);
});
}
I am using GWT and AppEngine for a project. I would like to know how can I share data (ArrayList objects)between widgets, so I could centralize the logic and reduce the number of RPC calls to the server.
I have thought of two ways, but I don't know which is better:
1) When I instantiate the widget, I pass the ArrayList object as a parameter, although I don't know how to do that because the widget gets instantiated with :
ThisAppShell shell = GWT.create(ThisAppShell.class);
2) By using a mechanism like eventBus
http://www.dev-articles.com/article/Gwt-EventBus-(HandlerManager)-the-easy-way-396001
When the user loads the application,after the login process is complete, I would like to download a list of employees which should be available for all widgets. This should all be done in the onModuleLoad() method. I would like to download them all at startup because I would like to implement some sort of caching mechanism. For example, I want to have 2 ArrayList instances:
- emplListOnStart which is populated when the application is loading
- emplListChanges, an array on which the user will make modifications from inside widgets.
After the user has finished making the changes (he presses the "Save" button), the two arrays will be compared, the differences will be saved in appengine (via RPC) and also updated in emplListOnStart.
This is the code for the EntryPoint class:
public class ThisApp implements EntryPoint {
ThisAppShell shell = GWT.create(ThisAppShell.class);
LoginServiceAsync loginService = GWT.create(LoginService.class);
private ArrayList<Employee> emplListOnStart;
private ArrayList<Employee> emplListChanges;
public void onModuleLoad() {
RootLayoutPanel.get().clear();
RootLayoutPanel.get().add(shell);
loginService.isAuthenticated(new AsyncCallback<UserDto>() {
public void onFailure(Throwable caught) {
// TODO Auto-generated method stub
}
public void onSuccess(UserDto result) {
//Here I should load the emplListOnStart list;
}
});
shell.getLogoutLink().addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
loginService.logout(new AsyncCallback() {
public void onFailure(Throwable caught) {
}
public void onSuccess(Object result) {
//Here the user will get logged out
}
});
Window.Location.assign("");
}
});
}
}
And here is the code for the widget:
public class ThisAppShell extends Composite {
private static ThisAppShellUiBinder uiBinder = GWT
.create(ThisAppShellUiBinder.class);
interface ThisAppShellUiBinder extends UiBinder<Widget, ThisAppShell> {
}
#UiField
Anchor logout_link;
#UiField
StackLayoutPanel stackLPanel;
#UiField
TabLayoutPanel tabLPanel;
public ThisAppShell() {
initWidget(uiBinder.createAndBindUi(this));
initializeWidget();
}
public void initializeWidget() {
stackLPanel.add(new HTML("Manage empl."), new HTML("Employees"), 30);
stackLPanel.add(new HTML("Manage Dept."), new HTML("Departments"), 30);
// Add a home tab
HTML homeText = new HTML("This is the home tab");
tabLPanel.add(homeText, "Home");
// Add a tab
HTML moreInfo = new HTML("This is the more info tab");
tabLPanel.add(moreInfo, "More info");
// Return the content
tabLPanel.selectTab(0);
}
public Anchor getLogoutLink() {
return logout_link;
}
}
Is this possible, or how could this be done better?
Thank you.
I think there are two ways to do it:
Create a setter on your widget to set your ArrayList instances (setData()). You can then call this function in the onSuccess method of your loginService.
Inject the singleton instance of a global EventBus into your widget (using i.e. gin/guice) and fire an event containing your data. In the widget you have to attach an EventHandler for the specific event (i.e. LoadEmplListEvent).
I think both solutions are fine to use.
Solution one creates a tighter coupling to your widget but is easier to implement and I think you should take this route if you only have a small number of widgets where you work
with the data.
Solution is a cleaner approach because it de-couples your widgets from the rest. You fire the event the data in your onSuccess method once and you don't care about the widgets.
The widgets that are interested in the data will make sure that they handle the event appropriately (by handling the event). I guess if you have a lot of widgets that have to deal with the data the second approach is the one to go for.
My question is similar to this question. I hope I can provide some more detail and context to get it answered.
So here's some context: I have a simple in-house silverlight (ver 4) app with WCF Ria services that I'm building for our small support team. It uses authentication against a third-party vended database, but all other user information, e.g. FriendlyName and Roles (only 1 role per user) comes from our own database. I'm trying to keep this simple and don't want to implement custom membership and role providers.
I have few domain service operations that I want to restrict to certain roles, so I tried using the RequiresRole attribute like so:
[RequiresRole("Admin", "HelpDesk", "Billing" )]
public RisStudyInfo GetStudyInfo(string accession) {
return ris.GetStudyInfo(accession);
}
On the client side WebContext.Current.User.IsInRole("Admin") returns true, but I always get access denied when calling the service. The RequiresAuthentication attribute works as expected.
Below is the implementation of my AuthenticationService. The User class simply inherits from UserBase and adds the FriendlyName property. Any ideas what I'm doing wrong?
[EnableClientAccess]
public class AuthenticationService : AuthenticationBase<User> {
UserDataService userData = new UserDataService();
protected override bool ValidateUser(string userName, string password) {
var auth = new DatabaseAuthenticator();
return auth.Authenticate(userName, password);
}
protected override User GetAuthenticatedUser(IPrincipal principal) {
User user = null;
if (principal.Identity.IsAuthenticated) {
user = new User();
user.FriendlyName = userData.GetFriendlyName(principal.Identity.Name);
user.Name = principal.Identity.Name;
user.Roles = GetRolesFor(user.Name);
}
return user;
}
private IEnumerable<string> GetRolesFor(string username) {
IList<string> roles = new List<string>();
string role = userData.GetRolesFor(username);
if (role != null)
roles.Add(role);
return roles;
}
Figured it out. At least 2 things wrong. First clue found here. The second clue here
1.Turns out I really do need to write a custom role provider. Only need to implement GetRolesForUser though.
public override string[] GetRolesForUser(string username) {
return new string[] { _userService.GetRolesFor(username) };
}
2.Configure the custom role provider correctly in the web.config
<roleManager cacheRolesInCookie="true" enabled="true" defaultProvider="MyRoleProvider">
<providers>
<add name="MyRoleProvider" type="MyProject.Web.Providers.MyRoleProvider, MyProject.Web"/>
</providers>
</roleManager>
I solved this one by using the local credential store to cache credentials. Whenever a local cred check fails a foreign check occurs and the cache is populated/updated. This was a trivial override of the ValidateUser method. It does mean that stale passwords continue to work until the updated password is used (it will fail locally, pass remotely and trigger an update).
This approach meant that internally everything worked as per an out of the box configuration with no need for any other mods (apart from removing the local create-a-user links).
I recently refactored some code in an Active Directory role provider to remove support for multiple domains. In the process my integration tests broke in ways that I didn't expect. The tests do not reliably succeed unless I put significant delays between the test set up code and the code that invoked the method being tested. If I run the test using the debugger it always succeeds and I can't see any problems with the code. If I run the test using the automated tools one or more tests fail and fail in ways that are unexpected.
How can I reliabily test role provider code that uses the System.Directory.AccountManagement namespace classes and methods?
Note: In keeping with the SO paradigm, I'm providing the solution that I found as a separate answer. I'm open to other solutions, however, if you feel that your solution works better than mine. This question is being contributed because I couldn't find any existing questions on SO that addressed my problem.
Some related questions are:
How to unit-test a NextPasswordChangeDate function against the Active Directory
How to setup a Active Directory environment test?
I discovered that the problem was that the PrincipalSearchers that I was using in the role provider did not always contact the same domain controller as the code used in set up did. This would result in errors due to propagation delays between domain controllers. To solve this problem I used constructor injection to provide the PrincipalContext used in set up to the role provider. This allows the role provider to always use the same context as the test code. In addition I replaced the SearchRoot on the PrincipalSearcher with a search root based on the PrincipalContext provided via constructor injection. Relevant code below. Note that the role provider implements IDisposable in order to dispose of the domain context if one isn't supplied externally.
private bool DisposeContext { get; set; }
private PrincipalContext DomainContext { get; set; }
public PrintAccountingRoleProvider() : this( null ) { }
public PrintAccountingRoleProvider( PrincipalContext domainContext )
{
this.DisposeContext = domainContext == null;
this.DomainContext = domainContext ?? new PrincipalContext( ContextType.Domain );
}
...
private UserPrincipal FindUser( string userName )
{
using (PrincipalSearcher userSearcher = new PrincipalSearcher())
{
UserPrincipal userFilter = new UserPrincipal( this.DomainContext );
userFilter.SamAccountName = userName;
userSearcher.QueryFilter = userFilter;
// Replace the searcher with one directly associated with the context to ensure that any changes
// made elsewhere will be reflected if we call the search immediately following the change. This
// is critical in the integration tests.
var searcher = userSearcher.GetUnderlyingSearcher() as DirectorySearcher;
searcher.SearchRoot = new DirectoryEntry( #"LDAP://" + this.DomainContext.ConnectedServer + #"/dc=iowa,dc=uiowa,dc=edu" );
return userSearcher.FindOne() as UserPrincipal;
}
}
...
private void Dispose( bool disposing )
{
if (!this.disposed)
{
if (disposing)
{
if (this.DisposeContext && this.DomainContext != null)
{
this.DomainContext.Dispose();
this.DomainContext = null;
}
}
this.disposed = true;
}
}