Index: kernel/classes/datatypes/ezuser/ezldapuser.php
===================================================================
--- kernel/classes/datatypes/ezuser/ezldapuser.php	(revision 24102)
+++ kernel/classes/datatypes/ezuser/ezldapuser.php	(working copy)
@@ -554,6 +554,13 @@
                 {
                     if ( $LDAPUserGroupAttributeType )
                     {
+                        // Should we create user groups that are specified in LDAP, but not found in eZ Publish?
+                        $createMissingGroups = ( $LDAPIni->variable( 'LDAPSettings', 'LDAPCreateMissingGroups' ) === 'enabled' );
+                        if ( $LDAPIni->variable( 'LDAPSettings', 'LDAPGroupRootNodeId' ) !== '' )
+                            $parentNodeID = $LDAPIni->variable( 'LDAPSettings', 'LDAPGroupRootNodeId' );
+                        else
+                            $parentNodeID = 5;
+
                         $groupAttributeCount = $info[0][$LDAPUserGroupAttribute]['count'];
                         if ( $LDAPUserGroupAttributeType == "name" )
                         {
@@ -567,24 +574,10 @@
                                 {
                                     $groupName = $info[0][$LDAPUserGroupAttribute][$i];
                                 }
-                                if ( $groupName != null )
-                                {
-                                    $groupQuery = "SELECT ezcontentobject_tree.node_id
-                                                     FROM ezcontentobject, ezcontentobject_tree
-                                                    WHERE ezcontentobject.name like '$groupName'
-                                                      AND ezcontentobject.id=ezcontentobject_tree.contentobject_id
-                                                      AND ezcontentobject.contentclass_id=$userGroupClassID";
-                                    $groupObject = $db->arrayQuery( $groupQuery );
 
-                                    if ( count( $groupObject ) > 0 and $i == 0 )
-                                    {
-                                        $defaultUserPlacement = $groupObject[0]['node_id'];
-                                    }
-                                    else if ( count( $groupObject ) > 0 )
-                                    {
-                                        $extraNodeAssignments[] = $groupObject[0]['node_id'];
-                                    }
-                                }
+                                // Save group node id to either defaultUserPlacement or extraNodeAssignments
+                                self::getNodeAssignmentsForGroupName( $groupName, ($i == 0), $defaultUserPlacement, $extraNodeAssignments,
+                                                                      $createMissingGroups, $parentNodeID );
                             }
                         }
                         else if ( $LDAPUserGroupAttributeType == "id" )
@@ -599,28 +592,35 @@
                                 {
                                     $groupID = $info[0][$LDAPUserGroupAttribute][$i];
                                 }
+                                $groupName = "LDAP $groupID";
 
-                                if ( $groupID != null )
+                                // Save group node id to either defaultUserPlacement or extraNodeAssignments
+                                self::getNodeAssignmentsForGroupName( $groupName, ($i == 0), $defaultUserPlacement, $extraNodeAssignments,
+                                                                      $createMissingGroups, $parentNodeID );
+                            }
+                        }
+                        else if ( $LDAPUserGroupAttributeType == "dn" )
+                        {
+                            for ( $i = 0; $i < $groupAttributeCount; $i++ )
+                            {
+                                $groupDN = $info[0][$LDAPUserGroupAttribute][$i];
+                                $groupName = self::getGroupNameByDN( $ds, $groupDN );
+
+                                if ( $groupName )
                                 {
-                                    $groupName = "LDAP " . $groupID;
-                                    $groupQuery = "SELECT ezcontentobject_tree.node_id
-                                                     FROM ezcontentobject, ezcontentobject_tree
-                                                    WHERE ezcontentobject.name like '$groupName'
-                                                      AND ezcontentobject.id=ezcontentobject_tree.contentobject_id
-                                                      AND ezcontentobject.contentclass_id=$userGroupClassID";
-                                    $groupObject = $db->arrayQuery( $groupQuery );
-
-                                    if ( count( $groupObject ) > 0 and $i == 0 )
-                                    {
-                                        $defaultUserPlacement = $groupObject[0]['node_id'];
-                                    }
-                                    else if ( count( $groupObject ) > 0 )
-                                    {
-                                        $extraNodeAssignments[] = $groupObject[0]['node_id'];
-                                    }
+                                    // Save group node id to either defaultUserPlacement or extraNodeAssignments
+                                    self::getNodeAssignmentsForGroupName( $groupName, ($i == 0), $defaultUserPlacement, $extraNodeAssignments,
+                                                                          $createMissingGroups, $parentNodeID );
                                 }
                             }
                         }
