cancel
Showing results for 
Search instead for 
Did you mean: 

Help, syncing Active Directory users and groups

nullman
Champ in-the-making
Champ in-the-making
Hello,

I was able to get NTLM authentication and NTLM pass-through working following the Wiki. However, I haven't been able to get my Acitive Directory users and groups to sync via LDAP.

I don't get any errors when Alfresco starts, but I do see this when I enable debug for LDAP:

14:34:20,446 DEBUG [authentication.ldap.LDAPGroupExportSource] Found 0
14:34:20,446 DEBUG [authentication.ldap.LDAPGroupExportSource] Top 0
14:34:20,446 DEBUG [authentication.ldap.LDAPGroupExportSource] Secondary 0

Does this mean my LDAP connection is working, but I am searching AD incorrectly?

Our Setup: Windows 2003, Community 1.3.0 bundle (Tomcat)

(Edit: This post was edited after learning more about how to configure LDAP/AD)
12 REPLIES 12

csiege
Champ in-the-making
Champ in-the-making
We have it working pretty well. 🙂

I would first remove the ntlm config file and just go for the ldap first…

Make sure to add a username to the authority-services-context.xml file to specify your AD username as an admin so when you get logged in you have admin rights.

ok so first just setup the ldap-authentication-context.xml file to get the syncing with AD and the ldap authentication into alfresco working…

here is our file… it syncs users 60 seconds after the startup, groups 120 seconds after the startup… then every hour….

You will HAVE TO change some of the ldap settings … search for "***" in the below to find what I think you need to set…

Once you get this working… just drop in the ntlm file as is…  wake the changes to the web.xml file per the wiki and the ntlm will work. 🙂

Oh, one thing!  you might want to enable more logging in the log4j.properties file.  We found the sync would crash on a user if one of their fields were null, like lastname was left blank in AD.

Let me know how it goes.

Chris


<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>

<beans>

    <!– DAO that rejects changes - LDAP is read only at the moment –>

    <bean id="alfDaoImpl" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>org.alfresco.repo.security.authentication.MutableAuthenticationDao</value>
        </property>
        <property name="transactionManager">
            <ref bean="transactionManager" />
        </property>
        <property name="target">
            <bean class="org.alfresco.repo.security.authentication.ntlm.NullMutableAuthenticationDao" />
        </property>
        <property name="transactionAttributes">
          <props>
               <prop key="*">${server.transaction.mode.default}</prop>
          </props>
        </property>
   </bean>

    <!– LDAP authentication configuration –>

    <!–

    You can also use JAAS authentication for Kerberos against Active Directory or NTLM if you also require single sign on from the
    web browser. You do not have to use LDAP authentication to synchronise groups and users from an LDAP store if it supports other
    authentication routes, like Active Directory.

    –>

   
    <bean id="authenticationComponentImpl" class="org.alfresco.repo.security.authentication.ldap.LDAPAuthenticationComponentImpl">
        <property name="LDAPInitialDirContextFactory">
            <ref bean="ldapInitialDirContextFactory"/>
        </property>
        <property name="userNameFormat">
           

<!–

            This maps between what the user types in and what is passed through to the underlying LDAP authentication.

            "%s" - the user id is passed through without modification.
            Used for LDAP authentication such as DIGEST-MD5, anything that is not "simple".

            "cn=%s,ou=London,dc=company,dc=com" - If the user types in "Joe Bloggs" the authentricate as "cn=Joe Bloggs,ou=London,dc=company,dc=com"
            Usually for simple authentication.
