diff --git a/apps/user_ldap/group_ldap.php b/apps/user_ldap/group_ldap.php
index 1a35691be85fe3b812607c878cb8c2f2e5efd4e3..0d3a70575bae1e46922b5643242fe40471c3dd2f 100644
--- a/apps/user_ldap/group_ldap.php
+++ b/apps/user_ldap/group_ldap.php
@@ -50,20 +50,29 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 		if(!$this->enabled) {
 			return false;
 		}
-		if($this->access->connection->isCached('inGroup'.$uid.':'.$gid)) {
-			return $this->access->connection->getFromCache('inGroup'.$uid.':'.$gid);
+		$cacheKey = 'inGroup'.$uid.':'.$gid;
+		if($this->access->connection->isCached($cacheKey)) {
+			return $this->access->connection->getFromCache($cacheKey);
 		}
-		$dn_user = $this->access->username2dn($uid);
-		$dn_group = $this->access->groupname2dn($gid);
+
+		$userDN = $this->access->username2dn($uid);
+		$groupDN = $this->access->groupname2dn($gid);
 		// just in case
-		if(!$dn_group || !$dn_user) {
-			$this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, false);
+		if(!$groupDN || !$userDN) {
+			$this->access->connection->writeToCache($cacheKey, false);
 			return false;
 		}
+
+		//check primary group first
+		if($gid === $this->getUserPrimaryGroup($userDN)) {
+			$this->access->connection->writeToCache($cacheKey, true);
+			return true;
+		}
+
 		//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
-		$members = array_keys($this->_groupMembers($dn_group));
+		$members = array_keys($this->_groupMembers($groupDN));
 		if(!$members) {
-			$this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, false);
+			$this->access->connection->writeToCache($cacheKey, false);
 			return false;
 		}
 
@@ -82,8 +91,8 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 			$members = $dns;
 		}
 
-		$isInGroup = in_array($dn_user, $members);
-		$this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, $isInGroup);
+		$isInGroup = in_array($userDN, $members);
+		$this->access->connection->writeToCache($cacheKey, $isInGroup);
 
 		return $isInGroup;
 	}
