1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-19 13:19:43 +02:00

Persist space permissions for groups and users

This commit is contained in:
sauls8t 2018-03-03 17:46:29 +00:00
parent 0d39f7251e
commit 7ccb3b4658
16 changed files with 320 additions and 157 deletions

View file

@ -1,4 +1,3 @@
gui/public/tinymce/** gui/public/tinymce/**
gui/public/tinymce/ gui/public/tinymce/
gui/public/tinymce gui/public/tinymce

View file

@ -28,8 +28,10 @@ import (
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/mail" "github.com/documize/community/domain/mail"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/group"
"github.com/documize/community/model/permission" "github.com/documize/community/model/permission"
"github.com/documize/community/model/space" "github.com/documize/community/model/space"
"github.com/documize/community/model/user"
) )
// Handler contains the runtime information such as logging and database. // Handler contains the runtime information such as logging and database.
@ -122,52 +124,85 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
hasEveryoneRole := false hasEveryoneRole := false
roleCount := 0 roleCount := 0
// Permissions can be assigned to both groups and individual users.
// Pre-fetch users with group membership to help us work out
// if user belongs to a group with permissions.
groupMembers, err := h.Store.Group.GetMembers(ctx)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
for _, perm := range model.Permissions { for _, perm := range model.Permissions {
perm.OrgID = ctx.OrgID perm.OrgID = ctx.OrgID
perm.SpaceID = id perm.SpaceID = id
isGroup := perm.Who == permission.GroupPermission
groupRecords := []group.Record{}
if isGroup {
// get group records for just this group
groupRecords = group.FilterGroupRecords(groupMembers, perm.WhoID)
}
// Ensure the space owner always has access! // Ensure the space owner always has access!
if perm.UserID == ctx.UserID { if (!isGroup && perm.WhoID == ctx.UserID) ||
(isGroup && group.UserHasGroupMembership(groupMembers, perm.WhoID, ctx.UserID)) {
me = true me = true
} }
// Only persist if there is a role! // Only persist if there is a role!
if permission.HasAnyPermission(perm) { if permission.HasAnyPermission(perm) {
// identify publically shared spaces // identify publically shared spaces
if perm.UserID == "" { if perm.WhoID == "" {
perm.UserID = "0" perm.WhoID = user.EveryoneUserID
} }
if perm.WhoID == user.EveryoneUserID {
if perm.UserID == "0" {
hasEveryoneRole = true hasEveryoneRole = true
} }
// Encode group/user permission and save to store.
r := permission.EncodeUserPermissions(perm) r := permission.EncodeUserPermissions(perm)
roleCount++ roleCount++
for _, p := range r { for _, p := range r {
err = h.Store.Permission.AddPermission(ctx, p) err = h.Store.Permission.AddPermission(ctx, p)
if err != nil { if err != nil {
h.Runtime.Log.Error("set permission", err) ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
} }
} }
// We send out space invitation emails to those users // We send out space invitation emails to those users
// that have *just* been given permissions. // that have *just* been given permissions.
if _, isExisting := previousRoleUsers[perm.UserID]; !isExisting { if _, isExisting := previousRoleUsers[perm.WhoID]; !isExisting {
// we skip 'everyone'
if perm.WhoID != user.EveryoneUserID {
whoToEmail := []string{}
// we skip 'everyone' (user id != empty string) if isGroup {
if perm.UserID != "0" && perm.UserID != "" { // send email to each group member
existingUser, err := h.Store.User.Get(ctx, perm.UserID) for i := range groupRecords {
if err != nil { whoToEmail = append(whoToEmail, groupRecords[i].UserID)
response.WriteServerError(w, method, err) }
h.Runtime.Log.Error(method, err) } else {
break // send email to individual user
whoToEmail = append(whoToEmail, perm.WhoID)
} }
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx} for i := range whoToEmail {
go mailer.ShareSpaceExistingUser(existingUser.Email, inviter.Fullname(), url, sp.Name, model.Message) existingUser, err := h.Store.User.Get(ctx, whoToEmail[i])
h.Runtime.Log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, sp.Name, existingUser.Email)) if err != nil {
h.Runtime.Log.Error(method, err)
continue
}
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.ShareSpaceExistingUser(existingUser.Email, inviter.Fullname(), url, sp.Name, model.Message)
h.Runtime.Log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, sp.Name, existingUser.Email))
}
} }
} }
} }
@ -233,6 +268,7 @@ func (h *Handler) GetSpacePermissions(w http.ResponseWriter, r *http.Request) {
perms, err := h.Store.Permission.GetSpacePermissions(ctx, spaceID) perms, err := h.Store.Permission.GetSpacePermissions(ctx, spaceID)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return return
} }
@ -246,6 +282,40 @@ func (h *Handler) GetSpacePermissions(w http.ResponseWriter, r *http.Request) {
records = append(records, permission.DecodeUserPermissions(up)) records = append(records, permission.DecodeUserPermissions(up))
} }
// populate user/group name for thing that has permission record
groups, err := h.Store.Group.GetAll(ctx)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
for i := range records {
if records[i].Who == permission.GroupPermission {
for j := range groups {
if records[i].WhoID == groups[j].RefID {
records[i].Name = groups[j].Name
break
}
}
}
if records[i].Who == permission.UserPermission {
if records[i].WhoID == user.EveryoneUserID {
records[i].Name = "Everyone"
} else {
u, err := h.Store.User.Get(ctx, records[i].WhoID)
if err != nil {
h.Runtime.Log.Info(fmt.Sprintf("user not found %s", records[i].WhoID))
h.Runtime.Log.Error(method, err)
continue
}
records[i].Name = u.Fullname()
}
}
}
response.WriteJSON(w, records) response.WriteJSON(w, records)
} }
@ -261,7 +331,7 @@ func (h *Handler) GetUserSpacePermissions(w http.ResponseWriter, r *http.Request
} }
perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, spaceID) perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, spaceID)
if err != nil && err != sql.ErrNoRows { if err != nil {
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
return return
} }
@ -464,11 +534,6 @@ func (h *Handler) SetDocumentPermissions(w http.ResponseWriter, r *http.Request)
return return
} }
// if !HasPermission(ctx, *h.Store, doc.LabelID, permission.SpaceManage, permission.SpaceOwner) {
// response.WriteForbiddenError(w)
// return
// }
defer streamutil.Close(r.Body) defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
@ -528,17 +593,38 @@ func (h *Handler) SetDocumentPermissions(w http.ResponseWriter, r *http.Request)
return return
} }
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s/d/%s/%s", url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s/d/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name), doc.RefID, stringutil.MakeSlug(doc.Title)))
sp.RefID, stringutil.MakeSlug(sp.Name), doc.RefID, stringutil.MakeSlug(doc.Title)))
// Permissions can be assigned to both groups and individual users.
// Pre-fetch users with group membership to help us work out
// if user belongs to a group with permissions.
groupMembers, err := h.Store.Group.GetMembers(ctx)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
for _, perm := range model { for _, perm := range model {
perm.OrgID = ctx.OrgID perm.OrgID = ctx.OrgID
perm.DocumentID = id perm.DocumentID = id
isGroup := perm.Who == permission.GroupPermission
groupRecords := []group.Record{}
if isGroup {
// get group records for just this group
groupRecords = group.FilterGroupRecords(groupMembers, perm.WhoID)
}
// Only persist if there is a role! // Only persist if there is a role!
if permission.HasAnyDocumentPermission(perm) { if permission.HasAnyDocumentPermission(perm) {
r := permission.EncodeUserDocumentPermissions(perm) if perm.WhoID == "" {
perm.WhoID = user.EveryoneUserID
}
r := permission.EncodeUserDocumentPermissions(perm)
for _, p := range r { for _, p := range r {
err = h.Store.Permission.AddPermission(ctx, p) err = h.Store.Permission.AddPermission(ctx, p)
if err != nil { if err != nil {
@ -547,19 +633,32 @@ func (h *Handler) SetDocumentPermissions(w http.ResponseWriter, r *http.Request)
} }
// Send email notification to users who have been given document approver role // Send email notification to users who have been given document approver role
if _, isExisting := previousRoleUsers[perm.UserID]; !isExisting { if _, isExisting := previousRoleUsers[perm.WhoID]; !isExisting {
// we skip 'everyone' as it has no email address!
if perm.WhoID != user.EveryoneUserID && perm.DocumentRoleApprove {
whoToEmail := []string{}
// we skip 'everyone' (user id != empty string) if isGroup {
if perm.UserID != "0" && perm.UserID != "" && perm.DocumentRoleApprove { // send email to each group member
existingUser, err := h.Store.User.Get(ctx, perm.UserID) for i := range groupRecords {
if err != nil { whoToEmail = append(whoToEmail, groupRecords[i].UserID)
response.WriteServerError(w, method, err) }
break } else {
// send email to individual user
whoToEmail = append(whoToEmail, perm.WhoID)
} }
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx} for i := range whoToEmail {
go mailer.DocumentApprover(existingUser.Email, inviter.Fullname(), url, doc.Title) existingUser, err := h.Store.User.Get(ctx, whoToEmail[i])
h.Runtime.Log.Info(fmt.Sprintf("%s has made %s document approver for: %s", inviter.Email, existingUser.Email, doc.Title)) if err != nil {
h.Runtime.Log.Error(method, err)
continue
}
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.DocumentApprover(existingUser.Email, inviter.Fullname(), url, doc.Title)
h.Runtime.Log.Info(fmt.Sprintf("%s has made %s document approver for: %s", inviter.Email, existingUser.Email, doc.Title))
}
} }
} }
} }

View file

@ -35,7 +35,7 @@ func (s Scope) AddPermission(ctx domain.RequestContext, r permission.Permission)
r.Created = time.Now().UTC() r.Created = time.Now().UTC()
_, err = ctx.Transaction.Exec("INSERT INTO permission (orgid, who, whoid, action, scope, location, refid, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", _, err = ctx.Transaction.Exec("INSERT INTO permission (orgid, who, whoid, action, scope, location, refid, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
r.OrgID, r.Who, r.WhoID, string(r.Action), r.Scope, r.Location, r.RefID, r.Created) r.OrgID, string(r.Who), r.WhoID, string(r.Action), string(r.Scope), string(r.Location), r.RefID, r.Created)
if err != nil { if err != nil {
err = errors.Wrap(err, "unable to execute insert permission") err = errors.Wrap(err, "unable to execute insert permission")
@ -64,7 +64,8 @@ func (s Scope) AddPermissions(ctx domain.RequestContext, r permission.Permission
func (s Scope) GetUserSpacePermissions(ctx domain.RequestContext, spaceID string) (r []permission.Permission, err error) { func (s Scope) GetUserSpacePermissions(ctx domain.RequestContext, spaceID string) (r []permission.Permission, err error) {
err = s.Runtime.Db.Select(&r, ` err = s.Runtime.Db.Select(&r, `
SELECT id, orgid, who, whoid, action, scope, location, refid SELECT id, orgid, who, whoid, action, scope, location, refid
FROM permission WHERE orgid=? AND location='space' AND refid=? AND who='user' AND (whoid=? OR whoid='0') FROM permission
WHERE orgid=? AND location='space' AND refid=? AND who='user' AND (whoid=? OR whoid='0')
UNION ALL UNION ALL
SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid
FROM permission p FROM permission p

View file

@ -203,7 +203,6 @@ export default Component.extend(AuthProvider, ModalMixin, {
group.set('isMember', false); group.set('isMember', false);
if (is.undefined(groupId) || is.undefined(userId)) { if (is.undefined(groupId) || is.undefined(userId)) {
console.log(groupId, userId);
return; return;
} }
@ -218,7 +217,6 @@ export default Component.extend(AuthProvider, ModalMixin, {
group.set('isMember', true); group.set('isMember', true);
if (is.undefined(groupId) || is.undefined(userId)) { if (is.undefined(groupId) || is.undefined(userId)) {
console.log(groupId, userId);
return; return;
} }

View file

@ -9,80 +9,78 @@
// //
// https://documize.com // https://documize.com
import { setProperties } from '@ember/object';
import Component from '@ember/component';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { A } from "@ember/array"
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import Component from '@ember/component';
export default Component.extend(ModalMixin, { export default Component.extend(ModalMixin, {
folderService: service('folder'), groupSvc: service('group'),
userService: service('user'), spaceSvc: service('folder'),
userSvc: service('user'),
appMeta: service(), appMeta: service(),
store: service(), store: service(),
spacePermissions: null,
didReceiveAttrs() { didReceiveAttrs() {
this.get('userService').getSpaceUsers(this.get('folder.id')).then((users) => { let spacePermissions = A([]);
this.set('users', users); let constants = this.get('constants');
// set up users // get groups
let folderPermissions = []; this.get('groupSvc').getAll().then((groups) => {
this.set('groups', groups);
users.forEach((user) => { groups.forEach((g) => {
let u = { let pr = this.permissionRecord(constants.WhoType.Group, g.get('id'), g.get('name'));
orgId: this.get('folder.orgId'), spacePermissions.pushObject(pr);
folderId: this.get('folder.id'),
userId: user.get('id'),
fullname: user.get('fullname'),
spaceView: false,
spaceManage: false,
spaceOwner: false,
documentAdd: false,
documentEdit: false,
documentDelete: false,
documentMove: false,
documentCopy: false,
documentTemplate: false,
documentApprove: false,
};
let data = this.get('store').normalize('space-permission', u)
folderPermissions.pushObject(this.get('store').push(data));
}); });
// set up Everyone user // get space permissions
let u = { this.get('spaceSvc').getPermissions(this.get('folder.id')).then((permissions) => {
orgId: this.get('folder.orgId'), permissions.forEach((perm, index) => { // eslint-disable-line no-unused-vars
folderId: this.get('folder.id'), // is this permission for group or user?
userId: '0', if (perm.get('who') === constants.WhoType.Group) {
fullname: ' Everyone', // group permission
spaceView: false, spacePermissions.forEach((sp) => {
spaceManage: false, if (sp.get('whoId') == perm.get('whoId')) {
spaceOwner: false, sp.setProperties(perm);
documentAdd: false, }
documentEdit: false, });
documentDelete: false, } else {
documentMove: false, // user permission
documentCopy: false, spacePermissions.pushObject(perm);
documentTemplate: false,
documentApprove: false,
};
let data = this.get('store').normalize('space-permission', u)
folderPermissions.pushObject(this.get('store').push(data));
this.get('folderService').getPermissions(this.get('folder.id')).then((permissions) => {
permissions.forEach((permission, index) => { // eslint-disable-line no-unused-vars
let record = folderPermissions.findBy('userId', permission.get('userId'));
if (is.not.undefined(record)) {
record = setProperties(record, permission);
} }
}); });
this.set('permissions', folderPermissions.sortBy('fullname')); this.set('spacePermissions', spacePermissions.sortBy('who', 'name'));
}); });
}); });
}, },
permissionRecord(who, whoId, name) {
let raw = {
id: whoId,
orgId: this.get('folder.orgId'),
folderId: this.get('folder.id'),
whoId: whoId,
who: who,
name: name,
spaceView: false,
spaceManage: false,
spaceOwner: false,
documentAdd: false,
documentEdit: false,
documentDelete: false,
documentMove: false,
documentCopy: false,
documentTemplate: false,
documentApprove: false,
};
let rec = this.get('store').normalize('space-permission', raw);
return this.get('store').push(rec);
},
getDefaultInvitationMessage() { getDefaultInvitationMessage() {
return "Hey there, I am sharing the " + this.get('folder.name') + " space (in " + this.get("appMeta.title") + ") with you so we can both collaborate on documents."; return "Hey there, I am sharing the " + this.get('folder.name') + " space (in " + this.get("appMeta.title") + ") with you so we can both collaborate on documents.";
}, },
@ -90,12 +88,13 @@ export default Component.extend(ModalMixin, {
actions: { actions: {
setPermissions() { setPermissions() {
let message = this.getDefaultInvitationMessage(); let message = this.getDefaultInvitationMessage();
let permissions = this.get('permissions'); let permissions = this.get('spacePermissions');
let folder = this.get('folder'); let folder = this.get('folder');
let payload = { Message: message, Permissions: permissions }; let payload = { Message: message, Permissions: permissions };
let constants = this.get('constants');
let hasEveryone = _.find(permissions, function (permission) { let hasEveryone = _.find(permissions, (permission) => {
return permission.get('userId') === "0" && return permission.get('whoId') === constants.EveryoneUserId &&
(permission.get('spaceView') || permission.get('documentAdd') || permission.get('documentEdit') || permission.get('documentDelete') || (permission.get('spaceView') || permission.get('documentAdd') || permission.get('documentEdit') || permission.get('documentDelete') ||
permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate') || permission.get('documentApprove')); permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate') || permission.get('documentApprove'));
}); });
@ -103,7 +102,7 @@ export default Component.extend(ModalMixin, {
// see if more than oen user is granted access to space (excluding everyone) // see if more than oen user is granted access to space (excluding everyone)
let roleCount = 0; let roleCount = 0;
permissions.forEach((permission) => { permissions.forEach((permission) => {
if (permission.get('userId') !== "0" && if (permission.get('whoId') !== constants.EveryoneUserId &&
(permission.get('spaceView') || permission.get('documentAdd') || permission.get('documentEdit') || permission.get('documentDelete') || (permission.get('spaceView') || permission.get('documentAdd') || permission.get('documentEdit') || permission.get('documentDelete') ||
permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate') || permission.get('documentApprove'))) { permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate') || permission.get('documentApprove'))) {
roleCount += 1; roleCount += 1;
@ -120,7 +119,7 @@ export default Component.extend(ModalMixin, {
} }
} }
this.get('folderService').savePermissions(folder.get('id'), payload).then(() => { this.get('spaceSvc').savePermissions(folder.get('id'), payload).then(() => {
this.modalClose('#space-permission-modal'); this.modalClose('#space-permission-modal');
}); });
} }

View file

@ -10,7 +10,6 @@
// https://documize.com // https://documize.com
import $ from 'jquery'; import $ from 'jquery';
import Component from '@ember/component';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { schedule } from '@ember/runloop'; import { schedule } from '@ember/runloop';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
@ -18,6 +17,7 @@ import TooltipMixin from '../../mixins/tooltip';
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import AuthMixin from '../../mixins/auth'; import AuthMixin from '../../mixins/auth';
import stringUtil from '../../utils/string'; import stringUtil from '../../utils/string';
import Component from '@ember/component';
export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, { export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
spaceService: service('folder'), spaceService: service('folder'),

View file

@ -11,6 +11,9 @@
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
// access like so:
// let constants = this.get('constants');
let constants = EmberObject.extend({ let constants = EmberObject.extend({
// Document // Document
ProtectionType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects ProtectionType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
@ -48,7 +51,15 @@ let constants = EmberObject.extend({
PageType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects PageType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
Tab: 'tab', Tab: 'tab',
Section: 'section' Section: 'section'
} },
// Who a permission record relates to
WhoType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
User: 'user',
Group: 'role'
},
EveryoneUserId: "0"
}); });
export default { constants } export default { constants }

View file

@ -11,14 +11,12 @@
import Model from 'ember-data/model'; import Model from 'ember-data/model';
import attr from 'ember-data/attr'; import attr from 'ember-data/attr';
// import { belongsTo, hasMany } from 'ember-data/relationships';
export default Model.extend({ export default Model.extend({
orgId: attr('string'), orgId: attr('string'),
folderId: attr('string'), folderId: attr('string'),
userId: attr('string'), whoId: attr('string'),
fullname: attr('string'), // client-side usage only, not from API who: attr('string'),
spaceView: attr('boolean'), spaceView: attr('boolean'),
spaceManage: attr('boolean'), spaceManage: attr('boolean'),
spaceOwner: attr('boolean'), spaceOwner: attr('boolean'),
@ -28,5 +26,6 @@ export default Model.extend({
documentMove: attr('boolean'), documentMove: attr('boolean'),
documentCopy: attr('boolean'), documentCopy: attr('boolean'),
documentTemplate: attr('boolean'), documentTemplate: attr('boolean'),
documentApprove: attr('boolean') documentApprove: attr('boolean'),
name: attr('string') // read-only
}); });

View file

@ -4,7 +4,7 @@ export default ApplicationSerializer.extend({
normalize(modelClass, resourceHash) { normalize(modelClass, resourceHash) {
return { return {
data: { data: {
id: resourceHash.userId ? resourceHash.userId : 0, id: resourceHash.whoId ? resourceHash.whoId : 0,
type: modelClass.modelName, type: modelClass.modelName,
attributes: resourceHash attributes: resourceHash
} }

View file

@ -20,6 +20,7 @@ export default BaseService.extend({
localStorage: service(), localStorage: service(),
store: service(), store: service(),
currentFolder: null, currentFolder: null,
permissions: null,
init() { init() {
this._super(...arguments); this._super(...arguments);

View file

@ -27,38 +27,50 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{#each permissions as |permission|}} {{#each spacePermissions as |permission|}}
<tr> <tr>
<td>{{permission.fullname}} {{if (eq permission.userId session.user.id) '(you)'}}</td>
<td> <td>
{{input type="checkbox" id=(concat 'space-role-view-' permission.userId) checked=permission.spaceView}} {{#if (eq permission.who "role")}}
<span class="button-icon-gray button-icon-small align-middle">
<i class="material-icons">people</i>
</span>
&nbsp;<b>{{permission.name}}</b>
{{else}}
<span class="button-icon-gray button-icon-small align-middle">
<i class="material-icons">person</i>
</span>
&nbsp;<b>{{permission.name}}</b> {{if (eq permission.whoId session.user.id) '(you)'}}
{{/if}}
</td> </td>
<td> <td>
{{input type="checkbox" id=(concat 'space-role-manage-' permission.userId) checked=permission.spaceManage}} {{input type="checkbox" id=(concat 'space-role-view-' permission.whoId) checked=permission.spaceView}}
</td> </td>
<td> <td>
{{input type="checkbox" id=(concat 'space-role-owner-' permission.userId) checked=permission.spaceOwner}} {{input type="checkbox" id=(concat 'space-role-manage-' permission.whoId) checked=permission.spaceManage}}
</td> </td>
<td> <td>
{{input type="checkbox" id=(concat 'doc-role-add-' permission.userId) checked=permission.documentAdd}} {{input type="checkbox" id=(concat 'space-role-owner-' permission.whoId) checked=permission.spaceOwner}}
</td> </td>
<td> <td>
{{input type="checkbox" id=(concat 'doc-role-edit-' permission.userId) checked=permission.documentEdit}} {{input type="checkbox" id=(concat 'doc-role-add-' permission.whoId) checked=permission.documentAdd}}
</td> </td>
<td> <td>
{{input type="checkbox" id=(concat 'doc-role-delete-' permission.userId) checked=permission.documentDelete}} {{input type="checkbox" id=(concat 'doc-role-edit-' permission.whoId) checked=permission.documentEdit}}
</td> </td>
<td> <td>
{{input type="checkbox" id=(concat 'doc-role-move-' permission.userId) checked=permission.documentMove}} {{input type="checkbox" id=(concat 'doc-role-delete-' permission.whoId) checked=permission.documentDelete}}
</td> </td>
<td> <td>
{{input type="checkbox" id=(concat 'doc-role-copy-' permission.userId) checked=permission.documentCopy}} {{input type="checkbox" id=(concat 'doc-role-move-' permission.whoId) checked=permission.documentMove}}
</td> </td>
<td> <td>
{{input type="checkbox" id=(concat 'doc-role-template-' permission.userId) checked=permission.documentTemplate}} {{input type="checkbox" id=(concat 'doc-role-copy-' permission.whoId) checked=permission.documentCopy}}
</td> </td>
<td> <td>
{{input type="checkbox" id=(concat 'doc-role-approve-' permission.userId) checked=permission.documentApprove}} {{input type="checkbox" id=(concat 'doc-role-template-' permission.whoId) checked=permission.documentTemplate}}
</td>
<td>
{{input type="checkbox" id=(concat 'doc-role-approve-' permission.whoId) checked=permission.documentApprove}}
</td> </td>
</tr> </tr>
{{/each}} {{/each}}

View file

@ -41,3 +41,27 @@ type Record struct {
Name string `json:"name"` Name string `json:"name"`
Purpose string `json:"purpose"` Purpose string `json:"purpose"`
} }
// UserHasGroupMembership returns true if user belongs to specified group.
func UserHasGroupMembership(r []Record, groupID, userID string) bool {
for i := range r {
if r[i].RoleID == groupID && r[i].UserID == userID {
return true
}
}
return false
}
// FilterGroupRecords returns only those records matching group ID.
func FilterGroupRecords(r []Record, groupID string) (m []Record) {
m = []Record{}
for i := range r {
if r[i].RoleID == groupID {
m = append(m, r[i])
}
}
return
}

View file

@ -15,11 +15,12 @@ package permission
// This data structure is made from database permission records for the document, // This data structure is made from database permission records for the document,
// and it is designed to be sent to HTTP clients (web, mobile). // and it is designed to be sent to HTTP clients (web, mobile).
type DocumentRecord struct { type DocumentRecord struct {
OrgID string `json:"orgId"` OrgID string `json:"orgId"`
DocumentID string `json:"documentId"` DocumentID string `json:"documentId"`
UserID string `json:"userId"` WhoID string `json:"whoId"`
DocumentRoleEdit bool `json:"documentRoleEdit"` Who WhoType `json:"who"`
DocumentRoleApprove bool `json:"documentRoleApprove"` DocumentRoleEdit bool `json:"documentRoleEdit"`
DocumentRoleApprove bool `json:"documentRoleApprove"`
} }
// DecodeUserDocumentPermissions returns a flat, usable permission summary record // DecodeUserDocumentPermissions returns a flat, usable permission summary record
@ -29,7 +30,8 @@ func DecodeUserDocumentPermissions(perm []Permission) (r DocumentRecord) {
if len(perm) > 0 { if len(perm) > 0 {
r.OrgID = perm[0].OrgID r.OrgID = perm[0].OrgID
r.UserID = perm[0].WhoID r.WhoID = perm[0].WhoID
r.Who = perm[0].Who
r.DocumentID = perm[0].RefID r.DocumentID = perm[0].RefID
} }
@ -67,8 +69,8 @@ func HasAnyDocumentPermission(p DocumentRecord) bool {
func EncodeDocumentRecord(r DocumentRecord, a Action) (p Permission) { func EncodeDocumentRecord(r DocumentRecord, a Action) (p Permission) {
p = Permission{} p = Permission{}
p.OrgID = r.OrgID p.OrgID = r.OrgID
p.Who = UserPermission p.WhoID = r.WhoID
p.WhoID = r.UserID p.Who = r.Who
p.Location = LocationDocument p.Location = LocationDocument
p.RefID = r.DocumentID p.RefID = r.DocumentID
p.Action = a p.Action = a

View file

@ -15,19 +15,21 @@ package permission
// This data structure is made from database permission records for the space, // This data structure is made from database permission records for the space,
// and it is designed to be sent to HTTP clients (web, mobile). // and it is designed to be sent to HTTP clients (web, mobile).
type Record struct { type Record struct {
OrgID string `json:"orgId"` OrgID string `json:"orgId"`
SpaceID string `json:"folderId"` SpaceID string `json:"folderId"`
UserID string `json:"userId"` WhoID string `json:"whoId"`
SpaceView bool `json:"spaceView"` Who WhoType `json:"who"`
SpaceManage bool `json:"spaceManage"` SpaceView bool `json:"spaceView"`
SpaceOwner bool `json:"spaceOwner"` SpaceManage bool `json:"spaceManage"`
DocumentAdd bool `json:"documentAdd"` SpaceOwner bool `json:"spaceOwner"`
DocumentEdit bool `json:"documentEdit"` DocumentAdd bool `json:"documentAdd"`
DocumentDelete bool `json:"documentDelete"` DocumentEdit bool `json:"documentEdit"`
DocumentMove bool `json:"documentMove"` DocumentDelete bool `json:"documentDelete"`
DocumentCopy bool `json:"documentCopy"` DocumentMove bool `json:"documentMove"`
DocumentTemplate bool `json:"documentTemplate"` DocumentCopy bool `json:"documentCopy"`
DocumentApprove bool `json:"documentApprove"` DocumentTemplate bool `json:"documentTemplate"`
DocumentApprove bool `json:"documentApprove"`
Name string `json:"name"` // read-only, user or group name
} }
// DecodeUserPermissions returns a flat, usable permission summary record // DecodeUserPermissions returns a flat, usable permission summary record
@ -37,7 +39,8 @@ func DecodeUserPermissions(perm []Permission) (r Record) {
if len(perm) > 0 { if len(perm) > 0 {
r.OrgID = perm[0].OrgID r.OrgID = perm[0].OrgID
r.UserID = perm[0].WhoID r.WhoID = perm[0].WhoID
r.Who = perm[0].Who
r.SpaceID = perm[0].RefID r.SpaceID = perm[0].RefID
} }
@ -118,8 +121,8 @@ func HasAnyPermission(p Record) bool {
func EncodeRecord(r Record, a Action) (p Permission) { func EncodeRecord(r Record, a Action) (p Permission) {
p = Permission{} p = Permission{}
p.OrgID = r.OrgID p.OrgID = r.OrgID
p.Who = UserPermission p.Who = r.Who
p.WhoID = r.UserID p.WhoID = r.WhoID
p.Location = LocationSpace p.Location = LocationSpace
p.RefID = r.SpaceID p.RefID = r.SpaceID
p.Action = a p.Action = a

View file

@ -71,3 +71,8 @@ func Exists(users []User, userID string) bool {
return false return false
} }
const (
// EveryoneUserID provides a shortcut to state "all authenticated users".
EveryoneUserID string = "0"
)

View file

@ -91,9 +91,9 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"GET", "OPTIONS"}, nil, document.Get) Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"GET", "OPTIONS"}, nil, document.Get)
Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"PUT", "OPTIONS"}, nil, document.Update) Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"PUT", "OPTIONS"}, nil, document.Update)
Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"DELETE", "OPTIONS"}, nil, document.Delete) Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"DELETE", "OPTIONS"}, nil, document.Delete)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions", []string{"GET", "OPTIONS"}, nil, permission.GetDocumentPermissions) // Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions", []string{"GET", "OPTIONS"}, nil, permission.GetDocumentPermissions)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions", []string{"PUT", "OPTIONS"}, nil, permission.SetDocumentPermissions) // Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions", []string{"PUT", "OPTIONS"}, nil, permission.SetDocumentPermissions)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions/user", []string{"GET", "OPTIONS"}, nil, permission.GetUserDocumentPermissions) // Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions/user", []string{"GET", "OPTIONS"}, nil, permission.GetUserDocumentPermissions)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/level", []string{"POST", "OPTIONS"}, nil, page.ChangePageLevel) Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/level", []string{"POST", "OPTIONS"}, nil, page.ChangePageLevel)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/sequence", []string{"POST", "OPTIONS"}, nil, page.ChangePageSequence) Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/sequence", []string{"POST", "OPTIONS"}, nil, page.ChangePageSequence)
@ -119,9 +119,9 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
Add(rt, RoutePrefixPrivate, "space/{spaceID}", []string{"DELETE", "OPTIONS"}, nil, space.Delete) Add(rt, RoutePrefixPrivate, "space/{spaceID}", []string{"DELETE", "OPTIONS"}, nil, space.Delete)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/move/{moveToId}", []string{"DELETE", "OPTIONS"}, nil, space.Remove) Add(rt, RoutePrefixPrivate, "space/{spaceID}/move/{moveToId}", []string{"DELETE", "OPTIONS"}, nil, space.Remove)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions", []string{"PUT", "OPTIONS"}, nil, permission.SetSpacePermissions) // Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions", []string{"PUT", "OPTIONS"}, nil, permission.SetSpacePermissions)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions/user", []string{"GET", "OPTIONS"}, nil, permission.GetUserSpacePermissions) // Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions/user", []string{"GET", "OPTIONS"}, nil, permission.GetUserSpacePermissions)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions", []string{"GET", "OPTIONS"}, nil, permission.GetSpacePermissions) // Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions", []string{"GET", "OPTIONS"}, nil, permission.GetSpacePermissions)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/invitation", []string{"POST", "OPTIONS"}, nil, space.Invite) Add(rt, RoutePrefixPrivate, "space/{spaceID}/invitation", []string{"POST", "OPTIONS"}, nil, space.Invite)
Add(rt, RoutePrefixPrivate, "space/manage", []string{"GET", "OPTIONS"}, nil, space.GetAll) Add(rt, RoutePrefixPrivate, "space/manage", []string{"GET", "OPTIONS"}, nil, space.GetAll)
Add(rt, RoutePrefixPrivate, "space/{spaceID}", []string{"GET", "OPTIONS"}, nil, space.Get) Add(rt, RoutePrefixPrivate, "space/{spaceID}", []string{"GET", "OPTIONS"}, nil, space.Get)
@ -131,11 +131,11 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
Add(rt, RoutePrefixPrivate, "category/space/{spaceID}/summary", []string{"GET", "OPTIONS"}, nil, category.GetSummary) Add(rt, RoutePrefixPrivate, "category/space/{spaceID}/summary", []string{"GET", "OPTIONS"}, nil, category.GetSummary)
Add(rt, RoutePrefixPrivate, "category/document/{documentID}", []string{"GET", "OPTIONS"}, nil, category.GetDocumentCategoryMembership) Add(rt, RoutePrefixPrivate, "category/document/{documentID}", []string{"GET", "OPTIONS"}, nil, category.GetDocumentCategoryMembership)
Add(rt, RoutePrefixPrivate, "category/{categoryID}/permission", []string{"PUT", "OPTIONS"}, nil, permission.SetCategoryPermissions) // Add(rt, RoutePrefixPrivate, "category/{categoryID}/permission", []string{"PUT", "OPTIONS"}, nil, permission.SetCategoryPermissions)
Add(rt, RoutePrefixPrivate, "category/{categoryID}/permission", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryPermissions) // Add(rt, RoutePrefixPrivate, "category/{categoryID}/permission", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryPermissions)
Add(rt, RoutePrefixPrivate, "category/space/{spaceID}", []string{"GET", "OPTIONS"}, []string{"filter", "all"}, category.GetAll) Add(rt, RoutePrefixPrivate, "category/space/{spaceID}", []string{"GET", "OPTIONS"}, []string{"filter", "all"}, category.GetAll)
Add(rt, RoutePrefixPrivate, "category/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.Get) Add(rt, RoutePrefixPrivate, "category/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.Get)
Add(rt, RoutePrefixPrivate, "category/{categoryID}/user", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryViewers) // Add(rt, RoutePrefixPrivate, "category/{categoryID}/user", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryViewers)
Add(rt, RoutePrefixPrivate, "category/member/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.GetSpaceCategoryMembers) Add(rt, RoutePrefixPrivate, "category/member/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.GetSpaceCategoryMembers)
Add(rt, RoutePrefixPrivate, "category/member", []string{"POST", "OPTIONS"}, nil, category.SetDocumentCategoryMembership) Add(rt, RoutePrefixPrivate, "category/member", []string{"POST", "OPTIONS"}, nil, category.SetDocumentCategoryMembership)
Add(rt, RoutePrefixPrivate, "category/{categoryID}", []string{"PUT", "OPTIONS"}, nil, category.Update) Add(rt, RoutePrefixPrivate, "category/{categoryID}", []string{"PUT", "OPTIONS"}, nil, category.Update)
@ -192,6 +192,16 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
Add(rt, RoutePrefixPrivate, "group/{groupID}/join/{userID}", []string{"POST", "OPTIONS"}, nil, group.JoinGroup) Add(rt, RoutePrefixPrivate, "group/{groupID}/join/{userID}", []string{"POST", "OPTIONS"}, nil, group.JoinGroup)
Add(rt, RoutePrefixPrivate, "group/{groupID}/leave/{userID}", []string{"DELETE", "OPTIONS"}, nil, group.LeaveGroup) Add(rt, RoutePrefixPrivate, "group/{groupID}/leave/{userID}", []string{"DELETE", "OPTIONS"}, nil, group.LeaveGroup)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions", []string{"GET", "OPTIONS"}, nil, permission.GetDocumentPermissions)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions", []string{"PUT", "OPTIONS"}, nil, permission.SetDocumentPermissions)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions/user", []string{"GET", "OPTIONS"}, nil, permission.GetUserDocumentPermissions)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions", []string{"PUT", "OPTIONS"}, nil, permission.SetSpacePermissions)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions/user", []string{"GET", "OPTIONS"}, nil, permission.GetUserSpacePermissions)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions", []string{"GET", "OPTIONS"}, nil, permission.GetSpacePermissions)
Add(rt, RoutePrefixPrivate, "category/{categoryID}/permission", []string{"PUT", "OPTIONS"}, nil, permission.SetCategoryPermissions)
Add(rt, RoutePrefixPrivate, "category/{categoryID}/permission", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryPermissions)
Add(rt, RoutePrefixPrivate, "category/{categoryID}/user", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryViewers)
// fetch methods exist to speed up UI rendering by returning data in bulk // fetch methods exist to speed up UI rendering by returning data in bulk
Add(rt, RoutePrefixPrivate, "fetch/category/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.FetchSpaceData) Add(rt, RoutePrefixPrivate, "fetch/category/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.FetchSpaceData)
Add(rt, RoutePrefixPrivate, "fetch/document/{documentID}", []string{"GET", "OPTIONS"}, nil, document.FetchDocumentData) Add(rt, RoutePrefixPrivate, "fetch/document/{documentID}", []string{"GET", "OPTIONS"}, nil, document.FetchDocumentData)