–>
           


            <value>%s</value>
        </property>
    </bean>
   

    <!–

    This bean is used to support general LDAP authentication. It is also used to provide read only access to users and groups
    to pull them out of the LDAP reopsitory

    –>

    <bean id="ldapInitialDirContextFactory" class="org.alfresco.repo.security.authentication.ldap.LDAPInitialDirContextFactoryImpl">
        <property name="initialDirContextEnvironment">
            <map>
                <!– The LDAP provider –>
                <entry key="java.naming.factory.initial">
                    <value>com.sun.jndi.ldap.LdapCtxFactory</value>
                </entry>

                <!– The url to the LDAP server –>
                <!– Note you can use space separated urls - they will be tried in turn until one works –>
                <!– This could be used to authenticate against one or more ldap servers (you will not know which one ….) –>
                <entry key="java.naming.provider.url">
                    <value>ldap://*** YOUR AD SERVERNAME HERE ***:389</value>
                </entry>

                <!– The authentication mechanism to use      –>
                <!– Some sasl authentication mechanisms may require a realm to be set –>
                <!–                java.naming.security.sasl.realm –>
                <!– The available options will depend on your LDAP provider –>
                <entry key="java.naming.security.authentication">
                    <!–  <value>DIGEST-MD5</value> –>
           <value>DIGEST-MD5</value>
                </entry>

                <!– The id of a user who can read group and user information –>
                <!– This does not go through the pattern substitution defined above and is used "as is" –>
                <entry key="java.naming.security.principal">
                   
           <value>***A USERNAME THAT HAS READ ACCESS TO AD MUST NOT BE ADMINISTRATOR*** </value>
                </entry>

                <!– The password for the user defined above –>
                <entry key="java.naming.security.credentials">
                    <value>*** PASSWORD FOR THE USERNAME ABOVE ***</value>
                </entry>
            </map>
        </property>
    </bean>

    <!– Ldap Syncronisation support –>

    <!–

    There can be more than one stack of beans that import users or groups. For example, it may be easier
    to have a version of ldapPeopleExportSource, and associated beans, for each sub-tree of your ldap directory
    from which you want to import users. You could then limit users to be imported from two or more sub tress and ignore
    users found else where. The same applies to the import of groups.

    The defaults shown below are for OpenLDAP.

    –>


    <!– Extract user information from LDAP and transform this to XML –>

    <bean id="ldapPeopleExportSource" class="org.alfresco.repo.security.authentication.ldap.LDAPPersonExportSource">
        <!–
        The query to select objects that represent the users to import.

        For Open LDAP, using a basic schema, the following is probably what you want:
        (objectclass=inetOrgPerson)

        For Active Directory:
        (objectclass=user)
        –>
        <property name="personQuery">
            <value>(objectclass=user)</value>
        </property>

        <!– cn=Users,
        The seach base restricts the LDAP query to a sub section of tree on the LDAP server.
        –>
      
         <!– *** THIS IS WHERE YOU NEED TO SETUP WHERE IN AD TO GET THE USERS *** –>
        <property name="searchBase">
            <value>CN=Users,DC=***YOUR DC HERE***,DC=local</value>
      <!– <value>OU=Security Groups,OU=Groups,DC=***,DC=local</value> –>
        </property>

        <!–
        The unique identifier for the user.

        THIS MUST MATCH WHAT THE USER TYPES IN AT THE LOGIN PROMPT

        For simple LDAP authentication this is likely to be "cn" or, less friendly, "distinguishedName"

        In OpenLDAP, using other authentication mechanisms "uid", but this depends on how you map
        from the id in the LDAP authentication request to search for the inetOrgPerson against which
        to authenticate.

        In Active Directory this is most likely to be "sAMAccountName"

        This property is mandatory and must appear on all users found by the query defined above.

        –>
        <property name="userIdAttributeName">
            <value>sAMAccountName</value>
        </property>

        <!– Services –>
        <property name="LDAPInitialDirContextFactory">
            <ref bean="ldapInitialDirContextFactory"/>
        </property>
        <property name="personService">
            <ref bean="personService"></ref>
        </property>
        <property name="namespaceService">
            <ref bean="namespaceService"/>
        </property>

        <!–
        The path to the location of a space to use as the default home folder.
        This folder should be readable by all users, or a group to which all imported users belong.
        –>
        <property name="defaultHomeFolder">
            <value>/app:company_home</value>
        </property>

        <!–
        This property defines a mapping between attributes held on LDAP user objects and
        the properties of user objects held in the repository. The key is the QName of an attribute in
        the repository, the value is the attribute name from the user/inetOrgPerson/.. object in the
        LDAP repository.
        –>
        <property name="attributeMapping">
            <map>
                <entry key="cm:userName">
                    <!– Must match the same attribute as userIdAttributeName –>
                    <value>sAMAccountName</value>
                </entry>
                <entry key="cm:firstName">
                    <!– OpenLDAP: "givenName" –>
                    <!– Active Directory: "givenName" –>
                    <value>givenName</value>
                </entry>
                <entry key="cm:lastName">
                    <!– OpenLDAP: "sn" –>
                    <!– Active Directory: "sn" –>
                    <value>sn</value>
                </entry>
                <entry key="cm:email">
                    <!– OpenLDAP: "mail" –>
                    <!– Active Directory: "???" –>
                    <value>mail</value>
                </entry>
                <!– <entry key="cm:organizationId"> –>
                    <!– OpenLDAP: "o" –>
                    <!– Active Directory: "???" –>
                    <!– <value>o</value> –>
                <!– </entry> –>
            </map>
        </property>
    </bean>

    <!– Extract group information from LDAP and transform this to XML –>

    <bean id="ldapGroupExportSource" class="org.alfresco.repo.security.authentication.ldap.LDAPGroupExportSource">
        <!–
        The query to select objects that represent the groups to import.

        For Open LDAP, using a basic schema, the following is probably what you want:
        (objectclass=groupOfNames)

        For Active Directory:
        (objectclass=group/organizationalUnit)
        –>
        <property name="groupQuery">
            <value>(objectClass=group)</value>
        </property>

        <!–
        The seach base restricts the LDAP query to a sub section of tree on the LDAP server.
        –>
        <property name="searchBase">
            <value>OU=Alfresco,OU=Groups,DC=***YOU DC HERE ***,DC=local</value>
        </property>

        <!–
        The unique identifier for the user. This must match the userIdAttributeName on the ldapPeopleExportSource bean above.
        –>
        <property name="userIdAttributeName">
            <value>sAMAccountName</value>
        </property>

        <!–
        An attribute that is a unique identifier for each group found.
        This is also the name of the group with the current group implementation.
        This is mandatory for any groups found.

        OpenLDAP: "cn" as it is mandatory on groupOfNames
        Active Directory: "cn"

        –>
        <property name="groupIdAttributeName">
            <value>cn</value>
        </property>

        <!–
        The objectClass attribute for group members.
        For each member of a group, the distinguished name is given.
        The object is looked up by its DN. If the object is of this class it is treated as a group.
        –>
        <property name="groupType">
      <value>group</value>
        </property>

        <!–
        The objectClass attribute for person members.
        For each member of a group, the distinguished name is given.
        The object is looked up by its DN. If the object is of this class it is treated as a person.
        –>
        <property name="personType">
            <!– <value>inetOrgPerson/person</value> –>
      <value>person</value>
        </property>
        <property name="LDAPInitialDirContextFactory">
            <ref bean="ldapInitialDirContextFactory"/>
        </property>
        <property name="namespaceService">
            <ref bean="namespaceService"/>
        </property>

        <!–
        The repeating attribute on group objects (found by query or as sub groups)
        used to define membership of the group. This is assumed to hold distinguished names of
        other groups or users/people; the above types are used to determine this.

        OpenLDAP: "member" as it is mandatory on groupOfNames
        Active Directory: "member"

        –>
        <property name="memberAttribute">
            <value>member</value>
        </property>
    </bean>

    <!– Job definitions to import LDAP people and groups –>
    <!– The triggers register themselves with the scheduler –>
    <!– You may comment in the default scheduler to enable these triggers –>
    <!– If a cron base trigger is what you want seee scheduled-jobs-context.xml for examples. –>

    <!– Trigger to load poeple –>
    <!– Note you can have more than one initial (context, trigger, import job and export source) set –>
    <!– This would allow you to load people from more than one ldap store –>

    <bean id="ldapPeopleTrigger" class="org.alfresco.util.TriggerBean">
        <property name="jobDetail">
            <bean id="ldapPeopleJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
                <property name="jobClass">
                    <value>org.alfresco.repo.importer.ImporterJob</value>
                </property>
                <property name="jobDataAsMap">
                    <map>
                        <entry key="bean">
                            <ref bean="ldapPeopleImport"/>
                        </entry>
                    </map>
                </property>
            </bean>
        </property>
        <!– Start after 60 seconds of starting the repository –>
        <property name="startDelay">
            <value>60000</value>
        </property>
        <!– Repeat every hour –>
        <property name="repeatInterval">
            <value>3600000</value>
        </property>
        <!– Commented out to disable –>
        <property name="scheduler">
            <ref bean="schedulerFactory" />
        </property>
     </bean>

    <bean id="ldapGroupTrigger" class="org.alfresco.util.TriggerBean">
        <property name="jobDetail">
            <bean id="ldapGroupJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
                <property name="jobClass">
                    <value>org.alfresco.repo.importer.ImporterJob</value>
                </property>
                <property name="jobDataAsMap">
                    <map>
                        <entry key="bean">
                            <ref bean="ldapGroupImport"/>
                        </entry>
                    </map>
                </property>
            </bean>
        </property>
        <!– Start after 120 seconds of starting the repository –>
        <property name="startDelay">
            <value>120000</value>
        </property>
        <!– Repeat every hour –>
        <property name="repeatInterval">
            <value>3600000</value>
        </property>
        <!– Commented out to disable –>
        <property name="scheduler">
            <ref bean="schedulerFactory" />
        </property>
     </bean>

    <!– The bean that imports xml describing people –>

    <bean id="ldapPeopleImport" class="org.alfresco.repo.importer.ExportSourceImporter">
        <property name="importerService">
            <ref bean="importerComponent"/>
        </property>
        <property name="transactionService">
            <ref bean="transactionComponent"/>
        </property>
        <property name="authenticationComponent">
            <ref bean="authenticationComponent"/>
        </property>
        <property name="exportSource">
            <ref bean="ldapPeopleExportSource"/>
        </property>

        <!– The store that contains people - this should not be changed –>
        <property name="storeRef">
            <value>${spaces.store}</value>
        </property>

        <!– The location of people nodes within the store defined above - this should not be changed –>
        <property name="path">
            <value>/${system.system_container.childname}/${system.people_container.childname}</value>
        </property>

        <!– If true, clear all existing people before import, if false update/add people from the xml –>
        <property name="clearAllChildren">
            <value>false</value>
        </property>
        <property name="nodeService">
            <ref bean="nodeService"/>
        </property>
        <property name="searchService">
            <ref bean="searchService"/>
        </property>
        <property name="namespacePrefixResolver">
            <ref bean="namespaceService"/>
        </property>
    </bean>

    <!– The bean that imports xml descibing groups –>

    <bean id="ldapGroupImport" class="org.alfresco.repo.importer.ExportSourceImporter">
        <property name="importerService">
            <ref bean="importerComponent"/>
        </property>
        <property name="transactionService">
            <ref bean="transactionComponent"/>
        </property>
        <property name="authenticationComponent">
            <ref bean="authenticationComponent"/>
        </property>
        <property name="exportSource">
            <ref bean="ldapGroupExportSource"/>
        </property>
        <!– The store that contains group information - this should not be changed –>
        <property name="storeRef">
            <value>${alfresco_user_store.store}</value>
        </property>

        <!– The location of group information in the store above - this should not be changed –>
        <property name="path">
            <value>/${alfresco_user_store.system_container.childname}/${alfresco_user_store.authorities_container.childname}</value>
        </property>

        <!– If true, clear all existing groups before import, if false update/add groups from the xml –>
        <property name="clearAllChildren">
            <value>false</value>
        </property>
        <property name="nodeService">
            <ref bean="nodeService"/>
        </property>
        <property name="searchService">
            <ref bean="searchService"/>
        </property>
        <property name="namespacePrefixResolver">
            <ref bean="namespaceService"/>
        </property>
    </bean>