@@ -91,6 +100,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 	/**
 	 * @param string $dnGroup
 	 * @param array|null &$seen
+	 * @return array|mixed|null
 	 */
 	private function _groupMembers($dnGroup, &$seen = null) {
 		if ($seen === null) {
@@ -125,6 +135,125 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 		return $allMembers;
 	}
 
+	/**
+	 * translates a primary group ID into an ownCloud internal name
+	 * @param string $gid as given by primaryGroupID on AD
+	 * @param string $dn a DN that belongs to the same domain as the group
+	 * @return string|bool
+	 */
+	public function primaryGroupID2Name($gid, $dn) {
+		$cacheKey = 'primaryGroupIDtoName';
+		if($this->access->connection->isCached($cacheKey)) {
+			$groupNames = $this->access->connection->getFromCache($cacheKey);
+			if(isset($groupNames[$gid])) {
+				return $groupNames[$gid];
+			}
+		}
+
+		$domainObjectSid = $this->access->getSID($dn);
+		if($domainObjectSid === false) {
+			return false;
+		}
+
+		//we need to get the DN from LDAP
+		$filter = $this->access->combineFilterWithAnd(array(
+			$this->access->connection->ldapGroupFilter,
+			'objectsid=' . $domainObjectSid . '-' . $gid
+		));
+		$result = $this->access->searchGroups($filter, array('dn'), 1);
+		if(empty($result)) {
+			return false;
+		}
+		$dn = $result[0];
+
+		//and now the group name
+		//NOTE once we have separate ownCloud group IDs and group names we can
+		//directly read the display name attribute instead of the DN
+		$name = $this->access->dn2groupname($dn);
+
+		$this->access->connection->writeToCache($cacheKey, $name);
+
+		return $name;
+	}
+
+	/**
+	 * returns the entry's primary group ID
+	 * @param string $dn
+	 * @param string $attribute
+	 * @return string|bool
+	 */
+	private function getEntryGroupID($dn, $attribute) {
+		$value = $this->access->readAttribute($dn, $attribute);
+		if(is_array($value) && !empty($value)) {
+			return $value[0];
+		}
+		return false;
+	}
+
+	/**
+	 * returns the group's primary ID
+	 * @param string $dn
+	 * @return string|bool
+	 */
+	public function getGroupPrimaryGroupID($dn) {
+		return $this->getEntryGroupID($dn, 'primaryGroupToken');
+	}
+
+	/**
+	 * returns the user's primary group ID
+	 * @param string $dn
+	 * @return string|bool
+	 */
+	public function getUserPrimaryGroupIDs($dn) {
+		return $this->getEntryGroupID($dn, 'primaryGroupID');
+	}
+
+	/**
+	 * returns a list of users that have the given group as primary group
+	 *
+	 * @param string $groupDN
+	 * @param $limit
+	 * @param int $offset
+	 * @return string[]
+	 */
+	public function getUsersInPrimaryGroup($groupDN, $limit = -1, $offset = 0) {
+		$groupID = $this->getGroupPrimaryGroupID($groupDN);
+		if($groupID === false) {
+			return array();
+		}
+
+		$filter = $this->access->combineFilterWithAnd(array(
+			$this->access->connection->ldapUserFilter,
+			'primaryGroupID=' . $groupID
+		));
+
+		$users = $this->access->fetchListOfUsers(
+			$filter,
+			array($this->access->connection->ldapUserDisplayName, 'dn'),
+			$limit,
+			$offset
+		);
+
+		return $users;
+	}
+
+	/**
+	 * gets the primary group of a user
+	 * @param string $dn
+	 * @return string
+	 */
+	public function getUserPrimaryGroup($dn) {
+		$groupID = $this->getUserPrimaryGroupIDs($dn);
+		if($groupID !== false) {
+			$groupName = $this->primaryGroupID2Name($groupID, $dn);
+			if($groupName !== false) {
+				return $groupName;
+			}
+		}
+
+		return false;
+	}
+
 	/**
 	 * Get all groups a user belongs to
 	 * @param string $uid Name of the user
@@ -161,7 +290,14 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 		}
 
 		$groups = array_values($this->getGroupsByMember($uid));
-		$groups = array_unique($this->access->ownCloudGroupNames($groups), SORT_LOCALE_STRING);
+		$groups = $this->access->ownCloudGroupNames($groups);
+
+		$primaryGroup = $this->getUserPrimaryGroup($userDN);
+		if($primaryGroup !== false) {
+			$groups[] = $primaryGroup;
+		}
+
+		$groups = array_unique($groups, SORT_LOCALE_STRING);
 		$this->access->connection->writeToCache($cacheKey, $groups);
 
 		return $groups;
@@ -170,6 +306,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 	/**
 	 * @param string $dn
 	 * @param array|null &$seen
+	 * @return array
 	 */
 	private function getGroupsByMember($dn, &$seen = null) {
 		if ($seen === null) {
@@ -205,6 +342,11 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 
 	/**
 	 * get a list of all users in a group
+	 *
+	 * @param string $gid
+	 * @param string $search
+	 * @param int $limit
+	 * @param int $offset
 	 * @return array with user ids
 	 */
 	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
@@ -214,9 +356,9 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 		if(!$this->groupExists($gid)) {
 			return array();
 		}
-		$cachekey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
+		$cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
 		// check for cache of the exact query
-		$groupUsers = $this->access->connection->getFromCache($cachekey);
+		$groupUsers = $this->access->connection->getFromCache($cacheKey);
 		if(!is_null($groupUsers)) {
 			return $groupUsers;
 		}
@@ -225,7 +367,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 		$groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
 		if(!is_null($groupUsers)) {
 			$groupUsers = array_slice($groupUsers, $offset, $limit);
-			$this->access->connection->writeToCache($cachekey, $groupUsers);
+			$this->access->connection->writeToCache($cacheKey, $groupUsers);
 			return $groupUsers;
 		}
 
@@ -235,14 +377,14 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 		$groupDN = $this->access->groupname2dn($gid);
 		if(!$groupDN) {
 			// group couldn't be found, return empty resultset
-			$this->access->connection->writeToCache($cachekey, array());
+			$this->access->connection->writeToCache($cacheKey, array());
 			return array();
 		}
 
 		$members = array_keys($this->_groupMembers($groupDN));
 		if(!$members) {
-			//in case users could not be retrieved, return empty resultset
-			$this->access->connection->writeToCache($cachekey, array());
+			//in case users could not be retrieved, return empty result set
+			$this->access->connection->writeToCache($cacheKey, array());
 			return array();
 		}
 
@@ -250,7 +392,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 		$isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
 		foreach($members as $member) {
 			if($isMemberUid) {
-				//we got uids, need to get their DNs to 'tranlsate' them to usernames
+				//we got uids, need to get their DNs to 'translate' them to user names
 				$filter = $this->access->combineFilterWithAnd(array(
 					\OCP\Util::mb_str_replace('%uid', $member,
 						$this->access->connection->ldapLoginFilter, 'UTF-8'),
@@ -276,10 +418,16 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 				}
 			}
 		}
+
 		natsort($groupUsers);
 		$this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
 		$groupUsers = array_slice($groupUsers, $offset, $limit);
-		$this->access->connection->writeToCache($cachekey, $groupUsers);
+
+		//and get users that have the group as primary
+		$primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $limit, $offset);
+		$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers));
+
+		$this->access->connection->writeToCache($cacheKey, $groupUsers);
 
 		return $groupUsers;
 	}
