1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-08-05 13:35:25 +02:00

Improve Space permissions

Closes out loopholes that allowed managers to kick owners.
This commit is contained in:
McMatts 2018-11-16 19:18:10 +00:00
parent 09635b67ab
commit 5d632712e0
30 changed files with 1015 additions and 877 deletions

View file

@ -22,6 +22,7 @@ export default Component.extend(Notifier, Modals, {
subscription: null,
planCloud: false,
planSelfhost: false,
comment: 'Nothing in particular -- just passing through. Please close my Documize account.',
didReceiveAttrs() {
this._super(...arguments);
@ -47,10 +48,17 @@ export default Component.extend(Notifier, Modals, {
});
},
onRequestClosure() {
this.modalOpen("#deactivation-request-modal", {"show": true}, '#close-comment');
},
onDeactivate() {
this.get('global').deactivate().then(() => {
this.modalClose("#deactivation-request-modal");
let comment = this.get('comment');
this.get('global').deactivate(comment).then(() => {
this.showDone();
this.modalOpen("#deactivation-modal", {"show": true});
this.modalOpen("#deactivation-confirmation-modal", {"show": true});
});
}
}

View file

@ -0,0 +1,105 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import $ from 'jquery';
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import Notifier from '../../mixins/notifier';
import Modals from '../../mixins/modal';
import Component from '@ember/component';
export default Component.extend(Notifier, Modals, {
spaceSvc: service('folder'),
browserSvc: service('browser'),
documentSvc: service('document'),
spaces: null,
label: computed('model', function() {
switch (this.get('model').length) {
case 1:
return "space";
default:
return "spaces";
}
}),
init() {
this._super(...arguments);
this.loadData();
},
didReceiveAttrs() {
this._super(...arguments);
this.deleteSpace = {
id: '',
name: ''
};
},
loadData() {
this.get('spaceSvc').manage().then((s) => {
this.set('spaces', s);
});
},
actions: {
onShow(id) {
this.set('deleteSpace.id', id);
},
onDelete() {
let deleteSpace = this.get('deleteSpace');
let spaceId = deleteSpace.id;
let spaceNameTyped = deleteSpace.name;
let space = this.get('spaces').findBy('id', spaceId);
let spaceName = space.get('name');
if (spaceNameTyped !== spaceName || spaceNameTyped === '' || spaceName === '') {
$('#delete-space-name').addClass('is-invalid').focus();
return;
}
$('#space-delete-modal').modal('hide');
$('#space-delete-modal').modal('dispose');
this.get('spaceSvc').delete(spaceId).then(() => { /* jshint ignore:line */
this.set('deleteSpace.id', '');
this.set('deleteSpace.name', '');
this.loadData();
});
},
onExport() {
this.showWait();
let spec = {
spaceId: '',
data: _.pluck(this.get('folders'), 'id'),
filterType: 'space',
};
this.get('documentSvc').export(spec).then((htmlExport) => {
this.get('browserSvc').downloadFile(htmlExport, 'documize.html');
this.showDone();
});
},
onOwner(spaceId) {
this.showWait();
this.get('spaceSvc').grantOwnerPermission(spaceId).then(() => { /* jshint ignore:line */
this.showDone();
});
}
}
});

View file

@ -62,6 +62,15 @@ export default Component.extend(ModalMixin, TooltipMixin, Notifer, {
cat.set('documents', docCount);
cat.set('users', userCount);
});
this.get('categorySvc').getUserVisible(this.get('space.id')).then((cm) => {
cm.forEach((cm) => {
let cat = _.findWhere(c, {id: cm.get('id') });
if (is.not.undefined(cat)) {
cat.set('access', is.not.undefined(cat));
}
});
});
});
});
},

View file