</beans>

nullman
Champ in-the-making
Champ in-the-making
Thanks so much for the help. I'm still lost however. I changed my web.xml and ntlm-authentication files back to the originals and started working on ldap-authentication-context.xml.

I see this in the Wiki: http://wiki.alfresco.com/wiki/Enterprise_Security_and_Authentication_Configuration

MD5 Digest authentication

This is the default authentication mechanism that should be supported by all LDAP servers.

Active Directory does not support this, but it does support Kerberos authentication which you could use in combination with the synchronisation config.

In your provided config it looks DIGEST-MD5 authentication is enabled. Is that correct? From the statement in the Wiki it would seem this shouldn't work with Active Directory, but I am probably just confused.


Thanks

andy
Champ on-the-rise
Champ on-the-rise
Hi

Not all ldap servers actually support digest MD5.
Some versions of ActiveDirectory do. It may be configurable??

If you connect to your ldap server, using something like the Softerra LDAP browser, you will be able to see which authentication mechanisms are supported. They are listed at the top level as you log in.

Regards

Andy

nullman
Champ in-the-making
Champ in-the-making
Thanks Andy. Using Softerra's LDAP Browser I was able to figure out that I could connect, but not browse the directory as an anonymous user. I am now using a login that allows me to browse in LDAP Browser. However, one problem down and up comes the next one.

