<cfcomponent output="Yes" displayname="Active Directory Service" hint="Provides General Active Directory Communication">

<!--- Create component-wide instance variable. --->
<cfset Instance = StructNew()>

<cffunction name="Init" returntype="ActiveDirectoryService" access="public" output="Yes" displayname="Active Directory Service Initializer" hint="Initializes the Active Directory Communications Service.">

	<cfargument name="ServerList" type="string" required="No" default="server1,server2" displayname="Comma-separated list of LDAP servers" hint="Comma-separated list of LDAP servers.">
	<cfargument name="UserName" type="string" required="No" default="username" displayname="Active Directory LDAP account user name" hint="User account that has access to Active Directory.  This account will need to be a domain admin in order to modify another domain admins attributes.">
	<cfargument name="Password" type="string" required="No" default="password" displayname="Active Directory LDAP account password" hint="Password for the user account in the form of 'acenet\username'.">
	<cfargument name="Port" type="string" required="No" default="389" displayname="Active Directory LDAP port" hint="Active Directory LDAP query connection port.">
	<cfargument name="QueryTimeout" type="string" required="No" default="10" displayname="Active Directory query timeout" hint="Active Directory LDAP query connection timeout.">

	<cfif not IsNumeric(Arguments.Port)>
		<cfthrow
			message="Active Directory Communication Service could not be initialized."
			detail="The PORT agrument must be a numeric value."
			type="AceLink.Exception.ActiveDirectoryService.Init.InvalidValue">
	</cfif>

	<cfif not IsNumeric(Arguments.QueryTimeout) or Arguments.QueryTimeout lt 1>
		<cfthrow
			message="Active Directory Communication Service could not be initialized."
			detail="The QUERYTIMEOUT agrument must be a numeric value greater than zero."
			type="AceLink.Exception.ActiveDirectoryService.Init.InvalidValue">
	</cfif>

	<cfset Instance.LDAPServerList = Arguments.ServerList>
	<cfset Instance.LDAPUserName = Arguments.UserName>
	<cfset Instance.LDAPPassword = Arguments.Password>
	<cfset Instance.LDAPPort = Arguments.Port>
	<cfset Instance.LDAPTimeout = Arguments.QueryTimeout>

	<!--- Return a "pointer" to this instance. --->
	<!--- This allows a single statement to create, and initilize, an instance. --->
	<!--- Example: objAD = CreateObject("component", "cfc.ActiveDirectoryService").Init() --->
	<cfreturn this>

</cffunction>

<cffunction name="LDAPQuery" returntype="query" access="public" output="Yes" displayname="LDAP Query" hint="Provides basic Active Directory read-only queries.">

	<cfargument name="Start" type="string" required="Yes" displayname="Search Start" hint="LDAP query starts in this Active Directory container (OU).">
	<cfargument name="Scope"  type="string" required="Yes" displayname="Search Scope" hint="LDAP query is limited to BASE|ONELEVEL|SUBTREE.">
	<cfargument name="LDAPAttributes" type="string" required="Yes" displayname="LDAP Query Attributes" hint="LDAP query will return this comma-separated list of attributes.">
	<cfargument name="Filter" type="string" required="Yes" displayname="Query Results Filter" hint="LDAP query results will be filtered according to this string.">
	<cfargument name="SortOrder" type="string" required="No" default="cn" displayname="Query Results Sort Order" hint="LDAP query results will be sorted according to this comma-separated list of attributes.">
	<cfargument name="Separator" type="string" required="No" default="|" displayname="Query Results Value Separator Character" hint="LDAP query results that contain multi-value attributes will be separated by this character.">

	<cfset var Results = "">

	<!--- Ensure the instance has been properly initialized prior to calling this method. --->
	<cfif StructIsEmpty(Instance)>
		<cfthrow
			message="LDAPQuery request could not be completed."
			detail="The Active Directory Communication Service has not been initialized."
			type="AceLink.Exception.ActiveDirectoryService.Init">
	</cfif>

	<!--- Ensure the SCOPE argument contains a valid value. --->
	<cfif not ReFindNoCase("(^Base$)|(^OneLevel$)|(^SubTree$)", Arguments.Scope)>
		<cfthrow
			message="LDAPQuery request could not be completed."
			detail="Incorrect SCOPE argument value passed.  Valid values are 'BASE', 'ONELEVEL' and 'SUBTREE'"
			type="AceLink.Exception.LDAPQuery.InvalidScope">
	</cfif>

	<!--- Attempt to perform the LDAP query against each server until a successful query is performed. --->
	<cfloop from="1" to="#ListLen(Instance.LDAPServerList)#" index="ThisServer">
		<cftry>
			<cfldap
				action="query"
				name="Results"
				start="#Arguments.Start#"
				scope="#Arguments.Scope#"
				attributes="#Arguments.LDAPAttributes#"
				separator="#Arguments.Separator#"
				filter="#Arguments.Filter#"
				sort="#Arguments.SortOrder#"
				server="#ListGetAt(Instance.LDAPServerList, ThisServer)#"
				port="#Instance.LDAPPort#"
				username="#Instance.LDAPUserName#"
				password="#Instance.LDAPPassword#"
				timeout="#Instance.LDAPTimeOut#">

			<cfreturn Results>

			<cfcatch type="Any">
				<!--- Do nothing in order to try all ldap servers. --->
			</cfcatch>
		</cftry>
	</cfloop>

	<!--- If we got here, we were not able to perform a successful LDAP query. --->
	<cfthrow
		message="LDAPQuery request could not be completed."
		detail="A domain controller could not be contacted, invalid Active Directory credentials were used or incorrect attributes were passed to the LDAP query."
		extendedinfo="Ensure at least one domain controller is available to the ColdFusion server.  Ensure the LDAPQuery account has the appropriate permissions to query Active Directory.  Ensure a properly-formed LDAP query is passed to the LDAPQuery method.  Some attributes, such as 'memberOf' cannot be used to sort the results in some instances."
		type="AceLink.Exception.LDAPQuery">

</cffunction>

</cfcomponent>


