diff --git a/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryConfiguration.cs b/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryConfiguration.cs
new file mode 100644
index 0000000..e54df96
--- /dev/null
+++ b/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryConfiguration.cs
@@ -0,0 +1,170 @@
+/*
+ * ====================
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of the Common Development
+ * and Distribution License("CDDL") (the "License"). You may not use this file
+ * except in compliance with the License.
+ *
+ * You can obtain a copy of the License at
+ * http://IdentityConnectors.dev.java.net/legal/license.txt
+ * See the License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * When distributing the Covered Code, include this CDDL Header Notice in each file
+ * and include the License file at identityconnectors/legal/license.txt.
+ * If applicable, add the following below this CDDL Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ * ====================
+ */
+using System;
+using System.Text;
+using Org.IdentityConnectors.Framework.Spi;
+using Org.IdentityConnectors.Framework.Common.Exceptions;
+using Org.IdentityConnectors.Framework.Spi.Operations;
+
+namespace Org.IdentityConnectors.ActiveDirectory
+{
+ public class ActiveDirectoryConfiguration : Org.IdentityConnectors.Framework.Spi.AbstractConfiguration
+ {
+ [ConfigurationProperty(Confidential = false, DisplayMessageKey = "display_DirectoryAdminName",
+ Required = true, HelpMessageKey = "help_DirectoryAdminName", Order = 1)]
+ public String DirectoryAdminName
+ { get; set; }
+
+ [ConfigurationProperty(Confidential = true, DisplayMessageKey = "display_DirectoryAdminPassword",
+ Required = true, HelpMessageKey = "help_DirectoryAdminPassword", Order = 2)]
+ public String DirectoryAdminPassword
+ { get; set; }
+
+ [ConfigurationProperty(Confidential = false, DisplayMessageKey = "display_ObjectClass", HelpMessageKey = "help_ObjectClass", Order = 3)]
+ public String ObjectClass
+ { get; set; }
+
+ [ConfigurationProperty(Confidential = false, DisplayMessageKey = "display_Container",
+ Required=true, HelpMessageKey = "help_Container", Order = 4)]
+ public String Container
+ { get; set; }
+
+ [ConfigurationProperty(Confidential = false, DisplayMessageKey = "display_CreateHomeDirectory", HelpMessageKey = "help_CreateHomeDirectory", Order = 5)]
+ public bool CreateHomeDirectory { get; set; }
+
+ [ConfigurationProperty(Confidential = false, DisplayMessageKey = "display_LDAPHostName", HelpMessageKey = "help_LDAPHostName", Order = 6)]
+ public String LDAPHostName
+ { get; set; }
+
+ [ConfigurationProperty(Confidential = false, DisplayMessageKey = "display_SearchChildDomains", HelpMessageKey = "help_SearchChildDomains", Order = 7)]
+ public bool SearchChildDomains { get; set; }
+
+ [ConfigurationProperty(Confidential = false, DisplayMessageKey = "display_domainName",
+ Required = true, HelpMessageKey = "help_domainName", Order = 8)]
+ public String DomainName
+ { get; set; }
+
+ [ConfigurationProperty(OperationTypes = new Type[] { typeof(SyncOp) }, Confidential = false, DisplayMessageKey = "display_SyncGlobalCatalogServer", HelpMessageKey = "help_SyncGlobalCatalogServer", Order = 9)]
+ public String SyncGlobalCatalogServer
+ { get; set; }
+
+ [ConfigurationProperty(OperationTypes = new Type[] { typeof(SyncOp) }, Confidential = false, DisplayMessageKey = "display_SyncDomainController", HelpMessageKey = "help_SyncDomainController", Order=10)]
+ public String SyncDomainController
+ { get; set; }
+
+ private string _searchContext = string.Empty;
+ [ConfigurationProperty(Confidential = false, DisplayMessageKey = "display_SearchContext", HelpMessageKey = "help_SearchContext", Order=11)]
+ public String SearchContext
+ {
+ get
+ {
+ if((_searchContext == null) || (_searchContext.Length == 0))
+ {
+ return Container;
+ }
+ return _searchContext;
+ }
+
+ set
+ {
+ _searchContext = value;
+ }
+ }
+
+ public ActiveDirectoryConfiguration()
+ {
+ DomainName = "";
+ Container = "";
+ DirectoryAdminName = "administrator";
+ ObjectClass = "User";
+ CreateHomeDirectory = true;
+ SearchChildDomains = false;
+ LDAPHostName = "";
+ }
+
+ ///
+ /// Determines if the configuration is valid.
+ ///
+ /// See for the definition of a valid
+ /// configuration.
+ ///
+ /// Thrown when the configuration is not valid.
+ public override void Validate()
+ {
+ var message = new StringBuilder();
+
+ Boolean foundError = false;
+
+ // can't lookup the schema without the domain name
+ if ((DomainName == null) || (DomainName.Length == 0))
+ {
+ message.Append(ConnectorMessages.Format(
+ "confReqParam_domainName", "Domain name not supplied "));
+ foundError = true;
+ }
+
+ if ((DirectoryAdminName == null) || (DirectoryAdminName.Length == 0))
+ {
+ message.Append(ConnectorMessages.Format(
+ "confReqParam_adminName", "Directory administrator name not supplied "));
+ foundError = true;
+ }
+
+ if ((DirectoryAdminPassword == null) || (DirectoryAdminPassword.Length == 0))
+ {
+ message.Append(ConnectorMessages.Format(
+ "confReqParam_adminPass", "Directory administrator password not supplied "));
+ foundError = true;
+ }
+
+ if ((ObjectClass == null) || (ObjectClass.Length == 0))
+ {
+ message.Append(ConnectorMessages.Format(
+ "confReqParam_objClass", "ObjectClass was not supplied "));
+ foundError = true;
+ }
+
+ if (string.IsNullOrEmpty(Container))
+ {
+ message.Append(ConnectorMessages.Format(
+ "confReqParam_Container", "Container was not supplied "));
+ foundError = true;
+ }
+ else
+ {
+ if (!ActiveDirectoryUtils.IsValidDn(Container))
+ {
+ message.Append( ConnectorMessages.Format(
+ "confParam_Container_invalid_path", @"Container '{0}' could not be recognized as a distinguished name (DN) ", Container ) );
+ foundError = true;
+ }
+ }
+
+ if (foundError)
+ {
+ throw new ConfigurationException( ConnectorMessages.Format(
+ "ex_ConfigErrors", "Configuration errors: {0}", message.ToString() ) );
+ }
+ }
+ }
+}
diff --git a/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryConnector.cs b/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryConnector.cs
new file mode 100644
index 0000000..1666806
--- /dev/null
+++ b/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryConnector.cs
@@ -0,0 +1,1266 @@
+/*
+ * ====================
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of the Common Development
+ * and Distribution License("CDDL") (the "License"). You may not use this file
+ * except in compliance with the License.
+ *
+ * You can obtain a copy of the License at
+ * http://IdentityConnectors.dev.java.net/legal/license.txt
+ * See the License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * When distributing the Covered Code, include this CDDL Header Notice in each file
+ * and include the License file at identityconnectors/legal/license.txt.
+ * If applicable, add the following below this CDDL Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ * ====================
+ * Portions Copyrighted 2012-2014 ForgeRock AS.
+ */
+using System;
+using System.Reflection;
+using System.Collections.Generic;
+using Org.IdentityConnectors.Common;
+using Org.IdentityConnectors.Framework.Spi;
+using Org.IdentityConnectors.Framework.Spi.Operations;
+using System.Diagnostics;
+using Org.IdentityConnectors.Framework.Common.Objects;
+using Org.IdentityConnectors.Framework.Common.Exceptions;
+using System.DirectoryServices;
+using DS = System.DirectoryServices;
+using System.DirectoryServices.ActiveDirectory;
+using System.Text;
+using Org.IdentityConnectors.Common.Script;
+using System.Globalization;
+
+namespace Org.IdentityConnectors.ActiveDirectory
+{
+ public enum UpdateType
+ {
+ ADD,
+ DELETE,
+ REPLACE
+ }
+
+
+ ///
+ /// The Active Directory Connector
+ ///
+ [ConnectorClass("connector_displayName",
+ typeof(ActiveDirectoryConfiguration),
+ MessageCatalogPaths = new String[] { "Org.IdentityConnectors.ActiveDirectory.Messages" }
+ )]
+ public class ActiveDirectoryConnector : CreateOp, Connector, SchemaOp, DeleteOp,
+ SearchOp, TestOp, UpdateAttributeValuesOp, ScriptOnResourceOp, SyncOp,
+ AuthenticateOp, PoolableConnector
+ {
+ public static IDictionary> AttributesReturnedByDefault = null;
+
+ // special attribute names
+ public static readonly string ATT_CONTAINER = "ad_container";
+ public static readonly string ATT_USER_PASSWORD = "userPassword";
+ public static readonly string ATT_CN = "cn";
+ public static readonly string ATT_OU = "ou";
+ public static readonly string ATT_OBJECT_GUID = "objectGuid";
+ public static readonly string ATT_IS_DELETED = "isDeleted";
+ public static readonly string ATT_USN_CHANGED = "uSNChanged";
+ public static readonly string ATT_DISTINGUISHED_NAME = "distinguishedName";
+ public static readonly string ATT_SAMACCOUNT_NAME = "sAMAccountName";
+ public static readonly string ATT_MEMBER = "member";
+ public static readonly string ATT_MEMBEROF = "memberOf";
+ public static readonly string ATT_HOME_DIRECTORY = "homeDirectory";
+ public static readonly string ATT_OBJECT_SID = "objectSid";
+ public static readonly string ATT_PWD_LAST_SET = "pwdLastSet";
+ public static readonly string ATT_ACCOUNT_EXPIRES = "accountExpires";
+ public static readonly string ATT_LOCKOUT_TIME = "lockoutTime";
+ public static readonly string ATT_GROUP_TYPE = "groupType";
+ public static readonly string ATT_DESCRIPTION = "description";
+ public static readonly string ATT_SHORT_NAME = "name";
+ public static readonly string ATT_DISPLAY_NAME = "displayName";
+ public static readonly string ATT_USER_ACOUNT_CONTROL = "userAccountControl";
+ public static readonly string ATT_PASSWORD_NEVER_EXPIRES = "PasswordNeverExpires";
+ public static readonly string ATT_ACCOUNTS = ConnectorAttributeUtil.CreateSpecialName("ACCOUNTS");
+ public static readonly string OBJECTCLASS_OU = "organizationalUnit";
+ public static readonly string OBJECTCLASS_GROUP = "Group";
+ public static readonly string OPTION_DOMAIN = "w2k_domain";
+ public static readonly string OPTION_RETURN_UID_ONLY = "returnUidOnly";
+
+ public static readonly ObjectClass ouObjectClass = new ObjectClass(OBJECTCLASS_OU);
+ public static readonly ObjectClass groupObjectClass = new ObjectClass(OBJECTCLASS_GROUP);
+
+ private static readonly string OLD_SEARCH_FILTER_STRING = "Search Filter String";
+ private static readonly string OLD_SEARCH_FILTER = "searchFilter";
+
+ ActiveDirectoryConfiguration _configuration = null;
+ ActiveDirectoryUtils _utils = null;
+ private static Schema _schema = null;
+ private DirectoryEntry _dirHandler = null;
+ //private DirectorySearcher searcher = null;
+ public ActiveDirectoryConnector()
+ {
+ // populate default attributes and Schema
+ Schema();
+ }
+
+ #region CreateOp Members
+ // implementation of CreateSpiOp
+ public virtual Uid Create(ObjectClass oclass,
+ ICollection attributes, OperationOptions options)
+ {
+ Uid uid = null;
+ bool created = false;
+ DirectoryEntry containerDe = null;
+ DirectoryEntry newDe = null;
+
+ // I had lots of problems here. Here are the things
+ // that seemed to make everything work:
+ // - Create the object with the minimum data and commit it,
+ // then update the object with the rest of the info.
+ // - After updating an object and committing, be sure to
+ // do a refresh cache before continuing to use it. If
+ // not, it seems like multi-value attributes get hosed.
+ // - Group membership cannot be change by memberOf, but must
+ // be changed by changing the members property of the group
+
+ Trace.TraceInformation("Create method");
+ if (_configuration == null)
+ {
+ throw new ConfigurationException(_configuration.ConnectorMessages.Format(
+ "ex_ConnectorNotConfigured", "Connector has not been configured"));
+ }
+ Name nameAttribute = ConnectorAttributeUtil.GetNameFromAttributes(attributes);
+ if (nameAttribute == null)
+ {
+ throw new ConnectorException(
+ _configuration.ConnectorMessages.Format("ex_OperationalAttributeNull",
+ "The name operational attribute cannot be null"));
+ }
+
+ String ldapContainerPath = ActiveDirectoryUtils.GetLDAPPath(_configuration.LDAPHostName,
+ ActiveDirectoryUtils.GetParentDn(nameAttribute.GetNameValue()));
+ String ldapEntryPath = ActiveDirectoryUtils.GetLDAPPath(_configuration.LDAPHostName,
+ nameAttribute.GetNameValue());
+
+ try
+ {
+ if (!DirectoryEntry.Exists(ldapContainerPath))
+ {
+ throw new ConnectorException("Container does not exist");
+ }
+
+ // Get the correct container, and put the new user in it
+ containerDe = new DirectoryEntry(ldapContainerPath,
+ _configuration.DirectoryAdminName, _configuration.DirectoryAdminPassword);
+ newDe = containerDe.Children.Add(
+ ActiveDirectoryUtils.GetRelativeName(nameAttribute),
+ _utils.GetADObjectClass(oclass));
+
+ if (oclass.Equals(ActiveDirectoryConnector.groupObjectClass))
+ {
+ ConnectorAttribute groupAttribute =
+ ConnectorAttributeUtil.Find(ActiveDirectoryConnector.ATT_GROUP_TYPE, attributes);
+ if (groupAttribute != null)
+ {
+ int? groupType = ConnectorAttributeUtil.GetIntegerValue(groupAttribute);
+ if (groupType.HasValue)
+ {
+ newDe.Properties[ActiveDirectoryConnector.ATT_GROUP_TYPE].Value = groupType;
+ }
+ }
+ }
+
+ newDe.CommitChanges();
+ created = true;
+ // default to creating users enabled
+ if ((ObjectClass.ACCOUNT.Equals(oclass)) &&
+ (ConnectorAttributeUtil.Find(OperationalAttributes.ENABLE_NAME, attributes) == null))
+ {
+ ICollection temp = new HashSet(attributes);
+ temp.Add(ConnectorAttributeBuilder.Build(OperationalAttributes.ENABLE_NAME, true));
+ _utils.UpdateADObject(oclass, newDe, temp, UpdateType.REPLACE, _configuration);
+ }
+ else
+ {
+ _utils.UpdateADObject(oclass, newDe, attributes, UpdateType.REPLACE, _configuration);
+ }
+ Object guidValue = newDe.Properties["objectGUID"].Value;
+ if (guidValue != null)
+ {
+ // format the uid in the special way required for searching
+ String guidString =
+ ActiveDirectoryUtils.ConvertUIDBytesToGUIDString(
+ (Byte[])guidValue);
+
+ Trace.TraceInformation("Created object with uid {0}", guidString);
+ uid = new Uid(guidString);
+ }
+ else
+ {
+ Trace.TraceError("Unable to find uid attribute for newly created object");
+ }
+
+
+ }
+ catch (DirectoryServicesCOMException exception)
+ {
+ // have to make sure the new thing gets deleted in
+ // the case of error
+ Console.WriteLine("caught exception:" + exception);
+ Trace.TraceError(exception.Message);
+ if (created)
+ {
+ // In the case of an exception, make sure we
+ // don't leave any partial objects around
+ newDe.DeleteTree();
+ }
+ throw;
+ }
+ catch (Exception exception)
+ {
+ Console.WriteLine("caught exception:" + exception);
+ Trace.TraceError(exception.Message);
+ if (created)
+ {
+ // In the case of an exception, make sure we
+ // don't leave any partial objects around
+ newDe.DeleteTree();
+ }
+ throw;
+ }
+ finally
+ {
+ if (containerDe != null)
+ {
+ containerDe.Dispose();
+ }
+ if (newDe != null)
+ {
+ newDe.Dispose();
+ }
+ }
+ return uid;
+ }
+
+ #endregion
+
+ #region Connector Members
+
+ // implementation of Connector
+ public virtual void Init(Configuration configuration)
+ {
+ Trace.TraceInformation("Active Directory Init method");
+ configuration.Validate();
+ _configuration = (ActiveDirectoryConfiguration)configuration;
+ _utils = new ActiveDirectoryUtils(_configuration);
+
+ // since we are a poolable connector, let's establish a persistent connection to AD
+ bool useGC = false;
+ if (_configuration.SearchChildDomains)
+ {
+ useGC = true;
+ }
+ string path = GetSearchContainerPath(useGC, _configuration.LDAPHostName, _configuration.Container);
+ Trace.TraceInformation("Search: Getting root node for search");
+ _dirHandler = new DirectoryEntry(path, _configuration.DirectoryAdminName, _configuration.DirectoryAdminPassword);
+ //searcher = new DirectorySearcher(_dirHandler);
+ }
+
+ #endregion
+
+ #region IDisposable Members
+
+ public virtual void Dispose()
+ {
+ if (_dirHandler != null)
+ {
+ _dirHandler.Dispose();
+ }
+ }
+
+ #endregion
+
+ protected ICollection GetDefaultAttributeListForObjectClass(
+ ObjectClass oclass, ObjectClassInfo oclassInfo)
+ {
+ ICollection defaultAttributeList = new List();
+
+ foreach (ConnectorAttributeInfo attInfo in oclassInfo.ConnectorAttributeInfos)
+ {
+ if (attInfo.IsReturnedByDefault)
+ {
+ defaultAttributeList.Add(attInfo.Name);
+ }
+ }
+
+ return defaultAttributeList;
+ }
+
+ #region SchemaOp Members
+ // implementation of SchemaSpiOp
+ public Schema Schema()
+ {
+ Trace.TraceInformation("Schema method");
+ if (_schema != null)
+ {
+ Trace.TraceInformation("Returning cached schema");
+ return _schema;
+ }
+
+ SchemaBuilder schemaBuilder =
+ new SchemaBuilder(SafeType.Get(this));
+ AttributesReturnedByDefault = new Dictionary>();
+
+ //iterate through supported object classes
+ foreach (ObjectClass oc in GetSupportedObjectClasses())
+ {
+ ObjectClassInfo ocInfo = GetObjectClassInfo(oc);
+ Assertions.NullCheck(ocInfo, "ocInfo");
+
+ //populate the list of default attributes to get
+ AttributesReturnedByDefault.Add(oc, new HashSet());
+ foreach (ConnectorAttributeInfo caInfo in ocInfo.ConnectorAttributeInfos)
+ {
+ if (caInfo.IsReturnedByDefault)
+ {
+ AttributesReturnedByDefault[oc].Add(caInfo.Name);
+ }
+ }
+
+ //add object class to schema
+ schemaBuilder.DefineObjectClass(ocInfo);
+
+ //add supported operations
+ IList> supportedOps = GetSupportedOperations(oc);
+ if (supportedOps != null)
+ {
+ foreach (SafeType op in supportedOps)
+ {
+ schemaBuilder.AddSupportedObjectClass(op, ocInfo);
+ }
+ }
+
+ //remove unsupported operatons
+ IList> unSupportedOps = GetUnSupportedOperations(oc);
+ if (unSupportedOps != null)
+ {
+ foreach (SafeType op in unSupportedOps)
+ {
+ schemaBuilder.RemoveSupportedObjectClass(op, ocInfo);
+ }
+ }
+ }
+ Trace.TraceInformation("Finished retrieving schema");
+ _schema = schemaBuilder.Build();
+ Trace.TraceInformation("Returning schema");
+
+ return _schema;
+ }
+
+ ///
+ /// Defines the supported object classes by the connector, used for schema building
+ ///
+ /// List of supported object classes
+ protected virtual ICollection GetSupportedObjectClasses()
+ {
+ IDictionary objectClassInfos =
+ CommonUtils.GetOCInfo("Org.IdentityConnectors.ActiveDirectory.ObjectClasses.xml");
+
+ return objectClassInfos.Keys;
+ }
+
+ ///
+ /// Gets the object class info for specified object class, used for schema building
+ ///
+ /// ObjectClass to get info for
+ /// ObjectClass' ObjectClassInfo
+ protected virtual ObjectClassInfo GetObjectClassInfo(ObjectClass oc)
+ {
+ IDictionary objectClassInfos =
+ CommonUtils.GetOCInfo("Org.IdentityConnectors.ActiveDirectory.ObjectClasses.xml");
+
+ return objectClassInfos[oc];
+ }
+
+ ///
+ /// Gets the list of supported operations by the object class, used for schema building
+ ///
+ ///
+ ///
+ protected virtual IList> GetSupportedOperations(ObjectClass oc)
+ {
+ return null;
+ }
+
+ ///
+ /// Gets the list of UNsupported operations by the object class, used for schema building
+ ///
+ ///
+ ///
+ protected virtual IList> GetUnSupportedOperations(ObjectClass oc)
+ {
+ if (oc.Equals(ActiveDirectoryConnector.groupObjectClass) || oc.Equals(ouObjectClass))
+ {
+ return new List> {
+ SafeType.Get(),
+ SafeType.Get()};
+ }
+
+ return null;
+ }
+
+ #endregion
+
+ #region SearchOp Members
+
+ // implementation of SearchSpiOp
+ public virtual Org.IdentityConnectors.Framework.Common.Objects.Filters.FilterTranslator CreateFilterTranslator(ObjectClass oclass, OperationOptions options)
+ {
+ return new ActiveDirectoryFilterTranslator();
+ }
+
+ // implementation of SearchSpiOp
+ public virtual void ExecuteQuery(ObjectClass oclass, string query,
+ ResultsHandler handler, OperationOptions options)
+ {
+ try
+ {
+ bool useGC = false;
+ if (_configuration.SearchChildDomains)
+ {
+ useGC = true;
+ }
+
+ IDictionary searchOptions = options.Options;
+
+ SearchScope searchScope = GetADSearchScopeFromOptions(options);
+ string searchContainer = GetADSearchContainerFromOptions(options);
+
+ // for backward compatibility, support old query style from resource adapters
+ // but log a warning
+ if ((query == null) || (query.Length == 0))
+ {
+ if ((options != null) && (options.Options != null))
+ {
+ Object oldStyleQuery = null;
+ if (options.Options.Keys.Contains(OLD_SEARCH_FILTER_STRING))
+ {
+ oldStyleQuery = options.Options[OLD_SEARCH_FILTER_STRING];
+ }
+ else if (options.Options.Keys.Contains(OLD_SEARCH_FILTER))
+ {
+ oldStyleQuery = options.Options[OLD_SEARCH_FILTER];
+ }
+ if ((oldStyleQuery != null) && (oldStyleQuery is string))
+ {
+ query = (string)oldStyleQuery;
+ Trace.TraceWarning(_configuration.ConnectorMessages.Format(
+ "warn_CompatibilityModeQuery",
+ "Using Identity Manger Resource Adapter style query ''{0}''. This should be updated to use the new connector query syntax.",
+ ((query != null) && (query.Length > 0)) ? query : ""));
+ }
+ }
+ }
+
+ ExecuteQuery(oclass, query, handler, options,
+ false, null, _configuration.LDAPHostName, useGC, searchContainer, searchScope);
+ }
+ catch (Exception e)
+ {
+ Trace.TraceError(String.Format("Caught Exception: {0}", e));
+ throw;
+ }
+ }
+
+ public string GetADSearchContainerFromOptions(OperationOptions options)
+ {
+ if (options != null)
+ {
+ QualifiedUid qUid = options.getContainer;
+ if (qUid != null)
+ {
+ return ConnectorAttributeUtil.GetStringValue(qUid.Uid);
+ }
+ }
+
+ return _configuration.Container;
+ }
+
+ public SearchScope GetADSearchScopeFromOptions(OperationOptions options)
+ {
+ if (options != null)
+ {
+ string scope = options.Scope;
+ if (scope != null)
+ {
+ if (scope.Equals(OperationOptions.SCOPE_ONE_LEVEL))
+ {
+ return SearchScope.OneLevel;
+ }
+ else if (scope.Equals(OperationOptions.SCOPE_SUBTREE))
+ {
+ return SearchScope.Subtree;
+ }
+ else if (scope.Equals(OperationOptions.SCOPE_OBJECT))
+ {
+ return SearchScope.Base;
+ }
+ else
+ {
+ throw new ConnectorException(_configuration.ConnectorMessages.Format(
+ "ex_invalidSearchScope", "An invalid search scope was specified: {0}", scope));
+ }
+ }
+ }
+
+ // default value is subtree;
+ return SearchScope.Subtree;
+ }
+
+ // this is used by the ExecuteQuery method of SearchSpiOp, and
+ // by the SyncSpiOp
+ private void ExecuteQuery(ObjectClass oclass, string query,
+ ResultsHandler handler, OperationOptions options, bool includeDeleted,
+ SortOption sortOption, string serverName, bool useGlobalCatalog,
+ string searchRoot, SearchScope searchScope)
+ {
+ Trace.TraceInformation("Search: modifying query");
+ StringBuilder fullQueryBuilder = new StringBuilder();
+ if (query == null)
+ {
+ fullQueryBuilder.Append("(objectclass=");
+ fullQueryBuilder.Append(_utils.GetADObjectClass(oclass));
+ fullQueryBuilder.Append(")");
+ }
+ else
+ {
+ fullQueryBuilder.Append("(&(objectclass=");
+ fullQueryBuilder.Append(_utils.GetADObjectClass(oclass));
+ fullQueryBuilder.Append(")");
+ fullQueryBuilder.Append(query);
+ fullQueryBuilder.Append(")");
+ }
+
+ query = fullQueryBuilder.ToString();
+
+ if (query == null)
+ {
+ Trace.TraceInformation("query is null");
+ }
+ else
+ {
+ // for backward compatibility ...
+ if ((ObjectClass.ACCOUNT.Equals(oclass)) && (!includeDeleted))
+ {
+ query = String.Format("(&(ObjectCategory=Person){0})", query);
+ }
+
+ Trace.TraceInformation("Setting search string to \'{0}\'", query);
+ }
+
+ DirectorySearcher searcher = null;
+ DirectoryEntry searchRootEntry = null;
+ try
+ {
+ if (searchRoot.Equals(_configuration.Container, StringComparison.OrdinalIgnoreCase))
+ {
+ searcher = new DirectorySearcher(_dirHandler, query);
+ }
+ else
+ {
+ // options give a different root context for search, let use a new connection
+ string path;
+ path = GetSearchContainerPath(useGlobalCatalog, serverName, searchRoot);
+ Trace.TraceInformation("Search: Getting root node for search");
+ searchRootEntry = new DirectoryEntry(path, _configuration.DirectoryAdminName, _configuration.DirectoryAdminPassword);
+ searcher = new DirectorySearcher(searchRootEntry, query);
+ }
+
+ searcher.PageSize = 1000;
+ searcher.SearchScope = searchScope;
+
+ if (includeDeleted)
+ {
+ searcher.Tombstone = true;
+ }
+
+ if (sortOption != null)
+ {
+ searcher.Sort = sortOption;
+ }
+
+ Trace.TraceInformation("Search: Performing query");
+
+ ICollection attributesToReturn = null;
+ SearchResultCollection resultSet = null;
+ int count = 0;
+ attributesToReturn = GetAttributesToReturn(oclass, options);
+ try
+ {
+ resultSet = searcher.FindAll();
+ foreach (DS.SearchResult result in resultSet)
+ {
+ buildConnectorObject(result, oclass, useGlobalCatalog, searchRoot, attributesToReturn, handler);
+ count++;
+ }
+ }
+ finally
+ {
+ Trace.TraceInformation("Search: found {0} results", count);
+ // Important to dispose to avoid memory leak
+ if (resultSet != null)
+ {
+ resultSet.Dispose();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Trace.TraceWarning(e.Message);
+ }
+ finally
+ {
+ //searcher.Dispose();
+ if (searchRootEntry != null)
+ {
+ searchRootEntry.Dispose();
+ }
+ }
+ }
+
+
+ private void buildConnectorObject(DS.SearchResult result, ObjectClass oclass, bool useGlobalCatalog, string searchRoot, ICollection attributesToReturn, ResultsHandler handler)
+ {
+ try
+ {
+ Trace.TraceInformation("Found object {0}", result.Path);
+ ConnectorObjectBuilder builder = new ConnectorObjectBuilder();
+ builder.ObjectClass = oclass;
+
+ bool isDeleted = false;
+ if (result.Properties.Contains(ATT_IS_DELETED))
+ {
+ ResultPropertyValueCollection pvc = result.Properties[ATT_IS_DELETED];
+ if (pvc.Count > 0)
+ {
+ isDeleted = (bool)pvc[0];
+ }
+ }
+
+ if (isDeleted.Equals(false))
+ {
+ // if we were using the global catalog (gc), we have to
+ // now retrieve the object from a domain controller (dc)
+ // because the gc may not have have all of the attributes,
+ // depending on which attributes are replicated to the gc.
+ DS.SearchResult savedGcResult = null;
+ DS.SearchResult savedDcResult = result;
+ if (useGlobalCatalog)
+ {
+ savedGcResult = result;
+
+ String dcSearchRootPath = ActiveDirectoryUtils.GetLDAPPath(
+ _configuration.LDAPHostName, searchRoot);
+
+ DirectoryEntry dcSearchRoot = new DirectoryEntry(dcSearchRootPath,
+ _configuration.DirectoryAdminName, _configuration.DirectoryAdminPassword);
+
+ string dcSearchQuery = String.Format("(" + ATT_DISTINGUISHED_NAME + "={0})",
+ ActiveDirectoryUtils.GetDnFromPath(savedGcResult.Path));
+ DirectorySearcher dcSearcher =
+ new DirectorySearcher(dcSearchRoot, dcSearchQuery);
+ savedDcResult = dcSearcher.FindOne();
+ if (savedDcResult == null)
+ {
+ // in this case, there is no choice, but to use
+ // what is in the global catalog. We would have
+ //liked to have read from the regular ldap, but there
+ // is not one. This is the case for domainDNS objects
+ // (at least for child domains in certain or maybe all
+ // circumstances).
+ savedDcResult = savedGcResult;
+ }
+ dcSearcher.Dispose();
+ dcSearchRoot.Dispose();
+ }
+
+ foreach (string attributeName in attributesToReturn)
+ {
+ DS.SearchResult savedResults = savedDcResult;
+ // if we are using the global catalog, we had to get the
+ // dc's version of the directory entry, but for usnchanged,
+ // we need the gc version of it
+ if (useGlobalCatalog && attributeName.Equals(ATT_USN_CHANGED,
+ StringComparison.CurrentCultureIgnoreCase))
+ {
+ savedResults = savedGcResult;
+ }
+
+ AddAttributeIfNotNull(builder,
+ _utils.GetConnectorAttributeFromADEntry(
+ oclass, attributeName, savedResults));
+ }
+ }
+ else
+ {
+ // get uid
+ AddAttributeIfNotNull(builder,
+ _utils.GetConnectorAttributeFromADEntry(
+ oclass, Uid.NAME, result));
+
+ // get uid
+ AddAttributeIfNotNull(builder,
+ _utils.GetConnectorAttributeFromADEntry(
+ oclass, Name.NAME, result));
+
+ // get usnchanged
+ AddAttributeIfNotNull(builder,
+ _utils.GetConnectorAttributeFromADEntry(
+ oclass, ATT_USN_CHANGED, result));
+
+ // add isDeleted
+ builder.AddAttribute(ATT_IS_DELETED, true);
+ }
+
+ String msg = String.Format("Returning ''{0}''",
+ (result.Path != null) ? result.Path : "");
+ Trace.TraceInformation(msg);
+ handler.Handle(builder.Build());
+ }
+ catch (DirectoryServicesCOMException e)
+ {
+ // there is a chance that we found the result, but
+ // in the mean time, it was deleted. In that case,
+ // log an error and continue
+ Trace.TraceWarning("Error in creating ConnectorObject from DirectoryEntry. It may have been deleted during search.");
+ Trace.TraceWarning(e.Message);
+ }
+ catch (Exception e)
+ {
+ // In that case, of any error, try to continue
+ Trace.TraceWarning("Error in creating ConnectorObject from DirectoryEntry.");
+ Trace.TraceWarning(e.Message);
+ }
+ }
+
+ private string GetSearchContainerPath(bool useGC, string hostname, string searchContainer)
+ {
+ String path;
+
+ if (useGC)
+ {
+ path = ActiveDirectoryUtils.GetGCPath(hostname, searchContainer);
+ }
+ else
+ {
+ path = ActiveDirectoryUtils.GetLDAPPath(hostname, searchContainer);
+ }
+
+ return path;
+ }
+
+ private ICollection GetAttributesToReturn(ObjectClass oclass, OperationOptions options)
+ {
+ ICollection attributeNames = null;
+
+ if ((options.AttributesToGet != null) && (options.AttributesToGet.Length > 0))
+ {
+ attributeNames = new HashSet(options.AttributesToGet);
+ }
+ else
+ {
+ attributeNames = AttributesReturnedByDefault[oclass];
+ }
+
+ // Uid and name are always returned
+ attributeNames.Add(Uid.NAME);
+ attributeNames.Add(Name.NAME);
+ return attributeNames;
+ }
+
+ private void AddAttributeIfNotNull(ConnectorObjectBuilder builder,
+ ConnectorAttribute attribute)
+ {
+ if (attribute != null)
+ {
+ builder.AddAttribute(attribute);
+ }
+ }
+
+ #endregion
+
+ #region TestOp Members
+
+ public virtual void Test()
+ {
+ _configuration.Validate();
+
+ bool objectFound = true;
+ // now make sure they specified a valid value for the User Object Class
+ ActiveDirectorySchema ADSchema = _utils.GetADSchema();
+ ActiveDirectorySchemaClass ADSchemaClass = null;
+ try
+ {
+ ADSchemaClass = ADSchema.FindClass(_configuration.ObjectClass);
+
+ }
+ catch (ActiveDirectoryObjectNotFoundException exception)
+ {
+ objectFound = false;
+ }
+ if ((!objectFound) || (ADSchemaClass == null))
+ {
+ throw new ConnectorException(
+ _configuration.ConnectorMessages.Format(
+ "ex_InvalidObjectClassInConfiguration",
+ "Invalid Object Class was specified in the connector configuration. Object Class \'{0}\' was not found in Active Directory",
+ _configuration.ObjectClass));
+ }
+
+ try
+ {
+ // see if the Container exists
+ if (!DirectoryEntry.Exists(GetSearchContainerPath(UseGlobalCatalog(),
+ _configuration.LDAPHostName, _configuration.Container)))
+ {
+ throw new ConnectorException(
+ _configuration.ConnectorMessages.Format(
+ "ex_InvalidContainerInConfiguration",
+ "An invalid container was supplied: {0}",
+ _configuration.Container));
+ }
+ }
+ catch (DirectoryServicesCOMException dscex)
+ {
+ Trace.TraceError(string.Format(CultureInfo.InvariantCulture,
+ "Failed to determine whether the Container '{0}' exists. Exception: {1}", _configuration.Container, dscex));
+
+ throw new ConnectorException(
+ _configuration.ConnectorMessages.Format(
+ "ex_ContainerNotFound",
+ "Could not find the Container '{0}', the following message was returned from the server: {1}",
+ _configuration.Container, dscex.Message), dscex);
+ }
+ }
+
+ #endregion
+
+ #region AdvancedUpdateOp Members
+ public Uid Update(ObjectClass objclass, Uid uid, ICollection attrs, OperationOptions options)
+ {
+ return Update(UpdateType.REPLACE, objclass, ConnectorAttributeUtil.AddUid(attrs, uid), options);
+ }
+
+ public Uid AddAttributeValues(ObjectClass objclass,
+ Uid uid,
+ ICollection valuesToAdd,
+ OperationOptions options)
+ {
+ return Update(UpdateType.ADD, objclass, ConnectorAttributeUtil.AddUid(valuesToAdd, uid), options);
+ }
+
+ public Uid RemoveAttributeValues(ObjectClass objclass,
+ Uid uid,
+ ICollection valuesToRemove,
+ OperationOptions options)
+ {
+ return Update(UpdateType.DELETE, objclass, ConnectorAttributeUtil.AddUid(valuesToRemove, uid), options);
+ }
+
+ // implementation of AdvancedUpdateSpiOp
+ public virtual Uid Update(UpdateType type, ObjectClass oclass,
+ ICollection attributes, OperationOptions options)
+ {
+ Uid updatedUid = null;
+
+ Trace.TraceInformation("Update method");
+ if (_configuration == null)
+ {
+ throw new ConfigurationException(_configuration.ConnectorMessages.Format(
+ "ex_ConnectorNotConfigured", "Connector has not been configured"));
+ }
+
+ updatedUid = ConnectorAttributeUtil.GetUidAttribute(attributes);
+ if (updatedUid == null)
+ {
+ throw new ConnectorException(_configuration.ConnectorMessages.Format(
+ "ex_UIDNotPresent", "Uid was not present"));
+ }
+
+ DirectoryEntry updateEntry =
+ ActiveDirectoryUtils.GetDirectoryEntryFromUid(_configuration.LDAPHostName, updatedUid,
+ _configuration.DirectoryAdminName, _configuration.DirectoryAdminPassword);
+
+ _utils.UpdateADObject(oclass, updateEntry,
+ attributes, type, _configuration);
+
+ updateEntry.Dispose();
+ return updatedUid;
+ }
+
+ #endregion
+
+ #region DeleteOp Members
+
+ // implementation of DeleteSpiOp
+ public virtual void Delete(ObjectClass objClass, Uid uid, OperationOptions options)
+ {
+ DirectoryEntry de = null;
+ try
+ {
+ de = ActiveDirectoryUtils.GetDirectoryEntryFromUid(_configuration.LDAPHostName, uid,
+ _configuration.DirectoryAdminName, _configuration.DirectoryAdminPassword);
+ }
+ catch (System.DirectoryServices.DirectoryServicesCOMException e)
+ {
+ // if it's not found, throw that, else just rethrow
+ if (e.ErrorCode == -2147016656)
+ {
+ throw new UnknownUidException();
+ }
+ throw;
+ }
+
+ if (objClass.Equals(ObjectClass.ACCOUNT))
+ {
+ // if it's a user account, get the parent's child list
+ // and remove this entry
+ DirectoryEntry parent = de.Parent;
+ parent.Children.Remove(de);
+ }
+ else
+ {
+ // translate the object class. We dont care what
+ // it is, but this will throw the correct exception
+ // if it's an invalid one.
+ _utils.GetADObjectClass(objClass);
+ // delete this entry and all it's children
+ de.DeleteTree();
+ }
+ de.Dispose();
+ }
+
+ #endregion
+
+
+ #region ScriptOnResourceOp Members
+
+ public object RunScriptOnResource(ScriptContext request, OperationOptions options)
+ {
+ IDictionary arguments = new Dictionary(request.ScriptArguments);
+ // per Will D. batch scripts need special parameters set, but other scripts
+ // don't. He doesn't feel that this can be changed at present, so setting
+ // the parameters here.
+
+ // Cant find a constant for the string to represent the shell script executor,
+ // replace embedded string constant if one turns up.
+ if (request.ScriptLanguage.Equals("Shell", StringComparison.CurrentCultureIgnoreCase))
+ {
+ IDictionary shellArguments = new Dictionary();
+ String shellPrefix = "";
+ if (options.Options.ContainsKey("variablePrefix"))
+ {
+ shellPrefix = (string)options.Options["variablePrefix"];
+ }
+
+ foreach (String argumentName in arguments.Keys)
+ {
+ shellArguments.Add((shellPrefix + argumentName), arguments[argumentName]);
+ }
+
+ arguments = shellArguments;
+
+ if (options.RunAsUser != null)
+ {
+ arguments.Add("USERNAME", options.RunAsUser);
+ arguments.Add("PASSWORD",
+ options.RunWithPassword.ToSecureString());
+ }
+ }
+
+
+ ScriptExecutorFactory factory = ScriptExecutorFactory.NewInstance(request.ScriptLanguage);
+ ScriptExecutor executor = factory.NewScriptExecutor(new Assembly[0], request.ScriptText, true);
+ return executor.Execute(arguments);
+ }
+
+ #endregion
+
+ #region SyncOp Members
+
+ // implementation of SyncSpiOp
+ public class SyncResults
+ {
+ SyncResultsHandler _syncResultsHandler;
+ ActiveDirectorySyncToken _adSyncToken;
+ ActiveDirectoryConfiguration _configuration;
+
+ internal SyncResults(SyncResultsHandler syncResultsHandler,
+ ActiveDirectorySyncToken adSyncToken, ActiveDirectoryConfiguration configuration)
+ {
+ _syncResultsHandler = syncResultsHandler;
+ _adSyncToken = adSyncToken;
+ _configuration = configuration;
+ }
+
+ public ResultsHandler SyncHandler
+ {
+ get
+ {
+ return new ResultsHandler()
+ {
+ Handle = obj =>
+ {
+ SyncDeltaBuilder builder = new SyncDeltaBuilder();
+ ICollection attrs = new HashSet();
+ foreach (ConnectorAttribute attribute in obj.GetAttributes())
+ {
+ // add all attributes to the object except the
+ // one used to flag deletes.
+ if (!attribute.Name.Equals(ATT_IS_DELETED))
+ {
+ attrs.Add(attribute);
+ }
+ }
+
+ ConnectorObjectBuilder coBuilder = new ConnectorObjectBuilder();
+ coBuilder.SetName(obj.Name);
+ coBuilder.SetUid(obj.Uid);
+ coBuilder.ObjectClass = obj.ObjectClass;
+ coBuilder.AddAttributes(attrs);
+ builder.Object = coBuilder.Build();
+
+ ConnectorAttribute tokenAttr =
+ ConnectorAttributeUtil.Find(ATT_USN_CHANGED, obj.GetAttributes());
+ if (tokenAttr == null)
+ {
+ string msg = _configuration.ConnectorMessages.Format("ex_missingSyncAttribute",
+ "Attribute {0} is not present in connector object. Cannot proceed with Synchronization",
+ ATT_USN_CHANGED);
+ Trace.TraceError(msg);
+ throw new ConnectorException(msg);
+ }
+ long tokenUsnValue = (long)ConnectorAttributeUtil.GetSingleValue(tokenAttr);
+
+ bool? isDeleted = false;
+ ConnectorAttribute isDeletedAttr =
+ ConnectorAttributeUtil.Find(ATT_IS_DELETED, obj.GetAttributes());
+ if (isDeletedAttr != null)
+ {
+ isDeleted = (bool?)ConnectorAttributeUtil.GetSingleValue(isDeletedAttr);
+ _adSyncToken.LastDeleteUsn = tokenUsnValue;
+ }
+ else
+ {
+ _adSyncToken.LastModifiedUsn = tokenUsnValue;
+ }
+
+ builder.Token = _adSyncToken.GetSyncToken();
+
+ if ((isDeleted != null) && (isDeleted.Equals(true)))
+ {
+ builder.DeltaType = SyncDeltaType.DELETE;
+ }
+ else
+ {
+ builder.DeltaType = SyncDeltaType.CREATE_OR_UPDATE;
+ }
+
+ builder.Uid = obj.Uid;
+ _syncResultsHandler.Handle(builder.Build());
+ return true;
+ }
+ };
+ }
+ }
+ }
+
+ public virtual void Sync(ObjectClass objClass, SyncToken token,
+ SyncResultsHandler handler, OperationOptions options)
+ {
+ String serverName = GetSyncServerName();
+
+ ActiveDirectorySyncToken adSyncToken =
+ new ActiveDirectorySyncToken(token, serverName, UseGlobalCatalog());
+
+ string modifiedQuery = GetSyncUpdateQuery(adSyncToken);
+ string deletedQuery = GetSyncDeleteQuery(adSyncToken);
+
+ OperationOptionsBuilder builder = new OperationOptionsBuilder();
+ SyncResults syncResults = new SyncResults(handler, adSyncToken, _configuration);
+
+ // find modified usn's
+ ExecuteQuery(objClass, modifiedQuery, syncResults.SyncHandler, builder.Build(),
+ false, new SortOption(ATT_USN_CHANGED, SortDirection.Ascending),
+ serverName, UseGlobalCatalog(), GetADSearchContainerFromOptions(null), SearchScope.Subtree);
+
+ // find deleted usn's
+ DirectoryContext domainContext = new DirectoryContext(DirectoryContextType.DirectoryServer,
+ serverName,
+ _configuration.DirectoryAdminName,
+ _configuration.DirectoryAdminPassword);
+ Domain domain = Domain.GetDomain(domainContext);
+ String deleteObjectsSearchRoot = null;
+ if (domain != null)
+ {
+ DirectoryEntry domainDe = domain.GetDirectoryEntry();
+ deleteObjectsSearchRoot = ActiveDirectoryUtils.GetDnFromPath(domainDe.Path);
+ domainDe.Dispose();
+ }
+ ExecuteQuery(objClass, deletedQuery, syncResults.SyncHandler, builder.Build(),
+ true, new SortOption(ATT_USN_CHANGED, SortDirection.Ascending),
+ serverName, UseGlobalCatalog(), deleteObjectsSearchRoot, SearchScope.Subtree);
+
+ }
+
+ public virtual SyncToken GetLatestSyncToken(ObjectClass objectClass)
+ {
+ string serverName = GetSyncServerName();
+ long highestCommittedUsn = 0;
+ bool useGlobalCatalog = UseGlobalCatalog();
+ if (useGlobalCatalog)
+ {
+ DirectoryContext context = new DirectoryContext(DirectoryContextType.DirectoryServer,
+ serverName, _configuration.DirectoryAdminName, _configuration.DirectoryAdminPassword);
+ GlobalCatalog gc = GlobalCatalog.GetGlobalCatalog(context);
+ highestCommittedUsn = gc.HighestCommittedUsn;
+ }
+ else
+ {
+ DirectoryContext context = new DirectoryContext(DirectoryContextType.DirectoryServer,
+ serverName, _configuration.DirectoryAdminName, _configuration.DirectoryAdminPassword);
+ DomainController dc = DomainController.GetDomainController(context);
+ highestCommittedUsn = dc.HighestCommittedUsn;
+ }
+
+ ActiveDirectorySyncToken token =
+ new ActiveDirectorySyncToken("", serverName, useGlobalCatalog);
+ token.LastDeleteUsn = highestCommittedUsn;
+ token.LastModifiedUsn = highestCommittedUsn;
+ return token.GetSyncToken();
+ }
+
+ string GetSyncServerName()
+ {
+ string serverName = null;
+
+ if (UseGlobalCatalog())
+ {
+ serverName = _configuration.SyncGlobalCatalogServer;
+ }
+ else
+ {
+ serverName = _configuration.SyncDomainController;
+ }
+
+ if ((serverName == null) || (serverName.Length == 0))
+ {
+ Trace.TraceWarning("No server was configured for synchronization, so picking one. You should configure a server for best performance.");
+ // we have to know which server we are working against,
+ // so find one.
+ if (UseGlobalCatalog())
+ {
+ DirectoryContext context = new DirectoryContext(
+ DirectoryContextType.Forest, _configuration.DomainName,
+ _configuration.DirectoryAdminName,
+ _configuration.DirectoryAdminPassword);
+ GlobalCatalog gc = GlobalCatalog.FindOne(context);
+ _configuration.SyncGlobalCatalogServer = gc.ToString();
+ serverName = _configuration.SyncGlobalCatalogServer;
+ }
+ else
+ {
+ DirectoryContext context = new DirectoryContext(
+ DirectoryContextType.Domain, _configuration.DomainName,
+ _configuration.DirectoryAdminName,
+ _configuration.DirectoryAdminPassword);
+ DomainController controller = DomainController.FindOne(context);
+ _configuration.SyncDomainController = controller.ToString();
+ serverName = _configuration.SyncDomainController;
+ }
+ }
+ return serverName;
+ }
+
+ bool UseGlobalCatalog()
+ {
+ return (_configuration.SearchChildDomains);
+ }
+
+ String GetSyncUpdateQuery(ActiveDirectorySyncToken adSyncToken)
+ {
+ string modifiedQuery = null;
+
+ // if the token is not null, we may be able to start from
+ // the usn contained there
+ if (adSyncToken != null)
+ {
+ modifiedQuery = string.Format("(!({0}<={1}))", ATT_USN_CHANGED, adSyncToken.LastModifiedUsn);
+ }
+
+ return modifiedQuery;
+ }
+
+ String GetSyncDeleteQuery(ActiveDirectorySyncToken adSyncToken)
+ {
+ string deletedQuery = null;
+
+ // if the token is not null, we may be able to start from
+ // the usn contained there
+ if (adSyncToken != null)
+ {
+ deletedQuery = string.Format("(&(!({0}<={1}))(isDeleted=TRUE))", ATT_USN_CHANGED, adSyncToken.LastDeleteUsn);
+ }
+ else
+ {
+ deletedQuery = string.Format("(isDeleted=TRUE)");
+ }
+
+ return deletedQuery;
+ }
+
+ #endregion
+
+ #region AuthenticateOp Members
+
+ public Uid Authenticate(ObjectClass objectClass, string username,
+ Org.IdentityConnectors.Common.Security.GuardedString password,
+ OperationOptions options)
+ {
+ bool returnUidOnly = false;
+
+ if (options != null)
+ {
+ if (options.Options.ContainsKey(OPTION_DOMAIN))
+ {
+ string domainName = options.Options[OPTION_DOMAIN].ToString();
+ if ((domainName != null) && (domainName.Length > 0))
+ {
+ username = string.Format("{0}@{1}", username, options.Options["w2k_domain"]);
+ }
+ }
+ else if (options.Options.ContainsKey(OPTION_RETURN_UID_ONLY))
+ {
+ returnUidOnly = true;
+ }
+ }
+
+ PasswordChangeHandler handler = new PasswordChangeHandler(_configuration);
+ return handler.Authenticate(username, password, returnUidOnly);
+ }
+
+ #endregion
+
+ #region PoolableConnector Members
+
+ public void CheckAlive()
+ {
+ return;
+ }
+
+ #endregion
+ }
+}
diff --git a/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryConnector.csproj b/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryConnector.csproj
new file mode 100644
index 0000000..3821ac3
--- /dev/null
+++ b/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryConnector.csproj
@@ -0,0 +1,147 @@
+
+
+
+
+ {BDF495CA-0FCD-4E51-A871-D467CDE3B43E}
+ Debug
+ AnyCPU
+ Library
+ Org.IdentityConnectors.ActiveDirectory
+ ActiveDirectory.Connector
+ v4.0
+ true
+ $(OPENICF_HOME)
+
+
+ prompt
+ 4
+ AnyCPU
+ bin\Debug\
+ True
+ Full
+ False
+ True
+ DEBUG;TRACE
+
+
+ pdbonly
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ AnyCPU
+ False
+ True
+ False
+
+
+
+
+ False
+ $(ConnectorFrameworkDir)\Common.dll
+ False
+
+
+ False
+ $(ConnectorFrameworkDir)\Framework.dll
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Code
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {97d25db0-0363-11cf-abc4-02608c9e7553}
+ 1
+ 0
+ 0
+ tlbimp
+ False
+
+
+ {97d25db0-0363-11cf-abc4-02608c9e7553}
+ 1
+ 0
+ 0
+ tlbimp
+ False
+
+
+
+
+ Designer
+
+
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+
+
\ No newline at end of file
diff --git a/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryFilterTranslator.cs b/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryFilterTranslator.cs
new file mode 100644
index 0000000..a8a3ada
--- /dev/null
+++ b/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryFilterTranslator.cs
@@ -0,0 +1,475 @@
+/*
+ * ====================
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of the Common Development
+ * and Distribution License("CDDL") (the "License"). You may not use this file
+ * except in compliance with the License.
+ *
+ * You can obtain a copy of the License at
+ * http://IdentityConnectors.dev.java.net/legal/license.txt
+ * See the License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * When distributing the Covered Code, include this CDDL Header Notice in each file
+ * and include the License file at identityconnectors/legal/license.txt.
+ * If applicable, add the following below this CDDL Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ * ====================
+ */
+using System;
+using System.Text;
+using Org.IdentityConnectors.Framework.Common.Objects.Filters;
+using Org.IdentityConnectors.Framework.Common.Objects;
+using Org.IdentityConnectors.Framework.Common.Exceptions;
+
+namespace Org.IdentityConnectors.ActiveDirectory
+{
+ ///
+ /// This was taken from the LDAP filter translator (java) and ported to
+ /// C#. There are a few changes, but not many ... that will change over
+ /// time of course.
+ ///
+ public class ActiveDirectoryFilterTranslator : AbstractFilterTranslator
+ {
+ protected override String CreateAndExpression(String leftExpression,
+ String rightExpression) {
+ StringBuilder builder = new StringBuilder();
+ builder.Append("(&");
+ builder.Append(leftExpression);
+ builder.Append(rightExpression);
+ builder.Append(')');
+ return builder.ToString();
+ }
+
+ protected override String CreateOrExpression(String leftExpression,
+ String rightExpression) {
+ StringBuilder builder = new StringBuilder();
+ builder.Append("(|");
+ builder.Append(leftExpression);
+ builder.Append(rightExpression);
+ builder.Append(')');
+ return builder.ToString();
+ }
+
+ protected override String CreateContainsExpression(ContainsFilter filter,
+ Boolean not) {
+ String[] attrNames = GetLdapNamesForAttribute(filter.GetAttribute());
+ if (attrNames == null) {
+ return null;
+ }
+
+ StringBuilder builder = new StringBuilder();
+ if (not) {
+ builder.Append("(!");
+ }
+ if (attrNames.Length == 1) {
+ builder.Append('(');
+ builder.Append(attrNames[0]);
+ builder.Append("=*");
+ int len = builder.Length;
+ GetLdapFilterValue(builder, attrNames[0], filter.GetValue());
+ // Build (attr=*) rather than (attr=**) for zero-length values.
+ if (builder.Length != len) {
+ builder.Append('*');
+ }
+ builder.Append(')');
+ } else {
+ builder.Append("(|");
+ foreach (String attrName in attrNames) {
+ builder.Append('(');
+ builder.Append(attrName);
+ builder.Append("=*");
+ int len = builder.Length;
+ GetLdapFilterValue(builder, attrName, filter.GetValue());
+ // Build (attr=*) rather than (attr=**) for zero-length values.
+ if (builder.Length != len) {
+ builder.Append('*');
+ }
+ builder.Append(')');
+ }
+ builder.Append(')');
+ }
+ if (not) {
+ builder.Append(')');
+ }
+ return builder.ToString();
+ }
+
+ protected override String CreateStartsWithExpression(StartsWithFilter filter,
+ Boolean not) {
+ String[] attrNames = GetLdapNamesForAttribute(filter.GetAttribute());
+ if (attrNames == null) {
+ return null;
+ }
+
+ StringBuilder builder = new StringBuilder();
+ if (not) {
+ builder.Append("(!");
+ }
+ if (attrNames.Length == 1) {
+ builder.Append('(');
+ builder.Append(attrNames[0]);
+ builder.Append('=');
+ GetLdapFilterValue(builder, attrNames[0], filter.GetValue());
+ builder.Append("*)");
+ } else {
+ builder.Append("(|");
+ foreach (String attrName in attrNames) {
+ builder.Append('(');
+ builder.Append(attrName);
+ builder.Append('=');
+ GetLdapFilterValue(builder, attrName, filter.GetValue());
+ builder.Append("*)");
+ }
+ builder.Append(')');
+ }
+ if (not) {
+ builder.Append(')');
+ }
+ return builder.ToString();
+ }
+
+ protected override String CreateEndsWithExpression(EndsWithFilter filter,
+ Boolean not) {
+ String[] attrNames = GetLdapNamesForAttribute(filter.GetAttribute());
+ if (attrNames == null) {
+ return null;
+ }
+
+ StringBuilder builder = new StringBuilder();
+ if (not) {
+ builder.Append("(!");
+ }
+ if (attrNames.Length == 1) {
+ builder.Append('(');
+ builder.Append(attrNames[0]);
+ builder.Append("=*");
+ GetLdapFilterValue(builder, attrNames[0], filter.GetValue());
+ builder.Append(')');
+ } else {
+ builder.Append("(|");
+ foreach (String attrName in attrNames) {
+ builder.Append('(');
+ builder.Append(attrName);
+ builder.Append("=*");
+ GetLdapFilterValue(builder, attrName, filter.GetValue());
+ builder.Append(')');
+ }
+ builder.Append(')');
+ }
+ if (not) {
+ builder.Append(')');
+ }
+ return builder.ToString();
+ }
+
+ protected override String CreateEqualsExpression(EqualsFilter filter, Boolean not) {
+ // The LDAP equality filter matches any one attribute value,
+ // whereas the connector EqualsFilter matches an attribute and
+ // its values exactly.
+ if (not) {
+ return null;
+ }
+
+ ConnectorAttribute attr = filter.GetAttribute();
+ // if there is only one thing to search on, and it's
+ // a uid we need to convert the uid to something we
+ // can search on. NOTE: only handling the case where
+ // we are doing an equality search, and only one item
+ // is in the equality search ... It's all that makes
+ // sense for uid.
+ if (attr is Uid)
+ {
+ String attrValue = ((Uid)attr).GetUidValue();
+ if (LooksLikeGUID(attrValue))
+ {
+ String searchGuid = GetUidSearchString(((Uid)attr).GetUidValue());
+ attr = new Uid(searchGuid);
+ } else {
+ attr = new Name(attrValue);
+ }
+
+ }
+
+
+ String[] attrNames = GetLdapNamesForAttribute(attr);
+ if (attrNames == null) {
+ return null;
+ }
+
+ StringBuilder builder = new StringBuilder();
+
+ if (attr.Value == null) {
+ return null;
+ }
+ if (attr.Value.Count == 1) {
+ BuildEqualityFilter(builder, attrNames,
+ attr.Value[0]);
+ } else {
+ builder.Append("(&");
+ foreach (Object value in attr.Value) {
+ BuildEqualityFilter(builder, attrNames, value);
+ }
+ builder.Append(')');
+ }
+
+ return builder.ToString();
+ }
+
+ protected override String CreateGreaterThanExpression(GreaterThanFilter filter,
+ Boolean not) {
+ // Note that (!(a > X)) is only the same as (a <= X) if every object
+ // has a value of a.
+ if (not) {
+ return null;
+ }
+
+ String[] attrNames = GetLdapNamesForAttribute(filter.GetAttribute());
+ if (attrNames == null) {
+ return null;
+ }
+
+ StringBuilder builder = new StringBuilder();
+ BuildGreaterOrEqualFilter(builder, attrNames, filter.GetValue());
+ return builder.ToString();
+ }
+
+ protected override String CreateGreaterThanOrEqualExpression(
+ GreaterThanOrEqualFilter filter, Boolean not) {
+ String[] attrNames = GetLdapNamesForAttribute(filter.GetAttribute());
+ if (attrNames == null) {
+ return null;
+ }
+
+ StringBuilder builder = new StringBuilder();
+ if (not) {
+ builder.Append("(!");
+ }
+ BuildGreaterOrEqualFilter(builder, attrNames, filter.GetValue());
+ if (not) {
+ builder.Append(')');
+ }
+ return builder.ToString();
+ }
+
+ protected override String CreateLessThanExpression(LessThanFilter filter,
+ Boolean not) {
+ // Note that (!(a < X)) is only the same as (a >= X) if every object
+ // has a value of a.
+ if (not) {
+ return null;
+ }
+
+ String[] attrNames = GetLdapNamesForAttribute(filter.GetAttribute());
+ if (attrNames == null) {
+ return null;
+ }
+
+ StringBuilder builder = new StringBuilder();
+ BuildLessOrEqualFilter(builder, attrNames, filter.GetValue());
+ return builder.ToString();
+ }
+
+ protected override String CreateLessThanOrEqualExpression(
+ LessThanOrEqualFilter filter, Boolean not) {
+ String[] attrNames = GetLdapNamesForAttribute(filter.GetAttribute());
+ if (attrNames == null) {
+ return null;
+ }
+
+ StringBuilder builder = new StringBuilder();
+ if (not) {
+ builder.Append("(!");
+ }
+ BuildLessOrEqualFilter(builder, attrNames, filter.GetValue());
+ if (not) {
+ builder.Append(')');
+ }
+ return builder.ToString();
+ }
+
+ /**
+ * Get the string representation of an attribute value suitable for
+ * embedding in an LDAP search filter (RFC 2254 / RFC 4515).
+ *
+ * @param builder A string builder on to which a suitably escaped attribute
+ * value will be appended.
+ *
+ * @param value The attribute value to be embedded.
+ */
+ static void GetLdapFilterValue(StringBuilder builder,
+ String AttributeName, Object value) {
+ // at this point, this can probably go away
+ // it was here to properyly escape queries, but
+ // it doesn't seem that they need escaping.
+ if (value == null)
+ {
+ return;
+ }
+ else
+ {
+ builder.Append(value);
+ }
+ }
+
+ /**
+ * Get the LDAP name or names for a given connector attribute used in a
+ * search filter.
+ *
+ * @param attr The connector attribute used in a search filter.
+ *
+ * @return The name or names of the corresponding LDAP attribute.
+ * Returns null if the attribute cannot be specified in an LDAP
+ * filter.
+ */
+
+ protected virtual String[] GetLdapNamesForAttribute(ConnectorAttribute attr) {
+ // Special processing for certain connector attributes.
+ String[] attrNames = null;
+ if (attr is Uid) {
+ /*
+ attrNames = new String[] {
+ configCache.getConfiguration().getUuidAttribute() };
+ */
+ attrNames = new String[] { "objectGUID" };
+ } else if (attr is Name) {
+ /*
+ attrNames = configCache.getNamingAttributes();
+ */
+ attrNames = new String [] { "distinguishedName" };
+ } else if (attr.Is(OperationalAttributes.PASSWORD_NAME)) {
+ /*
+ attrNames = new String[] {
+ configCache.getConfiguration().getPasswordAttribute()
+ };
+ */
+ attrNames = new String[] { "userPassword" };
+ } else if (ConnectorAttributeUtil.IsSpecial(attr)) {
+ return null;
+ } else {
+ attrNames = new String[] { attr.Name };
+ }
+
+ return attrNames;
+ }
+
+ static void BuildEqualityFilter(StringBuilder builder,
+ String[] attrNames,
+ Object attrValue) {
+ if (attrNames.Length == 1) {
+ builder.Append('(');
+ builder.Append(attrNames[0]);
+ builder.Append('=');
+ GetLdapFilterValue(builder, attrNames[0], attrValue);
+ builder.Append(')');
+ } else {
+ builder.Append("(|");
+ foreach (String attrName in attrNames) {
+ builder.Append('(');
+ builder.Append(attrName);
+ builder.Append('=');
+ GetLdapFilterValue(builder, attrName, attrValue);
+ builder.Append(')');
+ }
+ builder.Append(')');
+ }
+ }
+
+ static void BuildGreaterOrEqualFilter(StringBuilder builder,
+ String[] attrNames,
+ Object attrValue) {
+ if (attrNames.Length == 1) {
+ builder.Append('(');
+ builder.Append(attrNames[0]);
+ builder.Append(">=");
+ GetLdapFilterValue(builder, attrNames[0], attrValue);
+ builder.Append(')');
+ } else {
+ builder.Append("(|");
+ foreach (String attrName in attrNames) {
+ builder.Append('(');
+ builder.Append(attrName);
+ builder.Append(">=");
+ GetLdapFilterValue(builder, attrName, attrValue);
+ builder.Append(')');
+ }
+ builder.Append(')');
+ }
+ }
+
+ static void BuildLessOrEqualFilter(StringBuilder builder,
+ String[] attrNames,
+ Object attrValue) {
+ if (attrNames.Length == 1) {
+ builder.Append('(');
+ builder.Append(attrNames[0]);
+ builder.Append("<=");
+ GetLdapFilterValue(builder, attrNames[0], attrValue);
+ builder.Append(')');
+ } else {
+ builder.Append("(|");
+ foreach (String attrName in attrNames) {
+ builder.Append('(');
+ builder.Append(attrName);
+ builder.Append("<=");
+ GetLdapFilterValue(builder, attrName, attrValue);
+ builder.Append(')');
+ }
+ builder.Append(')');
+ }
+ }
+
+ // This is a special case for IDM backward compatibility for
+ // non account objects. If it doesn't look like a UID, just
+ // assume it's a dn
+ static internal bool LooksLikeGUID(string value)
+ {
+ string[] uidStringParts = value.Split('=');
+ if ((uidStringParts.Length != 2) || (uidStringParts[1] == null))
+ {
+ // This is a special case for IDM backward compatibility for
+ // non account objects. If it doesn't look like a UID, just
+ // assume it's a dn
+ return false;
+ }
+
+ return true;
+ }
+
+ // This is called to fix up UID values which are in the
+ // format , but need to be in the
+ // format \\xx\\xx\\xx...
+ static internal string GetUidSearchString(string uidString)
+ {
+ // be tolerant of whitespace between '<' and "GUID",
+ // and between "GUID" and '=' and between '=' and
+ // start of guidstring, and between start of guidstring
+ // and '>'
+ string uidSearchString = "";
+ string[] uidStringParts = uidString.Split('=');
+ if ((uidStringParts.Length != 2) || (uidStringParts[1] == null))
+ {
+ throw new ConnectorException("Uid is not in the expected format");
+ }
+ uidSearchString = uidStringParts[1].Trim();
+
+ // take off the final '>'
+ uidSearchString = uidSearchString.Substring(0, uidSearchString.IndexOf('>'));
+
+ // now put the '\' characters in
+ string escapedSearchString = "";
+ for(int position = 0;position < uidSearchString.Length;position++) {
+ if(position % 2 == 0) {
+ escapedSearchString += "\\";
+ }
+ escapedSearchString += uidSearchString[position];
+ }
+
+ return escapedSearchString;
+ }
+ }
+}
diff --git a/dotnet-connector/ActiveDirectoryConnector/ActiveDirectorySyncToken.cs b/dotnet-connector/ActiveDirectoryConnector/ActiveDirectorySyncToken.cs
new file mode 100644
index 0000000..ed0c828
--- /dev/null
+++ b/dotnet-connector/ActiveDirectoryConnector/ActiveDirectorySyncToken.cs
@@ -0,0 +1,86 @@
+/*
+ * ====================
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of the Common Development
+ * and Distribution License("CDDL") (the "License"). You may not use this file
+ * except in compliance with the License.
+ *
+ * You can obtain a copy of the License at
+ * http://IdentityConnectors.dev.java.net/legal/license.txt
+ * See the License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * When distributing the Covered Code, include this CDDL Header Notice in each file
+ * and include the License file at identityconnectors/legal/license.txt.
+ * If applicable, add the following below this CDDL Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ * ====================
+ */
+using System;
+using Org.IdentityConnectors.Framework.Common.Objects;
+using Org.IdentityConnectors.Framework.Common.Exceptions;
+
+namespace Org.IdentityConnectors.ActiveDirectory
+{
+ public class ActiveDirectorySyncToken
+ {
+ internal long LastModifiedUsn { get; set; }
+ internal long LastDeleteUsn { get; set; }
+ internal bool UseGlobalCatalog { get; set; }
+ internal string SyncServer { get; set; }
+
+ public ActiveDirectorySyncToken(SyncToken token, string serverName, bool useGlobalCatalog)
+ : this(token == null ? null : (string)token.Value, serverName, useGlobalCatalog)
+ {
+ }
+
+ public ActiveDirectorySyncToken(String tokenValue, string configServerName, bool configUseGlobalCatalog)
+ {
+ UseGlobalCatalog = configUseGlobalCatalog;
+ SyncServer = configServerName;
+
+ if ((tokenValue == null) || (tokenValue.Length == 0))
+ {
+ LastDeleteUsn = 0;
+ LastModifiedUsn = 0;
+ return;
+ }
+
+ string[] tokenParts = (tokenValue).Split('|');
+ if (tokenParts.Length != 4)
+ {
+ throw new ConnectorException("Unable to parse sync token");
+ }
+
+ string tokenSyncServer = tokenParts[3];
+ bool tokenUseGlobalCatalog = bool.Parse(tokenParts[2]);
+
+ // If the token server is the same as the configured server,
+ // use the token value (usn) to limit the query. The token is
+ // server specific though, so we cant use the usn if it didn't come
+ // from this server.
+ // If no server is configured, just try to use what we used last time.
+ if ((SyncServer != null) && (SyncServer.Equals(configServerName)) &&
+ (UseGlobalCatalog.Equals(tokenUseGlobalCatalog)))
+ {
+ LastModifiedUsn = long.Parse(tokenParts[0]);
+ LastDeleteUsn = long.Parse(tokenParts[1]);
+ }
+ else
+ {
+ LastModifiedUsn = 0;
+ LastDeleteUsn = 0;
+ }
+ }
+
+ public SyncToken GetSyncToken()
+ {
+ return new SyncToken(String.Format("{0}|{1}|{2}|{3}",
+ LastModifiedUsn, LastDeleteUsn, UseGlobalCatalog, SyncServer));
+ }
+ }
+}
diff --git a/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryUtils.cs b/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryUtils.cs
new file mode 100644
index 0000000..edb2fc9
--- /dev/null
+++ b/dotnet-connector/ActiveDirectoryConnector/ActiveDirectoryUtils.cs
@@ -0,0 +1,816 @@
+/*
+ * ====================
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of the Common Development
+ * and Distribution License("CDDL") (the "License"). You may not use this file
+ * except in compliance with the License.
+ *
+ * You can obtain a copy of the License at
+ * http://IdentityConnectors.dev.java.net/legal/license.txt
+ * See the License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * When distributing the Covered Code, include this CDDL Header Notice in each file
+ * and include the License file at identityconnectors/legal/license.txt.
+ * If applicable, add the following below this CDDL Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ * ====================
+ * Portions Copyrighted 2012-2014 ForgeRock AS.
+ */
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Text;
+using Org.IdentityConnectors.Framework.Common.Objects;
+using System.DirectoryServices;
+using DS = System.DirectoryServices;
+using Org.IdentityConnectors.Framework.Common.Exceptions;
+using System.Diagnostics;
+using System.Security;
+using ActiveDs;
+using Org.IdentityConnectors.Common.Security;
+using System.DirectoryServices.ActiveDirectory;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+
+namespace Org.IdentityConnectors.ActiveDirectory
+{
+ ///
+ /// Collection of Active directory utilities. Some are static methods,
+ /// other require configuration, so they are instance methods.
+ ///
+ public class ActiveDirectoryUtils
+ {
+ ActiveDirectoryConfiguration _configuration = null;
+ private CustomAttributeHandlers _customHandlers = null;
+ private ICollection _knownObjectClasses = new HashSet(StringComparer.CurrentCultureIgnoreCase);
+
+ ///
+ /// Constructor
+ ///
+ ///
+ /// Configuration object for the connector.
+ ///
+ public ActiveDirectoryUtils(ActiveDirectoryConfiguration configuration)
+ {
+ _configuration = configuration;
+ _customHandlers = new CustomAttributeHandlers(_configuration);
+ }
+
+ ///
+ /// Converts a guid in byte array form to a string suitable
+ /// for ldap search.
+ ///
+ ///
+ ///
+ internal static String ConvertUIDBytesToSearchString(Byte[] guidBytes)
+ {
+ String searchGuid = "";
+
+ for (int i = 0; i < guidBytes.Length; i++)
+ {
+ searchGuid += String.Format("\\{0:x2}", guidBytes[i]);
+ }
+
+ return searchGuid;
+ }
+
+ ///
+ /// Converts a guid in byte array form to a string with the format
+ /// >GUID = xxxxxxxxxxxxxxxxxxxxxxxxxxxxx< where the x's represent
+ /// uppercase hexadecimal digits
+ ///
+ ///
+ ///
+ internal static String ConvertUIDBytesToGUIDString(Byte[] guidBytes)
+ {
+ return ConvertBytesToADSpecialString("GUID", guidBytes);
+ }
+
+ internal static String ConvertSIDBytesToGUIDString(Byte[] sidBytes)
+ {
+ return ConvertBytesToADSpecialString("SID", sidBytes);
+ }
+
+ internal static String ConvertBytesToADSpecialString(string attribute, Byte[] bytes)
+ {
+ String guidString = "<" + attribute + "=";
+
+ for (int i = 0; i < bytes.Length; i++)
+ {
+ guidString += String.Format("{0:x2}", bytes[i]);
+ }
+ guidString += ">";
+
+ return guidString;
+ }
+
+ ///
+ /// Returns an ldap path in the form of:
+ /// LDAP://servernameIfSpecified/path
+ ///
+ /// Servername can be null
+ /// Path should not be null
+ ///
+ internal static String GetLDAPPath(string serverName, string path)
+ {
+ return GetFullPath("LDAP", serverName, path);
+ }
+
+ ///
+ /// Returns a path string in the format:
+ /// GC://servernameIfSpecified/path
+ ///
+ /// Servername is optional
+ /// Path should be specified
+ ///
+ internal static String GetGCPath(string serverName, string path)
+ {
+ return GetFullPath("GC", serverName, path);
+ }
+
+ ///
+ /// Returns a path string in the format:
+ /// provider://servernameIfSpecified/path
+ ///
+ /// provider (such as ldap or gc)
+ /// servername - optional
+ /// path to resource
+ ///
+ internal static String GetFullPath(string provider, string serverName, string path)
+ {
+ IADsPathname pathName = getADSPathname(provider, serverName, path);
+ return pathName.Retrieve((int)ADS_FORMAT_ENUM.ADS_FORMAT_X500);
+ }
+
+ ///
+ /// uses iadspathname to create paths in a standard way
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static IADsPathname getADSPathname(string provider, string serverName, string path)
+ {
+ IADsPathname pathName = new PathnameClass();
+ if ((provider != null) && (provider.Length != 0))
+ {
+ pathName.Set(provider, (int)ADS_SETTYPE_ENUM.ADS_SETTYPE_PROVIDER);
+ }
+
+ if ((serverName != null) && (serverName.Length != 0))
+ {
+ pathName.Set(serverName, (int)ADS_SETTYPE_ENUM.ADS_SETTYPE_SERVER);
+ }
+
+ if ((path != null) && (path.Length != 0))
+ {
+ // must supply a path
+ pathName.Set(path, (int)ADS_SETTYPE_ENUM.ADS_SETTYPE_DN);
+ }
+ return pathName;
+ }
+
+ ///
+ /// Gets the dn of the parent object of the object specified by childDn
+ ///
+ /// distinguished name of an object to retrieve the parent of
+ /// distinguished name of the parent of 'childDn' or null
+ internal static string GetParentDn(string childDn)
+ {
+ IADsPathname pathName = getADSPathname(null, null, childDn);
+ return pathName.Retrieve((int)ADS_FORMAT_ENUM.ADS_FORMAT_X500_PARENT);
+ }
+
+ ///
+ /// Updates an AD object (also called by create after object is created)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal void UpdateADObject(ObjectClass oclass,
+ DirectoryEntry directoryEntry, ICollection attributes,
+ UpdateType type, ActiveDirectoryConfiguration config)
+ {
+ if(oclass.Equals(ObjectClass.ACCOUNT))
+ {
+ // translate attribute passed in
+ foreach (ConnectorAttribute attribute in attributes)
+ {
+ // encountered problems when processing change password at the same time
+ // as setting expired. It would be set to expired, but the change would
+ // clear that. So we must ensure that expired comes last.
+ if (OperationalAttributes.PASSWORD_EXPIRED_NAME.Equals(attribute.Name))
+ {
+ continue;
+ }
+
+ AddConnectorAttributeToADProperties(oclass,
+ directoryEntry, attribute, type);
+
+ // Uncommenting the next line is very helpful in
+ // finding mysterious errors.
+ // directoryEntry.CommitChanges();
+ }
+
+ directoryEntry.CommitChanges();
+
+ // now do the password change. This is handled separately, because
+ // it might be a user changing his own password, or it might be an
+ // administrative change.
+
+ GuardedString gsNewPassword = ConnectorAttributeUtil.GetPasswordValue(attributes);
+ if (gsNewPassword != null)
+ {
+ GuardedString gsCurrentPassword = ConnectorAttributeUtil.GetCurrentPasswordValue(attributes);
+ PasswordChangeHandler changeHandler = new PasswordChangeHandler(_configuration);
+ if (gsCurrentPassword == null)
+ {
+ // just a normal password change
+ changeHandler.changePassword(directoryEntry, gsNewPassword);
+ }
+ else
+ {
+ changeHandler.changePassword(directoryEntry,
+ gsCurrentPassword, gsNewPassword);
+ }
+
+
+ UserAccountControl.Set(directoryEntry.Properties[ActiveDirectoryConnector.ATT_USER_ACOUNT_CONTROL],
+ UserAccountControl.PASSWD_NOTREQD, false);
+ directoryEntry.CommitChanges();
+ }
+
+ // see note in loop above for explaination of this
+ ConnectorAttribute expirePasswordAttribute = ConnectorAttributeUtil.Find(
+ OperationalAttributes.PASSWORD_EXPIRED_NAME, attributes);
+
+ if (expirePasswordAttribute != null)
+ {
+ AddConnectorAttributeToADProperties(oclass,
+ directoryEntry, expirePasswordAttribute, type);
+ directoryEntry.CommitChanges();
+ }
+ /*
+ UserAccountControl.Set(directoryEntry.Properties[ActiveDirectoryConnector.ATT_USER_ACOUNT_CONTROL],
+ UserAccountControl.PASSWD_NOTREQD, false);
+ */
+ directoryEntry.CommitChanges();
+
+ HandleNameChange(type, directoryEntry, attributes);
+ HandleContainerChange(type, directoryEntry, attributes, config);
+ }
+ else if (oclass.Equals(ActiveDirectoryConnector.groupObjectClass))
+ {
+ // translate attribute passed in
+ foreach (ConnectorAttribute attribute in attributes)
+ {
+ // Temporary
+ // Trace.TraceInformation(String.Format("Setting attribute {0} to {1}",
+ // attribute.Name, attribute.Value));
+ AddConnectorAttributeToADProperties(oclass,
+ directoryEntry, attribute, type);
+ // Uncommenting the next line is very helpful in
+ // finding mysterious errors.
+ // directoryEntry.CommitChanges();
+ }
+
+ directoryEntry.CommitChanges();
+ HandleNameChange(type, directoryEntry, attributes);
+ HandleContainerChange(type, directoryEntry, attributes, config);
+ }
+ else if (oclass.Equals(ActiveDirectoryConnector.ouObjectClass))
+ {
+ // translate attribute passed in
+ foreach (ConnectorAttribute attribute in attributes)
+ {
+ // Temporary
+ // Trace.TraceInformation(String.Format("Setting attribute {0} to {1}",
+ // attribute.Name, attribute.Value));
+ AddConnectorAttributeToADProperties(oclass,
+ directoryEntry, attribute, type);
+ // Uncommenting the next line is very helpful in
+ // finding mysterious errors.
+ // directoryEntry.CommitChanges();
+ }
+
+ directoryEntry.CommitChanges();
+ HandleNameChange(type, directoryEntry, attributes);
+ HandleContainerChange(type, directoryEntry, attributes, config);
+ }
+ else
+ {
+ String objectClassName = GetADObjectClass(oclass);
+ // translate attribute passed in
+ foreach (ConnectorAttribute attribute in attributes)
+ {
+ // Temporary
+ // Trace.TraceInformation(String.Format("Setting attribute {0} to {1}",
+ // attribute.Name, attribute.Value));
+ AddConnectorAttributeToADProperties(oclass,
+ directoryEntry, attribute, type);
+ // Uncommenting the next line is very helpful in
+ // finding mysterious errors.
+ // directoryEntry.CommitChanges();
+ }
+
+ directoryEntry.CommitChanges();
+ HandleNameChange(type, directoryEntry, attributes);
+ HandleContainerChange(type, directoryEntry, attributes, config);
+ }
+ }
+
+ internal ConnectorAttribute GetConnectorAttributeFromADEntry(ObjectClass oclass,
+ String attributeName, DS.SearchResult searchResult)
+ {
+ // Boolean translated = false;
+ if (searchResult == null)
+ {
+ throw new ConnectorException(_configuration.ConnectorMessages.Format(
+ "ex_AttributeNull",
+ "Could not add connector attribute to search result"));
+ }
+
+ return _customHandlers.GetCaFromDe(oclass,
+ attributeName, searchResult);
+
+ }
+
+ internal void AddConnectorAttributeToADProperties(ObjectClass oclass,
+ DirectoryEntry directoryEntry, ConnectorAttribute attribute,
+ UpdateType type)
+ {
+ // Boolean translated = false;
+ if (directoryEntry == null)
+ {
+ throw new ConnectorException(_configuration.ConnectorMessages.Format(
+ "ex_CouldNotAddNullAttributeToDe",
+ "Could not add connector attribute to directory entry"));
+ }
+
+ _customHandlers.UpdateDeFromCa(oclass, type,
+ directoryEntry, attribute);
+
+ }
+
+ /*
+ ///
+ /// creates and returns a connector attribute or null. the attribute
+ /// has the name 'name' and the values associated with 'name' in the
+ /// directory entry
+ ///
+ ///
+ ///
+ ///
+ private static ConnectorAttribute CreateConnectorAttribute(String name,
+ PropertyValueCollection pvc)
+ {
+ ConnectorAttributeBuilder attributeBuilder = new ConnectorAttributeBuilder();
+
+ if (name == null)
+ {
+ return null;
+ }
+
+ attributeBuilder.Name = name;
+
+ if (pvc == null)
+ {
+ attributeBuilder.AddValue(null);
+ }
+ else
+ {
+ for (int i = 0; i < pvc.Count; i++)
+ {
+ Object valueObject = pvc[i];
+ if ((pvc[i] == null) ||
+ (FrameworkUtil.IsSupportedAttributeType(valueObject.GetType())))
+ {
+ attributeBuilder.AddValue(pvc[i]);
+ }
+ else
+ {
+ Trace.TraceWarning(
+ "Unsupported attribute type ... calling ToString (Name: \'{0}\'({1}) Type: \'{2}\' String Value: \'{3}\'",
+ name, i, pvc[i].GetType(), pvc[i].ToString());
+ attributeBuilder.AddValue(pvc[i].ToString());
+ }
+ }
+ }
+
+ return attributeBuilder.Build();
+ }
+
+ private static void AddConnectorAttributeToADProperties_general(
+ PropertyCollection properties,
+ ConnectorAttribute attribute, UpdateType type)
+ {
+ // null out the values if we are deleting
+ // or replacing attributes.
+ if (type.Equals(UpdateType.DELETE) ||
+ type.Equals(UpdateType.REPLACE))
+ {
+ properties[attribute.Name].Value = null;
+ }
+
+ // if we are updating or adding, put the
+ // new values in.
+ if (type.Equals(UpdateType.ADD) ||
+ type.Equals(UpdateType.REPLACE))
+ {
+ foreach (Object valueObject in attribute.Value)
+ {
+ properties[attribute.Name].Add(valueObject);
+ }
+ }
+ }
+ */
+
+ ///
+ /// Gets a single value from a propertyvaluecollection
+ /// for a particular property name. Its an error if the
+ /// property contains multiple values.
+ ///
+ ///
+ ///
+ internal Object GetSingleValue(PropertyValueCollection pvc)
+ {
+ if((pvc == null) || (pvc.Count == 0))
+ {
+ return null;
+ }
+
+ if (pvc.Count > 1)
+ {
+ String msg = _configuration.ConnectorMessages.Format(
+ "ex_ExpectingSingleValue",
+ "Expecting single value, but found multiple values for attribute {0}",
+ pvc.PropertyName);
+ throw new ConnectorException(msg);
+ }
+
+ return pvc[0];
+ }
+
+ ///
+ /// Finds a DirectoryEntry by it's uid
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static DirectoryEntry GetDirectoryEntryFromUid(String serverName,
+ Uid uid, string adminUserName, string adminPassword)
+ {
+ DirectoryEntry foundDirectoryEntry = new DirectoryEntry(
+ ActiveDirectoryUtils.GetLDAPPath(serverName, uid.GetUidValue()),
+ adminUserName, adminPassword);
+ string dn = (string)foundDirectoryEntry.Properties["distinguishedName"][0];
+ foundDirectoryEntry.Dispose();
+ foundDirectoryEntry = new DirectoryEntry(
+ ActiveDirectoryUtils.GetLDAPPath(serverName, dn),
+ adminUserName, adminPassword);
+ return foundDirectoryEntry;
+ }
+
+ ///
+ /// Returns the AD ObjectClass associated with a particular
+ /// Connector ObjectClass
+ ///
+ ///
+ ///
+ internal String GetADObjectClass(ObjectClass oclass)
+ {
+
+ if (oclass.Equals(ObjectClass.ACCOUNT))
+ {
+ return _configuration.ObjectClass;
+ }
+ else if (ActiveDirectoryConnector.groupObjectClass.Equals(oclass))
+ {
+ return "Group";
+ }
+ else if (ActiveDirectoryConnector.ouObjectClass.Equals(oclass))
+ {
+ return "organizationalUnit";
+ }
+ else
+ {
+ // It's not something I know about, so I'll consult the AD schema.
+ // if it's there, fine, but if not throw an exception.
+
+ //first check to see if we have seen it before.
+ String objectClassName = oclass.GetObjectClassValue();
+ if(_knownObjectClasses.Contains(objectClassName))
+ {
+ return objectClassName;
+ }
+
+ // if we havent seen it before, consult AD's schema
+ ActiveDirectorySchema ADSchema = GetADSchema();
+ ActiveDirectorySchemaClass ADSchemaClass = null;
+ try
+ {
+ ADSchemaClass = ADSchema.FindClass(objectClassName);
+ _knownObjectClasses.Add(objectClassName);
+ return objectClassName;
+ }
+ catch (ActiveDirectoryObjectNotFoundException exception)
+ {
+ String msg = _configuration.ConnectorMessages.Format(
+ "ex_ObjectClassInvalidForConnector",
+ "ObjectClass \'{0}\' is not valid for this connector",
+ objectClassName);
+ throw new ConnectorException(msg);
+ }
+
+ }
+ }
+
+ ///
+ /// Puts an ldap string into a normalilzed format
+ ///
+ ///
+ ///
+ public static String NormalizeLdapString(String ldapString)
+ {
+ /// (?
+ /// Returns the leaf value of a distinguished name
+ ///
+ ///
+ ///
+ internal static String GetNameAsCN(String nameValue)
+ {
+ IADsPathname pathName = getADSPathname(null, null, nameValue);
+ return pathName.Retrieve((int)ADS_FORMAT_ENUM.ADS_FORMAT_LEAF);
+ }
+
+ ///
+ /// This does not work ... for now, don't handle container changes
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static void HandleContainerChange(UpdateType type,
+ DirectoryEntry directoryEntry, ICollection attributes,
+ ActiveDirectoryConfiguration config)
+ {
+ Name nameAttribute = ConnectorAttributeUtil.GetNameFromAttributes(attributes);
+ if(nameAttribute == null)
+ {
+ // no name, so must not be a container change
+ return;
+ }
+
+ if (!type.Equals(UpdateType.REPLACE))
+ {
+ // this only make sense for replace. you can't
+ // add a name or delete a name
+ return;
+ }
+
+ String oldContainer = GetParentDn(directoryEntry.Path);
+ String newContainer = GetParentDn(nameAttribute.GetNameValue());
+
+ if (!NormalizeLdapString(oldContainer).Equals(NormalizeLdapString(newContainer), StringComparison.OrdinalIgnoreCase))
+ {
+ if (newContainer != null)
+ {
+ try
+ {
+ if (!NormalizeLdapString(oldContainer).Equals(
+ NormalizeLdapString(newContainer), StringComparison.OrdinalIgnoreCase))
+ {
+ String newContainerLdapPath = ActiveDirectoryUtils.GetLDAPPath(
+ config.LDAPHostName, newContainer);
+ DirectoryEntry newContainerDe = new DirectoryEntry(newContainerLdapPath,
+ config.DirectoryAdminName, config.DirectoryAdminPassword);
+ directoryEntry.MoveTo(newContainerDe);
+ newContainerDe.Dispose();
+ }
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ }
+ }
+ }
+
+ private static void HandleNameChange(UpdateType type,
+ DirectoryEntry directoryEntry,
+ ICollection attributes)
+ {
+ Name nameAttribute = ConnectorAttributeUtil.GetNameFromAttributes(attributes);
+ if (nameAttribute != null)
+ {
+ // this only make sense for replace. you can't
+ // add a name or delete a name
+ if (type.Equals(UpdateType.REPLACE))
+ {
+ String oldName = directoryEntry.Name;
+ String newName = GetRelativeName(nameAttribute);
+ if (!NormalizeLdapString(oldName).Equals(NormalizeLdapString(newName), StringComparison.OrdinalIgnoreCase))
+ {
+ directoryEntry.Rename(newName);
+ }
+ }
+ }
+ }
+
+ public static SecureString GetSecureString(String stringToSecure)
+ {
+ SecureString secure = new SecureString();
+
+ foreach (char nextChar in stringToSecure)
+ {
+ secure.AppendChar(nextChar);
+ }
+
+ return secure;
+ }
+
+ internal static string GetDnFromPath(string fullPath)
+ {
+ IADsPathname pathName = new PathnameClass();
+ pathName.Set(fullPath, (int)ADS_SETTYPE_ENUM.ADS_SETTYPE_FULL);
+ return pathName.Retrieve((int)ADS_FORMAT_ENUM.ADS_FORMAT_X500_DN);
+ }
+
+ internal static DomainController GetDomainController(ActiveDirectoryConfiguration configuration)
+ {
+ String serverName = configuration.LDAPHostName;
+ DomainController controller = null;
+
+ if ((serverName == null) || (serverName.Length == 0))
+ {
+ // get the active directory schema
+ DirectoryContext context = new DirectoryContext(
+ DirectoryContextType.Domain,
+ configuration.DomainName,
+ configuration.DirectoryAdminName,
+ configuration.DirectoryAdminPassword);
+ controller = DomainController.FindOne(context);
+ }
+ else
+ {
+ DirectoryContext context = new DirectoryContext(
+ DirectoryContextType.DirectoryServer,
+ configuration.LDAPHostName,
+ configuration.DirectoryAdminName,
+ configuration.DirectoryAdminPassword);
+ controller = DomainController.GetDomainController(context);
+ }
+
+ return controller;
+ }
+
+ public static string GetDomainControllerName(ActiveDirectoryConfiguration configuration)
+ {
+ string serverName = configuration.LDAPHostName;
+ if (string.IsNullOrEmpty(serverName))
+ {
+ // serverless
+ using (DirectoryEntry rootDe = new DirectoryEntry("LDAP://RootDSE",
+ configuration.DirectoryAdminName, configuration.DirectoryAdminPassword))
+ {
+ serverName = rootDe.Properties["dnsHostName"].Value as string;
+ }
+ }
+
+ return serverName;
+ }
+
+ internal ActiveDirectorySchema GetADSchema()
+ {
+ String serverName = _configuration.LDAPHostName;
+ Forest forest = null;
+
+ if ((serverName == null) || (serverName.Length == 0))
+ {
+ // get the active directory schema
+ Trace.TraceInformation("Trying to lookup Domain controller for domain {0}",
+ _configuration.DomainName);
+
+ DirectoryContext context = new DirectoryContext(
+ DirectoryContextType.Domain,
+ _configuration.DomainName,
+ _configuration.DirectoryAdminName,
+ _configuration.DirectoryAdminPassword);
+
+ DomainController dc = DomainController.FindOne(context);
+ Trace.TraceInformation("Found Domain controller named {0} with ipAddress {1} for domain {2}",
+ dc.Name, dc.IPAddress, _configuration.DomainName);
+ forest = dc.Forest;
+ Trace.TraceInformation("Found forest");
+ }
+ else
+ {
+ DirectoryContext context = new DirectoryContext(
+ DirectoryContextType.DirectoryServer,
+ _configuration.LDAPHostName,
+ _configuration.DirectoryAdminName,
+ _configuration.DirectoryAdminPassword);
+ forest = Forest.GetForest(context);
+ }
+
+ Trace.TraceInformation("Getting schema");
+ ActiveDirectorySchema ADSchema = forest.Schema;
+ Trace.TraceInformation("Got schema");
+
+ return ADSchema;
+ }
+
+ // gets a long from a LargeInteger (COM object)
+ static internal ulong GetLongFromLargeInteger(LargeInteger largeInteger)
+ {
+ ulong lHigh = 0;
+ ulong lLow = 0;
+ unchecked
+ {
+ lHigh = (uint)largeInteger.HighPart;
+ lLow = (uint)largeInteger.LowPart;
+ }
+
+ ulong retVal = (ulong) lHigh;
+ retVal = (retVal << 32);
+ retVal = retVal + (ulong)lLow;
+ return retVal;
+ }
+
+ // sets a LargeInteger (COM object) from a long
+ static internal LargeInteger GetLargeIntegerFromLong(Int64 int64Value)
+ {
+ LargeInteger largeInteger = new LargeIntegerClass();
+ largeInteger.HighPart = (int)(int64Value >> 32); ;
+ largeInteger.LowPart = unchecked((int)(int64Value & 0xFFFFFFFF));
+ return largeInteger;
+ }
+
+ ///
+ /// Determines whether is a valid distinguished name.
+ ///
+ /// The string representation of the distinguished name to validate.
+ ///
+ /// true if is valid; otherwise, false.
+ ///
+ /// A DN is valid if it can be processed by the AD API. This method does not test RFC 2253 compliance,
+ /// but only basic syntactical check.
+ internal static bool IsValidDn(string dn)
+ {
+ var result = false;
+ try
+ {
+ if (getADSPathname( null, null, dn ) != null)
+ {
+ result = true;
+ }
+ }
+ catch (COMException comex)
+ {
+ if (comex.ErrorCode == -2147463168) //E_ADS_BAD_PATHNAME
+ {
+ result = false;
+ }
+ else
+ {
+ throw;
+ }
+ }
+ return result;
+ }
+ }
+
+}
diff --git a/dotnet-connector/ActiveDirectoryConnector/CommonUtils.cs b/dotnet-connector/ActiveDirectoryConnector/CommonUtils.cs
new file mode 100644
index 0000000..ae61e3b
--- /dev/null
+++ b/dotnet-connector/ActiveDirectoryConnector/CommonUtils.cs
@@ -0,0 +1,73 @@
+/*
+ * ====================
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of the Common Development
+ * and Distribution License("CDDL") (the "License"). You may not use this file
+ * except in compliance with the License.
+ *
+ * You can obtain a copy of the License at
+ * http://IdentityConnectors.dev.java.net/legal/license.txt
+ * See the License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * When distributing the Covered Code, include this CDDL Header Notice in each file
+ * and include the License file at identityconnectors/legal/license.txt.
+ * If applicable, add the following below this CDDL Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ * ====================
+ */
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.IO;
+using Org.IdentityConnectors.Common;
+using Org.IdentityConnectors.Framework.Common.Objects;
+using Org.IdentityConnectors.Framework.Common.Serializer;
+
+namespace Org.IdentityConnectors.ActiveDirectory
+{
+ public class CommonUtils
+ {
+ ///
+ /// reads the object class info definitions from xml
+ ///
+ ///Dictionary of object classes
+ protected internal static IDictionary GetOCInfo(string name)
+ {
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ Stream stream = assembly.GetManifestResourceStream(name);
+
+ Assertions.NullCheck(stream, "stream");
+
+ //we just read
+ TextReader streamReader = new StreamReader(stream);
+ String xml;
+ try
+ {
+ xml = streamReader.ReadToEnd();
+ }
+ finally
+ {
+ streamReader.Close();
+ }
+
+ //read from xml
+ var ret = (ICollection