I didn't have my search base defined well enough and a whole bunch of things I didn't want got pulled into Alfresco. I backed up the repository, got rid of the database, and did a restore - to get rid of all the "users" that were now in Alfresco. I decided I'd figure out what search base DN to use in Alfresco by using softerra's tool.

Using LDAP Browser I used Base of CN=Users,DC=crp,DC=[company],DC=com and see the users listed there as CN=[full name]. If I select a user I see they have a value of user for objectclass.

If I use LDAP Browser's search directory feature and use a Search DN: CN=Users,DC=crp,DC=[company],DC=com and a search filter of (objectclass=user), which if I understand this correctly is what Alfresco is going to do to grab the users, I see the users I want to import. Well, I actually see more than I want, but I'll get to that in a minute.

When I start Alfresco I still see this in the log and no users get imported:

14:34:20,446 DEBUG [authentication.ldap.LDAPGroupExportSource] Found 0
14:34:20,446 DEBUG [authentication.ldap.LDAPGroupExportSource] Top 0
14:34:20,446 DEBUG [authentication.ldap.LDAPGroupExportSource] Secondary 0

The time that everything got pulled into Alfresco I didn't narrow the base to CN=Users and I believe every record that had an objectclass of user was imported. Problem is that was a lot of non-Alfresco users. The people in CN=Users are the ones I want to import.

I'm not sure why this is. At the moment I am just trying to get the user's synced. I'll get to groups once that is working.

One other question. Because I see more users than I'd like to import using the above data in LDAP Browser can I restrict Alfresco's search to users that have certain parameters? (I assume so based on reading old posts about restricting users that were missing values Alfresco needed, now fixed in 1.3). For example I'd like to use something like this in order to get the users filtered down further:


        <property name="personQuery">
            <value><![CDATA[(&(objectclass=user)(givenName=*)(sn=*)(sAMAccountType=805306368))]]></value>
        </property>
       
        <!–
        The seach base restricts the LDAP query to a sub section of tree on the LDAP server.
        –>

        <property name="searchBase">
            <value>CN=Users,DC=crp,DC=[company],DC=com</value>
        </property>

Any more advice or ideas?

My ldap-authetication-context.xml file

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>