+                        else
+                        {
+                            eZDebug::writeError( "Bad LDAPUserGroupAttributeType '$LDAPUserGroupAttributeType'. It must be either 'name', 'id' or 'dn'.",
+                                                 __METHOD__ );
+                            $user = false;
+                            return $user;
+                        }
                     }
                 }
 
@@ -1288,7 +1288,100 @@
         return true;
     }
 
+    /*
+        Static method, for internal usage only
+        Finds a user group with the given name and remembers the node ID for it. The first match is always used.
+        If $createMissingGroups is true, it will create any groups it does not find.
+    */
+    static function getNodeAssignmentsForGroupName( $groupName,
+                                $isFirstGroupAssignment,
+                                &$defaultUserPlacement,
+                                &$extraNodeAssignments,
+                                $createMissingGroups,
+                                $parentNodeID )
+    {
+        if ( !is_string( $groupName ) or $groupName === '' )
+        {
+            eZDebug::writeError( 'The groupName must be a non empty string. Bad groupName: ' . $groupName, __METHOD__ );
+            return;
+        }
 
+        $db = eZDB::instance();
+        $ini = eZINI::instance();
+        $userGroupClassID = $ini->variable( "UserSettings", "UserGroupClassID" );
+
+        $groupQuery = "SELECT ezcontentobject_tree.node_id
+                       FROM ezcontentobject, ezcontentobject_tree
+                       WHERE ezcontentobject.name like '$groupName'
+                       AND ezcontentobject.id = ezcontentobject_tree.contentobject_id
+                       AND ezcontentobject.contentclass_id = $userGroupClassID";
+        $groupRows = $db->arrayQuery( $groupQuery );
+
+        if ( count( $groupRows ) > 0 and $isFirstGroupAssignment )
+        {
+            $defaultUserPlacement = $groupRows[0]['node_id'];
+            return;
+        }
+        else if ( count( $groupRows ) > 0 )
+        {
+            $extraNodeAssignments[] = $groupRows[0]['node_id'];
+            return;
+        }
+
+        // Should we create user groups that are specified in LDAP, but not found in eZ Publish?
+        if ( !$createMissingGroups )
+        {
+            return;
+        }
+
+        $newNodeIDs = self::publishNewUserGroup( array( $parentNodeID ), array( 'name' => $groupName ) );
+
+        if ( count( $newNodeIDs ) > 0 and $isFirstGroupAssignment )
+        {
+            $defaultUserPlacement = $newNodeIDs[0]; // We only supplied one parent to publishNewUserGroup(), so there is only one node
+        }
+        else if ( count( $newNodeIDs ) > 0 )
+        {
+            $extraNodeAssignments[] = $newNodeIDs[0]; // We only supplied one parent to publishNewUserGroup(), so there is only one node
+        }
+    }
+
+    /*
+        Static method, for internal usage only
+        Fetch the LDAP group object given by the DN, and return its name
+    */
+    static function getGroupNameByDN( $ds, $groupDN )
+    {
+        $LDAPIni = eZINI::instance( 'ldap.ini' );
+        $LDAPGroupNameAttribute = $LDAPIni->variable( 'LDAPSettings', 'LDAPGroupNameAttribute' );
+
+        // First, try to see if the $LDAPGroupNameAttribute is contained within the DN, in that case we can read it directly
+        $groupDNParts = ldap_explode_dn( $groupDN, 0 );
+        list( $firstName, $firstValue ) = explode( '=', $groupDNParts[0] );
+
+        if ( $firstName = $LDAPGroupNameAttribute ) // Read the group name attribute directly from the group DN
+        {
+            $groupName = $firstValue;
+        }
+        else // Read the LDAP group object, get the group name attribute from it
+        {
+            $sr = ldap_read( $ds, $groupDN, "($LDAPGroupNameAttribute=*)", array( $LDAPGroupNameAttribute ) );
+            $info = ldap_get_entries( $ds, $sr );
+
+            if ( $info['count'] < 1 or $info[0]['count'] < 1 )
+            {
+                eZDebug::writeWarning( 'LDAP group not found, tried DN: ' . $groupDN, __METHOD__ );
+                return false;
+            }
+
+            $groupName = $info[0][$LDAPGroupNameAttribute];
+            if ( is_array( $groupName ) ) // This may be a string or an array of strings, depending on LDAP setup
+                $groupName = $groupName[0]; // At least one must exist, since we specified it in the search filter
+        }
+
+        return $groupName;
+    }
+
 }
 
 ?>