@ -34,6 +34,9 @@ export default Component.extend(Notifier, Modals, {
isSpaceAdmin: computed('permissions', function() {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
}),
isNotSpaceOwner: computed('permissions', function() {
return !this.get('permissions.spaceOwner');
}),
didReceiveAttrs() {
this._super(...arguments);

View file

@ -29,7 +29,6 @@ export default Component.extend(AuthMixin, {
init() {
this._super(...arguments);
// this.filteredDocs = [];
this.setup();
},

View file

@ -9,85 +9,7 @@
//
// https://documize.com
import $ from 'jquery';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
import Notifier from '../../../mixins/notifier';
import TooltipMixin from '../../../mixins/tooltip';
import Controller from '@ember/controller';
export default Controller.extend(TooltipMixin, Notifier, {
folderService: service('folder'),
browserSvc: service('browser'),
documentSvc: service('document'),
dropdown: null,
init() {
this._super(...arguments);
this.folders = [];
this.deleteSpace = {
id: '',
name: ''
};
},
label: computed('folders', function() {
switch (this.get('folders').length) {
case 1:
return "space";
default:
return "spaces";
}
}),
actions: {
onShow(id) {
this.set('deleteSpace.id', id);
},
onDelete() {
let deleteSpace = this.get('deleteSpace');
let spaceId = deleteSpace.id;
let spaceNameTyped = deleteSpace.name;
let space = this.get('folders').findBy('id', spaceId);
let spaceName = space.get('name');
if (spaceNameTyped !== spaceName || spaceNameTyped === '' || spaceName === '') {
$('#delete-space-name').addClass('is-invalid').focus();
return;
}
$('#space-delete-modal').modal('hide');
$('#space-delete-modal').modal('dispose');
this.get('folderService').delete(spaceId).then(() => { /* jshint ignore:line */
this.set('deleteSpace.id', '');
this.set('deleteSpace.name', '');
this.get('folderService').adminList().then((folders) => {
let nonPrivateFolders = folders.rejectBy('spaceType', 2);
if (is.empty(nonPrivateFolders) || is.null(folders) || is.undefined(folders)) {
nonPrivateFolders = [];
}
this.set('folders', nonPrivateFolders);
});
});
},
onExport() {
this.showWait();
let spec = {
spaceId: '',
data: _.pluck(this.get('folders'), 'id'),
filterType: 'space',
};
this.get('documentSvc').export(spec).then((htmlExport) => {
this.get('browserSvc').downloadFile(htmlExport, 'documize.html');
this.showDone();
});
}
}
export default Controller.extend({
});

View file

@ -9,33 +9,17 @@
//
// https://documize.com
import { inject as service } from '@ember/service';
import Route from '@ember/routing/route';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
import Route from '@ember/routing/route';
export default Route.extend(AuthenticatedRouteMixin, {
folderService: service('folder'),
beforeModel() {
if (!this.session.isAdmin) {
this.transitionTo('auth.login');
}
},
model() {
return this.get('folderService').adminList();
},
setupController(controller, model) {
let nonPrivateFolders = model.rejectBy('spaceType', 2);
if (is.empty(nonPrivateFolders) || is.null(model) || is.undefined(model)) {
nonPrivateFolders = [];
}
controller.set('folders', nonPrivateFolders);
},
activate() {
this.get('browser').setTitle('Spaces');
this.get('browser').setTitle('Manage Spaces');
}
});

View file

@ -1,50 +1 @@
{{#if folders}}
<div class="row">
<div class="col">
<div class="view-customize">
<h1 class="admin-heading">{{folders.length}} shared {{label}}</h1>
<button type="button" class="btn btn-success" onclick={{action 'onExport'}}>Export as HTML</button>
</div>
</div>
</div>
<div class="view-customize">
<div class="space-list">
{{#each folders as |folder|}}
<div class="space row">
<div class="col-12 col-sm-8">
{{#link-to 'folder' folder.id folder.slug class="alt"}}{{folder.name}}{{/link-to}}
</div>
<div class="col-12 col-sm-4 text-right">
<div id="space-delete-button-{{folder.id}}" class="button-icon-danger align-middle" data-toggle="tooltip" data-placement="top" title="Delete space" {{action "onShow" folder.id}}>
<i class="material-icons" data-toggle="modal" data-target="#space-delete-modal" data-backdrop="static">delete</i>
</div>
</div>
</div>
{{/each}}
</div>
</div>
<div id="space-delete-modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">Space Deletion</div>
<div class="modal-body">
<form onsubmit={{action 'onDelete'}}>
<p>Are you sure you want to delete this space and all documents?</p>
<div class="form-group">
<label for="delete-space-name">Please type space name to confirm</label>
{{input type='text' id="delete-space-name" class="form-control mousetrap" placeholder="Space name" value=deleteSpace.name}}
<small class="form-text text-muted">This will delete all documents and templates within this space!</small>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" onclick={{action 'onDelete'}}>Delete</button>
</div>
</div>
</div>
</div>
{{else}}
<p>There are no spaces to maintain</p>
{{/if}}
{{customize/space-admin}}

View file

@ -84,7 +84,7 @@ export default Controller.extend(Tooltips, Notifier, {
let constants = this.get('constants');
// if document approval mode is locked return
if (document.get('protection') == constants.ProtectionType.Lock) {
if (document.get('protection') === constants.ProtectionType.Lock) {
// should not really happen
return;
}

View file

@ -335,7 +335,7 @@ export default Service.extend({
} else {
let id = this.get('storageSvc').getSessionItem('anonId');
if (is.not.null(id) && is.not.undefined(id) && id.length === 16) {
if (is.not.null(id) && is.not.undefined(id) && id.length >= 16) {
userId = id;
} else {
userId = stringUtil.anonUserId();

View file

@ -174,8 +174,9 @@ export default BaseService.extend({
});
},
// returns all spaces -- for use by documize admin user
adminList() {
// Returns all shared spaces and spaces without an owner.
// Administrator only method.
manage() {
return this.get('ajax').request(`space/manage`, {
method: "GET"
}).then((response) => {
@ -189,5 +190,12 @@ export default BaseService.extend({
return data;
});
}
},
// Add admin as space owner.
grantOwnerPermission(folderId) {
return this.get('ajax').request(`space/manage/owner/${folderId}`, {
method: 'POST',
});
},
});

View file

@ -236,10 +236,12 @@ export default Service.extend({
});
},
deactivate() {
deactivate(comment) {
if(this.get('sessionService.isAdmin')) {
return this.get('ajax').request(`deactivate`, {
method: 'POST',
contentType: 'text',
data: comment,
}).then(() => {
return;
});

View file

@ -65,13 +65,11 @@
> .space-list {
padding: 0;
margin: 0;
margin: 3rem 0;
> .space {
margin: 15px 0;
padding: 15px;
@include card();
@include ease-in();
padding: 15px 0;
font-size: 1.2rem;
color: $color-primary;
}

View file

@ -143,7 +143,7 @@
<p>Requests can take up to 24 hours to process.</p>
{{#link-to 'customize.backup' class="btn btn-success"}}PERFORM BACKUP{{/link-to}}
<div class="button-gap" />
<button class="btn btn-danger" {{action 'onDeactivate'}}>REQUEST ACCOUNT CLOSURE</button>
<button class="btn btn-danger" {{action 'onRequestClosure'}}>CLOSE ACCOUNT</button>
</div>
</div>
</div>
@ -151,16 +151,37 @@
{{/if}}
{{/if}}
<div id="deactivation-modal" class="modal" tabindex="-1" role="dialog">
<div id="deactivation-request-modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">Request Account Closure</div>
<div class="modal-body">
<form {{action "onEditComment" on="submit"}}>
<div class="form-group">
<label for="the-comment">Comment</label>
{{focus-textarea id="close-comment" class="form-control" rows="5" value=comment}}
<small class="form-text text-muted">We always welcome product feedback</small>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" onclick={{action 'onDeactivate'}}>Close Account</button>
</div>
</div>
</div>
</div>
<div id="deactivation-confirmation-modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">Deactivation Requested</div>
<div class="modal-body">
<p>Your request has been sent and will be processed shortly.</p>
<p>If you haven't already, perform a backup to download all your data.</p>
<p>If you haven't already, please run a backup to download all your data.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">ok</button>
</div>
</div>
</div>

View file

@ -0,0 +1,58 @@
<div class="row">
<div class="col">
<div class="view-customize">
<h1 class="admin-heading">Manage Spaces</h1>
<h2 class="sub-heading">Delete spaces, take ownership of shared spaces and orphaned spaces</h2>
</div>
</div>
</div>
<div class="view-customize my-5">
{{#if spaces}}
<button type="button" class="btn btn-success" onclick={{action 'onExport'}}>Export content</button>
<div class="space-list">
{{#each spaces as |space|}}
<div class="space row">
<div class="col-12 col-sm-8">
{{#link-to 'folder' space.id space.slug class="alt"}}{{space.name}}{{/link-to}}
</div>
<div class="col-12 col-sm-4 text-right">
<div id="space-ownership-button-{{space.id}}" class="button-icon-gray align-middle" data-toggle="tooltip" data-placement="top" title="Add myself as space owner" {{action "onOwner" space.id}}>
<i class="material-icons" data-toggle="modal">person_add</i>
</div>
<div class="button-icon-gap" />
<div id="space-delete-button-{{space.id}}" class="button-icon-danger align-middle" data-toggle="tooltip" data-placement="top" title="Delete space and all content" {{action "onShow" space.id}}>
<i class="material-icons" data-toggle="modal" data-target="#space-delete-modal" data-backdrop="static">delete</i>
</div>
</div>
</div>
{{/each}}
</div>
<div id="space-delete-modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">Space Deletion</div>
<div class="modal-body">
<form onsubmit={{action 'onDelete'}}>
<p>Are you sure you want to delete this space and all documents?</p>
<div class="form-group">
<label for="delete-space-name">Please type space name to confirm</label>
{{input type='text' id="delete-space-name" class="form-control mousetrap" placeholder="Space name" value=deleteSpace.name}}
<small class="form-text text-muted">This will delete all documents and templates within this space!</small>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" onclick={{action 'onDelete'}}>Delete</button>
</div>
</div>
</div>
</div>
{{else}}
<p>There are no shared spaces to manage</p>
{{/if}}
</div>

View file

@ -19,7 +19,10 @@
{{else}}
<div class="category col-8">
<div class="name">{{cat.category}}</div>
<div class="info">{{cat.documents}} {{if (eq cat.documents 1) 'document' 'documents' }} &middot; {{cat.users}} users/groups</div>
<div class="info">
{{cat.documents}} {{if (eq cat.documents 1) 'document' 'documents' }} &middot; {{cat.users}} users/groups
{{#unless cat.access}}<span class="text-danger">(you have no view permission)</span>{{/unless}}
</div>
</div>
{{/if}}
<div class="col-4 buttons text-right">

View file

@ -74,7 +74,7 @@
</td>
<td>{{x-toggle value=permission.spaceView onToggle=(action (mut permission.spaceView))}}</td>
<td>{{x-toggle value=permission.spaceManage onToggle=(action (mut permission.spaceManage))}}</td>
<td>{{x-toggle value=permission.spaceOwner onToggle=(action (mut permission.spaceOwner))}}</td>
<td>{{x-toggle value=permission.spaceOwner onToggle=(action (mut permission.spaceOwner)) disabled=isNotSpaceOwner}}</td>
<td>{{x-toggle value=permission.documentAdd onToggle=(action (mut permission.documentAdd))}}</td>
<td>{{x-toggle value=permission.documentEdit onToggle=(action (mut permission.documentEdit))}}</td>
<td>{{x-toggle value=permission.documentDelete onToggle=(action (mut permission.documentDelete))}}</td>

View file

@ -1,6 +1,6 @@
{
"name": "documize",
"version": "1.76.0",
"version": "1.76.1",
"description": "The Document IDE",
"private": true,
"repository": "",