<beans>
   
    <!– DAO that rejects changes - LDAP is read only at the moment –>
   
    <bean id="alfDaoImpl" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>org.alfresco.repo.security.authentication.MutableAuthenticationDao</value>
        </property>
        <property name="transactionManager">
            <ref bean="transactionManager" />
        </property>
        <property name="target">
            <bean class="org.alfresco.repo.security.authentication.ntlm.NullMutableAuthenticationDao" />
        </property>
        <property name="transactionAttributes">
          <props>
               <prop key="*">${server.transaction.mode.default}</prop>
          </props>
        </property>
   </bean>

    <!– LDAP authentication configuration –>
   
    <!–
   
    You can also use JAAS authentication for Kerberos against Active Directory or NTLM if you also require single sign on from the
    web browser. You do not have to use LDAP authentication to synchronise groups and users from an LDAP store if it supports other
    authentication routes, like Active Directory.
   
    –>
   
    <bean id="authenticationComponentImpl" class="org.alfresco.repo.security.authentication.ldap.LDAPAuthenticationComponentImpl">
        <property name="LDAPInitialDirContextFactory">
            <ref bean="ldapInitialDirContextFactory"/>
        </property>
        <property name="userNameFormat">
            <!–
           
            This maps between what the user types in and what is passed through to the underlying LDAP authentication.
           
            "%s" - the user id is passed through without modification.
            Used for LDAP authentication such as DIGEST-MD5, anything that is not "simple".
           
            "cn=%s,ou=London,dc=company,dc=com" - If the user types in "Joe Bloggs" the authentricate as "cn=Joe Bloggs,ou=London,dc=company,dc=com"
            Usually for simple authentication.
           
            –>
            <value>cn=%s,dc=[COMPANY],dc=com</value>
        </property>
    </bean>
   
    <!–
   
    This bean is used to support general LDAP authentication. It is also used to provide read only access to users and groups
    to pull them out of the LDAP reopsitory
   
    –>
   
    <bean id="ldapInitialDirContextFactory" class="org.alfresco.repo.security.authentication.ldap.LDAPInitialDirContextFactoryImpl">
        <property name="initialDirContextEnvironment">
            <map>
                <!– The LDAP provider –>
                <entry key="java.naming.factory.initial">
                    <value>com.sun.jndi.ldap.LdapCtxFactory</value>
                </entry>
               
                <!– The url to the LDAP server –>
                <!– Note you can use space separated urls - they will be tried in turn until one works –>
                <!– This could be used to authenticate against one or more ldap servers (you will not know which one ….) –>
                <entry key="java.naming.provider.url">
                    <value>ldap://nocdc01.[COMPANY].com:389</value>
                </entry>
               
                <!– The authentication mechanism to use      –>
                <!– Some sasl authentication mechanisms may require a realm to be set –>
                <!–                java.naming.security.sasl.realm –>
                <!– The available options will depend on your LDAP provider –>
                <entry key="java.naming.security.authentication">
                    <value>simple</value>
                </entry>
               
                <!– The id of a user who can read group and user information –>
                <!– This does not go through the pattern substitution defined above and is used "as is" –>
                <entry key="java.naming.security.principal">
                    <value>[DOMAINACCOUNT]</value>
                </entry>
               
                <!– The password for the user defined above –>
                <entry key="java.naming.security.credentials">
                    <value>[PASSWORD]</value>
                </entry>

            </map>
        </property>
    </bean>
   
    <!– Ldap Syncronisation support –>
   
    <!–
       
    There can be more than one stack of beans that import users or groups. For example, it may be easier
    to have a version of ldapPeopleExportSource, and associated beans, for each sub-tree of your ldap directory
    from which you want to import users. You could then limit users to be imported from two or more sub tress and ignore
    users found else where. The same applies to the import of groups.
        
    The defaults shown below are for OpenLDAP.   
       
    –>
       
  
    <!– Extract user information from LDAP and transform this to XML –>
    
    <bean id="ldapPeopleExportSource" class="org.alfresco.repo.security.authentication.ldap.LDAPPersonExportSource">
        <!–
        The query to select objects that represent the users to import.
       
        For Open LDAP, using a basic schema, the following is probably what you want:
        (objectclass=inetOrgPerson)
       
        For Active Directory:
        (objectclass=user)
        –>
        <property name="personQuery">
            <value>(objectclass=user)</value>
        </property>
       
        <!–
        The seach base restricts the LDAP query to a sub section of tree on the LDAP server.
        –>
        <property name="searchBase">
            <value>CN=users,dc=crp,dc=[COMPANY],dc=com</value>
        </property>
       
        <!–
        The unique identifier for the user.
       
        THIS MUST MATCH WHAT THE USER TYPES IN AT THE LOGIN PROMPT   
       
        For simple LDAP authentication this is likely to be "cn" or, less friendly, "distinguishedName"
       
        In OpenLDAP, using other authentication mechanisms "uid", but this depends on how you map
        from the id in the LDAP authentication request to search for the inetOrgPerson against which
        to authenticate.
       
        In Active Directory this is most likely to be "sAMAccountName"
       
        This property is mandatory and must appear on all users found by the query defined above.
       
        –>
        <property name="userIdAttributeName">
            <value>sAMAccountName</value>
        </property>
       
        <!– Services –>
        <property name="LDAPInitialDirContextFactory">
            <ref bean="ldapInitialDirContextFactory"/>
        </property>
        <property name="personService">
            <ref bean="personService"></ref>
        </property>
        <property name="namespaceService">
            <ref bean="namespaceService"/>
        </property>
       
        <!–
        The path to the location of a space to use as the default home folder.
        This folder should be readable by all users, or a group to which all imported users belong.
        –>
        <property name="defaultHomeFolder">
            <value>/app:company_home</value>
        </property>
       
        <!–
        This property defines a mapping between attributes held on LDAP user objects and
        the properties of user objects held in the repository. The key is the QName of an attribute in
        the repository, the value is the attribute name from the user/inetOrgPerson/.. object in the
        LDAP repository.    
        –>
        <property name="attributeMapping">
            <map>
                <entry key="cm:userName">
                    <!– Must match the same attribute as userIdAttributeName –>
                    <value>sAMAccountName</value>
                </entry>
                <entry key="cm:firstName">
                    <!– OpenLDAP: "givenName" –>
                    <!– Active Directory: "givenName" –>
                    <value>givenName</value>
                </entry>
                <entry key="cm:lastName">
                    <!– OpenLDAP: "sn" –>
                    <!– Active Directory: "sn" –>
                    <value>sn</value>
                </entry>
                <entry key="cm:email">
                    <!– OpenLDAP: "mail" –>
                    <!– Active Directory: "???" –>
                    <value>mail</value>
                </entry>
                <!– <entry key="cm:organizationId"> –>
                    <!– OpenLDAP: "o" –>
                    <!– Active Directory: "???" –>
                <!– <value>o</value> –>
                <!– </entry> –>
            </map>
        </property>
    </bean>
   
    <!– Extract group information from LDAP and transform this to XML –>
   
    <bean id="ldapGroupExportSource" class="org.alfresco.repo.security.authentication.ldap.LDAPGroupExportSource">
        <!–
        The query to select objects that represent the groups to import.
       
        For Open LDAP, using a basic schema, the following is probably what you want:
        (objectclass=groupOfNames)
       
        For Active Directory:
        (objectclass=group)
        –>
        <property name="groupQuery">
            <value>(objectclass=group)</value>
        </property>
       
        <!–
        The seach base restricts the LDAP query to a sub section of tree on the LDAP server.
        –>
        <property name="searchBase">
            <value>CN=users,dc=crp,dc=[COMPANY],dc=com</value>
        </property>
       
        <!–
        The unique identifier for the user. This must match the userIdAttributeName on the ldapPeopleExportSource bean above.
        –>
        <property name="userIdAttributeName">
            <value>sAMAccountName</value>
        </property>
       
        <!–
        An attribute that is a unique identifier for each group found.
        This is also the name of the group with the current group implementation.
        This is mandatory for any groups found.
       
        OpenLDAP: "cn" as it is mandatory on groupOfNames
        Active Directory: "cn"
       
        –>
        <property name="groupIdAttributeName">
            <value>cn</value>
        </property>
       
        <!–
        The objectClass attribute for group members.
        For each member of a group, the distinguished name is given.
        The object is looked up by its DN. If the object is of this class it is treated as a group.
        –>
        <property name="groupType">
            <value>group</value>
        </property>
       
        <!–
        The objectClass attribute for person members.
        For each member of a group, the distinguished name is given.
        The object is looked up by its DN. If the object is of this class it is treated as a person.
        –>
        <property name="personType">
            <value>person</value>
        </property>
        <property name="LDAPInitialDirContextFactory">
            <ref bean="ldapInitialDirContextFactory"/>
        </property>
        <property name="namespaceService">
            <ref bean="namespaceService"/>
        </property>
       
        <!–
        The repeating attribute on group objects (found by query or as sub groups)
        used to define membership of the group. This is assumed to hold distinguished names of
        other groups or users/people; the above types are used to determine this.
       
        OpenLDAP: "member" as it is mandatory on groupOfNames
        Active Directory: "member"
       
        –>
        <property name="memberAttribute">
            <value>member</value>
        </property>
       
        <property name="authorityDAO">
            <ref bean="authorityDAO"/>
        </property>
    </bean>
   
    <!– Job definitions to import LDAP people and groups –>
    <!– The triggers register themselves with the scheduler –>
    <!– You may comment in the default scheduler to enable these triggers –>
    <!– If a cron base trigger is what you want seee scheduled-jobs-context.xml for examples. –>
   
    <!– Trigger to load poeple –>
    <!– Note you can have more than one initial (context, trigger, import job and export source) set –>
    <!– This would allow you to load people from more than one ldap store –>
   
    <bean id="ldapPeopleTrigger" class="org.alfresco.util.TriggerBean">
        <property name="jobDetail">
            <bean id="ldapPeopleJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
                <property name="jobClass">
                    <value>org.alfresco.repo.importer.ImporterJob</value>
                </property>
                <property name="jobDataAsMap">
                    <map>
                        <entry key="bean">
                            <ref bean="ldapPeopleImport"/>
                        </entry>
                    </map>
                </property>
            </bean>
        </property>
        <!– Start after 30 seconds of starting the repository –>
        <property name="startDelay">
            <value>30000</value>
        </property>
        <!– Repeat every hour –>
        <property name="repeatInterval">
            <value>3600000</value>
        </property>
        <!– Commented out to disable –>
        <property name="scheduler">
            <ref bean="schedulerFactory" />
        </property>       
    </bean>
   
    <bean id="ldapGroupTrigger" class="org.alfresco.util.TriggerBean">
        <property name="jobDetail">
            <bean id="ldapGroupJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
                <property name="jobClass">
                    <value>org.alfresco.repo.importer.ImporterJob</value>
                </property>
                <property name="jobDataAsMap">
                    <map>
                        <entry key="bean">
                            <ref bean="ldapGroupImport"/>
                        </entry>
                    </map>
                </property>
            </bean>
        </property>
        <!– Start after 30 seconds of starting the repository –>
        <property name="startDelay">
            <value>30000</value>
        </property>
        <!– Repeat every hour –>
        <property name="repeatInterval">
            <value>3600000</value>
        </property>
        <!– Commented out to disable –>
        <property name="scheduler">
            <ref bean="schedulerFactory" />
        </property>
    </bean>
   
    <!– The bean that imports xml describing people –>
   
    <bean id="ldapPeopleImport" class="org.alfresco.repo.importer.ExportSourceImporter">
        <property name="importerService">
            <ref bean="importerComponent"/>
        </property>
        <property name="transactionService">
            <ref bean="transactionComponent"/>
        </property>
        <property name="authenticationComponent">
            <ref bean="authenticationComponent"/>
        </property>
        <property name="exportSource">
            <ref bean="ldapPeopleExportSource"/>
        </property>

        <!– The store that contains people - this should not be changed –>
        <property name="storeRef">
            <value>${spaces.store}</value>
        </property>
       
        <!– The location of people nodes within the store defined above - this should not be changed –>
        <property name="path">
            <value>/${system.system_container.childname}/${system.people_container.childname}</value>
        </property>
       
        <!– If true, clear all existing people before import, if false update/add people from the xml –>
        <property name="clearAllChildren">
            <value>false</value>
        </property>
        <property name="nodeService">
            <ref bean="nodeService"/>
        </property>
        <property name="searchService">
            <ref bean="searchService"/>
        </property>
        <property name="namespacePrefixResolver">
            <ref bean="namespaceService"/>
        </property>
       
       
        <property name="caches">
            <set>
                <ref bean="permissionsAccessCache"/>
            </set>
        </property>
    </bean>
   
    <!– The bean that imports xml descibing groups –>
   
    <bean id="ldapGroupImport" class="org.alfresco.repo.importer.ExportSourceImporter">
        <property name="importerService">
            <ref bean="importerComponent"/>
        </property>
        <property name="transactionService">
            <ref bean="transactionComponent"/>
        </property>
        <property name="authenticationComponent">
            <ref bean="authenticationComponent"/>
        </property>
        <property name="exportSource">
            <ref bean="ldapGroupExportSource"/>
        </property>
        <!– The store that contains group information - this should not be changed –>
        <property name="storeRef">
            <value>${alfresco_user_store.store}</value>
        </property>
       
        <!– The location of group information in the store above - this should not be changed –>
        <property name="path">
            <value>/${alfresco_user_store.system_container.childname}/${alfresco_user_store.authorities_container.childname}</value>
        </property>
       
        <!– If true, clear all existing groups before import, if false update/add groups from the xml –>
        <property name="clearAllChildren">
            <value>true</value>
        </property>
        <property name="nodeService">
            <ref bean="nodeService"/>
        </property>
        <property name="searchService">
            <ref bean="searchService"/>
        </property>
        <property name="namespacePrefixResolver">
            <ref bean="namespaceService"/>
        </property>
       
        <!– caches to clear on import of groups –>
        <property name="caches">
            <set>
                <ref bean="userToAuthorityCache"/>
                <ref bean="permissionsAccessCache"/>
            </set>
        </property>
       
        <!– userToAuthorityCache –>
    </bean>
   
