Merge pull request #2512 from nextcloud/cleanup-system-tag-usage

Only allow admins to delete tags
This commit is contained in:
Roeland Jago Douma 2017-01-06 16:17:09 +01:00 committed by GitHub
commit 6347d97c7f
5 changed files with 103 additions and 53 deletions

View File

@ -157,12 +157,13 @@ class SystemTagNode implements \Sabre\DAV\INode {
public function delete() { public function delete() {
try { try {
if (!$this->isAdmin) {
throw new Forbidden('No permission to delete tag ' . $this->tag->getId());
}
if (!$this->tagManager->canUserSeeTag($this->tag, $this->user)) { if (!$this->tagManager->canUserSeeTag($this->tag, $this->user)) {
throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found'); throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found');
} }
if (!$this->tagManager->canUserAssignTag($this->tag, $this->user)) {
throw new Forbidden('No permission to delete tag ' . $this->tag->getId());
}
$this->tagManager->deleteTags($this->tag->getId()); $this->tagManager->deleteTags($this->tag->getId());
} catch (TagNotFoundException $e) { } catch (TagNotFoundException $e) {

View File

@ -24,19 +24,17 @@
namespace OCA\DAV\Tests\unit\SystemTag; namespace OCA\DAV\Tests\unit\SystemTag;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAV\Exception\Conflict;
use OC\SystemTag\SystemTag; use OC\SystemTag\SystemTag;
use OCP\SystemTag\TagNotFoundException; use OCP\SystemTag\TagNotFoundException;
use OCP\SystemTag\TagAlreadyExistsException; use OCP\SystemTag\TagAlreadyExistsException;
use OCP\SystemTag\ISystemTag; use OCP\SystemTag\ISystemTag;
use Sabre\DAV\Exception\Forbidden;
class SystemTagNodeTest extends \Test\TestCase { class SystemTagNodeTest extends \Test\TestCase {
/** /**
* @var \OCP\SystemTag\ISystemTagManager * @var \OCP\SystemTag\ISystemTagManager|\PHPUnit_Framework_MockObject_MockObject
*/ */
private $tagManager; private $tagManager;
@ -113,7 +111,7 @@ class SystemTagNodeTest extends \Test\TestCase {
/** /**
* @dataProvider tagNodeProvider * @dataProvider tagNodeProvider
*/ */
public function testUpdateTag($isAdmin, $originalTag, $changedArgs) { public function testUpdateTag($isAdmin, ISystemTag $originalTag, $changedArgs) {
$this->tagManager->expects($this->once()) $this->tagManager->expects($this->once())
->method('canUserSeeTag') ->method('canUserSeeTag')
->with($originalTag) ->with($originalTag)
@ -173,7 +171,7 @@ class SystemTagNodeTest extends \Test\TestCase {
/** /**
* @dataProvider tagNodeProviderPermissionException * @dataProvider tagNodeProviderPermissionException
*/ */
public function testUpdateTagPermissionException($originalTag, $changedArgs, $expectedException = null) { public function testUpdateTagPermissionException(ISystemTag $originalTag, $changedArgs, $expectedException = null) {
$this->tagManager->expects($this->any()) $this->tagManager->expects($this->any())
->method('canUserSeeTag') ->method('canUserSeeTag')
->with($originalTag) ->with($originalTag)
@ -242,17 +240,16 @@ class SystemTagNodeTest extends \Test\TestCase {
*/ */
public function testDeleteTag($isAdmin) { public function testDeleteTag($isAdmin) {
$tag = new SystemTag(1, 'tag1', true, true); $tag = new SystemTag(1, 'tag1', true, true);
$this->tagManager->expects($this->once()) $this->tagManager->expects($isAdmin ? $this->once() : $this->never())
->method('canUserSeeTag') ->method('canUserSeeTag')
->with($tag) ->with($tag)
->will($this->returnValue(true)); ->will($this->returnValue(true));
$this->tagManager->expects($this->once()) $this->tagManager->expects($isAdmin ? $this->once() : $this->never())
->method('canUserAssignTag')
->with($tag)
->will($this->returnValue(true));
$this->tagManager->expects($this->once())
->method('deleteTags') ->method('deleteTags')
->with('1'); ->with('1');
if (!$isAdmin) {
$this->setExpectedException(Forbidden::class);
}
$this->getTagNode($isAdmin, $tag)->delete(); $this->getTagNode($isAdmin, $tag)->delete();
} }
@ -261,7 +258,7 @@ class SystemTagNodeTest extends \Test\TestCase {
[ [
// cannot delete invisible tag // cannot delete invisible tag
new SystemTag(1, 'Original', false, true), new SystemTag(1, 'Original', false, true),
'Sabre\DAV\Exception\NotFound', 'Sabre\DAV\Exception\Forbidden',
], ],
[ [
// cannot delete non-assignable tag // cannot delete non-assignable tag
@ -279,20 +276,11 @@ class SystemTagNodeTest extends \Test\TestCase {
->method('canUserSeeTag') ->method('canUserSeeTag')
->with($tag) ->with($tag)
->will($this->returnValue($tag->isUserVisible())); ->will($this->returnValue($tag->isUserVisible()));
$this->tagManager->expects($this->any())
->method('canUserAssignTag')
->with($tag)
->will($this->returnValue($tag->isUserAssignable()));
$this->tagManager->expects($this->never()) $this->tagManager->expects($this->never())
->method('deleteTags'); ->method('deleteTags');
try { $this->setExpectedException($expectedException);
$this->getTagNode(false, $tag)->delete(); $this->getTagNode(false, $tag)->delete();
} catch (\Exception $e) {
$thrown = $e;
}
$this->assertInstanceOf($expectedException, $thrown);
} }
/** /**
@ -304,14 +292,10 @@ class SystemTagNodeTest extends \Test\TestCase {
->method('canUserSeeTag') ->method('canUserSeeTag')
->with($tag) ->with($tag)
->will($this->returnValue($tag->isUserVisible())); ->will($this->returnValue($tag->isUserVisible()));
$this->tagManager->expects($this->any())
->method('canUserAssignTag')
->with($tag)
->will($this->returnValue($tag->isUserAssignable()));
$this->tagManager->expects($this->once()) $this->tagManager->expects($this->once())
->method('deleteTags') ->method('deleteTags')
->with('1') ->with('1')
->will($this->throwException(new TagNotFoundException())); ->will($this->throwException(new TagNotFoundException()));
$this->getTagNode(false, $tag)->delete(); $this->getTagNode(true, $tag)->delete();
} }
} }

View File

@ -70,12 +70,13 @@ Feature: tags
When "user0" edits the tag with name "TagWithGroups" and sets its groups to "group1|group3" When "user0" edits the tag with name "TagWithGroups" and sets its groups to "group1|group3"
Then The response should have a status code "403" Then The response should have a status code "403"
Scenario: Deleting a normal tag as regular user should work Scenario: Deleting a normal tag as regular user should fail
Given user "user0" exists Given user "user0" exists
Given "admin" creates a "normal" tag with name "MySuperAwesomeTagName" Given "admin" creates a "normal" tag with name "MySuperAwesomeTagName"
When "user0" deletes the tag with name "MySuperAwesomeTagName" When "user0" deletes the tag with name "MySuperAwesomeTagName"
Then The response should have a status code "204" Then The response should have a status code "403"
And "0" tags should exist for "admin" And The following tags should exist for "admin"
|MySuperAwesomeTagName|true|true|
Scenario: Deleting a not user-assignable tag as regular user should fail Scenario: Deleting a not user-assignable tag as regular user should fail
Given user "user0" exists Given user "user0" exists
@ -93,6 +94,12 @@ Feature: tags
And The following tags should exist for "admin" And The following tags should exist for "admin"
|MySuperAwesomeTagName|false|true| |MySuperAwesomeTagName|false|true|
Scenario: Deleting a normal tag as admin should work
Given "admin" creates a "normal" tag with name "MySuperAwesomeTagName"
When "admin" deletes the tag with name "MySuperAwesomeTagName"
Then The response should have a status code "204"
And "0" tags should exist for "admin"
Scenario: Deleting a not user-assignable tag as admin should work Scenario: Deleting a not user-assignable tag as admin should work
Given "admin" creates a "not user-assignable" tag with name "MySuperAwesomeTagName" Given "admin" creates a "not user-assignable" tag with name "MySuperAwesomeTagName"
When "admin" deletes the tag with name "MySuperAwesomeTagName" When "admin" deletes the tag with name "MySuperAwesomeTagName"

View File

@ -40,7 +40,9 @@
'<form class="systemtags-rename-form">' + '<form class="systemtags-rename-form">' +
' <label class="hidden-visually" for="{{cid}}-rename-input">{{renameLabel}}</label>' + ' <label class="hidden-visually" for="{{cid}}-rename-input">{{renameLabel}}</label>' +
' <input id="{{cid}}-rename-input" type="text" value="{{name}}">' + ' <input id="{{cid}}-rename-input" type="text" value="{{name}}">' +
' <a href="#" class="delete icon icon-delete" title="{{deleteTooltip}}"></a>' + ' {{#if isAdmin}}' +
' <a href="#" class="delete icon icon-delete" title="{{deleteTooltip}}"></a>' +
' {{/if}}' +
'</form>'; '</form>';
/** /**
@ -148,7 +150,8 @@
cid: this.cid, cid: this.cid,
name: oldName, name: oldName,
deleteTooltip: t('core', 'Delete'), deleteTooltip: t('core', 'Delete'),
renameLabel: t('core', 'Rename') renameLabel: t('core', 'Rename'),
isAdmin: this._isAdmin
})); }));
$item.find('.label').after($renameForm); $item.find('.label').after($renameForm);
$item.find('.label, .systemtags-actions').addClass('hidden'); $item.find('.label, .systemtags-actions').addClass('hidden');

View File

@ -267,20 +267,6 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() {
saveStub.restore(); saveStub.restore();
}); });
it('deletes model and submits change when clicking delete', function() {
var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy');
expect($dropdown.find('.delete').length).toEqual(0);
$dropdown.find('.rename').mouseup();
// delete button appears
expect($dropdown.find('.delete').length).toEqual(1);
$dropdown.find('.delete').mouseup();
expect(destroyStub.calledOnce).toEqual(true);
expect(destroyStub.calledOn(view.collection.get('1')));
destroyStub.restore();
});
}); });
describe('setting data', function() { describe('setting data', function() {
it('sets value when calling setValues', function() { it('sets value when calling setValues', function() {
@ -299,12 +285,18 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() {
}); });
describe('as admin', function() { describe('as admin', function() {
var $dropdown;
beforeEach(function() { beforeEach(function() {
view = new OC.SystemTags.SystemTagsInputField({ view = new OC.SystemTags.SystemTagsInputField({
isAdmin: true isAdmin: true
}); });
view.render();
$('.testInputContainer').append(view.$el); $('.testInputContainer').append(view.$el);
$dropdown = $('<div class="select2-dropdown"></div>');
select2Stub.withArgs('dropdown').returns($dropdown);
$('#testArea').append($dropdown);
view.render();
}); });
it('formatResult renders tag name with visibility', function() { it('formatResult renders tag name with visibility', function() {
var opts = select2Stub.getCall(0).args[0]; var opts = select2Stub.getCall(0).args[0];
@ -431,15 +423,50 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() {
]); ]);
}); });
}); });
describe('tag actions', function() {
var opts;
beforeEach(function() {
opts = select2Stub.getCall(0).args[0];
view.collection.add([
new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}),
]);
$dropdown.append(opts.formatResult(view.collection.get('1').toJSON()));
});
it('deletes model and submits change when clicking delete', function() {
var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy');
expect($dropdown.find('.delete').length).toEqual(0);
$dropdown.find('.rename').mouseup();
// delete button appears
expect($dropdown.find('.delete').length).toEqual(1);
$dropdown.find('.delete').mouseup();
expect(destroyStub.calledOnce).toEqual(true);
expect(destroyStub.calledOn(view.collection.get('1')));
destroyStub.restore();
});
});
}); });
describe('as user', function() { describe('as user', function() {
var $dropdown;
beforeEach(function() { beforeEach(function() {
view = new OC.SystemTags.SystemTagsInputField({ view = new OC.SystemTags.SystemTagsInputField({
isAdmin: false isAdmin: false
}); });
view.render();
$('.testInputContainer').append(view.$el); $('.testInputContainer').append(view.$el);
$dropdown = $('<div class="select2-dropdown"></div>');
select2Stub.withArgs('dropdown').returns($dropdown);
$('#testArea').append($dropdown);
view.render();
}); });
it('formatResult renders tag name only', function() { it('formatResult renders tag name only', function() {
var opts = select2Stub.getCall(0).args[0]; var opts = select2Stub.getCall(0).args[0];
@ -570,5 +597,33 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() {
]); ]);
}); });
}); });
describe('tag actions', function() {
var opts;
beforeEach(function() {
opts = select2Stub.getCall(0).args[0];
view.collection.add([
new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}),
]);
$dropdown.append(opts.formatResult(view.collection.get('1').toJSON()));
});
it('deletes model and submits change when clicking delete', function() {
var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy');
expect($dropdown.find('.delete').length).toEqual(0);
$dropdown.find('.rename').mouseup();
// delete button appears only for admins
expect($dropdown.find('.delete').length).toEqual(0);
$dropdown.find('.delete').mouseup();
expect(destroyStub.notCalled).toEqual(true);
destroyStub.restore();
});
});
}); });
}); });