@@ -291,32 +439,32 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 	 * @return int|bool
 	 */
 	public function countUsersInGroup($gid, $search = '') {
-		$cachekey = 'countUsersInGroup-'.$gid.'-'.$search;
+		$cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
 		if(!$this->enabled || !$this->groupExists($gid)) {
 			return false;
 		}
-		$groupUsers = $this->access->connection->getFromCache($cachekey);
+		$groupUsers = $this->access->connection->getFromCache($cacheKey);
 		if(!is_null($groupUsers)) {
 			return $groupUsers;
 		}
 
 		$groupDN = $this->access->groupname2dn($gid);
 		if(!$groupDN) {
-			// group couldn't be found, return empty resultset
-			$this->access->connection->writeToCache($cachekey, false);
+			// group couldn't be found, return empty result set
+			$this->access->connection->writeToCache($cacheKey, false);
 			return false;
 		}
 
 		$members = array_keys($this->_groupMembers($groupDN));
 		if(!$members) {
-			//in case users could not be retrieved, return empty resultset
-			$this->access->connection->writeToCache($cachekey, false);
+			//in case users could not be retrieved, return empty result set
+			$this->access->connection->writeToCache($cacheKey, false);
 			return false;
 		}
 
 		if(empty($search)) {
 			$groupUsers = count($members);
-			$this->access->connection->writeToCache($cachekey, $groupUsers);
+			$this->access->connection->writeToCache($cacheKey, $groupUsers);
 			return $groupUsers;
 		}
 		$isMemberUid =
@@ -334,7 +482,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 		$groupUsers = array();
 		foreach($members as $member) {
 			if($isMemberUid) {
-				//we got uids, need to get their DNs to 'tranlsate' them to usernames
+				//we got uids, need to get their DNs to 'translate' them to user names
 				$filter = $this->access->combineFilterWithAnd(array(
 					\OCP\Util::mb_str_replace('%uid', $member,
 						$this->access->connection->ldapLoginFilter, 'UTF-8'),
@@ -359,11 +507,19 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 			}
 		}
 
+		//and get users that have the group as primary
+		$primaryUsers = $this->getUsersInPrimaryGroup($groupDN);
+		$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers));
+
 		return count($groupUsers);
 	}
 
 	/**
 	 * get a list of all groups
+	 *
+	 * @param string $search
+	 * @param $limit
+	 * @param int $offset
 	 * @return array with group names
 	 *
 	 * Returns a list with all groups (used by getGroups)
@@ -372,11 +528,11 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 		if(!$this->enabled) {
 			return array();
 		}
-		$cachekey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
+		$cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
 
 		//Check cache before driving unnecessary searches
-		\OCP\Util::writeLog('user_ldap', 'getGroups '.$cachekey, \OCP\Util::DEBUG);
-		$ldap_groups = $this->access->connection->getFromCache($cachekey);
+		\OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, \OCP\Util::DEBUG);
+		$ldap_groups = $this->access->connection->getFromCache($cacheKey);
 		if(!is_null($ldap_groups)) {
 			return $ldap_groups;
 		}
@@ -397,26 +553,30 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 				$offset);
 		$ldap_groups = $this->access->ownCloudGroupNames($ldap_groups);
 
-		$this->access->connection->writeToCache($cachekey, $ldap_groups);
+		$this->access->connection->writeToCache($cacheKey, $ldap_groups);
 		return $ldap_groups;
 	}
 
 	/**
 	 * get a list of all groups using a paged search
+	 *
+	 * @param string $search
+	 * @param int $limit
+	 * @param int $offset
 	 * @return array with group names
 	 *
 	 * Returns a list with all groups
-   	 * Uses a paged search if available to override a
-   	 * server side search limit.
-   	 * (active directory has a limit of 1000 by default)
+	 * Uses a paged search if available to override a
+	 * server side search limit.
+	 * (active directory has a limit of 1000 by default)
 	 */
 	public function getGroups($search = '', $limit = -1, $offset = 0) {
 		if(!$this->enabled) {
 			return array();
 		}
-		$pagingsize = $this->access->connection->ldapPagingSize;
+		$pagingSize = $this->access->connection->ldapPagingSize;
 		if ((! $this->access->connection->hasPagedResultSupport)
-		   	|| empty($pagingsize)) {
+		   	|| empty($pagingSize)) {
 			return $this->getGroupsChunk($search, $limit, $offset);
 		}
 		$maxGroups = 100000; // limit max results (just for safety reasons)
@@ -428,7 +588,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 		$chunkOffset = $offset;
 		$allGroups = array();
 		while ($chunkOffset < $overallLimit) {
-			$chunkLimit = min($pagingsize, $overallLimit - $chunkOffset);
+			$chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
 			$ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
 			$nread = count($ldapGroups);
 			\OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', \OCP\Util::DEBUG);
@@ -445,6 +605,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
 
 	/**
 	 * @param string $group
+	 * @return bool
 	 */
 	public function groupMatchesFilter($group) {
 		return (strripos($group, $this->groupSearch) !== false);
diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php
index ca5d13869957306a93fa851cbcb3ee8911c94c6c..e3b6566bcf094fb3289e340ddde07f98a9a9e18d 100644
--- a/apps/user_ldap/lib/access.php
+++ b/apps/user_ldap/lib/access.php
@@ -28,6 +28,9 @@ namespace OCA\user_ldap\lib;
  * @package OCA\user_ldap\lib
  */
 class Access extends LDAPUtility implements user\IUserTools {
+	/**
+	 * @var \OCA\user_ldap\lib\Connection
+	 */
 	public $connection;
 	public $userManager;
 	//never ever check this var directly, always use getPagedSearchResultState
@@ -61,8 +64,8 @@ class Access extends LDAPUtility implements user\IUserTools {
 
 	/**
 	 * reads a given attribute for an LDAP record identified by a DN
-	 * @param $dn the record in question
-	 * @param $attr the attribute that shall be retrieved
+	 * @param string $dn the record in question
+	 * @param string $attr the attribute that shall be retrieved
 	 *        if empty, just check the record's existence
 	 * @param string $filter
 	 * @return array|false an array of values on success or an empty
@@ -180,6 +183,33 @@ class Access extends LDAPUtility implements user\IUserTools {
 		return $dn;
 	}
 
+	/**
+	 * returns a DN-string that is cleaned from not domain parts, e.g.
+	 * cn=foo,cn=bar,dc=foobar,dc=server,dc=org
+	 * becomes dc=foobar,dc=server,dc=org
+	 * @param string $dn
+	 * @return string
+	 */
+	public function getDomainDNFromDN($dn) {
+		$allParts = $this->ldap->explodeDN($dn, 0);
+		if($allParts === false) {
+			//not a valid DN
+			return '';
+		}
+		$domainParts = array();
+		$dcFound = false;
+		foreach($allParts as $part) {
+			if(!$dcFound && strpos($part, 'dc=') === 0) {
+				$dcFound = true;
+			}
+			if($dcFound) {
+				$domainParts[] = $part;
+			}
+		}
+		$domainDN = implode(',', $domainParts);
+		return $domainDN;
+	}
+
 	/**
 	 * gives back the database table for the query
 	 * @param bool $isUser
@@ -534,7 +564,7 @@ class Access extends LDAPUtility implements user\IUserTools {
 			if(!\OC_Group::groupExists($altName)) {
 				return $altName;
 			}
-			$altName = $name . '_' . $lastNo + $attempts;
+			$altName = $name . '_' . ($lastNo + $attempts);
 			$attempts++;
 		}
 		return false;
@@ -581,6 +611,7 @@ class Access extends LDAPUtility implements user\IUserTools {
 
 	/**
 	 * @param boolean $isUsers
+	 * @return array
 	 */
 	private function mappedComponents($isUsers) {
 		$table = $this->getMapTable($isUsers);
@@ -834,7 +865,7 @@ class Access extends LDAPUtility implements user\IUserTools {
 	private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
 		\OCP\Util::writeLog('user_ldap', 'Count filter:  '.print_r($filter, true), \OCP\Util::DEBUG);
 
-		if(is_null($limit)) {
+		if(is_null($limit) || $limit <= 0) {
 			$limit = intval($this->connection->ldapPagingSize);
 		}
 
@@ -894,6 +925,10 @@ class Access extends LDAPUtility implements user\IUserTools {
 	 * @return array with the search result
 	 */
 	private function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
+		if($limit <= 0) {
+			//otherwise search will fail
+			$limit = null;
+		}
 		$search = $this->executeSearch($filter, $base, $attr, $limit, $offset);
 		if($search === false) {
 			return array();
@@ -908,7 +943,7 @@ class Access extends LDAPUtility implements user\IUserTools {
 			$this->processPagedSearchStatus($sr, $filter, $base, 1, $limit,
 											$offset, $pagedSearchOK,
 											$skipHandling);
-			return;
+			return array();
 		}
 
 		// Do the server-side sorting
@@ -1232,6 +1267,55 @@ class Access extends LDAPUtility implements user\IUserTools {
 		return strtoupper($hex_guid_to_guid_str);
 	}
 
+	/**
+	 * gets a SID of the domain of the given dn
+	 * @param string $dn
+	 * @return string|bool
+	 */
+	public function getSID($dn) {
+		$domainDN = $this->getDomainDNFromDN($dn);
+		$cacheKey = 'getSID-'.$domainDN;
+		if($this->connection->isCached($cacheKey)) {
+			return $this->connection->getFromCache($cacheKey);
+		}
+
+		$objectSid = $this->readAttribute($domainDN, 'objectsid');
+		if(!is_array($objectSid) || empty($objectSid)) {
+			$this->connection->writeToCache($cacheKey, false);
+			return false;
+		}
+		$domainObjectSid = $this->convertSID2Str($objectSid[0]);
+		$this->connection->writeToCache($cacheKey, $domainObjectSid);
+
+		return $domainObjectSid;
+	}
+
+	/**
+	 * converts a binary SID into a string representation
+	 * @param string $sid
+	 * @return string
+	 * @link http://blogs.freebsdish.org/tmclaugh/2010/07/21/finding-a-users-primary-group-in-ad/#comment-2855
+	 */
+	public function convertSID2Str($sid) {
+		try {
+			$srl = ord($sid[0]);
+			$numberSubID = ord($sid[1]);
+			$x = substr($sid, 2, 6);
+			$h = unpack('N', "\x0\x0" . substr($x,0,2));
+			$l = unpack('N', substr($x,2,6));
+			$iav = bcadd(bcmul($h[1], bcpow(2,32)), $l[1]);
+			$subIDs = array();
+			for ($i=0; $i<$numberSubID; $i++) {
+				$subID = unpack('V', substr($sid, 8+4*$i, 4));
+				$subIDs[] = $subID[1];
+			}
+		} catch (\Exception $e) {
+			return '';
+		}
+
+		return sprintf('S-%d-%d-%s', $srl, $iav, implode('-', $subIDs));
+	}
+
 	/**
 	 * converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
 	 * @param string $dn the DN
diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php
index bafb0e0b895bf546d8f9d679b4d5de4a59c683c8..336ea7b3bbc424f7b1a1e6cd9e557fa689e2ee85 100644
--- a/apps/user_ldap/lib/connection.php
+++ b/apps/user_ldap/lib/connection.php
@@ -23,6 +23,13 @@
 
 namespace OCA\user_ldap\lib;
 
+//magic properties (incomplete)
+/**
+ * responsible for LDAP connections in context with the provided configuration
+ * @property string ldapUserFilter
+ * @property string ldapUserDisplayName
+ * @property boolean hasPagedResultSupport
+*/
 class Connection extends LDAPUtility {
 	private $ldapConnectionRes = null;
 	private $configPrefix;
diff --git a/apps/user_ldap/lib/ildapwrapper.php b/apps/user_ldap/lib/ildapwrapper.php
index 97ae08101163d8ffb1b64424d487b8c99d6f8083..590f6d7ac7abedf96315fd9b87d5324441868ff9 100644
--- a/apps/user_ldap/lib/ildapwrapper.php
+++ b/apps/user_ldap/lib/ildapwrapper.php
@@ -89,6 +89,15 @@ interface ILDAPWrapper {
 	 */
 	public function error($link);
 
+	/**
+	 * Splits DN into its component parts
+	 * @param string $dn
+	 * @param int @withAttrib
+	 * @return array|false
+	 * @link http://www.php.net/manual/en/function.ldap-explode-dn.php
+	 */
+	public function explodeDN($dn, $withAttrib);
+
 	/**
 	 * Return first result id
 	 * @param resource $link LDAP link resource
diff --git a/apps/user_ldap/lib/ldap.php b/apps/user_ldap/lib/ldap.php
index 2b20b2ab7386c2bda3e6904f17cdd6689f8f1bb7..967754db7d399147a2cc1099a90b4cdb7c10bc5d 100644
--- a/apps/user_ldap/lib/ldap.php
+++ b/apps/user_ldap/lib/ldap.php
@@ -98,6 +98,17 @@ class LDAP implements ILDAPWrapper {
 		return $this->invokeLDAPMethod('error', $link);
 	}
 
+	/**
+	 * Splits DN into its component parts
+	 * @param string $dn
+	 * @param int @withAttrib
+	 * @return array|false
+	 * @link http://www.php.net/manual/en/function.ldap-explode-dn.php
+	 */
+	public function explodeDN($dn, $withAttrib) {
+		return $this->invokeLDAPMethod('ldap_explode_dn', $dn, $withAttrib);
+	}
+
 	/**
 	 * @param LDAP $link
 	 * @param LDAP $result
diff --git a/apps/user_ldap/tests/access.php b/apps/user_ldap/tests/access.php
index 8ead5d684821a4b3429185495736308512fae257..2ff7540b8eff81b10eb6183f0636a242b07070dc 100644
--- a/apps/user_ldap/tests/access.php
+++ b/apps/user_ldap/tests/access.php
@@ -77,4 +77,54 @@ class Test_Access extends \PHPUnit_Framework_TestCase {
 		$expected = 'foo\\\\*bar';
 		$this->assertTrue($expected === $access->escapeFilterPart($input));
 	}
-}
\ No newline at end of file
+
+	public function testConvertSID2StrSuccess() {
+		list($lw, $con, $um) = $this->getConnecterAndLdapMock();
+		$access = new Access($con, $lw, $um);
+
+		$sidBinary = file_get_contents(__DIR__ . '/data/sid.dat');
+		$sidExpected = 'S-1-5-21-249921958-728525901-1594176202';
+
+		$this->assertSame($sidExpected, $access->convertSID2Str($sidBinary));
+	}
+
+	public function testConvertSID2StrInputError() {
+		list($lw, $con, $um) = $this->getConnecterAndLdapMock();
+		$access = new Access($con, $lw, $um);
+
+		$sidIllegal = 'foobar';
+		$sidExpected = '';
+
+		$this->assertSame($sidExpected, $access->convertSID2Str($sidIllegal));
+	}
+
+	public function testGetDomainDNFromDNSuccess() {
+		list($lw, $con, $um) = $this->getConnecterAndLdapMock();
+		$access = new Access($con, $lw, $um);
+
+		$inputDN = 'uid=zaphod,cn=foobar,dc=my,dc=server,dc=com';
+		$domainDN = 'dc=my,dc=server,dc=com';
+
+		$lw->expects($this->once())
+			->method('explodeDN')
+			->with($inputDN, 0)
+			->will($this->returnValue(explode(',', $inputDN)));
+
+		$this->assertSame($domainDN, $access->getDomainDNFromDN($inputDN));
+	}
+
+	public function testGetDomainDNFromDNError() {
+		list($lw, $con, $um) = $this->getConnecterAndLdapMock();
+		$access = new Access($con, $lw, $um);
+
+		$inputDN = 'foobar';
+		$expected = '';
+
+		$lw->expects($this->once())
+			->method('explodeDN')
+			->with($inputDN, 0)
+			->will($this->returnValue(false));
+
+		$this->assertSame($expected, $access->getDomainDNFromDN($inputDN));
+	}
+}
diff --git a/apps/user_ldap/tests/data/sid.dat b/apps/user_ldap/tests/data/sid.dat
new file mode 100644
index 0000000000000000000000000000000000000000..3d500c6a872f3c8904df6fe85af623dbce42b0b2
Binary files /dev/null and b/apps/user_ldap/tests/data/sid.dat differ
diff --git a/apps/user_ldap/tests/group_ldap.php b/apps/user_ldap/tests/group_ldap.php
index 1184fe1e82ecf6d562a9a89412de9612249a7a9a..c4aed25a1cc00fa6de4b539b9f402141c3180272 100644
--- a/apps/user_ldap/tests/group_ldap.php
+++ b/apps/user_ldap/tests/group_ldap.php
@@ -95,6 +95,10 @@ class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
 			->method('groupname2dn')
 			->will($this->returnValue('cn=group,dc=foo,dc=bar'));
 
+		$access->expects($this->any())
+			->method('fetchListOfUsers')
+			->will($this->returnValue(array()));
+
 		$access->expects($this->any())
 			->method('readAttribute')
 			->will($this->returnCallback(function($name) {
@@ -111,7 +115,9 @@ class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
 
 		$access->expects($this->any())
 			->method('dn2username')
-			->will($this->returnValue('foobar'));
+			->will($this->returnCallback(function() {
+				return 'foobar' . \OCP\Util::generateRandomBytes(7);
+			}));
 
 		$groupBackend = new GroupLDAP($access);
 		$users = $groupBackend->countUsersInGroup('group', '3');
@@ -119,4 +125,148 @@ class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
 		$this->assertSame(2, $users);
 	}
 
+	public function testPrimaryGroupID2NameSuccess() {
+		$access = $this->getAccessMock();
+		$this->enableGroups($access);
+
+		$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
+
+		$access->expects($this->once())
+			->method('getSID')
+			->with($userDN)
+			->will($this->returnValue('S-1-5-21-249921958-728525901-1594176202'));
+
+		$access->expects($this->once())
+			->method('searchGroups')
+			->will($this->returnValue(array('cn=foo,dc=barfoo,dc=bar')));
+
+		$access->expects($this->once())
+			->method('dn2groupname')
+			->with('cn=foo,dc=barfoo,dc=bar')
+			->will($this->returnValue('MyGroup'));
+
+		$groupBackend = new GroupLDAP($access);
+
+		$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
+
+		$this->assertSame('MyGroup', $group);
+	}
+
+	public function testPrimaryGroupID2NameNoSID() {
+		$access = $this->getAccessMock();
+		$this->enableGroups($access);
+
+		$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
+
+		$access->expects($this->once())
+			->method('getSID')
+			->with($userDN)
+			->will($this->returnValue(false));
+
+		$access->expects($this->never())
+			->method('searchGroups');
+
+		$access->expects($this->never())
+			->method('dn2groupname');
+
+		$groupBackend = new GroupLDAP($access);
+
+		$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
+
+		$this->assertSame(false, $group);
+	}
+
+	public function testPrimaryGroupID2NameNoGroup() {
+		$access = $this->getAccessMock();
+		$this->enableGroups($access);
+
+		$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
+
+		$access->expects($this->once())
+			->method('getSID')
+			->with($userDN)
+			->will($this->returnValue('S-1-5-21-249921958-728525901-1594176202'));
+
+		$access->expects($this->once())
+			->method('searchGroups')
+			->will($this->returnValue(array()));
+
+		$access->expects($this->never())
+			->method('dn2groupname');
+
+		$groupBackend = new GroupLDAP($access);
+
+		$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
+
+		$this->assertSame(false, $group);
+	}
+
+	public function testPrimaryGroupID2NameNoName() {
+		$access = $this->getAccessMock();
+		$this->enableGroups($access);
+
+		$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
+
+		$access->expects($this->once())
+			->method('getSID')
+			->with($userDN)
+			->will($this->returnValue('S-1-5-21-249921958-728525901-1594176202'));
+
+		$access->expects($this->once())
+			->method('searchGroups')
+			->will($this->returnValue(array('cn=foo,dc=barfoo,dc=bar')));
+
+		$access->expects($this->once())
+			->method('dn2groupname')
+			->will($this->returnValue(false));
+
+		$groupBackend = new GroupLDAP($access);
+
+		$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
+
+		$this->assertSame(false, $group);
+	}
+
+	public function testGetEntryGroupIDValue() {
+		//tests getEntryGroupID via getGroupPrimaryGroupID
+		//which is basically identical to getUserPrimaryGroupIDs
+		$access = $this->getAccessMock();
+		$this->enableGroups($access);
+
+		$dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar';
+		$attr = 'primaryGroupToken';
+
+		$access->expects($this->once())
+			->method('readAttribute')
+			->with($dn, $attr)
+			->will($this->returnValue(array('3117')));
+
+		$groupBackend = new GroupLDAP($access);
+
+		$gid = $groupBackend->getGroupPrimaryGroupID($dn);
+
+		$this->assertSame('3117', $gid);
+	}
+
+	public function testGetEntryGroupIDNoValue() {
+		//tests getEntryGroupID via getGroupPrimaryGroupID
+		//which is basically identical to getUserPrimaryGroupIDs
+		$access = $this->getAccessMock();
+		$this->enableGroups($access);
+
+		$dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar';
+		$attr = 'primaryGroupToken';
+
+		$access->expects($this->once())
+			->method('readAttribute')
+			->with($dn, $attr)
+			->will($this->returnValue(false));
+
+		$groupBackend = new GroupLDAP($access);
+
+		$gid = $groupBackend->getGroupPrimaryGroupID($dn);
+
+		$this->assertSame(false, $gid);
+	}
+
 }