</beans>

andy
Champ on-the-rise
Champ on-the-rise
Hi

See
https://www.microsoft.com/technet/prodtechnol/exchange/2003/insider/ldapquery.mspx
for some basics about ldap queries or one of the many books.

When I start Alfresco I still see this in the log and no users get imported:

14:34:20,446 DEBUG [authentication.ldap.LDAPGroupExportSource] Found 0
14:34:20,446 DEBUG [authentication.ldap.LDAPGroupExportSource] Top 0
14:34:20,446 DEBUG [authentication.ldap.LDAPGroupExportSource] Secondary 0

…is about group import doing nothing, which is what I expect from what you have described.

Users are pulled in a bit later and should generate some different debug output about loading people.

You can limit the users you find by extending the search filter. Your example looks OK.

It looks like people should be imported after 30 seconds from your settings, and then every hour.

I would not recommend simple authentication for production.

The identifier the user types in at the login prompt has to match the user id defined in the LDAP group and user imports.

Regards

Andy

richard_tsai
Champ in-the-making
Champ in-the-making
Hi! Andy,
   Our Alfresco is able to sync with AD sever, however, we found a lots of users and groups missing. That should be caused by MaxPageSize(Default is 1000) setting in AD sever. However, to avoid DoS attack, our AD administrator hopes us to solve this in ap level. I wonder whether there is any parameter for this.

   http://support.microsoft.com/kb/315071/en-us

