diff options
-rw-r--r-- | .drone.yml | 2 | ||||
-rw-r--r-- | apps/federatedfilesharing/css/settings-personal.scss | 3 | ||||
-rw-r--r-- | apps/federatedfilesharing/img/social-googleplus.svg | 1 | ||||
-rw-r--r-- | apps/federatedfilesharing/templates/settings-personal.php | 4 | ||||
-rw-r--r-- | apps/user_ldap/lib/Connection.php | 3 | ||||
-rw-r--r-- | apps/user_ldap/lib/Group_LDAP.php | 140 | ||||
-rw-r--r-- | apps/user_ldap/tests/Group_LDAPTest.php | 57 | ||||
-rw-r--r-- | build/integration/features/bootstrap/LDAPContext.php | 8 | ||||
-rw-r--r-- | build/integration/ldap_features/ldap-openldap.feature | 64 | ||||
-rw-r--r-- | build/integration/ldap_features/openldap-numerical-id.feature | 36 | ||||
-rw-r--r-- | lib/private/App/AppStore/Bundles/SocialSharingBundle.php | 1 | ||||
-rw-r--r-- | lib/private/Files/Cache/Cache.php | 30 | ||||
-rw-r--r-- | lib/private/Files/Cache/Scanner.php | 2 | ||||
-rw-r--r-- | lib/private/Files/Cache/Wrapper/CacheJail.php | 4 | ||||
-rw-r--r-- | lib/private/Files/Cache/Wrapper/CacheWrapper.php | 4 | ||||
-rw-r--r-- | tests/lib/App/AppStore/Bundles/SocialSharingBundleTest.php | 1 | ||||
-rw-r--r-- | tests/lib/Files/Cache/ScannerTest.php | 38 |
17 files changed, 300 insertions, 98 deletions
diff --git a/.drone.yml b/.drone.yml index e3cfc2d7be5..f821333ee91 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1049,7 +1049,7 @@ services: matrix: TESTS: acceptance openldap: - image: nextcloudci/openldap:openldap-6 + image: nextcloudci/openldap:openldap-7 environment: - SLAPD_DOMAIN=nextcloud.ci - SLAPD_ORGANIZATION=Nextcloud diff --git a/apps/federatedfilesharing/css/settings-personal.scss b/apps/federatedfilesharing/css/settings-personal.scss index eda7a80cb1a..c64a6fc9e75 100644 --- a/apps/federatedfilesharing/css/settings-personal.scss +++ b/apps/federatedfilesharing/css/settings-personal.scss @@ -33,6 +33,3 @@ .social-facebook { @include icon-color('social-facebook', 'federatedfilesharing', $color-black); } -.social-googleplus { - @include icon-color('social-googleplus', 'federatedfilesharing', $color-black); -} diff --git a/apps/federatedfilesharing/img/social-googleplus.svg b/apps/federatedfilesharing/img/social-googleplus.svg deleted file mode 100644 index 53af74c64f1..00000000000 --- a/apps/federatedfilesharing/img/social-googleplus.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" height="32" id="Layer_1" viewBox="0 0 32 32" width="32"><path d="M12.26 1c-1.044 0-2.164.12-3.36.365-1.207.284-2.37.885-3.49 1.807C3.776 4.748 2.96 6.502 2.96 8.438c0 1.6.576 3.003 1.73 4.21 1.098 1.292 2.697 1.953 4.795 1.98.395 0 .818-.028 1.266-.078-.074.207-.155.43-.24.676-.1.23-.15.523-.15.87 0 .578.13 1.075.39 1.488.222.424.475.823.76 1.197-.92.026-2.075.142-3.466.346-1.405.244-2.746.73-4.02 1.458-1.138.678-1.924 1.465-2.356 2.36-.447.894-.67 1.71-.67 2.437 0 1.497.688 2.782 2.06 3.857 1.362 1.15 3.422 1.738 6.182 1.763 3.297-.05 5.82-.837 7.567-2.363 1.686-1.475 2.53-3.166 2.53-5.076-.026-1.343-.333-2.432-.923-3.266a13.466 13.466 0 0 0-2.184-2.247l-1.337-1.096a7.868 7.868 0 0 1-.596-.672c-.242-.27-.362-.607-.362-1.02 0-.422.118-.793.352-1.113.2-.31.416-.584.652-.827.41-.36.797-.712 1.16-1.057.33-.345.64-.723.933-1.133.6-.846.912-1.974.935-3.383 0-.77-.087-1.442-.26-2.018a7.594 7.594 0 0 0-1.596-2.633 5.256 5.256 0 0 0-.836-.673h2.43L20.14 1h-7.88zm12.418.17v4.134h-4.134v2.003h4.134v4.134h2.004V7.308h4.134V5.304h-4.134V1.17h-2.004zM9.823 2.304c.807.026 1.52.265 2.14.712.61.475 1.1 1.093 1.474 1.85.794 1.577 1.192 3.145 1.192 4.697 0 .36-.03.803-.088 1.33a4.06 4.06 0 0 1-.643 1.54c-.69.706-1.555 1.08-2.593 1.118-.82 0-1.55-.25-2.19-.752-.64-.5-1.16-1.11-1.563-1.83-.834-1.54-1.25-3.022-1.25-4.447a4.844 4.844 0 0 1 .856-2.927c.708-.835 1.595-1.265 2.663-1.29zm1.667 17.54c.09 0 .16.003.21.01.337 0 .632.01.884.038 1.47 1.026 2.548 1.89 3.24 2.595.65.743.976 1.613.976 2.613 0 1.233-.477 2.238-1.43 3.02-.978.795-2.396 1.205-4.253 1.23-2.07-.025-3.702-.486-4.894-1.382-1.255-.897-1.88-2.04-1.88-3.423 0-.704.143-1.307.433-1.806a4.11 4.11 0 0 1 .92-1.194 5.237 5.237 0 0 1 1.11-.71c.365-.155.646-.27.847-.347a16.935 16.935 0 0 1 2.504-.56c.62-.056 1.065-.085 1.333-.085z" id="path4713" opacity=".5"/></svg> diff --git a/apps/federatedfilesharing/templates/settings-personal.php b/apps/federatedfilesharing/templates/settings-personal.php index 89f7b1eb1e7..ac893cc2599 100644 --- a/apps/federatedfilesharing/templates/settings-personal.php +++ b/apps/federatedfilesharing/templates/settings-personal.php @@ -28,10 +28,6 @@ style('federatedfilesharing', 'settings-personal'); data-url='https://twitter.com/intent/tweet?text=<?php p(urlencode($_['message_with_URL'])); ?>'> Twitter </button> - <button class="social-googleplus pop-up" - data-url='https://plus.google.com/share?url=<?php p(urlencode($_['reference'])); ?>'> - Google+ - </button> <button class="social-diaspora pop-up" data-url='https://sharetodiaspora.github.io/?title=<?php p($_['message_without_URL']); ?>&url=<?php p(urlencode($_['reference'])); ?>'> Diaspora diff --git a/apps/user_ldap/lib/Connection.php b/apps/user_ldap/lib/Connection.php index ba393dffc12..4335f8e4397 100644 --- a/apps/user_ldap/lib/Connection.php +++ b/apps/user_ldap/lib/Connection.php @@ -62,6 +62,9 @@ use OCP\ILogger; * @property string ldapEmailAttribute * @property string ldapExtStorageHomeAttribute * @property string homeFolderNamingRule + * @property bool|string ldapNestedGroups + * @property string[] ldapBaseGroups + * @property string ldapGroupFilter */ class Connection extends LDAPUtility { private $ldapConnectionRes = null; diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php index 2240c2ad229..1658807c0dd 100644 --- a/apps/user_ldap/lib/Group_LDAP.php +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -58,6 +58,11 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD */ protected $cachedGroupsByMember; + /** + * @var string[] $cachedNestedGroups array of groups with gid (DN) as key + */ + protected $cachedNestedGroups; + /** @var GroupPluginManager */ protected $groupPluginManager; @@ -71,6 +76,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD $this->cachedGroupMembers = new CappedMemoryCache(); $this->cachedGroupsByMember = new CappedMemoryCache(); + $this->cachedNestedGroups = new CappedMemoryCache(); $this->groupPluginManager = $groupPluginManager; } @@ -212,12 +218,12 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD */ private function _groupMembers($dnGroup, &$seen = null) { if ($seen === null) { - $seen = array(); + $seen = []; } - $allMembers = array(); + $allMembers = []; if (array_key_exists($dnGroup, $seen)) { // avoid loops - return array(); + return []; } // used extensively in cron job, caching makes sense for nested groups $cacheKey = '_groupMembers'.$dnGroup; @@ -226,19 +232,12 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD return $groupMembers; } $seen[$dnGroup] = 1; - $members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr, - $this->access->connection->ldapGroupFilter); + $members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr); if (is_array($members)) { - foreach ($members as $member) { - $allMembers[$member] = 1; - $nestedGroups = $this->access->connection->ldapNestedGroups; - if (!empty($nestedGroups)) { - $subMembers = $this->_groupMembers($member, $seen); - if ($subMembers) { - $allMembers += $subMembers; - } - } - } + $fetcher = function($memberDN, &$seen) { + return $this->_groupMembers($memberDN, $seen); + }; + $allMembers = $this->walkNestedGroups($dnGroup, $fetcher, $members); } $allMembers += $this->getDynamicGroupMembers($dnGroup); @@ -251,30 +250,69 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD * @param string $DN * @param array|null &$seen * @return array + * @throws \OC\ServerNotAvailableException */ - private function _getGroupDNsFromMemberOf($DN, &$seen = null) { - if ($seen === null) { - $seen = array(); - } - if (array_key_exists($DN, $seen)) { - // avoid loops - return array(); - } - $seen[$DN] = 1; + private function _getGroupDNsFromMemberOf($DN) { $groups = $this->access->readAttribute($DN, 'memberOf'); if (!is_array($groups)) { - return array(); + return []; + } + + $fetcher = function($groupDN) { + if (isset($this->cachedNestedGroups[$groupDN])) { + $nestedGroups = $this->cachedNestedGroups[$groupDN]; + } else { + $nestedGroups = $this->access->readAttribute($groupDN, 'memberOf'); + if (!is_array($nestedGroups)) { + $nestedGroups = []; + } + $this->cachedNestedGroups[$groupDN] = $nestedGroups; + } + return $nestedGroups; + }; + + $groups = $this->walkNestedGroups($DN, $fetcher, $groups); + return $this->access->groupsMatchFilter($groups); + } + + /** + * @param string $dn + * @param \Closure $fetcher args: string $dn, array $seen, returns: string[] of dns + * @param array $list + * @return array + */ + private function walkNestedGroups(string $dn, \Closure $fetcher, array $list): array { + $nesting = (int) $this->access->connection->ldapNestedGroups; + // depending on the input, we either have a list of DNs or a list of LDAP records + // also, the output expects either DNs or records. Testing the first element should suffice. + $recordMode = is_array($list) && isset($list[0]) && is_array($list[0]) && isset($list[0]['dn'][0]); + + if ($nesting !== 1) { + if($recordMode) { + // the keys are numeric, but should hold the DN + return array_reduce($list, function ($transformed, $record) use ($dn) { + if($record['dn'][0] != $dn) { + $transformed[$record['dn'][0]] = $record; + } + return $transformed; + }, []); + } + return $list; } - $groups = $this->access->groupsMatchFilter($groups); - $allGroups = $groups; - $nestedGroups = $this->access->connection->ldapNestedGroups; - if ((int)$nestedGroups === 1) { - foreach ($groups as $group) { - $subGroups = $this->_getGroupDNsFromMemberOf($group, $seen); - $allGroups = array_merge($allGroups, $subGroups); + + $seen = []; + while ($record = array_pop($list)) { + $recordDN = $recordMode ? $record['dn'][0] : $record; + if ($recordDN === $dn || array_key_exists($recordDN, $seen)) { + // Prevent loops + continue; } + $fetched = $fetcher($record, $seen); + $list = array_merge($list, $fetched); + $seen[$recordDN] = $record; } - return $allGroups; + + return $recordMode ? $seen : array_keys($seen); } /** @@ -737,34 +775,28 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD */ private function getGroupsByMember($dn, &$seen = null) { if ($seen === null) { - $seen = array(); + $seen = []; } - $allGroups = array(); if (array_key_exists($dn, $seen)) { // avoid loops - return array(); + return []; } + $allGroups = []; $seen[$dn] = true; - $filter = $this->access->combineFilterWithAnd(array( - $this->access->connection->ldapGroupFilter, - $this->access->connection->ldapGroupMemberAssocAttr.'='.$dn - )); + $filter = $this->access->connection->ldapGroupMemberAssocAttr.'='.$dn; $groups = $this->access->fetchListOfGroups($filter, - array($this->access->connection->ldapGroupDisplayName, 'dn')); + [$this->access->connection->ldapGroupDisplayName, 'dn']); if (is_array($groups)) { - foreach ($groups as $groupobj) { - $groupDN = $groupobj['dn'][0]; - $allGroups[$groupDN] = $groupobj; - $nestedGroups = $this->access->connection->ldapNestedGroups; - if (!empty($nestedGroups)) { - $supergroups = $this->getGroupsByMember($groupDN, $seen); - if (is_array($supergroups) && (count($supergroups)>0)) { - $allGroups = array_merge($allGroups, $supergroups); - } + $fetcher = function ($dn, &$seen) { + if(is_array($dn) && isset($dn['dn'][0])) { + $dn = $dn['dn'][0]; } - } + return $this->getGroupsByMember($dn, $seen); + }; + $allGroups = $this->walkNestedGroups($dn, $fetcher, $groups); } - return $allGroups; + $visibleGroups = $this->access->groupsMatchFilter(array_keys($allGroups)); + return array_intersect_key($allGroups, array_flip($visibleGroups)); } /** @@ -811,7 +843,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD $primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset); $posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset); - $members = array_keys($this->_groupMembers($groupDN)); + $members = $this->_groupMembers($groupDN); if(!$members && empty($posixGroupUsers) && empty($primaryUsers)) { //in case users could not be retrieved, return empty result set $this->access->connection->writeToCache($cacheKey, []); @@ -886,7 +918,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLD return false; } - $members = array_keys($this->_groupMembers($groupDN)); + $members = $this->_groupMembers($groupDN); $primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, ''); if(!$members && $primaryUserCount === 0) { //in case users could not be retrieved, return empty result set diff --git a/apps/user_ldap/tests/Group_LDAPTest.php b/apps/user_ldap/tests/Group_LDAPTest.php index ec8d888e2a0..cb5760e3171 100644 --- a/apps/user_ldap/tests/Group_LDAPTest.php +++ b/apps/user_ldap/tests/Group_LDAPTest.php @@ -98,16 +98,27 @@ class Group_LDAPTest extends TestCase { public function testCountEmptySearchString() { $access = $this->getAccessMock(); $pluginManager = $this->getPluginManagerMock(); + $groupDN = 'cn=group,dc=foo,dc=bar'; $this->enableGroups($access); $access->expects($this->any()) ->method('groupname2dn') - ->will($this->returnValue('cn=group,dc=foo,dc=bar')); + ->will($this->returnValue($groupDN)); $access->expects($this->any()) ->method('readAttribute') - ->will($this->returnValue(array('u11', 'u22', 'u33', 'u34'))); + ->willReturnCallback(function($dn) use ($groupDN) { + if($dn === $groupDN) { + return [ + 'uid=u11,ou=users,dc=foo,dc=bar', + 'uid=u22,ou=users,dc=foo,dc=bar', + 'uid=u33,ou=users,dc=foo,dc=bar', + 'uid=u34,ou=users,dc=foo,dc=bar' + ]; + } + return []; + }); // for primary groups $access->expects($this->once()) @@ -132,7 +143,7 @@ class Group_LDAPTest extends TestCase { $access->expects($this->any()) ->method('fetchListOfUsers') - ->will($this->returnValue(array())); + ->will($this->returnValue([])); $access->expects($this->any()) ->method('readAttribute') @@ -145,7 +156,7 @@ class Group_LDAPTest extends TestCase { if(strpos($name, 'u') === 0) { return strpos($name, '3'); } - return array('u11', 'u22', 'u33', 'u34'); + return ['u11', 'u22', 'u33', 'u34']; })); $access->expects($this->any()) @@ -625,7 +636,7 @@ class Group_LDAPTest extends TestCase { ->method('dn2groupname') ->will($this->returnArgument(0)); - $access->expects($this->exactly(3)) + $access->expects($this->exactly(1)) ->method('groupsMatchFilter') ->will($this->returnArgument(0)); @@ -659,14 +670,15 @@ class Group_LDAPTest extends TestCase { $access->expects($this->once()) ->method('username2dn') ->will($this->returnValue($dn)); - $access->expects($this->never()) ->method('readAttribute') ->with($dn, 'memberOf'); - $access->expects($this->once()) ->method('nextcloudGroupNames') ->will($this->returnValue([])); + $access->expects($this->any()) + ->method('groupsMatchFilter') + ->willReturnArgument(0); $groupBackend = new GroupLDAP($access, $pluginManager); $groupBackend->getUserGroups('userX'); @@ -680,12 +692,15 @@ class Group_LDAPTest extends TestCase { $access->connection->expects($this->any()) ->method('__get') ->will($this->returnCallback(function($name) { - if($name === 'useMemberOfToDetectMembership') { - return 0; - } else if($name === 'ldapDynamicGroupMemberURL') { - return ''; - } else if($name === 'ldapNestedGroups') { - return false; + switch($name) { + case 'useMemberOfToDetectMembership': + return 0; + case 'ldapDynamicGroupMemberURL': + return ''; + case 'ldapNestedGroups': + return false; + case 'ldapGroupMemberAssocAttr': + return 'member'; } return 1; })); @@ -716,10 +731,12 @@ class Group_LDAPTest extends TestCase { ->method('nextcloudGroupNames') ->with([$group1, $group2]) ->will($this->returnValue(['group1', 'group2'])); - $access->expects($this->once()) ->method('fetchListOfGroups') ->will($this->returnValue([$group1, $group2])); + $access->expects($this->any()) + ->method('groupsMatchFilter') + ->willReturnArgument(0); $groupBackend = new GroupLDAP($access, $pluginManager); $groups = $groupBackend->getUserGroups('userX'); @@ -999,14 +1016,6 @@ class Group_LDAPTest extends TestCase { $groups1, ['cn=Birds,' . $base => $groups1] ], - [ #2 – test uids with nested groups - 'cn=Birds,' . $base, - $expGroups2, - [ - 'cn=Birds,' . $base => $groups1, - '8427' => $groups2Nested, // simplified - nested groups would work with DNs - ], - ], ]; } @@ -1045,9 +1054,7 @@ class Group_LDAPTest extends TestCase { $ldap = new GroupLDAP($access, $pluginManager); $resultingMembers = $this->invokePrivate($ldap, '_groupMembers', [$groupDN]); - $expected = array_keys(array_flip($expectedMembers)); - - $this->assertEquals($expected, array_keys($resultingMembers), '', 0.0, 10, true); + $this->assertEquals($expectedMembers, $resultingMembers, '', 0.0, 10, true); } } diff --git a/build/integration/features/bootstrap/LDAPContext.php b/build/integration/features/bootstrap/LDAPContext.php index ee7acab6f5f..2ad737bf8b8 100644 --- a/build/integration/features/bootstrap/LDAPContext.php +++ b/build/integration/features/bootstrap/LDAPContext.php @@ -27,6 +27,7 @@ use PHPUnit\Framework\Assert; class LDAPContext implements Context { use BasicStructure; + use CommandLine; protected $configID; @@ -37,6 +38,8 @@ class LDAPContext implements Context { if($this->configID === null) { return; } + $this->disableLDAPConfiguration(); # via occ in case of big config issues + $this->asAn('admin'); $this->sendingTo('DELETE', $this->apiUrl . '/' . $this->configID); } @@ -196,4 +199,9 @@ class LDAPContext implements Context { $backend = (string)simplexml_load_string($this->response->getBody())->data[0]->backend; Assert::assertEquals('LDAP', $backend); } + + public function disableLDAPConfiguration() { + $configKey = $this->configID . 'ldap_configuration_active'; + $this->invokingTheCommand('config:app:set user_ldap ' . $configKey . ' --value="0"'); + } } diff --git a/build/integration/ldap_features/ldap-openldap.feature b/build/integration/ldap_features/ldap-openldap.feature index 4b0b02c5b4f..6c5ed8b462b 100644 --- a/build/integration/ldap_features/ldap-openldap.feature +++ b/build/integration/ldap_features/ldap-openldap.feature @@ -102,3 +102,67 @@ Feature: LDAP | ldapHost | foo.bar | | ldapPort | 2456 | Then Expect ServerException on failed web login as "alice" + + Scenario: Test LDAP group membership with intermediate groups not matching filter + Given modify LDAP configuration + | ldapBaseGroups | ou=OtherGroups,dc=nextcloud,dc=ci | + | ldapGroupFilter | (&(cn=Gardeners)(objectclass=groupOfNames)) | + | ldapNestedGroups | 1 | + | useMemberOfToDetectMembership | 1 | + | ldapUserFilter | (&(objectclass=inetorgperson)(!(uid=alice))) | + | ldapExpertUsernameAttr | uid | + | ldapGroupMemberAssocAttr | member | + And As an "admin" + # for population + And sending "GET" to "/cloud/groups" + And sending "GET" to "/cloud/groups/Gardeners/users" + Then the OCS status code should be "200" + And the "users" result should match + | alice | 0 | + | clara | 1 | + | elisa | 1 | + | gustaf | 1 | + | jesper | 1 | + + Scenario: Test LDAP group membership with intermediate groups not matching filter and without memberof + Given modify LDAP configuration + | ldapBaseGroups | ou=OtherGroups,dc=nextcloud,dc=ci | + | ldapGroupFilter | (&(cn=Gardeners)(objectclass=groupOfNames)) | + | ldapNestedGroups | 1 | + | useMemberOfToDetectMembership | 0 | + | ldapUserFilter | (&(objectclass=inetorgperson)(!(uid=alice))) | + | ldapExpertUsernameAttr | uid | + | ldapGroupMemberAssocAttr | member | + And As an "admin" + # for population + And sending "GET" to "/cloud/groups" + And sending "GET" to "/cloud/groups/Gardeners/users" + Then the OCS status code should be "200" + And the "users" result should match + | alice | 0 | + | clara | 1 | + | elisa | 1 | + | gustaf | 1 | + | jesper | 1 | + + Scenario: Test LDAP group membership with intermediate groups not matching filter, numeric group ids + Given modify LDAP configuration + | ldapBaseGroups | ou=NumericGroups,dc=nextcloud,dc=ci | + | ldapGroupFilter | (&(cn=2000)(objectclass=groupOfNames)) | + | ldapNestedGroups | 1 | + | useMemberOfToDetectMembership | 1 | + | ldapUserFilter | (&(objectclass=inetorgperson)(!(uid=alice))) | + | ldapExpertUsernameAttr | uid | + | ldapGroupMemberAssocAttr | member | + And As an "admin" + # for population + And sending "GET" to "/cloud/groups" + And sending "GET" to "/cloud/groups/2000/users" + Then the OCS status code should be "200" + And the "users" result should match + | alice | 0 | + | clara | 1 | + | elisa | 1 | + | gustaf | 1 | + | jesper | 1 | + diff --git a/build/integration/ldap_features/openldap-numerical-id.feature b/build/integration/ldap_features/openldap-numerical-id.feature index 2d87ba33e6e..4112df0ae1a 100644 --- a/build/integration/ldap_features/openldap-numerical-id.feature +++ b/build/integration/ldap_features/openldap-numerical-id.feature @@ -29,3 +29,39 @@ Scenario: Test by logging in And Logging in using web as "92379" And Sending a "GET" to "/remote.php/webdav/welcome.txt" with requesttoken Then the HTTP status code should be "200" + +Scenario: Test LDAP group retrieval with numeric group ids and nesting + # Nesting does not play a role here really + Given modify LDAP configuration + | ldapBaseGroups | ou=NumericGroups,dc=nextcloud,dc=ci | + | ldapGroupFilter | (objectclass=groupOfNames) | + | ldapNestedGroups | 1 | + | useMemberOfToDetectMembership | 1 | + And As an "admin" + And sending "GET" to "/cloud/groups" + Then the OCS status code should be "200" + And the "groups" result should match + | 2000 | 1 | + | 3000 | 1 | + | 3001 | 1 | + | 3002 | 1 | + +Scenario: Test LDAP group membership with intermediate groups not matching filter, numeric group ids + Given modify LDAP configuration + | ldapBaseGroups | ou=NumericGroups,dc=nextcloud,dc=ci | + | ldapGroupFilter | (&(cn=2000)(objectclass=groupOfNames)) | + | ldapNestedGroups | 1 | + | useMemberOfToDetectMembership | 1 | + | ldapUserFilter | (&(objectclass=inetorgperson)(!(uid=alice))) | + | ldapGroupMemberAssocAttr | member | + And As an "admin" + # for population + And sending "GET" to "/cloud/groups" + And sending "GET" to "/cloud/groups/2000/users" + Then the OCS status code should be "200" + And the "users" result should match + | 92379 | 0 | + | 54172 | 1 | + | 50194 | 1 | + | 59376 | 1 | + | 59463 | 1 | diff --git a/lib/private/App/AppStore/Bundles/SocialSharingBundle.php b/lib/private/App/AppStore/Bundles/SocialSharingBundle.php index c882a8df557..59ea3e08cf2 100644 --- a/lib/private/App/AppStore/Bundles/SocialSharingBundle.php +++ b/lib/private/App/AppStore/Bundles/SocialSharingBundle.php @@ -38,7 +38,6 @@ class SocialSharingBundle extends Bundle { public function getAppIdentifiers() { return [ 'socialsharing_twitter', - 'socialsharing_googleplus', 'socialsharing_facebook', 'socialsharing_email', 'socialsharing_diaspora', diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index 7b42cc2aa57..ef1f0f3c870 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Andreas Fischer <bantu@owncloud.com> + * @author Ari Selseng <ari@selseng.net> * @author Artem Kochnev <MrJeos@gmail.com> * @author Björn Schießle <bjoern@schiessle.org> * @author Florin Peter <github@florin-peter.de> @@ -774,15 +775,38 @@ class Cache implements ICache { * @param string|boolean $path * @param array $data (optional) meta data of the folder */ - public function correctFolderSize($path, $data = null) { + public function correctFolderSize($path, $data = null, $isBackgroundScan = false) { $this->calculateFolderSize($path, $data); if ($path !== '') { $parent = dirname($path); if ($parent === '.' or $parent === '/') { $parent = ''; } - $this->correctFolderSize($parent); + if ($isBackgroundScan) { + $parentData = $this->get($parent); + if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) { + $this->correctFolderSize($parent, $parentData, $isBackgroundScan); + } + } else { + $this->correctFolderSize($parent); + } + } + } + + /** + * get the incomplete count that shares parent $folder + * + * @param int $fileId the file id of the folder + * @return int + */ + public function getIncompleteChildrenCount($fileId) { + if ($fileId > -1) { + $sql = 'SELECT count(*) + FROM `*PREFIX*filecache` WHERE `parent` = ? AND size = -1'; + $result = $this->connection->executeQuery($sql, [$fileId]); + return (int)$result->fetchColumn(); } + return -1; } /** @@ -919,4 +943,4 @@ class Cache implements ICache { return trim(\OC_Util::normalizeUnicode($path), '/'); } -} +}
\ No newline at end of file diff --git a/lib/private/Files/Cache/Scanner.php b/lib/private/Files/Cache/Scanner.php index ca9a0b794f9..e684b853103 100644 --- a/lib/private/Files/Cache/Scanner.php +++ b/lib/private/Files/Cache/Scanner.php @@ -532,7 +532,7 @@ class Scanner extends BasicEmitter implements IScanner { $callback(); \OC_Hook::emit('Scanner', 'correctFolderSize', array('path' => $path)); if ($this->cacheActive && $this->cache instanceof Cache) { - $this->cache->correctFolderSize($path); + $this->cache->correctFolderSize($path, null, true); } } catch (\OCP\Files\StorageInvalidException $e) { // skip unavailable storages diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php index 57f58e7d839..7e113d13678 100644 --- a/lib/private/Files/Cache/Wrapper/CacheJail.php +++ b/lib/private/Files/Cache/Wrapper/CacheJail.php @@ -265,9 +265,9 @@ class CacheJail extends CacheWrapper { * @param string|boolean $path * @param array $data (optional) meta data of the folder */ - public function correctFolderSize($path, $data = null) { + public function correctFolderSize($path, $data = null, $isBackgroundSize = false) { if ($this->getCache() instanceof Cache) { - $this->getCache()->correctFolderSize($this->getSourcePath($path), $data); + $this->getCache()->correctFolderSize($this->getSourcePath($path), $data, $isBackgroundSize); } } diff --git a/lib/private/Files/Cache/Wrapper/CacheWrapper.php b/lib/private/Files/Cache/Wrapper/CacheWrapper.php index da0a1b54e7f..223e678f323 100644 --- a/lib/private/Files/Cache/Wrapper/CacheWrapper.php +++ b/lib/private/Files/Cache/Wrapper/CacheWrapper.php @@ -258,9 +258,9 @@ class CacheWrapper extends Cache { * @param string|boolean $path * @param array $data (optional) meta data of the folder */ - public function correctFolderSize($path, $data = null) { + public function correctFolderSize($path, $data = null, $isBackgroundScan = false) { if ($this->getCache() instanceof Cache) { - $this->getCache()->correctFolderSize($path, $data); + $this->getCache()->correctFolderSize($path, $data, $isBackgroundScan); } } diff --git a/tests/lib/App/AppStore/Bundles/SocialSharingBundleTest.php b/tests/lib/App/AppStore/Bundles/SocialSharingBundleTest.php index 02ea0eb6ae5..3d8bccf1935 100644 --- a/tests/lib/App/AppStore/Bundles/SocialSharingBundleTest.php +++ b/tests/lib/App/AppStore/Bundles/SocialSharingBundleTest.php @@ -31,7 +31,6 @@ class SocialSharingBundleTest extends BundleBase { $this->bundleName = 'Social sharing bundle'; $this->bundleAppIds = [ 'socialsharing_twitter', - 'socialsharing_googleplus', 'socialsharing_facebook', 'socialsharing_email', 'socialsharing_diaspora', diff --git a/tests/lib/Files/Cache/ScannerTest.php b/tests/lib/Files/Cache/ScannerTest.php index 736df2174d1..0f5335f4416 100644 --- a/tests/lib/Files/Cache/ScannerTest.php +++ b/tests/lib/Files/Cache/ScannerTest.php @@ -203,6 +203,44 @@ class ScannerTest extends \Test\TestCase { $this->assertFalse($this->cache->getIncomplete()); } + public function testBackgroundScanNestedIncompleteFolders() { + $this->storage->mkdir('folder'); + $this->scanner->backgroundScan(); + + $this->storage->mkdir('folder/subfolder1'); + $this->storage->mkdir('folder/subfolder2'); + + $this->storage->mkdir('folder/subfolder1/subfolder3'); + $this->cache->put('folder', ['size' => -1]); + $this->cache->put('folder/subfolder1', ['size' => -1]); + + // do a scan to get the folders into the cache. + $this->scanner->backgroundScan(); + + $this->assertTrue($this->cache->inCache('folder/subfolder1/subfolder3')); + + $this->storage->file_put_contents('folder/subfolder1/bar1.txt', 'foobar'); + $this->storage->file_put_contents('folder/subfolder1/subfolder3/bar3.txt', 'foobar'); + $this->storage->file_put_contents('folder/subfolder2/bar2.txt', 'foobar'); + + //mark folders as incomplete. + $this->cache->put('folder/subfolder1', ['size' => -1]); + $this->cache->put('folder/subfolder2', ['size' => -1]); + $this->cache->put('folder/subfolder1/subfolder3', ['size' => -1]); + + $this->scanner->backgroundScan(); + + $this->assertTrue($this->cache->inCache('folder/subfolder1/bar1.txt')); + $this->assertTrue($this->cache->inCache('folder/subfolder2/bar2.txt')); + $this->assertTrue($this->cache->inCache('folder/subfolder1/subfolder3/bar3.txt')); + + //check if folder sizes are correct. + $this->assertEquals(18, $this->cache->get('folder')['size']); + $this->assertEquals(12, $this->cache->get('folder/subfolder1')['size']); + $this->assertEquals(6, $this->cache->get('folder/subfolder1/subfolder3')['size']); + $this->assertEquals(6, $this->cache->get('folder/subfolder2')['size']); + } + public function testReuseExisting() { $this->fillTestFolders(); |