-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow admin to blacklist groups for the files_sharing app #31672
Conversation
Codecov Report
@@ Coverage Diff @@
## master #31672 +/- ##
============================================
+ Coverage 63.16% 63.17% +0.01%
- Complexity 18444 18461 +17
============================================
Files 1158 1161 +3
Lines 69284 69327 +43
Branches 1260 1260
============================================
+ Hits 43760 43800 +40
- Misses 25155 25158 +3
Partials 369 369
Continue to review full report at Codecov.
|
1d21dda
to
28c782d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See review comments.
I'm not convinced about the need to have a cache here.
apps/files_sharing/js/settings.js
Outdated
*/ | ||
|
||
$(document).ready(function(){ | ||
$('#files_sharing .files_sharing_settings').change(function(){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so the generic functions didn't work ? might be possible to adjust the generic existing JS function for saving settings to include textarea
additionally to input
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not aware of any generic function to save values in the appconfig table. I've just found the OC.AppConfig.setValue
function to perform the operation (code already adjusted to use it, soon), but there isn't auto binding, so I'll need to bind the event manually.
@@ -396,6 +401,9 @@ public function createShare() { | |||
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); | |||
return new \OC\OCS\Result(null, 404, $this->l->t('Please specify a valid group')); | |||
} | |||
if ($this->sharingBlacklist->isGroupBlacklisted($this->groupManager->get($shareWith))) { | |||
return new \OC\OCS\Result(null, 403, $this->l->t('The group is blacklisted for sharing')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if this qualifies as information disclosure, maybe just return 404 "Please specify a valid group" as above
cc @settermjd
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly? I'm don't believe that this is information disclosure. However, something more generic, yet still informative, such as "This group is not available for sharing, would work better.
* Set with the known configuration keys. Only these keys should be able to be set and retrieved | ||
*/ | ||
private $knownKeys = [ | ||
'blacklisted_group_displaynames' => true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like early optimization/refactoring before it is even needed, but ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove the class because get and set for the appconfig table will go through core (using OC.Appconfig in js). No need to handle this on our own.
}, $groupBackendList); | ||
|
||
$tmpl = new Template('files_sharing', 'settings'); | ||
$tmpl->assign('backendNames', $backendNames); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great that you put this in 👍
use OCP\IConfig; | ||
use OCP\IGroup; | ||
|
||
class SharingBlacklist { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add PHPDoc. Is this the actual black list array or just a utility class ? This is what a developer wonders about when seeing only the class name used in various places
|
||
/** | ||
* Check if the target group is blacklisted | ||
* @param IGroup $group the group to check |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing @return
$blacklistedComponents = explode("\n", $configuredBlacklist); | ||
|
||
$result = []; | ||
foreach ($blacklistedComponents as $blacklistedComponent) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this operation so expensive that we need a cache with all complexity that comes with it ?
in general I don't think we expect more than 10 or so blacklisted groups
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the sharees we'll need to check if each group is blacklisted. This leads potentially to a lot of calls to this function.
Since the blacklist will be stored as a large string, each call will need to fetch the string and process it in order to make the decission. This might be heavy we need to check a lot of groups.
In addition, I'd rather not rely on the appconfig to be cached to have a decent performance.
I also don't expect a lot of blacklisted groups, but we might need to include more options for the blacklist, such as blacklist by group id, and I'd prefer the check to be as fast as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, fair enough. Make sure the unit tests also take the cache into account and prove that it properly invalidates, if applicable.
<?php endforeach; ?> | ||
</ul> | ||
</div> | ||
<textarea placeholder="OC\Group\Database::localGroups
OCA\User_LDAP\Group_Proxy::ldapGroup" class="files_sharing_settings" name="blacklisted_group_displaynames"><?php p($_['blacklistedDisplaynames']); ?></textarea> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's this weird 

entity here ? likely a newline. I suggest to put this in a template variable instead and initialize it from the PHP template in which you can write it in a more readable format that includes \n
Code should be mostly ready, but there are tests missing for the SettingsPanel.php and SharingBlacklist.php and the |
Documentation relevant |
$groupBackend = \get_class($group->getBackend()); | ||
$groupDisplayname = $group->getDisplayName(); | ||
|
||
if (isset($this->blacklistCache['displaynames'][$groupBackend][$groupDisplayname])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am still concerned we do the matching on the displaynames. Can't we store ID's but allow admins to specify displaynames?
6061a60
to
5364d96
Compare
5364d96
to
bf8f99d
Compare
Backport to stable10 in #31740 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
apart from the settings cache this looks good
} | ||
|
||
private function fetchBlacklistedReceiverGroupIds() { | ||
$configuredBlacklist = $this->config->getAppValue('files_sharing', 'blacklisted_receiver_groups', '[]'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we now removed the class names from the group ids, maybe we don't need the cache any more ?
based on @tomneedham's comment here #31578 (comment) we can use the same approach as "exclude groups from sharing" which also doesn't expect a higher number of groups than the blacklist.
also now the logic for reading the settings has simplified itself since we don't store the prefix any more
@jvillafanez can you remove the cache and read directly from the setting ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'll still need this. The problem is with sharees: we'll fetch a list of groups and each group needs to be checked whether is blacklisted or not; without proper caching we'll need to fetch the list from the configuration and then parse the list for each group.
Even if we rely on the configuration being cached, we still need to parse the stored json several times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there no way to change the caller code then to only fetch the blacklist array once ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might use a filterBlacklistedGroups(array $groups)
function, but the calls would be a bit weird:
- The share API just needs to check one group, so you'll need to wrap it in an array, which feels bad.
- For the sharees, the intention would be to return the groups that aren't blacklisted. However, in case we need to do something with the blacklisted groups (maybe log them) things will get complicated both for the caller and the implementation. Note that, in any case, we'll need to traverse the sharees list twice.
I agree the code is a bit complex, but it should be fine if this complexity is isolated in this class. On the other hand, the usage is simple. Maybe we can document in the class what is the expected usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was more thinking that the caller code calls "give me all the blacklisted groups", store it in a local var and then use that when checking against each group. Unless the caller codes is spread across various classes, this could work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll move the complexity to the callers. you still need to compare the target group with the blacklist. This comparison code will be spread into several classes, and the way you'll likely compare will use the in_array
function instead of more efficients approaches (you don't want to add more complexity to the caller).
Current code has a faster performance an a cleaner interface at a cost of including a cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Current code has a faster performance an a cleaner interface at a cost of including a cache.
Okay, let's leave it then
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks good.
What happens if shares exist that were shared with a group that has now been blacklisted ? I assume these must still appear in the shares list and be unshareable by users. I don't think we need to add a mechanism to also exclude existing shares/mounts that point to blacklisted groups.
@tomneedham thoughts on the latter ?
@jvillafanez let's move on with this. The additional concern, if addressed in any way, will not affect the code of this PR. |
I've added a comment here to address as part of the ticket/concept before we close it: #31578 (comment) |
@jvillafanez please adjust the backport PR and ping me there when done |
|
||
/** | ||
* Class to handle a blacklist for sharing. The main functionality is to check if a particular group | ||
* has been blacklisted for sharing, which means that noone should share with that group. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"should share", or be "should able to share"?
@@ -284,7 +290,7 @@ protected function getGroups($search) { | |||
foreach ($groups as $group) { | |||
// FIXME: use a more efficient approach |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't think of a more efficient approach off the top of my head. However, you could use an implementation of a FilterIterator to simplify the logic here, making it that much simpler to maintain over time.
return [ | ||
[$groupMock1, '["Mygroup"]'], | ||
[$groupMock1, '["Mygroup", "my_other_group"]'], | ||
[$groupMock1, '["one group", "Mygroup"]'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this, effectively, the same as the line above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There were issues in various places with group names that contain a space - maybe that was a concern here?
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Description
Allow admins to blacklist groups so user can't share with those groups. Blacklist will be done per backend + group displayname for now.
Related Issue
#31578
Motivation and Context
How Has This Been Tested?
Manually tested for now
Screenshots (if appropriate):
Types of changes
Checklist: