Exposing the root cause of ActiveDirectoryRoleProvider errors
Have you ever had issues when connecting to a Windows Active Directory using the ActiveDirectoryRoleProvider? The Error message thrown at you can be somewhat descriptive but doesn't really give you any idea about what is actually causing the crash. Here are two exceptions I've seen in the past:
- [DirectoryServicesCOMException (0x80072030): There is no such object on the server. ]
- [DirectoryServicesCOMException (0x80072032): An invalid dn syntax has been specified. ]
The later is due to the fact that an "illegal" character is used in a DirectoryEntry, e.g. a decimal comma "," in a CN (username) or a backslash "\" in a group name. Johan Olofsson wrote a blog post on how to handle this, Some ActiveDirectoryRoleProvider issues.
There is no such object on the server
I just recently came across the "There is no such object on the server" error in a project once again so I thought I'd share a little remedy to the problem. It won't fix the actual problem but it will tell you what is causing it so that you can fix it in your Active Directory.
The error usually shows itself when you try to set access rights for a page or try to load groups in Admin mode. The GetAllRoles method in the ActiveDirectoryRoleProvider class gets executed and subsequent calls to CreateDirectoryDataFromDirectoryEntry are made. Here is part of the Stack Trace:
[DirectoryServicesCOMException (0x80072030): There is no such object on the server.]
System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail) +557
System.DirectoryServices.DirectoryEntry.Bind() +44
System.DirectoryServices.DirectoryEntry.get_AdsObject() +42
System.DirectoryServices.PropertyCollection.Contains(String propertyName) +29
EPiServer.Security.AdsiDataFactory.CreateDirectoryDataFromDirectoryEntry(DirectoryEntry entry) +216
EPiServer.Security.AdsiDataFactory.GetEntry(String distinguishedName) +137
EPiServer.Security.ActiveDirectoryRoleProvider.GetAllRoles() +184
There is no way to figure out which propertyName or DirectoryEntry is being sought for without catching the error a little bit more gracefully, so I extracted EPiServer's AdsiDataFactory with .NET Reflector and added some error handling to the CreateDirectoryDataFromDirectoryEntry method. I also changed the signature a bit passing in the distinguishedName so that the object (user or group) that was causing the error could be displayed in the error message.
1: protected DirectoryData CreateDirectoryDataFromDirectoryEntry(DirectoryEntry entry, string distinguishedName)
2: {
3: if (entry == null)
4: {
5: return null;
6: }
7: Dictionary<string, string[]> properties = new Dictionary<string, string[]>(this._propertiesToLoad.Count);
8: foreach (string str in this._propertiesToLoad)
9: {
10: try
11: {
12: if (entry.Properties.Contains(str))
13: {
14: PropertyValueCollection values = entry.Properties[str];
15: string[] strArray = new string[values.Count];
16: for (int i = 0; i < values.Count; i++)
17: {
18: strArray[i] = values[i].ToString();
19: }
20: properties.Add(str, strArray);
21: }
22: }
23: catch (DirectoryServicesCOMException ex)
24: {
25: StringBuilder sb = new StringBuilder();
26: foreach (string s in this._propertiesToLoad)
27: {
28: sb.Append(" | " + s);
29: }
30: throw new Exception("Error in CreateDirectoryDataFromDirectoryEntry!" + Environment.NewLine +
31: "Value of last str: " + str + Environment.NewLine +
32: "All Properties: " + sb.ToString() + Environment.NewLine +
33: "distinguishedName: " + distinguishedName, ex);
34: }
35: }
36: return new DirectoryData(this.DistinguishedName(properties), entry.SchemaClassName, properties);
37: }
With this try/catch block in place we now know the value of the last property and all other properties that were loaded including the DistinguishedName.
All that's missing now is to instantiate the "new" AdsiDataFactory with a new ADRoleProvider that inherits from EPiServer.Security. ActiveDirectoryRoleProvider and to change the roleManager section in web.config to use our new RoleProvider.
1: namespace MyWebSite.Security
2: {
3: public class ADRoleProvider : EPiServer.Security.ActiveDirectoryRoleProvider
4: {
5: public ADRoleProvider()
6: {
7: base.DirectoryDataFactory = new AdsiDataFactory();
8: }
9: }
10: }
<roleManager enabled="true"
defaultProvider="ActiveDirectoryRoleProvider"
cacheRolesInCookie="true">
<providers>
<clear />
<add name="ActiveDirectoryRoleProvider"
type="MyWebSite.Security.ADRoleProvider, MyWebSite.Security"
connectionStringName="ActiveDirectoryProviderConnection"
connectionUsername="username"
connectionPassword="password" />
</providers>
</roleManager>
Hope this helps anyone else having the same issues.
Here’s the complete code for the altered AdsiDataFactory with error handling in place (had some trouble attaching files :-) )
1: using System;
2: using System.Collections.Generic;
3: using System.Collections.Specialized;
4: using System.Configuration;
5: using System.Configuration.Provider;
6: using System.DirectoryServices;
7: using System.Text;
8: using System.Web;
9: using System.Web.Caching;
10: using System.Web.Configuration;
11: using EPiServer.Security;
12:
13: namespace MyWebSite.Security
14: {
15: public class AdsiDataFactory : DirectoryDataFactory
16: {
17: // Fields
18: private string _baseConnectionString;
19: private const string _cacheKeyEntryPrefix = "EPiServer:DirectoryServiceEntry:";
20: private const string _cacheKeyFindAllPrefix = "EPiServer:DirectoryServiceFindAll:";
21: private const string _cacheKeyFindOnePrefix = "EPiServer:DirectoryServiceFindOne:";
22: private TimeSpan _cacheTimeout;
23: private AuthenticationTypes _connectionProtection;
24: private string _connectionString;
25: private const string _distingushedNameAttribute = "distinguishedName";
26: private const string _objectClassAttribute = "objectClass";
27: private string _password;
28: private List<string> _propertiesToLoad;
29: private const string _reservedCharacters = "\r\n+\\\"<>;/";
30: private const string _rootCacheKey = "EPiServer:DirectoryServiceRoot";
31: private string _username;
32:
33: // Methods
34: public AdsiDataFactory()
35: {
36: }
37:
38: public AdsiDataFactory(string connectionString, string username, string password, AuthenticationTypes connectionProtection, TimeSpan absoluteCacheTimeout)
39: {
40: this._connectionString = connectionString;
41: this._username = username;
42: this._password = password;
43: this._connectionProtection = connectionProtection;
44: this._cacheTimeout = absoluteCacheTimeout;
45: this.Initialize();
46: }
47:
48: public override void AddPropertyToLoad(string propertyName)
49: {
50: if (!this._propertiesToLoad.Contains(propertyName))
51: {
52: this._propertiesToLoad.Add(propertyName);
53: this.ClearCache();
54: }
55: }
56:
57: public void ClearCache()
58: {
59: HttpRuntime.Cache.Remove("EPiServer:DirectoryServiceRoot");
60: }
61:
62: protected DirectoryData CreateDirectoryDataFromDirectoryEntry(DirectoryEntry entry, string distinguishedName)
63: {
64: if (entry == null)
65: {
66: return null;
67: }
68: Dictionary<string, string[]> properties = new Dictionary<string, string[]>(this._propertiesToLoad.Count);
69: foreach (string str in this._propertiesToLoad)
70: {
71: try
72: {
73: if (entry.Properties.Contains(str))
74: {
75: PropertyValueCollection values = entry.Properties[str];
76: string[] strArray = new string[values.Count];
77: for (int i = 0; i < values.Count; i++)
78: {
79: strArray[i] = values[i].ToString();
80: }
81: properties.Add(str, strArray);
82: }
83: }
84: catch (DirectoryServicesCOMException ex)
85: {
86: StringBuilder sb = new StringBuilder();
87: foreach (string s in this._propertiesToLoad)
88: {
89: sb.Append(" | " + s);
90: }
91:
92: throw new Exception("Error in CreateDirectoryDataFromDirectoryEntry!" + Environment.NewLine +
93: "Value of last str: " + str + Environment.NewLine +
94: "All Properties: " + sb.ToString() + Environment.NewLine +
95: "distinguishedName: " + distinguishedName, ex);
96: }
97: }
98: return new DirectoryData(this.DistinguishedName(properties), entry.SchemaClassName, properties);
99: }
100:
101: protected DirectoryData CreateDirectoryDataFromSearchResult(SearchResult result)
102: {
103: if (result == null)
104: {
105: return null;
106: }
107: Dictionary<string, string[]> properties = new Dictionary<string, string[]>(this._propertiesToLoad.Count);
108: foreach (string str in this._propertiesToLoad)
109: {
110: if (result.Properties.Contains(str))
111: {
112: ResultPropertyValueCollection values = result.Properties[str];
113: string[] strArray = new string[values.Count];
114: for (int i = 0; i < values.Count; i++)
115: {
116: strArray[i] = values[i].ToString();
117: }
118: properties.Add(str, strArray);
119: }
120: }
121: return new DirectoryData(this.DistinguishedName(properties), this.SchemaClassName(properties), properties);
122: }
123:
124: protected DirectoryEntry CreateDirectoryEntry()
125: {
126: return new DirectoryEntry(this._connectionString, this._username, this._password, this._connectionProtection);
127: }
128:
129: protected DirectoryEntry CreateDirectoryEntry(string rootDistinguishedName)
130: {
131: if (!base.IsWithinSubtree(rootDistinguishedName))
132: {
133: return null;
134: }
135: return new DirectoryEntry(this._baseConnectionString + this.EscapeDistinguishedName(rootDistinguishedName), this._username, this._password, this._connectionProtection);
136: }
137:
138: protected string DistinguishedName(Dictionary<string, string[]> properties)
139: {
140: return properties["distinguishedName"][0];
141: }
142:
143: protected string EscapeDistinguishedName(string distinguishedName)
144: {
145: StringBuilder builder = new StringBuilder(distinguishedName.Length);
146: foreach (char ch in distinguishedName)
147: {
148: if (_reservedCharacters.IndexOf(ch) >= 0)
149: {
150: builder.Append('\\');
151: }
152: builder.Append(ch);
153: }
154: return builder.ToString();
155: }
156:
157: public override IList<DirectoryData> FindAll(string filter, SearchScope scope, string sortByProperty)
158: {
159: string cacheKey = "EPiServer:DirectoryServiceFindAll:" + filter + scope.ToString();
160: IList<DirectoryData> values = (IList<DirectoryData>)HttpRuntime.Cache[cacheKey];
161: if (values == null)
162: {
163: using (DirectorySearcher searcher = new DirectorySearcher(this.CreateDirectoryEntry(), filter, this._propertiesToLoad.ToArray(), scope))
164: {
165: using (SearchResultCollection results = searcher.FindAll())
166: {
167: if (results == null)
168: {
169: return null;
170: }
171: if (sortByProperty == null)
172: {
173: values = new List<DirectoryData>(results.Count);
174: foreach (SearchResult result in results)
175: {
176: values.Add(this.CreateDirectoryDataFromSearchResult(result));
177: }
178: }
179: else
180: {
181: SortedList<string, DirectoryData> list2 = new SortedList<string, DirectoryData>(results.Count);
182: foreach (SearchResult result2 in results)
183: {
184: DirectoryData data = this.CreateDirectoryDataFromSearchResult(result2);
185: list2.Add(data.GetFirstPropertyValue(sortByProperty), data);
186: }
187: values = list2.Values;
188: }
189: }
190: }
191: this.StoreInCache(cacheKey, values);
192: }
193: return values;
194: }
195:
196: public override DirectoryData FindOne(string filter, SearchScope scope)
197: {
198: string cacheKey = "EPiServer:DirectoryServiceFindOne:" + filter + scope.ToString();
199: DirectoryData data = (DirectoryData)HttpRuntime.Cache[cacheKey];
200: if (data == null)
201: {
202: using (DirectorySearcher searcher = new DirectorySearcher(this.CreateDirectoryEntry(), filter, this._propertiesToLoad.ToArray(), scope))
203: {
204: data = this.CreateDirectoryDataFromSearchResult(searcher.FindOne());
205: if (data == null)
206: {
207: return null;
208: }
209: }
210: this.StoreInCache(cacheKey, data);
211: }
212: return data;
213: }
214:
215: public override DirectoryData GetEntry(string distinguishedName)
216: {
217: string cacheKey = "EPiServer:DirectoryServiceEntry:" + distinguishedName;
218: DirectoryData data = (DirectoryData)HttpRuntime.Cache[cacheKey];
219: if (data == null)
220: {
221: using (DirectoryEntry entry = this.CreateDirectoryEntry(distinguishedName))
222: {
223: data = this.CreateDirectoryDataFromDirectoryEntry(entry, distinguishedName);
224: }
225: if (data != null)
226: {
227: this.StoreInCache(cacheKey, data);
228: }
229: }
230: return data;
231: }
232:
233: private void GetParametersFromConfig(NameValueCollection config)
234: {
235: string str;
236: string str2;
237: string str3;
238: if (!this.TryGetDestructive(config, "connectionStringName", out str))
239: {
240: throw new ProviderException("Required attribute connectionStringName not supplied.");
241: }
242: ConnectionStringSettings settings = WebConfigurationManager.ConnectionStrings[str];
243: if (settings == null)
244: {
245: throw new ProviderException(string.Format("Connection string {0} not found.", str));
246: }
247: this._connectionString = settings.ConnectionString;
248: if (string.IsNullOrEmpty(this._connectionString))
249: {
250: throw new ProviderException(string.Format("Connection string {0} is empty.", str));
251: }
252: if (!this.TryGetDestructive(config, "connectionUsername", out this._username))
253: {
254: throw new ProviderException("Required attribute connectionUsername not supplied.");
255: }
256: if (!this.TryGetDestructive(config, "connectionPassword", out this._password))
257: {
258: throw new ProviderException("Required attribute connectionPassword not supplied.");
259: }
260: this._connectionProtection = AuthenticationTypes.Secure;
261: if (this.TryGetDestructive(config, "connectionProtection", out str2))
262: {
263: try
264: {
265: this._connectionProtection = (AuthenticationTypes)Enum.Parse(typeof(AuthenticationTypes), str2, true);
266: }
267: catch (ArgumentException)
268: {
269: throw new ProviderException(string.Format("Attribute connectionProtection has illegal value {0}, supported values are {1}.", str2, string.Join(", ", Enum.GetNames(typeof(AuthenticationTypes)))));
270: }
271: }
272: if (this.TryGetDestructive(config, "cacheTimeout", out str3))
273: {
274: if (!TimeSpan.TryParse(str3, out this._cacheTimeout))
275: {
276: throw new ProviderException(string.Format("Attribute cacheTimeout has illegal value {0}, should be formatted as \"hours:minutes:seconds\"", str3));
277: }
278: }
279: else
280: {
281: this._cacheTimeout = new TimeSpan(0, 10, 0);
282: }
283: }
284:
285: private void Initialize()
286: {
287: this._propertiesToLoad = new List<string>(5);
288: this._propertiesToLoad.Add("distinguishedName");
289: this._propertiesToLoad.Add("objectClass");
290: int index = this._connectionString.IndexOf("://");
291: if (index < 0)
292: {
293: throw new ProviderException(string.Format("Protocol specification missing from connection string {0}", this._connectionString));
294: }
295: int num2 = this._connectionString.IndexOf("/", (int)(index + 3));
296: if (num2 < 0)
297: {
298: this._baseConnectionString = this._connectionString + "/";
299: }
300: else if ((num2 + 1) < this._connectionString.Length)
301: {
302: this._baseConnectionString = this._connectionString.Remove(num2 + 1);
303: }
304: else
305: {
306: this._baseConnectionString = this._connectionString;
307: }
308: using (DirectoryEntry entry = this.CreateDirectoryEntry())
309: {
310: base.RootDistinguishedName = entry.Properties["distinguishedName"][0].ToString();
311: }
312: }
313:
314: public override void Initialize(NameValueCollection config)
315: {
316: this.GetParametersFromConfig(config);
317: this.Initialize();
318: }
319:
320: protected string SchemaClassName(Dictionary<string, string[]> properties)
321: {
322: string str = string.Empty;
323: string[] strArray = properties["objectClass"];
324: if (strArray != null)
325: {
326: str = strArray[strArray.Length - 1];
327: }
328: return str;
329: }
330:
331: protected void StoreInCache(string cacheKey, object data)
332: {
333: if (HttpRuntime.Cache["EPiServer:DirectoryServiceRoot"] == null)
334: {
335: HttpRuntime.Cache.Insert("EPiServer:DirectoryServiceRoot", new object());
336: }
337: HttpRuntime.Cache.Insert(cacheKey, data, new CacheDependency(null, new string[] { "EPiServer:DirectoryServiceRoot" }), DateTime.Now.Add(this._cacheTimeout), Cache.NoSlidingExpiration);
338: }
339:
340: protected bool TryGetDestructive(NameValueCollection config, string name, out string value)
341: {
342: value = config[name];
343: if (value != null)
344: {
345: config.Remove(name);
346: }
347: if (string.IsNullOrEmpty(value))
348: {
349: value = null;
350: return false;
351: }
352: return true;
353: }
354:
355: // Properties
356: public AuthenticationTypes ConnectionProtection
357: {
358: get
359: {
360: return this._connectionProtection;
361: }
362: protected set
363: {
364: this._connectionProtection = value;
365: }
366: }
367:
368: public string ConnectionString
369: {
370: get
371: {
372: return this._connectionString;
373: }
374: protected set
375: {
376: this._connectionString = value;
377: }
378: }
379:
380: public string Password
381: {
382: get
383: {
384: return this._password;
385: }
386: protected set
387: {
388: this._password = value;
389: }
390: }
391:
392: public string Username
393: {
394: get
395: {
396: return this._username;
397: }
398: protected set
399: {
400: this._username = value;
401: }
402: }
403: }
404:
405: }
Thank's alot for this piece of code, i found a wierd group in our own domain which failed to load. And three groups in our customers domain. But these groups didn't cause any problems in CMS 5.2, only in CMS 6, which the current site was upgraded to. We didn't experience any exceptions in the ActiveDirectoryProvider before upgrading!
What is the solution for the problem with "[DirectoryServicesCOMException (0x80072032): An invalid dn syntax has been specified. ]"? Johan's articel is unfortunately not available...