Index: settings/ldap.ini
===================================================================
--- settings/ldap.ini	(revision 24102)
+++ settings/ldap.ini	(working copy)
@@ -42,15 +42,20 @@
 # Group mapping settings:
 # Root node id where LDAP groups are created, node id: 5 is used if blank
 LDAPGroupRootNodeId=5
-# Possible values: UseGroupAttribute (old style group assignig using LDAPUserGroupAttribute setting),
-# SimpleMapping (using LDAPUserGroupMap array for name-to-name group mapping) or GetGroupsTree
+# Possible values: UseGroupAttribute (old style group assigning using LDAPUserGroupAttribute setting),
+# SimpleMapping (using LDAPUserGroupMap array for name-to-name group mapping), and GetGroupsTree
+# (automatic name-to-name group mapping, creating groups as needed)
 LDAPGroupMappingType=UseGroupAttribute
+# When 'LDAPGroupMappingType' is set to 'UseGroupAttribute', this setting decides whether groups that
+# are not found in eZ Publish, should be created.
+LDAPCreateMissingGroups=disabled
 # Base LDAP dn which should be used to fetch user group objects from LDAP
 LDAPGroupBaseDN=
 # LDAP user group class
 LDAPGroupClass=exampleGroupDAClassName
 # Attribute which should be used to obtain name of an LDAP group
 # Required then 'LDAPGroupMappingType' is set to 'GetGroupsTree' or 'SimpleMapping'
+# and when 'LDAPGroupMappingType' is set to 'UseGroupAttribute' and 'LDAPUserGroupAttributeType' is 'dn'
 LDAPGroupNameAttribute=cn
 # Attribute of LDAP user which should be used to obtain groups which user(group) belongs to.
 # Required then 'LDAPGroupMappingType' is set to 'GetGroupsTree' or 'SimpleMapping'
@@ -61,10 +66,14 @@
 # used then 'LDAPGroupMappingType' is set to 'SimpleMapping'
 LDAPUserGroupMap[]
 
-# LDAP attribute type for user group. Could be name or id
+# LDAP attribute type for user group. Could be name, id or dn
 LDAPUserGroupAttributeType=name
 # LDAP attribute for user group. For example, employeetype. If specified, LDAP users
 # will be saved under the same group as in LDAP server.
+# Depends on the value of LDAPUserGroupAttributeType:
+# name: attribute holding name of group
+# id: attribute holding nummeric id of group
+# dn: attribute holding DN of group
 LDAPUserGroupAttribute=employeetype
 # LDAP attribute for First name. Normally, givenname
 LDAPFirstNameAttribute=givenname
Index: tests/tests/kernel/datatypes/ezuser/ezldapuser_test.php
===================================================================
--- tests/tests/kernel/datatypes/ezuser/ezldapuser_test.php	(revision 24102)
+++ tests/tests/kernel/datatypes/ezuser/ezldapuser_test.php	(working copy)
@@ -131,6 +131,7 @@
 
         $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPGroupRootNodeId', $this->mainGroupNodeId );
         $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPGroupMappingType', 'UseGroupAttribute' );
+        $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPCreateMissingGroups', 'disabled' );
         $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPGroupBaseDN', 'dc--phpuc,dc--ez,dc--no' );
         $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPGroupClass', 'organizationalUnit' );
         $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPGroupNameAttribute', 'ou' );
@@ -177,6 +178,7 @@
         }
 
         $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPGroupMappingType', 'UseGroupAttribute' );
+        $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPCreateMissingGroups', 'disabled' );
         $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPUserGroupAttributeType', 'name' );
         $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPUserGroupAttribute', 'ou' );
 
@@ -207,6 +209,7 @@
         }
 
         $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPGroupMappingType', 'UseGroupAttribute' );
+        $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPCreateMissingGroups', 'disabled' );
         $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPUserGroupAttributeType', 'name' );
         $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPUserGroupAttribute', 'ou' );
 