richard_tsai
Champ in-the-making
Champ in-the-making
Hi!
   I have modified some java code under org.alfresco.repo.security.authentication.ldap.*. I replaced all InitialDirContext by using its subclass, InitialLdapContext.class for PagedResultControl. The following is my LDAPPersonExportSource.java. For the other .java under the package, I just replaced InitialDirContext to InitialLdapContext.

      I've created an Alfresco JIRA. If some of you are interested, please refer to http://issues.alfresco.com/browse/AR-1147.


    public void generateExport(XMLWriter writer)
    {
        QName nodeUUID = QName.createQName("sys:node-uuid", namespaceService);

        Collection<String> prefixes = namespaceService.getPrefixes();
        QName childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService);

        try
        {
            AttributesImpl attrs = new AttributesImpl();
            attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName
                    .toPrefixString(), null, ContentModel.TYPE_PERSON.toPrefixString(namespaceService));

            writer.startDocument();

            for (String prefix : prefixes)
            {
                if (!prefix.equals("xml"))
                {
                    String uri = namespaceService.getNamespaceURI(prefix);
                    writer.startPrefixMapping(prefix, uri);
                }
            }

            writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view",
                    NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl());

            //InitialDirContext ctx = null;
            InitialLdapContext ctx = null;
            try
            {
                ctx = ldapInitialContextFactory.getDefaultIntialDirContext();

                // Authentication has been successful.
                // Set the current user, they are now authenticated.

                SearchControls userSearchCtls = new SearchControls();
                userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
                userSearchCtls.setCountLimit(Integer.MAX_VALUE);
               
                // richard_tsai Customization
                int Start = 0;
                int Step = 1000;
                int Finish = 999;
                boolean Finished = false;
                String Range;
                byte[] cookie = null;

                // new added  
                Control[] ctls = new Control[]{new PagedResultsControl(Step, Control.CRITICAL)};
                ctx.setRequestControls(ctls);
               
           while (!Finished) {      
                    NamingEnumeration searchResults = ctx.search(searchBase, personQuery, userSearchCtls);

                    int batchCount = 0;
                    while (searchResults.hasMoreElements())
                    {
                        // richard_tsai Customized
                        batchCount++;
                        // richard_tsai Customized
                       
                        SearchResult result = (SearchResult) searchResults.next();
                        Attributes attributes = result.getAttributes();
                        Attribute uidAttribute = attributes.get(userIdAttributeName);
                        if (uidAttribute == null)
                        {
                            if(errorOnMissingUID)
                            {
                            throw new ExportSourceImporterException(
                                    "User returned by user search does not have mandatory user id attribute " + attributes);
                            }
                            else
                            {
                                s_logger.warn("User returned by user search does not have mandatory user id attribute " + attributes);
                                continue;
                            }
                        }
                        String uid = (String) uidAttribute.get(0);
                        if (s_logger.isDebugEnabled())
                        {
                            s_logger.debug("Adding user for " + uid);
                        }


                        writer.startElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON
                                .getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService), attrs);

                        // permissions

                        // owner

                        writer.startElement(ContentModel.ASPECT_OWNABLE.getNamespaceURI(), ContentModel.ASPECT_OWNABLE
                                .getLocalName(), ContentModel.ASPECT_OWNABLE.toPrefixString(namespaceService),
                                new AttributesImpl());

                        writer.endElement(ContentModel.ASPECT_OWNABLE.getNamespaceURI(), ContentModel.ASPECT_OWNABLE
                                .getLocalName(), ContentModel.ASPECT_OWNABLE.toPrefixString(namespaceService));

                        writer.startElement(ContentModel.PROP_OWNER.getNamespaceURI(), ContentModel.PROP_OWNER
                                .getLocalName(), ContentModel.PROP_OWNER.toPrefixString(namespaceService),
                                new AttributesImpl());

                        writer.characters(uid.toCharArray(), 0, uid.length());

                        writer.endElement(ContentModel.PROP_OWNER.getNamespaceURI(),
                                ContentModel.PROP_OWNER.getLocalName(), ContentModel.PROP_OWNER
                                        .toPrefixString(namespaceService));

                        for (String key : attributeMapping.keySet())
                        {
                            QName keyQName = QName.createQName(key, namespaceService);

                            writer.startElement(keyQName.getNamespaceURI(), keyQName.getLocalName(), keyQName
                                    .toPrefixString(namespaceService), new AttributesImpl());

                            // cater for null
                            String attributeName = attributeMapping.get(key);
                            if (attributeName != null)
                            {
                                Attribute attribute = attributes.get(attributeName);
                                if (attribute != null)
                                {
                                    String value = (String) attribute.get(0);
                                    if (value != null)
                                    {
                                        writer.characters(value.toCharArray(), 0, value.length());
                                    }
                                }
                                else
                                {
                                    String defaultValue = attributeDefaults.get(key);
                                    if(defaultValue != null)
                                    {
                                        writer.characters(defaultValue.toCharArray(), 0, defaultValue.length());
                                    }
                                }
                            }
                            else
                            {
                                String defaultValue = attributeDefaults.get(key);
                                if(defaultValue != null)
                                {
                                    writer.characters(defaultValue.toCharArray(), 0, defaultValue.length());
                                }
                            }

                            writer.endElement(keyQName.getNamespaceURI(), keyQName.getLocalName(), keyQName
                                    .toPrefixString(namespaceService));
                        }

                        if (personService.personExists(uid))
                        {
                            String uguid = personService.getPerson(uid).getId();

                            writer.startElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID
                                    .toPrefixString(namespaceService), new AttributesImpl());

                            writer.characters(uguid.toCharArray(), 0, uguid.length());

                            writer.endElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID
                                    .toPrefixString(namespaceService));
                        }
                        writer.endElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON
                                .getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService));

                    }
                   
                    // ========================================
                    // richard_tsai Customized
                    // ========================================
                    if (batchCount<Step) Finished = true;
                    cookie = parseControls(ctx.getResponseControls());
                    ctx.setRequestControls(new Control[]{new PagedResultsControl(Step, cookie, Control.CRITICAL) });
                    // ========================================
                    // End of richard_tsai Customized
                    // ========================================
                }   
            }
            catch (NamingException e)
            {
                throw new ExportSourceImporterException("Failed to import people.", e);
            }
            // richard_tsai Customized
            catch (IOException ioe)
            {
                throw new ExportSourceImporterException("Failed to import people.", ioe);
            }
            // End of richard_tsai Customized
            finally
            {
                if (ctx != null)
                {
                    try
                    {
                        ctx.close();
                    }
                    catch (NamingException e)
                    {
                        throw new ExportSourceImporterException("Failed to import people.", e);
                    }
                }
            }

            for (String prefix : prefixes)
            {
                if (!prefix.equals("xml"))
                {
                    writer.endPrefixMapping(prefix);
                }
            }

            writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX
                    + ":" + "view");

            writer.endDocument();
        }
        catch (SAXException e)
        {
            throw new ExportSourceImporterException("Failed to create file for import.", e);
        }
    }

    // Added static mothod
    static byte[] parseControls(Control[] controls) throws NamingException {

            byte[] cookie = null;

            if (controls != null) {

                    for (int i = 0; i < controls.length; i++) {
                            if (controls[i] instanceof PagedResultsResponseControl) {
                                    PagedResultsResponseControl prrc = (PagedResultsResponseControl)controls[i];
                                    cookie = prrc.getCookie();
                            }
                    }
            }

            return (cookie == null) ? new byte[0] : cookie;
    }   

andy
Champ on-the-rise
Champ on-the-rise
Hi

Cool, thanks for that.
I will look at adding this or something similar.

You need to go through a few hoops to make contributions.
See http://wiki.alfresco.com/wiki/Source_Code.

In particular, http://dev.alfresco.com/resource/AlfrescoContributionAgreementv2.pdf

Regards

Andy

schambon
Champ in-the-making
Champ in-the-making
Hi,

Reviving this thread – we are in the exact same situation. Is there any chance of this patch or something functionally similar being integrated into core Alfresco? I'd hate to have to hack the software myself (and support the hack through version upgrades etc.)

Best regards,
Sylvain.