From 7ccb3b46589f4c35166bf10e1ed5f5bf63da5604 Mon Sep 17 00:00:00 2001 From: sauls8t Date: Sat, 3 Mar 2018 17:46:29 +0000 Subject: [PATCH] Persist space permissions for groups and users --- .jshintignore | 3 +- domain/permission/endpoint.go | 173 ++++++++++++++---- domain/permission/mysql/store.go | 5 +- gui/app/components/customize/user-list.js | 2 - gui/app/components/folder/permission-admin.js | 121 ++++++------ gui/app/components/toolbar/for-space.js | 2 +- gui/app/constants/constants.js | 13 +- gui/app/models/space-permission.js | 9 +- gui/app/serializers/space-permission.js | 2 +- gui/app/services/folder.js | 1 + .../components/folder/permission-admin.hbs | 36 ++-- model/group/group.go | 24 +++ model/permission/document.go | 18 +- model/permission/space.go | 35 ++-- model/user/user.go | 5 + server/routing/routes.go | 28 ++- 16 files changed, 320 insertions(+), 157 deletions(-) diff --git a/.jshintignore b/.jshintignore index af18237a..699b2e42 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,4 +1,3 @@ gui/public/tinymce/** gui/public/tinymce/ -gui/public/tinymce - +gui/public/tinymce \ No newline at end of file diff --git a/domain/permission/endpoint.go b/domain/permission/endpoint.go index 0fb7c808..015f2186 100644 --- a/domain/permission/endpoint.go +++ b/domain/permission/endpoint.go @@ -28,8 +28,10 @@ import ( "github.com/documize/community/domain" "github.com/documize/community/domain/mail" "github.com/documize/community/model/audit" + "github.com/documize/community/model/group" "github.com/documize/community/model/permission" "github.com/documize/community/model/space" + "github.com/documize/community/model/user" ) // 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 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 { perm.OrgID = ctx.OrgID 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! - if perm.UserID == ctx.UserID { + if (!isGroup && perm.WhoID == ctx.UserID) || + (isGroup && group.UserHasGroupMembership(groupMembers, perm.WhoID, ctx.UserID)) { me = true } // Only persist if there is a role! if permission.HasAnyPermission(perm) { // identify publically shared spaces - if perm.UserID == "" { - perm.UserID = "0" + if perm.WhoID == "" { + perm.WhoID = user.EveryoneUserID } - - if perm.UserID == "0" { + if perm.WhoID == user.EveryoneUserID { hasEveryoneRole = true } + // Encode group/user permission and save to store. r := permission.EncodeUserPermissions(perm) roleCount++ - for _, p := range r { err = h.Store.Permission.AddPermission(ctx, p) 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 // 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 perm.UserID != "0" && perm.UserID != "" { - existingUser, err := h.Store.User.Get(ctx, perm.UserID) - if err != nil { - response.WriteServerError(w, method, err) - h.Runtime.Log.Error(method, err) - break + if isGroup { + // send email to each group member + for i := range groupRecords { + whoToEmail = append(whoToEmail, groupRecords[i].UserID) + } + } else { + // send email to individual user + whoToEmail = append(whoToEmail, perm.WhoID) } - 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)) + for i := range whoToEmail { + existingUser, err := h.Store.User.Get(ctx, whoToEmail[i]) + 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) if err != nil && err != sql.ErrNoRows { response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) return } @@ -246,6 +282,40 @@ func (h *Handler) GetSpacePermissions(w http.ResponseWriter, r *http.Request) { 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) } @@ -261,7 +331,7 @@ func (h *Handler) GetUserSpacePermissions(w http.ResponseWriter, r *http.Request } perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, spaceID) - if err != nil && err != sql.ErrNoRows { + if err != nil { response.WriteServerError(w, method, err) return } @@ -464,11 +534,6 @@ func (h *Handler) SetDocumentPermissions(w http.ResponseWriter, r *http.Request) return } - // if !HasPermission(ctx, *h.Store, doc.LabelID, permission.SpaceManage, permission.SpaceOwner) { - // response.WriteForbiddenError(w) - // return - // } - defer streamutil.Close(r.Body) body, err := ioutil.ReadAll(r.Body) if err != nil { @@ -528,17 +593,38 @@ func (h *Handler) SetDocumentPermissions(w http.ResponseWriter, r *http.Request) return } - url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s/d/%s/%s", - sp.RefID, stringutil.MakeSlug(sp.Name), doc.RefID, stringutil.MakeSlug(doc.Title))) + url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s/d/%s/%s", 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 { perm.OrgID = ctx.OrgID 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! if permission.HasAnyDocumentPermission(perm) { - r := permission.EncodeUserDocumentPermissions(perm) + if perm.WhoID == "" { + perm.WhoID = user.EveryoneUserID + } + r := permission.EncodeUserDocumentPermissions(perm) for _, p := range r { err = h.Store.Permission.AddPermission(ctx, p) 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 - 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 perm.UserID != "0" && perm.UserID != "" && perm.DocumentRoleApprove { - existingUser, err := h.Store.User.Get(ctx, perm.UserID) - if err != nil { - response.WriteServerError(w, method, err) - break + if isGroup { + // send email to each group member + for i := range groupRecords { + whoToEmail = append(whoToEmail, groupRecords[i].UserID) + } + } else { + // send email to individual user + whoToEmail = append(whoToEmail, perm.WhoID) } - 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)) + for i := range whoToEmail { + existingUser, err := h.Store.User.Get(ctx, whoToEmail[i]) + 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)) + } } } } diff --git a/domain/permission/mysql/store.go b/domain/permission/mysql/store.go index f2a08998..2c2a3554 100644 --- a/domain/permission/mysql/store.go +++ b/domain/permission/mysql/store.go @@ -35,7 +35,7 @@ func (s Scope) AddPermission(ctx domain.RequestContext, r permission.Permission) r.Created = time.Now().UTC() _, 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 { 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) { err = s.Runtime.Db.Select(&r, ` 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 SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid FROM permission p diff --git a/gui/app/components/customize/user-list.js b/gui/app/components/customize/user-list.js index 0803ecb7..b1a53f12 100644 --- a/gui/app/components/customize/user-list.js +++ b/gui/app/components/customize/user-list.js @@ -203,7 +203,6 @@ export default Component.extend(AuthProvider, ModalMixin, { group.set('isMember', false); if (is.undefined(groupId) || is.undefined(userId)) { - console.log(groupId, userId); return; } @@ -218,7 +217,6 @@ export default Component.extend(AuthProvider, ModalMixin, { group.set('isMember', true); if (is.undefined(groupId) || is.undefined(userId)) { - console.log(groupId, userId); return; } diff --git a/gui/app/components/folder/permission-admin.js b/gui/app/components/folder/permission-admin.js index f3165a66..c91010e9 100644 --- a/gui/app/components/folder/permission-admin.js +++ b/gui/app/components/folder/permission-admin.js @@ -9,80 +9,78 @@ // // https://documize.com -import { setProperties } from '@ember/object'; -import Component from '@ember/component'; import { inject as service } from '@ember/service'; +import { A } from "@ember/array" import ModalMixin from '../../mixins/modal'; +import Component from '@ember/component'; export default Component.extend(ModalMixin, { - folderService: service('folder'), - userService: service('user'), - appMeta: service(), + groupSvc: service('group'), + spaceSvc: service('folder'), + userSvc: service('user'), + appMeta: service(), store: service(), + spacePermissions: null, didReceiveAttrs() { - this.get('userService').getSpaceUsers(this.get('folder.id')).then((users) => { - this.set('users', users); + let spacePermissions = A([]); + let constants = this.get('constants'); - // set up users - let folderPermissions = []; + // get groups + this.get('groupSvc').getAll().then((groups) => { + this.set('groups', groups); - users.forEach((user) => { - let u = { - orgId: this.get('folder.orgId'), - 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)); + groups.forEach((g) => { + let pr = this.permissionRecord(constants.WhoType.Group, g.get('id'), g.get('name')); + spacePermissions.pushObject(pr); }); - // set up Everyone user - let u = { - orgId: this.get('folder.orgId'), - folderId: this.get('folder.id'), - userId: '0', - fullname: ' Everyone', - 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)); - - 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); + // get space permissions + this.get('spaceSvc').getPermissions(this.get('folder.id')).then((permissions) => { + permissions.forEach((perm, index) => { // eslint-disable-line no-unused-vars + // is this permission for group or user? + if (perm.get('who') === constants.WhoType.Group) { + // group permission + spacePermissions.forEach((sp) => { + if (sp.get('whoId') == perm.get('whoId')) { + sp.setProperties(perm); + } + }); + } else { + // user permission + spacePermissions.pushObject(perm); } }); - 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() { 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: { setPermissions() { let message = this.getDefaultInvitationMessage(); - let permissions = this.get('permissions'); + let permissions = this.get('spacePermissions'); let folder = this.get('folder'); let payload = { Message: message, Permissions: permissions }; + let constants = this.get('constants'); - let hasEveryone = _.find(permissions, function (permission) { - return permission.get('userId') === "0" && + let hasEveryone = _.find(permissions, (permission) => { + return permission.get('whoId') === constants.EveryoneUserId && (permission.get('spaceView') || permission.get('documentAdd') || permission.get('documentEdit') || permission.get('documentDelete') || 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) let roleCount = 0; 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('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate') || permission.get('documentApprove'))) { 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'); }); } diff --git a/gui/app/components/toolbar/for-space.js b/gui/app/components/toolbar/for-space.js index 45986935..e17a31f4 100644 --- a/gui/app/components/toolbar/for-space.js +++ b/gui/app/components/toolbar/for-space.js @@ -10,7 +10,6 @@ // https://documize.com import $ from 'jquery'; -import Component from '@ember/component'; import { computed } from '@ember/object'; import { schedule } from '@ember/runloop'; import { inject as service } from '@ember/service'; @@ -18,6 +17,7 @@ import TooltipMixin from '../../mixins/tooltip'; import ModalMixin from '../../mixins/modal'; import AuthMixin from '../../mixins/auth'; import stringUtil from '../../utils/string'; +import Component from '@ember/component'; export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, { spaceService: service('folder'), diff --git a/gui/app/constants/constants.js b/gui/app/constants/constants.js index 6d440867..72e0a3c3 100644 --- a/gui/app/constants/constants.js +++ b/gui/app/constants/constants.js @@ -11,6 +11,9 @@ import EmberObject from "@ember/object"; +// access like so: +// let constants = this.get('constants'); + let constants = EmberObject.extend({ // Document 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 Tab: 'tab', 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 } \ No newline at end of file diff --git a/gui/app/models/space-permission.js b/gui/app/models/space-permission.js index 8072f6eb..67b2b269 100644 --- a/gui/app/models/space-permission.js +++ b/gui/app/models/space-permission.js @@ -11,14 +11,12 @@ import Model from 'ember-data/model'; import attr from 'ember-data/attr'; -// import { belongsTo, hasMany } from 'ember-data/relationships'; export default Model.extend({ orgId: attr('string'), folderId: attr('string'), - userId: attr('string'), - fullname: attr('string'), // client-side usage only, not from API - + whoId: attr('string'), + who: attr('string'), spaceView: attr('boolean'), spaceManage: attr('boolean'), spaceOwner: attr('boolean'), @@ -28,5 +26,6 @@ export default Model.extend({ documentMove: attr('boolean'), documentCopy: attr('boolean'), documentTemplate: attr('boolean'), - documentApprove: attr('boolean') + documentApprove: attr('boolean'), + name: attr('string') // read-only }); diff --git a/gui/app/serializers/space-permission.js b/gui/app/serializers/space-permission.js index 2c2733a5..c6542cf8 100644 --- a/gui/app/serializers/space-permission.js +++ b/gui/app/serializers/space-permission.js @@ -4,7 +4,7 @@ export default ApplicationSerializer.extend({ normalize(modelClass, resourceHash) { return { data: { - id: resourceHash.userId ? resourceHash.userId : 0, + id: resourceHash.whoId ? resourceHash.whoId : 0, type: modelClass.modelName, attributes: resourceHash } diff --git a/gui/app/services/folder.js b/gui/app/services/folder.js index e68e67f7..4a344546 100644 --- a/gui/app/services/folder.js +++ b/gui/app/services/folder.js @@ -20,6 +20,7 @@ export default BaseService.extend({ localStorage: service(), store: service(), currentFolder: null, + permissions: null, init() { this._super(...arguments); diff --git a/gui/app/templates/components/folder/permission-admin.hbs b/gui/app/templates/components/folder/permission-admin.hbs index d2610da8..8f60401d 100644 --- a/gui/app/templates/components/folder/permission-admin.hbs +++ b/gui/app/templates/components/folder/permission-admin.hbs @@ -27,38 +27,50 @@ - {{#each permissions as |permission|}} + {{#each spacePermissions as |permission|}} - {{permission.fullname}} {{if (eq permission.userId session.user.id) '(you)'}} - {{input type="checkbox" id=(concat 'space-role-view-' permission.userId) checked=permission.spaceView}} + {{#if (eq permission.who "role")}} + + people + +  {{permission.name}} + {{else}} + + person + +  {{permission.name}} {{if (eq permission.whoId session.user.id) '(you)'}} + {{/if}} - {{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}} - {{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}} - {{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}} - {{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}} - {{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}} - {{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}} - {{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}} - {{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}} - {{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}} + + + {{input type="checkbox" id=(concat 'doc-role-approve-' permission.whoId) checked=permission.documentApprove}} {{/each}} diff --git a/model/group/group.go b/model/group/group.go index 75f4f3be..a6c14631 100644 --- a/model/group/group.go +++ b/model/group/group.go @@ -41,3 +41,27 @@ type Record struct { Name string `json:"name"` 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 +} diff --git a/model/permission/document.go b/model/permission/document.go index c49ee93e..6b1e12fd 100644 --- a/model/permission/document.go +++ b/model/permission/document.go @@ -15,11 +15,12 @@ package permission // This data structure is made from database permission records for the document, // and it is designed to be sent to HTTP clients (web, mobile). type DocumentRecord struct { - OrgID string `json:"orgId"` - DocumentID string `json:"documentId"` - UserID string `json:"userId"` - DocumentRoleEdit bool `json:"documentRoleEdit"` - DocumentRoleApprove bool `json:"documentRoleApprove"` + OrgID string `json:"orgId"` + DocumentID string `json:"documentId"` + WhoID string `json:"whoId"` + Who WhoType `json:"who"` + DocumentRoleEdit bool `json:"documentRoleEdit"` + DocumentRoleApprove bool `json:"documentRoleApprove"` } // DecodeUserDocumentPermissions returns a flat, usable permission summary record @@ -29,7 +30,8 @@ func DecodeUserDocumentPermissions(perm []Permission) (r DocumentRecord) { if len(perm) > 0 { 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 } @@ -67,8 +69,8 @@ func HasAnyDocumentPermission(p DocumentRecord) bool { func EncodeDocumentRecord(r DocumentRecord, a Action) (p Permission) { p = Permission{} p.OrgID = r.OrgID - p.Who = UserPermission - p.WhoID = r.UserID + p.WhoID = r.WhoID + p.Who = r.Who p.Location = LocationDocument p.RefID = r.DocumentID p.Action = a diff --git a/model/permission/space.go b/model/permission/space.go index 0ec34481..51ee931a 100644 --- a/model/permission/space.go +++ b/model/permission/space.go @@ -15,19 +15,21 @@ package permission // This data structure is made from database permission records for the space, // and it is designed to be sent to HTTP clients (web, mobile). type Record struct { - OrgID string `json:"orgId"` - SpaceID string `json:"folderId"` - UserID string `json:"userId"` - SpaceView bool `json:"spaceView"` - SpaceManage bool `json:"spaceManage"` - SpaceOwner bool `json:"spaceOwner"` - DocumentAdd bool `json:"documentAdd"` - DocumentEdit bool `json:"documentEdit"` - DocumentDelete bool `json:"documentDelete"` - DocumentMove bool `json:"documentMove"` - DocumentCopy bool `json:"documentCopy"` - DocumentTemplate bool `json:"documentTemplate"` - DocumentApprove bool `json:"documentApprove"` + OrgID string `json:"orgId"` + SpaceID string `json:"folderId"` + WhoID string `json:"whoId"` + Who WhoType `json:"who"` + SpaceView bool `json:"spaceView"` + SpaceManage bool `json:"spaceManage"` + SpaceOwner bool `json:"spaceOwner"` + DocumentAdd bool `json:"documentAdd"` + DocumentEdit bool `json:"documentEdit"` + DocumentDelete bool `json:"documentDelete"` + DocumentMove bool `json:"documentMove"` + DocumentCopy bool `json:"documentCopy"` + 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 @@ -37,7 +39,8 @@ func DecodeUserPermissions(perm []Permission) (r Record) { if len(perm) > 0 { 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 } @@ -118,8 +121,8 @@ func HasAnyPermission(p Record) bool { func EncodeRecord(r Record, a Action) (p Permission) { p = Permission{} p.OrgID = r.OrgID - p.Who = UserPermission - p.WhoID = r.UserID + p.Who = r.Who + p.WhoID = r.WhoID p.Location = LocationSpace p.RefID = r.SpaceID p.Action = a diff --git a/model/user/user.go b/model/user/user.go index 20bf36fe..b33f612d 100644 --- a/model/user/user.go +++ b/model/user/user.go @@ -71,3 +71,8 @@ func Exists(users []User, userID string) bool { return false } + +const ( + // EveryoneUserID provides a shortcut to state "all authenticated users". + EveryoneUserID string = "0" +) diff --git a/server/routing/routes.go b/server/routing/routes.go index d6e7ef96..009a66fb 100644 --- a/server/routing/routes.go +++ b/server/routing/routes.go @@ -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{"PUT", "OPTIONS"}, nil, document.Update) 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{"PUT", "OPTIONS"}, nil, permission.SetDocumentPermissions) - Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions/user", []string{"GET", "OPTIONS"}, nil, permission.GetUserDocumentPermissions) + // 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, "documents/{documentID}/pages/level", []string{"POST", "OPTIONS"}, nil, page.ChangePageLevel) 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}/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/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{"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, "space/{spaceID}/invitation", []string{"POST", "OPTIONS"}, nil, space.Invite) Add(rt, RoutePrefixPrivate, "space/manage", []string{"GET", "OPTIONS"}, nil, space.GetAll) 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/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{"GET", "OPTIONS"}, nil, permission.GetCategoryPermissions) + // 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/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/{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", []string{"POST", "OPTIONS"}, nil, category.SetDocumentCategoryMembership) 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}/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 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)