@@ -219,6 +222,75 @@
     }
 
     /**
+     * Test scenario for LDAP login using UseGroupAttribute with LDAPUserGroupAttributeType=dn
+     * for issue #: ...
+     *
+     * Test Outline
+     * ------------
+     * 1. Set correct LDAPGroupMappingType
+     * 2. Login with username and password
+     * 3. Check parent nodes of user object
+     * 4. Login with username and password for another user
+     * 5. Check parent nodes of user object
+     *
+     * @result:
+     *   ##The first user is placed in the newly created Empire and Sith groups
+     *   ##(the following assertions are not executed)
+     * @expected:
+     *   The first user is placed in the existing StarWars and Rogues groups.
+     *   The second user has three node assignments.
+     *   The first two assignments are the existing StarWars and RebelAlliance groups.
+     *   The third assignment is the newly created Jedi group.
+     * @link http://issues.ez.no/ ...
+     */
+    public function testLoginUserUseGroupAttributeDN()
+    {
+        if ( !self::ldapIsEnabled() )
+        {
+            $this->markTestSkipped( 'LDAP is not loaded' );
+            return;
+        }
+
+        $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPGroupMappingType', 'UseGroupAttribute' );
+        $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPCreateMissingGroups', 'enabled' );
+        $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPGroupNameAttribute', 'ou' );
+        $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPUserGroupAttributeType', 'dn' );
+        $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPUserGroupAttribute', 'seeAlso' );
+        $this->ldapINI->setVariable( 'LDAPSettings', 'KeepGroupAssignment', 'disabled' );
+
+        $user = eZLDAPUser::loginUser( 'boba.fett', 'ihatesarlacs' );
+        $contentObject = $user->attribute( 'contentobject' );
+        self::assertEquals( array( $this->starWarsGroupNodeId, $this->rogueGroupNodeId ),
+                            $contentObject->attribute( 'parent_nodes' ) );
+
+        // Change the root node id, in order to create the Jedi group under the StarWars group
+        $this->ldapINI->setVariable( 'LDAPSettings', 'LDAPGroupRootNodeId', $this->starWarsGroupNodeId );
+
+        // Try a user with a group that is not in ezp
+        $user = eZLDAPUser::loginUser( 'yoda', 'dagobah4eva' );
+        $contentObject = $user->attribute( 'contentobject' );
+        $parentNodeIDs = $contentObject->attribute( 'parent_nodes' );
+
+        // The user should be assigned to 3 groups
+        self::assertEquals( 3, count( $parentNodeIDs ) );
+
+        // The StarWars group already exists
+        $node0 = eZContentObjectTreeNode::fetch( $parentNodeIDs[0] );
+        self::assertEquals( array( $this->starWarsGroupNodeId, $this->mainGroupNodeId, 'StarWars' ),
+                            array( $parentNodeIDs[0], $node0->attribute( 'parent_node_id' ), $node0->attribute( 'name' ) ) );
+
+        // The RebelAlliance group already exists
+        $node1 = eZContentObjectTreeNode::fetch( $parentNodeIDs[1] );
+        self::assertEquals( array( $this->rebelGroupNodeId, $this->starWarsGroupNodeId, 'RebelAlliance' ),
+                            array( $parentNodeIDs[1], $node1->attribute( 'parent_node_id' ), $node1->attribute( 'name' ) ) );
+
+        // The Jedi group is created by the login handler
+        $node2 = eZContentObjectTreeNode::fetch( $parentNodeIDs[2] );
+        self::assertEquals( array( $this->starWarsGroupNodeId, 'Jedi' ),
+                            array( $node2->attribute( 'parent_node_id' ), $node2->attribute( 'name' ) ) );
+    }
+
+    /**
      * Test scenario for LDAP login using SimpleMapping
      *
      * Test Outline
Index: doc/features/4.3/ldap_login_handler.txt
===================================================================
--- doc/features/4.3/ldap_login_handler.txt	(revision 0)
+++ doc/features/4.3/ldap_login_handler.txt	(revision 0)
@@ -0,0 +1,21 @@
+LDAP login handler improvements
+===============================
+
+New LDAPUserGroupAttributeType: dn
+----------------------------------
+
+When this is set, the LDAPUserGroupAttribute should be set to an LDAP
+attribute that holds the DN of the group(s) that the user belongs to. If the
+user belongs to multiple groups, then this attribute should be set multiple
+times in the LDAP user object - it should not contain multiple DNs. (This is
+how LDAP attributes are normally used.) The 'dn' value comes in addition to
+the existing allowed values 'name' and 'id', which are not changed.
+
+UseGroupAttribute mode can now create groups
+--------------------------------------------
+
+Previously when LDAPGroupMappingType=UseGroupAttribute, no user groups would
+be created. If the indicated group(s) were not found, the user(s) would be
+placed in the default group. With the addition of the LDAPCreateMissingGroups
+setting this is now supported. When it is enabled, missing groups will be
+created. It is disabled by default, for backwards compatibility.
