The general form of the problem is something I often cited in code reviews at Anheuser-Busch. Now I believe that Microsoft owes its success much more to salesmanship than engineering ability, but this is a high-profile product with years of history in the field, so I'm surprised the bug has survived so long. I guess it's worth publishing my advice (which I'm sure did not originate with me):
- Throw or propagate exceptions at the right level of abstraction. The Java language designers were on to something with checked exceptions: exceptions really can be regarded as part of your API (see also criticism linked at c2). If you have a method
String Profile.GetSetting(String)
with two implementations, one of which uses ADO.NET for relational database storage while the other usesSystem.IO.File
for filesystem storage, then callers should never seeSystem.Data.Common.DbException
s. If, as the maintainer ofProfile.GetSetting
, you anticipate ADO.NET or System.IO exceptions, then you should catch them and throw exceptions of some more appropriate type(s). It's OK and usually a good idea to use what you catch to initialize theInnerException
property of what you throw: that's useful diagnostic information. But, you should make the effort to wrap the exceptions you propagate when doing so mitigates a leaky abstraction. - Use distinct exception types for distinct equivalence classes of conditions you think callers might reasonably treat in different ways. I think it's helpful to think about Lisp conditions, which generalize exceptions, when you make design decisions involving errors.
- Document the exceptions you throw.
And without further ado, the specifics:
Bug 4541
Summary: | The trust relationship between the primary domain and the trusted domain failed. | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Product: | REDACTED | Reporter: | Foy, Sean | ||||||||||||
Component: | triage | Assignee: | Foy, Sean | ||||||||||||
Status: | ASSIGNED | ||||||||||||||
Severity: | blocker | CC: | michael.jorgensen@REDACTED | ||||||||||||
Priority: | P3 | ||||||||||||||
Version: | unspecified | ||||||||||||||
Hardware: | All | ||||||||||||||
OS: | All | ||||||||||||||
URL: | http://tahaze-ww07.REDACTED/REDACTED/requests/19791117T120000?requestor=sean.foy@REDACTED | ||||||||||||||
Whiteboard: | |||||||||||||||
Time tracking: |
| ||||||||||||||
Deadline: |
Additional hours worked: 1.0 There are no documented exceptions for WindowsPrincipal.IsInRole and although Google finds many reports of this exception, the most promising resolutions are vaguely described patches from Microsoft for SharePoint. But empirically I've found that I get reasonable answers for queries of the form
p.IsInRole(@"domain\group") and
p.IsInRole(@"group@domain")
where the domain name can be any non-empty string. But I get this exception for queries of the form
p.IsInRole(@"group").
Evidently IsInRole expects a domain name and throws this exception when none is present.
And here's the patch:
Index: MvcUtils.cs
===================================================================
--- MvcUtils.cs (revision 4851)
+++ MvcUtils.cs (working copy)
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
@@ -214,8 +215,22 @@
return result.ToString();
}
+ public static Boolean IsInRole(IPrincipal p, String role) {
+ try {
+ return p.Identity.IsAuthenticated && p.IsInRole(role);
+ }
+ catch (SystemException e) {
+ // see bug 4541
+ if (!e.Message.StartsWith("The trust relationship between the primary domain and the trusted domain failed.")) {
+ throw;
+ }
+
+ return false;
+ }
+ }
+
public static Boolean IsInRole(String role) {
- return HttpContext.Current.User.Identity.IsAuthenticated && HttpContext.Current.User.IsInRole(role);
+ return IsInRole(HttpContext.Current.User, role);
}
}
Index: TestMvcUtils.cs
===================================================================
--- TestMvcUtils.cs (revision 4851)
+++ TestMvcUtils.cs (working copy)
@@ -175,6 +175,32 @@
r => authorized.IsInRole(r)));
}
+ private System.Security.Principal.WindowsPrincipal getWindowsPrincipal() {
+ return
+ new System.Security.Principal.WindowsPrincipal(
+ System.Security.Principal.WindowsIdentity.GetCurrent());
+
+ }
+
+ [Test]
+ public void WindowsPrincipalIsInRoleExpectsDomain() {
+ var p = getWindowsPrincipal();
+ try {
+ p.IsInRole("whatever");
+ Assert.Fail("maybe we can simplify MvcUtils.IsInRole after all");
+ }
+ catch (SystemException) {
+ //expected
+ }
+ }
+
+ [Test]
+ public void IsInRoleToleratesAbsenceOfDomains() {
+ var p = getWindowsPrincipal();
+ MVCUtils.IsInRole(p, @"domain\whatever");
+ MVCUtils.IsInRole(p, "whatever@domain");
+ }
+
public Mock<httpcontextbase> mockContext(String applicationRoot, String requestUrl) {</httpcontextbase>
var appRoot = new Uri(applicationRoot);
var ctx = new Mock<httpcontextbase>();</httpcontextbase>
Index: Web.config
===================================================================
--- Web.config (revision 4851)
+++ Web.config (working copy)
@@ -12,8 +12,8 @@
<add key="ActiveDirectoryForestName" value="REDACTED"></add>
<add key="ActiveDirectoryUserName" value="REDACTED\svc.tahaze.websql"></add>
<add key="ActiveDirectoryPassword" value="REDACTED"></add>
- <add key="map-to-role-msl" value="REDACTED_Form_Editors"></add>
- <add key="map-to-role-admin" value="REDACTED_Admins"></add>
+ <add key="map-to-role-msl" value="REDACTED\REDACTED_Form_Editors"></add>
+ <add key="map-to-role-admin" value="REDACTED\REDACTED_Admins"></add>
<add key="MVCAuthnz.CookieAuthenticationHttpModule.signingKey" value="REDACTED"></add>
<add key="seanfoy.oopsnet.assemblyQualifiedName" value="seanfoy.oopsnet.ASPNetErrorHandler,seanfoy.oopsnet"></add>
<add key="seanfoy.oopsnet.handlerName" value="handleError"></add>
@@ -84,7 +84,6 @@
-->
-
<system.web></system.web>
<httpmodules></httpmodules>
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing"></add>
0. logout if you're not using Windows Authentication.
1. create a request
2. follow the link to the request from the assignment page
3. authenticate (implicitly or otherwise) using Windows Authentication
Expected results: request edit page
Actual results: authorization (IsInRole predicate evaluation on a
WindowsPrincipal) fails.
System.SystemException: The trust relationship between the primary domain and the trusted domain failed.
at
System.Security.Principal.NTAccount.TranslateToSids(IdentityReferenceCollection
sourceAccounts, Boolean& someFailed)
at System.Security.Principal.NTAccount.Translate(IdentityReferenceCollection
sourceAccounts, Type targetType, Boolean& someFailed)
at System.Security.Principal.NTAccount.Translate(IdentityReferenceCollection
sourceAccounts, Type targetType, Boolean forceSuccess)
at System.Security.Principal.WindowsPrincipal.IsInRole(String role)
at MVCUtils.IsInRole(String role) in
c:\documents-and-settings\sean.foy\my-documents\REDACTED\MvcUtils.cs:line 218
at REDACTED.REDACTED.IsInRole(String rolename) in
c:\documents-and-settings\sean.foy\my-documents\REDACTED\REDACTED.cs:line 284
at REDACTED.Viewee03e5938d284b4d944019fc1e9c8130.RenderViewLevel0() in
c:\documents-and-settings\sean.foy\my-documents\REDACTED\Views\Request\requestform.spark:line
20
at REDACTED.Viewee03e5938d284b4d944019fc1e9c8130.RenderView(TextWriter writer) in
c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET
Files\REDACTED\930b9661\fd62837e49004099a1fddf1e5288ae22-1.cs:line 1240
at Spark.Web.Mvc.SparkView.Render(ViewContext viewContext, TextWriter
writer)
at System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context)
at
System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext
controllerContext, ActionResult actionResult)
at
System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<invokeactionresultwithfilters>b__e()
at
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter
filter, ResultExecutingContext preContext, Func`1 continuation)
at
System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass11.<>c__DisplayClass13.<invokeactionresultwithfilters>b__10()
at
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext
controllerContext, IList`1 filters, ActionResult actionResult)
at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext
controllerContext, String actionName)
at System.Web.Mvc.Controller.ExecuteCore()
at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)
at
System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext
requestContext)
at System.Web.Mvc.MvcHandler.ProcessRequest(HttpContextBase httpContext)
at System.Web.Mvc.MvcHandler.ProcessRequest(HttpContext httpContext)
at
System.Web.Mvc.MvcHandler.System.Web.IHttpHandler.ProcessRequest(HttpContext
httpContext)
at
System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean&
completedSynchronously)
REDACTED, Version=1.0.4835.11, Culture=neutral, PublicKeyToken=null version
1.0.4835.11