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)SerializerUtil.DeserializeXmlObject(xml, true); + + Assertions.NullCheck(ret, "ret"); + + //create map of object infos + var map = new Dictionary(ret.Count); + foreach (ObjectClassInfo o in ret) + { + map.Add(new ObjectClass(o.ObjectType.ToString()), o); + } + + return map; + } + } +} diff --git a/dotnet-connector/ActiveDirectoryConnector/CustomAttributeHandlers.cs b/dotnet-connector/ActiveDirectoryConnector/CustomAttributeHandlers.cs new file mode 100644 index 0000000..66f8973 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/CustomAttributeHandlers.cs @@ -0,0 +1,1341 @@ +/* + * ==================== + * 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.Collections.Generic; +using System.Linq; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common; +using System.DirectoryServices; +using DS = System.DirectoryServices; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using System.Diagnostics; +using ActiveDs; +using System.IO; +using System.Security.AccessControl; +using System.Security.Principal; +using Org.IdentityConnectors.Common; + +namespace Org.IdentityConnectors.ActiveDirectory +{ + /// + /// This class will encapsulate all changes from AD attributes + /// to Connector attributes and from Connector attributes to + /// AD attributes. If attributes are more complex and can't be + /// handled here, add them to the appropriate ignore list, and handle + /// them in the AD Connector (or elsewhere). + /// + /// If it is a connector attribute that has the same name as the + /// ad attribute, and the value requires no translation, it will + /// be handled by the generic handler. If not, add a delegate for + /// either AD->Connector or for Connector->AD (or both). + /// + internal class CustomAttributeHandlers + { + // Max range retrieval to obtain the members of a group + private static readonly int GRP_MEMBERS_MAXRANGE = 1500; + // names from active directory attributes to ignore during + // generic translation + IList IgnoreADAttributeNames_account = new List(); + IList IgnoreADAttributeNames_group = new List(); + + // names from connector attributes to ignore during + // generic translation + IList IgnoreConnectorAttributeNames_account = new List(); + IList IgnoreConnectorAttributeNames_group = new List(); + IList IgnoreConnectorAttributeNames_ou = new List(); + IList IgnoreConnectorAttributeNames_generic = new List(); + + // method to update a directory entry from a connector attribute + Dictionary + UpdateDeFromCaDelegates = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + + // method to get a connector attribute from a directory entry + Dictionary + GetCaFromDeDelegates = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + + ActiveDirectoryConfiguration _configuration = null; + + internal CustomAttributeHandlers(ActiveDirectoryConfiguration configuration) { + // save the configuration + _configuration = configuration; + + // Connector attributes names to ignore for accounts + IgnoreConnectorAttributeNames_account.Add(Name.NAME); + IgnoreConnectorAttributeNames_account.Add(ActiveDirectoryConnector.ATT_CONTAINER); + IgnoreConnectorAttributeNames_account.Add(Uid.NAME); + IgnoreConnectorAttributeNames_account.Add(OperationalAttributes.PASSWORD_NAME); + IgnoreConnectorAttributeNames_account.Add(OperationalAttributes.CURRENT_PASSWORD_NAME); + + // Connector attributes names to ignore for groups + IgnoreConnectorAttributeNames_group.Add(Name.NAME); + IgnoreConnectorAttributeNames_group.Add(ActiveDirectoryConnector.ATT_CONTAINER); + IgnoreConnectorAttributeNames_group.Add(Uid.NAME); + IgnoreConnectorAttributeNames_group.Add("authOrig"); + IgnoreConnectorAttributeNames_group.Add("unauthOrig"); + IgnoreConnectorAttributeNames_group.Add("groupTypes"); + + // Connector attributes names to ignore for ous + IgnoreConnectorAttributeNames_ou.Add(Name.NAME); + IgnoreConnectorAttributeNames_ou.Add(Uid.NAME); + + // Connector attributes names to ignore for everything else + IgnoreConnectorAttributeNames_generic.Add(Name.NAME); + IgnoreConnectorAttributeNames_generic.Add(Uid.NAME); + + // methods to update a directory entry from a connectorattribute + UpdateDeFromCaDelegates.Add(ActiveDirectoryConnector.ATT_ACCOUNTS, + UpdateDeFromCa_OpAtt_Accounts); + UpdateDeFromCaDelegates.Add(PredefinedAttributes.GROUPS_NAME, + UpdateDeFromCa_OpAtt_Groups); + UpdateDeFromCaDelegates.Add(ActiveDirectoryConnector.ATT_HOME_DIRECTORY, + UpdateDeFromCa_Att_HomeDirectory); + UpdateDeFromCaDelegates.Add(OperationalAttributes.ENABLE_NAME, + UpdateDeFromCa_OpAtt_Enable); + UpdateDeFromCaDelegates.Add(OperationalAttributes.PASSWORD_EXPIRED_NAME, + UpdateDeFromCa_OpAtt_PasswordExpired); + UpdateDeFromCaDelegates.Add(OperationalAttributes.PASSWORD_EXPIRATION_DATE_NAME, + UpdateDeFromCa_OpAtt_PasswordExpireDate); + UpdateDeFromCaDelegates.Add(OperationalAttributes.LOCK_OUT_NAME, + UpdateDeFromCa_OpAtt_Lockout); + UpdateDeFromCaDelegates.Add(ActiveDirectoryConnector.ATT_PASSWORD_NEVER_EXPIRES, + UpdateDeFromCa_PasswordNeverExpires); + + // supporting class not implemented in the framework + /* + UpdateDeFromCaDelegates.Add(OperationalAttributes.ENABLE_DATE_NAME, + UpdateDeFromCa_OpAtt_EnableDate); + UpdateDeFromCaDelegates.Add(OperationalAttributes.DISABLE_DATE_NAME, + UpdateDeFromCa_OpAtt_DisableDate); + */ + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_ALLOW_LOGON, + UpdateDeFromCa_Att_TSAllowLogon); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_INITIAL_PROGRAM, + UpdateDeFromCa_Att_TSInitialProgram); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_INITIAL_PROGRAM_DIR, + UpdateDeFromCa_Att_TSInitialProgramDir); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_MAX_CONNECTION_TIME, + UpdateDeFromCa_Att_TSMaxConnectionTime); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_MAX_DISCONNECTION_TIME, + UpdateDeFromCa_Att_TSMaxDisconnectionTime); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_MAX_IDLE_TIME, + UpdateDeFromCa_Att_TSMaxIdleTime); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_CONNECT_CLIENT_DRIVES_AT_LOGON, + UpdateDeFromCa_Att_TSConnectClientDrivesAtLogon); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_CONNECT_CLIENT_PRINTERS_AT_LOGON, + UpdateDeFromCa_Att_TSConnectClientPrintersAtLogon); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_DEFAULT_TO_MAIN_PRINTER, + UpdateDeFromCa_Att_TSDefaultToMainPrinter); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_BROKEN_CONNECTION_ACTION, + UpdateDeFromCa_Att_TSBrokenConnectionAction); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_RECONNECTION_ACTION, + UpdateDeFromCa_Att_TSReconnectionAction); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_ENABLE_REMOTE_CONTROL, + UpdateDeFromCa_Att_TSEnableRemoteControl); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_PROFILE_PATH, + UpdateDeFromCa_Att_TSProfilePath); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_HOME_DIRECTORY, + UpdateDeFromCa_Att_TSHomeDirectory); + UpdateDeFromCaDelegates.Add(TerminalServicesUtils.TS_HOME_DRIVE, + UpdateDeFromCa_Att_TSHomeDrive); + + // methods to create a connector attribute from a directory entry + GetCaFromDeDelegates.Add(Name.NAME, GetCaFromDe_OpAtt_Name); + GetCaFromDeDelegates.Add(Uid.NAME, GetCaFromDe_OpAtt_Uid); + GetCaFromDeDelegates.Add(ActiveDirectoryConnector.ATT_CONTAINER, + GetCaFromDe_Att_Container); + GetCaFromDeDelegates.Add(ActiveDirectoryConnector.ATT_ACCOUNTS, + GetCaFromDe_OpAtt_Accounts); + GetCaFromDeDelegates.Add(PredefinedAttributes.GROUPS_NAME, + GetCaFromDe_OpAtt_Groups); + GetCaFromDeDelegates.Add(ActiveDirectoryConnector.ATT_MEMBER, + GetCaFromDe_OpAtt_GroupMembers); + GetCaFromDeDelegates.Add(OperationalAttributes.ENABLE_NAME, + GetCaFromDe_OpAtt_Enabled); + GetCaFromDeDelegates.Add(OperationalAttributes.PASSWORD_EXPIRED_NAME, + GetCaFromDe_OpAtt_PasswordExpired); + GetCaFromDeDelegates.Add(PredefinedAttributes.DESCRIPTION, + GetCaFromDe_OpAtt_Description); + GetCaFromDeDelegates.Add(PredefinedAttributes.SHORT_NAME, + GetCaFromDe_OpAtt_ShortName); + GetCaFromDeDelegates.Add(OperationalAttributes.LOCK_OUT_NAME, + GetCaFromDe_OpAtt_Lockout); + GetCaFromDeDelegates.Add(OperationalAttributes.PASSWORD_EXPIRATION_DATE_NAME, + GetCaFromDe_OpAtt_PasswordExpireDate); + GetCaFromDeDelegates.Add(ActiveDirectoryConnector.ATT_PASSWORD_NEVER_EXPIRES, + GetCaFromDe_PasswordNeverExpires); + // supporting class not implemented in the framework + /* + GetCaFromDeDelegates.Add(OperationalAttributes.ENABLE_DATE_NAME, + GetCaFromDe_OpAtt_EnableDate); + GetCaFromDeDelegates.Add(OperationalAttributes.DISABLE_DATE_NAME, + GetCaFromDe_OpAtt_DisableDate); + */ + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_INITIAL_PROGRAM, + GetCaFromDe_Att_TSInitialProgram); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_INITIAL_PROGRAM_DIR, + GetCaFromDe_Att_TSInitalProgramDir); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_ALLOW_LOGON, + GetCaFromDe_Att_TSAllowLogon); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_MAX_CONNECTION_TIME, + GetCaFromDe_Att_TSMaxConnectionTime); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_MAX_DISCONNECTION_TIME, + GetCaFromDe_Att_TSMaxDisconnectionTime); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_MAX_IDLE_TIME, + GetCaFromDe_Att_TSMaxIdleTime); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_CONNECT_CLIENT_DRIVES_AT_LOGON, + GetCaFromDe_Att_TSConnectClientDrivesAtLogon); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_CONNECT_CLIENT_PRINTERS_AT_LOGON, + GetCaFromDe_Att_TSConnectClientPrintersAtLogon); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_DEFAULT_TO_MAIN_PRINTER, + GetCaFromDe_Att_TSDefaultToMainPrinter); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_BROKEN_CONNECTION_ACTION, + GetCaFromDe_Att_TSBrokenConnectionAction); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_RECONNECTION_ACTION, + GetCaFromDe_Att_TSReconnectionAction); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_ENABLE_REMOTE_CONTROL, + GetCaFromDe_Att_TSEnableRemoteControl); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_PROFILE_PATH, + GetCaFromDe_Att_TSProfilePath); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_HOME_DIRECTORY, + GetCaFromDe_Att_TSHomeDirectory); + GetCaFromDeDelegates.Add(TerminalServicesUtils.TS_HOME_DRIVE, + GetCaFromDe_Att_TSHomeDrive); + } + + internal void UpdateDeFromCa(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) { + + // if this gets big, replace with dictionary key by object class + IList ignoreList = null; + if(oclass.Equals(ObjectClass.ACCOUNT)) { + ignoreList = IgnoreConnectorAttributeNames_account; + } + else if (oclass.Equals(ActiveDirectoryConnector.groupObjectClass)) + { + ignoreList = IgnoreConnectorAttributeNames_group; + } + else if (oclass.Equals(ActiveDirectoryConnector.ouObjectClass)) + { + ignoreList = IgnoreConnectorAttributeNames_ou; + } + else + { + ignoreList = IgnoreConnectorAttributeNames_generic; + } + + // if it's an ignored attribute, we're done + if ((ignoreList != null) && + (ignoreList.Contains(attribute.Name, + StringComparer.CurrentCultureIgnoreCase))) { + return; + } + + if (UpdateDeFromCaDelegates.ContainsKey(attribute.Name)) + { + // if it's an attribute with a special handler, + // call the handler + UpdateDeFromCa_delegate handler = + UpdateDeFromCaDelegates[attribute.Name]; + handler(oclass, type, directoryEntry, attribute); + } + else + { + // if none of the above, call the generic handler + UpdateDeFromCa_Att_Generic(oclass, type, directoryEntry, attribute); + } + } + + + internal ConnectorAttribute GetCaFromDe(ObjectClass oclass, + string attributeName, DS.SearchResult searchResult) + { + ConnectorAttribute attribute = null; + + if (GetCaFromDeDelegates.ContainsKey(attributeName)) + { + // if it's an attribute with a special handler, + // call the handler + GetCaFromDe_delegate handler = GetCaFromDeDelegates[attributeName]; + attribute = handler(oclass, attributeName, searchResult); + } + else + { + // if none of the above, call the generic handler + attribute = GetCaFromDe_Att_Generic(oclass, attributeName, searchResult); + } + + return attribute; + } + + internal delegate void UpdateDeFromCa_delegate(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute); + + internal delegate ConnectorAttribute GetCaFromDe_delegate(ObjectClass oclass, + string attributeName, DS.SearchResult searchResult); + + public void GetAddsAndDeletes(ICollectionvaluesToAdd, ICollectionvaluesToRemove, + PropertyValueCollection oldValues, ICollectionnewValues, UpdateType type) { + if (UpdateType.ADD.Equals(type)) + { + // add all groups + foreach (Object value in newValues) + { + valuesToAdd.Add(value); + } + } + else if (UpdateType.REPLACE.Equals(type)) + { + // look through existing values, and remove them + // if they are not in the newValues + if (oldValues != null) + { + foreach (Object value in oldValues) + { + if (!newValues.Contains(value)) + { + valuesToRemove.Add(value); + } + } + } + + // look through the values passed in and + // add them if they are not existing values + foreach (Object value in newValues) + { + if ((oldValues == null) || (!oldValues.Contains(value))) + { + valuesToAdd.Add(value); + } + } + } + else if (UpdateType.DELETE.Equals(type)) + { + foreach (Object value in newValues) + { + valuesToRemove.Add(value); + } + } + } + + #region UpdateDeFromCa handlers + + + + internal void UpdateDeFromCa_OpAtt_Groups(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + if (oclass.Equals(ObjectClass.ACCOUNT)) + { + // in this case, AD will not allow groups to be added + // to a user. To simulate this, lookup each added group + // and add this user to the group + ICollection newValues = attribute.Value; + PropertyValueCollection oldValues = null; + if(directoryEntry.Properties.Contains(ActiveDirectoryConnector.ATT_MEMBEROF)) { + oldValues = directoryEntry.Properties[ActiveDirectoryConnector.ATT_MEMBEROF]; + } + + ICollection groupsToAdd = new HashSet(); + ICollection groupsToRemove = new HashSet(); + + GetAddsAndDeletes(groupsToAdd, groupsToRemove, oldValues, newValues, type); + + foreach (Object obj in groupsToRemove) + { + // lookup the group and remove this user from group if it's a + // valid group. + String groupPath = ActiveDirectoryUtils.GetLDAPPath( + _configuration.LDAPHostName, (String)obj); + DirectoryEntry groupDe = new DirectoryEntry(groupPath, + _configuration.DirectoryAdminName, _configuration.DirectoryAdminPassword); + String distinguishedName = ActiveDirectoryUtils.GetDnFromPath(directoryEntry.Path); + groupDe.Properties[ActiveDirectoryConnector.ATT_MEMBER].Remove(distinguishedName); + groupDe.CommitChanges(); + groupDe.Dispose(); + } + + foreach (Object obj in groupsToAdd) + { + // lookup the group and add this user to group if it's a + // valid group. + String groupPath = ActiveDirectoryUtils.GetLDAPPath( + _configuration.LDAPHostName, (String)obj); + DirectoryEntry groupDe = new DirectoryEntry(groupPath, + _configuration.DirectoryAdminName, _configuration.DirectoryAdminPassword); + String distinguishedName = ActiveDirectoryUtils.GetDnFromPath(directoryEntry.Path); + groupDe.Properties[ActiveDirectoryConnector.ATT_MEMBER].Add(distinguishedName); + groupDe.CommitChanges(); + groupDe.Dispose(); + } + } + else + { + throw new ConnectorException( + String.Format("''{0}'' is an invalid attribute for object class ''{1}''", + PredefinedAttributeInfos.GROUPS, oclass.GetObjectClassValue())); + } + } + + internal void UpdateDeFromCa_OpAtt_Accounts(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + if (ActiveDirectoryConnector.groupObjectClass.Equals(oclass)) + { + // create an 'attribute' with the real name, and then call the + // generic version + ConnectorAttribute newAttribute = ConnectorAttributeBuilder.Build( + ActiveDirectoryConnector.ATT_MEMBER, attribute.Value); + UpdateDeFromCa_Att_Generic(oclass, type, directoryEntry, newAttribute); + } + else + { + throw new ConnectorException( + String.Format("'{0}' is an invalid attribute for object class '{1}'", + ActiveDirectoryConnector.ATT_ACCOUNTS, oclass.GetObjectClassValue())); + } + } + + internal void UpdateDeFromCa_Att_HomeDirectory(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + String homeDir = ConnectorAttributeUtil.GetStringValue(attribute); + + if (homeDir != null) + { + if (type == UpdateType.REPLACE) + { + // first set the attribute + UpdateDeFromCa_Att_Generic(oclass, type, directoryEntry, attribute); + + // now create attribute if needed/possible + if (_configuration.CreateHomeDirectory) + { + // from old code ... should start with '\\' and have at least one + // '\' later that's not the end of the string + // i.e + // \\somemachine\someshare\somedirectory + // \\somemachine\someshare\somedirectory\someotherdirectory + // but not + // \\somemachine\someshare\ + // \\somemachine\someshare + // just ignore if it's not correct + String directoryName = ConnectorAttributeUtil.GetStringValue(attribute); + if (directoryName.StartsWith("\\\\")) + { + int secondPathSepIndex = directoryName.IndexOf('\\', 2); + if ((secondPathSepIndex > 2) && (directoryName.Length > secondPathSepIndex + 1)) + { + // name passes, so create directory + + // create security object + DirectorySecurity dirSecurity = new DirectorySecurity(); + PropertyValueCollection pvc = + directoryEntry.Properties[ActiveDirectoryConnector.ATT_OBJECT_SID]; + // there should always be exactly one sid + SecurityIdentifier sid = new SecurityIdentifier((byte[])pvc[0], 0); + // dirSecurity.SetOwner(sid); + InheritanceFlags iFlags = InheritanceFlags.ContainerInherit; + dirSecurity.AddAccessRule( + new FileSystemAccessRule(sid, FileSystemRights.FullControl, + InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, + PropagationFlags.None, AccessControlType.Allow) + ); + Directory.CreateDirectory(directoryName, dirSecurity); + } + } + } + } + else + { + throw new ConnectorException("Only updatetype of replace is supported for home directory"); + } + } + } + + internal void UpdateDeFromCa_OpAtt_Enable(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) { + + // set the proper flag in the userAccountControl bitfield + PropertyValueCollection uacPvc = + directoryEntry.Properties[UserAccountControl.UAC_ATTRIBUTE_NAME]; + + UserAccountControl.Set(uacPvc, + UserAccountControl.ACCOUNTDISABLE, + // attribute is enable, but the flag is for + // disable, so send the opposite + !ConnectorAttributeUtil.GetBooleanValue(attribute)); + } + + internal void UpdateDeFromCa_OpAtt_PasswordExpired(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + bool? passwordExpired = ConnectorAttributeUtil.GetBooleanValue(attribute); + if ((passwordExpired.HasValue) && (passwordExpired.Value == true)) + { + directoryEntry.Properties[ActiveDirectoryConnector.ATT_PWD_LAST_SET].Clear(); + directoryEntry.Properties[ActiveDirectoryConnector.ATT_PWD_LAST_SET].Value = ActiveDirectoryUtils.GetLargeIntegerFromLong(0); + } + else + { + directoryEntry.Properties[ActiveDirectoryConnector.ATT_PWD_LAST_SET].Clear(); + directoryEntry.Properties[ActiveDirectoryConnector.ATT_PWD_LAST_SET].Value = ActiveDirectoryUtils.GetLargeIntegerFromLong(-1); + } + } + + internal void UpdateDeFromCa_OpAtt_PasswordExpireDate(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + DateTime? expireDate = ConnectorAttributeUtil.GetDateTimeValue(attribute); + if(expireDate.HasValue) { + directoryEntry.Properties[ActiveDirectoryConnector.ATT_ACCOUNT_EXPIRES].Value = + ActiveDirectoryUtils.GetLargeIntegerFromLong(expireDate.Value.ToFileTime()); + } + } + + internal void UpdateDeFromCa_OpAtt_Lockout(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + bool? lockout = ConnectorAttributeUtil.GetBooleanValue(attribute); + if (lockout.HasValue) + { + long lockoutTime = lockout.Value ? DateTimeUtil.GetCurrentUtcTimeMillis() : 0; + + if(lockoutTime != 0) { + throw new ConnectorException(_configuration.ConnectorMessages.Format( + "ex_LockAccountNotAllowed", "Active Directory does not support locking users. User may be unlocked only")); + } + directoryEntry.Properties[ActiveDirectoryConnector.ATT_LOCKOUT_TIME].Value = + ActiveDirectoryUtils.GetLargeIntegerFromLong(lockoutTime); + } + } + internal void UpdateDeFromCa_PasswordNeverExpires(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + bool? passwordNeverExpires = ConnectorAttributeUtil.GetBooleanValue(attribute); + + PropertyValueCollection pvc = + directoryEntry.Properties[ActiveDirectoryConnector.ATT_USER_ACOUNT_CONTROL]; + UserAccountControl.Set(pvc, + UserAccountControl.DONT_EXPIRE_PASSWORD, + passwordNeverExpires); + } + // supporting class not implemented in the framework +/* + internal void UpdateDeFromCa_OpAtt_EnableDate(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + } + + internal void UpdateDeFromCa_OpAtt_DisableDate(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + / * + long? utcMilliDate = ConnectorAttributeUtil.GetLongValue(attribute); + if(utcMilliDate == null) { + return; + } + + DateTime disableDate = DateTime.FromFileTimeUtc((long)utcMilliDate); + + LargeInteger disableTicks = new LargeIntegerClass(); + disableTicks.HighPart = (int)((disableDate.Ticks >> 32) & 0xFFFFFFFF); + disableTicks.LowPart = (int)(disableDate.Ticks & 0xFFFFFFFF); + + PropertyValueCollection pvc = directoryEntry.Properties["accountExpires"]; + if ((pvc == null) || (pvc.Count == 0)) + { + // if nothing there, add the value + pvc.Add(disableTicks); + } + else + { + // set the value + pvc[0] = disableTicks; + } + * / + } +*/ + internal void UpdateDeFromCa_Att_TSAllowLogon(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetAllowLogon(type, directoryEntry, + ConnectorAttributeUtil.GetIntegerValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSInitialProgram(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetInitialProgram(type, directoryEntry, + ConnectorAttributeUtil.GetStringValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSInitialProgramDir(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetInitialProgramDir(type, directoryEntry, + ConnectorAttributeUtil.GetStringValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSMaxConnectionTime(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetMaxConnectionTime(type, directoryEntry, + ConnectorAttributeUtil.GetIntegerValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSMaxDisconnectionTime(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetMaxDisconnectionTime(type, directoryEntry, + ConnectorAttributeUtil.GetIntegerValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSMaxIdleTime(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetMaxIdleTime(type, directoryEntry, + ConnectorAttributeUtil.GetIntegerValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSConnectClientDrivesAtLogon(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetConnectClientDrivesAtLogon(type, directoryEntry, + ConnectorAttributeUtil.GetIntegerValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSConnectClientPrintersAtLogon(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetConnectClientPrintersAtLogon(type, directoryEntry, + ConnectorAttributeUtil.GetIntegerValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSDefaultToMainPrinter(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetDefaultToMainPrinter(type, directoryEntry, + ConnectorAttributeUtil.GetIntegerValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSBrokenConnectionAction(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetBrokenConnectionAction(type, directoryEntry, + ConnectorAttributeUtil.GetIntegerValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSReconnectionAction(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetReconnectionAction(type, directoryEntry, + ConnectorAttributeUtil.GetIntegerValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSEnableRemoteControl(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetEnableRemoteControl(type, directoryEntry, + ConnectorAttributeUtil.GetIntegerValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSProfilePath(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetProfilePath(type, directoryEntry, + ConnectorAttributeUtil.GetStringValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSHomeDirectory(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetHomeDirectory(type, directoryEntry, + ConnectorAttributeUtil.GetStringValue(attribute)); + } + + internal void UpdateDeFromCa_Att_TSHomeDrive(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + TerminalServicesUtils.SetHomeDrive(type, directoryEntry, + ConnectorAttributeUtil.GetStringValue(attribute)); + } + + internal void UpdateDeFromCa_Att_Generic(ObjectClass oclass, + UpdateType type, DirectoryEntry directoryEntry, + ConnectorAttribute attribute) + { + + // null out the values if we are replacing attributes. + if (type.Equals(UpdateType.REPLACE)) + { + // There is a problem where some attributes cant be set + // even if the value is just being set to the same as + // before. This especially comes up for RDN (such as + // cn and ou). As a workaround, and for backward compatibility + // with IDM, if the value is the same, just ignore it. + + // check equality + IList attributeValue = attribute.Value; + PropertyValueCollection pvc = directoryEntry.Properties[attribute.Name]; + if((attributeValue != null) && (pvc != null) && (attributeValue.Count == pvc.Count)) + { + Boolean valueEqual = true; + + foreach (Object attValueObj in attributeValue) + { + if (!pvc.Contains(attValueObj)) + { + valueEqual = false; + break; + } + } + if (valueEqual) + { + // the value is already set, so just return without doing anything + return; + } + } + directoryEntry.Properties[attribute.Name].Value = null; + } + + if (attribute.Value == null) + { + return; + } + // 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) + { + directoryEntry.Properties[attribute.Name].Add(valueObject); + } + } + else if (type.Equals(UpdateType.DELETE)) + { + // if deleting, find the values, + // and remove them if they exist + if (directoryEntry.Properties.Contains(attribute.Name)) + { + PropertyValueCollection pvc = directoryEntry.Properties[attribute.Name]; + + foreach (Object valueObject in attribute.Value) + { + if (pvc.Contains(valueObject)) + { + pvc.Remove(valueObject); + } + } + } + + } + } + + #endregion + + #region GetCaFromDe Handlers + private ConnectorAttribute GetCaFromDe_Att_Generic( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + ConnectorAttributeBuilder attributeBuilder = new ConnectorAttributeBuilder(); + + if (attributeName == null) + { + return null; + } + + attributeBuilder.Name = attributeName; + + ResultPropertyValueCollection pvc = null; + if (searchResult.Properties.Contains(attributeName)) + { + pvc = searchResult.Properties[attributeName]; + } + + if (pvc == null) + { + return 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}\'", + attributeName, i, pvc[i].GetType(), pvc[i].ToString()); + attributeBuilder.AddValue(pvc[i].ToString()); + } + } + } + + return attributeBuilder.Build(); + } + + private ConnectorAttribute GetCaFromDe_OpAtt_GroupMembers( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + ConnectorAttributeBuilder attributeBuilder = new ConnectorAttributeBuilder(); + + if (attributeName == null) + { + return null; + } + + attributeBuilder.Name = attributeName; + + ResultPropertyValueCollection pvc = null; + if (searchResult.Properties.Contains(attributeName)) + { + pvc = searchResult.Properties[attributeName]; + } + else + // Group without members + { + return null; + } + + // Range issue most probably... + // see: http://msdn.microsoft.com/en-us/library/ms817827.aspx + if (pvc.Count == 0) + { + DirectoryEntry entry = null; + DirectorySearcher searcher = null; + + int first = 0; + int last = first + (GRP_MEMBERS_MAXRANGE - 1); + bool badQuery = false; + bool quit = false; + string memberRange; + + try + { + entry = searchResult.GetDirectoryEntry(); + searcher = new DirectorySearcher(entry); + searcher.Filter = "(objectClass=*)"; + do + { + if (!badQuery) + { + memberRange = String.Format("member;range={0}-{1}", first, last); + } + else + { + memberRange = String.Format("member;range={0}-*", first); + } + searcher.PropertiesToLoad.Clear(); + searcher.PropertiesToLoad.Add(memberRange); + DS.SearchResult sresult = searcher.FindOne(); + if (sresult.Properties.Contains(memberRange)) + { + foreach (object valueObject in sresult.Properties[memberRange]) + { + if ((valueObject == null) || (FrameworkUtil.IsSupportedAttributeType(valueObject.GetType()))) + { + attributeBuilder.AddValue(valueObject); + } + } + if (badQuery) + { + quit = true; + } + } + else + { + badQuery = true; + } + if (!badQuery) + { + first = last + 1; + last = first + (GRP_MEMBERS_MAXRANGE - 1); + } + } + while (!quit); + } + catch (Exception ex) + { + } + finally + { + if (entry != null) + { + entry.Dispose(); + } + if (searcher != null) + { + searcher.Dispose(); + } + } + return attributeBuilder.Build(); + } + else if (pvc == null) + { + return 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}\'", + attributeName, i, pvc[i].GetType(), pvc[i].ToString()); + attributeBuilder.AddValue(pvc[i].ToString()); + } + } + } + return attributeBuilder.Build(); + } + private ConnectorAttribute GetCaFromDe_OpAtt_Name( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + String value = null; + ResultPropertyValueCollection pvc = null; + + pvc = searchResult.Properties[ActiveDirectoryConnector.ATT_DISTINGUISHED_NAME]; + if ((pvc != null) && (pvc.Count == 1) && (pvc[0] is String)) + { + value = (String)pvc[0]; + } + else + { + throw new ConnectorException("There should be exactly one value for the name attribute"); + } + + return ConnectorAttributeBuilder.Build(Name.NAME, ActiveDirectoryUtils.NormalizeLdapString(value)); + } + + private ConnectorAttribute GetCaFromDe_OpAtt_Uid( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + ICollection value = new List(); + + // uid is objectGuid + ResultPropertyValueCollection pvc = + searchResult.Properties[ActiveDirectoryConnector.ATT_OBJECT_GUID]; + + if ((pvc.Count == 1) && (pvc[0] is Byte[])) + { + value.Add(ActiveDirectoryUtils.ConvertUIDBytesToGUIDString((Byte[])pvc[0])); + } + else if (pvc.Count > 1) + { + throw new ConnectorException("There should be only one UID, but multiple values were specified"); + } + + return ConnectorAttributeBuilder.Build(Uid.NAME, value); + } + + private ConnectorAttribute GetCaFromDe_Att_Container( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + if (searchResult == null) + { + return null; + } + + DirectoryEntry de = searchResult.GetDirectoryEntry(); + DirectoryEntry parentDe = de.Parent; + String container = ""; + if (parentDe != null) + { + container = ActiveDirectoryUtils.GetDnFromPath(parentDe.Path); + } + parentDe.Dispose(); + de.Dispose(); + return ConnectorAttributeBuilder.Build( + ActiveDirectoryConnector.ATT_CONTAINER, container); + } + + private ConnectorAttribute GetCaFromDe_OpAtt_Groups( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + ConnectorAttribute realAttribute = GetCaFromDe_Att_Generic( + oclass, ActiveDirectoryConnector.ATT_MEMBEROF, searchResult); + if (realAttribute == null) + { + return null; + } + else + { + return ConnectorAttributeBuilder.Build(PredefinedAttributes.GROUPS_NAME, + realAttribute.Value); + } + } + + private ConnectorAttribute GetCaFromDe_OpAtt_Accounts( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + ConnectorAttribute realAttribute = GetCaFromDe_Att_Generic( + oclass, ActiveDirectoryConnector.ATT_MEMBER, searchResult); + if (realAttribute == null) + { + return null; + } + else + { + return ConnectorAttributeBuilder.Build(ActiveDirectoryConnector.ATT_ACCOUNTS, + realAttribute.Value); + } + } + + private ConnectorAttribute GetCaFromDe_OpAtt_Enabled( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + if (searchResult == null) + { + return null; + } + DirectoryEntry de = searchResult.GetDirectoryEntry(); + bool disabled = UserAccountControl.IsSet( + de.Properties[UserAccountControl.UAC_ATTRIBUTE_NAME], + UserAccountControl.ACCOUNTDISABLE); + de.Dispose(); + return ConnectorAttributeBuilder.BuildEnabled(!disabled); + } + + private ConnectorAttribute GetCaFromDe_OpAtt_PasswordExpired( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + ConnectorAttribute realAttribute = GetCaFromDe_Att_Generic( + oclass, ActiveDirectoryConnector.ATT_PWD_LAST_SET, searchResult); + if (realAttribute != null) + { + long? lastSetDate = ConnectorAttributeUtil.GetLongValue(realAttribute); + if ((lastSetDate.HasValue) && (lastSetDate.Value != 0)) + { + return ConnectorAttributeBuilder.BuildPasswordExpired(false); + } + else + { + return ConnectorAttributeBuilder.BuildPasswordExpired(true); + } + } + return null; + } + + private ConnectorAttribute GetCaFromDe_OpAtt_Description( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + ConnectorAttribute realDescription = GetCaFromDe_Att_Generic( + oclass, ActiveDirectoryConnector.ATT_DESCRIPTION, searchResult); + + if (realDescription != null) + { + string description = ConnectorAttributeUtil.GetStringValue(realDescription); + + if (description != null) + { + return ConnectorAttributeBuilder.Build(PredefinedAttributes.DESCRIPTION, description); + } + } + return null; + } + + private ConnectorAttribute GetCaFromDe_OpAtt_ShortName( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + ConnectorAttribute realShortName = GetCaFromDe_Att_Generic( + oclass, ActiveDirectoryConnector.ATT_SHORT_NAME, searchResult); + + if (realShortName != null) + { + string shortName = ConnectorAttributeUtil.GetStringValue(realShortName); + + if (shortName != null) + { + return ConnectorAttributeBuilder.Build(PredefinedAttributes.SHORT_NAME, shortName); + } + } + return null; + } + + private ConnectorAttribute GetCaFromDe_OpAtt_PasswordExpireDate( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + // get the value from ad + ConnectorAttribute accountExpireAttribute = GetCaFromDe_Att_Generic( + oclass, ActiveDirectoryConnector.ATT_ACCOUNT_EXPIRES, searchResult); + + // now change name + if (accountExpireAttribute != null) + { + long? expireValue = ConnectorAttributeUtil.GetLongValue(accountExpireAttribute); + // if value present and not set to never expires + if ((expireValue != null) && (!expireValue.Value.Equals(9223372036854775807))) + { + DateTime expireDate = DateTime.FromFileTime(expireValue.Value); + return ConnectorAttributeBuilder.BuildPasswordExpirationDate(expireDate); + } + else + { + return null; + } + } + return null; + } + + private ConnectorAttribute GetCaFromDe_OpAtt_Lockout( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + bool locked = false; + + ConnectorAttribute realAttribute = GetCaFromDe_Att_Generic( + oclass, ActiveDirectoryConnector.ATT_LOCKOUT_TIME, searchResult); + if (realAttribute != null) + { + long? lockoutDate = ConnectorAttributeUtil.GetLongValue(realAttribute); + if ((lockoutDate.HasValue) && (lockoutDate.Value != 0)) + { + // if there is a date (non zero), then the account + // is locked + locked = true; + } + } + return ConnectorAttributeBuilder.BuildLockOut(locked); + } + + private ConnectorAttribute GetCaFromDe_PasswordNeverExpires( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + ConnectorAttribute ca = null; + DirectoryEntry de = searchResult.GetDirectoryEntry(); + if(de != null) { + PropertyValueCollection pvc = + de.Properties[ActiveDirectoryConnector.ATT_USER_ACOUNT_CONTROL]; + if (pvc != null) + { + bool pne = UserAccountControl.IsSet(pvc, + UserAccountControl.DONT_EXPIRE_PASSWORD); + ca = ConnectorAttributeBuilder.Build(attributeName, pne); + } + de.Dispose(); + } + return ca; + } + + // supporting class not implemented in the framework +/* + private ConnectorAttribute GetCaFromDe_OpAtt_EnableDate( + ObjectClass oclass, string attributeName, SearchResult searchResult) + { + return null; + } + + private ConnectorAttribute GetCaFromDe_OpAtt_DisableDate( + ObjectClass oclass, string attributeName, SearchResult searchResult) + { + / * + if (searchResult == null) + { + return null; + } + + ResultPropertyValueCollection rpvc = + searchResult.Properties["accountExpires"]; + if(rpvc.Count == 0) { + return null; + } + + long ticks = (long)rpvc[0]; + if ((ticks < DateTime.MinValue.Ticks) || (ticks > DateTime.MaxValue.Ticks)) + { + return null; + } + + DateTime disableDate = new DateTime(ticks); + + return ConnectorAttributeBuilder.BuildDisableDate(disableDate); + * / + return null; + } + */ + + private ConnectorAttribute GetCaFromDe_Att_TSInitialProgram( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute(TerminalServicesUtils.TS_INITIAL_PROGRAM, + TerminalServicesUtils.GetInitialProgram(searchResult)); + } + + private ConnectorAttribute GetCaFromDe_Att_TSInitalProgramDir( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute(TerminalServicesUtils.TS_INITIAL_PROGRAM_DIR, + TerminalServicesUtils.GetInitialProgramDir(searchResult)); + } + + + private ConnectorAttribute GetCaFromDe_Att_TSAllowLogon( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute(TerminalServicesUtils.TS_ALLOW_LOGON, + TerminalServicesUtils.GetAllowLogon(searchResult)); + } + + private ConnectorAttribute GetCaFromDe_Att_TSMaxConnectionTime( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute(TerminalServicesUtils.TS_MAX_CONNECTION_TIME, + TerminalServicesUtils.GetMaxConnectionTime(searchResult)); + } + + private ConnectorAttribute GetCaFromDe_Att_TSMaxDisconnectionTime( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute(TerminalServicesUtils.TS_MAX_DISCONNECTION_TIME, + TerminalServicesUtils.GetMaxDisconnectionTime(searchResult)); + } + + private ConnectorAttribute GetCaFromDe_Att_TSMaxIdleTime( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute(TerminalServicesUtils.TS_MAX_IDLE_TIME, + TerminalServicesUtils.GetMaxIdleTime(searchResult)); + } + + private ConnectorAttribute GetCaFromDe_Att_TSConnectClientDrivesAtLogon( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute(TerminalServicesUtils.TS_CONNECT_CLIENT_DRIVES_AT_LOGON, + TerminalServicesUtils.GetConnectClientDrivesAtLogon(searchResult)); + } + + private ConnectorAttribute GetCaFromDe_Att_TSConnectClientPrintersAtLogon( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute( + TerminalServicesUtils.TS_CONNECT_CLIENT_PRINTERS_AT_LOGON, + TerminalServicesUtils.GetConnectClientPrintersAtLogon(searchResult)); + } + + private ConnectorAttribute GetCaFromDe_Att_TSDefaultToMainPrinter( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute( + TerminalServicesUtils.TS_DEFAULT_TO_MAIN_PRINTER, + TerminalServicesUtils.GetDefaultToMainPrinter(searchResult)); + } + + private ConnectorAttribute GetCaFromDe_Att_TSBrokenConnectionAction( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute( + TerminalServicesUtils.TS_BROKEN_CONNECTION_ACTION, + TerminalServicesUtils.GetBrokenConnectionAction(searchResult)); + } + + private ConnectorAttribute GetCaFromDe_Att_TSReconnectionAction( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute(TerminalServicesUtils.TS_RECONNECTION_ACTION, + TerminalServicesUtils.GetReconnectionAction(searchResult)); + } + + private ConnectorAttribute GetCaFromDe_Att_TSEnableRemoteControl( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute(TerminalServicesUtils.TS_ENABLE_REMOTE_CONTROL, + TerminalServicesUtils.GetEnableRemoteControl(searchResult)); + } + + private ConnectorAttribute GetCaFromDe_Att_TSProfilePath( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute(TerminalServicesUtils.TS_PROFILE_PATH, + TerminalServicesUtils.GetProfilePath(searchResult)); + } + private ConnectorAttribute GetCaFromDe_Att_TSHomeDirectory( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute(TerminalServicesUtils.TS_HOME_DIRECTORY, + TerminalServicesUtils.GetHomeDirectory(searchResult)); + } + private ConnectorAttribute GetCaFromDe_Att_TSHomeDrive( + ObjectClass oclass, string attributeName, DS.SearchResult searchResult) + { + return ReturnConnectorAttribute(TerminalServicesUtils.TS_HOME_DRIVE, + TerminalServicesUtils.GetHomeDrive(searchResult)); + } + + #endregion + + internal ConnectorAttribute ReturnConnectorAttribute + (string name, T value) { + ConnectorAttribute newAttribute = null; + + if (value != null) + { + newAttribute = ConnectorAttributeBuilder.Build( + name, value); + } + return newAttribute; + } + } + +} diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.de-DE.resx b/dotnet-connector/ActiveDirectoryConnector/Messages.de-DE.resx new file mode 100755 index 0000000..6c3f405 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/Messages.de-DE.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Windows Active Directory-Connector + + + Ausgangsverzeichnis erstellen + + + Konto des Verzeichnisadministrators + + + Passwort des Verzeichnisadministrators + + + Domänenname + + + Active Directory Domänencontroller-Hostname + + + Objektklasse für Benutzerobjekte + + + Untergeordnete Domänen durchsuchen + + + Container + + + Domänencontroller synchronisieren + + + Globalen Katalogserver synchronisieren + + + Suchkontext + + + Geben Sie an, ob das Ausgangsverzeichnis des Benutzers erstellt werden soll oder nicht. + + + Geben Sie den Benutzernamen des Administrators ein, mit dem das System authentifiziert werden soll. Die Einstellung kann entweder ein Benutzername oder 'domänenname'\'benutzername' sein. + + + Geben Sie das Passwort ein, dass bei der Authentifizierung verwendet werden soll. + + + Der Name der Windows-Domäne (z. B. windowsdomaene.eigenefirma.com) + + + Für die domänenübergreifende Administration geben Sie den Hostnamen, die IP-Adresse oder den Domänennamen des LDAP-Servers ein. + + + Geben Sie die Aktive Directory-Objektklasse für die Benutzerobjekte an, die auf dieser Ressource verwaltet werden. Die Standardeinstellung lautet User. Für die meisten Anwendungen ist diese Einstellung ausreichend. + + + Wählen Sie, ob bei Suchabfragen am Active Directory auch untergeordnete Domänen eingeschlossen werden sollen. Außerdem müssen die Attribute "Suchcontainer" und "Suchkontext synchronisieren" (siehe Synchronisationseinstellungen) auf die oberste übergeordnete Domäne festgelegt werden, z. B. DC=mydomain,DC=com. + + + Geben Sie ein Containerobjekt an, das als Standard-Root für alle Suchvorgänge fungiert. Es werden nur Objekte unter diesem Container durchsucht, es sei denn, in dem Suchvorgang werden explizit weitere Kriterien übergeben. Wenn Sie beispielsweise Benutzer aus dem Container "Users" abrufen wollen, geben Sie Folgendes ein: CN=Users,DC=MYDOMAIN,DC=COM. Wenn kein Wert angegeben wurde, wird der Wert des ''Base Container''-Attributs verwendet. + + + Bei einer aktiven Synchronisation zu verwendender Domänencontroller. Wird nur dann verwendet, wenn keine untergeordneten Domänen durchsucht werden. + + + Name des globalen Katalogservers. Dieser Name ist nur dann erforderlich, wenn untergeordnete Domänen durchsucht werden. + + + Geben Sie an, wo im ADSI-Verzeichnis gesucht werden soll, wenn versucht wird, das durch searchAttributes festgelegte Attribut in das native DN-Format aufzulösen. + + + Der Connector wurde nicht konfiguriert + + + Für die Objektklasse {0} wird kein Löschvorgang unterstützt. + + + Ungültige Objektklasse: {0} + + + Das Attribut {0} ist nicht im Connector-Objekt vorhanden. Synchronisierung kann nicht fortgesetzt werden + + + Der Name des funktionsbereiten Attributs kann nicht Null lauten + + + Für die Objektklasse {0} ist kein Synchronisierungsvorgang vorhanden. + + + Keine UID vorhanden + + + In der Connector-Konfiguration wurde eine ungültige Objektklasse angegeben. Die Objektklasse \'{0}\' konnte im Active Directory nicht gefunden werden + + + Zu dem Suchergebnis <Null> konnte kein Connector-Attribut hinzugefügt werden + + + Zu dem Verzeichniseintrag <Null> konnte kein Connector-Attribut hinzugefügt werden + + + Einzelner Wert wurde erwartet, es wurden aber mehrere Werte für das Attribut {0} gefunden + + + Die Objektklasse \'{0}\' ist für diesen Connector nicht zulässig + + + Für den Benutzer {0} wurden ungültige Anmeldedaten angegeben + + + Die Passwortablauffrist kann nur durch Zurücksetzen des Passworts zurückgesetzt werden. + + + Active Directory unterstützt das Sperren von Benutzern nicht. Der Benutzer kann nur freigegeben werden + + + Es wurde ein ungültiger Suchbereich angegeben: {0} + + + Bei der Validierung des Benutzers {0} ist ein Ausnahmefehler aufgetreten. Der Benutzer wurde erfolgreich authentifiziert, aber die GUI des Benutzers konnte nicht ermittelt werden. + + + Bei der Validierung des Benutzers {0} ist ein Ausnahmefehler aufgetreten. Der Benutzer wurde erfolgreich authentifiziert, aber die SID des Benutzers konnte nicht ermittelt werden. + + + Der Name des Verzeichnisadministrators wurde nicht angegeben. + + + Das Passwort des Verzeichnisadministrators wurde nicht angegeben. + + + Der Domänenname wurde nicht angegeben. + + + Die Objektklasse wurde nicht angegeben. + + + Der Suchcontainer wurde nicht angegeben. + + + Verwenden der Identity Manager Ressourcenadapter-Stilabfrage '{0}'. Dies sollte aktualisiert werden, um die neue Connector-Abfragesyntax zu verwenden. + + + Es wurde ein ungültiger Container angegeben: {0} + + + Shell-Skript Variablenpräfix + + + Ein Präfix, der zu allen Shell-Skript-Argumentnamen hinzugefügt wird. Beispiel: Wenn das Präfix auf "Connector_" gesetzt ist, wird das Argument "User" zu "Connector_User". + + + Das Benutzerkonto wurde gesperrt + + + Das Benutzerpasswort muss geändert werden + + + Für den Benutzer {0} ist das Benutzerkonto abgelaufen + + + Es wurde ein ungültiger Suchkontext angegeben: {0} + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.en.Designer.cs b/dotnet-connector/ActiveDirectoryConnector/Messages.en.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.en.resx b/dotnet-connector/ActiveDirectoryConnector/Messages.en.resx new file mode 100644 index 0000000..aacdfbf --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/Messages.en.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ActiveDirectory Connector + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.es-ES.resx b/dotnet-connector/ActiveDirectoryConnector/Messages.es-ES.resx new file mode 100644 index 0000000..3e98a6b --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/Messages.es-ES.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Conector de Windows Active Directory + + + Crear directorio principal + + + Cuenta de administrador de Directory + + + Contraseña de administrador de Directory + + + Nombre de dominio + + + Nombre de host del controlador de dominio de Active Directory + + + Clase de objeto para objetos de usuario + + + Dominios secundarios de búsqueda + + + Contenedor + + + Sincronizar controlador de dominio + + + Sincronizar servidor del catálogo global + + + Contexto de la búsqueda + + + Especifique si se debe crear el directorio principal para el usuario. + + + Introduzca el nombre de usuario del administrador con el que debe autenticarse el sistema. El valor puede ser un nombre de usuario o 'nombredominio'\'nombreusuario'. + + + Introduzca la contraseña que se debe usar al autenticar. + + + Nombre del dominio de Windows (ej. dominiowindows.miempresa.com) + + + En la administración de dominios cruzados, introduzca el nombre de host, la dirección IP o el nombre de dominio del servidor LDAP. + + + Especifique la clase de objeto de Active Directory para los objetos de usuario que se administrarán en este recurso. El valor predeterminado es User, que debe ser adecuado en la mayoría de los casos. + + + Indique si quiere que las búsquedas de Active Directory incluyan dominios secundarios. Además, los atributos Contenedor de búsqueda y Sincronizar contexto de búsqueda (véase la configuración de sincronización) deben estar configurados en la parte superior del dominio principal; por ejemplo, DC=mydomain,DC=com. + + + Especifique un objeto de contenedor que será el root predeterminado de todas la búsquedas. A no ser que se produzca una búsqueda explícita con otros criterios, sólo se buscarán los objetos de este contenedor. Por ejemplo, si desea recuperar usuarios del contenedor de usuarios, escriba CN=Users,DC=MYDOMAIN,DC=COM. Si no se especifica ningún valor, se utiliza el valor del atributo 'Base Container'. + + + Controlador de dominio que se debe usar al activar la sincronización. Sólo se utiliza cuando no se busca en dominios secundarios. + + + Nombre del servidor del catálogo global. Sólo es necesario cuando se busca en dominios secundarios. + + + Especifique dónde buscar en el directorio ADSI cuando se intente resolver el atributo especificado por Atributos de búsqueda al formato dn nativo. + + + No se ha configurado el conector + + + No se admite la eliminación para la clase de objeto {0} + + + Clase de objeto no válida: {0} + + + El atributo {0} no está presente en el objeto de conector. No se puede proceder a la sincronización. + + + El atributo operativo de nombre no puede ser nulo + + + La operación de sincronización no está disponible para la clase de objeto {0} + + + No había Uid + + + Se había especificado una clase de objeto no válida en la configuración del conector. La clase de objeto \'{0}\' no se ha encontrado en Active Directory + + + No se pudo agregar el atributo de conector al resultado de búsqueda <null> + + + No se pudo agregar el atributo de conector a la entrada de directorio <null> + + + Se esperaba un solo valor, pero se encontraron varios para el atributo {0} + + + La clase de objeto \'{0}\' no es válida para este conector + + + Credenciales suministradas para usuario {0} no válidas + + + La caducidad de la contraseña sólo puede restablecerse al restablecer la contraseña + + + Active Directory no admite bloqueo de usuarios. El usuario sólo se puede desbloquear + + + Se ha suministrado un ámbito de búsqueda no válido: {0} + + + Ha ocurrido una excepción durante la validación del usuario {0}. El usuario se ha autenticado satisfactoriamente, pero no se ha podido determinar su guid. + + + Ha ocurrido una excepción durante la validación del usuario {0}. El usuario se ha autenticado satisfactoriamente, pero no se ha podido determinar su sid. + + + No se ha suministrado el nombre del administrador de Directory. + + + No se ha suministrado la contraseña del administrador de Directory. + + + No se ha suministrado el nombre de dominio. + + + No se ha suministrado la clase de objeto. + + + No se ha suministrado el contenedor de búsqueda. + + + Se está usando el estilo de consulta del adaptador de recursos de Identity Manager '{0}'. Debe actualizarse para utilizar la sintaxis de consulta del nuevo conector. + + + Se ha suministrado un contenedor no válido: {0} + + + Prefijo variable de secuencia de comandos shell + + + Prefijo que se añadirá a todos los nombres de argumento de secuencia de comandos shell. Por ejemplo, si el prefijo se configura en "Conector_", un argumento llamado "Usuario" se convertirá en "Conector_Usuario" + + + La cuenta del usuario ha sido bloqueada + + + La contraseña de usuario se debe cambiar + + + Ha caducado la cuenta de usuario del usuario {0} + + + Se ha suministrado un contexto de búsqueda no válido: {0} + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.es.resx b/dotnet-connector/ActiveDirectoryConnector/Messages.es.resx new file mode 100644 index 0000000..aacdfbf --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/Messages.es.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ActiveDirectory Connector + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.fr-FR.resx b/dotnet-connector/ActiveDirectoryConnector/Messages.fr-FR.resx new file mode 100755 index 0000000..415467f --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/Messages.fr-FR.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Connecteur Windows Active Directory + + + Créer un répertoire personnel + + + Compte de l’administrateur de répertoires + + + Mot de passe de l’administrateur de répertoires + + + Nom du domaine + + + Nom d’hôte du contrôleur de domaine Active Directory + + + Classe d’objet utilisateur + + + Rechercher dans les domaines enfant + + + Conteneur + + + Synchr. le contrôleur de domaine + + + Synchr. le serveur de catalogue global + + + Contexte de recherche + + + Spécifiez si le répertoire personnel de l’utilisateur sera créé ou non. + + + Saisissez le nom d’utilisateur de l’administrateur que le système doit utiliser pour l’authentification. Ce paramètre peut correspondre à un nom d’utilisateur ou à nom_domaine\nom_utilisateur. + + + Saisissez le mot de passe utilisateur devant servir à l’authentification. + + + Nom du domaine Windows (par ex., domainewindows.monentreprise.com) + + + Pour l’administration interdomaine, saisissez le nom d’hôte, l’adresse IP ou le nom de domaine du serveur LDAP. + + + Spécifiez la classe d’objet Active Directory destinée aux objets utilisateur à gérer sur cette ressource. La classe par défaut est User (Utilisateur). Elle devrait convenir dans la majorité des cas. + + + Indiquez si les recherches effectuées dans Active Directory doivent inclure les domaines enfant. Assurez-vous par ailleurs que les attributs Conteneur de recherche et Synchr. le contexte de recherche (voir les paramètres de synchronisation) sont définis sur la racine du domaine parent (par ex., DC=mondomaine,DC=com). + + + Spécifiez un objet conteneur qui servira de racine par défaut à toutes les recherches. À moins qu’une recherche ne passe explicitement d’autres critères, seuls les objets situés sous ce conteneur feront l’objet d’une recherche. Par exemple, pour extraire les utilisateurs du conteneur Utilisateurs, saisissez CN=Utilisateurs,DC=MONDOMAINE,DC=COM. Si aucune valeur n’est spécifiée, c’est celle de l’attribut Conteneur de base qui est utilisée. + + + Contrôleur de domaine à utiliser lors d’une synchronisation active. Uniquement utilisé lorsque la recherche ne porte pas sur les domaines enfant. + + + Nom du serveur de catalogue global. Uniquement utilisé lorsque la recherche porte sur les domaines enfant. + + + Spécifiez où rechercher dans le répertoire ADSI lorsque vous tentez de résoudre l’attribut spécifié par searchAttributes au format DN natif. + + + Le connecteur n’a pas été configuré. + + + La suppression n’est pas prise en charge pour la classe d’objet {0}. + + + Classe d’objet incorrecte : {0} + + + L’attribut {0} ne figure pas dans l’objet connecteur. Impossible de poursuivre la synchronisation. + + + L’attribut opérationnel du nom doit être spécifié. + + + L’opération de synchronisation n’est pas disponible pour la classe d’objet {0}. + + + UID introuvable + + + Une classe d’objet erronée a été spécifiée dans la configuration du connecteur. Impossible de trouver la classe d’objet {0} dans Active Directory. + + + Impossible d’ajouter l’attribut de connecteur à un résultat de recherche <nul>. + + + Impossible d’ajouter l’attribut de connecteur à une entrée de répertoire <nul>. + + + Valeur unique attendue, alors que plusieurs valeurs ont été trouvées pour l’attribut {0}. + + + La classe d’objet {0} est incompatible avec ce connecteur. + + + Informations d’authentification incorrectes fournies pour l’utilisateur {0} + + + La date d’expiration du mot de passe peut uniquement être réinitialisée via la réinitialisation du mot de passe lui-même. + + + Active Directory ne prend pas en charge le verrouillage des utilisateurs. L’utilisateur doit impérativement être déverrouillé. + + + Une étendue de recherche incorrecte a été spécifiée : {0} + + + Une exception s’est produite lors de la validation de l’utilisateur {0}. L’utilisateur a bien été authentifié, mais son GUID n’a pas pu être déterminé. + + + Une exception s’est produite lors de la validation de l’utilisateur {0}. L’utilisateur a bien été authentifié, mais son SID n’a pas pu être déterminé. + + + Le nom de l’administrateur de répertoires n’a pas été fourni. + + + Le mot de passe de l’administrateur de répertoires n’a pas été fourni. + + + Le nom de domaine n’a pas été fourni. + + + La classe d’objet n’a pas été fournie. + + + Le conteneur de recherche n’a pas été fourni. + + + Utilisation de la requête de type Adaptateur de ressources Identity Manager {0}. Elle devrait être mise à jour afin d’utiliser la nouvelle syntaxe de requête du connecteur. + + + Un conteneur erroné a été fourni : {0} + + + Préfixe de script shell variable + + + Préfixe ajouté à tous les noms d’arguments de script shell. Par exemple, si le préfixe est défini sur Connector_, un argument appelé User est converti en Connector_User. + + + Le compte de l’utilisateur a été verrouillé. + + + Le mot de passe utilisateur doit être changé. + + + Le compte de l’utilisateur {0} a expiré. + + + Un contexte de recherche erroné a été fourni : {0} + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.it-IT.resx b/dotnet-connector/ActiveDirectoryConnector/Messages.it-IT.resx new file mode 100755 index 0000000..519638f --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/Messages.it-IT.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Connettore Windows Active Directory + + + Crea directory home + + + Account amministratore directory + + + Password amministratore directory + + + Nome dominio + + + Nome host controller di dominio Active Directory + + + Classe oggetto per oggetti utente + + + Ricerca nei domini figli + + + Contenitore + + + Sincronizza controller di dominio + + + Sincronizza server di catalogo globale + + + Contesto di ricerca + + + Specificare se verrà creata la directory home dell’utente. + + + Immettere il nome utente dell’amministratore con cui il sistema deve eseguire l’autenticazione. È possibile inserire direttamente il nome utente o usare il formato ’nome_dominio\’nome_utente’. + + + Immettere la password da utilizzare per l’autenticazione. + + + Nome del dominio Windows (ad es. nomedominio.azienda.com) + + + Per l’amministrazione interdominio, immettere nome host, indirizzo IP o nome dominio del server LDAP. + + + Specificare la classe oggetto di Active Directory per gli oggetti utente che verranno gestiti su questa risorsa. L’impostazione predefinita, User, dovrebbe essere appropriata per la maggior parte delle situazioni. + + + Selezionare questa opzione se le ricerche in Active Directory devono includere i domini figli. Inoltre, gli attributi Contenitore di ricerca e Sincronizza contesto di ricerca (vedere le impostazioni di sincronizzazione) devono essere impostati sul livello più elevato del dominio padre, es. DC=nomedominio,DC=com. + + + Specificare un oggetto contenitore che fungerà da radice predefinita per tutte le ricerche. A meno che la ricerca non passi esplicitamente altri criteri, verranno ricercati solo gli oggetti inclusi in questo contenitore. Ad esempio, se si desidera richiamare gli utenti dal contenitore Users, immettere CN=Users,DC=NOMEDOMINIO,DC=COM Se non si specifica un valore, viene usato quello dell’attributo ’Base Container’. + + + Il controller di dominio da usare durante Active Sync. Usato solo se non viene eseguita la ricerca nei domini figli. + + + Nome del server di catalogo globale. È richiesto solo se si esegue la ricerca nei domini figli. + + + Specificare dove cercare nella directory ADSI quando si cerca di risolvere l’attributo specificato da searchAttributes nel formato DN nativo. + + + Il connettore non è stato configurato + + + Eliminazione non supportata per la classe oggetto {0} + + + Classe oggetto non valida: {0} + + + L’attributo {0} non è presente nell’oggetto connettore. Impossibile procedere con la sincronizzazione + + + L’attributo operativo del nome non può essere nullo + + + L’operazione di sincronizzazione non è disponibile per la classe oggetto {0} + + + UID non presente + + + È stata specificata una classe oggetto non valida nella configurazione del connettore. Impossibile trovare la classe oggetto \’{0}\’ in Active Directory + + + Impossibile aggiungere l’attributo del connettore a risultati di ricerca nulli + + + Impossibile aggiungere l’attributo del connettore a una voce di directory nulla + + + Era atteso un valore singolo ma sono stati trovati più valori per l’attributo {0} + + + La classe oggetto \’{0}\’ non è valida per questo connettore + + + Le credenziali fornite per l’utente {0} non sono valide + + + La scadenza della password può essere modificata solo ripristinando la password + + + Active Directory non supporta il blocco degli utenti. Gli utenti possono solo essere sbloccati + + + È stato fornito un ambito di ricerca non valido: {0} + + + Si è verificata un’eccezione nella convalida dell’utente {0}. L’utente è stato autenticato correttamente ma non è stato possibile determinare il relativo GUID. + + + Si è verificata un’eccezione nella convalida dell’utente {0}. L’utente è stato autenticato correttamente ma non è stato possibile determinare il relativo SID. + + + Non è stato fornito il nome dell’amministratore della directory. + + + Non è stata fornita la password dell’amministratore della directory. + + + Non è stato fornito il nome del dominio. + + + Non è stata fornita la classe oggetto. + + + Non è stato fornito il contenitore di ricerca. + + + La query utilizza lo stile dell’adattatore di risorsa di Identity Manager ’{0}’. Deve essere aggiornata in modo da utilizzare la nuova sintassi di query del connettore. + + + È stato fornito un contenitore non valido: {0} + + + Prefisso variabili script della shell + + + Il prefisso che verrà aggiunto a tutti i nomi degli argomenti degli script della shell. Ad esempio, se il prefisso è impostato su "Connettore_" un argomento di nome "Utente" verrà trasformato in "Connettore_Utente" + + + L’account utente è stato bloccato + + + La password deve essere modificata + + + Account utente scaduto per l’utente {0} + + + È stato fornito un contesto di ricerca non valido: {0} + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.ja-JP.resx b/dotnet-connector/ActiveDirectoryConnector/Messages.ja-JP.resx new file mode 100755 index 0000000..df86522 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/Messages.ja-JP.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Windows Active Directory コネクタ + + + ホームディレクトリの作成 + + + ディレクトリ管理者のアカウント + + + ディレクトリ管理者のパスワード + + + ドメイン名 + + + Active Directory ドメインコントローラのホスト名 + + + ユーザーオブジェクトのオブジェクトクラス + + + 子ドメインの検索 + + + コンテナ + + + ドメインコントローラの同期 + + + グローバルカタログサーバーの同期 + + + 検索コンテキスト + + + ユーザー用ホームディレクトリを作成するかどうかを指定します。 + + + システムが認証に使用する管理者のユーザー名を入力します。ユーザー名または 'ドメイン名'\ユーザー名' の形式で設定できます。 + + + 認証に使用するパスワードを入力します。 + + + Windows のドメイン名 (例: windowsdomain.mycompany.com) + + + クロスドメイン管理を行う場合は、ホスト名、IP アドレス、または LDAP サーバーのドメイン名を入力します。 + + + このリソース上で管理するユーザーオブジェクトの Active Directory オブジェクトクラスを指定します。デフォルトは User で、多くの場合、この設定が適切です。 + + + Active Directory の検索に子ドメインを含めるかどうかを選択します。さらに、親ドメインの先頭に、DC=mydomain,DC=com のように、検索コンテナおよび検索コンテキスト (同期設定を参照) 属性を設定してください。 + + + すべての検索のデフォルトルートとなるコンテナオブジェクトを指定します。検索を明示的に他の条件に渡さない場合、このコンテナ内のオブジェクトのみが検索されます。たとえば、Users コンテナからユーザーを取得する場合は、CN=Users,DC=MYDOMAIN,DC=COM を入力します。値を指定しないと、'Base Container' 属性の値が使用されます。 + + + アクティブ同期中に使用するドメインコントローラ。子ドメインを検索しない場合にのみ使用します。 + + + グローバルカタログサーバーの名前。子ドメインを検索する場合にのみ必要です。 + + + 検索属性で指定された属性のネイティブ DN 形式への解決を試みる場合に、ADSI ディレクトリ中でどこを検索するかを指定します。 + + + コネクタが設定されていません + + + オブジェクトクラス {0} では削除はサポートされていません + + + 無効なオブジェクトクラス: {0} + + + コネクタオブジェクトに属性 {0} がありません。同期を続行できません + + + 名前のオペレーショナル属性を null にすることはできません + + + オブジェクトクラス {0} では同期動作はできません + + + UID がありません + + + コネクタ設定に無効なオブジェクトクラスが指定されました。Active Directory からオブジェクトクラス \'{0}\' が見つかりませんでした + + + <null> の検索結果にコネクタ属性を追加できませんでした + + + <null> のディレクトリ入力にコネクタ属性を追加できませんでした + + + 単一の値を要求しているときに、属性 {0} に対して複数の値が検索されました + + + オブジェクトクラス \'{0}\' はこのコネクタでは無効です + + + ユーザー {0} に無効な認証情報が指定されました + + + パスワードをリセットすることでのみパスワードを期限切れにすることができます + + + Active Directory はユーザーのロックをサポートしません。ユーザーのロック解除のみができます + + + 無効な検索範囲が指定されました: {0} + + + ユーザー {0} の検証時に例外が発生しました。ユーザーの認証は正常に実行されましたが、ユーザーの GUID を特定できませんでした。 + + + ユーザー {0} の検証時に例外が発生しました。ユーザーの認証は正常に実行されましたが、ユーザーの SID を特定できませんでした。 + + + ディレクトリ管理者の名前が指定されていません。 + + + ディレクトリ管理者のパスワードが指定されていません。 + + + ドメイン名が指定されていません。 + + + オブジェクトクラスが指定されていません。 + + + 検索コンテナが指定されていません。 + + + 識別情報マネージャーリソースアダプタのスタイルクエリー '{0}' を使用しています。新規コネクタのクエリー構文を使用するには、このスタイルクエリーを更新してください。 + + + 無効なコンテナが指定されました: {0} + + + シェルスクリプトの変数のプレフィックス + + + シェルスクリプトのすべての引数名に追加されるプレフィックス。たとえば、プレフィックスを "Connector_" に設定すると、引数 "User" は "Connector_User" になります + + + ユーザーのアカウントがロックされています + + + ユーザーのパスワードを変更してください + + + ユーザー {0} のユーザーアカウントの期限が切れています + + + 無効な検索コンテキストが指定されました: {0} + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.ko-KR.resx b/dotnet-connector/ActiveDirectoryConnector/Messages.ko-KR.resx new file mode 100755 index 0000000..c8854a6 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/Messages.ko-KR.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Windows Active Directory Connector + + + 홈 디렉토리 작성 + + + 디렉토리 관리자 계정 + + + 디렉토리 관리자 비밀번호 + + + 도메인 이름 + + + Active Directory 도메인 컨트롤러 호스트 이름 + + + 사용자 객체의 객체 클래스 + + + 하위 도메인 검색 + + + 컨테이너 + + + 도메인 컨트롤러 동기화 + + + 전역 카탈로그 서버 동기화 + + + 검색 컨텍스트 + + + 사용자의 홈 디렉토리를 만들지 여부를 지정합니다. + + + 시스템이 인증해야 하는 관리자 사용자 이름을 입력합니다. 설정은 사용자 이름 또는 'domainname'\'username'일 수 있습니다. + + + 인증할 때 사용해야 하는 비밀번호를 입력합니다. + + + Windows 도메인 이름(예: windowsdomain.mycompany.com) + + + 교차 도메인 관리의 경우 LDAP 서버의 호스트 이름, IP 주소 또는 도메인 이름을 입력합니다. + + + 이 자원에서 관리될 사용자 객체의 Active Directory 객체 클래스를 지정합니다. 기본값은 User이며, 대부분의 경우 기본값이 적절합니다. + + + Active Directory의 검색에 하위 도메인을 포함하려면 선택합니다. 또한 검색 컨테이너 및 검색 컨텍스트 동기화(동기화 설정 참조) 속성을 상위 도메인의 최상위로 설정해야 합니다(예: DC=mydomain,DC=com). + + + 모든 검색의 기본 루트가 되는 컨테이너 객체를 지정합니다. 검색이 명백히 다른 기준을 충족하지 않으면 이 컨테이너 아래에 있는 객체만 검색됩니다. 예를 들어 Users 컨테이너에서 사용자를 검색하려면 CN=Users,DC=MYDOMAIN,DC=COM을 입력합니다. 값을 지정하지 않으면 '기본 컨테이너' 속성 값이 사용됩니다. + + + 활성 동기화 중에 사용할 도메인 컨트롤러입니다. 하위 도메인을 검색하지 않는 경우에만 사용됩니다. + + + 전역 카탈로그 서버의 이름입니다. 이 이름은 하위 도메인을 검색하는 경우에만 필요합니다. + + + searchAttributes에 의하여 지정된 속성을 원래 DN 형식으로 변환할 때 찾을 ADSI의 디렉토리를 지정합니다. + + + 커넥터가 구성되지 않았습니다. + + + ObjectClass {0}은(는) 삭제할 수 없습니다. + + + 잘못된 객체 클래스: {0} + + + 속성 {0}이(가) 커넥터 객체에 없습니다. 동기화를 계속할 수 없습니다. + + + 이름 작업 속성은 null일 수 없습니다. + + + 동기화 작업을 ObjectClass {0}에 사용할 수 없습니다. + + + Uid가 없습니다. + + + 커넥터 구성에 잘못된 객체 클래스가 지정되었습니다. 객체 클래스 \'{0}\'이(가) Active Directory에 없습니다. + + + 커넥터 속성을 <null> 검색 결과에 추가할 수 없습니다. + + + 커넥터 속성을 <null> 디렉토리 입력 항목에 추가할 수 없습니다. + + + 속성 {0}에 대해 하나의 값이 검색되어야 하는데, 여러 개의 값이 검색되었습니다. + + + ObjectClass \'{0}\'이(가) 이 커넥터에 유효하지 않습니다. + + + 사용자 {0}에 대해 잘못된 자격 증명이 제공되었습니다. + + + 비밀번호 만료는 비밀번호 재설정을 통해서만 재설정할 수 있습니다. + + + Active Directory는 사용자 잠금을 지원하지 않습니다. 사용자의 잠금을 해제만 할 수 있습니다. + + + 잘못된 검색 범위가 입력되었습니다. {0} + + + 사용자 {0}의 유효성을 검사하는 동안 예외가 발생했습니다. 사용자가 인증되었지만 사용자의 guid는 확인하지 못했습니다. + + + 사용자 {0}의 유효성을 검사하는 동안 예외가 발생했습니다. 사용자가 인증되었지만 사용자의 sid는 확인하지 못했습니다. + + + 디렉토리 관리자 이름을 제공하지 않았습니다. + + + 디렉토리 관리자 비밀번호를 제공하지 않았습니다. + + + 도메인 이름을 제공하지 않았습니다. + + + ObjectClass를 제공하지 않았습니다. + + + 검색 컨테이너를 제공하지 않았습니다. + + + Identity Manger 자원 어댑터 스타일 쿼리 '{0}'을(를) 사용하고 있습니다. 새 커넥터 쿼리 구문을 사용하려면 이 쿼리를 업데이트해야 합니다. + + + 잘못된 컨테이너를 제공했습니다. {0} + + + 쉘 스크립트 변수 접두어 + + + 모든 쉘 스크립트 인수 이름에 추가되는 접두어입니다. 예를 들어 접두어가 "Connector_"로 설정된 경우 "User"라는 인수는 "Connector_User"가 됩니다. + + + 사용자의 계정이 잠겼습니다. + + + 사용자 비밀번호를 변경해야 합니다. + + + 사용자 {0}에 대한 사용자 계정이 만료되었습니다. + + + 잘못된 검색 컨텍스트를 제공했습니다. {0} + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.pt-BR.resx b/dotnet-connector/ActiveDirectoryConnector/Messages.pt-BR.resx new file mode 100755 index 0000000..86ac023 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/Messages.pt-BR.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Conector do Windows Active Directory + + + Criar diretório principal + + + Conta do administrador do diretório + + + Senha do administrador do diretório + + + Nome do domínio + + + Nome do host do controlador de domínio do Active Directory + + + Classe de objeto para objetos Usuário + + + Pesquisar domínios filhos + + + Recipiente + + + Controlador de domínio de sincronização + + + Servidor de catálogo global de sincronização + + + Contexto da pesquisa + + + Especifique se o diretório principal do usuário será criado ou não. + + + Digite o nome do usuário administrador com o qual o sistema deve fazer a autenticação. A configuração pode ser um nome do usuário ou 'domainname'\'username'. + + + Digite a senha que deverá ser usada na autenticação. + + + Nome do domínio do Windows (por exemplo, windowsdomain.mycompany.com) + + + Para administração de domínio cruzado, digite o nome do host, o endereço IP ou o nome de domínio do servidor LDAP. + + + Especifique a classe de objeto do Active Directory para objetos Usuário que serão gerenciados neste recurso. O padrão é Usuário e, na maioria das situações, isso deverá ser adequado. + + + Selecione se você desejar que as pesquisas do Active Directory incluam domínios filhos. Além disso, os atributos Recipiente de pesquisas e Contexto da pesquisa de sincronização (consulte as configurações de sincronização) devem ser configurados para o topo do domínio pai; por exemplo, DC=mydomain,DC=com. + + + Especifique um objeto de recipiente que será a raiz padrão de todas as pesquisas. A menos que a pesquisa passe outros critérios explicitamente, somente objetos neste recipiente serão pesquisados. Por exemplo, se desejar recuperar usuários do recipiente Usuários, digite CN=Users,DC=MYDOMAIN,DC=COM. Se nenhum valor for especificado, será usado o valor do atributo 'Base Container'. + + + O controlador de domínio a ser usado durante o Active Sync. Usado apenas quando não está sendo feita uma pesquisa de domínios filhos. + + + Nome do servidor de catálogo global. Isso é necessário apenas para pesquisa de domínios filhos. + + + Especifica onde procurar no diretório ADSI ao tentar resolver o atributo especificado por searchAttributes para o formato original de dn. + + + O conector não foi configurado + + + Exclusão sem suporte para ObjectClass {0} + + + Classe de objeto inválida: {0} + + + O atributo {0} não está presente no objeto de conector. Não é possível continuar a sincronização. + + + O atributo operacional de nome não pode ser nulo + + + A operação de sincronização não está disponível para ObjectClass {0} + + + A UID não estava presente + + + Uma classe de objeto inválida foi especificada na configuração do conector. A classe de objeto \'{0}\' não foi encontrada no Active Directory + + + Não foi possível adicionar o atributo do conector ao resultado da pesquisa <nulo> + + + Não foi possível adicionar o atributo do conector à entrada de diretório <nula> + + + Esperando um valor único, mas foram encontrados vários valores para o atributo {0} + + + ObjectClass \'{0}\' não é válida para este conector + + + Credenciais inválidas fornecidas para o usuário {0} + + + Para redefinir a expiração da senha, é preciso redefinir a senha + + + O Active Directory não oferece suporte a bloqueio de usuários. O usuário só pode ser desbloqueado + + + Foi fornecido um âmbito de pesquisa inválido: {0} + + + Ocorreu uma exceção durante a validação do usuário {0}. O usuário foi autenticado com êxito, mas não foi possível determinar sua GUID. + + + Ocorreu uma exceção durante a validação do usuário {0}. O usuário foi autenticado com êxito, mas não foi possível determinar sua SID. + + + O nome do administrador do diretório não foi fornecido. + + + A senha do administrador do diretório não foi fornecida. + + + O nome do domínio não foi fornecido. + + + A ObjectClass não foi fornecida. + + + O recipiente de pesquisas não foi fornecido. + + + Usando consulta de estilo do adaptador de recurso do Identity Manager '{0}'. Isso deve ser atualizado para usar a nova sintaxe de consulta de conector. + + + Foi fornecido um recipiente inválido: {0} + + + Prefixo de variável de script de shell + + + O prefixo que será adicionado a todos os nomes de argumentos do script de shell. Por exemplo, se o prefixo for definido como "Connector_", um argumento denominado "User" se tornará "Connector_User" + + + A conta do usuário foi bloqueada + + + A senha do usuário deve ser alterada + + + A conta de usuário expirou para o usuário {0} + + + Foi fornecido um contexto de pesquisa inválido: {0} + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.resx b/dotnet-connector/ActiveDirectoryConnector/Messages.resx new file mode 100644 index 0000000..947472a --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/Messages.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Windows Active Directory Connector + + + Create Home Directory + + + Directory Adminstrator''s Account + + + Directory Administrator''s Password + + + Domain Name + + + Active Directory Domain Controller Hostname + + + Object Class for User Objects + + + Search Child Domains + + + Container + + + Sync Domain Controller + + + Sync Global Catalog Server + + + Search Context + + + Specify whether or not the home directory for the user will be created. + + + Enter the administrator user name with which the system should authenticate. The setting can be either be a username or 'domainname'\'username'. + + + Enter the password that should be used when authenticating. + + + Name of the windows domain (e.g. windowsdomain.mycompany.com) + + + For cross-domain administration, enter the hostname, IP address, or domain name of the LDAP server. + + + Specify the Active Directory object class for user objects that will be managed on this resource. The default is User, and for most situations, this should be fine. + + + Select if you want searches of Active Directory to include child domains. In addition, the Search Container and Sync Search Context (see sync settings) attributes must be set to the top of the parent domain, e.g. DC=mydomain,DC=com. + + + Specify a container object which will be the default root of all searches. Unless a search explicitly passes in other criteria, only objects under this container will be searched. For example, if you want to retrieve users from the Users container, enter CN=Users,DC=MYDOMAIN,DC=COM. If no value is specified, the value of the 'Base Container' attribute is used. + + + Domain controller to use during active sync. Only used if not searching child domains. + + + Name of the global catalog server. This is needed only if searching child domains. + + + Specify where to look in the ADSI directory when attempting to resolve the attribute specified by searchAttributes to the native dn format. + + + Connector has not been configured + + + Delete is not supported for ObjectClass {0} + + + Invalid object class: {0} + + + Attribute {0} is not present in connector object. Cannot proceed with synchronization + + + The name operational attribute cannot be null + + + Sync operation is not available for ObjectClass {0} + + + Uid was not present + + + Invalid Object Class was specified in the connector configuration. Object Class \'{0}\' was not found in Active Directory + + + Could not add connector attribute to <null> search result + + + Could not add connector attribute to <null> directory entry + + + Expecting single value, but found multiple values for attribute {0} + + + ObjectClass \'{0}\' is not valid for this connector + + + Invalid credentials supplied for user {0} + + + Password expiration can only be reset by reseting the password + + + Active Directory does not support locking users. User may be unlocked only + + + An invalid searchscope was supplied: {0} + + + An execption occurred during validation of user {0}. The user was successfully authenticated, but the user's guid could not be determined. + + + An execption occurred during validation of user {0}. The user was successfully authenticated, but the user's sid could not be determined. + + + Directory administrator name not supplied. + + + Directory administrator password not supplied. + + + Domain name not supplied. + + + ObjectClass was not supplied. + + + Search Container was not supplied. + + + Using Identity Manger Resource Adapter style query '{0}'. This should be updated to use the new connector query syntax. + + + An invalid container was supplied: {0} + + + Shell Script Variable Prefix + + + Prefix that will be added to all shell script argument names. For example, if the prefix is set to "Connector_" an argument called "User" would become "Connector_User" + + + User's account has been locked + + + User password must be changed + + + User account expired for user {0} + + + An invalid search context was supplied: {0} + + + Container '{0}' could not be recognized as a distinguished name (DN) + + + Configuration errors: {0} + + + Could not find the Container '{0}', the following message was returned from the server: {1} + + + User + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.zh-CN.resx b/dotnet-connector/ActiveDirectoryConnector/Messages.zh-CN.resx new file mode 100755 index 0000000..98e986c --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/Messages.zh-CN.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Windows Active Directory 连接器 + + + 创建主目录 + + + 目录管理员的帐户 + + + 目录管理员的密码 + + + 域名 + + + Active Directory 域控制器主机名 + + + 用户对象的对象类 + + + 搜索子域 + + + 容器 + + + 同步域控制器 + + + 同步全局目录服务器 + + + 搜索上下文 + + + 指定是否为用户创建主目录。 + + + 输入系统用以进行验证的管理员用户名。该设置可以为用户名或“域名”\“用户名”。 + + + 输入进行验证时应使用的密码。 + + + Windows 域的名称(例如 windowsdomain.mycompany.com) + + + 对于跨域管理,请输入 LDAP 服务器的主机名、IP 地址或域名。 + + + 指定将在此资源上管理的用户对象的 Active Directory 对象类。默认值为 User,在大多数情况下该值均适用。 + + + 如果要在 Active Directory 搜索中包括子域,请选择此项。此外,必须将“搜索容器”和“同步搜索上下文”(请参见同步设置)属性设置为父域的顶级,例如 DC=mydomain,DC=com。 + + + 指定将作为所有搜索的默认根目录的容器对象。除非搜索明确指定其他条件,否则只会搜索此容器下的对象。例如,如果要从 Users 容器中检索用户,请输入 CN=Users,DC=MYDOMAIN,DC=COM。如果没有指定任何值,则将使用“基容器”属性的值。 + + + 活动同步期间要使用的域控制器。仅在不搜索子域的情况下使用此项。 + + + 全局目录服务器的名称。仅在搜索子域的情况下需要此项。 + + + 指定要将由 searchAttributes 指定的属性解析为本机标识名格式时,从何处查找 ADSI 目录。 + + + 尚未配置连接器 + + + 对于对象类 {0},不支持删除操作 + + + 无效的对象类: {0} + + + 连接器对象中不存在属性 {0}。无法进行同步 + + + 名称操作属性不能为 null + + + 对于对象类 {0},无法执行同步操作 + + + 不存在 UID + + + 在连接器配置中指定了无效的对象类。在 Active Directory 中未找到对象类 \'{0}\' + + + 无法将连接器属性添加到 <null> 搜索结果 + + + 无法将连接器属性添加到 <null> 目录条目 + + + 属性 {0} 应有一个值,但却找到了多个值 + + + 对象类 \'{0}\' 对此连接器无效 + + + 为用户 {0} 提供的无效凭证 + + + 只能通过重置密码来重置密码到期 + + + Active Directory 不支持锁定用户。只能解除对用户的锁定 + + + 提供了无效的搜索范围: {0} + + + 验证用户 {0} 期间出现异常。已成功验证该用户,但无法确定该用户的 GUID。 + + + 验证用户 {0} 期间出现异常。已成功验证该用户,但无法确定该用户的 SID。 + + + 未提供目录管理员名称。 + + + 未提供目录管理员密码。 + + + 未提供域名。 + + + 未提供对象类。 + + + 未提供搜索容器。 + + + 使用 Identity Manger 资源适配器样式查询“{0}”。应对此进行更新,以使用新的连接器查询语法。 + + + 提供了无效的容器: {0} + + + Shell 脚本变量前缀 + + + 将添加到所有 Shell 脚本参数名称的前缀。例如,如果将该前缀设置为 "Connector_",则名为 "User" 的参数将变为 "Connector_User" + + + 用户帐户已被锁定 + + + 必须更改用户密码 + + + 用户 {0} 的用户帐户已到期 + + + 提供了无效的搜索上下文: {0} + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnector/Messages.zh-TW.resx b/dotnet-connector/ActiveDirectoryConnector/Messages.zh-TW.resx new file mode 100755 index 0000000..4f74434 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/Messages.zh-TW.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Windows Active Directory Connector + + + 建立主目錄 + + + 目錄管理員的帳號 + + + 目錄管理員的密碼 + + + 網域名稱 + + + Active Directory 網域控制器主機名稱 + + + 使用者物件的物件類別 + + + 搜尋子網域 + + + 容器 + + + 同步化網域控制器 + + + 同步化全域目錄伺服器 + + + 搜尋上下文 + + + 指定是否要建立使用者的主目錄。 + + + 輸入系統進行認證時採用的管理員使用者名稱。此設定可以是「使用者名稱」或「網域名稱」\「使用者名稱」。 + + + 輸入進行認證時應使用的密碼。 + + + Windows 網域的名稱 (例如 windowsdomain.mycompany.com) + + + 若是跨網域進行管理,請輸入 LDAP 伺服器的主機名稱、IP 位址或網域名稱。 + + + 針對要在此資源上管理的使用者物件,指定 Active Directory 物件類別。預設值為【User】,且在大部分情況下均適用。 + + + 若要 Active Directory 的搜尋包含子網域,請選取此選項。此外此外您必須在父網域頂端設定【搜尋容器】及【同步化搜尋上下文】(請參閱同步化設定) 屬性 (例如 DC=mydomain,DC=com)。 + + + 指定將成為所有搜尋之預設根目錄的容器物件。除非搜尋明確指定有其他條件,否則只會搜尋此容器中的物件。例如,若想要擷取【Users】容器中的使用者,請輸入 CN=Users,DC=MYDOMAIN,DC=COM。若未指定任何值,將使用「基底容器」屬性值。 + + + Active Sync 期間要使用的網域控制器。只有在不搜尋子網域時才使用。 + + + 全域目錄伺服器的名稱。只有在搜尋子網域時才需要。 + + + 指定將 searchAttributes 所指定的屬性解析為本機 DN 格式時,要在何處查詢 ADSI 目錄。 + + + 尚未配置連接器 + + + 不支援刪除物件類別 {0} + + + 無效的物件類別: {0} + + + 連接器物件中沒有屬性 {0}。無法繼續同步化 + + + 名稱作業屬性不得為空 + + + 同步化作業不適用於物件類別 {0} + + + uid 不存在 + + + 連接器配置中指定了無效的物件類別。Active Directory 中找不到物件類別「{0}」 + + + 無法將連接器屬性增加至 <null> 搜尋結果 + + + 無法將連接器屬性增加至 <null> 目錄項目 + + + 屬性 {0} 應僅有單一值,但卻出現多個值 + + + 物件類別「{0}」對此連接器無效 + + + 為使用者 {0} 提供的憑證無效 + + + 必須重設密碼才可重設密碼過期功能 + + + Active Directory 不支援鎖定使用者。只能解除鎖定使用者 + + + 提供的搜尋範圍無效: {0} + + + 驗證使用者 {0} 期間發生異常。已成功認證使用者,但無法確定使用者的 GUID。 + + + 驗證使用者 {0} 期間發生異常。已成功認證使用者,但無法確定使用者的 SID。 + + + 未提供目錄管理員名稱。 + + + 未提供目錄管理員密碼。 + + + 未提供網域名稱。 + + + 未提供物件類別。 + + + 未提供搜尋容器。 + + + 使用 Identity Manger 資源配接卡樣式查詢「{0}」。應更新為使用新的連接器查詢語法。 + + + 提供的容器無效: {0} + + + shell 程序檔變數前綴 + + + 將增加至所有 shell 程序檔引數名稱的前綴。例如,若將前綴設定為「Connector_」,則稱為「User」的引數會變成「Connector_User」 + + + 使用者的帳號已鎖定 + + + 必須變更使用者密碼 + + + 使用者 {0} 的使用者帳號已過期。 + + + 提供的搜尋上下文無效: {0} + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnector/ObjectClasses.xml b/dotnet-connector/ActiveDirectoryConnector/ObjectClasses.xml new file mode 100644 index 0000000..61ee554 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/ObjectClasses.xml @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet-connector/ActiveDirectoryConnector/PasswordChangeHandler.cs b/dotnet-connector/ActiveDirectoryConnector/PasswordChangeHandler.cs new file mode 100644 index 0000000..970e374 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/PasswordChangeHandler.cs @@ -0,0 +1,281 @@ +/* + * ==================== + * 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.Diagnostics; +using System.Security.Permissions; +using System.DirectoryServices; +using Org.IdentityConnectors.Common.Security; +using ActiveDs; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using System.Threading; +using Org.IdentityConnectors.Framework.Common.Objects; +using System.Security.Principal; +using System.Runtime.InteropServices; + +namespace Org.IdentityConnectors.ActiveDirectory +{ + internal class AuthenticationHelper + { + // errors are documented in winerror.h + internal static readonly int ERROR_PASSWORD_MUST_CHANGE = 1907; + internal static readonly int ERROR_LOGON_FAILURE = 1326; + internal static readonly int ERROR_ACCOUNT_LOCKED_OUT = 1909; + internal static readonly int ERROR_ACCOUNT_EXPIRED = 1793; + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, + int dwLogonType, int dwLogonProvider, ref IntPtr phToken); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] + public extern static bool CloseHandle(IntPtr handle); + + private ActiveDirectoryConfiguration _configuration = null; + + internal AuthenticationHelper(ActiveDirectoryConfiguration configuration) + { + _configuration = configuration; + } + + // Advice from Microsoft - If you incorporate this code into a DLL, be sure to + // demand FullTrust. See full article in WindowsIdentity.Impersonate() documentation: + // http://msdn.microsoft.com/en-us/library/chf6fbt4.aspx + [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] + internal Uid ValidateUserCredentials(string username, string password) + { + IntPtr tokenHandle = new IntPtr(0); + try + { + const int LOGON32_PROVIDER_DEFAULT = 0; + const int LOGON32_LOGON_INTERACTIVE = 2; + const int LOGON32_LOGON_NETWORK = 3; + + tokenHandle = IntPtr.Zero; + bool success = LogonUser(username, null, password, LOGON32_LOGON_NETWORK, + LOGON32_PROVIDER_DEFAULT, ref tokenHandle); + if(!success) + { + int lastWindowsError = Marshal.GetLastWin32Error(); + if(lastWindowsError == ERROR_PASSWORD_MUST_CHANGE) + { + string message = _configuration.ConnectorMessages.Format("ex_PasswordMustChange", + "User password must be changed"); + + PasswordExpiredException pweException = new PasswordExpiredException(message); + pweException.Uid = GetUidFromSamAccountName(username); + + throw pweException; + } + else if(lastWindowsError == ERROR_LOGON_FAILURE) + { + string message = _configuration.ConnectorMessages.Format( + "ex_InvalidCredentials", "Invalid credentials supplied for user {0}", username); + + throw new InvalidCredentialException(message); + } + else if (lastWindowsError == ERROR_ACCOUNT_LOCKED_OUT) + { + string message = _configuration.ConnectorMessages.Format("ex_AccountLocked", + "User's account has been locked"); + throw new InvalidCredentialException(message); + } + else if (lastWindowsError == ERROR_ACCOUNT_EXPIRED) + { + string message = _configuration.ConnectorMessages.Format("ex_AccountExpired", + "User account expired for user {0}", username); + throw new InvalidCredentialException(message); + } + else + { + // no idea what could have gone wrong, so log it and throw connector error + string errorMessage = string.Format( + "Windows returned error number {0} from LogonUser call", lastWindowsError); + Trace.TraceError(errorMessage); + //TODO: Add localization + throw new ConnectorException(errorMessage); + } + } + WindowsIdentity windowsId = new WindowsIdentity(tokenHandle); + Uid uid = GetUidFromSid(windowsId.User); + windowsId.Dispose(); + return uid; + } + catch(Exception e) + { + Trace.TraceError(e.Message); + throw; + } + finally + { + if(tokenHandle != IntPtr.Zero) + { + CloseHandle(tokenHandle); + } + } + } + + internal Uid GetUidFromSid(SecurityIdentifier accountSid) + { + string sidString = ""; + DirectoryEntry userDe = new DirectoryEntry( + ActiveDirectoryUtils.GetLDAPPath(_configuration.LDAPHostName, sidString), + _configuration.DirectoryAdminName, _configuration.DirectoryAdminPassword); + byte[] guid = userDe.Guid.ToByteArray(); + userDe.Dispose(); + return new Uid(ActiveDirectoryUtils.ConvertUIDBytesToGUIDString(guid)); + } + + internal Uid GetUidFromSamAccountName(String sAMAccountName) + { + WindowsIdentity windowsId = new WindowsIdentity(sAMAccountName); + + try + { + if (windowsId.User == null) + { + throw new ConnectorException(_configuration.ConnectorMessages.Format( + "ex_SIDLookup", "An execption occurred during validation of user {0}. The user's sid could not be determined.", + sAMAccountName)); + } + return GetUidFromSid(windowsId.User); + } + finally + { + if (windowsId != null) + { + windowsId.Dispose(); + windowsId = null; + } + } + } + } + + /** + * This class will decrypt passwords, and handle + * authentication and password changes (both + * administrative and user) + */ + internal class PasswordChangeHandler + { + String _currentPassword; + String _newPassword; + ActiveDirectoryConfiguration _configuration = null; + static Semaphore authenticationSem = new Semaphore(1, 1, "ActiveDirectoryConnectorAuthSem"); + static readonly int ERR_PASSWORD_MUST_BE_CHANGED = -2147022989; + static readonly int ERR_PASSWORD_EXPIRED = -2147023688; + + + internal PasswordChangeHandler(ActiveDirectoryConfiguration configuration) + { + _configuration = configuration; + } + + /// + /// sets the _currentPassword variable + /// + /// + //internal void setCurrentPassword(UnmanagedArray clearChars) + //{ + // _currentPassword = ""; + + // // build up the string from the unmanaged array + // for (int i = 0; i < clearChars.Length; i++) + // { + // _currentPassword += clearChars[i]; + // } + //} + // Gael 1.1 legacy + /// + /// Sets the _newPassword variable + /// + /// + //internal void setNewPassword(UnmanagedArray clearChars) + //{ + // _newPassword = ""; + + // // build up the string from the unmanaged array + // for (int i = 0; i < clearChars.Length; i++) + // { + // _newPassword += clearChars[i]; + // } + //} + // Gael - 1.1 legacy + + /// + /// Does an administrative password change. The Directory + /// entry must be created with username and password of + /// a user with permission to change the password + /// + /// + /// + internal void changePassword(DirectoryEntry directoryEntry, + GuardedString gsNewPassword) + { + // decrypt and save the new password + _newPassword = SecurityUtil.Decrypt(gsNewPassword); + + // get the native com object as an IADsUser, and set the + // password + IADsUser user = (IADsUser)directoryEntry.NativeObject; + user.SetPassword(_newPassword); + } + + /// + /// Does a user password change. Must supply the currentpassword + /// and the new password + /// + /// + /// + /// + internal void changePassword(DirectoryEntry directoryEntry, + GuardedString gsCurrentPassword, GuardedString gsNewPassword) + { + // decrypt and save the old nad new passwords + _newPassword = SecurityUtil.Decrypt(gsNewPassword); + _currentPassword = SecurityUtil.Decrypt(gsCurrentPassword); + + // get the native com object as an IADsUser, and change the + // password + IADsUser user = (IADsUser)directoryEntry.NativeObject; + user.ChangePassword(_currentPassword, _newPassword); + } + + /// + /// Authenticates the user + /// + /// + /// + /// + internal Uid Authenticate(/*DirectoryEntry directoryEntry,*/ string username, + GuardedString password, bool returnUidOnly) + { + AuthenticationHelper authHelper = new AuthenticationHelper(_configuration); + if(returnUidOnly) + { + return authHelper.GetUidFromSamAccountName(username); + } + _currentPassword = SecurityUtil.Decrypt(password); + return authHelper.ValidateUserCredentials(username, _currentPassword); + } + + } +} diff --git a/dotnet-connector/ActiveDirectoryConnector/TerminalServicesUtils.cs b/dotnet-connector/ActiveDirectoryConnector/TerminalServicesUtils.cs new file mode 100644 index 0000000..e2fb705 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/TerminalServicesUtils.cs @@ -0,0 +1,312 @@ +/* + * ==================== + * 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.Diagnostics; +using System.DirectoryServices; +using Org.IdentityConnectors.Framework.Common.Exceptions; + +namespace Org.IdentityConnectors.ActiveDirectory +{ + /** + * This class will handle setting all of the terminal services attributes + */ + public class TerminalServicesUtils + { + // used to be 'Terminal Services Initial Program' + public static string TS_INITIAL_PROGRAM = "TerminalServicesInitialProgram"; + + // used to be 'Terminal Services Initial Program Directory' + public static string TS_INITIAL_PROGRAM_DIR = "TerminalServicesWorkDirectory"; + + // used to be 'Terminal Services Inherit Initial Program' + + // used to be 'Terminal Services Allow Logon' - defaults to false, so testing true + public static string TS_ALLOW_LOGON = "AllowLogon"; + + // used to be 'Terminal Services Active Session Timeout' + public static string TS_MAX_CONNECTION_TIME = "MaxConnectionTime"; + + // used to be 'Terminal Services Disconnected Session Timeout' + public static string TS_MAX_DISCONNECTION_TIME = "MaxDisconnectionTime"; + + // used to be 'Terminal Services Idle Timeout' + public static string TS_MAX_IDLE_TIME = "MaxIdleTime"; + + // used to be 'Terminal Services Connect Client Drives At Logon' + public static string TS_CONNECT_CLIENT_DRIVES_AT_LOGON = "ConnectClientDrivesAtLogon"; + + // used to be 'Terminal Services Connect Client Printers At Logon' + public static string TS_CONNECT_CLIENT_PRINTERS_AT_LOGON = "ConnectClientPrintersAtLogon"; + + // used to be 'Terminal Services Default To Main Client Printer' + public static string TS_DEFAULT_TO_MAIN_PRINTER = "DefaultToMainPrinter"; + + // used to be 'Terminal Services End Session On Timeout Or Broken Connection' + public static string TS_BROKEN_CONNECTION_ACTION = "BrokenConnectionAction"; + + // used to be 'Terminal Services Allow Reconnect From Originating Client Only' + public static string TS_RECONNECTION_ACTION = "ReconnectionAction"; + + // used to be 'Terminal Services Callback Settings' + + // used to be 'Terminal Services Callback Phone Number' + + // used to be 'Terminal Services Remote Control Settings' + public static string TS_ENABLE_REMOTE_CONTROL = "EnableRemoteControl"; + + // used to be 'Terminal Services User Profile' + public static string TS_PROFILE_PATH = "TerminalServicesProfilePath"; + + // used to be 'Terminal Services Local Home Directory' + public static string TS_HOME_DIRECTORY = "TerminalServicesHomeDirectory"; + + // used to be 'Terminal Services Home Directory Drive' + public static string TS_HOME_DRIVE = "TerminalServicesHomeDrive"; + + private static T GetValue(SearchResult searchResult, string name, T defaultValue) + { + // get the directory entry + DirectoryEntry directoryEntry = searchResult.GetDirectoryEntry(); + + // get 'name' from the directory entry, and return it if it exists + try + { + object result = directoryEntry.InvokeGet(name); + if (result != null) + { + T value = (T)result; + return value; + } + } + catch (Exception e) + { + Trace.TraceWarning("Unable to retrieve property called {0}", name); + Trace.TraceWarning(e.Message); + } + finally + { + directoryEntry.Dispose(); + } + + // if the name didn't exist, return 'defaultValue' + return defaultValue; + } + + internal static void SetValue(UpdateType type, + DirectoryEntry directoryEntry, string name, T value) + { + if (!type.Equals(UpdateType.REPLACE)) + { + // Only allow replace on single value attributes, + // and for now, all terminal services are single value + ThrowInvalidUpdateType(name); + } + + if (value == null) + { + throw new ArgumentException(); + } + + // invoke set on 'name' with 'value' + directoryEntry.InvokeSet(name, value); + } + + private static void ThrowInvalidUpdateType(string attributeName) + { + // throws an exception that says invalid update type + string msg = string.Format("The update type specified is invalid for the terminal services attribute ''{0}''", + attributeName); + throw new ConnectorException(msg); + } + + internal static string GetInitialProgram(SearchResult searchResult) + { + return GetValue(searchResult, TS_INITIAL_PROGRAM, null); + } + + internal static void SetInitialProgram(UpdateType type, DirectoryEntry directoryEntry, + string initialProgram) + { + SetValue(type, directoryEntry, TS_INITIAL_PROGRAM, initialProgram); + } + + internal static string GetInitialProgramDir(SearchResult searchResult) + { + return GetValue(searchResult, TS_INITIAL_PROGRAM_DIR, null); + } + + internal static void SetInitialProgramDir(UpdateType type, + DirectoryEntry directoryEntry, string initialProgramDir) + { + SetValue(type, directoryEntry, TS_INITIAL_PROGRAM_DIR, initialProgramDir); + } + + internal static int? GetAllowLogon(SearchResult searchResult) + { + return GetValue(searchResult, TS_ALLOW_LOGON, null); + } + + internal static void SetAllowLogon(UpdateType type, DirectoryEntry directoryEntry, + int? isAllowed) + { + SetValue(type, directoryEntry, TS_ALLOW_LOGON, isAllowed); + } + + internal static int? GetMaxConnectionTime(SearchResult searchResult) + { + return GetValue(searchResult, TS_MAX_CONNECTION_TIME, null); + } + + internal static void SetMaxConnectionTime(UpdateType type, DirectoryEntry directoryEntry, + int? maxConnectionTime) + { + SetValue(type, directoryEntry, TS_MAX_CONNECTION_TIME, maxConnectionTime); + } + + internal static int? GetMaxDisconnectionTime(SearchResult searchResult) + { + return GetValue(searchResult, TS_MAX_DISCONNECTION_TIME, null); + } + + internal static void SetMaxDisconnectionTime(UpdateType type, + DirectoryEntry directoryEntry, int? maxDisconnectionTime) + { + SetValue(type, directoryEntry, TS_MAX_DISCONNECTION_TIME, maxDisconnectionTime); + } + + internal static int? GetMaxIdleTime(SearchResult searchResult) + { + return GetValue(searchResult, TS_MAX_IDLE_TIME, null); + } + + internal static void SetMaxIdleTime(UpdateType type, DirectoryEntry directoryEntry, + int? maxIdleTime) + { + SetValue(type, directoryEntry, TS_MAX_IDLE_TIME, maxIdleTime); + } + + internal static int? GetConnectClientDrivesAtLogon(SearchResult searchResult) + { + return GetValue(searchResult, TS_CONNECT_CLIENT_DRIVES_AT_LOGON, null); + } + + internal static void SetConnectClientDrivesAtLogon(UpdateType type, + DirectoryEntry directoryEntry, int? connectClientDrivesAtLogon) + { + SetValue(type, directoryEntry, TS_CONNECT_CLIENT_DRIVES_AT_LOGON, + connectClientDrivesAtLogon); + } + + internal static int? GetConnectClientPrintersAtLogon(SearchResult searchResult) + { + return GetValue(searchResult, TS_CONNECT_CLIENT_PRINTERS_AT_LOGON, null); + } + + internal static void SetConnectClientPrintersAtLogon(UpdateType type, + DirectoryEntry directoryEntry, int? connectClientPrintersAtLogon) + { + SetValue(type, directoryEntry, TS_CONNECT_CLIENT_PRINTERS_AT_LOGON, + connectClientPrintersAtLogon); + } + + internal static int? GetDefaultToMainPrinter(SearchResult searchResult) + { + return GetValue(searchResult, TS_DEFAULT_TO_MAIN_PRINTER, null); + } + + internal static void SetDefaultToMainPrinter(UpdateType type, + DirectoryEntry directoryEntry, int? defaultToMainPrinter) + { + SetValue(type, directoryEntry, TS_DEFAULT_TO_MAIN_PRINTER, + defaultToMainPrinter); + } + + internal static int? GetBrokenConnectionAction(SearchResult searchResult) + { + return GetValue(searchResult, TS_BROKEN_CONNECTION_ACTION, null); + } + + internal static void SetBrokenConnectionAction(UpdateType type, + DirectoryEntry directoryEntry, int? brokenConnectionAction) + { + SetValue(type, directoryEntry, TS_BROKEN_CONNECTION_ACTION, + brokenConnectionAction); + } + + internal static int? GetReconnectionAction(SearchResult searchResult) + { + return GetValue(searchResult, TS_RECONNECTION_ACTION, null); + } + + internal static void SetReconnectionAction(UpdateType type, + DirectoryEntry directoryEntry, int? reconnectionAction) + { + SetValue(type, directoryEntry, TS_RECONNECTION_ACTION, reconnectionAction); + } + + internal static int? GetEnableRemoteControl(SearchResult searchResult) + { + return GetValue(searchResult, TS_ENABLE_REMOTE_CONTROL, null); + } + + internal static void SetEnableRemoteControl(UpdateType type, + DirectoryEntry directoryEntry, int? enableRemoteControl) + { + SetValue(type, directoryEntry, TS_ENABLE_REMOTE_CONTROL, enableRemoteControl); + } + + internal static string GetProfilePath(SearchResult searchResult) + { + return GetValue(searchResult, TS_PROFILE_PATH, null); + } + + internal static void SetProfilePath(UpdateType type, + DirectoryEntry directoryEntry, string profilePath) + { + SetValue(type, directoryEntry, TS_PROFILE_PATH, profilePath); + } + + internal static string GetHomeDirectory(SearchResult searchResult) + { + return GetValue(searchResult, TS_HOME_DIRECTORY, null); + } + + internal static void SetHomeDirectory(UpdateType type, + DirectoryEntry directoryEntry, string homeDirectory) + { + SetValue(type, directoryEntry, TS_HOME_DIRECTORY, homeDirectory); + } + + internal static string GetHomeDrive(SearchResult searchResult) + { + return GetValue(searchResult, TS_HOME_DRIVE, null); + } + + internal static void SetHomeDrive(UpdateType type, + DirectoryEntry directoryEntry, string homeDrive) + { + SetValue(type, directoryEntry, TS_HOME_DRIVE, homeDrive); + } + + } +} diff --git a/dotnet-connector/ActiveDirectoryConnector/UserAccountControl.cs b/dotnet-connector/ActiveDirectoryConnector/UserAccountControl.cs new file mode 100644 index 0000000..d9131af --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/UserAccountControl.cs @@ -0,0 +1,109 @@ +/* + * ==================== + * 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.DirectoryServices; + +namespace Org.IdentityConnectors.ActiveDirectory +{ + internal class UserAccountControl + { + public static string UAC_ATTRIBUTE_NAME = "userAccountControl"; + + // values taken from - http://support.microsoft.com/kb/305144 + static public int SCRIPT = 0x0001; + static public int ACCOUNTDISABLE = 0x0002; + static public int HOMEDIR_REQUIRED = 0x0008; + static public int LOCKOUT = 0x0010; + static public int PASSWD_NOTREQD = 0x0020; + + // Note You cannot assign this permission by directly modifying + // the UserAccountControl attribute. For information about how + // to set the permission programmatically, see the "Property + // flag descriptions" section. + static public int PASSWD_CANT_CHANGE = 0x0040; + + static public int ENCRYPTED_TEXT_PWD_ALLOWED = 0x0080; + static public int TEMP_DUPLICATE_ACCOUNT = 0x0100; + static public int NORMAL_ACCOUNT = 0x0200; + static public int INTERDOMAIN_TRUST_ACCOUNT = 0x0800; + static public int WORKSTATION_TRUST_ACCOUNT = 0x1000; + static public int SERVER_TRUST_ACCOUNT = 0x2000; + static public int DONT_EXPIRE_PASSWORD = 0x10000; + static public int MNS_LOGON_ACCOUNT = 0x20000; + static public int SMARTCARD_REQUIRED = 0x40000; + static public int TRUSTED_FOR_DELEGATION = 0x80000; + static public int NOT_DELEGATED = 0x100000; + static public int USE_DES_KEY_ONLY = 0x200000; + static public int DONT_REQ_PREAUTH = 0x400000; + public static int PASSWORD_EXPIRED = 0x800000; + public static int TRUSTED_TO_AUTH_FOR_DELEGATION = 0x1000000; + + // get the uac value from the property value collection + private static int GetUAC(PropertyValueCollection pvc) + { + // default schema says it's an integer, so it better be one + if ((pvc != null) && (pvc.Count == 1) && (pvc[0] is int)) + { + return (int)pvc[0]; + } + else + { + return 0; + } + } + + // sets the uac value in a propertyvaluecollection + private static void SetUAC(PropertyValueCollection pvc, int value) + { + // set the value + pvc[0] = value; + } + + // generically set a value in the uac to the value of 'isSet' + internal static void Set(PropertyValueCollection pvc, int flag, bool? isSet) + { + int uac = GetUAC(pvc); + if(isSet == null) + { + throw new ArgumentException(); + } + // boolean false + if (isSet.Value.Equals(false)) + { + uac &= (~flag); + } + else + { + uac |= flag; + } + SetUAC(pvc, uac); + } + + // chec to see if a particular value of the uac is set + internal static bool IsSet(PropertyValueCollection pvc, int flag) + { + int uac = GetUAC(pvc); + return ((uac & flag) != 0); + } + } +} diff --git a/dotnet-connector/ActiveDirectoryConnector/version.template b/dotnet-connector/ActiveDirectoryConnector/version.template new file mode 100644 index 0000000..34c3267 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnector/version.template @@ -0,0 +1 @@ +1.4.1.0 \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnectorTests/ActiveDirectoryConfigurationTests.cs b/dotnet-connector/ActiveDirectoryConnectorTests/ActiveDirectoryConfigurationTests.cs new file mode 100644 index 0000000..23ff7af --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnectorTests/ActiveDirectoryConfigurationTests.cs @@ -0,0 +1,171 @@ +/* + * ==================== + * 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 2014 ForgeRock AS. + */ + +using NUnit.Framework; +using Org.IdentityConnectors.Test.Common; +using Org.IdentityConnectors.Framework.Common.Exceptions; + +namespace Org.IdentityConnectors.ActiveDirectory +{ + [TestFixture] + public class ActiveDirectoryConfigurationTests + { + [Test] + public void TestProperties() + { + var sut = new ActiveDirectoryConfiguration + { + ConnectorMessages = TestHelpers.CreateDummyMessages() + }; + + var container = TestHelpers.GetProperties( + typeof( ActiveDirectoryConnector ) ).GetProperty( ConfigHelper.CONFIG_PROPERTY_CONTAINER ); + sut.Container = container; + Assert.AreEqual( container, sut.Container, "Container" ); + + //test with the negate of the default value + var createHomeDirectory = !sut.CreateHomeDirectory; + sut.CreateHomeDirectory = createHomeDirectory; + Assert.AreEqual( createHomeDirectory, sut.CreateHomeDirectory, "CreateHomeDirectory" ); + + var directoryAdminName = TestHelpers.GetProperties( + typeof( ActiveDirectoryConnector ) ).GetProperty( ConfigHelper.CONFIG_PROPERTY_USER ); + sut.DirectoryAdminName = directoryAdminName; + Assert.AreEqual( directoryAdminName, sut.DirectoryAdminName, "DirectoryAdminName" ); + + var directoryAdminPassword = TestHelpers.GetProperties( + typeof( ActiveDirectoryConnector ) ).GetProperty( ConfigHelper.CONFIG_PROPERTY_PASSWORD ); + sut.DirectoryAdminPassword = directoryAdminPassword; + Assert.AreEqual( directoryAdminPassword, sut.DirectoryAdminPassword, "DirectoryAdminPassword" ); + + var domainName = TestHelpers.GetProperties( + typeof( ActiveDirectoryConnector ) ).GetProperty( ConfigHelper.CONFIG_PROPERTY_DOMAIN_NAME ); + sut.DomainName = domainName; + Assert.AreEqual( domainName, sut.DomainName, "DomainName" ); + + var ldapHostName = TestHelpers.GetProperties( + typeof( ActiveDirectoryConnector ) ).GetProperty( ConfigHelper.CONFIG_PROPERTY_LDAPHOSTNAME ); + sut.LDAPHostName = ldapHostName; + Assert.AreEqual( ldapHostName, sut.LDAPHostName, "LDAPHostName" ); + + const string objectClassName = "DOES NOT MATTER"; + sut.ObjectClass = objectClassName; + Assert.AreEqual( objectClassName, sut.ObjectClass, "ObjectClass" ); + + //test with the negate of the default value + var searchChildDomains = !sut.SearchChildDomains; + sut.SearchChildDomains = searchChildDomains; + Assert.AreEqual( searchChildDomains, sut.SearchChildDomains, "SearchChildDomains" ); + + var searchContext = TestHelpers.GetProperties( + typeof( ActiveDirectoryConnector ) ).GetProperty( ConfigHelper.CONFIG_PROPERTY_SEARCH_CONTEXT ); + sut.SearchContext = searchContext; + Assert.AreEqual( searchContext, sut.SearchContext, "SearchContext" ); + + var syncDomainController = TestHelpers.GetProperties( + typeof( ActiveDirectoryConnector ) ).GetProperty( ConfigHelper.CONFIG_PROPERTY_SYNC_DOMAIN_CONTROLLER ); + sut.SyncDomainController = syncDomainController; + Assert.AreEqual( syncDomainController, sut.SyncDomainController, "SyncDomainController" ); + + var syncGlobalCatalogServer = TestHelpers.GetProperties( + typeof( ActiveDirectoryConnector ) ).GetProperty( ConfigHelper.CONFIG_PROPERTY_GC_DOMAIN_CONTROLLER ); + sut.SyncGlobalCatalogServer = syncGlobalCatalogServer; + Assert.AreEqual( syncGlobalCatalogServer, sut.SyncGlobalCatalogServer, "SyncGlobalCatalogServer" ); + } + + [Test] + public void TestValidate() + { + var sut = (ActiveDirectoryConfiguration)ConfigHelper.GetConfiguration(); + //test if the default configuration is valid, hence the following tests will fail only if the + //changed property is incorrect + sut.Validate(); + + var domainName = sut.DomainName; + try + { + sut.DomainName = string.Empty; + sut.Validate(); + Assert.Fail( "Exception was not thrown for empty DomainName" ); + } + catch(ConfigurationException) + { + sut.DomainName = domainName; + } + + var directoryAdminName = sut.DirectoryAdminName; + try + { + sut.DirectoryAdminName = string.Empty; + sut.Validate(); + Assert.Fail( "Exception was not thrown for empty DirectoryAdminName" ); + } + catch (ConfigurationException) + { + sut.DirectoryAdminName = directoryAdminName; + } + + var directoryAdminPassword = sut.DirectoryAdminPassword; + try + { + sut.DirectoryAdminPassword = string.Empty; + sut.Validate(); + Assert.Fail( "Exception was not thrown for empty DirectoryAdminPassword" ); + } + catch (ConfigurationException) + { + sut.DirectoryAdminPassword = directoryAdminPassword; + } + + var objectClass = sut.ObjectClass; + try + { + sut.ObjectClass = string.Empty; + sut.Validate(); + Assert.Fail( "Exception was not thrown for empty ObjectClass" ); + } + catch (ConfigurationException) + { + sut.ObjectClass = objectClass; + } + + + var container = sut.Container; + try + { + sut.Container = string.Empty; + sut.Validate(); + Assert.Fail( "Exception was not thrown for empty Container" ); + + sut.Container = "CN=ClaytonFarlow.DC=NotMyCompany.DC=com"; + sut.Validate(); + Assert.Fail( "Exception was not thrown for Container containing periods" ); + } + catch (ConfigurationException) + { + sut.Container = container; + } + } + } +} diff --git a/dotnet-connector/ActiveDirectoryConnectorTests/ActiveDirectoryConnectorTest.cs b/dotnet-connector/ActiveDirectoryConnectorTests/ActiveDirectoryConnectorTest.cs new file mode 100644 index 0000000..01d29b6 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnectorTests/ActiveDirectoryConnectorTest.cs @@ -0,0 +1,2904 @@ +/* + * ==================== + * 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 2014 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.AccessControl; +using System.Security.Principal; +using System.Text; +using System.Threading; +using NUnit.Framework; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Test.Common; + +namespace Org.IdentityConnectors.ActiveDirectory +{ + [TestFixture] + public class ActiveDirectoryConnectorTest + { + Random _rand = new Random(); + + + // having troubles with duplicate random numbers + public static List randomList = new List(); + + [Test] + public void TestTest() + { + ActiveDirectoryConnector connectorGood = new ActiveDirectoryConnector(); + ActiveDirectoryConfiguration config = (ActiveDirectoryConfiguration)ConfigHelper.GetConfiguration(); + connectorGood.Init(config); + connectorGood.Test(); + + var objectClass = config.ObjectClass; + try + { + config.ObjectClass = "BadObjectClass"; + ActiveDirectoryConnector connectorBad = new ActiveDirectoryConnector(); + connectorBad.Init(config); + connectorBad.Test(); + + Assert.Fail("Bad configuration should have caused an exception"); + } + catch (ConnectorException e) + { + config.ObjectClass = objectClass; + } + + var container = config.Container; + try + { + config.Container += ",DC=BadDC"; + ActiveDirectoryConnector connectorBad = new ActiveDirectoryConnector(); + connectorBad.Init(config); + connectorBad.Test(); + + Assert.Fail("Configuration with bad DC in Container should have caused an exception"); + } + catch (ConnectorException e) + { + config.Container = container; + } + } + + [Test] + public void TestSchema() + { + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Schema schema = connector.Schema(); + Boolean foundOptionalAttributes = false; + Boolean foundOperationalAttributes = false; + + // just a very high level check of things. Should have 3 ObjectClassInfos, + // (group, account, and organizationalUnit) and nothing contained in them + // should be null. Group and account should have some operational + // attributes + Assert.AreEqual(3, schema.ObjectClassInfo.Count); + foreach (ObjectClassInfo ocInfo in schema.ObjectClassInfo) + { + Assert.IsNotNull(ocInfo); + Assert.That((ocInfo.ObjectType == ObjectClass.ACCOUNT.GetObjectClassValue()) + || (ocInfo.ObjectType == ActiveDirectoryConnector.OBJECTCLASS_GROUP) + || (ocInfo.ObjectType == ActiveDirectoryConnector.OBJECTCLASS_OU)); + Trace.WriteLine("****** " + ocInfo.ObjectType); + + // skip this for organizational unit ... it doesnt really have this + if (ocInfo.ObjectType.Equals(ActiveDirectoryConnector.ouObjectClass)) + { + continue; + } + + foreach (ConnectorAttributeInfo caInfo in ocInfo.ConnectorAttributeInfos) + { + Assert.IsNotNull(caInfo); + Trace.WriteLine(String.Format("{0} {1} {2} {3}", caInfo.Name, + caInfo.IsCreatable ? "createable" : "", + caInfo.IsUpdateable ? "updateable" : "", + caInfo.IsRequired ? "required" : "", + caInfo.IsMultiValued ? "multivalue" : "")); + if (ConnectorAttributeUtil.IsSpecial(caInfo)) + { + foundOperationalAttributes = true; + } + else + { + if (!caInfo.IsRequired) + { + foundOptionalAttributes = true; + } + } + } + Assert.That(foundOperationalAttributes && foundOptionalAttributes); + } + } + + // test proper behavior of each supported operation + // and test proper reporting of unsuppoorted operations + [Test] + public void TestBasics_Account() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + + // create user + ICollection createAttributes = GetNormalAttributes_Account(); + Uid createUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, createAttributes); + + // update the user - replace + ICollection updateReplaceAttrs = + new List(); + Name oldName = ConnectorAttributeUtil.GetNameFromAttributes(createAttributes); + String newName = ActiveDirectoryUtils.GetRelativeName(oldName); + newName = newName.Trim() + "_new, " + GetProperty(ConfigHelper.CONFIG_PROPERTY_CONTAINER); + updateReplaceAttrs.Add(ConnectorAttributeBuilder.Build( + Name.NAME, newName)); + updateReplaceAttrs.Add(ConnectorAttributeBuilder.Build( + "sn", "newsn")); + Uid updateReplaceUid = UpdateReplaceAndVerifyObject(connector, + ObjectClass.ACCOUNT, createUid, updateReplaceAttrs); + + // update the user - add + ICollection updateAddAttrs = + new List(); + updateAddAttrs.Add(ConnectorAttributeBuilder.Build("otherHomePhone", "123.456.7890", "098.765.4321")); + Uid updateAddUid = UpdateAddAndVerifyUser(connector, + ObjectClass.ACCOUNT, createUid, updateAddAttrs, null); + + // update the user - delete + + // delete user + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, createUid, true, true); + } + + // test proper behaviour of each supported operation + // and test proper reporting of unsuppoorted operations + [Test] + public void TestBasics_Group() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + + Uid uidToDelete = null; + try + { + // create group + ICollection createAttributes = GetNormalAttributes_Group(); + createAttributes.Add(ConnectorAttributeBuilder.Build(ActiveDirectoryConnector.ATT_ACCOUNTS, + CreateGroupMember(connector))); + + // create object + uidToDelete = connector.Create(ActiveDirectoryConnector.groupObjectClass, createAttributes, null); + Uid createUid = uidToDelete; + Assert.IsNotNull(createUid); + + // find new object ... have to add groups to list of things to return + OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder(); + ICollection attributesToGet = GetDefaultAttributesToGet(ActiveDirectoryConnector.groupObjectClass); + attributesToGet.Add(ActiveDirectoryConnector.ATT_ACCOUNTS); + optionsBuilder.AttributesToGet = attributesToGet.ToArray(); + + ConnectorObject newObject = GetConnectorObjectFromUid(connector, + ActiveDirectoryConnector.groupObjectClass, createUid, optionsBuilder.Build()); + VerifyObject(createAttributes, newObject); + // update the group - replace + ICollection updateReplaceAttrs = + new List(); + Name oldName = ConnectorAttributeUtil.GetNameFromAttributes(createAttributes); + String newName = ActiveDirectoryUtils.GetRelativeName(oldName); + newName = newName.Trim() + "_new, " + GetProperty(ConfigHelper.CONFIG_PROPERTY_CONTAINER); + + updateReplaceAttrs.Add(createUid); + updateReplaceAttrs.Add(ConnectorAttributeBuilder.Build( + Name.NAME, newName)); + updateReplaceAttrs.Add(ConnectorAttributeBuilder.Build( + "description", "New description")); + uidToDelete = UpdateReplaceAndVerifyObject(connector, + ActiveDirectoryConnector.groupObjectClass, createUid, updateReplaceAttrs); + Uid updateReplaceUid = uidToDelete; + + // update the group - add + ICollection updateAddAttrs = + new List(); + updateAddAttrs.Add(ConnectorAttributeBuilder.Build(ActiveDirectoryConnector.ATT_ACCOUNTS, + CreateGroupMember(connector), CreateGroupMember(connector))); + + uidToDelete = UpdateAddAndVerifyUser(connector, + ActiveDirectoryConnector.groupObjectClass, updateReplaceUid, updateAddAttrs, optionsBuilder.Build()); + } + finally + { + if (uidToDelete != null) + { + // delete user + DeleteAndVerifyObject(connector, ActiveDirectoryConnector.groupObjectClass, uidToDelete, true, true); + } + } + } + + [Test] + public void TestCreate_Account() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid createUid = null; + try + { + ICollection createAttributes = GetNormalAttributes_Account(); + createUid = CreateAndVerifyObject(connector, ObjectClass.ACCOUNT, createAttributes); + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, false, true); + } + } + } + + [Test] + public void TestCreate_Group() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid createUid = null; + try + { + ICollection createAttributes = GetNormalAttributes_Group(); + createUid = CreateAndVerifyObject(connector, ActiveDirectoryConnector.groupObjectClass, createAttributes); + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ActiveDirectoryConnector.groupObjectClass, + createUid, false, true); + } + } + } + + [Test] + public void TestCreate_OrganizationalUnit() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid createUid = null; + try + { + ICollection createAttributes = GetNormalAttributes_OrganizationalUnit(); + createUid = CreateAndVerifyObject(connector, + ActiveDirectoryConnector.ouObjectClass, createAttributes); + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, + ActiveDirectoryConnector.ouObjectClass, + createUid, false, true); + } + } + } + + [Test] + public void TestCreateWithHomeDirectory_Account() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + ActiveDirectoryConfiguration config = (ActiveDirectoryConfiguration)ConfigHelper.GetConfiguration(); + config.CreateHomeDirectory = true; + connector.Init(config); + Uid userUid = null; + try + { + // get the normal attributes + ICollection createAttributes = GetNormalAttributes_Account(); + + // read the homedir path, and append the samaccountname + StringBuilder homeDirPathBuilder = new StringBuilder(GetProperty(ConfigHelper.TEST_PARAM_SHARED_HOME_FOLDER)); + if (!homeDirPathBuilder.ToString().EndsWith("\\")) + { + homeDirPathBuilder.Append('\\'); + } + ConnectorAttribute samAccountNameAttr = ConnectorAttributeUtil.Find( + ActiveDirectoryConnector.ATT_SAMACCOUNT_NAME, createAttributes); + homeDirPathBuilder.Append(ConnectorAttributeUtil.GetStringValue(samAccountNameAttr)); + + // if it exists, delete it + String homeDir = homeDirPathBuilder.ToString(); + if (Directory.Exists(homeDir)) + { + Directory.Delete(homeDir); + } + Assert.IsFalse(Directory.Exists(homeDir)); + + // add homeDirectory to the attributes, and create user + createAttributes.Add(ConnectorAttributeBuilder.Build("homeDirectory", homeDir)); + userUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, createAttributes); + + // now directory should exist + Assert.IsTrue(Directory.Exists(homeDir)); + + // get sid to check permissions + OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder(); + ICollection attributesToGet = GetDefaultAttributesToGet(ObjectClass.ACCOUNT); + attributesToGet.Add(ActiveDirectoryConnector.ATT_OBJECT_SID); + optionsBuilder.AttributesToGet = attributesToGet.ToArray(); + + ConnectorObject newUser = GetConnectorObjectFromUid(connector, + ObjectClass.ACCOUNT, userUid, optionsBuilder.Build()); + ConnectorAttribute sidAttr = + newUser.GetAttributeByName(ActiveDirectoryConnector.ATT_OBJECT_SID); + Byte[] sidBytes = (Byte[])ConnectorAttributeUtil.GetSingleValue(sidAttr); + SecurityIdentifier newUserSid = new SecurityIdentifier(sidBytes, 0); + + // check permissions + DirectoryInfo dirInfo = new DirectoryInfo(homeDir); + DirectorySecurity dirSec = dirInfo.GetAccessControl(); + AuthorizationRuleCollection rules = dirSec.GetAccessRules(true, true, typeof(SecurityIdentifier)); + bool foundCorrectRule = false; + foreach (AuthorizationRule rule in rules) + { + if (rule is FileSystemAccessRule) + { + FileSystemAccessRule fsaRule = (FileSystemAccessRule)rule; + if (fsaRule.IdentityReference.Equals(newUserSid)) + { + if ((fsaRule.AccessControlType.Equals(AccessControlType.Allow)) && + (fsaRule.FileSystemRights.Equals(FileSystemRights.FullControl)) && + (fsaRule.InheritanceFlags.Equals(InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit)) && + (fsaRule.IsInherited.Equals(false))) + { + foundCorrectRule = true; + } + + } + } + } + + // remove the directory (before assertion may fail) + Directory.Delete(homeDir); + + // check that we found the proper permission record + Assert.IsTrue(foundCorrectRule); + } + finally + { + if (userUid != null) + { + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, userUid, false, false); + } + } + } + + [Test] // tests that if create home directory is set to false, no directory is created + public void TestCreateWithHomeDirectoryNoCreateConfig_Account() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + ActiveDirectoryConfiguration config = (ActiveDirectoryConfiguration)ConfigHelper.GetConfiguration(); + config.CreateHomeDirectory = false; + connector.Init(config); + Uid userUid = null; + try + { + // get the normal attributes + ICollection createAttributes = GetNormalAttributes_Account(); + + // read the homedir path, and append the samaccountname + StringBuilder homeDirPathBuilder = new StringBuilder(GetProperty(ConfigHelper.TEST_PARAM_SHARED_HOME_FOLDER)); + if (!homeDirPathBuilder.ToString().EndsWith("\\")) + { + homeDirPathBuilder.Append('\\'); + } + ConnectorAttribute samAccountNameAttr = ConnectorAttributeUtil.Find( + ActiveDirectoryConnector.ATT_SAMACCOUNT_NAME, createAttributes); + homeDirPathBuilder.Append(ConnectorAttributeUtil.GetStringValue(samAccountNameAttr)); + + // if it exists, delete it + String homeDir = homeDirPathBuilder.ToString(); + if (Directory.Exists(homeDir)) + { + Directory.Delete(homeDir); + } + Assert.IsFalse(Directory.Exists(homeDir)); + + // add homeDirectory to the attributes, and create user + createAttributes.Add(ConnectorAttributeBuilder.Build("homeDirectory", homeDir)); + userUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, createAttributes); + + // now directory should not exist + // (createhomedirectory was set to false in the configuration) + Assert.IsFalse(Directory.Exists(homeDir)); + } + finally + { + if (userUid != null) + { + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, userUid, false, false); + } + } + } + + [Test] + public void TestSearchNoFilter_Account() + { + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + Configuration config = ConfigHelper.GetConfiguration(); + config.ConnectorMessages = TestHelpers.CreateDummyMessages(); + connector.Init(config); + + ICollection createdUids = new HashSet(); + + try + { + int numCreated = 0; + for (numCreated = 0; numCreated < 5; numCreated++) + { + createdUids.Add(CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, GetNormalAttributes_Account())); + } + + ICollection results = TestHelpers.SearchToList(connector, + ObjectClass.ACCOUNT, null); + + // not sure how many should be found ... it should find everything + // it's hard to say how many that is, but it should at least find the + // number we created + Assert.GreaterOrEqual(results.Count, numCreated); + + // check that they are all of the proper objectclass + foreach (ConnectorObject co in results) + { + ConnectorAttribute objectClassAttr = + co.GetAttributeByName("objectClass"); + Boolean foundCorrectObjectClass = false; + foreach (Object o in objectClassAttr.Value) + { + if ((o is String) && (o != null)) + { + String stringValue = (String)o; + if (stringValue.ToUpper().Trim().Equals("USER")) + { + foundCorrectObjectClass = true; + } + } + } + Assert.IsTrue(foundCorrectObjectClass); + } + } + finally + { + foreach (Uid uid in createdUids) + { + if (uid != null) + { + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, uid, false, true); + } + } + } + } + + [Test] + public void TestSearchNoFilter_Group() + { + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + ICollection createdUids = new HashSet(); + + try + { + int numCreated = 0; + for (numCreated = 0; numCreated < 5; numCreated++) + { + createdUids.Add(CreateAndVerifyObject(connector, + ActiveDirectoryConnector.groupObjectClass, GetNormalAttributes_Group())); + } + + ICollection results = TestHelpers.SearchToList(connector, + ActiveDirectoryConnector.groupObjectClass, null); + + // not sure how many should be found ... it should find everything + // it's hard to say how many that is, but it should at least find the + // number we created + Assert.GreaterOrEqual(results.Count, numCreated); + + // check that they are all of the proper objectclass + foreach (ConnectorObject co in results) + { + ConnectorAttribute objectClassAttr = + co.GetAttributeByName("objectClass"); + Boolean foundCorrectObjectClass = false; + foreach (Object o in objectClassAttr.Value) + { + if ((o is String) && (o != null)) + { + String stringValue = (String)o; + if (stringValue.ToUpper().Trim().Equals( + ActiveDirectoryConnector.OBJECTCLASS_GROUP, StringComparison.CurrentCultureIgnoreCase)) + { + foundCorrectObjectClass = true; + } + } + } + Assert.IsTrue(foundCorrectObjectClass); + } + } + finally + { + foreach (Uid uid in createdUids) + { + if (uid != null) + { + DeleteAndVerifyObject(connector, ActiveDirectoryConnector.groupObjectClass, uid, false, true); + } + } + } + } + + [Test] + public void TestSearchByName_account() + { + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + + Uid createUid = null; + + try + { + createUid = CreateAndVerifyObject(connector, ObjectClass.ACCOUNT, + GetNormalAttributes_Account()); + + // find out what the name was + ConnectorObject newObject = GetConnectorObjectFromUid(connector, + ObjectClass.ACCOUNT, createUid); + Name nameAttr = newObject.Name; + Assert.IsNotNull(nameAttr); + + //search normally + ICollection results = TestHelpers.SearchToList(connector, + ObjectClass.ACCOUNT, FilterBuilder.EqualTo(nameAttr)); + + // there really should only be one + Assert.AreEqual(results.Count, 1); + + // and it must have the value we were searching for + String createName = ActiveDirectoryUtils.NormalizeLdapString(nameAttr.GetNameValue()); + String foundName = ActiveDirectoryUtils.NormalizeLdapString(results.ElementAt(0).Name.GetNameValue()); + Assert.AreEqual(createName, foundName); + + //search in uppercase + ConnectorAttribute nameUpper = ConnectorAttributeBuilder.Build( + nameAttr.Name, nameAttr.GetNameValue().ToUpper()); + results = TestHelpers.SearchToList(connector, + ObjectClass.ACCOUNT, FilterBuilder.EqualTo(nameUpper)); + + // there really should only be one + Assert.AreEqual(results.Count, 1); + + // and it must have the value we were searching for + createName = ActiveDirectoryUtils.NormalizeLdapString(nameAttr.GetNameValue()); + foundName = ActiveDirectoryUtils.NormalizeLdapString(results.ElementAt(0).Name.GetNameValue()); + Assert.AreEqual(createName, foundName); + + //search in lowercase + ConnectorAttribute nameLower = ConnectorAttributeBuilder.Build( + nameAttr.Name, nameAttr.GetNameValue().ToLower()); + results = TestHelpers.SearchToList(connector, + ObjectClass.ACCOUNT, FilterBuilder.EqualTo(nameLower)); + + // there really should only be one + Assert.AreEqual(results.Count, 1); + + // and it must have the value we were searching for + createName = ActiveDirectoryUtils.NormalizeLdapString(nameAttr.GetNameValue()); + foundName = ActiveDirectoryUtils.NormalizeLdapString(results.ElementAt(0).Name.GetNameValue()); + Assert.AreEqual(createName, foundName); + + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, false, true); + } + } + } + + [Test] + public void TestSearchByName_group() + { + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + + Uid createUid = null; + + try + { + createUid = CreateAndVerifyObject(connector, ActiveDirectoryConnector.groupObjectClass, + GetNormalAttributes_Group()); + + // find out what the name was + ConnectorObject newObject = GetConnectorObjectFromUid(connector, + ActiveDirectoryConnector.groupObjectClass, createUid); + Name nameAttr = newObject.Name; + Assert.IsNotNull(nameAttr); + + //search normally + ICollection results = TestHelpers.SearchToList(connector, + ActiveDirectoryConnector.groupObjectClass, FilterBuilder.EqualTo(nameAttr)); + + // there really should only be one + Assert.AreEqual(results.Count, 1); + + // and it must have the value we were searching for + String createName = ActiveDirectoryUtils.NormalizeLdapString(nameAttr.GetNameValue()); + String foundName = ActiveDirectoryUtils.NormalizeLdapString(results.ElementAt(0).Name.GetNameValue()); + Assert.AreEqual(createName, foundName); + + //search in uppercase + ConnectorAttribute nameUpper = ConnectorAttributeBuilder.Build( + nameAttr.Name, nameAttr.GetNameValue().ToUpper()); + results = TestHelpers.SearchToList(connector, + ActiveDirectoryConnector.groupObjectClass, FilterBuilder.EqualTo(nameUpper)); + + // there really should only be one + Assert.AreEqual(results.Count, 1); + + // and it must have the value we were searching for + createName = ActiveDirectoryUtils.NormalizeLdapString(nameAttr.GetNameValue()); + foundName = ActiveDirectoryUtils.NormalizeLdapString(results.ElementAt(0).Name.GetNameValue()); + Assert.AreEqual(createName, foundName); + + //search in lowercase + ConnectorAttribute nameLower = ConnectorAttributeBuilder.Build( + nameAttr.Name, nameAttr.GetNameValue().ToLower()); + results = TestHelpers.SearchToList(connector, + ActiveDirectoryConnector.groupObjectClass, FilterBuilder.EqualTo(nameLower)); + + // there really should only be one + Assert.AreEqual(results.Count, 1); + + // and it must have the value we were searching for + createName = ActiveDirectoryUtils.NormalizeLdapString(nameAttr.GetNameValue()); + foundName = ActiveDirectoryUtils.NormalizeLdapString(results.ElementAt(0).Name.GetNameValue()); + Assert.AreEqual(createName, foundName); + + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ActiveDirectoryConnector.groupObjectClass, + createUid, false, true); + } + } + } + + [Test] + public void TestSearchByCNWithWildcard_account() + { + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + + Uid uid1 = null; + Uid uid2 = null; + + try + { + // create a couple things to find + uid1 = CreateAndVerifyObject(connector, ObjectClass.ACCOUNT, + GetNormalAttributes_Account()); + uid2 = CreateAndVerifyObject(connector, ObjectClass.ACCOUNT, + GetNormalAttributes_Account()); + + ICollection results = TestHelpers.SearchToList(connector, + ObjectClass.ACCOUNT, FilterBuilder.EqualTo( + ConnectorAttributeBuilder.Build("CN", "nunit*"))); + + // there should be at least the two we just created + Assert.GreaterOrEqual(results.Count, 2); + foreach (ConnectorObject co in results) + { + // and it must have the value we were searching for + String foundName = ActiveDirectoryUtils.NormalizeLdapString( + co.Name.GetNameValue()); + Assert.That(foundName.ToUpper().StartsWith("CN=NUNIT")); + } + } + finally + { + if (uid1 != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + uid1, false, true); + } + if (uid2 != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + uid2, false, true); + } + } + } + + [Test] + public void TestSearchByRegularAttribute_account() + { + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + + Uid createUid = null; + + try + { + ConnectorAttribute createSnAttr = + ConnectorAttributeBuilder.Build("sn", "nunitSearch"); + ICollection attributes = GetNormalAttributes_Account(); + attributes.Remove(ConnectorAttributeUtil.Find("sn", attributes)); + attributes.Add(createSnAttr); + createUid = CreateAndVerifyObject(connector, ObjectClass.ACCOUNT, + attributes); + + ICollection results = TestHelpers.SearchToList(connector, + ObjectClass.ACCOUNT, FilterBuilder.EqualTo(createSnAttr)); + + // there should be at least the newly created one + Assert.GreaterOrEqual(results.Count, 1); + + // and it must have the value we were searching for + Boolean foundCreated = false; + foreach (ConnectorObject resultObject in results) + { + ConnectorAttribute foundSnAttr = + resultObject.GetAttributeByName("sn"); + Assert.AreEqual(createSnAttr, foundSnAttr); + + // keep track of if we've found the one we created + if (createUid.Equals(resultObject.Uid)) + { + // cant have it twice + Assert.IsFalse(foundCreated); + foundCreated = true; + } + } + // be certain we saw the one we created + Assert.IsTrue(foundCreated); + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, false, true); + } + } + } + + [Test] + public void TestSearchByRegularAttributeWithWildcard_account() + { + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + + ICollection uids = new HashSet(); + + try + { + int randomNumber = GetRandomNumber(); + + String snPrefix = "nunitWCTest"; + + for (int i = 0; i < 10; i++) + { + ICollection attributes = + GetNormalAttributes_Account(); + attributes.Remove(ConnectorAttributeUtil.Find("sn", attributes)); + attributes.Add(ConnectorAttributeBuilder.Build("sn", + snPrefix + GetRandomNumber())); + Uid tempUid = CreateAndVerifyObject(connector, ObjectClass.ACCOUNT, + attributes); + Assert.IsNotNull(tempUid); + uids.Add(tempUid); + } + + ICollection results = TestHelpers.SearchToList(connector, + ObjectClass.ACCOUNT, FilterBuilder.StartsWith( + ConnectorAttributeBuilder.Build("sn", snPrefix))); + + // there should be at least the newly created one + Assert.GreaterOrEqual(results.Count, 1); + + // make a duplicate list + ICollection uidsToValidate = new HashSet(uids); + + // and it must have the value we were searching for + foreach (ConnectorObject resultObject in results) + { + ConnectorAttribute foundSnAttr = + resultObject.GetAttributeByName("sn"); + String snValue = ConnectorAttributeUtil.GetStringValue(foundSnAttr); + Assert.That(snValue.StartsWith(snPrefix)); + uidsToValidate.Remove(resultObject.Uid); + if (uidsToValidate.Count == 0) + { + break; + } + } + Assert.AreEqual(0, uidsToValidate.Count); + } + finally + { + foreach (Uid createdUid in uids) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createdUid, false, true); + } + } + } + + // test proper behavior of create with ALL attributes specified + [Test] + public void TestCreateWithAllAttributes_Account() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid createUid = null; + + try + { + // create user + ICollection createAttributes = GetAllAttributes_Account(); + createUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, createAttributes); + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, false, true); + } + } + } + + // test proper behavior of create with ALL attributes specified + [Test] + public void Test_OpAtt_Enabled_Account() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid createUid = null; + + try + { + // create user + ICollection createAttributes = GetNormalAttributes_Account(); + createAttributes.Add(ConnectorAttributeBuilder.BuildEnabled(true)); + createUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, createAttributes); + + ICollection updateReplaceAttributes = + new HashSet(); + updateReplaceAttributes.Add(ConnectorAttributeBuilder.BuildEnabled(false)); + UpdateReplaceAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, updateReplaceAttributes); + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, false, true); + } + } + } + + // Test scripting + [Test] + public void TestScriptOnResource() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + + try + { + RunScript(connector, "", "", ""); + RunScript(connector, GetProperty(ConfigHelper.CONFIG_PROPERTY_SCRIPT_USER_LOCAL), + GetProperty(ConfigHelper.CONFIG_PROPERTY_SCRIPT_PASSWORD_LOCAL), ""); + RunScript(connector, GetProperty(ConfigHelper.CONFIG_PROPERTY_SCRIPT_USER_DOMAIN), + GetProperty(ConfigHelper.CONFIG_PROPERTY_SCRIPT_PASSWORD_DOMAIN), ""); + + // now try one with the prefix set + ActiveDirectoryConfiguration prefixConfig = (ActiveDirectoryConfiguration)ConfigHelper.GetConfiguration(); + string prefix = "UnitTest_"; + connector.Dispose(); + connector.Init(prefixConfig); + RunScript(connector, "", "", prefix); + + + // try with invalid credentials + bool scriptFailed = false; + try + { + RunScript(connector, GetProperty(ConfigHelper.CONFIG_PROPERTY_USER), "bogus", ""); + } + catch (Exception e) + { + scriptFailed = true; + } + Assert.That(scriptFailed); + } + finally + { + } + } + + [Test] + public void TestAddGroup_Account() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid groupUid = null; + Uid userUid = null; + try + { + userUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, GetNormalAttributes_Account()); + Filter userUidFilter = FilterBuilder.EqualTo(userUid); + IList foundUserObjects = + TestHelpers.SearchToList(connector, ObjectClass.ACCOUNT, userUidFilter); + Assert.AreEqual(1, foundUserObjects.Count); + + groupUid = CreateAndVerifyObject(connector, + ActiveDirectoryConnector.groupObjectClass, GetNormalAttributes_Group()); + Filter groupUidFilter = FilterBuilder.EqualTo(groupUid); + IList foundGroupObjects = + TestHelpers.SearchToList(connector, ActiveDirectoryConnector.groupObjectClass, groupUidFilter); + Assert.AreEqual(1, foundGroupObjects.Count); + String groupName = foundGroupObjects[0].Name.GetNameValue(); + + ICollection modifiedAttrs = new HashSet(); + modifiedAttrs.Add(ConnectorAttributeBuilder.Build(PredefinedAttributes.GROUPS_NAME, groupName)); + OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder(); + ICollection attributesToGet = GetDefaultAttributesToGet(ObjectClass.ACCOUNT); + attributesToGet.Add(PredefinedAttributes.GROUPS_NAME); + optionsBuilder.AttributesToGet = attributesToGet.ToArray(); + UpdateAddAndVerifyUser(connector, ObjectClass.ACCOUNT, + userUid, modifiedAttrs, optionsBuilder.Build()); + + } + finally + { + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, userUid, false, false); + DeleteAndVerifyObject(connector, ActiveDirectoryConnector.groupObjectClass, groupUid, false, false); + } + } + + [Test] + public void TestRemoveAttributeValue() + { + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + + Uid createUid = null; + + try + { + int randomNumber = GetRandomNumber(); + ICollection attributes = new HashSet(); + + attributes.Add(ConnectorAttributeBuilder.Build( + "ad_container", GetProperty(ConfigHelper.CONFIG_PROPERTY_CONTAINER))); + attributes.Add(ConnectorAttributeBuilder.Build( + "userPassword", "secret")); + attributes.Add(ConnectorAttributeBuilder.Build( + "sAMAccountName", "nunit" + randomNumber)); + attributes.Add(ConnectorAttributeBuilder.Build( + "givenName", "nunit")); + attributes.Add(ConnectorAttributeBuilder.Build( + "sn", "TestUser" + randomNumber)); + attributes.Add(ConnectorAttributeBuilder.Build( + "displayName", "nunit test user " + randomNumber)); + attributes.Add(ConnectorAttributeBuilder.Build( + Name.NAME, "cn=nunit" + randomNumber + "," + + GetProperty(ConfigHelper.CONFIG_PROPERTY_CONTAINER))); + attributes.Add(ConnectorAttributeBuilder.Build( + "mail", "nunitUser" + randomNumber + "@some.com")); + attributes.Add(ConnectorAttributeBuilder.Build( + "otherHomePhone", "512.555.1212", "512.123.4567")); + + createUid = CreateAndVerifyObject(connector, ObjectClass.ACCOUNT, + attributes); + + ICollection modifyAttributes = new HashSet(); + modifyAttributes.Add(createUid); + modifyAttributes.Add(ConnectorAttributeBuilder.Build("otherHomePhone", "512.555.1212")); + + connector.Update(UpdateType.DELETE, ObjectClass.ACCOUNT, modifyAttributes, null); + + Filter uidFilter = FilterBuilder.EqualTo(createUid); + IList objects = TestHelpers.SearchToList(connector, ObjectClass.ACCOUNT, uidFilter); + Assert.AreEqual(1, objects.Count); + + ConnectorAttribute otherHomePhoneAttr = ConnectorAttributeUtil.Find( + "otherHomePhone", objects[0].GetAttributes()); + + Assert.AreEqual(1, otherHomePhoneAttr.Value.Count); + Assert.AreEqual("512.123.4567", ConnectorAttributeUtil.GetSingleValue(otherHomePhoneAttr)); + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, false, true); + } + } + } + + [Test] + public void TestContainerChange_account() + { + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + + Uid createOuUid = null; + Uid createUserUid = null; + + try + { + // create container for this test + ICollection ouAttributes = GetNormalAttributes_OrganizationalUnit(); + createOuUid = CreateAndVerifyObject(connector, + ActiveDirectoryConnector.ouObjectClass, ouAttributes); + ICollection ouResults = TestHelpers.SearchToList( + connector, ActiveDirectoryConnector.ouObjectClass, FilterBuilder.EqualTo(createOuUid)); + Assert.AreEqual(1, ouResults.Count); + Assert.AreEqual(createOuUid, ouResults.ElementAt(0).Uid); + + // as a reminder, the uid is the dn for non account objects (idm backward compatiblity) + String ouPath = createOuUid.GetUidValue(); + + // create user + ICollection userAttributes = GetNormalAttributes_Account(); + createUserUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, userAttributes); + + //now change user container to the newly created one + Name createdName = ConnectorAttributeUtil.GetNameFromAttributes(userAttributes); + String newName = ActiveDirectoryUtils.GetRelativeName(createdName); + newName += ", " + ouPath; + ICollection updateAttrs = new HashSet(); + updateAttrs.Add(new Name(newName)); + updateAttrs.Add(createUserUid); + + connector.Update(UpdateType.REPLACE, ObjectClass.ACCOUNT, updateAttrs, null); + + ICollection results = TestHelpers.SearchToList( + connector, ObjectClass.ACCOUNT, FilterBuilder.EqualTo(createUserUid)); + Assert.AreEqual(1, results.Count); + Assert.AreEqual(createUserUid, results.ElementAt(0).Uid); + ConnectorAttribute foundContainerAttr = results.ElementAt(0).GetAttributeByName("ad_container"); + Assert.IsNotNull(foundContainerAttr); + + String lhs = ActiveDirectoryUtils.NormalizeLdapString(ouPath); + String rhs = ActiveDirectoryUtils.NormalizeLdapString(ConnectorAttributeUtil.GetStringValue(foundContainerAttr)); + Assert.AreEqual(lhs, rhs); + } + finally + { + if (createUserUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUserUid, false, true); + } + + if (createOuUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ActiveDirectoryConnector.ouObjectClass, + createOuUid, false, true); + } + } + } + + [Test] + public void TestContainerChange_group() + { + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + + Uid createOuUid = null; + Uid createGroupUid = null; + Uid updateGroupUid = null; + + try + { + // create container for this test + ICollection ouAttributes = GetNormalAttributes_OrganizationalUnit(); + createOuUid = CreateAndVerifyObject(connector, + ActiveDirectoryConnector.ouObjectClass, ouAttributes); + ICollection ouResults = TestHelpers.SearchToList( + connector, ActiveDirectoryConnector.ouObjectClass, FilterBuilder.EqualTo(createOuUid)); + Assert.AreEqual(1, ouResults.Count); + Assert.AreEqual(createOuUid, ouResults.ElementAt(0).Uid); + + // as a reminder, the uid is the dn for non account objects (idm backward compatiblity) + String ouPath = createOuUid.GetUidValue(); + + // create group + ICollection groupAttributes = GetNormalAttributes_Group(); + createGroupUid = CreateAndVerifyObject(connector, + ActiveDirectoryConnector.groupObjectClass, groupAttributes); + + //now change group's container to the newly created one + Name createdName = ConnectorAttributeUtil.GetNameFromAttributes(groupAttributes); + String newName = ActiveDirectoryUtils.GetRelativeName(createdName); + newName += ", " + ouPath; + ICollection updateAttrs = new HashSet(); + updateAttrs.Add(new Name(newName)); + updateAttrs.Add(createGroupUid); + + updateGroupUid = connector.Update(UpdateType.REPLACE, + ActiveDirectoryConnector.groupObjectClass, updateAttrs, null); + + ICollection results = TestHelpers.SearchToList( + connector, ActiveDirectoryConnector.groupObjectClass, FilterBuilder.EqualTo(updateGroupUid)); + Assert.AreEqual(1, results.Count); + Assert.AreEqual(updateGroupUid, results.ElementAt(0).Uid); + ConnectorAttribute foundContainerAttr = results.ElementAt(0).GetAttributeByName("ad_container"); + Assert.IsNotNull(foundContainerAttr); + + String lhs = ActiveDirectoryUtils.NormalizeLdapString(ouPath); + String rhs = ActiveDirectoryUtils.NormalizeLdapString(ConnectorAttributeUtil.GetStringValue(foundContainerAttr)); + Assert.AreEqual(lhs, rhs); + } + finally + { + if (updateGroupUid != null) + { + //remove the one. if we updated, this is the id + DeleteAndVerifyObject(connector, ActiveDirectoryConnector.groupObjectClass, + updateGroupUid, false, true); + } + else if (createGroupUid != null) + { + //remove the one. if we didn't update, this is the id + DeleteAndVerifyObject(connector, ActiveDirectoryConnector.groupObjectClass, + createGroupUid, false, true); + } + + if (createOuUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ActiveDirectoryConnector.ouObjectClass, + createOuUid, false, true); + } + } + } + + [Ignore] + [Test] + public void TestEnableDate() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid createUid = null; + + try + { + // create user + ICollection createAttributes = GetNormalAttributes_Account(); + createAttributes.Add(ConnectorAttributeBuilder.BuildEnabled(true)); + createAttributes.Add(ConnectorAttributeBuilder.BuildEnableDate(new DateTime(2000, 01, 01))); + createUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, createAttributes); + + ICollection updateReplaceAttributes = + new HashSet(); + updateReplaceAttributes.Add(ConnectorAttributeBuilder.BuildEnabled(false)); + UpdateReplaceAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, updateReplaceAttributes); + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, false, true); + } + } + } + + [Ignore] + [Test] + public void TestDisableDate() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid createUid = null; + + try + { + // create user + ICollection createAttributes = GetNormalAttributes_Account(); + createAttributes.Add(ConnectorAttributeBuilder.BuildEnabled(true)); + + // disable tommorrow + DateTime disableDate = DateTime.Now.AddHours(24); + + createAttributes.Add(ConnectorAttributeBuilder.BuildDisableDate(disableDate)); + createUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, createAttributes); + + ICollection updateReplaceAttributes = + new HashSet(); + updateReplaceAttributes.Add(ConnectorAttributeBuilder.BuildEnabled(false)); + UpdateReplaceAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, updateReplaceAttributes); + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, false, true); + } + } + } + + // test sync + [Test] + public void TestSyncGC() + { + // test with searchChildDomain (uses GC) + TestSync(true, GetProperty(ConfigHelper.config_PROPERTY_SYNC_CONTAINER_ROOT, null)); + TestSync(true, GetProperty(ConfigHelper.config_PROPERTY_SYNC_CONTAINER_CHILD, null)); + } + + // test sync + [Test] + public void TestSyncDC() + { + // test withouth searchChildDomains (uses DC) + TestSync(false, GetProperty(ConfigHelper.config_PROPERTY_SYNC_CONTAINER_ROOT, null)); + TestSync(false, GetProperty(ConfigHelper.config_PROPERTY_SYNC_CONTAINER_CHILD, null)); + } + + [Test] + public void TestUserPasswordChange() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid createUid = null; + + try + { + // create user + ICollection createAttributes = GetNormalAttributes_Account(); + // remove password, and set to something memorable + createAttributes.Remove(ConnectorAttributeUtil.Find(OperationalAttributes.PASSWORD_NAME, createAttributes)); + GuardedString gsCurrentPassword = GetGuardedString("1Password"); + createAttributes.Add(ConnectorAttributeBuilder.BuildPassword(gsCurrentPassword)); + createAttributes.Add(ConnectorAttributeBuilder.BuildEnabled(true)); + createUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, createAttributes); + + // make sure authenticate works here + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + gsCurrentPassword, null); + + ICollection updateReplaceAttributes = + new HashSet(); + GuardedString gsNewPassword = GetGuardedString("LongPassword2MeetTheRequirements!"); + updateReplaceAttributes.Add(ConnectorAttributeBuilder.BuildCurrentPassword(gsCurrentPassword)); + updateReplaceAttributes.Add(ConnectorAttributeBuilder.BuildPassword(gsNewPassword)); + UpdateReplaceAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, updateReplaceAttributes); + + // make sure authenticate works here + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + gsNewPassword, null); + + bool caughtAuthenticateFailedException = false; + try + { + // make sure authenticate doesnt work with original password + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + gsCurrentPassword, null); + } + catch (Exception e) + { + caughtAuthenticateFailedException = true; + } + + Assert.IsTrue(caughtAuthenticateFailedException, "Negative test case should throw an exception"); + + + // now a negative test case + GuardedString gsBogusPassword = GetGuardedString("BogusPassword"); + ICollection updateErrorReplaceAttributes = + new HashSet(); + updateErrorReplaceAttributes.Add(ConnectorAttributeBuilder.BuildCurrentPassword(gsBogusPassword)); + updateErrorReplaceAttributes.Add(ConnectorAttributeBuilder.BuildPassword(gsNewPassword)); + bool caughtWrongCurrentPasswordException = false; + try + { + // update should fail due to wrong current password + UpdateReplaceAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, updateErrorReplaceAttributes); + } + catch (Exception e) + { + caughtWrongCurrentPasswordException = true; + } + + Assert.IsTrue(caughtWrongCurrentPasswordException, "Negative test case should throw an exception"); + + // make sure authenticate works here + + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, false, true); + } + } + } + + [Test] + public void TestAuthenticateUser() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid createUid = null; + + try + { + // create user + ICollection createAttributes = GetNormalAttributes_Account(); + // remove password, and set to something memorable + createAttributes.Remove(ConnectorAttributeUtil.Find(OperationalAttributes.PASSWORD_NAME, createAttributes)); + GuardedString gsCurrentPassword = GetGuardedString("1Password"); + createAttributes.Add(ConnectorAttributeBuilder.BuildPassword(gsCurrentPassword)); + createAttributes.Add(ConnectorAttributeBuilder.BuildEnabled(true)); + createUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, createAttributes); + + // make sure authenticate works here + Uid authUid = connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + gsCurrentPassword, null); + + Assert.AreEqual(createUid, authUid); + + // make sure authenticate fails - wrong password + bool caughtException = false; + try + { + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + GetGuardedString("boguspassword"), null); + + } + catch (InvalidCredentialException e) + { + caughtException = true; + } + Assert.IsTrue(caughtException, "Negative test case should throw InvalidCredentialsException"); + + // change password + ICollection updateReplaceAttributes = + new HashSet(); + GuardedString gsNewPassword = GetGuardedString("LongPassword2MeetTheRequirements!"); + updateReplaceAttributes.Add(ConnectorAttributeBuilder.BuildCurrentPassword(gsCurrentPassword)); + updateReplaceAttributes.Add(ConnectorAttributeBuilder.BuildPassword(gsNewPassword)); + UpdateReplaceAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, updateReplaceAttributes); + + // make sure authenticate works here - new password + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + gsNewPassword, null); + + // make sure it fails with the wrong password + caughtException = false; + try + { + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + GetGuardedString("bogusPassword"), null); + } + catch (Exception e) + { + caughtException = true; + } + Assert.IsTrue(caughtException, "Negative test case should throw an exception"); + + // now set user must change password attribute + ICollection expirePasswordAttrs = + new HashSet(); + expirePasswordAttrs.Add(ConnectorAttributeBuilder.BuildPasswordExpired(true)); + UpdateReplaceAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, expirePasswordAttrs); + + // make sure authenticate fails - correct password, but expired + caughtException = false; + try + { + // make sure authenticate fails with correct password + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + gsNewPassword, null); + } + catch (PasswordExpiredException e) + { + caughtException = true; + Assert.AreEqual(createUid, e.Uid); + } + Assert.IsTrue(caughtException, "Negative test case should throw an exception"); + + // make sure authenticate fails - incorrect password, and expired + caughtException = false; + try + { + // make sure authenticate fails with wrong password (invalid credentials exception) + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + GetGuardedString("bogusPassword"), null); + } + catch (InvalidCredentialException e) + { + caughtException = true; + } + Assert.IsTrue(caughtException, "Negative test case should throw an exception"); + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, false, true); + } + } + } + + [Test] + public void TestShortnameAndDescription() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid uidAccount = null; + Uid uidGroup = null; + Uid uidOu = null; + string accountDescription = "nunit test account description"; + string groupDescription = "nunit test group description"; + string ouDescription = "nunit test ou description"; + try + { + ICollection accountAttributes = GetNormalAttributes_Account(); + RemoveAttributeByName(accountAttributes, "description"); + accountAttributes.Add(ConnectorAttributeBuilder.Build( + "description", accountDescription)); + ICollection groupAttributes = GetNormalAttributes_Group(); + RemoveAttributeByName(groupAttributes, "description"); + groupAttributes.Add(ConnectorAttributeBuilder.Build( + "description", groupDescription)); + ICollection ouAttributes = GetNormalAttributes_OrganizationalUnit(); + RemoveAttributeByName(ouAttributes, "description"); + ouAttributes.Add(ConnectorAttributeBuilder.Build( + "description", ouDescription)); + + uidAccount = CreateObject(connector, ObjectClass.ACCOUNT, accountAttributes); + + OperationOptionsBuilder accountOptionsBuilder = new OperationOptionsBuilder(); + ICollection accountAttributesToGet = GetDefaultAttributesToGet(ObjectClass.ACCOUNT); + accountAttributesToGet.Add(PredefinedAttributes.DESCRIPTION); + accountAttributesToGet.Add(PredefinedAttributes.SHORT_NAME); + accountAttributesToGet.Add("name"); + accountAttributesToGet.Add("description"); + accountOptionsBuilder.AttributesToGet = accountAttributesToGet.ToArray(); + + ConnectorObject accountObject = GetConnectorObjectFromUid(connector, + ObjectClass.ACCOUNT, uidAccount, accountOptionsBuilder.Build()); + + // compare description + string foundAccountDescription = ConnectorAttributeUtil.GetStringValue( + ConnectorAttributeUtil.Find(PredefinedAttributes.DESCRIPTION, accountObject.GetAttributes())); + Assert.AreEqual(accountDescription, foundAccountDescription); + + // compare shortname + string accountShortName = ConnectorAttributeUtil.GetStringValue( + ConnectorAttributeUtil.Find( + PredefinedAttributes.SHORT_NAME, accountObject.GetAttributes())); + string accountDisplayName = ConnectorAttributeUtil.GetStringValue( + ConnectorAttributeUtil.Find( + "name", accountObject.GetAttributes())); + Assert.AreEqual(accountShortName, accountDisplayName); + + uidGroup = CreateObject(connector, ActiveDirectoryConnector.groupObjectClass, groupAttributes); + + OperationOptionsBuilder groupOptionsBuilder = new OperationOptionsBuilder(); + ICollection groupAttributesToGet = GetDefaultAttributesToGet(ActiveDirectoryConnector.groupObjectClass); + groupAttributesToGet.Add(PredefinedAttributes.DESCRIPTION); + groupAttributesToGet.Add(PredefinedAttributes.SHORT_NAME); + groupAttributesToGet.Add("name"); + groupAttributesToGet.Add("description"); + groupOptionsBuilder.AttributesToGet = groupAttributesToGet.ToArray(); + + ConnectorObject groupObject = GetConnectorObjectFromUid(connector, + ActiveDirectoryConnector.groupObjectClass, uidGroup, groupOptionsBuilder.Build()); + + // compare description + string foundGroupDescription = ConnectorAttributeUtil.GetStringValue( + ConnectorAttributeUtil.Find(PredefinedAttributes.DESCRIPTION, groupObject.GetAttributes())); + Assert.AreEqual(groupDescription, foundGroupDescription); + + // compare shortnameB + string groupShortName = ConnectorAttributeUtil.GetStringValue( + ConnectorAttributeUtil.Find( + PredefinedAttributes.SHORT_NAME, groupObject.GetAttributes())); + string groupDisplayName = ConnectorAttributeUtil.GetStringValue( + ConnectorAttributeUtil.Find( + "name", groupObject.GetAttributes())); + Assert.AreEqual(groupShortName, groupDisplayName); + + uidOu = CreateObject(connector, ActiveDirectoryConnector.ouObjectClass, ouAttributes); + OperationOptionsBuilder ouOptionsBuilder = new OperationOptionsBuilder(); + ICollection ouAttributesToGet = GetDefaultAttributesToGet(ActiveDirectoryConnector.ouObjectClass); + ouAttributesToGet.Add(PredefinedAttributes.DESCRIPTION); + ouAttributesToGet.Add(PredefinedAttributes.SHORT_NAME); + ouAttributesToGet.Add("name"); + ouAttributesToGet.Add("description"); + ouOptionsBuilder.AttributesToGet = ouAttributesToGet.ToArray(); + + ConnectorObject ouObject = GetConnectorObjectFromUid(connector, + ActiveDirectoryConnector.ouObjectClass, uidOu, ouOptionsBuilder.Build()); + + // compare description + string foundOuDescription = ConnectorAttributeUtil.GetStringValue( + ConnectorAttributeUtil.Find(PredefinedAttributes.DESCRIPTION, ouObject.GetAttributes())); + Assert.AreEqual(ouDescription, foundOuDescription); + + // compare shortname + string ouShortName = ConnectorAttributeUtil.GetStringValue( + ConnectorAttributeUtil.Find( + PredefinedAttributes.SHORT_NAME, ouObject.GetAttributes())); + string ouDisplayName = ConnectorAttributeUtil.GetStringValue( + ConnectorAttributeUtil.Find( + "name", ouObject.GetAttributes())); + Assert.AreEqual(ouShortName, ouDisplayName); + } + finally + { + if (uidAccount != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + uidAccount, false, true); + } + if (uidGroup != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ActiveDirectoryConnector.groupObjectClass, + uidGroup, false, true); + } + if (uidOu != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ActiveDirectoryConnector.ouObjectClass, + uidOu, false, true); + } + } + } + + [Test] + public void TestPasswordExpiration() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid createUid = null; + + try + { + // create user + ICollection createAttributes = GetNormalAttributes_Account(); + // remove password, and set to something memorable + createAttributes.Remove(ConnectorAttributeUtil.Find(OperationalAttributes.PASSWORD_NAME, createAttributes)); + GuardedString gsCurrentPassword = GetGuardedString("1Password"); + createAttributes.Add(ConnectorAttributeBuilder.BuildPassword(gsCurrentPassword)); + createAttributes.Add(ConnectorAttributeBuilder.BuildEnabled(true)); + createUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, createAttributes); + + // make sure authenticate works here + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + gsCurrentPassword, null); + + // now set expiration to right now + ICollection expirePasswordNowAttrs = + new HashSet(); + + expirePasswordNowAttrs.Add( + ConnectorAttributeBuilder.BuildPasswordExpirationDate(DateTime.UtcNow)); + UpdateReplaceAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, expirePasswordNowAttrs); + + // sometimes expiring now, really means in a few milliseconds + // there is some rounding or something that happens. + Thread.Sleep(120000); + + // make sure authenticate fails - correct password, but expired + bool caughtException = false; + try + { + // make sure authenticate fails with correct password + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + gsCurrentPassword, null); + } + catch (InvalidCredentialException e) + { + if (e.Message.Contains("ex_AccountExpired")) + { + caughtException = true; + } + } + + Assert.IsTrue(caughtException, "Negative test case should throw an exception"); + + // set expiration to tommorrow + ICollection expirePasswordTomorrowAttrs = + new HashSet(); + expirePasswordTomorrowAttrs.Add( + ConnectorAttributeBuilder.BuildPasswordExpirationDate(DateTime.UtcNow.AddDays(1))); + UpdateReplaceAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, expirePasswordTomorrowAttrs); + + // make sure succeeds + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + gsCurrentPassword, null); + + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, false, true); + } + } + } + + [Test] + public void TestAccountLocked() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid createUid = null; + + try + { + // create user + ICollection createAttributes = GetNormalAttributes_Account(); + // remove password, and set to something memorable + createAttributes.Remove(ConnectorAttributeUtil.Find(OperationalAttributes.PASSWORD_NAME, createAttributes)); + GuardedString gsCurrentPassword = GetGuardedString("1Password"); + createAttributes.Add(ConnectorAttributeBuilder.BuildPassword(gsCurrentPassword)); + createAttributes.Add(ConnectorAttributeBuilder.BuildEnabled(true)); + createUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, createAttributes); + + // make sure authenticate works here + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + gsCurrentPassword, null); + + // not allowed to lock ... only unlock, so test unlock + // setting on machine must lockout user after 3 unsuccessful + // attempst for this to work. + // lock out by having unsucessful attempts. + try + { + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + GetGuardedString("bogusPassword"), null); + } + catch (Exception e) + { + } + + try + { + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + GetGuardedString("bogusPassword"), null); + } + catch (Exception e) + { + } + + try + { + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + GetGuardedString("bogusPassword"), null); + } + catch (Exception e) + { + } + + bool exceptionCaught = false; + try + { + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + gsCurrentPassword, null); + } + catch (Exception e) + { + exceptionCaught = true; + } + Assert.IsTrue(exceptionCaught, "Account not locked. Make sure that the server is setup for account lockout after 3 attempts"); + + ICollection unlockAttrs = + new HashSet(); + unlockAttrs.Add( + ConnectorAttributeBuilder.BuildLockOut(false)); + UpdateReplaceAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, unlockAttrs); + + // make sure succeeds + connector.Authenticate(ObjectClass.ACCOUNT, + ConnectorAttributeUtil.GetAsStringValue(ConnectorAttributeUtil.Find("sAMAccountName", createAttributes)), + gsCurrentPassword, null); + + + // now try to write lockout. Should get connector exception + bool connectorExceptionCaught = false; + try + { + ICollection lockAttrs = + new HashSet(); + lockAttrs.Add( + ConnectorAttributeBuilder.BuildLockOut(true)); + UpdateReplaceAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, lockAttrs); + + } + catch (ConnectorException e) + { + connectorExceptionCaught = true; + } + Assert.IsTrue(connectorExceptionCaught); + } + finally + { + if (createUid != null) + { + //remove the one we created + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, + createUid, false, true); + } + } + } + + public void TestSync(bool searchChildDomains, String container) + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + ActiveDirectoryConfiguration configuration = + (ActiveDirectoryConfiguration)ConfigHelper.GetConfiguration(); + configuration.SearchContext = container; + configuration.SearchChildDomains = searchChildDomains; + connector.Init(configuration); + + Uid createUid = null; + + ICollection createdUids = new List(); + try + { + SyncTestHelper syncHelper = new SyncTestHelper(); + + // do the first sync + //connector.Sync(ObjectClass.ACCOUNT, syncHelper.Token, syncHelper.SyncHandler_Initial, null); + + syncHelper.Init(connector.GetLatestSyncToken(ObjectClass.ACCOUNT)); + ICollection attributes = null; + + // create some users + for (int i = 0; i < 10; i++) + { + attributes = GetNormalAttributes_Account(); + createUid = CreateAndVerifyObject(connector, ObjectClass.ACCOUNT, + attributes); + syncHelper.AddModUid(createUid, attributes); + createdUids.Add(createUid); + } + + // sync, and verify + connector.Sync(ObjectClass.ACCOUNT, syncHelper._token, syncHelper.SyncHandler_ModifiedAccounts, null); + syncHelper.CheckAllSyncsProcessed(); + + // reset everything + syncHelper.Init(connector.GetLatestSyncToken(ObjectClass.ACCOUNT)); + + // modify a user, then add some users, then modify one of the added users + attributes = new List(); + attributes.Add(createdUids.First()); + attributes.Add(ConnectorAttributeBuilder.Build("sn", "replaced")); + connector.Update(UpdateType.REPLACE, ObjectClass.ACCOUNT, attributes, null); + syncHelper.AddModUid(createdUids.First(), attributes); + + for (int i = 0; i < 10; i++) + { + attributes = GetNormalAttributes_Account(); + createUid = CreateAndVerifyObject(connector, ObjectClass.ACCOUNT, + attributes); + syncHelper.AddModUid(createUid, attributes); + createdUids.Add(createUid); + } + + attributes = new List(); + attributes.Add(createdUids.Last()); + attributes.Add(ConnectorAttributeBuilder.Build("sn", "replaced")); + connector.Update(UpdateType.REPLACE, ObjectClass.ACCOUNT, attributes, null); + syncHelper.AddModUid(createdUids.Last(), attributes); + + // sync, and verify + connector.Sync(ObjectClass.ACCOUNT, syncHelper._token, syncHelper.SyncHandler_ModifiedAccounts, null); + syncHelper.CheckAllSyncsProcessed(); + + syncHelper.Init(connector.GetLatestSyncToken(ObjectClass.ACCOUNT)); + // delete the user + foreach (Uid uid in createdUids) + { + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, uid, + false, true); + syncHelper.AddDelUid(uid); + } + // sync and verify + connector.Sync(ObjectClass.ACCOUNT, syncHelper._token, syncHelper.SyncHandler_DeletedAccounts, null); + syncHelper.CheckAllSyncsProcessed(); + + createUid = CreateAndVerifyObject(connector, ObjectClass.ACCOUNT, + GetNormalAttributes_Account()); + syncHelper.AddModUid(createUid, attributes); + createdUids.Add(createUid); + + // now get the latest sync token, and it + // should be greater or equal to the last one we saw + SyncToken latestToken = connector.GetLatestSyncToken(ObjectClass.ACCOUNT); + Assert.Greater(GetUpdateUsnFromToken(latestToken), GetUpdateUsnFromToken(syncHelper._token)); + Assert.GreaterOrEqual(GetDeleteUsnFromToken(latestToken), GetDeleteUsnFromToken(syncHelper._token)); + } + finally + { + foreach (Uid uid in createdUids) + { + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, uid, + false, false); + } + } + } + + [Test] + public void TestGetLastSyncToken() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + ActiveDirectoryConfiguration configuration = + (ActiveDirectoryConfiguration)ConfigHelper.GetConfiguration(); + configuration.SearchChildDomains = false; + connector.Init(configuration); + SyncToken noGCToken = connector.GetLatestSyncToken(ObjectClass.ACCOUNT); + + configuration.SearchChildDomains = true; + connector.Init(configuration); + SyncToken GCToken = connector.GetLatestSyncToken(ObjectClass.ACCOUNT); + } + + public long GetUpdateUsnFromToken(SyncToken token) + { + string[] tokenParts = ((string)token.Value).Split('|'); + return long.Parse(tokenParts[0]); + } + + public long GetDeleteUsnFromToken(SyncToken token) + { + string[] tokenParts = ((string)token.Value).Split('|'); + return long.Parse(tokenParts[1]); + } + + class SyncTestHelper + { + IDictionary> _mods = null; + IList _dels = null; + + public SyncToken _token { get; set; } + + public void Init(SyncToken token) + { + _mods = new Dictionary>(); + _dels = new List(); + _token = token; + } + + public void AddModUid(Uid uid, ICollection attributes) + { + _mods[uid] = attributes; + } + + public void AddDelUid(Uid uid) + { + _dels.Add(uid); + } + + public bool SyncHandler_Initial(SyncDelta delta) + { + // do nothing .. just establishing the baseline + _token = delta.Token; + return true; + } + + public SyncResultsHandler SyncHandler_ModifiedAccounts + { + get + { + return new SyncResultsHandler() + { + Handle = delta => + { + _token = delta.Token; + if (delta.DeltaType.Equals(SyncDeltaType.CREATE_OR_UPDATE)) + { + // just ignore extra ones. they might have come in by other means + if (_mods.ContainsKey(delta.Uid)) + { + ICollection requestedAttrs = _mods[delta.Uid]; + + ActiveDirectoryConnectorTest.VerifyObject(requestedAttrs, + delta.Object); + _mods.Remove(delta.Uid); + } + } + return true; + } + }; + } + } + + public SyncResultsHandler SyncHandler_DeletedAccounts + { + get + { + return new SyncResultsHandler() + { + Handle = delta => + { + _token = delta.Token; + + _dels.Remove(delta.Uid); + return true; + } + }; + } + } + + public bool SyncHandler_Mixed(SyncDelta delta) + { + return true; + } + + public void CheckAllSyncsProcessed() + { + // since the handlers remove things from + // the list as found, this method is called + // at then end of a sync, and all arrays should + // be empty ... meaning everything is accounted + // for. + Assert.AreEqual(0, _dels.Count); + Assert.AreEqual(0, _mods.Count); + } + } + + public void RunScript(ActiveDirectoryConnector connector, String user, + string password, string prefix) + { + string tempFileName = Path.GetTempFileName(); + String arg0Name = "ARG0"; + String arg1Name = "ARG1"; + + string scriptText = String.Format( + "echo %{0}%:%{1}%:%USERNAME%:%PASSWORD% > \"{2}\"", prefix + arg0Name, + prefix + arg1Name, tempFileName); + + IDictionary arguments = new Dictionary(); + string arg0 = "argument_zero"; + string arg1 = "argument one"; + arguments.Add(arg0Name, arg0); + arguments.Add(arg1Name, arg1); + + OperationOptionsBuilder builder = new OperationOptionsBuilder(); + if (user.Length > 0) + { + builder.RunAsUser = user; + } + if (password.Length > 0) + { + builder.RunWithPassword = GetGuardedString(password); + } + builder.Options["variablePrefix"] = prefix; + + ScriptContext context = new ScriptContext("Shell", scriptText, arguments); + object resultObject = connector.RunScriptOnResource(context, builder.Build()); + Assert.IsNotNull(resultObject); + Assert.That(resultObject is int); + Assert.AreEqual(0, resultObject); + FileStream outputFs = new FileStream(tempFileName, FileMode.Open, FileAccess.Read); + StreamReader outputReader = new StreamReader(outputFs); + // read the first line + string output = outputReader.ReadLine(); + string[] returnedArray = output.Split(':'); + Assert.AreEqual(4, returnedArray.Length); + Assert.AreEqual((arg0), returnedArray[0]); + Assert.AreEqual((arg1), returnedArray[1]); + } + /* + [Test] + public void testBooScript() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(GetConfiguration()); + + try + { + string tempFileName = Path.GetTempFileName(); + StringBuilder scriptText = new StringBuilder(); + scriptText.Append("print(\"this is, \");"); + scriptText.Append("print(\"a test.\");"); + + IDictionary arguments = new Dictionary(); + string arg0 = "argument_zero"; + string arg1 = "argument one"; + arguments.Add("ARG0", arg0); + arguments.Add("ARG1", arg1); + + OperationOptionsBuilder builder = new OperationOptionsBuilder(); + + ScriptContext context = new ScriptContext("Boo", scriptText.ToString(), arguments); + object resultObject = connector.RunScriptOnResource(context, builder.Build()); + } + finally + { + } + } + */ + + // does a create and verify, then looks up and returns + // the new user's dn (used for adding to a group) + public String CreateGroupMember(ActiveDirectoryConnector connector) + { + Uid uidMember = CreateAndVerifyObject(connector, ObjectClass.ACCOUNT, + GetNormalAttributes_Account()); + + Filter uidFilter = FilterBuilder.EqualTo(uidMember); + ICollection foundObjects = TestHelpers.SearchToList( + connector, ObjectClass.ACCOUNT, uidFilter); + Assert.IsTrue(foundObjects.Count == 1); + String dnMember = ConnectorAttributeUtil.GetAsStringValue( + foundObjects.ElementAt(0).GetAttributeByName("distinguishedName")); + return dnMember; + } + + public Uid UpdateReplaceAndVerifyObject(ActiveDirectoryConnector connector, + ObjectClass oclass, Uid uid, ICollection attributes) + { + attributes.Add(uid); + Filter uidFilter = FilterBuilder.EqualTo(uid); + + // find the object ... can't update if it doesn't exist + ICollection currentConnectorObjects = TestHelpers.SearchToList( + connector, oclass, uidFilter); + Assert.AreEqual(1, currentConnectorObjects.Count); + + Uid updatedUid = connector.Update(UpdateType.REPLACE, oclass, + attributes, null); + + Assert.IsNotNull(updatedUid); + + uidFilter = FilterBuilder.EqualTo(updatedUid); + ICollection updatedConnectorObjects = TestHelpers.SearchToList( + connector, oclass, uidFilter); + Assert.IsTrue(updatedConnectorObjects.Count == 1); + VerifyObject(attributes, updatedConnectorObjects.ElementAt(0)); + return updatedUid; + } + + public Uid UpdateAddAndVerifyUser(ActiveDirectoryConnector connector, + ObjectClass oclass, Uid uid, ICollection attributes, + OperationOptions searchOptions) + { + // find the existing one, and save off all attributes + Filter uidFilter = FilterBuilder.EqualTo(uid); + ICollection currentObjects = TestHelpers.SearchToList( + connector, oclass, uidFilter, searchOptions); + Assert.IsTrue(currentObjects.Count == 1); + ICollection currentAttributes = + currentObjects.ElementAt(0).GetAttributes(); + + // build a list that has the 'added' values added to the existing values + ICollection comparisonAttributes = new List(); + foreach (ConnectorAttribute updateAttribute in attributes) + { + ConnectorAttribute existingAttribute = ConnectorAttributeUtil.Find( + updateAttribute.Name, currentAttributes); + comparisonAttributes.Add(AttConcat(updateAttribute, existingAttribute)); + } + + // make sure the uid is present in the attributes + attributes.Add(uid); + // now update with ADD to add additional home phones + Uid updatedUid = connector.Update(UpdateType.ADD, oclass, + attributes, null); + + // find it back + ICollection updatedObjects = TestHelpers.SearchToList( + connector, oclass, uidFilter, searchOptions); + Assert.IsTrue(updatedObjects.Count == 1); + + VerifyObject(comparisonAttributes, updatedObjects.ElementAt(0)); + + return updatedUid; + } + + /// + /// Concatenates two attributes' values + /// + /// Must be non null + /// May be null + /// new attribute with name of ca1 and value of ca1 + ca2 + public ConnectorAttribute AttConcat(ConnectorAttribute ca1, ConnectorAttribute ca2) + { + ConnectorAttributeBuilder builder = new ConnectorAttributeBuilder(); + Assert.IsNotNull(ca1); + if (ca2 == null) + { + // if the second is null, just build up a dummy one + ca2 = ConnectorAttributeBuilder.Build(ca1.Name); + } + + Assert.AreEqual(ca1.Name, ca2.Name); + builder.Name = ca1.Name; + builder.AddValue(ca1.Value); + builder.AddValue(ca2.Value); + + return builder.Build(); + } + + public Uid UpdateDeleteAndVerifyUser(ActiveDirectoryConnector connector, + ICollection attributes) + { + throw new NotImplementedException(); + } + + public void DeleteAndVerifyObject(ActiveDirectoryConnector connector, + ObjectClass oclass, Uid uid, bool verifyExists, bool verifyDeleted) + { + Filter uidFilter = FilterBuilder.EqualTo(uid); + + if (verifyExists) + { + // verify that object currently exists + ICollection foundObjects = TestHelpers.SearchToList( + connector, oclass, uidFilter); + + // verify that it was deleted + Assert.AreEqual(1, foundObjects.Count); + } + + // delete + try + { + connector.Delete(oclass, uid, null); + } + catch + { + if (verifyDeleted) + { + throw; + } + } + + if (verifyDeleted) + { + // verify that object was deleted + ICollection deletedObjects = TestHelpers.SearchToList( + connector, oclass, uidFilter); + + // verify that it was deleted + Assert.AreEqual(0, deletedObjects.Count); + } + } + + [Test] + // note that you must create at least one ou for this test to work + public void TestOuSearch() + { + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + ActiveDirectoryConfiguration config = (ActiveDirectoryConfiguration)ConfigHelper.GetConfiguration(); + connector.Init(config); + ObjectClass OUObjectClass = ActiveDirectoryConnector.ouObjectClass; + + ICollection foundObjects = TestHelpers.SearchToList( + connector, OUObjectClass, null); + Assert.Greater(foundObjects.Count, 0); + } + + [Test] + public void TestUnmatchedCaseGUIDSearch() + { + //Initialize Connector + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Uid userUid = null; + try + { + // test normal case + userUid = CreateAndVerifyObject(connector, + ObjectClass.ACCOUNT, GetNormalAttributes_Account()); + Filter userUidFilter = FilterBuilder.EqualTo(userUid); + IList foundUserObjects = + TestHelpers.SearchToList(connector, ObjectClass.ACCOUNT, userUidFilter); + Assert.AreEqual(1, foundUserObjects.Count); + + // now test for searching with uppercase guid + userUidFilter = FilterBuilder.EqualTo(new Uid(userUid.GetUidValue().ToUpper())); + foundUserObjects = TestHelpers.SearchToList( + connector, ObjectClass.ACCOUNT, userUidFilter); + Assert.AreEqual(1, foundUserObjects.Count); + + // now test for searching with lowercase guid + userUidFilter = FilterBuilder.EqualTo(new Uid(userUid.GetUidValue().ToLower())); + foundUserObjects = TestHelpers.SearchToList( + connector, ObjectClass.ACCOUNT, userUidFilter); + Assert.AreEqual(1, foundUserObjects.Count); + } + finally + { + if (userUid != null) + { + DeleteAndVerifyObject(connector, ObjectClass.ACCOUNT, userUid, false, false); + } + } + } + + [Test] + public void TestObjectRename() + { + var sut = new ActiveDirectoryConnector(); + sut.Init(ConfigHelper.GetConfiguration()); + + RenameObjectAndVerify(sut, ObjectClass.ACCOUNT, GetNormalAttributes_Account()); + RenameObjectAndVerify(sut, ActiveDirectoryConnector.groupObjectClass, GetNormalAttributes_Group()); + RenameObjectAndVerify(sut, ActiveDirectoryConnector.ouObjectClass, GetNormalAttributes_OrganizationalUnit()); + } + + private void RenameObjectAndVerify(ActiveDirectoryConnector connector, ObjectClass oc, ICollection createAttributes) + { + Uid createdUid = null; + Uid updatedUid = null; + try + { + // create the objec + createdUid = CreateAndVerifyObject(connector, oc, createAttributes); + + // update the name of the object + var oldName = ConnectorAttributeUtil.GetNameFromAttributes(createAttributes); + var newName = ActiveDirectoryUtils.GetRelativeName(oldName); + newName = newName.Trim() + "_new, " + GetProperty(ConfigHelper.CONFIG_PROPERTY_CONTAINER); + + updatedUid = UpdateReplaceAndVerifyObject(connector, oc, createdUid, + new List() { ConnectorAttributeBuilder.Build(Name.NAME, newName) }); + + if (oc.Equals(ObjectClass.ACCOUNT)) + { + Assert.AreEqual(createdUid, updatedUid, "The Uid of an object of type ACCOUNT must not change."); + } + + // test if the original object exists + var nameFilter = FilterBuilder.EqualTo(ConnectorAttributeBuilder.Build(Name.NAME, oldName.Value)); + var optionsBuilder = new OperationOptionsBuilder() + { + AttributesToGet = new[] { Name.NAME } + }; + var originalObjects = TestHelpers.SearchToList(connector, oc, nameFilter, optionsBuilder.Build()); + Assert.AreEqual(0, originalObjects.Count, + string.Format(System.Globalization.CultureInfo.InvariantCulture, + "An object of type '{0}' with the original name exists.", oc)); + } + finally + { + if (createdUid != null) + { + DeleteAndVerifyObject(connector, oc, createdUid, false, false); + } + + //make sure that the updated object is deleted as well + if (updatedUid != null) + { + DeleteAndVerifyObject(connector, oc, updatedUid, false, false); + } + } + } + + public Uid CreateAndVerifyObject(ActiveDirectoryConnector connector, + ObjectClass oclass, ICollection attributes) + { + // create object + Uid uid = CreateObject(connector, oclass, attributes); + VerifyObject(connector, uid, oclass, attributes); + return uid; + } + + public Uid CreateObject(ActiveDirectoryConnector connector, + ObjectClass oclass, ICollection attributes) + { + // if it exists, remove and recreate + Filter nameFilter = FilterBuilder.EqualTo(ConnectorAttributeBuilder.Build( + Name.NAME, ConnectorAttributeUtil.GetNameFromAttributes(attributes).Value)); + ICollection foundObjects = TestHelpers.SearchToList(connector, oclass, nameFilter); + if ((foundObjects != null) && (foundObjects.Count > 0)) + { + Assert.AreEqual(1, foundObjects.Count); + DeleteAndVerifyObject(connector, oclass, foundObjects.ElementAt(0).Uid, false, true); + } + + // create object + Uid uid = connector.Create(oclass, attributes, null); + Assert.IsNotNull(uid); + + return uid; + } + + public void VerifyObject(ActiveDirectoryConnector connector, Uid uid, + ObjectClass oclass, ICollection attributes) + { + // verify the object + VerifyObject(attributes, GetConnectorObjectFromUid(connector, oclass, uid)); + } + + /** + * NOTES: + * - cn and __NAME__ should be the same. Test if they are not + * test for proper behavior if __name__ is not supplied + * - test bogus attributes to like attribut named BogusAttr = hello + * or something + * - test writing to a read only attribute + * - test attributes with special values such as *, (, ), \ + */ + + public ICollection GetNormalAttributes_Account() + { + ICollection attributes = new HashSet(); + int randomNumber = GetRandomNumber(); + + // the container ... is a fabricated attribute + attributes.Add(ConnectorAttributeBuilder.Build( + "ad_container", GetProperty(ConfigHelper.CONFIG_PROPERTY_CONTAINER))); + attributes.Add(ConnectorAttributeBuilder.Build( + "userPassword", "secret")); + attributes.Add(ConnectorAttributeBuilder.Build( + "sAMAccountName", "nunitUser" + randomNumber)); + attributes.Add(ConnectorAttributeBuilder.Build( + "givenName", "nunit")); + attributes.Add(ConnectorAttributeBuilder.Build( + "sn", "TestUser" + randomNumber)); + attributes.Add(ConnectorAttributeBuilder.Build( + "displayName", "nunit test user " + randomNumber)); + attributes.Add(ConnectorAttributeBuilder.Build( + Name.NAME, "cn=nunit" + randomNumber + "," + + GetProperty(ConfigHelper.CONFIG_PROPERTY_CONTAINER))); + attributes.Add(ConnectorAttributeBuilder.Build( + "mail", "nunitUser" + randomNumber + "@some.com")); + attributes.Add(ConnectorAttributeBuilder.Build( + "otherHomePhone", "512.555.1212", "512.123.4567")); + return attributes; + } + + public ICollection GetAllAttributes_Account() + { + ICollection attributes = new HashSet(); + int randomNumber = GetRandomNumber(); + + // the container ... is a fabricated attribute + attributes.Add(ConnectorAttributeBuilder.Build( + "ad_container", GetProperty(ConfigHelper.CONFIG_PROPERTY_CONTAINER))); + GuardedString password = new GuardedString(); + foreach (char c in "secret") + { + password.AppendChar(c); + } + attributes.Add(ConnectorAttributeBuilder.BuildPassword( + password)); + attributes.Add(ConnectorAttributeBuilder.Build( + "sAMAccountName", "nunit" + randomNumber)); + attributes.Add(ConnectorAttributeBuilder.Build( + "givenName", "nunit")); + attributes.Add(ConnectorAttributeBuilder.Build( + "sn", "TestUser" + randomNumber)); + attributes.Add(ConnectorAttributeBuilder.Build( + "displayName", "nunit test user " + randomNumber)); + attributes.Add(ConnectorAttributeBuilder.Build( + "mail", "nunitUser" + randomNumber + "@some.com")); + attributes.Add(ConnectorAttributeBuilder.Build( + "telephoneNumber", "333-547-8453")); + // attributes.Add(ConnectorAttributeBuilder.Build( + // "msExchHomeServerName", "")); + attributes.Add(ConnectorAttributeBuilder.Build( + "employeeID", "1234567")); + attributes.Add(ConnectorAttributeBuilder.Build( + "division", "Identity Services")); + attributes.Add(ConnectorAttributeBuilder.Build( + "mobile", "554-210-8631")); + // attributes.Add(ConnectorAttributeBuilder.Build( + // "mDBOverQuotaLimit", "")); + attributes.Add(ConnectorAttributeBuilder.Build( + "middleName", "testCase")); + attributes.Add(ConnectorAttributeBuilder.Build( + "description", "This user was created as a test case for the AD Connector")); + // attributes.Add(ConnectorAttributeBuilder.Build( + // "mDBOverHardQuotaLimit", "")); + // attributes.Add(ConnectorAttributeBuilder.Build( + // "mDBUseDefaults", "")); + attributes.Add(ConnectorAttributeBuilder.Build( + "department", "Connector Affairs")); + // for manager, it looks like the manager has to exist + // attributes.Add(ConnectorAttributeBuilder.Build( + // "manager", "Some Guy")); + // attributes.Add(ConnectorAttributeBuilder.Build( + // "mDBStorageQuota", "")); + // attributes.Add(ConnectorAttributeBuilder.Build( + // "mailNickName", "")); + attributes.Add(ConnectorAttributeBuilder.Build( + "title", "Manager")); + attributes.Add(ConnectorAttributeBuilder.Build( + "initials", "XYZ")); + // attributes.Add(ConnectorAttributeBuilder.Build( + // "homeMTA", "")); + attributes.Add(ConnectorAttributeBuilder.Build( + "co", "United States")); + // attributes.Add(ConnectorAttributeBuilder.Build( + // "homeMDB", "")); + attributes.Add(ConnectorAttributeBuilder.Build( + "company", "NUnit Test Company")); + attributes.Add(ConnectorAttributeBuilder.Build( + "facsimileTelephoneNumber", "111-222-3333")); + attributes.Add(ConnectorAttributeBuilder.Build( + "homePhone", "222-333-4444")); + // attributes.Add(ConnectorAttributeBuilder.Build( + // "directoryEntryWS_PasswordExpired", "")); + attributes.Add(ConnectorAttributeBuilder.Build( + "streetAddress", "12345 Some Street")); + attributes.Add(ConnectorAttributeBuilder.Build( + "l", "Austin")); + attributes.Add(ConnectorAttributeBuilder.Build( + "st", "Texas")); + attributes.Add(ConnectorAttributeBuilder.Build( + "postalCode", "78717")); + // attributes.Add(ConnectorAttributeBuilder.Build( + // "AccountLocked", "")); + + // used to be 'Terminal Services Initial Program' + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_INITIAL_PROGRAM, "myprog.exe")); + + // used to be 'Terminal Services Initial Program Directory' + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_INITIAL_PROGRAM_DIR, "c:\\nunittest\\dir")); + + // unknown ... + // attributes.Add(ConnectorAttributeBuilder.Build( + // "Terminal Services Inherit Initial Program", true)); + + // used to be 'Terminal Services Allow Logon' - defaults to false, so testing true + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_ALLOW_LOGON, 1)); + + // used to be 'Terminal Services Active Session Timeout' + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_MAX_CONNECTION_TIME, 10000)); + + // used to be 'Terminal Services Disconnected Session Timeout' + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_MAX_DISCONNECTION_TIME, 20000)); + + // used to be 'Terminal Services Idle Timeout' + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_MAX_IDLE_TIME, 30000)); + + // used to be 'Terminal Services Connect Client Drives At Logon' + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_CONNECT_CLIENT_DRIVES_AT_LOGON, 1)); + + // used to be 'Terminal Services Connect Client Printers At Logon' + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_CONNECT_CLIENT_PRINTERS_AT_LOGON, 1)); + + // used to be 'Terminal Services Default To Main Client Printer' + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_DEFAULT_TO_MAIN_PRINTER, 1)); + + // used to be 'Terminal Services End Session On Timeout Or Broken Connection' + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_BROKEN_CONNECTION_ACTION, 1)); + + // used to be 'Terminal Services Allow Reconnect From Originating Client Only' + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_RECONNECTION_ACTION, 1)); + + // attributes.Add(ConnectorAttributeBuilder.Build( + // "Terminal Services Callback Settings", "")); + + // attributes.Add(ConnectorAttributeBuilder.Build( + // "Terminal Services Callback Phone Number", "")); + + // used to be 'Terminal Services Remote Control Settings' + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_ENABLE_REMOTE_CONTROL, 1)); + + // used to be 'Terminal Services User Profile + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_PROFILE_PATH, "\\My Profile")); + + // used to be 'Terminal Services Local Home Directory + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_HOME_DIRECTORY, "\\My Home Dir")); + + // used to be 'Terminal Services Home Directory Drive + attributes.Add(ConnectorAttributeBuilder.Build( + TerminalServicesUtils.TS_HOME_DRIVE, "C:")); + + // uSNChanged should be read only + // attributes.Add(ConnectorAttributeBuilder.Build( + // "uSNChanged", "")); + // objectGUID should be read only + // attributes.Add(ConnectorAttributeBuilder.Build( + // "objectGUID", "")); + + + // now set name operational attribute + attributes.Add(ConnectorAttributeBuilder.Build( + Name.NAME, "cn=nunit" + randomNumber + "," + + GetProperty(ConfigHelper.CONFIG_PROPERTY_CONTAINER))); + + + /* + // a few attributes not used in IDM + + // country code is not returned by default + attributes.Add(ConnectorAttributeBuilder.Build( + "countryCode", 23)); + + */ + return attributes; + } + + public ICollection GetNormalAttributes_Group() + { + ICollection attributes = new List(); + int randomNumber = GetRandomNumber(); + + attributes.Add(ConnectorAttributeBuilder.Build( + "mail", "groupmail@example.com")); + attributes.Add(ConnectorAttributeBuilder.Build( + "description", "Original Description" + randomNumber)); + attributes.Add(ConnectorAttributeBuilder.Build( + Name.NAME, "cn=nunitGroup" + randomNumber + "," + + GetProperty(ConfigHelper.CONFIG_PROPERTY_CONTAINER))); + attributes.Add(ConnectorAttributeBuilder.Build( + "groupType", 4)); + return attributes; + } + + public ICollection GetNormalAttributes_OrganizationalUnit() + { + ICollection attributes = new List(); + int randomNumber = GetRandomNumber(); + + attributes.Add(ConnectorAttributeBuilder.Build( + Name.NAME, "ou=nunitOU" + randomNumber + "," + + GetProperty(ConfigHelper.CONFIG_PROPERTY_CONTAINER))); + return attributes; + } + + private static void VerifyObject(ICollection requestedAttributes, + ConnectorObject returnedObject) + { + // verify guid is in the proper format. This is important for IDM. + if (returnedObject.ObjectClass.Equals(ObjectClass.ACCOUNT)) + { + + Uid uid = returnedObject.Uid; + String uidValue = uid.GetUidValue(); + Assert.That(uidValue.StartsWith((""), "GUID for account objects must end with >"); + Assert.That(uidValue.ToLower().Replace("guid", "GUID").Equals(uidValue), + "GUID for account objects must have lowercase hex strings"); + } + + // for now, skipping values that are very difficult to + // determine equality ... or they are not returned like + // 'userPassword'. + ICollection skipAttributeNames = new List(); + skipAttributeNames.Add("USERPASSWORD"); + skipAttributeNames.Add(OperationalAttributes.PASSWORD_NAME); + skipAttributeNames.Add(OperationalAttributes.CURRENT_PASSWORD_NAME); + skipAttributeNames.Add(Uid.NAME); + // have to ignore the password expire attribute. It will not come + // back EXACTLY the same as it was set. It seems like may ad rounds + // off to the nearest second, or minute, or something. + skipAttributeNames.Add(OperationalAttributes.PASSWORD_EXPIRATION_DATE_NAME); + + ICollection ldapStringAttributes = new List(); + ldapStringAttributes.Add("AD_CONTAINER"); + ldapStringAttributes.Add(Name.NAME); + ldapStringAttributes.Add(PredefinedAttributes.GROUPS_NAME); + ldapStringAttributes.Add(ActiveDirectoryConnector.ATT_ACCOUNTS); + + // for each attribute in the connector object ... + foreach (ConnectorAttribute attribute in requestedAttributes) + { + ConnectorAttribute returnedAttribute = returnedObject.GetAttributeByName( + attribute.Name); + + if (skipAttributeNames.Contains(attribute.Name.ToUpper())) + { + Trace.TraceWarning("Skipping comparison of attribute {0}", + attribute.Name); + Trace.TraceWarning("requested values were:"); + foreach (Object requestedValueObject in attribute.Value) + { + Trace.TraceWarning(requestedValueObject.ToString()); + } + if (returnedAttribute == null) + { + Trace.TraceWarning(" no {0} attribute was returned", + attribute.Name); + } + else + { + Trace.TraceWarning("returned values were:"); + foreach (Object returnedValueObject in returnedAttribute.Value) + { + Trace.TraceWarning(returnedValueObject.ToString()); + } + } + continue; + } + + Assert.IsNotNull(returnedAttribute); + + // for each value in the attribute ... + foreach (Object requestedValueObject in attribute.Value) + { + // order of multivalue attributes is not gauranted, so check + // all values of the returned object against the value + // also attributes like the ldap 'objectclass' might return + // more values than I set ... (set User, return user, top, inetorgperson) + Boolean foundValue = false; + foreach (Object returnedValueObject in returnedAttribute.Value) + { + Object lhs = requestedValueObject; + Object rhs = returnedValueObject; + + // if its an ldap string, put it in a standard form + if (ldapStringAttributes.Contains(attribute.Name.ToUpper())) + { + Assert.That(requestedValueObject is String); + Assert.That(returnedValueObject is String); + lhs = ActiveDirectoryUtils.NormalizeLdapString((String)requestedValueObject); + rhs = ActiveDirectoryUtils.NormalizeLdapString((String)returnedValueObject); + /* + // if either of them start with a server name, take it off + // it's not important to the comparison + string []lhsParts = ((string)lhs).Split('/'); + Assert.LessOrEqual(lhsParts.Length, 2); + lhs = (lhsParts.Length) == 1 ? lhsParts[0] : lhsParts[1]; + string[] rhsParts = ((string)rhs).Split('/'); + Assert.LessOrEqual(rhsParts.Length, 2); + lhs = (rhsParts.Length) == 1 ? rhsParts[0] : rhsParts[1]; + */ + } + + if (lhs.Equals(rhs)) + { + foundValue = true; + break; + } + } + Assert.IsTrue(foundValue, + String.Format("Could not find value {0} for attribute named {1}", + requestedValueObject, attribute.Name)); + } + } + } + + // this needs to be replaced by the real one. + public string GetProperty(string propertyName) + { + var propertyValue = + TestHelpers.GetProperties(typeof(ActiveDirectoryConnector)).GetProperty(propertyName); + //Trace.WriteLine(String.Format("GetProperty: {0} = {1}", propertyName, propertyValue)); + return propertyValue; + } + + public string GetProperty(string propertyName, string def) + { + var propertyValue = + TestHelpers.GetProperties(typeof(ActiveDirectoryConnector)).GetProperty(propertyName, def); + //Trace.WriteLine(String.Format("GetProperty: {0} = {1}", propertyName, propertyValue)); + return propertyValue; + } + + public ConnectorObject GetConnectorObjectFromUid( + ActiveDirectoryConnector connector, ObjectClass oclass, Uid uid) + { + return GetConnectorObjectFromUid(connector, oclass, uid, null); + } + + public ConnectorObject GetConnectorObjectFromUid( + ActiveDirectoryConnector connector, ObjectClass oclass, Uid uid, + OperationOptions options) + { + // get sid to check permissions + Filter uidFilter = FilterBuilder.EqualTo(uid); + ICollection objects = TestHelpers.SearchToList(connector, + oclass, uidFilter, options); + Assert.AreEqual(1, objects.Count); + return objects.ElementAt(0); + } + + public ICollection GetDefaultAttributesToGet(ObjectClass oclass) + { + ICollection attributesToGet = new HashSet(); + + ActiveDirectoryConnector connector = new ActiveDirectoryConnector(); + connector.Init(ConfigHelper.GetConfiguration()); + Schema schema = connector.Schema(); + ObjectClassInfo ocInfo = schema.FindObjectClassInfo(oclass.GetObjectClassValue()); + Assert.IsNotNull(ocInfo); + + foreach (ConnectorAttributeInfo caInfo in ocInfo.ConnectorAttributeInfos) + { + if (caInfo.IsReturnedByDefault) + { + attributesToGet.Add(caInfo.Name); + } + } + + return attributesToGet; + } + + public GuardedString GetGuardedString(string regularString) + { + GuardedString guardedString = new GuardedString(); + foreach (char c in regularString) + { + guardedString.AppendChar(c); + } + return guardedString; + } + + int GetRandomNumber() + { + const int randomRange = 10000000; + + int number = 0; + + // having trouble with duplicate random numbers, so try a few hundred + // times to get a unique one before giving up. + for (int i = 0; i < 500; i++) + { + number = _rand.Next(randomRange); +#if DEBUG + // make sure the debug numbers are in a different + // range than release ones to eliminate conflicts during + // the build where both configurations are run concurrently + number += randomRange; +#endif + if (!(randomList.Contains(number))) + { + randomList.Add(number); + break; + } + } + + return number; + } + + public void RemoveAttributeByName(ICollection accountAttributes, string name) + { + ConnectorAttribute ca = ConnectorAttributeUtil.Find(name, accountAttributes); + if (ca != null) + { + accountAttributes.Remove(ca); + } + } + } + +} diff --git a/dotnet-connector/ActiveDirectoryConnectorTests/ActiveDirectoryConnectorTests.csproj b/dotnet-connector/ActiveDirectoryConnectorTests/ActiveDirectoryConnectorTests.csproj new file mode 100644 index 0000000..2b4a569 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnectorTests/ActiveDirectoryConnectorTests.csproj @@ -0,0 +1,109 @@ + + + + + Debug + AnyCPU + 2.0 + {DB8F7DA9-11A2-4BEB-8C14-25D8229EBE7E} + Library + Properties + Org.IdentityConnectors.ActiveDirectory + ActiveDirectoryConnectorTests + v4.0 + 512 + + + $(OPENICF_HOME) + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + Project + + + + False + $(ConnectorFrameworkDir)\Common.dll + + + False + $(ConnectorFrameworkDir)\Framework.dll + + + False + $(ConnectorFrameworkDir)\FrameworkInternal.dll + + + False + $(ConnectorFrameworkDir)\Shell.ScriptExecutorFactory.dll + + + False + $(ConnectorFrameworkDir)\TestCommon.dll + + + + + + + + + + + + + + + + + + + + {BDF495CA-0FCD-4E51-A871-D467CDE3B43E} + ActiveDirectoryConnector + + + + + + + + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnectorTests/ConfigHelper.cs b/dotnet-connector/ActiveDirectoryConnectorTests/ConfigHelper.cs new file mode 100644 index 0000000..50d1804 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnectorTests/ConfigHelper.cs @@ -0,0 +1,89 @@ +/* + * ==================== + * 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 Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Test.Common; + +namespace Org.IdentityConnectors.ActiveDirectory +{ + internal static class ConfigHelper + { + #region Configuration property name constants + public static readonly string CONFIG_PROPERTY_USER = "config_user"; + public static readonly string CONFIG_PROPERTY_PASSWORD = "config_password"; + public static readonly string CONFIG_PROPERTY_CONTAINER = "config_container"; + public static readonly string CONFIG_PROPERTY_SCRIPT_USER_LOCAL = "config_script_user_local"; + public static readonly string CONFIG_PROPERTY_SCRIPT_PASSWORD_LOCAL = "config_script_password_local"; + public static readonly string CONFIG_PROPERTY_SCRIPT_USER_DOMAIN = "config_script_user_domain"; + public static readonly string CONFIG_PROPERTY_SCRIPT_PASSWORD_DOMAIN = "config_script_password_domain"; + public static readonly string CONFIG_PROPERTY_LDAPHOSTNAME = "config_ldap_hostname"; + public static readonly string CONFIG_PROPERTY_SEARCH_CONTEXT = "config_search_context"; + public static readonly string config_PROPERTY_SYNC_CONTAINER_ROOT = "config_sync_container_root"; + public static readonly string config_PROPERTY_SYNC_CONTAINER_CHILD = "config_sync_container_child"; + public static readonly string CONFIG_PROPERTY_DOMAIN_NAME = "config_domain_name"; + public static readonly string CONFIG_PROPERTY_SYNC_DOMAIN_CONTROLLER = "config_sync_domain_controller"; + public static readonly string CONFIG_PROPERTY_GC_DOMAIN_CONTROLLER = "config_sync_gc_domain_controller"; + public static readonly string TEST_PARAM_SHARED_HOME_FOLDER = "test_param_shared_home_folder"; + #endregion + + #region Methods + /// + /// Gets the configuration used by the unit tests to acces an AD resource. + /// + /// A new instance of . + public static Configuration GetConfiguration() + { + var config = new ActiveDirectoryConfiguration + { + ConnectorMessages = TestHelpers.CreateDummyMessages(), + + Container = TestHelpers.GetProperties( + typeof( ActiveDirectoryConnector ) ).GetProperty( + ConfigHelper.CONFIG_PROPERTY_CONTAINER ), + DirectoryAdminName = TestHelpers.GetProperties( + typeof( ActiveDirectoryConnector ) ).GetProperty( + ConfigHelper.CONFIG_PROPERTY_USER ), + DirectoryAdminPassword = TestHelpers.GetProperties( + typeof( ActiveDirectoryConnector ) ).GetProperty( + ConfigHelper.CONFIG_PROPERTY_PASSWORD ), + DomainName = TestHelpers.GetProperties( + typeof (ActiveDirectoryConnector)).GetProperty( + ConfigHelper.CONFIG_PROPERTY_DOMAIN_NAME), + LDAPHostName = TestHelpers.GetProperties( + typeof (ActiveDirectoryConnector)).GetProperty( + ConfigHelper.CONFIG_PROPERTY_LDAPHOSTNAME), + SearchContext = TestHelpers.GetProperties( + typeof (ActiveDirectoryConnector)).GetProperty( + ConfigHelper.CONFIG_PROPERTY_SEARCH_CONTEXT), + SyncDomainController = TestHelpers.GetProperties( + typeof (ActiveDirectoryConnector)).GetProperty( + ConfigHelper.CONFIG_PROPERTY_SYNC_DOMAIN_CONTROLLER), + SyncGlobalCatalogServer = TestHelpers.GetProperties( + typeof (ActiveDirectoryConnector)).GetProperty( + ConfigHelper.CONFIG_PROPERTY_GC_DOMAIN_CONTROLLER) + }; + return config; + } + #endregion + } +} diff --git a/dotnet-connector/ActiveDirectoryConnectorTests/Org.IdentityConnectors.ActiveDirectory.ActiveDirectoryConnector/config/config.xml b/dotnet-connector/ActiveDirectoryConnectorTests/Org.IdentityConnectors.ActiveDirectory.ActiveDirectoryConnector/config/config.xml new file mode 100644 index 0000000..ede50e9 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnectorTests/Org.IdentityConnectors.ActiveDirectory.ActiveDirectoryConnector/config/config.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/dotnet-connector/ActiveDirectoryConnectorTests/TestParams.resx b/dotnet-connector/ActiveDirectoryConnectorTests/TestParams.resx new file mode 100644 index 0000000..735169b --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnectorTests/TestParams.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + cn=users,dc=dv207518domain2,dc=central,dc=sun,dc=com + + + localhost + + + etegrity + + + 389 + + + etegrity + + + etegrity + + + dv207518domain2\administrator + + + administrator + + + dv207518domain2\administrator + + \ No newline at end of file diff --git a/dotnet-connector/ActiveDirectoryConnectorTests/version.template b/dotnet-connector/ActiveDirectoryConnectorTests/version.template new file mode 100644 index 0000000..34c3267 --- /dev/null +++ b/dotnet-connector/ActiveDirectoryConnectorTests/version.template @@ -0,0 +1 @@ +1.4.1.0 \ No newline at end of file diff --git a/dotnet-connector/DotNetCommonBuild.Targets b/dotnet-connector/DotNetCommonBuild.Targets new file mode 100644 index 0000000..e1b6f92 --- /dev/null +++ b/dotnet-connector/DotNetCommonBuild.Targets @@ -0,0 +1,112 @@ + + + + + $(MSBuildProjectDirectory)\version.template + $(MSBuildProjectDirectory)\version.txt + ForgeRock AS + Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + $(MSBuildProjectDirectory)\..\Build + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CommonBeforeBuild; + $(BuildDependsOn); + CommonAfterBuild + + + $(CleanDependsOn); + CommonClean + + + + + + + + + + + + + + diff --git a/dotnet-connector/DotNetConnectors.sln b/dotnet-connector/DotNetConnectors.sln new file mode 100644 index 0000000..4043737 --- /dev/null +++ b/dotnet-connector/DotNetConnectors.sln @@ -0,0 +1,31 @@ +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActiveDirectoryConnector", "ActiveDirectoryConnector\ActiveDirectoryConnector.csproj", "{BDF495CA-0FCD-4E51-A871-D467CDE3B43E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActiveDirectoryConnectorTests", "ActiveDirectoryConnectorTests\ActiveDirectoryConnectorTests.csproj", "{DB8F7DA9-11A2-4BEB-8C14-25D8229EBE7E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExchangeConnector", "ExchangeConnector\ExchangeConnector.csproj", "{F1CB12B6-0DD7-4DAB-9B21-630449B8610D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BDF495CA-0FCD-4E51-A871-D467CDE3B43E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDF495CA-0FCD-4E51-A871-D467CDE3B43E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDF495CA-0FCD-4E51-A871-D467CDE3B43E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDF495CA-0FCD-4E51-A871-D467CDE3B43E}.Release|Any CPU.Build.0 = Release|Any CPU + {DB8F7DA9-11A2-4BEB-8C14-25D8229EBE7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB8F7DA9-11A2-4BEB-8C14-25D8229EBE7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB8F7DA9-11A2-4BEB-8C14-25D8229EBE7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB8F7DA9-11A2-4BEB-8C14-25D8229EBE7E}.Release|Any CPU.Build.0 = Release|Any CPU + {F1CB12B6-0DD7-4DAB-9B21-630449B8610D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1CB12B6-0DD7-4DAB-9B21-630449B8610D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1CB12B6-0DD7-4DAB-9B21-630449B8610D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1CB12B6-0DD7-4DAB-9B21-630449B8610D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/dotnet-connector/ExchangeConnector/Data/CommandInfos.xml b/dotnet-connector/ExchangeConnector/Data/CommandInfos.xml new file mode 100644 index 0000000..66c0097 --- /dev/null +++ b/dotnet-connector/ExchangeConnector/Data/CommandInfos.xml @@ -0,0 +1,317 @@ + + + + + + New-MailUser + Name + + ExternalEmailAddress + OrganizationalUnit + Password + UserPrincipalName + Alias + DisplayName + DomainController + FirstName + Initials + LastName + MacAttachmentFormat + MessageBodyFormat + MessageFormat + ResetPasswordOnNextLogon + SamAccountName + TemplateInstance + UsePreferMessageFormat + + + + Set-MailUser + Identity + + AcceptMessagesOnlyFrom + AcceptMessagesOnlyFromDLMembers + Alias + CreateDTMFMap + CustomAttribute1 + CustomAttribute2 + CustomAttribute3 + CustomAttribute4 + CustomAttribute5 + CustomAttribute6 + CustomAttribute7 + CustomAttribute8 + CustomAttribute9 + CustomAttribute10 + CustomAttribute11 + CustomAttribute12 + CustomAttribute13 + CustomAttribute14 + CustomAttribute15 + DisplayName + DomainController + EmailAddresses + EmailAddressPolicyEnabled + Extensions + ExternalEmailAddress + GrantSendOnBehalfTo + HiddenFromAddressListsEnabled + Instance + MacAttachmentFormat + MessageBodyFormat + MessageFormat + Name + PrimarySmtpAddress + RecipientLimits + RejectMessagesFrom + RejectMessagesFromDLMembers + RequireSenderAuthenticationEnabled + SamAccountName + SecondaryAddress + SecondaryDialPlan + SimpleDisplayName + UMDtmfMap + UseMapiRichTextFormat + UsePreferMessageFormat + UserPrincipalName + WindowsEmailAddress + MaxReceiveSize + MaxSendSize + + + + Get-MailUser + Identity + + Identity + OrganizationalUnit + ReadFromDomainController + + + + + Enable-MailUser + Identity + + ExternalEmailAddress + Alias + DomainController + MacAttachmentFormat + MessageBodyFormat + MessageFormat + UsePreferMessageFormat + + + + + + Get-User + Identity + + Anr + Credential + DomainController + Filter + Identity + OrganizationalUnit + ReadFromDomainController + RecipientTypeDetails + ResultSize + Sortby + + + + Set-User + Identity + + AssistantName + City + Company + CountryOrRegion + CreateDTMFMap + Department + DisplayName + DomainController + Fax + FirstName + HomePhone + Initials + Instance + LastName + Manager + MobilePhone + Name + Notes + Office + OtherFax + OtherHomePhone + OtherTelephone + Pager + Phone + PhoneticDisplayName + PostalCode + PostOfficeBox + ResetPasswordOnNextLogon + SamAccountName + SimpleDisplayName + StateOrProvince + StreetAddress + Title + UMDialPlan + UMDtmfMap + UserPrincipalName + WebPage + WindowsEmailAddress + + + + + + Enable-Mailbox + Identity + + Database + Equipment + Alias + LinkedDomainController + LinkedMasterAccount + Room + Shared + ActiveSyncMailboxPolicy + DomainController + LinkedCredential + ManagedFolderMailboxPolicy + ManagedFolderMailboxPolicyAllowed + + + + Get-Mailbox + Identity + + Anr + Credential + DomainController + Filter + IgnoreDefaultScope + OrganizationalUnit + ReadFromDomainController + RecipientTypeDetails + ResultSize + SortBy + + + + Set-Mailbox + Identity + + AcceptMessagesOnlyFrom + AcceptMessagesOnlyFromDLMembers + Alias + AntispamBypassEnabled + ApplyMandatoryProperties + Confirm + CreateDTMFMap + CustomAttribute1 + CustomAttribute2 + CustomAttribute3 + CustomAttribute4 + CustomAttribute5 + CustomAttribute6 + CustomAttribute7 + CustomAttribute8 + CustomAttribute9 + CustomAttribute10 + CustomAttribute11 + CustomAttribute12 + CustomAttribute13 + CustomAttribute14 + CustomAttribute15 + DeliverToMailboxAndForward + DisplayName + DomainController + DowngradeHighPriorityMessagesEnabled + EmailAddresses + EmailAddressPolicyEnabled + EndDateForRetentionHold + Extensions + ExternalOofOptions + ForwardingAddress + GrantSendOnBehalfTo + HiddenFromAddressListsEnabled + IgnoreDefaultScope + Instance + IssueWarningQuota + Languages + LinkedCredential + LinkedDomainController + LinkedMasterAccount + ManagedFolderMailboxPolicy + ManagedFolderMailboxPolicyAllowed + MaxBlockedSenders + MaxReceiveSize + MaxSafeSenders + MaxSendSize + Name + Office + OfflineAddressBook + PrimarySmtpAddress + ProhibitSendQuota + ProhibitSendReceiveQuota + RecipientLimits + RejectMessagesFrom + RejectMessagesFromDLMembers + RemoveManagedFolderAndPolicy + RequireSenderAuthenticationEnabled + ResourceCapacity + ResourceCustom + RetainDeletedItemsFor + RetainDeletedItemsUntilBackup + RetentionHoldEnabled + RulesQuota + SamAccountName + SCLDeleteEnabled + SCLDeleteThreshold + SCLJunkEnabled + SCLJunkThreshold + SCLQuarantineEnabled + SCLQuarantineThreshold + SCLRejectEnabled + SCLRejectThreshold + SecondaryAddress + SecondaryDialPlan + SimpleDisplayName + StartDateForRetentionHold + Type + UMDtmfMap + UseDatabaseQuotaDefaults + UseDatabaseRetentionDefaults + UserPrincipalName + UseRusServer + WhatIf + WindowsEmailAddress + + + + + diff --git a/dotnet-connector/ExchangeConnector/Data/PersistenceUtility.cs b/dotnet-connector/ExchangeConnector/Data/PersistenceUtility.cs new file mode 100644 index 0000000..a922d7c --- /dev/null +++ b/dotnet-connector/ExchangeConnector/Data/PersistenceUtility.cs @@ -0,0 +1,72 @@ +// +// ==================== +// 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]" +// ==================== +// +// Tomas Knappek + +namespace Org.IdentityConnectors.Exchange.Data +{ + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Reflection; + using System.Xml.Serialization; + + /// + /// Persitence helper class, it uses to read the persistent data + /// + internal sealed class PersistenceUtility + { + /// + /// Prevents a default instance of the class from being created. + /// + private PersistenceUtility() + { + } + + /// + /// Reads the from persistent store (xml file) + /// + /// List of + /// if not able to read from persistent store + internal static IList ReadCommandInfo() + { + // persistent file + const string PersistFile = "Org.IdentityConnectors.Exchange.Data.CommandInfos.xml"; + + Assembly assembly = Assembly.GetExecutingAssembly(); + Stream stream = assembly.GetManifestResourceStream(PersistFile); + if (stream == null) + { + throw new IOException( + string.Format(CultureInfo.CurrentCulture, "Unable to read the {0} file from Assembly", PersistFile)); + } + + // we just read + using (TextReader streamReader = new StreamReader(stream)) + { + XmlSerializer ser = new XmlSerializer(typeof(List)); + List commandInfos = (List)ser.Deserialize(streamReader); + return commandInfos; + } + } + } +} diff --git a/dotnet-connector/ExchangeConnector/Data/SerializableCommandInfo.cs b/dotnet-connector/ExchangeConnector/Data/SerializableCommandInfo.cs new file mode 100644 index 0000000..5c70182 --- /dev/null +++ b/dotnet-connector/ExchangeConnector/Data/SerializableCommandInfo.cs @@ -0,0 +1,83 @@ +// +// ==================== +// 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]" +// ==================== +// +// Tomas Knappek +namespace Org.IdentityConnectors.Exchange.Data +{ + using System.Collections.Generic; + + /// + /// Command metadata data object, it has to be public in order to be + /// used by , + /// can be also made "struct" however we don't expect huge amount of data here + /// + public class SerializableCommandInfo + { + /// + /// Command parameters local variable + /// + private readonly List parameters; + + /// + /// Initializes a new instance of the class. + /// + public SerializableCommandInfo() + { + this.parameters = new List(); + } + + /// + /// Gets or sets Command name + /// + public string Name { get; set; } + + /// + /// Gets or sets special parameter name used as id for this command + /// + public string NameParameter { get; set; } + + /// + /// Gets Command parameters - only string type is supported + /// + /// Note: Cann't be IList because it is not supported by + /// + /// + [System.Xml.Serialization.XmlArray("Parameters")] + [System.Xml.Serialization.XmlArrayItem("string", typeof(string))] + public List Parameters + { + get + { + return this.parameters; + } + } + + /// + /// Adds parameter to command parameter list + /// + /// string to be added as parameter + public void AddParameter(string parameter) + { + this.parameters.Add(parameter); + } + } +} diff --git a/dotnet-connector/ExchangeConnector/ExchangeConfiguration.cs b/dotnet-connector/ExchangeConnector/ExchangeConfiguration.cs new file mode 100644 index 0000000..a06c78c --- /dev/null +++ b/dotnet-connector/ExchangeConnector/ExchangeConfiguration.cs @@ -0,0 +1,35 @@ +// +// ==================== +// 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]" +// ==================== +// +// Tomas Knappek + +namespace Org.IdentityConnectors.Exchange +{ + using Org.IdentityConnectors.ActiveDirectory; + + /// + /// MS Exchange specific configuration + /// + public class ExchangeConfiguration : ActiveDirectoryConfiguration + { + } +} diff --git a/dotnet-connector/ExchangeConnector/ExchangeConnector.FxCop b/dotnet-connector/ExchangeConnector/ExchangeConnector.FxCop new file mode 100644 index 0000000..916e0ca --- /dev/null +++ b/dotnet-connector/ExchangeConnector/ExchangeConnector.FxCop @@ -0,0 +1,46 @@ + + + + True + $(FxCopDir)\Xml\FxCopReport.xsl + + + + + + True + True + True + 10 + 1 + + False + + False + 120 + False + + + + $(ProjectDir)/../../../../Program Files/Reference Assemblies/Microsoft/WindowsPowerShell/v1.0/ + $(ProjectDir)/../Framework/bin/Release/ + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet-connector/ExchangeConnector/ExchangeConnector.cs b/dotnet-connector/ExchangeConnector/ExchangeConnector.cs new file mode 100644 index 0000000..45f423f --- /dev/null +++ b/dotnet-connector/ExchangeConnector/ExchangeConnector.cs @@ -0,0 +1,930 @@ +// +// ==================== +// 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 2014 ForgeRock AS. +// +// Tomas Knappek + +namespace Org.IdentityConnectors.Exchange +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + + using Org.IdentityConnectors.ActiveDirectory; + using Org.IdentityConnectors.Common; + using Org.IdentityConnectors.Framework.Common.Exceptions; + using Org.IdentityConnectors.Framework.Common.Objects; + using Org.IdentityConnectors.Framework.Common.Objects.Filters; + using Org.IdentityConnectors.Framework.Spi; + + /// + /// MS Exchange connector - build to have the same functionality as Exchange resource adapter + /// + [ConnectorClass("connector_displayName", + typeof(ExchangeConfiguration), + MessageCatalogPaths = new[] { "Org.IdentityConnectors.Exchange.Messages", + "Org.IdentityConnectors.ActiveDirectory.Messages" })] + public class ExchangeConnector : ActiveDirectoryConnector + { + #region Fields Definition + + /// + /// Recipient Type attribute name + /// + internal const string AttRecipientType = "RecipientType"; + + /// + /// External Mail Address attribute name + /// + internal const string AttExternalMail = "ExternalEmailAddress"; + + /// + /// Database attribute name + /// + internal const string AttDatabase = "Database"; + + /// + /// Deleted atrribute name + /// + internal const string AttIsDeleted = "isDeleted"; + + /// + /// External Mail attribute name as in AD + /// + internal const string AttExternalMailADName = "targetAddress"; + + /// + /// Database attribute name as in AD + /// + internal const string AttDatabaseADName = "homeMDB"; + + /// + /// Attribute mapping constant + /// + internal static readonly IDictionary AttMap2AD = new Dictionary + { + { AttDatabase, AttDatabaseADName }, + { AttExternalMail, AttExternalMailADName } + }; + + /// + /// Attribute mapping constant + /// + internal static readonly IDictionary AttMapFromAD = new Dictionary + { + { AttDatabaseADName, AttDatabase }, + { AttExternalMailADName, AttExternalMail } + }; + + /// + /// ClassName - used for debugging purposes + /// + private static readonly string ClassName = typeof(ExchangeConnector).ToString(); + + /// + /// Recipient type attribute info + /// + private static readonly ConnectorAttributeInfo AttInfoRecipientType = + ConnectorAttributeInfoBuilder.Build( + AttRecipientType, + typeof(string), + ConnectorAttributeInfo.Flags.REQUIRED | ConnectorAttributeInfo.Flags.NOT_RETURNED_BY_DEFAULT); + + /// + /// External Mail attribute info + /// + private static readonly ConnectorAttributeInfo AttInfoExternalMail = + ConnectorAttributeInfoBuilder.Build( + AttExternalMail, + typeof(string), + ConnectorAttributeInfo.Flags.NOT_RETURNED_BY_DEFAULT | ConnectorAttributeInfo.Flags.MULTIVALUED); + + /// + /// Database attribute info + /// + private static readonly ConnectorAttributeInfo AttInfoDatabase = + ConnectorAttributeInfoBuilder.Build( + AttDatabase, + typeof(string), + ConnectorAttributeInfo.Flags.NOT_RETURNED_BY_DEFAULT); + + /// + /// Recipient type attribute for Mailbox + /// + private const string RcptTypeMailBox = "UserMailbox"; + + /// + /// Recipient type attribute for MailUser + /// + private const string RcptTypeMailUser = "MailUser"; + + /// + /// Recipient type attribute for AD only User + /// + private const string RcptTypeUser = "User"; + + /// + /// Configuration instance + /// + private ExchangeConfiguration configuration; + + /// + /// Runspace instance + /// + private RunSpaceInstance runspace; + + #endregion + + #region CreateOp.Create implementation + + /// + /// Implementation of CreateOp.Create + /// + /// Object class(oc + /// Object attributes + /// Operation options + /// Uid of the created object + public override Uid Create( + ObjectClass oclass, ICollection attributes, OperationOptions options) + { + ExchangeUtility.NullCheck(oclass, "oclass", this.configuration); + ExchangeUtility.NullCheck(attributes, "attributes", this.configuration); + + // we handle accounts only + if (!oclass.Is(ObjectClass.ACCOUNT_NAME)) + { + return base.Create(oclass, attributes, options); + } + + const string METHOD = "Create"; + Debug.WriteLine(METHOD + ":entry", ClassName); + + // get recipient type + string rcptType = ExchangeUtility.GetAttValue(AttRecipientType, attributes) as string; + + PSExchangeConnector.CommandInfo cmdInfoEnable = null; + PSExchangeConnector.CommandInfo cmdInfoSet = null; + switch (rcptType) + { + case RcptTypeMailBox: + cmdInfoEnable = PSExchangeConnector.CommandInfo.EnableMailbox; + cmdInfoSet = PSExchangeConnector.CommandInfo.SetMailbox; + break; + case RcptTypeMailUser: + cmdInfoEnable = PSExchangeConnector.CommandInfo.EnableMailUser; + cmdInfoSet = PSExchangeConnector.CommandInfo.SetMailUser; + break; + case RcptTypeUser: + break; + default: + throw new ArgumentException( + this.configuration.ConnectorMessages.Format( + "ex_bad_rcpt", "Recipient type [{0}] is not supported", rcptType)); + } + + // first create the object in AD + Uid uid = base.Create(oclass, FilterOut(attributes, cmdInfoEnable, cmdInfoSet), options); + + if (rcptType == RcptTypeUser) + { + // AD account only, we do nothing + return uid; + } + + // prepare the command + Command cmdEnable = ExchangeUtility.GetCommand(cmdInfoEnable, attributes, this.configuration); + Command cmdSet = ExchangeUtility.GetCommand(cmdInfoSet, attributes, this.configuration); + + try + { + this.InvokePipeline(cmdEnable); + this.InvokePipeline(cmdSet); + } + catch + { + Trace.TraceWarning("Rolling back AD create for UID: " + uid.GetUidValue()); + + // rollback AD create + try + { + Delete(oclass, uid, options); + } + catch + { + Trace.TraceWarning("Not able to rollback AD create for UID: " + uid.GetUidValue()); + + // note: this is not perfect, we hide the original exception + throw; + } + + // rethrow original exception + throw; + } + + Debug.WriteLine(METHOD + ":exit", ClassName); + return uid; + } + + #endregion + + /// + /// Implementation of UpdateOp.Update + /// + /// Update type + /// Object class + /// Object attributes + /// Operation options + /// Uid of the updated object + public override Uid Update( + UpdateType type, + ObjectClass oclass, + ICollection attributes, + OperationOptions options) + { + const string METHOD = "Update"; + Debug.WriteLine(METHOD + ":entry", ClassName); + + ExchangeUtility.NullCheck(type, "updatetype", this.configuration); + ExchangeUtility.NullCheck(oclass, "oclass", this.configuration); + ExchangeUtility.NullCheck(attributes, "attributes", this.configuration); + + // we handle accounts only + if (!oclass.Is(ObjectClass.ACCOUNT_NAME)) + { + return base.Update(type, oclass, attributes, options); + } + + // get recipient type and database + string rcptType = ExchangeUtility.GetAttValue(AttRecipientType, attributes) as string; + string database = ExchangeUtility.GetAttValue(AttDatabase, attributes) as string; + + // update in AD first + var filtered = FilterOut( + attributes, + PSExchangeConnector.CommandInfo.EnableMailbox, + PSExchangeConnector.CommandInfo.EnableMailUser, + PSExchangeConnector.CommandInfo.SetMailbox, + PSExchangeConnector.CommandInfo.SetMailUser); + Uid uid = base.Update(type, oclass, filtered, options); + + ConnectorObject aduser = this.ADSearchByUid(uid, oclass, ExchangeUtility.AddAttributeToOptions(options, AttDatabaseADName)); + attributes.Add(aduser.Name); + PSExchangeConnector.CommandInfo cmdInfo = PSExchangeConnector.CommandInfo.GetUser; + if (aduser.GetAttributeByName(AttDatabaseADName) != null) + { + // we can be sure it is user mailbox type + cmdInfo = PSExchangeConnector.CommandInfo.GetMailbox; + } + + PSObject psuser = this.GetUser(cmdInfo, attributes); + string origRcptType = psuser.Members[AttRecipientType].Value.ToString(); + if (String.IsNullOrEmpty(rcptType)) + { + rcptType = origRcptType; + } + + if (rcptType == RcptTypeMailUser) + { + if (type == UpdateType.REPLACE) + { + if (origRcptType != rcptType) + { + Command cmdEnable = ExchangeUtility.GetCommand( + PSExchangeConnector.CommandInfo.EnableMailUser, attributes, this.configuration); + this.InvokePipeline(cmdEnable); + } + + Command cmdSet = ExchangeUtility.GetCommand(PSExchangeConnector.CommandInfo.SetMailUser, attributes, this.configuration); + this.InvokePipeline(cmdSet); + } + else + { + throw new ConnectorException(this.configuration.ConnectorMessages.Format( + "ex_wrong_update_type", "Update type [{0}] not supported", type)); + } + } + else if (rcptType == RcptTypeMailBox) + { + // we should execute something like this here: + // get-user -identity id|?{$_.RecipientType -eq "User"}|enable-mailbox -database "db" + // unfortunately I was not able to get it working with the pipeline... that's why there are two commands + // executed :-( + // alternatively there can be something like: + // get-user -identity id -RecipientTypeDetails User|enable-mailbox -database "db", but we have then trouble + // with detecting attempt to change the database attribute + string origDatabase = psuser.Members[AttDatabase] != null ? psuser.Members[AttDatabase].Value.ToString() : null; + if (origRcptType != rcptType) + { + Command cmdEnable = ExchangeUtility.GetCommand(PSExchangeConnector.CommandInfo.EnableMailbox, attributes, this.configuration); + this.InvokePipeline(cmdEnable); + } + else + { + // trying to update the database? + if (database != null && database != origDatabase) + { + throw new ArgumentException( + this.configuration.ConnectorMessages.Format( + "ex_not_updatable", "Update of [{0}] attribute is not supported", AttDatabase)); + } + } + + Command cmdSet = ExchangeUtility.GetCommand(PSExchangeConnector.CommandInfo.SetMailbox, attributes, this.configuration); + this.InvokePipeline(cmdSet); + } + else if (rcptType == RcptTypeUser && origRcptType != rcptType) + { + throw new ArgumentException( + this.configuration.ConnectorMessages.Format( + "ex_update_notsupported", "Update of [{0}] to [{1}] is not supported", AttRecipientType, rcptType)); + } + else if (rcptType != RcptTypeUser) + { + // unsupported rcpt type + throw new ArgumentException( + this.configuration.ConnectorMessages.Format( + "ex_bad_rcpt", "Recipient type [{0}] is not supported", rcptType)); + } + + Debug.WriteLine(METHOD + ":exit", ClassName); + return uid; + } + + /// + /// Tests if the connector is properly configured and ready + /// + public override void Test() + { + // validate the configuration first, this will check AD configuration too + this.configuration.Validate(); + + // AD validation (includes configuration validation too) + base.Test(); + + // runspace check + this.runspace.Test(); + } + + /// + /// Implementation of SynOp.Sync + /// + /// Object class + /// Sync token + /// Sync results handler + /// Operation options + public override void Sync( + ObjectClass objClass, SyncToken token, SyncResultsHandler handler, OperationOptions options) + { + ExchangeUtility.NullCheck(objClass, "oclass", this.configuration); + + // we handle accounts only + if (!objClass.Is(ObjectClass.ACCOUNT_NAME)) + { + base.Sync(objClass, token, handler, options); + return; + } + + ICollection attsToGet = null; + if (options != null && options.AttributesToGet != null) + { + attsToGet = CollectionUtil.NewSet(options.AttributesToGet); + } + + // delegate to get the exchange attributes if requested + SyncResultsHandler xchangeHandler = new SyncResultsHandler() + { + Handle = delta => + { + if (delta.DeltaType == SyncDeltaType.DELETE) + { + return handler.Handle(delta); + } + + // replace the ad attributes with exchange one and add recipient type + ConnectorObject updated = ExchangeUtility.ReplaceAttributes(delta.Object, attsToGet, AttMapFromAD); + updated = this.AddExchangeAttributes(objClass, updated, attsToGet); + if (updated != delta.Object) + { + // build new sync delta, cause we changed the object + SyncDeltaBuilder deltaBuilder = new SyncDeltaBuilder + { + DeltaType = delta.DeltaType, + Token = delta.Token, + Uid = delta.Uid, + Object = updated + }; + delta = deltaBuilder.Build(); + } + + return handler.Handle(delta); + } + }; + + // call AD sync, use xchangeHandler + base.Sync(objClass, token, xchangeHandler, options); + } + + /// + /// Implementation of SearchOp.ExecuteQuery + /// + /// Object class + /// Query to execute + /// Results handler + /// Operation options + public override void ExecuteQuery( + ObjectClass oclass, string query, ResultsHandler handler, OperationOptions options) + { + ExchangeUtility.NullCheck(oclass, "oclass", this.configuration); + + // we handle accounts only + if (!oclass.Is(ObjectClass.ACCOUNT_NAME)) + { + base.ExecuteQuery(oclass, query, handler, options); + return; + } + + ICollection attsToGet = null; + if (options != null && options.AttributesToGet != null) + { + attsToGet = CollectionUtil.NewList(options.AttributesToGet); + } + + // delegate to get the exchange attributes if requested + ResultsHandler filter = new ResultsHandler() + { + Handle = cobject => + { + ConnectorObject filtered = ExchangeUtility.ReplaceAttributes( + cobject, attsToGet, AttMapFromAD); + filtered = this.AddExchangeAttributes(oclass, filtered, attsToGet); + return handler.Handle(filtered); + } + }; + + ResultsHandler handler2use = handler; + OperationOptions options2use = options; + if (options != null && options.AttributesToGet != null) + { + if (attsToGet.Contains(AttDatabase) || attsToGet.Contains(AttExternalMail) + || attsToGet.Contains(AttRecipientType)) + { + // replace Exchange attributes with AD names + var newAttsToGet = ExchangeUtility.FilterReplace(attsToGet, AttMap2AD); + + // we have to remove recipient type, as it is unknown to AD + newAttsToGet.Remove(AttRecipientType); + + // build new op options + var builder = new OperationOptionsBuilder(options); + string[] attributesToGet = new string[newAttsToGet.Count]; + newAttsToGet.CopyTo(attributesToGet, 0); + builder.AttributesToGet = attributesToGet; + options2use = builder.Build(); + handler2use = filter; + } + } + + base.ExecuteQuery(oclass, query, handler2use, options2use); + } + + /// + /// Implementation of SearchOp.CreateFilterTranslator + /// + /// Object class + /// Operation options + /// Filter translator + public override FilterTranslator CreateFilterTranslator(ObjectClass oclass, OperationOptions options) + { + return new LegacyExchangeConnectorFilterTranslator(); + } + + /// + /// Inits the connector with configuration injected + /// + /// Connector configuration + public override void Init(Configuration configuration) + { + this.configuration = (ExchangeConfiguration)configuration; + base.Init(configuration); + this.runspace = new RunSpaceInstance(RunSpaceInstance.SnapIn.Exchange, configuration.ConnectorMessages); + } + + /// + /// Dispose resources, + /// + public sealed override void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Attribute normalizer + /// + /// Object class + /// Attribute to be normalized + /// Normalized attribute + public ConnectorAttribute NormalizeAttribute(ObjectClass oclass, ConnectorAttribute attribute) + //public override ConnectorAttribute NormalizeAttribute(ObjectClass oclass, ConnectorAttribute attribute) + { + // normalize the attribute using AD connector first + //attribute = base.NormalizeAttribute(oclass, attribute); + + // normalize external mail value + if (attribute.Name == AttExternalMail && attribute.Value != null) + { + IList normAttributes = new List(); + bool normalized = false; + foreach (object val in attribute.Value) + { + string strVal = val as string; + if (strVal != null) + { + string[] split = strVal.Split(':'); + if (split.Length == 2) + { + // it contains delimiter, use the second part + normAttributes.Add(split[1]); + normalized = true; + } + else + { + // put the original value + normAttributes.Add(val); + } + } + } + + if (normalized) + { + // build the attribute again + return ConnectorAttributeBuilder.Build(attribute.Name, normAttributes); + } + } + + // return the original attribute + return attribute; + } + + /// + /// Dispose the resources we use + /// + /// true if called from + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // free managed resources + if (this.runspace != null) + { + this.runspace.Dispose(); + this.runspace = null; + } + } + } + + /// + /// Gets the object class info for specified object class, used for schema building + /// + /// ObjectClass to get info for + /// ObjectClass' ObjectClassInfo + protected override ObjectClassInfo GetObjectClassInfo(ObjectClass oc) + { + // get the object class from base + ObjectClassInfo oinfo = base.GetObjectClassInfo(oc); + + // add additional attributes for ACCOUNT + if (oc.Is(ObjectClass.ACCOUNT_NAME)) + { + var classInfoBuilder = new ObjectClassInfoBuilder { IsContainer = oinfo.IsContainer, ObjectType = oinfo.ObjectType }; + classInfoBuilder.AddAllAttributeInfo(oinfo.ConnectorAttributeInfos); + classInfoBuilder.AddAttributeInfo(AttInfoDatabase); + classInfoBuilder.AddAttributeInfo(AttInfoRecipientType); + classInfoBuilder.AddAttributeInfo(AttInfoExternalMail); + oinfo = classInfoBuilder.Build(); + } + + // return + return oinfo; + } + + /// + /// helper method to filter out all attributes used in ExchangeConnector only + /// + /// Connector attributes + /// CommandInfo whose parameters will be used and filtered out from attributes + /// + /// Filtered connector attributes + /// + private static ICollection FilterOut(ICollection attributes, params PSExchangeConnector.CommandInfo[] cmdInfos) + { + IList attsToRemove = new List { AttRecipientType, AttDatabase, AttExternalMail }; + if (cmdInfos != null) + { + foreach (PSExchangeConnector.CommandInfo cmdInfo in cmdInfos) + { + if (cmdInfo != null) + { + CollectionUtil.AddAll(attsToRemove, cmdInfo.Parameters); + } + } + } + + return ExchangeUtility.FilterOut(attributes, attsToRemove); + } + + /// + /// This method tries to get name and value from and + /// creates out of it + /// + /// PSMemberInfo to get the data from + /// Created ConnectorAttribute or null if not possible to create it + private static ConnectorAttribute GetAsAttribute(PSMemberInfo info) + { + Assertions.NullCheck(info, "param"); + if (info.Value != null) + { + string value = info.Value.ToString(); + + // TODO: add type recognition, currently only string is supported + if (value != info.Value.GetType().ToString() && !string.IsNullOrEmpty(value)) + { + return ConnectorAttributeBuilder.Build(info.Name, value); + } + } + + return null; + } + + /// + /// Returns first element of the collection + /// + /// Object Type stored in collection + /// Collection to get the first element from + /// First element in the collection, null if the collection is empty + private static T GetFirstElement(IEnumerable collection) where T : class + { + foreach (T o in collection) + { + return o; + } + + return null; + } + + /// + /// Gets Recipient Type/Database from Exchange database, this method can be more general, but it is ok + /// for out needs + /// + /// object class, currently the moethod works for only + /// connector object to get the recipient type/database for + /// attributes to get + /// Connector Object with recipient type added + /// In case of some troubles in powershell (if the + /// user is not found we get this exception too) + private ConnectorObject AddExchangeAttributes(ObjectClass oc, ConnectorObject cobject, IEnumerable attToGet) + { + ExchangeUtility.NullCheck(oc, "name", this.configuration); + ExchangeUtility.NullCheck(oc, "cobject", this.configuration); + + // we support ACCOUNT only or there is nothing to add + if (!oc.Is(ObjectClass.ACCOUNT_NAME) || attToGet == null) + { + return cobject; + } + + // check it is not deleted object + bool? deleted = ExchangeUtility.GetAttValue(AttIsDeleted, cobject.GetAttributes()) as bool?; + if (deleted != null && deleted == true) + { + // do nothing, it is deleted object + return cobject; + } + + ICollection lattToGet = CollectionUtil.NewCaseInsensitiveSet(); + CollectionUtil.AddAll(lattToGet, attToGet); + foreach (string att in attToGet) + { + if (cobject.GetAttributeByName(att) != null && att != AttDatabase) + { + lattToGet.Remove(att); + } + } + + if (lattToGet.Count == 0) + { + return cobject; + } + + ConnectorObjectBuilder cobjBuilder = new ConnectorObjectBuilder(); + cobjBuilder.AddAttributes(cobject.GetAttributes()); + + PSExchangeConnector.CommandInfo cmdInfo = PSExchangeConnector.CommandInfo.GetUser; + + // prepare the connector attribute list to get the command + ICollection attributes = new Collection { cobject.Name }; + + // get the command + Command cmd = ExchangeUtility.GetCommand(cmdInfo, attributes, this.configuration); + ICollection foundObjects = this.InvokePipeline(cmd); + PSObject user = null; + if (foundObjects != null && foundObjects.Count == 1) + { + user = GetFirstElement(foundObjects); + foreach (var info in user.Properties) + { + ConnectorAttribute att = GetAsAttribute(info); + if (att != null && lattToGet.Contains(att.Name)) + { + cobjBuilder.AddAttribute(att); + lattToGet.Remove(att.Name); + } + } + + if (lattToGet.Count == 0) + { + return cobjBuilder.Build(); + } + } + + if (user == null) + { + // nothing to do + return cobject; + } + + string rcptType = user.Members[AttRecipientType].Value.ToString(); + foundObjects = null; + + // get detailed information + if (rcptType == RcptTypeMailBox) + { + foundObjects = this.InvokePipeline(ExchangeUtility.GetCommand(PSExchangeConnector.CommandInfo.GetMailbox, attributes, this.configuration)); + } + else if (rcptType == RcptTypeMailUser) + { + foundObjects = this.InvokePipeline(ExchangeUtility.GetCommand(PSExchangeConnector.CommandInfo.GetMailUser, attributes, this.configuration)); + } + + if (foundObjects != null && foundObjects.Count == 1) + { + PSObject userDetails = GetFirstElement(foundObjects); + foreach (var info in userDetails.Properties) + { + ConnectorAttribute att = GetAsAttribute(info); + if (att != null && lattToGet.Contains(att.Name)) + { + cobjBuilder.AddAttribute(att); + lattToGet.Remove(att.Name); + } + } + } + + return cobjBuilder.Build(); + } + + /// + /// Invokes command in PowerShell runspace, this method is just helper + /// method to do the exception localization + /// + /// Command to execute + /// Collection of returned from runspace + /// If some troubles with command execution, + /// the exception will be partially localized + private ICollection InvokePipeline(Command cmd) + { + try + { + Trace.TraceInformation("PowerShell Command: " + cmd); + foreach (CommandParameter parameter in cmd.Parameters) + { + Trace.TraceInformation("parameter: " + parameter.Name + " value:" + parameter.Value); + } + + return this.runspace.InvokePipeline(cmd); + } + catch (Exception e) + { + throw new ConnectorException(this.configuration.ConnectorMessages.Format( + "ex_powershell_problem", "Problem while PowerShell execution {0}", e)); + } + } + + /// + /// helper method for searching object in AD by UID + /// + /// Uid of the searched + /// Object class + /// Operation options + /// Connector object found by the Uid + private ConnectorObject ADSearchByUid(Uid uid, ObjectClass oclass, OperationOptions options) + { + ExchangeUtility.NullCheck(uid, "uid", this.configuration); + ExchangeUtility.NullCheck(oclass, "oclass", this.configuration); + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + + ConnectorObject ret = null; + Filter filter = FilterBuilder.EqualTo(uid); + var translator = base.CreateFilterTranslator(oclass, options); + IList queries = translator.Translate(filter); + + if (queries.Count == 1) + { + ResultsHandler handler = new ResultsHandler() + { + Handle = cobject => + { + ret = cobject; + return false; + } + }; + base.ExecuteQuery(oclass, queries[0], handler, options); + } + + return ret; + } + + /// + /// Gets the Exchange user using powershell Get-User command + /// + /// command info to get the user + /// attributes containing the Name + /// with user info + private PSObject GetUser(PSExchangeConnector.CommandInfo cmdInfo, ICollection attributes) + { + // assert we have user name + string name = ExchangeUtility.GetAttValue(Name.NAME, attributes) as string; + ExchangeUtility.NullCheck(name, "User name", this.configuration); + + Command cmdUser = ExchangeUtility.GetCommand(cmdInfo, attributes, this.configuration); + ICollection users = this.InvokePipeline(cmdUser); + if (users.Count == 1) + { + foreach (PSObject obj in users) + { + return obj; + } + } + + throw new ArgumentException( + this.configuration.ConnectorMessages.Format( + "ex_bad_username", "Provided User name is not unique or not existing")); + } + + /// + /// Filter translator which does MS Exchange specific translation + /// + private class LegacyExchangeConnectorFilterTranslator : ActiveDirectoryFilterTranslator + { + /// + /// Translates the connector attribute name to LDAP name + /// + /// Connector attribute name + /// Translated string array + protected override string[] GetLdapNamesForAttribute(ConnectorAttribute attr) + { + if (attr.Is(AttDatabase)) + { + return new[] { AttDatabaseADName }; + } + + if (attr.Is(AttExternalMail)) + { + return new[] { AttExternalMailADName }; + } + + if (attr.Is(AttRecipientType)) + { + return null; + } + + return base.GetLdapNamesForAttribute(attr); + } + } + } +} diff --git a/dotnet-connector/ExchangeConnector/ExchangeConnector.csproj b/dotnet-connector/ExchangeConnector/ExchangeConnector.csproj new file mode 100644 index 0000000..ee53a27 --- /dev/null +++ b/dotnet-connector/ExchangeConnector/ExchangeConnector.csproj @@ -0,0 +1,128 @@ + + + + + Debug + AnyCPU + 2.0 + {F1CB12B6-0DD7-4DAB-9B21-630449B8610D} + Library + Properties + Org.IdentityConnectors.Exchange + Exchange.Connector + Exchange + v4.0 + 512 + true + $(OPENICF_HOME) + false + ExchangeConnectorTests + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + bin\Debug\Exchange.Connector.xml + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + $(ConnectorFrameworkDir)\Common.dll + False + + + False + $(ConnectorFrameworkDir)\Framework.dll + False + + + + + False + + + + + + + + + + + + + + + + {BDF495CA-0FCD-4E51-A871-D467CDE3B43E} + ActiveDirectoryConnector + True + + + + + + + + + Designer + + + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + + \ No newline at end of file diff --git a/dotnet-connector/ExchangeConnector/ExchangeUtility.cs b/dotnet-connector/ExchangeConnector/ExchangeUtility.cs new file mode 100644 index 0000000..f69d41e --- /dev/null +++ b/dotnet-connector/ExchangeConnector/ExchangeUtility.cs @@ -0,0 +1,417 @@ +// +// ==================== +// 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]" +// ==================== +// +// Tomas Knappek + +namespace Org.IdentityConnectors.Exchange +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Management.Automation.Runspaces; + using System.Reflection; + using Microsoft.Win32; + using Org.IdentityConnectors.ActiveDirectory; + using Org.IdentityConnectors.Common; + using Org.IdentityConnectors.Framework.Common.Objects; + using Org.IdentityConnectors.Framework.Spi; + + /// + /// Description of ExchangeUtility. + /// + public sealed class ExchangeUtility : CommonUtils + { + /// + /// class name, used for logging purposes + /// + private static readonly string ClassName = typeof(ExchangeUtility).ToString(); + + /// + /// Embedded xml resource file containg the object class definitions + /// + private const string FileObjectClassDef = "Org.IdentityConnectors.Exchange.ObjectClasses.xml"; + + /// + /// Exchange 2007 registry key, used for building the exchange assembly resolver + /// + private const string Exchange2007RegKey = "Software\\Microsoft\\Exchange\\v8.0\\Setup\\"; + + /// + /// Exchange 2010 registry key, used for building the exchange assembly resolver + /// + private const string Exchange2010RegKey = "Software\\Microsoft\\ExchangeServer\\v14\\Setup\\"; + + /// + /// Exchange registry value name, used together with or w.r.t the + /// Exchange version to manage. + /// + private const string ExchangeRegValueName = "MsiInstallPath"; + + /// + /// Prevents a default instance of the class from being created. + /// + private ExchangeUtility() + { + } + + /// + /// Creates Exchange 2010 Assembly Resolver, + /// + /// The source of the event + /// A that contains the event data + /// Assembly resolver that resolves Exchange 2010 assemblies + internal static Assembly AssemblyResolver2010(object sender, ResolveEventArgs args) + { + // Add path for the Exchange 2010 DLLs + if (args.Name.Contains("Microsoft.Exchange")) + { + string installPath = GetRegistryStringValue(Exchange2010RegKey, ExchangeRegValueName); + installPath += "\\bin\\" + args.Name.Split(',')[0] + ".dll"; + return Assembly.LoadFrom(installPath); + } + + return null; + } + + /// + /// Creates Exchange 2007 Assembly Resolver, + /// + /// The source of the event + /// A that contains the event data + /// Assembly resolver that resolves Exchange 2007 assemblies + internal static Assembly AssemblyResolver2007(object sender, ResolveEventArgs args) + { + // Add path for the Exchange 2007 DLLs + if (args.Name.Contains("Microsoft.Exchange")) + { + string installPath = GetRegistryStringValue(Exchange2007RegKey, ExchangeRegValueName); + installPath += "\\bin\\" + args.Name.Split(',')[0] + ".dll"; + return Assembly.LoadFrom(installPath); + } + + return null; + } + + /// + /// Get registry value, which is expected to be a string + /// + /// Registry Key Name + /// Registry Value Name + /// Registry value + /// If is null + /// If some problem with the registry value + internal static string GetRegistryStringValue(string keyName, string valName) + { + const string MethodName = "GetRegistryStringValue"; + Debug.WriteLine(MethodName + "(" + keyName + ", " + valName + ")" + ":entry", ClassName); + + // argument check + if (keyName == null) + { + keyName = string.Empty; + } + + if (valName == null) + { + throw new ArgumentNullException("valName"); + } + + RegistryKey regKey = Registry.LocalMachine.OpenSubKey(keyName, false); + try + { + if (regKey != null) + { + object val = regKey.GetValue(valName); + if (val != null) + { + RegistryValueKind regType = regKey.GetValueKind(valName); + if (!regType.Equals(RegistryValueKind.String)) + { + throw new InvalidDataException(String.Format( + CultureInfo.CurrentCulture, + "Invalid Registry data type, key name: {0} value name: {1} should be String", + keyName, + valName)); + } + + return Convert.ToString(val, CultureInfo.CurrentCulture); + } + else + { + throw new InvalidDataException(String.Format( + CultureInfo.CurrentCulture, + "Missing value for key name: {0} value name: {1}", + keyName, + valName)); + } + } + else + { + throw new InvalidDataException(String.Format( + CultureInfo.CurrentCulture, + "Unable to open registry for key: {0}", + keyName)); + } + } + finally + { + if (regKey != null) + { + regKey.Close(); + } + + Debug.WriteLine(MethodName + ":exit", ClassName); + } + } + + /// + /// reads the object class info definitions from xml + /// + /// Dictionary of object classes + internal static IDictionary GetOCInfo() + { + return GetOCInfo(FileObjectClassDef); + } + + /// + /// Creates command based on the commanf info, reading the calues from attributes + /// + /// Command defition + /// Attribute values + /// Configuration object + /// + /// Ready to execute Command + /// + /// if some of the param is null + internal static Command GetCommand(PSExchangeConnector.CommandInfo cmdInfo, ICollection attributes, ExchangeConfiguration config) + { + Assertions.NullCheck(cmdInfo, "cmdInfo"); + Assertions.NullCheck(attributes, "attributes"); + + // create command + Command cmd = new Command(cmdInfo.Name); + + // map name attribute, if mapping specified + if (!string.IsNullOrEmpty(cmdInfo.NameParameter)) + { + object val = GetAttValue(Name.NAME, attributes); + if (val != null) + { + cmd.Parameters.Add(cmdInfo.NameParameter, val); + } + } + + foreach (string attName in cmdInfo.Parameters) + { + object val = GetAttValue(attName, attributes); + if (val == null && attName.Equals("DomainController")) + { + // add domain controller if not provided + val = ActiveDirectoryUtils.GetDomainControllerName(config); + } + + if (val != null) + { + cmd.Parameters.Add(attName, val); + } + } + + return cmd; + } + + /// + /// Helper method: Gets attribute value from the attribute collection + /// + /// attribute name + /// collection of attribute + /// Attribute value as object, null if not found + /// If some of the params is null + internal static object GetAttValue(string attName, ICollection attributes) + { + Assertions.NullCheck(attName, "attName"); + Assertions.NullCheck(attributes, "attributes"); + + object value = null; + ConnectorAttribute attribute = ConnectorAttributeUtil.Find(attName, attributes); + + if (attribute != null) + { + value = ConnectorAttributeUtil.GetSingleValue(attribute) ?? string.Empty; + } + + return value; + } + + /// + /// Helper method for filtering the specified attributes from collection of attributes + /// + /// Collection of attributes + /// Attribute names to be filtered out + /// Filtered collection of attributes + internal static ICollection FilterOut(ICollection attributes, IList names) + { + Assertions.NullCheck(attributes, "attributes"); + if (names == null || names.Count == 0) + { + return attributes; + } + + ICollection filtered = new List(); + foreach (ConnectorAttribute attribute in attributes) + { + if (!names.Contains(attribute.Name)) + { + filtered.Add(attribute); + } + } + + return filtered; + } + + /// + /// Helper method - Replaces specified collection Items + /// + /// Input to be searched for replacement + /// Replace mappings + /// Replaced + /// If some of the params is null + internal static ICollection FilterReplace(ICollection col, IDictionary map) + { + Assertions.NullCheck(col, "col"); + Assertions.NullCheck(map, "map"); + + ICollection newcol = CollectionUtil.NewList(col); + foreach (KeyValuePair pair in map) + { + if (newcol.Contains(pair.Key)) + { + newcol.Remove(pair.Key); + newcol.Add(pair.Value); + } + } + + return newcol; + } + + /// + /// Finds the attributes in connector object and rename it according to input array of names, but only + /// if the atribute name is in attributes to get + /// + /// ConnectorObject which attributes should be replaced + /// Attributes to get list + /// Replace mapping + /// ConnectorObject with replaced attributes + /// If some of the params is null + internal static ConnectorObject ReplaceAttributes(ConnectorObject cobject, ICollection attsToGet, IDictionary map) + { + Assertions.NullCheck(cobject, "cobject"); + Assertions.NullCheck(map, "map"); + if (attsToGet == null) + { + return cobject; + } + + var attributes = cobject.GetAttributes(); + var builder = new ConnectorObjectBuilder(); + foreach (ConnectorAttribute attribute in attributes) + { + string newName; + if (map.TryGetValue(attribute.Name, out newName) && attsToGet.Contains(newName)) + { + var newAttribute = RenameAttribute(attribute, newName); + builder.AddAttribute(newAttribute); + break; + } + + builder.AddAttribute(attribute); + } + + builder.AddAttributes(attributes); + builder.ObjectClass = cobject.ObjectClass; + builder.SetName(cobject.Name); + builder.SetUid(cobject.Uid); + return builder.Build(); + } + + /// + /// Renames the connector attribute to new name + /// + /// ConnectorAttribute to be renamed + /// New attribute name + /// Renamed ConnectorAttribute + /// If some of the params is null + internal static ConnectorAttribute RenameAttribute(ConnectorAttribute cattribute, string newName) + { + Assertions.NullCheck(cattribute, "cattribute"); + Assertions.NullCheck(newName, "newName"); + + var attBuilder = new ConnectorAttributeBuilder(); + attBuilder.AddValue(cattribute.Value); + attBuilder.Name = newName; + return attBuilder.Build(); + } + + /// + /// Localized null check + /// + /// Object to be check for null + /// Parameter name to be used in exception message + /// Configuration used for localization purposes + /// If the passed object is null + internal static void NullCheck(object obj, string param, Configuration config) + { + if (obj == null) + { + throw new ArgumentNullException(config.ConnectorMessages.Format( + "ex_argument_null", "The Argument [{0}] can't be null", param)); + } + } + + /// + /// Builds new Operation options and add the specified attribute names as + /// AttributesToGet (add to existing AttributesToGet) + /// + /// Existing Operation Options + /// attribute names to be add to AttributeToGet + /// New Operation Options + internal static OperationOptions AddAttributeToOptions(OperationOptions options, params string[] attNames) + { + OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder(options); + List attsToGet = new List(); + if (options.AttributesToGet != null) + { + attsToGet.AddRange(options.AttributesToGet); + } + + foreach (string attName in attNames) + { + attsToGet.Add(attName); + } + + optionsBuilder.AttributesToGet = attsToGet.ToArray(); + return optionsBuilder.Build(); + } + } +} diff --git a/dotnet-connector/ExchangeConnector/Messages.de-DE.resx b/dotnet-connector/ExchangeConnector/Messages.de-DE.resx new file mode 100644 index 0000000..c8ca72d --- /dev/null +++ b/dotnet-connector/ExchangeConnector/Messages.de-DE.resx @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exchange-Konnektor + + + Das Argument [{0}] darf nicht Null sein + + + Der angegebene Benutzername ist nicht eindeutig oder ist nicht vorhanden + + + Die Aktualisierung des Attributs [{0}] wird nicht unterstützt + + + Problem bei der PowerShell-Ausführung {0} + + + Aktualisierungstyp [{0}] wird nicht unterstützt + + + + Ausgangsverzeichnis erstellen + + + Konto des Verzeichnisadministrators + + + Passwort des Verzeichnisadministrators + + + Domänenname + + + Active Directory Domänencontroller-Hostname + + + Objektklasse für Benutzerobjekte + + + Untergeordnete Domänen durchsuchen + + + Container + + + Domänencontroller synchronisieren + + + Globalen Katalogserver synchronisieren + + + Suchkontext + + + Geben Sie an, ob das Ausgangsverzeichnis des Benutzers erstellt werden soll oder nicht. + + + Geben Sie den Benutzernamen des Administrators ein, mit dem das System authentifiziert werden soll. Die Einstellung kann entweder ein Benutzername oder 'domänenname'\'benutzername' sein. + + + Geben Sie das Passwort ein, dass bei der Authentifizierung verwendet werden soll. + + + Der Name der Windows-Domäne (z. B. windowsdomaene.eigenefirma.com) + + + Für die domänenübergreifende Administration geben Sie den Hostnamen, die IP-Adresse oder den Domänennamen des LDAP-Servers ein. + + + Geben Sie die Aktive Directory-Objektklasse für die Benutzerobjekte an, die auf dieser Ressource verwaltet werden. Die Standardeinstellung lautet User. Für die meisten Anwendungen ist diese Einstellung ausreichend. + + + Wählen Sie, ob bei Suchabfragen am Active Directory auch untergeordnete Domänen eingeschlossen werden sollen. Außerdem müssen die Attribute "Suchcontainer" und "Suchkontext synchronisieren" (siehe Synchronisationseinstellungen) auf die oberste übergeordnete Domäne festgelegt werden, z. B. DC=mydomain,DC=com. + + + Geben Sie ein Containerobjekt an, das als Standard-Root für alle Suchvorgänge fungiert. Es werden nur Objekte unter diesem Container durchsucht, es sei denn, in dem Suchvorgang werden explizit weitere Kriterien übergeben. Wenn Sie beispielsweise Benutzer aus dem Container "Users" abrufen wollen, geben Sie Folgendes ein: CN=Users,DC=MYDOMAIN,DC=COM. Wenn kein Wert angegeben wurde, wird der Wert des ''Base Container''-Attributs verwendet. + + + Bei einer aktiven Synchronisation zu verwendender Domänencontroller. Wird nur dann verwendet, wenn keine untergeordneten Domänen durchsucht werden. + + + Name des globalen Katalogservers. Dieser Name ist nur dann erforderlich, wenn untergeordnete Domänen durchsucht werden. + + + Geben Sie an, wo im ADSI-Verzeichnis gesucht werden soll, wenn versucht wird, das durch searchAttributes festgelegte Attribut in das native DN-Format aufzulösen. + + + Der Connector wurde nicht konfiguriert + + + Für die Objektklasse {0} wird kein Löschvorgang unterstützt. + + + Ungültige Objektklasse: {0} + + + Das Attribut {0} ist nicht im Connector-Objekt vorhanden. Synchronisierung kann nicht fortgesetzt werden + + + Der Name des funktionsbereiten Attributs kann nicht Null lauten + + + Für die Objektklasse {0} ist kein Synchronisierungsvorgang vorhanden. + + + Keine UID vorhanden + + + In der Connector-Konfiguration wurde eine ungültige Objektklasse angegeben. Die Objektklasse \'{0}\' konnte im Active Directory nicht gefunden werden + + + Zu dem Suchergebnis <Null> konnte kein Connector-Attribut hinzugefügt werden + + + Zu dem Verzeichniseintrag <Null> konnte kein Connector-Attribut hinzugefügt werden + + + Einzelner Wert wurde erwartet, es wurden aber mehrere Werte für das Attribut {0} gefunden + + + Die Objektklasse \'{0}\' ist für diesen Connector nicht zulässig + + + Für den Benutzer {0} wurden ungültige Anmeldedaten angegeben + + + Die Passwortablauffrist kann nur durch Zurücksetzen des Passworts zurückgesetzt werden. + + + Active Directory unterstützt das Sperren von Benutzern nicht. Der Benutzer kann nur freigegeben werden + + + Es wurde ein ungültiger Suchbereich angegeben: {0} + + + Bei der Validierung des Benutzers {0} ist ein Ausnahmefehler aufgetreten. Der Benutzer wurde erfolgreich authentifiziert, aber die GUI des Benutzers konnte nicht ermittelt werden. + + + Bei der Validierung des Benutzers {0} ist ein Ausnahmefehler aufgetreten. Der Benutzer wurde erfolgreich authentifiziert, aber die SID des Benutzers konnte nicht ermittelt werden. + + + Der Name des Verzeichnisadministrators wurde nicht angegeben. + + + Das Passwort des Verzeichnisadministrators wurde nicht angegeben. + + + Der Domänenname wurde nicht angegeben. + + + Die Objektklasse wurde nicht angegeben. + + + Der Suchcontainer wurde nicht angegeben. + + + Verwenden der Identity Manager Ressourcenadapter-Stilabfrage '{0}'. Dies sollte aktualisiert werden, um die neue Connector-Abfragesyntax zu verwenden. + + + Es wurde ein ungültiger Container angegeben: {0} + + + Shell-Skript Variablenpräfix + + + Ein Präfix, der zu allen Shell-Skript-Argumentnamen hinzugefügt wird. Beispiel: Wenn das Präfix auf "Connector_" gesetzt ist, wird das Argument "User" zu "Connector_User". + + + Das Benutzerkonto wurde gesperrt + + + Das Benutzerpasswort muss geändert werden + + + Für den Benutzer {0} ist das Benutzerkonto abgelaufen + + + Es wurde ein ungültiger Suchkontext angegeben: {0} + + + \ No newline at end of file diff --git a/dotnet-connector/ExchangeConnector/Messages.es-ES.resx b/dotnet-connector/ExchangeConnector/Messages.es-ES.resx new file mode 100644 index 0000000..96ea7ae --- /dev/null +++ b/dotnet-connector/ExchangeConnector/Messages.es-ES.resx @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Conector de Exchange + + + El argumento [{0}] no puede ser nulo + + + El nombre de usuario suministrado no es único o no existe + + + No se permite actualizar el atributo [{0}] + + + Problema durante la ejecución de PowerShell {0} + + + Tipo de actualización [{0}] no compatible + + + + Crear directorio principal + + + Cuenta de administrador de Directory + + + Contraseña de administrador de Directory + + + Nombre de dominio + + + Nombre de host del controlador de dominio de Active Directory + + + Clase de objeto para objetos de usuario + + + Dominios secundarios de búsqueda + + + Contenedor + + + Sincronizar controlador de dominio + + + Sincronizar servidor del catálogo global + + + Contexto de la búsqueda + + + Especifique si se debe crear el directorio principal para el usuario. + + + Introduzca el nombre de usuario del administrador con el que debe autenticarse el sistema. El valor puede ser un nombre de usuario o 'nombredominio'\'nombreusuario'. + + + Introduzca la contraseña que se debe usar al autenticar. + + + Nombre del dominio de Windows (ej. dominiowindows.miempresa.com) + + + En la administración de dominios cruzados, introduzca el nombre de host, la dirección IP o el nombre de dominio del servidor LDAP. + + + Especifique la clase de objeto de Active Directory para los objetos de usuario que se administrarán en este recurso. El valor predeterminado es User, que debe ser adecuado en la mayoría de los casos. + + + Indique si quiere que las búsquedas de Active Directory incluyan dominios secundarios. Además, los atributos Contenedor de búsqueda y Sincronizar contexto de búsqueda (véase la configuración de sincronización) deben estar configurados en la parte superior del dominio principal; por ejemplo, DC=mydomain,DC=com. + + + Especifique un objeto de contenedor que será el root predeterminado de todas la búsquedas. A no ser que se produzca una búsqueda explícita con otros criterios, sólo se buscarán los objetos de este contenedor. Por ejemplo, si desea recuperar usuarios del contenedor de usuarios, escriba CN=Users,DC=MYDOMAIN,DC=COM. Si no se especifica ningún valor, se utiliza el valor del atributo 'Base Container'. + + + Controlador de dominio que se debe usar al activar la sincronización. Sólo se utiliza cuando no se busca en dominios secundarios. + + + Nombre del servidor del catálogo global. Sólo es necesario cuando se busca en dominios secundarios. + + + Especifique dónde buscar en el directorio ADSI cuando se intente resolver el atributo especificado por Atributos de búsqueda al formato dn nativo. + + + No se ha configurado el conector + + + No se admite la eliminación para la clase de objeto {0} + + + Clase de objeto no válida: {0} + + + El atributo {0} no está presente en el objeto de conector. No se puede proceder a la sincronización. + + + El atributo operativo de nombre no puede ser nulo + + + La operación de sincronización no está disponible para la clase de objeto {0} + + + No había Uid + + + Se había especificado una clase de objeto no válida en la configuración del conector. La clase de objeto \'{0}\' no se ha encontrado en Active Directory + + + No se pudo agregar el atributo de conector al resultado de búsqueda <null> + + + No se pudo agregar el atributo de conector a la entrada de directorio <null> + + + Se esperaba un solo valor, pero se encontraron varios para el atributo {0} + + + La clase de objeto \'{0}\' no es válida para este conector + + + Credenciales suministradas para usuario {0} no válidas + + + La caducidad de la contraseña sólo puede restablecerse al restablecer la contraseña + + + Active Directory no admite bloqueo de usuarios. El usuario sólo se puede desbloquear + + + Se ha suministrado un ámbito de búsqueda no válido: {0} + + + Ha ocurrido una excepción durante la validación del usuario {0}. El usuario se ha autenticado satisfactoriamente, pero no se ha podido determinar su guid. + + + Ha ocurrido una excepción durante la validación del usuario {0}. El usuario se ha autenticado satisfactoriamente, pero no se ha podido determinar su sid. + + + No se ha suministrado el nombre del administrador de Directory. + + + No se ha suministrado la contraseña del administrador de Directory. + + + No se ha suministrado el nombre de dominio. + + + No se ha suministrado la clase de objeto. + + + No se ha suministrado el contenedor de búsqueda. + + + Se está usando el estilo de consulta del adaptador de recursos de Identity Manager '{0}'. Debe actualizarse para utilizar la sintaxis de consulta del nuevo conector. + + + Se ha suministrado un contenedor no válido: {0} + + + Prefijo variable de secuencia de comandos shell + + + Prefijo que se añadirá a todos los nombres de argumento de secuencia de comandos shell. Por ejemplo, si el prefijo se configura en "Conector_", un argumento llamado "Usuario" se convertirá en "Conector_Usuario" + + + La cuenta del usuario ha sido bloqueada + + + La contraseña de usuario se debe cambiar + + + Ha caducado la cuenta de usuario del usuario {0} + + + Se ha suministrado un contexto de búsqueda no válido: {0} + + + \ No newline at end of file diff --git a/dotnet-connector/ExchangeConnector/Messages.fr-FR.resx b/dotnet-connector/ExchangeConnector/Messages.fr-FR.resx new file mode 100644 index 0000000..9ec54c5 --- /dev/null +++ b/dotnet-connector/ExchangeConnector/Messages.fr-FR.resx @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Connecteur Exchange + + + L’argument [{0}] ne peut pas être nul. + + + Le nom d’utilisateur fourni n’est pas unique ou il n’existe pas. + + + La mise à jour de l’attribut [{0}] n’est pas prise en charge. + + + Un problème est survenu lors de l’exécution de PowerShell {0}. + + + La mise à jour du type [{0}] n’est pas prise en charge. + + + + Créer un répertoire personnel + + + Compte de l’administrateur de répertoires + + + Mot de passe de l’administrateur de répertoires + + + Nom du domaine + + + Nom d’hôte du contrôleur de domaine Active Directory + + + Classe d’objet utilisateur + + + Rechercher dans les domaines enfant + + + Conteneur + + + Synchr. le contrôleur de domaine + + + Synchr. le serveur de catalogue global + + + Contexte de recherche + + + Spécifiez si le répertoire personnel de l’utilisateur sera créé ou non. + + + Saisissez le nom d’utilisateur de l’administrateur que le système doit utiliser pour l’authentification. Ce paramètre peut correspondre à un nom d’utilisateur ou à nom_domaine\nom_utilisateur. + + + Saisissez le mot de passe utilisateur devant servir à l’authentification. + + + Nom du domaine Windows (par ex., domainewindows.monentreprise.com) + + + Pour l’administration interdomaine, saisissez le nom d’hôte, l’adresse IP ou le nom de domaine du serveur LDAP. + + + Spécifiez la classe d’objet Active Directory destinée aux objets utilisateur à gérer sur cette ressource. La classe par défaut est User (Utilisateur). Elle devrait convenir dans la majorité des cas. + + + Indiquez si les recherches effectuées dans Active Directory doivent inclure les domaines enfant. Assurez-vous par ailleurs que les attributs Conteneur de recherche et Synchr. le contexte de recherche (voir les paramètres de synchronisation) sont définis sur la racine du domaine parent (par ex., DC=mondomaine,DC=com). + + + Spécifiez un objet conteneur qui servira de racine par défaut à toutes les recherches. À moins qu’une recherche ne passe explicitement d’autres critères, seuls les objets situés sous ce conteneur feront l’objet d’une recherche. Par exemple, pour extraire les utilisateurs du conteneur Utilisateurs, saisissez CN=Utilisateurs,DC=MONDOMAINE,DC=COM. Si aucune valeur n’est spécifiée, c’est celle de l’attribut Conteneur de base qui est utilisée. + + + Contrôleur de domaine à utiliser lors d’une synchronisation active. Uniquement utilisé lorsque la recherche ne porte pas sur les domaines enfant. + + + Nom du serveur de catalogue global. Uniquement utilisé lorsque la recherche porte sur les domaines enfant. + + + Spécifiez où rechercher dans le répertoire ADSI lorsque vous tentez de résoudre l’attribut spécifié par searchAttributes au format DN natif. + + + Le connecteur n’a pas été configuré. + + + La suppression n’est pas prise en charge pour la classe d’objet {0}. + + + Classe d’objet incorrecte : {0} + + + L’attribut {0} ne figure pas dans l’objet connecteur. Impossible de poursuivre la synchronisation. + + + L’attribut opérationnel du nom doit être spécifié. + + + L’opération de synchronisation n’est pas disponible pour la classe d’objet {0}. + + + UID introuvable + + + Une classe d’objet erronée a été spécifiée dans la configuration du connecteur. Impossible de trouver la classe d’objet {0} dans Active Directory. + + + Impossible d’ajouter l’attribut de connecteur à un résultat de recherche <nul>. + + + Impossible d’ajouter l’attribut de connecteur à une entrée de répertoire <nul>. + + + Valeur unique attendue, alors que plusieurs valeurs ont été trouvées pour l’attribut {0}. + + + La classe d’objet {0} est incompatible avec ce connecteur. + + + Informations d’authentification incorrectes fournies pour l’utilisateur {0} + + + La date d’expiration du mot de passe peut uniquement être réinitialisée via la réinitialisation du mot de passe lui-même. + + + Active Directory ne prend pas en charge le verrouillage des utilisateurs. L’utilisateur doit impérativement être déverrouillé. + + + Une étendue de recherche incorrecte a été spécifiée : {0} + + + Une exception s’est produite lors de la validation de l’utilisateur {0}. L’utilisateur a bien été authentifié, mais son GUID n’a pas pu être déterminé. + + + Une exception s’est produite lors de la validation de l’utilisateur {0}. L’utilisateur a bien été authentifié, mais son SID n’a pas pu être déterminé. + + + Le nom de l’administrateur de répertoires n’a pas été fourni. + + + Le mot de passe de l’administrateur de répertoires n’a pas été fourni. + + + Le nom de domaine n’a pas été fourni. + + + La classe d’objet n’a pas été fournie. + + + Le conteneur de recherche n’a pas été fourni. + + + Utilisation de la requête de type Adaptateur de ressources Identity Manager {0}. Elle devrait être mise à jour afin d’utiliser la nouvelle syntaxe de requête du connecteur. + + + Un conteneur erroné a été fourni : {0} + + + Préfixe de script shell variable + + + Préfixe ajouté à tous les noms d’arguments de script shell. Par exemple, si le préfixe est défini sur Connector_, un argument appelé User est converti en Connector_User. + + + Le compte de l’utilisateur a été verrouillé. + + + Le mot de passe utilisateur doit être changé. + + + Le compte de l’utilisateur {0} a expiré. + + + Un contexte de recherche erroné a été fourni : {0} + + + \ No newline at end of file diff --git a/dotnet-connector/ExchangeConnector/Messages.ja-JP.resx b/dotnet-connector/ExchangeConnector/Messages.ja-JP.resx new file mode 100644 index 0000000..9490bc7 --- /dev/null +++ b/dotnet-connector/ExchangeConnector/Messages.ja-JP.resx @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exchange コネクタ + + + 引数 [{0}] は NULL にできません + + + 指定されたユーザー名が一意でないか存在しません + + + [{0}] 属性の更新はサポートされません + + + PowerShell の実行中に問題が発生しました {0} + + + 更新タイプ [{0}] はサポートされません + + + + ホームディレクトリの作成 + + + ディレクトリ管理者のアカウント + + + ディレクトリ管理者のパスワード + + + ドメイン名 + + + Active Directory ドメインコントローラのホスト名 + + + ユーザーオブジェクトのオブジェクトクラス + + + 子ドメインの検索 + + + コンテナ + + + ドメインコントローラの同期 + + + グローバルカタログサーバーの同期 + + + 検索コンテキスト + + + ユーザー用ホームディレクトリを作成するかどうかを指定します。 + + + システムが認証に使用する管理者のユーザー名を入力します。ユーザー名または 'ドメイン名'\ユーザー名' の形式で設定できます。 + + + 認証に使用するパスワードを入力します。 + + + Windows のドメイン名 (例: windowsdomain.mycompany.com) + + + クロスドメイン管理を行う場合は、ホスト名、IP アドレス、または LDAP サーバーのドメイン名を入力します。 + + + このリソース上で管理するユーザーオブジェクトの Active Directory オブジェクトクラスを指定します。デフォルトは User で、多くの場合、この設定が適切です。 + + + Active Directory の検索に子ドメインを含めるかどうかを選択します。さらに、親ドメインの先頭に、DC=mydomain,DC=com のように、検索コンテナおよび検索コンテキスト (同期設定を参照) 属性を設定してください。 + + + すべての検索のデフォルトルートとなるコンテナオブジェクトを指定します。検索を明示的に他の条件に渡さない場合、このコンテナ内のオブジェクトのみが検索されます。たとえば、Users コンテナからユーザーを取得する場合は、CN=Users,DC=MYDOMAIN,DC=COM を入力します。値を指定しないと、'Base Container' 属性の値が使用されます。 + + + アクティブ同期中に使用するドメインコントローラ。子ドメインを検索しない場合にのみ使用します。 + + + グローバルカタログサーバーの名前。子ドメインを検索する場合にのみ必要です。 + + + 検索属性で指定された属性のネイティブ DN 形式への解決を試みる場合に、ADSI ディレクトリ中でどこを検索するかを指定します。 + + + コネクタが設定されていません + + + オブジェクトクラス {0} では削除はサポートされていません + + + 無効なオブジェクトクラス: {0} + + + コネクタオブジェクトに属性 {0} がありません。同期を続行できません + + + 名前のオペレーショナル属性を null にすることはできません + + + オブジェクトクラス {0} では同期動作はできません + + + UID がありません + + + コネクタ設定に無効なオブジェクトクラスが指定されました。Active Directory からオブジェクトクラス \'{0}\' が見つかりませんでした + + + <null> の検索結果にコネクタ属性を追加できませんでした + + + <null> のディレクトリ入力にコネクタ属性を追加できませんでした + + + 単一の値を要求しているときに、属性 {0} に対して複数の値が検索されました + + + オブジェクトクラス \'{0}\' はこのコネクタでは無効です + + + ユーザー {0} に無効な認証情報が指定されました + + + パスワードをリセットすることでのみパスワードを期限切れにすることができます + + + Active Directory はユーザーのロックをサポートしません。ユーザーのロック解除のみができます + + + 無効な検索範囲が指定されました: {0} + + + ユーザー {0} の検証時に例外が発生しました。ユーザーの認証は正常に実行されましたが、ユーザーの GUID を特定できませんでした。 + + + ユーザー {0} の検証時に例外が発生しました。ユーザーの認証は正常に実行されましたが、ユーザーの SID を特定できませんでした。 + + + ディレクトリ管理者の名前が指定されていません。 + + + ディレクトリ管理者のパスワードが指定されていません。 + + + ドメイン名が指定されていません。 + + + オブジェクトクラスが指定されていません。 + + + 検索コンテナが指定されていません。 + + + 識別情報マネージャーリソースアダプタのスタイルクエリー '{0}' を使用しています。新規コネクタのクエリー構文を使用するには、このスタイルクエリーを更新してください。 + + + 無効なコンテナが指定されました: {0} + + + シェルスクリプトの変数のプレフィックス + + + シェルスクリプトのすべての引数名に追加されるプレフィックス。たとえば、プレフィックスを "Connector_" に設定すると、引数 "User" は "Connector_User" になります + + + ユーザーのアカウントがロックされています + + + ユーザーのパスワードを変更してください + + + ユーザー {0} のユーザーアカウントの期限が切れています + + + 無効な検索コンテキストが指定されました: {0} + + + \ No newline at end of file diff --git a/dotnet-connector/ExchangeConnector/Messages.ko-KR.resx b/dotnet-connector/ExchangeConnector/Messages.ko-KR.resx new file mode 100644 index 0000000..b070b4b --- /dev/null +++ b/dotnet-connector/ExchangeConnector/Messages.ko-KR.resx @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exchange 커넥터 + + + 인수 [{0}]은(는) null일 수 없습니다. + + + 제공된 사용자 이름이 고유하지 않거나 없습니다. + + + [{0}] 속성 업데이트는 지원되지 않습니다. + + + PowerShell에서 {0}을(를) 실행하는 동안 문제가 발생했습니다. + + + 업데이트 유형 [{0}]은(는) 지원되지 않습니다. + + + + 홈 디렉토리 작성 + + + 디렉토리 관리자 계정 + + + 디렉토리 관리자 비밀번호 + + + 도메인 이름 + + + Active Directory 도메인 컨트롤러 호스트 이름 + + + 사용자 객체의 객체 클래스 + + + 하위 도메인 검색 + + + 컨테이너 + + + 도메인 컨트롤러 동기화 + + + 전역 카탈로그 서버 동기화 + + + 검색 컨텍스트 + + + 사용자의 홈 디렉토리를 만들지 여부를 지정합니다. + + + 시스템이 인증해야 하는 관리자 사용자 이름을 입력합니다. 설정은 사용자 이름 또는 'domainname'\'username'일 수 있습니다. + + + 인증할 때 사용해야 하는 비밀번호를 입력합니다. + + + Windows 도메인 이름(예: windowsdomain.mycompany.com) + + + 교차 도메인 관리의 경우 LDAP 서버의 호스트 이름, IP 주소 또는 도메인 이름을 입력합니다. + + + 이 자원에서 관리될 사용자 객체의 Active Directory 객체 클래스를 지정합니다. 기본값은 User이며, 대부분의 경우 기본값이 적절합니다. + + + Active Directory의 검색에 하위 도메인을 포함하려면 선택합니다. 또한 검색 컨테이너 및 검색 컨텍스트 동기화(동기화 설정 참조) 속성을 상위 도메인의 최상위로 설정해야 합니다(예: DC=mydomain,DC=com). + + + 모든 검색의 기본 루트가 되는 컨테이너 객체를 지정합니다. 검색이 명백히 다른 기준을 충족하지 않으면 이 컨테이너 아래에 있는 객체만 검색됩니다. 예를 들어 Users 컨테이너에서 사용자를 검색하려면 CN=Users,DC=MYDOMAIN,DC=COM을 입력합니다. 값을 지정하지 않으면 '기본 컨테이너' 속성 값이 사용됩니다. + + + 활성 동기화 중에 사용할 도메인 컨트롤러입니다. 하위 도메인을 검색하지 않는 경우에만 사용됩니다. + + + 전역 카탈로그 서버의 이름입니다. 이 이름은 하위 도메인을 검색하는 경우에만 필요합니다. + + + searchAttributes에 의하여 지정된 속성을 원래 DN 형식으로 변환할 때 찾을 ADSI의 디렉토리를 지정합니다. + + + 커넥터가 구성되지 않았습니다. + + + ObjectClass {0}은(는) 삭제할 수 없습니다. + + + 잘못된 객체 클래스: {0} + + + 속성 {0}이(가) 커넥터 객체에 없습니다. 동기화를 계속할 수 없습니다. + + + 이름 작업 속성은 null일 수 없습니다. + + + 동기화 작업을 ObjectClass {0}에 사용할 수 없습니다. + + + Uid가 없습니다. + + + 커넥터 구성에 잘못된 객체 클래스가 지정되었습니다. 객체 클래스 \'{0}\'이(가) Active Directory에 없습니다. + + + 커넥터 속성을 <null> 검색 결과에 추가할 수 없습니다. + + + 커넥터 속성을 <null> 디렉토리 입력 항목에 추가할 수 없습니다. + + + 속성 {0}에 대해 하나의 값이 검색되어야 하는데, 여러 개의 값이 검색되었습니다. + + + ObjectClass \'{0}\'이(가) 이 커넥터에 유효하지 않습니다. + + + 사용자 {0}에 대해 잘못된 자격 증명이 제공되었습니다. + + + 비밀번호 만료는 비밀번호 재설정을 통해서만 재설정할 수 있습니다. + + + Active Directory는 사용자 잠금을 지원하지 않습니다. 사용자의 잠금을 해제만 할 수 있습니다. + + + 잘못된 검색 범위가 입력되었습니다. {0} + + + 사용자 {0}의 유효성을 검사하는 동안 예외가 발생했습니다. 사용자가 인증되었지만 사용자의 guid는 확인하지 못했습니다. + + + 사용자 {0}의 유효성을 검사하는 동안 예외가 발생했습니다. 사용자가 인증되었지만 사용자의 sid는 확인하지 못했습니다. + + + 디렉토리 관리자 이름을 제공하지 않았습니다. + + + 디렉토리 관리자 비밀번호를 제공하지 않았습니다. + + + 도메인 이름을 제공하지 않았습니다. + + + ObjectClass를 제공하지 않았습니다. + + + 검색 컨테이너를 제공하지 않았습니다. + + + Identity Manger 자원 어댑터 스타일 쿼리 '{0}'을(를) 사용하고 있습니다. 새 커넥터 쿼리 구문을 사용하려면 이 쿼리를 업데이트해야 합니다. + + + 잘못된 컨테이너를 제공했습니다. {0} + + + 쉘 스크립트 변수 접두어 + + + 모든 쉘 스크립트 인수 이름에 추가되는 접두어입니다. 예를 들어 접두어가 "Connector_"로 설정된 경우 "User"라는 인수는 "Connector_User"가 됩니다. + + + 사용자의 계정이 잠겼습니다. + + + 사용자 비밀번호를 변경해야 합니다. + + + 사용자 {0}에 대한 사용자 계정이 만료되었습니다. + + + 잘못된 검색 컨텍스트를 제공했습니다. {0} + + + \ No newline at end of file diff --git a/dotnet-connector/ExchangeConnector/Messages.resx b/dotnet-connector/ExchangeConnector/Messages.resx new file mode 100644 index 0000000..a2d6205 --- /dev/null +++ b/dotnet-connector/ExchangeConnector/Messages.resx @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exchange Connector + + + The Argument [{0}] can't be null + + + Recipient type [{0}] is not supported + + + Provided User name is not unique or not existing + + + Update of [{0}] attribute is not supported + + + Problem while PowerShell execution {0} + + + Update of [{0}] to [{1}] is not supported + + + Update type [{0}] not supported + + + Create Home Directory + + + Directory Adminstrator''s Account + + + Directory Administrator''s Password + + + Domain Name + + + Active Directory Domain Controller Hostname + + + Object Class for User Objects + + + Search Child Domains + + + Container + + + Sync Domain Controller + + + Sync Global Catalog Server + + + Search Context + + + Specify whether or not the home directory for the user will be created. + + + Enter the administrator user name with which the system should authenticate. The setting can be either be a username or 'domainname'\'username'. + + + Enter the password that should be used when authenticating. + + + Name of the windows domain (e.g. windowsdomain.mycompany.com) + + + For cross-domain administration, enter the hostname, IP address, or domain name of the LDAP server. + + + Specify the Active Directory object class for user objects that will be managed on this resource. The default is User, and for most situations, this should be fine. + + + Select if you want searches of Active Directory to include child domains. In addition, the Search Container and Sync Search Context (see sync settings) attributes must be set to the top of the parent domain, e.g. DC=mydomain,DC=com. + + + Specify a container object which will be the default root of all searches. Unless a search explicitly passes in other criteria, only objects under this container will be searched. For example, if you want to retrieve users from the Users container, enter CN=Users,DC=MYDOMAIN,DC=COM. If no value is specified, the value of the 'Base Container' attribute is used. + + + Domain controller to use during active sync. Only used if not searching child domains. + + + Name of the global catalog server. This is needed only if searching child domains. + + + Specify where to look in the ADSI directory when attempting to resolve the attribute specified by searchAttributes to the native dn format. + + + Connector has not been configured + + + Delete is not supported for ObjectClass {0} + + + Invalid object class: {0} + + + Attribute {0} is not present in connector object. Cannot proceed with synchronization + + + The name operational attribute cannot be null + + + Sync operation is not available for ObjectClass {0} + + + Uid was not present + + + Invalid Object Class was specified in the connector configuration. Object Class \'{0}\' was not found in Active Directory + + + Could not add connector attribute to <null> search result + + + Could not add connector attribute to <null> directory entry + + + Expecting single value, but found multiple values for attribute {0} + + + ObjectClass \'{0}\' is not valid for this connector + + + Invalid credentials supplied for user {0} + + + Password expiration can only be reset by reseting the password + + + Active Directory does not support locking users. User may be unlocked only + + + An invalid searchscope was supplied: {0} + + + An execption occurred during validation of user {0}. The user was successfully authenticated, but the user's guid could not be determined. + + + An execption occurred during validation of user {0}. The user was successfully authenticated, but the user's sid could not be determined. + + + Directory administrator name not supplied. + + + Directory administrator password not supplied. + + + Domain name not supplied. + + + ObjectClass was not supplied. + + + Search Container was not supplied. + + + Using Identity Manger Resource Adapter style query '{0}'. This should be updated to use the new connector query syntax. + + + An invalid container was supplied: {0} + + + Shell Script Variable Prefix + + + Prefix that will be added to all shell script argument names. For example, if the prefix is set to "Connector_" an argument called "User" would become "Connector_User" + + + User's account has been locked + + + User password must be changed + + + User account expired for user {0} + + + An invalid search context was supplied: {0} + + + There are no registered PowerShell snap-ins. + + + There is no supported Exchange PowerShell snap-in registered. + + + User + + \ No newline at end of file diff --git a/dotnet-connector/ExchangeConnector/Messages.zh-CN.resx b/dotnet-connector/ExchangeConnector/Messages.zh-CN.resx new file mode 100644 index 0000000..60d839d --- /dev/null +++ b/dotnet-connector/ExchangeConnector/Messages.zh-CN.resx @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exchange 连接器 + + + 参数 [{0}] 不能为 null + + + 提供的用户名不唯一或不存在 + + + 不支持更新 [{0}] 属性 + + + PowerShell 执行 {0} 时出现问题 + + + 不支持更新类型 [{0}] + + + + 创建主目录 + + + 目录管理员的帐户 + + + 目录管理员的密码 + + + 域名 + + + Active Directory 域控制器主机名 + + + 用户对象的对象类 + + + 搜索子域 + + + 容器 + + + 同步域控制器 + + + 同步全局目录服务器 + + + 搜索上下文 + + + 指定是否为用户创建主目录。 + + + 输入系统用以进行验证的管理员用户名。该设置可以为用户名或“域名”\“用户名”。 + + + 输入进行验证时应使用的密码。 + + + Windows 域的名称(例如 windowsdomain.mycompany.com) + + + 对于跨域管理,请输入 LDAP 服务器的主机名、IP 地址或域名。 + + + 指定将在此资源上管理的用户对象的 Active Directory 对象类。默认值为 User,在大多数情况下该值均适用。 + + + 如果要在 Active Directory 搜索中包括子域,请选择此项。此外,必须将“搜索容器”和“同步搜索上下文”(请参见同步设置)属性设置为父域的顶级,例如 DC=mydomain,DC=com。 + + + 指定将作为所有搜索的默认根目录的容器对象。除非搜索明确指定其他条件,否则只会搜索此容器下的对象。例如,如果要从 Users 容器中检索用户,请输入 CN=Users,DC=MYDOMAIN,DC=COM。如果没有指定任何值,则将使用“基容器”属性的值。 + + + 活动同步期间要使用的域控制器。仅在不搜索子域的情况下使用此项。 + + + 全局目录服务器的名称。仅在搜索子域的情况下需要此项。 + + + 指定要将由 searchAttributes 指定的属性解析为本机标识名格式时,从何处查找 ADSI 目录。 + + + 尚未配置连接器 + + + 对于对象类 {0},不支持删除操作 + + + 无效的对象类: {0} + + + 连接器对象中不存在属性 {0}。无法进行同步 + + + 名称操作属性不能为 null + + + 对于对象类 {0},无法执行同步操作 + + + 不存在 UID + + + 在连接器配置中指定了无效的对象类。在 Active Directory 中未找到对象类 \'{0}\' + + + 无法将连接器属性添加到 <null> 搜索结果 + + + 无法将连接器属性添加到 <null> 目录条目 + + + 属性 {0} 应有一个值,但却找到了多个值 + + + 对象类 \'{0}\' 对此连接器无效 + + + 为用户 {0} 提供的无效凭证 + + + 只能通过重置密码来重置密码到期 + + + Active Directory 不支持锁定用户。只能解除对用户的锁定 + + + 提供了无效的搜索范围: {0} + + + 验证用户 {0} 期间出现异常。已成功验证该用户,但无法确定该用户的 GUID。 + + + 验证用户 {0} 期间出现异常。已成功验证该用户,但无法确定该用户的 SID。 + + + 未提供目录管理员名称。 + + + 未提供目录管理员密码。 + + + 未提供域名。 + + + 未提供对象类。 + + + 未提供搜索容器。 + + + 使用 Identity Manger 资源适配器样式查询“{0}”。应对此进行更新,以使用新的连接器查询语法。 + + + 提供了无效的容器: {0} + + + Shell 脚本变量前缀 + + + 将添加到所有 Shell 脚本参数名称的前缀。例如,如果将该前缀设置为 "Connector_",则名为 "User" 的参数将变为 "Connector_User" + + + 用户帐户已被锁定 + + + 必须更改用户密码 + + + 用户 {0} 的用户帐户已到期 + + + 提供了无效的搜索上下文: {0} + + + \ No newline at end of file diff --git a/dotnet-connector/ExchangeConnector/Messages.zh-TW.resx b/dotnet-connector/ExchangeConnector/Messages.zh-TW.resx new file mode 100644 index 0000000..2b645d5 --- /dev/null +++ b/dotnet-connector/ExchangeConnector/Messages.zh-TW.resx @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exchange 連接器 + + + 引數 [{0}] 不可為空值 + + + 提供的使用者名稱非唯一或不存在 + + + 不支援更新 [{0}] 屬性 + + + PowerShell 執行 {0} 時發生問題 + + + 不支援更新類型 [{0}] + + + + 建立主目錄 + + + 目錄管理員的帳號 + + + 目錄管理員的密碼 + + + 網域名稱 + + + Active Directory 網域控制器主機名稱 + + + 使用者物件的物件類別 + + + 搜尋子網域 + + + 容器 + + + 同步化網域控制器 + + + 同步化全域目錄伺服器 + + + 搜尋上下文 + + + 指定是否要建立使用者的主目錄。 + + + 輸入系統進行認證時採用的管理員使用者名稱。此設定可以是「使用者名稱」或「網域名稱」\「使用者名稱」。 + + + 輸入進行認證時應使用的密碼。 + + + Windows 網域的名稱 (例如 windowsdomain.mycompany.com) + + + 若是跨網域進行管理,請輸入 LDAP 伺服器的主機名稱、IP 位址或網域名稱。 + + + 針對要在此資源上管理的使用者物件,指定 Active Directory 物件類別。預設值為【User】,且在大部分情況下均適用。 + + + 若要 Active Directory 的搜尋包含子網域,請選取此選項。此外此外您必須在父網域頂端設定【搜尋容器】及【同步化搜尋上下文】(請參閱同步化設定) 屬性 (例如 DC=mydomain,DC=com)。 + + + 指定將成為所有搜尋之預設根目錄的容器物件。除非搜尋明確指定有其他條件,否則只會搜尋此容器中的物件。例如,若想要擷取【Users】容器中的使用者,請輸入 CN=Users,DC=MYDOMAIN,DC=COM。若未指定任何值,將使用「基底容器」屬性值。 + + + Active Sync 期間要使用的網域控制器。只有在不搜尋子網域時才使用。 + + + 全域目錄伺服器的名稱。只有在搜尋子網域時才需要。 + + + 指定將 searchAttributes 所指定的屬性解析為本機 DN 格式時,要在何處查詢 ADSI 目錄。 + + + 尚未配置連接器 + + + 不支援刪除物件類別 {0} + + + 無效的物件類別: {0} + + + 連接器物件中沒有屬性 {0}。無法繼續同步化 + + + 名稱作業屬性不得為空 + + + 同步化作業不適用於物件類別 {0} + + + uid 不存在 + + + 連接器配置中指定了無效的物件類別。Active Directory 中找不到物件類別「{0}」 + + + 無法將連接器屬性增加至 <null> 搜尋結果 + + + 無法將連接器屬性增加至 <null> 目錄項目 + + + 屬性 {0} 應僅有單一值,但卻出現多個值 + + + 物件類別「{0}」對此連接器無效 + + + 為使用者 {0} 提供的憑證無效 + + + 必須重設密碼才可重設密碼過期功能 + + + Active Directory 不支援鎖定使用者。只能解除鎖定使用者 + + + 提供的搜尋範圍無效: {0} + + + 驗證使用者 {0} 期間發生異常。已成功認證使用者,但無法確定使用者的 GUID。 + + + 驗證使用者 {0} 期間發生異常。已成功認證使用者,但無法確定使用者的 SID。 + + + 未提供目錄管理員名稱。 + + + 未提供目錄管理員密碼。 + + + 未提供網域名稱。 + + + 未提供物件類別。 + + + 未提供搜尋容器。 + + + 使用 Identity Manger 資源配接卡樣式查詢「{0}」。應更新為使用新的連接器查詢語法。 + + + 提供的容器無效: {0} + + + shell 程序檔變數前綴 + + + 將增加至所有 shell 程序檔引數名稱的前綴。例如,若將前綴設定為「Connector_」,則稱為「User」的引數會變成「Connector_User」 + + + 使用者的帳號已鎖定 + + + 必須變更使用者密碼 + + + 使用者 {0} 的使用者帳號已過期。 + + + 提供的搜尋上下文無效: {0} + + + \ No newline at end of file diff --git a/dotnet-connector/ExchangeConnector/ObjectClasses.xml b/dotnet-connector/ExchangeConnector/ObjectClasses.xml new file mode 100644 index 0000000..1dd71ec --- /dev/null +++ b/dotnet-connector/ExchangeConnector/ObjectClasses.xml @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet-connector/ExchangeConnector/PSExchangeConnector.cs b/dotnet-connector/ExchangeConnector/PSExchangeConnector.cs new file mode 100644 index 0000000..fa01ce8 --- /dev/null +++ b/dotnet-connector/ExchangeConnector/PSExchangeConnector.cs @@ -0,0 +1,414 @@ +// +// ==================== +// 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]" +// ==================== +// +// Tomas Knappek + +namespace Org.IdentityConnectors.Exchange +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Management.Automation.Runspaces; + + using Data; + + using Org.IdentityConnectors.ActiveDirectory; + using Org.IdentityConnectors.Common; + using Org.IdentityConnectors.Framework.Common.Objects; + using Org.IdentityConnectors.Framework.Spi; + + /// + /// MS Exchange extension of Active Directory connector. + /// Pure PowerShell connector. + /// Note: Not Production quality yet, please do not use. + /// + public class PSExchangeConnector : ActiveDirectoryConnector + { + /// + /// MailBox object class name + /// + public const string MailboxName = "mailbox"; + + /// + /// MailUser object class name + /// + public const string MailUserName = "mailuser"; + + /// + /// MailBox object class, based on + /// + public static readonly ObjectClass Mailbox = new ObjectClass(MailboxName); + + /// + /// MailUser object class, based on + /// + public static readonly ObjectClass MailUser = new ObjectClass(MailUserName); + + /// + /// This Class name - used for logging purposes + /// + private static readonly string ClassName = typeof(PSExchangeConnector).ToString(); + + /// + /// Configuration instance variable, method for assignment + /// + private ExchangeConfiguration configuration; + + /// + /// Runspace instance variable, it is managed resource - has to be released + /// + private RunSpaceInstance runspace; + + /// + /// Map of object class infos, used for generating + /// + private IDictionary mapOcInfo; + + /// + /// Implementation of CreateOp.Create + /// + /// Object class(oc + /// Object attributes + /// Operation options + /// of the created object + public override Uid Create( + ObjectClass oclass, + ICollection attributes, + OperationOptions options) + { + const string METHOD = "Create"; + Debug.WriteLine(METHOD + ":entry", ClassName); + + // first create the object in AD + Uid uid = base.Create(oclass, attributes, options); + + try + { + if (oclass.Is(MailboxName)) + { + // enable mailbox for person + Command cmd = ExchangeUtility.GetCommand(CommandInfo.EnableMailbox, attributes, this.configuration); + this.runspace.InvokePipeline(cmd); + } + else if (oclass.Is(MailUserName)) + { + // enable mailuser + Command cmd = ExchangeUtility.GetCommand(CommandInfo.EnableMailUser, attributes, this.configuration); + this.runspace.InvokePipeline(cmd); + } + + Debug.WriteLine(METHOD + ":exit", ClassName); + } + catch + { + // do the rollback - delete the object by uid + // no need to check the uid is null, ensured by the create contract + this.Delete(oclass, uid, options); + throw; + } + + return uid; + } + + /// + /// Implementation of UpdateOp.Update + /// + /// Update type + /// Object class + /// Object attributes + /// Operation options + /// of the updated object + public override Uid Update( + UpdateType type, + ObjectClass oclass, + ICollection attributes, + OperationOptions options) + { + // TODO: Implement Update + return base.Update(type, oclass, attributes, options); + } + + /// + /// Tests if the connector is properly configured and ready + /// + public override void Test() + { + // validate the configuration first, this will check AD configuration too + this.configuration.Validate(); + + // AD validation (includes configuration validation too) + base.Test(); + + // runspace check + this.runspace.Test(); + } + + /// + /// Implementation of SynOp.Sync + /// + /// Object class + /// Syncronization token + /// Handler for syncronization results + /// Operation options, can be null + public override void Sync( + ObjectClass objClass, + SyncToken token, + SyncResultsHandler handler, + OperationOptions options) + { + // TODO: implement Sync + base.Sync(objClass, token, handler, options); + } + + /// + /// Implementation of SynOp.GetLatestSyncToken + /// + /// Object class + /// of the last sync + public override SyncToken GetLatestSyncToken(ObjectClass objectClass) + { + // TODO: Implement GetLatestSyncToken + return base.GetLatestSyncToken(objectClass); + } + + /// + /// Implementation of SearchOp.ExecuteQuery + /// + /// Object class + /// Query to execute + /// Result handler + /// Operation options + public override void ExecuteQuery( + ObjectClass oclass, + string query, + ResultsHandler handler, + OperationOptions options) + { + // TODO: Implement ExecuteQuery + base.ExecuteQuery(oclass, query, handler, options); + } + + /// + /// Implementation of SearchOp.CreateFilterTranslator + /// + /// Object class + /// Operation options + /// Exchange specific Filter translator + public override Org.IdentityConnectors.Framework.Common.Objects.Filters.FilterTranslator CreateFilterTranslator( + ObjectClass oclass, + OperationOptions options) + { + // TODO: Implement CreateFilterTranslator + return base.CreateFilterTranslator(oclass, options); + } + + /// + /// Inits the connector with configuration injected + /// + /// Initialized Exchange configuration + public override void Init(Configuration configuration) + { + base.Init(configuration); + this.configuration = (ExchangeConfiguration)configuration; + + // create runspace instance, will be alive as long as the connector instance is alive + this.runspace = new RunSpaceInstance(RunSpaceInstance.SnapIn.Exchange, configuration.ConnectorMessages); + + // read the object class info definitions + this.mapOcInfo = ExchangeUtility.GetOCInfo(); + } + + /// + /// Dispose resources, + /// + public sealed override void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose the resources we use + /// + /// true if called from + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // free managed resources + if (this.runspace != null) + { + this.runspace.Dispose(); + this.runspace = null; + } + } + } + + /// + /// Defines the supported object classes by the connector, used for schema building + /// + /// List of supported object classes + protected override ICollection GetSupportedObjectClasses() + { + ICollection objectClasses = base.GetSupportedObjectClasses(); + Assertions.NullCheck(objectClasses, "ocList"); + + ICollection ourObjectClass = new List(objectClasses); + + // add our object classes + ourObjectClass.Add(Mailbox); + ourObjectClass.Add(MailUser); + return ourObjectClass; + } + + /// + /// Gets the object class info for specified object class, used for schema building + /// + /// ObjectClass to get info for + /// ObjectClass' ObjectClassInfo + protected override ObjectClassInfo GetObjectClassInfo(ObjectClass oc) + { + ObjectClassInfo ret = CollectionUtil.GetValue(this.mapOcInfo, oc, null) ?? base.GetObjectClassInfo(oc); + Assertions.NullCheck(ret, "ret"); + return ret; + } + + /// + /// Command definition object, uses internally , + /// intended to be one place for all the PowerShell commands definition + /// + internal sealed class CommandInfo + { + /// + /// Enable-Mailbox command meta info + /// + internal static readonly CommandInfo EnableMailbox = new CommandInfo("Enable-Mailbox"); + + /// + /// Set-Mailbox command meta info + /// + internal static readonly CommandInfo SetMailbox = new CommandInfo("Set-Mailbox"); + + /// + /// Enable-MailUser command meta info + /// + internal static readonly CommandInfo EnableMailUser = new CommandInfo("Enable-MailUser"); + + /// + /// Set-MailUser command meta info + /// + internal static readonly CommandInfo SetMailUser = new CommandInfo("Set-MailUser"); + + /// + /// Get-MailUser command meta info + /// + internal static readonly CommandInfo GetMailUser = new CommandInfo("Get-MailUser"); + + /// + /// Get-User command meta info + /// + internal static readonly CommandInfo GetUser = new CommandInfo("Get-User"); + + /// + /// Get-Mailbox command meta info + /// + internal static readonly CommandInfo GetMailbox = new CommandInfo("Get-Mailbox"); + + /// + /// List of SerializableCommandInfo object - will be read from persistence + /// + private static IList serCmdInfos; + + /// + /// Private placeholder for concrete + /// + private SerializableCommandInfo serCmdInfo; + + /// + /// Initializes a new instance of the class. + /// , made private to be immutable + /// + /// Command name + private CommandInfo(string name) + { + this.Name = name; + } + + /// + /// Gets Comamnd Name + /// + internal string Name { get; private set; } + + /// + /// Gets Comand Parameters + /// + internal IList Parameters + { + get + { + return this.SerCmdInfo.Parameters; + } + } + + /// + /// Gets Name parameter + /// + internal string NameParameter + { + get + { + return this.SerCmdInfo.NameParameter; + } + } + + /// + /// Gets SerCmdInfo. + /// + private SerializableCommandInfo SerCmdInfo + { + get + { + // lazy init + if (serCmdInfos == null) + { + serCmdInfos = PersistenceUtility.ReadCommandInfo(); + } + + if (this.serCmdInfo == null) + { + foreach (SerializableCommandInfo info in serCmdInfos) + { + if (info.Name.Equals(this.Name)) + { + this.serCmdInfo = info; + break; + } + } + } + + return this.serCmdInfo; + } + } + } + } +} + diff --git a/dotnet-connector/ExchangeConnector/RunSpaceInstance.cs b/dotnet-connector/ExchangeConnector/RunSpaceInstance.cs new file mode 100644 index 0000000..1ebfb4f --- /dev/null +++ b/dotnet-connector/ExchangeConnector/RunSpaceInstance.cs @@ -0,0 +1,462 @@ +// +// ==================== +// 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]" +// ==================== +// +// Tomas Knappek + +namespace Org.IdentityConnectors.Exchange +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Globalization; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Text; + using Org.IdentityConnectors.Framework.Common.Exceptions; + using Org.IdentityConnectors.Framework.Common.Objects; + using Org.IdentityConnectors.Common; + + /// + /// + /// The implementation of the run space. This wraps the real run space object + /// from powershell for use in the pool + /// First written for the exchange adapter, the snapin is not needed if you do + /// not use it for exchange + /// + /// + /// Two possible ways of executing a command using different access point to + /// the Runspace: + /// - RunspaceInvoke: simple commands in string form, the command string can + /// contain multiple commands and is basically the same form + /// as what you use when typing a command in the exchange + /// shell + /// - PipelineInvoke: complex (multi) command structured pipelines which also + /// allow complex parameters, like objects, to be passed in. + /// + /// + internal sealed class RunSpaceInstance : IDisposable + { + /// + /// This class name, used for logging purposes + /// + private static readonly string ClassName = typeof(RunSpaceInstance).ToString(); + + /// + /// The Exchange 2007 snap in name which needs to be loaded + /// + private const string Exchange2007SnapIn = "Microsoft.Exchange.Management.PowerShell.Admin"; + + /// + /// The Exchange 2010 snap in name which needs to be loaded + /// + private const string Exchange2010SnapIn = "Microsoft.Exchange.Management.PowerShell.E2010"; + + /// + /// Instance variable keeping the + /// + private RunspaceConfiguration runSpaceConfig; + + /// + /// instance, managed resource + /// + private Runspace runSpace; + + /// + /// instance, managed resource + /// + private RunspaceInvoke runSpaceInvoke; + + /// + /// The catalog of localized messages. + /// + private ConnectorMessages _messageCatalog; + + /// + /// Initializes a new instance of the class. + /// + /// Type of snapin to be loaded + /// The message catalog used for conveying localized messages. + /// Thrown when is null. + public RunSpaceInstance(SnapIn snapin, ConnectorMessages messageCatalog) + { + Assertions.NullCheck( messageCatalog, "messageCatalog" ); + _messageCatalog = messageCatalog; + + // initialize this + this.InitRunSpace(snapin); + } + + /// + /// Snapin type to load + /// + internal enum SnapIn + { + /// + /// None - not defined + /// + None, + + /// + /// MS Exchange snapin + /// + Exchange + } + + /// + /// Defines the various supported Exchange versions. + /// + private enum ExchangeVersion + { + E2007, + E2010 + } + + /// + /// Test the state of this , throws if in incorrect state + /// + public void Test() + { + const string MethodName = "Test"; + Debug.WriteLine(MethodName + ":entry", ClassName); + + // compare the state against the passed in state + if (this.runSpace != null + && this.runSpace.RunspaceStateInfo.State == RunspaceState.Opened) + { + Debug.WriteLine(MethodName + ":exit", ClassName); + return; + } + + throw new InvalidRunspaceStateException("Runspace is not in Opened state"); + } + + /// invoke the command + /// command string to execute + /// collection of objects with the result + /// if no command is passed in return null + /// if no output/errors from the invoke return an empty collection + public ICollection InvokeCommand(string command) + { + return this.InvokeCommand(command, null); + } + + /// + /// invoke the command + /// The input is passed in to the environment as the $input variable and + /// can be used in the script as follows: + /// invokeCommand("$input | Set-Mailbox", inputEnum) + /// inputEnum in the example could be the output of an earlier + /// invokeCommand call (and thus a complex set of objects) + /// + /// command string to execute + /// input passed in as $input in the execution + /// environment + /// collection of objects with the result + /// if no command is passed in return null + /// if no output from the invoke return an empty collection + public ICollection InvokeCommand( + string command, + IEnumerable input) + { + const string MethodName = "InvokeCommand"; + Debug.WriteLine(MethodName + "(" + command + ")" + ":entry", ClassName); + + IList errors = null; + + // trim the spaces and check the length + if (command == null || command.Trim().Length == 0) + { + Trace.TraceError("CommandString argument can't be null or empty"); + throw new ArgumentException("CommandString argument can't be null or empty"); + } + + // run the command + Collection returns = + this.runSpaceInvoke.Invoke(command, input, out errors); + + // check for errors + CheckErrors(errors); + + // an empty collection instead of null when we have executed + if (returns == null) + { + Debug.WriteLine(MethodName + ":exit", ClassName); + returns = new Collection(); + } + + Trace.WriteLine(String.Format(CultureInfo.CurrentCulture, "{0} results returned", returns.Count), ClassName); + Debug.WriteLine(MethodName + ":exit", ClassName); + return returns; + } + + /// + /// invoke the powershell pipeline + /// + /// a collection of commands to execute + /// collection of objects with the result + /// if no command is passed in return null + /// if no output from the invoke return an empty collection + public ICollection InvokePipeline(Collection commands) + { + const string MethodName = "InvokePipeline"; + Debug.WriteLine(MethodName + ":entry", ClassName); + + IList errors = null; + + if (commands == null || commands.Count == 0) + { + throw new ArgumentException("Commands argument is null or empty"); + } + + // make sure the output is set + errors = null; + Collection results; + + // create the pipeline + Pipeline pipe = this.runSpace.CreatePipeline(); + + // add the commands to the pipeline + foreach (Command item in commands) + { + pipe.Commands.Add(item); + } + + // run the pipeline if we have something to execute + results = pipe.Invoke(); + PipelineReader reader = pipe.Error; + errors = (IList)reader.ReadToEnd(); + + // check for errors + CheckErrors(errors); + + // an empty collection instead of null when we have executed + if (results == null) + { + Debug.WriteLine("NO result returned"); + results = new Collection(); + } + + Debug.WriteLine(MethodName + ":exit", ClassName); + return results; + } + + /// + /// invoke the pipeline + /// + /// a command to execute + /// collection of objects with the result + /// if no command is passed in return null + /// if no output from the invoke return an empty collection + public ICollection InvokePipeline(Command item) + { + // create a new collection and add the command + // specifically not a CommandCollection: that will not work here + Collection commands = new Collection(); + commands.Add(item); + return this.InvokePipeline(commands); + } + + /// + /// Implementation of + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Checks whether errors List contains some error, if so the errors are concatenated and exception is thrown, + /// throws if the parameter is not empty + /// + /// List of error messages + private static void CheckErrors(IList errors) + { + StringBuilder builder = new StringBuilder(); + foreach (object error in errors) + { + builder.Append(error.ToString()); + builder.Append("\n"); + } + + if (builder.Length > 0) + { + throw new ConnectorException(builder.ToString()); + } + } + + /// + /// Dispose/Finalize pattern + /// + /// true if called from + private void Dispose(bool disposing) + { + if (disposing) + { + // Free other state (managed objects). + // clean up the runspace with attached things: + // the API docs show that the RunspaceInvoke will call Dispose on + // the Runspace which in turn calls Close on the Runspace. + if (this.runSpaceInvoke != null) + { + this.runSpaceInvoke.Dispose(); + } + else + { + if (this.runSpace != null) + { + this.runSpace.Dispose(); + } + } + } + _messageCatalog = null; + } + + /// + /// main initialisation routine for the + /// + /// to be initialized together with the + private void InitRunSpace(SnapIn snapin) + { + const string MethodName = "InitRunSpace"; + Debug.WriteLine(MethodName + "(" + snapin + ")" + ":entry", ClassName); + + // create a new config from scratch + PSSnapInException snapOutput = null; + this.runSpaceConfig = RunspaceConfiguration.Create(); + + switch (snapin) + { + case SnapIn.Exchange: + var serverVersion = GetExchangeServerVersion(); + switch (serverVersion) + { + case ExchangeVersion.E2007: + // used for force load of the exchange dll's + AppDomain.CurrentDomain.AssemblyResolve += + new ResolveEventHandler(ExchangeUtility.AssemblyResolver2007); + + this.runSpaceConfig.AddPSSnapIn(Exchange2007SnapIn, out snapOutput); + break; + case ExchangeVersion.E2010: + // used for force load of the exchange dll's + AppDomain.CurrentDomain.AssemblyResolve += + new ResolveEventHandler(ExchangeUtility.AssemblyResolver2010); + + this.runSpaceConfig.AddPSSnapIn(Exchange2010SnapIn, out snapOutput); + break; + } + break; + } + + // check snapOutput + if (snapOutput != null) + { + throw snapOutput; + } + + // create the real Runspace and open it for processing + this.runSpace = RunspaceFactory.CreateRunspace(this.runSpaceConfig); + this.runSpace.Open(); + this.runSpaceInvoke = new RunspaceInvoke(this.runSpace); + + Debug.WriteLine(MethodName + ":exit", ClassName); + } + + /// + /// Determines the version of the Exchange server. + /// + /// As the remote management functionality is not utilized, the Exchange powershell snap-in must be registered + /// on the local computer. Different snap-in is used to manage Exchange 2007 and 2010, hence the server version is determined by the + /// registered snap-in. + /// + /// The version of the Exchange server to manage. + /// Thrown when the version cannot be determined. + private ExchangeVersion GetExchangeServerVersion() + { + const string MethodName = "GetServerVersion"; + Debug.WriteLine(MethodName + ":entry", ClassName); + + const string ExchangeSnapinNamePrefix = "Microsoft.Exchange.Management.PowerShell."; + + ExchangeVersion? version = null; + using (var runspace = RunspaceFactory.CreateRunspace()) + { + runspace.Open(); + + using (var pipeline = runspace.CreatePipeline()) + { + var getSnapinsCommand = new Command("Get-PSSnapin"); + getSnapinsCommand.Parameters.Add("Registered"); + + pipeline.Commands.Add(getSnapinsCommand); + + var snapinList = pipeline.Invoke(); + + PipelineReader reader = pipeline.Error; + CheckErrors((IList)reader.ReadToEnd()); + + runspace.Close(); + + if ((snapinList == null) || (snapinList.Count == 0)) + { + Debug.WriteLine("No snap-in returned"); + throw new ConnectorException(_messageCatalog.Format("ex_NoPowerShellSnapins", "There are no registered PowerShell snap-ins.")); + } + + foreach (var snapin in snapinList) + { + if ((snapin.Properties["Name"] != null) && + (snapin.Properties["Name"].Value != null) && + (snapin.Properties["Name"].Value.ToString().StartsWith(ExchangeSnapinNamePrefix, + StringComparison.InvariantCultureIgnoreCase))) + { + var snapinName = snapin.Properties["Name"].Value.ToString(); + switch (snapinName.Substring(ExchangeSnapinNamePrefix.Length)) + { + case "Admin": + //Microsoft.Exchange.Management.PowerShell.Admin snap-in is used to manage Exchange 2007 + version = ExchangeVersion.E2007; + break; + case "E2010": + //Microsoft.Exchange.Management.PowerShell.E2010 snap-in is used to manage Exchange 2010 + version = ExchangeVersion.E2010; + break; + } + } + } + } + } + + if (!version.HasValue) + { + throw new ConnectorException(_messageCatalog.Format("ex_NoSupportedExchangeSnapin", + "There is no supported Exchange PowerShell snap-in registered.")); + } + + Debug.WriteLine(MethodName + ":exit", ClassName); + return version.Value; + } + } +} diff --git a/dotnet-connector/ExchangeConnector/version.template b/dotnet-connector/ExchangeConnector/version.template new file mode 100644 index 0000000..34c3267 --- /dev/null +++ b/dotnet-connector/ExchangeConnector/version.template @@ -0,0 +1 @@ +1.4.1.0 \ No newline at end of file diff --git a/dotnet-connector/legal-notices/CDDLv1_0.txt b/dotnet-connector/legal-notices/CDDLv1_0.txt new file mode 100644 index 0000000..da23621 --- /dev/null +++ b/dotnet-connector/legal-notices/CDDLv1_0.txt @@ -0,0 +1,384 @@ +Unless otherwise noted, all files in this distribution are released +under the Common Development and Distribution License (CDDL). +Exceptions are noted within the associated source files. + +-------------------------------------------------------------------- + + +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 + +1. Definitions. + + 1.1. "Contributor" means each individual or entity that creates + or contributes to the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Software, prior Modifications used by a Contributor (if any), + and the Modifications made by that particular Contributor. + + 1.3. "Covered Software" means (a) the Original Software, or (b) + Modifications, or (c) the combination of files containing + Original Software with files containing Modifications, in + each case including portions thereof. + + 1.4. "Executable" means the Covered Software in any form other + than Source Code. + + 1.5. "Initial Developer" means the individual or entity that first + makes Original Software available under this License. + + 1.6. "Larger Work" means a work which combines Covered Software or + portions thereof with code not governed by the terms of this + License. + + 1.7. "License" means this document. + + 1.8. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed + herein. + + 1.9. "Modifications" means the Source Code and Executable form of + any of the following: + + A. Any file that results from an addition to, deletion from or + modification of the contents of a file containing Original + Software or previous Modifications; + + B. Any new file that contains any part of the Original + Software or previous Modifications; or + + C. Any new file that is contributed or otherwise made + available under the terms of this License. + + 1.10. "Original Software" means the Source Code and Executable + form of computer software code that is originally released + under this License. + + 1.11. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, + process, and apparatus claims, in any patent Licensable by + grantor. + + 1.12. "Source Code" means (a) the common form of computer software + code in which modifications are made and (b) associated + documentation included in or with such code. + + 1.13. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms + of, this License. For legal entities, "You" includes any + entity which controls, is controlled by, or is under common + control with You. For purposes of this definition, + "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty + percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants. + + 2.1. The Initial Developer Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, the Initial + Developer hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer, to use, + reproduce, modify, display, perform, sublicense and + distribute the Original Software (or portions thereof), + with or without Modifications, and/or as part of a Larger + Work; and + + (b) under Patent Claims infringed by the making, using or + selling of Original Software, to make, have made, use, + practice, sell, and offer for sale, and/or otherwise + dispose of the Original Software (or portions thereof). + + (c) The licenses granted in Sections 2.1(a) and (b) are + effective on the date Initial Developer first distributes + or otherwise makes the Original Software available to a + third party under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: (1) for code that You delete from the Original + Software, or (2) for infringements caused by: (i) the + modification of the Original Software, or (ii) the + combination of the Original Software with other software + or devices. + + 2.2. Contributor Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, each + Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor to use, reproduce, + modify, display, perform, sublicense and distribute the + Modifications created by such Contributor (or portions + thereof), either on an unmodified basis, with other + Modifications, as Covered Software and/or as part of a + Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either + alone and/or in combination with its Contributor Version + (or portions of such combination), to make, use, sell, + offer for sale, have made, and/or otherwise dispose of: + (1) Modifications made by that Contributor (or portions + thereof); and (2) the combination of Modifications made by + that Contributor with its Contributor Version (or portions + of such combination). + + (c) The licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first distributes or + otherwise makes the Modifications available to a third + party. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: (1) for any code that Contributor has deleted + from the Contributor Version; (2) for infringements caused + by: (i) third party modifications of Contributor Version, + or (ii) the combination of Modifications made by that + Contributor with other software (except as part of the + Contributor Version) or other devices; or (3) under Patent + Claims infringed by Covered Software in the absence of + Modifications made by that Contributor. + +3. Distribution Obligations. + + 3.1. Availability of Source Code. + + Any Covered Software that You distribute or otherwise make + available in Executable form must also be made available in Source + Code form and that Source Code form must be distributed only under + the terms of this License. You must include a copy of this + License with every copy of the Source Code form of the Covered + Software You distribute or otherwise make available. You must + inform recipients of any such Covered Software in Executable form + as to how they can obtain such Covered Software in Source Code + form in a reasonable manner on or through a medium customarily + used for software exchange. + + 3.2. Modifications. + + The Modifications that You create or to which You contribute are + governed by the terms of this License. You represent that You + believe Your Modifications are Your original creation(s) and/or + You have sufficient rights to grant the rights conveyed by this + License. + + 3.3. Required Notices. + + You must include a notice in each of Your Modifications that + identifies You as the Contributor of the Modification. You may + not remove or alter any copyright, patent or trademark notices + contained within the Covered Software, or any notices of licensing + or any descriptive text giving attribution to any Contributor or + the Initial Developer. + + 3.4. Application of Additional Terms. + + You may not offer or impose any terms on any Covered Software in + Source Code form that alters or restricts the applicable version + of this License or the recipients' rights hereunder. You may + choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of + Covered Software. However, you may do so only on Your own behalf, + and not on behalf of the Initial Developer or any Contributor. + You must make it absolutely clear that any such warranty, support, + indemnity or liability obligation is offered by You alone, and You + hereby agree to indemnify the Initial Developer and every + Contributor for any liability incurred by the Initial Developer or + such Contributor as a result of warranty, support, indemnity or + liability terms You offer. + + 3.5. Distribution of Executable Versions. + + You may distribute the Executable form of the Covered Software + under the terms of this License or under the terms of a license of + Your choice, which may contain terms different from this License, + provided that You are in compliance with the terms of this License + and that the license for the Executable form does not attempt to + limit or alter the recipient's rights in the Source Code form from + the rights set forth in this License. If You distribute the + Covered Software in Executable form under a different license, You + must make it absolutely clear that any terms which differ from + this License are offered by You alone, not by the Initial + Developer or Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred + by the Initial Developer or such Contributor as a result of any + such terms You offer. + + 3.6. Larger Works. + + You may create a Larger Work by combining Covered Software with + other code not governed by the terms of this License and + distribute the Larger Work as a single product. In such a case, + You must make sure the requirements of this License are fulfilled + for the Covered Software. + +4. Versions of the License. + + 4.1. New Versions. + + Sun Microsystems, Inc. is the initial license steward and may + publish revised and/or new versions of this License from time to + time. Each version will be given a distinguishing version number. + Except as provided in Section 4.3, no one other than the license + steward has the right to modify this License. + + 4.2. Effect of New Versions. + + You may always continue to use, distribute or otherwise make the + Covered Software available under the terms of the version of the + License under which You originally received the Covered Software. + If the Initial Developer includes a notice in the Original + Software prohibiting it from being distributed or otherwise made + available under any subsequent version of the License, You must + distribute and make the Covered Software available under the terms + of the version of the License under which You originally received + the Covered Software. Otherwise, You may also choose to use, + distribute or otherwise make the Covered Software available under + the terms of any subsequent version of the License published by + the license steward. + + 4.3. Modified Versions. + + When You are an Initial Developer and You want to create a new + license for Your Original Software, You may create and use a + modified version of this License if You: (a) rename the license + and remove any references to the name of the license steward + (except to note that the license differs from this License); and + (b) otherwise make it clear that the license contains terms which + differ from this License. + +5. DISCLAIMER OF WARRANTY. + + COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" + BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED + SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR + PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND + PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY + COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE + INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY + NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF + WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS + DISCLAIMER. + +6. TERMINATION. + + 6.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to + cure such breach within 30 days of becoming aware of the breach. + Provisions which, by their nature, must remain in effect beyond + the termination of this License shall survive. + + 6.2. If You assert a patent infringement claim (excluding + declaratory judgment actions) against Initial Developer or a + Contributor (the Initial Developer or Contributor against whom You + assert such claim is referred to as "Participant") alleging that + the Participant Software (meaning the Contributor Version where + the Participant is a Contributor or the Original Software where + the Participant is the Initial Developer) directly or indirectly + infringes any patent, then any and all rights granted directly or + indirectly to You by such Participant, the Initial Developer (if + the Initial Developer is not the Participant) and all Contributors + under Sections 2.1 and/or 2.2 of this License shall, upon 60 days + notice from Participant terminate prospectively and automatically + at the expiration of such 60 day notice period, unless if within + such 60 day period You withdraw Your claim with respect to the + Participant Software against such Participant either unilaterally + or pursuant to a written agreement with Participant. + + 6.3. In the event of termination under Sections 6.1 or 6.2 above, + all end user licenses that have been validly granted by You or any + distributor hereunder prior to termination (excluding licenses + granted to You by any distributor) shall survive termination. + +7. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE + INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF + COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE + LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT + LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK + STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL + INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT + APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO + NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR + CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT + APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + + The Covered Software is a "commercial item," as that term is + defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial + computer software" (as that term is defined at 48 + C.F.R. 252.227-7014(a)(1)) and "commercial computer software + documentation" as such terms are used in 48 C.F.R. 12.212 + (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 + C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all + U.S. Government End Users acquire Covered Software with only those + rights set forth herein. This U.S. Government Rights clause is in + lieu of, and supersedes, any other FAR, DFAR, or other clause or + provision that addresses Government rights in computer software + under this License. + +9. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed + by the law of the jurisdiction specified in a notice contained + within the Original Software (except to the extent applicable law, + if any, provides otherwise), excluding such jurisdiction's + conflict-of-law provisions. Any litigation relating to this + License shall be subject to the jurisdiction of the courts located + in the jurisdiction and venue specified in a notice contained + within the Original Software, with the losing party responsible + for costs, including, without limitation, court costs and + reasonable attorneys' fees and expenses. The application of the + United Nations Convention on Contracts for the International Sale + of Goods is expressly excluded. Any law or regulation which + provides that the language of a contract shall be construed + against the drafter shall not apply to this License. You agree + that You alone are responsible for compliance with the United + States export administration regulations (and the export control + laws and regulation of any other countries) when You use, + distribute or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or + indirectly, out of its utilization of rights under this License + and You agree to work with Initial Developer and Contributors to + distribute such responsibility on an equitable basis. Nothing + herein is intended or shall be deemed to constitute any admission + of liability. + +-------------------------------------------------------------------- + +NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND +DISTRIBUTION LICENSE (CDDL) + +For Covered Software in this distribution, this License shall +be governed by the laws of the State of California (excluding +conflict-of-law provisions). + +Any litigation relating to this License shall be subject to the +jurisdiction of the Federal Courts of the Northern District of +California and the state courts of the State of California, with +venue lying in Santa Clara County, California. diff --git a/dotnet-connector/legal-notices/ForgeRock_License.txt b/dotnet-connector/legal-notices/ForgeRock_License.txt new file mode 100644 index 0000000..748cd37 --- /dev/null +++ b/dotnet-connector/legal-notices/ForgeRock_License.txt @@ -0,0 +1,144 @@ +READ THIS SOFTWARE LICENSE AGREEMENT CAREFULLY. BY DOWNLOADING OR INSTALLING +THE FORGEROCK SOFTWARE, YOU, ON BEHALF OF YOURSELF AND YOUR COMPANY, AGREE TO +BE BOUND BY THIS SOFTWARE LICENSE AGREEMENT. IF YOU DO NOT AGREE TO THESE +TERMS, DO NOT DOWNLOAD OR INSTALL THE FORGEROCK SOFTWARE. + +1. Software License. + +1.1. Development Right to Use. If Company intends to or does use the ForgeRock +Software only for the purpose(s) of developing, testing, prototyping and +demonstrating its application software, then ForgeRock hereby grants Company a +nonexclusive, nontransferable, limited license to use the ForgeRock Software +only for those purposes, solely at Company's facilities and only in a +non-production environment. ForgeRock may audit Company's use of the ForgeRock +Software to confirm that a production license is not required upon reasonable +written notice to Company. If Company intends to use the ForgeRock Software in +a live environment, Company must purchase a production license and may only use +the ForgeRock Software licensed thereunder in accordance with the terms and +conditions of that subscription agreement. + +1.2. Restrictions. Except as expressly set forth in this ForgeRock Software +License Agreement (the "Agreement"), Company shall not, directly or indirectly: +(a) sublicense, resell, rent, lease, distribute or otherwise transfer rights or +usage in the ForgeRock Software, including without limitation to Company +subsidiaries and affiliates; (b) remove or alter any copyright, trademark or +proprietary notices in the ForgeRock Software; or (c) use the ForgeRock +Software in any way that would subject the ForgeRock Software, in whole in or +in part, to a Copyleft License. As used herein, "Copyleft License" means a +software license that requires that information necessary for reproducing and +modifying such software must be made available publicly to recipients of +executable versions of such software (see, e.g., GNU General Public License and +http://www.gnu.org/copyleft/). + +2. Proprietary Rights. + +2.1. ForgeRock Intellectual Property. Title to and ownership of all copies of +the ForgeRock Software whether in machine-readable (source, object code or +other format) or printed form, and all related technical know-how and all +rights therein (including without limitation all intellectual property rights +applicable thereto), belong to ForgeRock and its licensors and shall remain the +exclusive property thereof. ForgeRock's name, logo, trade names and trademarks +are owned exclusively by ForgeRock and no right is granted to Company to use +any of the foregoing except as expressly permitted herein. All rights not +expressly granted to Company are reserved by ForgeRock and its licensors. + +2.2. Suggestions. Company hereby grants to ForgeRock a royalty-free, worldwide, +transferable, sublicensable and irrevocable right and license to use, copy, +modify and distribute, including by incorporating into any product or service +owned by ForgeRock, any suggestions, enhancements, recommendations or other +feedback provided by Company relating to any product or service owned or +offered by ForgeRock. + +2.3. Source Code. The source code underlying the ForgeRock Software is +available at www.forgerock.org. + +3. Term and Termination. The terms of this Agreement shall commence on the +Effective Date and shall continue in force unless earlier terminated in +accordance this Section. This Agreement shall terminate without notice to +Company in the event Company is in material breach of any of the terms and +conditions of this Agreement. As used herein, "Effective Date" means the date +on which Company first accepted this Agreement and downloads the ForgeRock +Software. + +4. Disclaimer of Warranties. THE FORGEROCK SOFTWARE LICENSED HEREUNDER IS +LICENSED "AS IS" AND WITHOUT WARRANTY OF ANY KIND. FORGEROCK AND IT'S LICENSORS +EXPRESSLY DISCLAIM ALL WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, +INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND ANY WARRANTY OF NON-INFRINGEMENT. + +5. General Indemnification. Company shall defend, indemnify and hold ForgeRock +harmless from and against any and all liabilities, damages, losses, costs and +expenses (including but not limited to reasonable fees of attorneys and other +professionals) payable to third parties based upon any claim arising out of or +related to the use of Company's products, provided that ForgeRock: (a) promptly +notifies Company of the claim; (b) provides Company with all reasonable +information and assistance, at Company's expense, to defend or settle such a +claim; and (c) grants Company authority and control of the defense or +settlement of such claim. Company shall not settle any such claim, without +ForgeRock's prior written consent, if such settlement would in any manner +effect ForgeRock's rights in the ForgeRock Software or otherwise. ForgeRock +reserves the right to retain counsel, at ForgeRock's expense, to participate in +the defense and settlement of any such claim. + +6. Limitation of Liability. IN NO EVENT SHALL FORGEROCK BE LIABLE FOR THE COST +OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, ANY LOST PROFITS, REVENUE, OR +DATA, INTERRUPTION OF BUSINESS OR FOR ANY INCIDENTAL, SPECIAL, CONSEQUENTIAL OR +INDIRECT DAMAGES OF ANY KIND, AND WHETHER ARISING OUT OF BREACH OF WARRANTY, +BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE OR IF SUCH DAMAGE COULD HAVE +BEEN REASONABLY FORESEEN. IN NO EVENT SHALL FORGEROCK'S LIABILITY ARISING OUT +OF OR RELATED TO THIS AGREEMENT WHETHER IN CONTRACT, TORT OR UNDER ANY OTHER +THEORY OF LIABILITY, EXCEED IN THE AGGREGATE $1,000 USD. + +7. General. + +7.1. Governing Law. This Agreement shall be governed by and interpreted in +accordance with the laws of the State of California without reference to its +conflicts of law provisions. + +7.2. Assignment. Company may not assign any of its rights or obligations under +this Agreement without the prior written consent of ForgeRock, which consent +shall not be unreasonably withheld. Any assignment not in conformity with this +Section shall be null and void. + +7.3. Waiver. A waiver on one occasion shall not be construed as a waiver of any +right on any future occasion. No delay or omission by a party in exercising any +of its rights hereunder shall operate as a waiver of such rights. + +7.4. Compliance with Law. The ForgeRock Software is subject to U.S. export +control laws, including the U.S. Export Administration Act and its associated +regulations, and may be subject to export or import regulations in other +countries. Company agrees to comply with all laws and regulations of the United +States and other countries ("Export Laws") to assure that neither the ForgeRock +Software, nor any direct products thereof are; (a) exported, directly or +indirectly, in violation of Export Laws, either to any countries that are +subject to U.S. export restrictions or to any end user who has been prohibited +from participating in the U.S. export transactions by any federal agency of the +U.S. government or (b) intended to be used for any purpose prohibited by Export +Laws, including, without limitation, nuclear, chemical, or biological weapons +proliferation. + +7.5. US Government Restrictions. Company acknowledges that the ForgeRock +Software consists of "commercial computer software" and "commercial computer +software documentation" as such terms are defined in the Code of Federal +Regulations. No Government procurement regulations or contract clauses or +provisions shall be deemed a part of any transaction between the parties unless +its inclusion is required by law, or mutually agreed in writing by the parties +in connection with a specific transaction. Use, duplication, reproduction, +release, modification, disclosure or transfer of the ForgeRock Software is +restricted in accordance with the terms of this Agreement. + +7.6. Provision Severability. In the event that it is determined by a court of +competent jurisdiction that any provision of this Agreement is invalid, +illegal, or otherwise unenforceable, such provision shall be enforced as nearly +as possible in accordance with the stated intention of the parties, while the +remainder of this Agreement shall remain in full force and effect and bind the +parties according to its terms. To the extent any provision cannot be enforced +in accordance with the stated intentions of the parties, such terms and +conditions shall be deemed not to be a part of this Agreement. + +7.7. Entire Agreement. This Agreement constitutes the entire and exclusive +agreement between the parties with respect to the subject matter hereof and +supersede any prior agreements between the parties with respect to such subject +matter +