Merge pull request #9919 from nextcloud/immutable-groups
Don't pretend we can add/remove users to/from groups when we can't
This commit is contained in:
commit
7fdba6f607
|
@ -115,7 +115,9 @@ class GroupsController extends AUserData {
|
||||||
'id' => $group->getGID(),
|
'id' => $group->getGID(),
|
||||||
'displayname' => $group->getDisplayName(),
|
'displayname' => $group->getDisplayName(),
|
||||||
'usercount' => $group->count(),
|
'usercount' => $group->count(),
|
||||||
'disabled' => $group->countDisabled()
|
'disabled' => $group->countDisabled(),
|
||||||
|
'canAdd' => $group->canAddUser(),
|
||||||
|
'canRemove' => $group->canRemoveUser(),
|
||||||
];
|
];
|
||||||
}, $groups);
|
}, $groups);
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,12 @@ class GroupsControllerTest extends \Test\TestCase {
|
||||||
$group
|
$group
|
||||||
->method('countDisabled')
|
->method('countDisabled')
|
||||||
->willReturn(11);
|
->willReturn(11);
|
||||||
|
$group
|
||||||
|
->method('canAddUser')
|
||||||
|
->willReturn(true);
|
||||||
|
$group
|
||||||
|
->method('canRemoveUser')
|
||||||
|
->willReturn(true);
|
||||||
|
|
||||||
return $group;
|
return $group;
|
||||||
}
|
}
|
||||||
|
@ -215,13 +221,18 @@ class GroupsControllerTest extends \Test\TestCase {
|
||||||
'id' => 'group1',
|
'id' => 'group1',
|
||||||
'displayname' => 'group1-name',
|
'displayname' => 'group1-name',
|
||||||
'usercount' => 123,
|
'usercount' => 123,
|
||||||
'disabled' => 11
|
'disabled' => 11,
|
||||||
|
'canAdd' => true,
|
||||||
|
'canRemove' => true
|
||||||
),
|
),
|
||||||
Array(
|
Array(
|
||||||
'id' => 'group2',
|
'id' => 'group2',
|
||||||
'displayname' => 'group2-name',
|
'displayname' => 'group2-name',
|
||||||
'usercount' => 123,
|
'usercount' => 123,
|
||||||
'disabled' => 11
|
'disabled' => 11,
|
||||||
|
'canAdd' => true,
|
||||||
|
'canRemove' => true
|
||||||
|
|
||||||
)
|
)
|
||||||
]], $result->getData());
|
]], $result->getData());
|
||||||
|
|
||||||
|
|
|
@ -782,7 +782,7 @@ input {
|
||||||
color: nc-lighten($color-main-text, 33%);
|
color: nc-lighten($color-main-text, 33%);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
/* selected checkmark icon */
|
/* selected checkmark icon */
|
||||||
&:not(.multiselect__option--disabled)::before {
|
&::before {
|
||||||
content: ' ';
|
content: ' ';
|
||||||
background-image: url('../img/actions/checkmark.svg?v=1');
|
background-image: url('../img/actions/checkmark.svg?v=1');
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
@ -790,12 +790,13 @@ input {
|
||||||
min-width: 16px;
|
min-width: 16px;
|
||||||
min-height: 16px;
|
min-height: 16px;
|
||||||
display: block;
|
display: block;
|
||||||
opacity: 0.5;
|
opacity: .5;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
&.multiselect__option--disabled {
|
&.multiselect__option--disabled {
|
||||||
background-color: nc-darken($color-main-background, 8%);
|
background-color: nc-darken($color-main-background, 8%);
|
||||||
|
opacity: .5;
|
||||||
}
|
}
|
||||||
/* add the prop tag-placeholder="create" to add the +
|
/* add the prop tag-placeholder="create" to add the +
|
||||||
* icon on top of an unknown-and-ready-to-be-created entry
|
* icon on top of an unknown-and-ready-to-be-created entry
|
||||||
|
@ -809,11 +810,15 @@ input {
|
||||||
&.multiselect__option--highlight {
|
&.multiselect__option--highlight {
|
||||||
color: $color-main-text;
|
color: $color-main-text;
|
||||||
}
|
}
|
||||||
&.multiselect__option--selected {
|
&:not(.multiselect__option--disabled):hover::before {
|
||||||
&::before {
|
opacity: .3;
|
||||||
visibility: visible;
|
}
|
||||||
}
|
&.multiselect__option--selected,
|
||||||
}
|
&:not(.multiselect__option--disabled):hover {
|
||||||
|
&::before {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
|
|
||||||
namespace OC\Group;
|
namespace OC\Group;
|
||||||
|
|
||||||
|
use OCP\GroupInterface;
|
||||||
use OCP\IGroup;
|
use OCP\IGroup;
|
||||||
use OCP\IUser;
|
use OCP\IUser;
|
||||||
use OCP\Group\Backend\ICountDisabledInGroup;
|
use OCP\Group\Backend\ICountDisabledInGroup;
|
||||||
|
@ -323,4 +324,30 @@ class Group implements IGroup {
|
||||||
}
|
}
|
||||||
return $users;
|
return $users;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
* @since 14.0.0
|
||||||
|
*/
|
||||||
|
public function canRemoveUser() {
|
||||||
|
foreach ($this->backends as $backend) {
|
||||||
|
if ($backend->implementsActions(GroupInterface::REMOVE_FROM_GOUP)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
* @since 14.0.0
|
||||||
|
*/
|
||||||
|
public function canAddUser() {
|
||||||
|
foreach ($this->backends as $backend) {
|
||||||
|
if ($backend->implementsActions(GroupInterface::ADD_TO_GROUP)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,7 +165,9 @@ class MetaData {
|
||||||
'id' => $group->getGID(),
|
'id' => $group->getGID(),
|
||||||
'name' => $group->getDisplayName(),
|
'name' => $group->getDisplayName(),
|
||||||
'usercount' => $this->sorting === self::SORT_USERCOUNT ? $group->count($userSearch) : 0,
|
'usercount' => $this->sorting === self::SORT_USERCOUNT ? $group->count($userSearch) : 0,
|
||||||
'disabled' => $group->countDisabled()
|
'disabled' => $group->countDisabled(),
|
||||||
|
'canAdd' => $group->canAddUser(),
|
||||||
|
'canRemove' => $group->canRemoveUser(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -126,4 +126,16 @@ interface IGroup {
|
||||||
* @since 8.0.0
|
* @since 8.0.0
|
||||||
*/
|
*/
|
||||||
public function delete();
|
public function delete();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
* @since 14.0.0
|
||||||
|
*/
|
||||||
|
public function canRemoveUser();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
* @since 14.0.0
|
||||||
|
*/
|
||||||
|
public function canAddUser();
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -75,7 +75,7 @@
|
||||||
<!-- hidden input trick for vanilla html5 form validation -->
|
<!-- hidden input trick for vanilla html5 form validation -->
|
||||||
<input type="text" :value="newUser.groups" v-if="!settings.isAdmin"
|
<input type="text" :value="newUser.groups" v-if="!settings.isAdmin"
|
||||||
tabindex="-1" id="newgroups" :required="!settings.isAdmin" />
|
tabindex="-1" id="newgroups" :required="!settings.isAdmin" />
|
||||||
<multiselect :options="groups" v-model="newUser.groups"
|
<multiselect :options="canAddGroups" v-model="newUser.groups"
|
||||||
:placeholder="t('settings', 'Add user in group')"
|
:placeholder="t('settings', 'Add user in group')"
|
||||||
label="name" track-by="id" class="multiselect-vue"
|
label="name" track-by="id" class="multiselect-vue"
|
||||||
:multiple="true" :close-on-select="false"
|
:multiple="true" :close-on-select="false"
|
||||||
|
@ -202,7 +202,7 @@ export default {
|
||||||
return disabledUsers;
|
return disabledUsers;
|
||||||
}
|
}
|
||||||
if (!this.settings.isAdmin) {
|
if (!this.settings.isAdmin) {
|
||||||
// We don't want subadmins to edit themselves
|
// we don't want subadmins to edit themselves
|
||||||
return this.users.filter(user => user.enabled !== false && user.id !== oc_current_user);
|
return this.users.filter(user => user.enabled !== false && user.id !== oc_current_user);
|
||||||
}
|
}
|
||||||
return this.users.filter(user => user.enabled !== false);
|
return this.users.filter(user => user.enabled !== false);
|
||||||
|
@ -213,6 +213,16 @@ export default {
|
||||||
.filter(group => group.id !== 'disabled')
|
.filter(group => group.id !== 'disabled')
|
||||||
.sort((a, b) => a.name.localeCompare(b.name));
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
},
|
},
|
||||||
|
canAddGroups() {
|
||||||
|
// disabled if no permission to add new users to group
|
||||||
|
return this.groups.map((group) => {
|
||||||
|
// clone object because we don't want
|
||||||
|
// to edit the original groups
|
||||||
|
group = Object.assign({}, group);
|
||||||
|
group.$isDisabled = group.canAdd !== true;
|
||||||
|
return group;
|
||||||
|
});
|
||||||
|
},
|
||||||
subAdminsGroups() {
|
subAdminsGroups() {
|
||||||
// data provided php side
|
// data provided php side
|
||||||
return this.$store.getters.getSubadminGroups;
|
return this.$store.getters.getSubadminGroups;
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
<input type="submit" class="icon-confirm" value="" />
|
<input type="submit" class="icon-confirm" value="" />
|
||||||
</form>
|
</form>
|
||||||
<div class="groups" :class="{'icon-loading-small': loading.groups}">
|
<div class="groups" :class="{'icon-loading-small': loading.groups}">
|
||||||
<multiselect :value="userGroups" :options="groups" :disabled="loading.groups||loading.all"
|
<multiselect :value="userGroups" :options="availableGroups" :disabled="loading.groups||loading.all"
|
||||||
tag-placeholder="create" :placeholder="t('settings', 'Add user in group')"
|
tag-placeholder="create" :placeholder="t('settings', 'Add user in group')"
|
||||||
label="name" track-by="id" class="multiselect-vue" :limit="2"
|
label="name" track-by="id" class="multiselect-vue" :limit="2"
|
||||||
:multiple="true" :taggable="settings.isAdmin" :closeOnSelect="false"
|
:multiple="true" :taggable="settings.isAdmin" :closeOnSelect="false"
|
||||||
|
@ -182,6 +182,23 @@ export default {
|
||||||
let userSubAdminsGroups = this.subAdminsGroups.filter(group => this.user.subadmin.includes(group.id));
|
let userSubAdminsGroups = this.subAdminsGroups.filter(group => this.user.subadmin.includes(group.id));
|
||||||
return userSubAdminsGroups;
|
return userSubAdminsGroups;
|
||||||
},
|
},
|
||||||
|
availableGroups() {
|
||||||
|
return this.groups.map((group) => {
|
||||||
|
// clone object because we don't want
|
||||||
|
// to edit the original groups
|
||||||
|
let groupClone = Object.assign({}, group);
|
||||||
|
|
||||||
|
// two settings here:
|
||||||
|
// 1. user NOT in group but no permission to add
|
||||||
|
// 2. user is in group but no permission to remove
|
||||||
|
groupClone.$isDisabled =
|
||||||
|
(group.canAdd !== true &&
|
||||||
|
!this.user.groups.includes(group.id)) ||
|
||||||
|
(group.canRemove !== true &&
|
||||||
|
this.user.groups.includes(group.id));
|
||||||
|
return groupClone;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/* QUOTA MANAGEMENT */
|
/* QUOTA MANAGEMENT */
|
||||||
usedQuota() {
|
usedQuota() {
|
||||||
|
@ -374,6 +391,9 @@ export default {
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
addUserGroup(group) {
|
addUserGroup(group) {
|
||||||
|
if (!group.canAdd) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
this.loading.groups = true;
|
this.loading.groups = true;
|
||||||
let userid = this.user.id;
|
let userid = this.user.id;
|
||||||
let gid = group.id;
|
let gid = group.id;
|
||||||
|
@ -388,6 +408,9 @@ export default {
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
removeUserGroup(group) {
|
removeUserGroup(group) {
|
||||||
|
if (!group.canRemove) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
this.loading.groups = true;
|
this.loading.groups = true;
|
||||||
let userid = this.user.id;
|
let userid = this.user.id;
|
||||||
let gid = group.id;
|
let gid = group.id;
|
||||||
|
|
Loading…
Reference in New Issue