1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-23 23:29:42 +02:00

refactored permission code

This commit is contained in:
Harvey Kandola 2017-09-18 17:53:42 +01:00
parent c12c000ef3
commit 6a651770b5
24 changed files with 753 additions and 632 deletions

View file

@ -24,7 +24,7 @@ CREATE TABLE IF NOT EXISTS `permission` (
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
ENGINE = MyISAM; ENGINE = MyISAM;
-- category represents "folder/label" assignment to document (1:M) -- category represents "folder/label/category" assignment to document (1:M)
DROP TABLE IF EXISTS `category`; DROP TABLE IF EXISTS `category`;
CREATE TABLE IF NOT EXISTS `category` ( CREATE TABLE IF NOT EXISTS `category` (
@ -32,7 +32,7 @@ CREATE TABLE IF NOT EXISTS `category` (
`refid` CHAR(16) NOT NULL COLLATE utf8_bin, `refid` CHAR(16) NOT NULL COLLATE utf8_bin,
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin, `orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
`labelid` CHAR(16) NOT NULL COLLATE utf8_bin, `labelid` CHAR(16) NOT NULL COLLATE utf8_bin,
`label` VARCHAR(30) NOT NULL, `category` VARCHAR(30) NOT NULL,
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE INDEX `idx_category_id` (`id` ASC), UNIQUE INDEX `idx_category_id` (`id` ASC),
INDEX `idx_category_refid` (`refid` ASC), INDEX `idx_category_refid` (`refid` ASC),
@ -45,6 +45,7 @@ DROP TABLE IF EXISTS `categorymember`;
CREATE TABLE IF NOT EXISTS `categorymember` ( CREATE TABLE IF NOT EXISTS `categorymember` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin, `orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
`categoryid` CHAR(16) NOT NULL COLLATE utf8_bin, `categoryid` CHAR(16) NOT NULL COLLATE utf8_bin,
`documentid` CHAR(16) NOT NULL COLLATE utf8_bin, `documentid` CHAR(16) NOT NULL COLLATE utf8_bin,

View file

@ -25,8 +25,8 @@ import (
"github.com/documize/community/core/secrets" "github.com/documize/community/core/secrets"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/document"
"github.com/documize/community/domain/organization" "github.com/documize/community/domain/organization"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search" indexer "github.com/documize/community/domain/search"
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
@ -89,7 +89,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -125,7 +125,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -177,7 +177,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }

View file

@ -22,7 +22,7 @@ import (
"github.com/documize/community/core/streamutil" "github.com/documize/community/core/streamutil"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/document" "github.com/documize/community/domain/permission"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/block" "github.com/documize/community/model/block"
) )
@ -57,7 +57,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanUploadDocument(ctx, *h.Store, b.LabelID) { if !permission.CanUploadDocument(ctx, *h.Store, b.LabelID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -165,7 +165,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
b.RefID = blockID b.RefID = blockID
if !document.CanUploadDocument(ctx, *h.Store, b.LabelID) { if !permission.CanUploadDocument(ctx, *h.Store, b.LabelID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }

View file

@ -28,7 +28,7 @@ import (
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/conversion/store" "github.com/documize/community/domain/conversion/store"
"github.com/documize/community/domain/document" "github.com/documize/community/domain/permission"
"github.com/documize/community/model/activity" "github.com/documize/community/model/activity"
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
@ -50,7 +50,7 @@ func (h *Handler) upload(w http.ResponseWriter, r *http.Request) (string, string
folderID := request.Param(r, "folderID") folderID := request.Param(r, "folderID")
if !document.CanUploadDocument(ctx, *h.Store, folderID) { if !permission.CanUploadDocument(ctx, *h.Store, folderID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return "", "", "" return "", "", ""
} }

View file

@ -23,8 +23,8 @@ import (
"github.com/documize/community/core/streamutil" "github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil" "github.com/documize/community/core/stringutil"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search" indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/space"
"github.com/documize/community/model/activity" "github.com/documize/community/model/activity"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/doc" "github.com/documize/community/model/doc"
@ -61,7 +61,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
return return
} }
if !CanViewDocumentInFolder(ctx, *h.Store, document.LabelID) { if !permission.CanViewSpaceDocument(ctx, *h.Store, document.LabelID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -147,7 +147,7 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
return return
} }
if !space.CanViewSpace(ctx, *h.Store, spaceID) { if !permission.CanViewSpace(ctx, *h.Store, spaceID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -210,7 +210,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
// return // return
// } // }
if !CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -269,7 +269,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
if !CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanDeleteDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }

View file

@ -1,117 +0,0 @@
// 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
package document
import (
"database/sql"
"github.com/documize/community/domain"
sp "github.com/documize/community/model/space"
)
// CanViewDocumentInFolder returns if the user has permission to view a document within the specified folder.
func CanViewDocumentInFolder(ctx domain.RequestContext, s domain.Store, labelID string) bool {
roles, err := s.Space.GetUserPermissions(ctx, labelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == labelID && role.Location == "space" && role.Scope == "object" &&
sp.HasPermission(role.Action, sp.SpaceView, sp.SpaceManage, sp.SpaceOwner) {
return true
}
}
return false
}
// CanViewDocument returns if the client has permission to view a given document.
func CanViewDocument(ctx domain.RequestContext, s domain.Store, documentID string) bool {
document, err := s.Document.Get(ctx, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
roles, err := s.Space.GetUserPermissions(ctx, document.LabelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == document.LabelID && role.Location == "space" && role.Scope == "object" &&
sp.HasPermission(role.Action, sp.SpaceView, sp.SpaceManage, sp.SpaceOwner) {
return true
}
}
return false
}
// CanChangeDocument returns if the clinet has permission to change a given document.
func CanChangeDocument(ctx domain.RequestContext, s domain.Store, documentID string) bool {
document, err := s.Document.Get(ctx, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
roles, err := s.Space.GetUserPermissions(ctx, document.LabelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == document.LabelID && role.Location == "space" && role.Scope == "object" &&
sp.HasPermission(role.Action, sp.DocumentEdit) {
return true
}
}
return false
}
// CanUploadDocument returns if the client has permission to upload documents to the given space.
func CanUploadDocument(ctx domain.RequestContext, s domain.Store, spaceID string) bool {
roles, err := s.Space.GetUserPermissions(ctx, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == spaceID && role.Location == "space" && role.Scope == "object" &&
sp.HasPermission(role.Action, sp.DocumentAdd) {
return true
}
}
return false
}

View file

@ -21,7 +21,7 @@ import (
"github.com/documize/community/core/response" "github.com/documize/community/core/response"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/document" "github.com/documize/community/domain/permission"
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
"github.com/documize/community/model/link" "github.com/documize/community/model/link"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
@ -57,7 +57,7 @@ func (h *Handler) GetLinkCandidates(w http.ResponseWriter, r *http.Request) {
} }
// permission check // permission check
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }

View file

@ -25,8 +25,8 @@ import (
"github.com/documize/community/core/streamutil" "github.com/documize/community/core/streamutil"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/document"
"github.com/documize/community/domain/link" "github.com/documize/community/domain/link"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search" indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
"github.com/documize/community/model/activity" "github.com/documize/community/model/activity"
@ -59,7 +59,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -165,7 +165,7 @@ func (h *Handler) GetPage(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -200,7 +200,7 @@ func (h *Handler) GetPages(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -238,7 +238,7 @@ func (h *Handler) GetPagesBatch(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -290,7 +290,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -366,7 +366,7 @@ func (h *Handler) DeletePages(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -471,7 +471,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -617,7 +617,7 @@ func (h *Handler) ChangePageSequence(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -678,7 +678,7 @@ func (h *Handler) ChangePageLevel(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -740,7 +740,7 @@ func (h *Handler) GetMeta(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -816,7 +816,7 @@ func (h *Handler) Copy(w http.ResponseWriter, r *http.Request) {
} }
// permission // permission
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -922,7 +922,7 @@ func (h *Handler) GetDocumentRevisions(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -953,7 +953,7 @@ func (h *Handler) GetRevisions(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -1000,7 +1000,7 @@ func (h *Handler) GetDiff(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -1064,7 +1064,7 @@ func (h *Handler) Rollback(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }

View file

@ -0,0 +1,278 @@
// 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
// Package permission handles API calls and persistence for spaces.
// Spaces in Documize contain documents.
package permission
import (
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/documize/community/core/env"
"github.com/documize/community/core/request"
"github.com/documize/community/core/response"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/mail"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/space"
)
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
}
// SetSpacePermissions persists specified space permissions
func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
method := "space.SetPermissions"
ctx := domain.GetRequestContext(r)
if !ctx.Editor {
response.WriteForbiddenError(w)
return
}
id := request.Param(r, "spaceID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
sp, err := h.Store.Space.Get(ctx, id)
if err != nil {
response.WriteNotFoundError(w, method, "space not found")
return
}
if sp.UserID != ctx.UserID {
response.WriteForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
h.Runtime.Log.Error(method, err)
return
}
var model = permission.PermissionsModel{}
err = json.Unmarshal(body, &model)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// We compare new permisions to what we had before.
// Why? So we can send out space invitation emails.
previousRoles, err := h.Store.Permission.GetSpacePermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Store all previous roles as map for easy querying
previousRoleUsers := make(map[string]bool)
for _, v := range previousRoles {
previousRoleUsers[v.WhoID] = true
}
// Who is sharing this space?
inviter, err := h.Store.User.Get(ctx, ctx.UserID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Nuke all previous permissions for this space
_, err = h.Store.Permission.DeleteSpacePermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
me := false
hasEveryoneRole := false
roleCount := 0
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
for _, perm := range model.Permissions {
perm.OrgID = ctx.OrgID
perm.SpaceID = id
// Ensure the space owner always has access!
if perm.UserID == ctx.UserID {
me = true
}
// Only persist if there is a role!
if permission.HasAnyPermission(perm) {
// identify publically shared spaces
if len(perm.UserID) == 0 {
hasEveryoneRole = true
}
r := permission.EncodeUserPermissions(perm)
for _, p := range r {
err = h.Store.Permission.AddPermission(ctx, p)
if err != nil {
h.Runtime.Log.Error("set permission", err)
}
roleCount++
}
// We send out space invitation emails to those users
// that have *just* been given permissions.
if _, isExisting := previousRoleUsers[perm.UserID]; !isExisting {
// we skip 'everyone' (user id != empty string)
if len(perm.UserID) > 0 {
existingUser, err := h.Store.User.Get(ctx, perm.UserID)
if err != nil {
response.WriteServerError(w, method, err)
break
}
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))
}
}
}
}
// Do we need to ensure permissions for space owner when shared?
if !me {
perm := permission.Permission{}
perm.OrgID = ctx.OrgID
perm.Who = "user"
perm.WhoID = ctx.UserID
perm.Scope = "object"
perm.Location = "space"
perm.RefID = id
perm.Action = "" // we send array for actions below
err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceView, permission.SpaceManage)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
return
}
}
// Mark up space type as either public, private or restricted access.
if hasEveryoneRole {
sp.Type = space.ScopePublic
} else {
if roleCount > 1 {
sp.Type = space.ScopeRestricted
} else {
sp.Type = space.ScopePrivate
}
}
err = h.Store.Space.Update(ctx, sp)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Store.Audit.Record(ctx, audit.EventTypeSpacePermission)
ctx.Transaction.Commit()
response.WriteEmpty(w)
}
// GetSpacePermissions returns permissions for alll users for given space.
func (h *Handler) GetSpacePermissions(w http.ResponseWriter, r *http.Request) {
method := "space.GetPermissions"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
perms, err := h.Store.Permission.GetSpacePermissions(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(perms) == 0 {
perms = []permission.Permission{}
}
userPerms := make(map[string][]permission.Permission)
for _, p := range perms {
userPerms[p.WhoID] = append(userPerms[p.WhoID], p)
}
records := []permission.Record{}
for _, up := range userPerms {
records = append(records, permission.DecodeUserPermissions(up))
}
response.WriteJSON(w, records)
}
// GetUserSpacePermissions returns permissions for the requested space, for current user.
func (h *Handler) GetUserSpacePermissions(w http.ResponseWriter, r *http.Request) {
method := "space.GetUserSpacePermissions"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(perms) == 0 {
perms = []permission.Permission{}
}
record := permission.DecodeUserPermissions(perms)
response.WriteJSON(w, record)
}

View file

@ -0,0 +1,136 @@
// 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
// Package mysql handles data persistence for space and document permissions.
package mysql
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/permission"
"github.com/pkg/errors"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
}
// AddPermission inserts the given record into the permisssion table.
func (s Scope) AddPermission(ctx domain.RequestContext, r permission.Permission) (err error) {
r.Created = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO permission (orgid, who, whoid, action, scope, location, refid, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "unable to prepare insert permission")
return
}
_, err = stmt.Exec(r.OrgID, r.Who, r.WhoID, string(r.Action), r.Scope, r.Location, r.RefID, r.Created)
if err != nil {
err = errors.Wrap(err, "unable to execute insert permission")
return
}
return
}
// AddPermissions inserts records into permission database table, one per action.
func (s Scope) AddPermissions(ctx domain.RequestContext, r permission.Permission, actions ...permission.Action) (err error) {
for _, a := range actions {
r.Action = a
s.AddPermission(ctx, r)
}
return
}
// GetUserSpacePermissions returns space permissions for user.
// Context is used to for user ID.
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='')
UNION ALL
SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid
FROM permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.location='space' AND refid=?
AND p.who='role' AND (r.userid=? OR r.userid='')`,
ctx.OrgID, spaceID, ctx.UserID, ctx.OrgID, spaceID, ctx.OrgID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select user permissions %s", ctx.UserID))
return
}
return
}
// GetSpacePermissions returns space permissions for all users.
func (s Scope) GetSpacePermissions(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'
UNION ALL
SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid
FROM permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.location='space' AND p.refid=?
AND p.who='role'`,
ctx.OrgID, spaceID, ctx.OrgID, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select space permissions %s", ctx.UserID))
return
}
return
}
// DeleteSpacePermissions removes records from permissions table for given space ID.
func (s Scope) DeleteSpacePermissions(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND location='space' AND refid='%s'", ctx.OrgID, spaceID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteUserSpacePermissions removes all roles for the specified user, for the specified space.
func (s Scope) DeleteUserSpacePermissions(ctx domain.RequestContext, spaceID, userID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND location='space' AND refid='%s' who='user' AND whoid='%s'",
ctx.OrgID, spaceID, userID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteUserPermissions removes all roles for the specified user, for the specified space.
func (s Scope) DeleteUserPermissions(ctx domain.RequestContext, userID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND who='user' AND whoid='%s'",
ctx.OrgID, userID)
return b.DeleteWhere(ctx.Transaction, sql)
}

View file

@ -0,0 +1,194 @@
// 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
package permission
import (
"database/sql"
"github.com/documize/community/domain"
pm "github.com/documize/community/model/permission"
)
// CanViewSpaceDocument returns if the user has permission to view a document within the specified folder.
func CanViewSpaceDocument(ctx domain.RequestContext, s domain.Store, labelID string) bool {
roles, err := s.Permission.GetUserSpacePermissions(ctx, labelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == labelID && role.Location == "space" && role.Scope == "object" &&
pm.HasPermission(role.Action, pm.SpaceView, pm.SpaceManage, pm.SpaceOwner) {
return true
}
}
return false
}
// CanViewDocument returns if the client has permission to view a given document.
func CanViewDocument(ctx domain.RequestContext, s domain.Store, documentID string) bool {
document, err := s.Document.Get(ctx, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
roles, err := s.Permission.GetUserSpacePermissions(ctx, document.LabelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == document.LabelID && role.Location == "space" && role.Scope == "object" &&
pm.HasPermission(role.Action, pm.SpaceView, pm.SpaceManage, pm.SpaceOwner) {
return true
}
}
return false
}
// CanChangeDocument returns if the clinet has permission to change a given document.
func CanChangeDocument(ctx domain.RequestContext, s domain.Store, documentID string) bool {
document, err := s.Document.Get(ctx, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
roles, err := s.Permission.GetUserSpacePermissions(ctx, document.LabelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == document.LabelID && role.Location == "space" && role.Scope == "object" && role.Action == pm.DocumentEdit {
return true
}
}
return false
}
// CanDeleteDocument returns if the clinet has permission to change a given document.
func CanDeleteDocument(ctx domain.RequestContext, s domain.Store, documentID string) bool {
document, err := s.Document.Get(ctx, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
roles, err := s.Permission.GetUserSpacePermissions(ctx, document.LabelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == document.LabelID && role.Location == "space" && role.Scope == "object" && role.Action == pm.DocumentDelete {
return true
}
}
return false
}
// CanUploadDocument returns if the client has permission to upload documents to the given space.
func CanUploadDocument(ctx domain.RequestContext, s domain.Store, spaceID string) bool {
roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == spaceID && role.Location == "space" && role.Scope == "object" &&
pm.HasPermission(role.Action, pm.DocumentAdd) {
return true
}
}
return false
}
// CanViewSpace returns if the user has permission to view the given spaceID.
func CanViewSpace(ctx domain.RequestContext, s domain.Store, spaceID string) bool {
roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == spaceID && role.Location == "space" && role.Scope == "object" &&
pm.HasPermission(role.Action, pm.SpaceView, pm.SpaceManage, pm.SpaceOwner) {
return true
}
}
return false
}
// HasDocumentAction returns if user can perform specified action.
func HasDocumentAction(ctx domain.RequestContext, s domain.Store, documentID string, a pm.Action) bool {
document, err := s.Document.Get(ctx, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
roles, err := s.Permission.GetUserSpacePermissions(ctx, document.LabelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == document.LabelID && role.Location == "space" && role.Scope == "object" && role.Action == a {
return true
}
}
return false
}

View file

@ -20,7 +20,7 @@ import (
"github.com/documize/community/core/response" "github.com/documize/community/core/response"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/document" "github.com/documize/community/domain/permission"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
) )
@ -59,7 +59,7 @@ func (h *Handler) RunSectionCommand(w http.ResponseWriter, r *http.Request) {
// it's up to the section handler to parse if required. // it's up to the section handler to parse if required.
// Permission checks // Permission checks
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -86,7 +86,7 @@ func (h *Handler) RefreshSections(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }

View file

@ -35,6 +35,7 @@ import (
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/doc" "github.com/documize/community/model/doc"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/space" "github.com/documize/community/model/space"
uuid "github.com/nu7hatch/gouuid" uuid "github.com/nu7hatch/gouuid"
) )
@ -105,7 +106,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return return
} }
perm := space.Permission{} perm := permission.Permission{}
perm.OrgID = sp.OrgID perm.OrgID = sp.OrgID
perm.Who = "user" perm.Who = "user"
perm.WhoID = ctx.UserID perm.WhoID = ctx.UserID
@ -114,7 +115,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
perm.RefID = sp.RefID perm.RefID = sp.RefID
perm.Action = "" // we send array for actions below perm.Action = "" // we send array for actions below
err = h.Store.Space.AddPermissions(ctx, perm, space.SpaceOwner, space.SpaceManage, space.SpaceView) err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceOwner, permission.SpaceManage, permission.SpaceView)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
@ -138,7 +139,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return return
} }
spCloneRoles, err := h.Store.Space.GetPermissions(ctx, model.CloneID) spCloneRoles, err := h.Store.Permission.GetSpacePermissions(ctx, model.CloneID)
if err != nil { if err != nil {
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)
@ -149,7 +150,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
for _, r := range spCloneRoles { for _, r := range spCloneRoles {
r.RefID = sp.RefID r.RefID = sp.RefID
err = h.Store.Space.AddPermission(ctx, r) err = h.Store.Permission.AddPermission(ctx, r)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
@ -451,7 +452,7 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
return return
} }
_, err = h.Store.Space.DeletePermissions(ctx, id) _, err = h.Store.Permission.DeleteSpacePermissions(ctx, id)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
@ -519,7 +520,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
_, err = h.Store.Space.DeletePermissions(ctx, id) _, err = h.Store.Permission.DeleteSpacePermissions(ctx, id)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
@ -542,245 +543,6 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
response.WriteEmpty(w) response.WriteEmpty(w)
} }
// SetPermissions persists specified spac3 permissions
func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) {
method := "space.SetPermissions"
ctx := domain.GetRequestContext(r)
if !ctx.Editor {
response.WriteForbiddenError(w)
return
}
id := request.Param(r, "spaceID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
sp, err := h.Store.Space.Get(ctx, id)
if err != nil {
response.WriteNotFoundError(w, method, "space not found")
return
}
if sp.UserID != ctx.UserID {
response.WriteForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
h.Runtime.Log.Error(method, err)
return
}
var model = space.PermissionsModel{}
err = json.Unmarshal(body, &model)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// We compare new permisions to what we had before.
// Why? So we can send out space invitation emails.
previousRoles, err := h.Store.Space.GetPermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Store all previous roles as map for easy querying
previousRoleUsers := make(map[string]bool)
for _, v := range previousRoles {
previousRoleUsers[v.WhoID] = true
}
// Who is sharing this space?
inviter, err := h.Store.User.Get(ctx, ctx.UserID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Nuke all previous permissions for this space
_, err = h.Store.Space.DeletePermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
me := false
hasEveryoneRole := false
roleCount := 0
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
for _, perm := range model.Permissions {
perm.OrgID = ctx.OrgID
perm.SpaceID = id
// Ensure the space owner always has access!
if perm.UserID == ctx.UserID {
me = true
}
// Only persist if there is a role!
if space.HasAnyPermission(perm) {
// identify publically shared spaces
if len(perm.UserID) == 0 {
hasEveryoneRole = true
}
r := space.EncodeUserPermissions(perm)
for _, p := range r {
err = h.Store.Space.AddPermission(ctx, p)
if err != nil {
h.Runtime.Log.Error("set permission", err)
}
roleCount++
}
// We send out space invitation emails to those users
// that have *just* been given permissions.
if _, isExisting := previousRoleUsers[perm.UserID]; !isExisting {
// we skip 'everyone' (user id != empty string)
if len(perm.UserID) > 0 {
existingUser, err := h.Store.User.Get(ctx, perm.UserID)
if err != nil {
response.WriteServerError(w, method, err)
break
}
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))
}
}
}
}
// Do we need to ensure permissions for space owner when shared?
if !me {
perm := space.Permission{}
perm.OrgID = ctx.OrgID
perm.Who = "user"
perm.WhoID = ctx.UserID
perm.Scope = "object"
perm.Location = "space"
perm.RefID = id
perm.Action = "" // we send array for actions below
err = h.Store.Space.AddPermissions(ctx, perm, space.SpaceView, space.SpaceManage)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
return
}
}
// Mark up space type as either public, private or restricted access.
if hasEveryoneRole {
sp.Type = space.ScopePublic
} else {
if roleCount > 1 {
sp.Type = space.ScopeRestricted
} else {
sp.Type = space.ScopePrivate
}
}
err = h.Store.Space.Update(ctx, sp)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Store.Audit.Record(ctx, audit.EventTypeSpacePermission)
ctx.Transaction.Commit()
response.WriteEmpty(w)
}
// GetPermissions returns permissions for alll users for given space.
func (h *Handler) GetPermissions(w http.ResponseWriter, r *http.Request) {
method := "space.GetPermissions"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
perms, err := h.Store.Space.GetPermissions(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(perms) == 0 {
perms = []space.Permission{}
}
userPerms := make(map[string][]space.Permission)
for _, p := range perms {
userPerms[p.WhoID] = append(userPerms[p.WhoID], p)
}
records := []space.PermissionRecord{}
for _, up := range userPerms {
records = append(records, space.DecodeUserPermissions(up))
}
response.WriteJSON(w, records)
}
// GetUserPermissions returns permissions for the requested space, for current user.
func (h *Handler) GetUserPermissions(w http.ResponseWriter, r *http.Request) {
method := "space.GetUserPermissions"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
perms, err := h.Store.Space.GetUserPermissions(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(perms) == 0 {
perms = []space.Permission{}
}
record := space.DecodeUserPermissions(perms)
response.WriteJSON(w, record)
}
// AcceptInvitation records the fact that a user has completed space onboard process. // AcceptInvitation records the fact that a user has completed space onboard process.
func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) { func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
method := "space.AcceptInvitation" method := "space.AcceptInvitation"
@ -971,9 +733,9 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
} }
// Ensure they have space roles // Ensure they have space roles
h.Store.Space.DeleteUserPermissions(ctx, sp.RefID, u.RefID) h.Store.Permission.DeleteUserSpacePermissions(ctx, sp.RefID, u.RefID)
perm := space.Permission{} perm := permission.Permission{}
perm.OrgID = sp.OrgID perm.OrgID = sp.OrgID
perm.Who = "user" perm.Who = "user"
perm.WhoID = u.RefID perm.WhoID = u.RefID
@ -982,7 +744,7 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
perm.RefID = sp.RefID perm.RefID = sp.RefID
perm.Action = "" // we send array for actions below perm.Action = "" // we send array for actions below
err = h.Store.Space.AddPermissions(ctx, perm, space.SpaceView) err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceView)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)

View file

@ -13,7 +13,6 @@
package mysql package mysql
import ( import (
"database/sql"
"fmt" "fmt"
"time" "time"
@ -163,108 +162,3 @@ func (s Scope) Delete(ctx domain.RequestContext, id string) (rows int64, err err
b := mysql.BaseQuery{} b := mysql.BaseQuery{}
return b.DeleteConstrained(ctx.Transaction, "label", ctx.OrgID, id) return b.DeleteConstrained(ctx.Transaction, "label", ctx.OrgID, id)
} }
// AddPermission inserts the given record into the permisssion table.
func (s Scope) AddPermission(ctx domain.RequestContext, r space.Permission) (err error) {
r.Created = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO permission (orgid, who, whoid, action, scope, location, refid, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "unable to prepare insert for space permission")
return
}
_, err = stmt.Exec(r.OrgID, r.Who, r.WhoID, string(r.Action), r.Scope, r.Location, r.RefID, r.Created)
if err != nil {
err = errors.Wrap(err, "unable to execute insert for space permission")
return
}
return
}
// AddPermissions inserts records into permission database table, one per action.
func (s Scope) AddPermissions(ctx domain.RequestContext, r space.Permission, actions ...space.PermissionAction) (err error) {
for _, a := range actions {
r.Action = a
s.AddPermission(ctx, r)
}
return
}
// GetUserPermissions returns space permissions for user.
// Context is used to for user ID.
func (s Scope) GetUserPermissions(ctx domain.RequestContext, spaceID string) (r []space.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='')
UNION ALL
SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid
FROM permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.location='space' AND refid=?
AND p.who='role' AND (r.userid=? OR r.userid='')`,
ctx.OrgID, spaceID, ctx.UserID, ctx.OrgID, spaceID, ctx.OrgID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select user permissions %s", ctx.UserID))
return
}
return
}
// GetPermissions returns space permissions for all users.
func (s Scope) GetPermissions(ctx domain.RequestContext, spaceID string) (r []space.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'
UNION ALL
SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid
FROM permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.location='space' AND p.refid=?
AND p.who='role'`,
ctx.OrgID, spaceID, ctx.OrgID, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select space permissions %s", ctx.UserID))
return
}
return
}
// DeletePermissions removes records from permissions table for given space ID.
func (s Scope) DeletePermissions(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND location='space' AND refid='%s'", ctx.OrgID, spaceID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteUserPermissions removes all roles for the specified user, for the specified space.
func (s Scope) DeleteUserPermissions(ctx domain.RequestContext, spaceID, userID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND location='space' AND refid='%s' who='user' AND whoid='%s'",
ctx.OrgID, spaceID, userID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteAllUserPermissions removes all roles for the specified user, for the specified space.
func (s Scope) DeleteAllUserPermissions(ctx domain.RequestContext, userID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND who='user' AND whoid='%s'",
ctx.OrgID, userID)
return b.DeleteWhere(ctx.Transaction, sql)
}

View file

@ -1,41 +0,0 @@
// 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
// Package space handles API calls and persistence for spaces.
// Spaces in Documize contain documents.
package space
import (
"database/sql"
"github.com/documize/community/domain"
"github.com/documize/community/model/space"
)
// CanViewSpace returns if the user has permission to view the given spaceID.
func CanViewSpace(ctx domain.RequestContext, s domain.Store, spaceID string) bool {
roles, err := s.Space.GetUserPermissions(ctx, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == spaceID && role.Location == "space" && role.Scope == "object" &&
space.HasPermission(role.Action, space.SpaceView, space.SpaceManage, space.SpaceOwner) {
return true
}
}
return false
}

View file

@ -20,6 +20,7 @@ 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/account" "github.com/documize/community/model/account"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/space" "github.com/documize/community/model/space"
"github.com/documize/community/model/user" "github.com/documize/community/model/user"
) )
@ -61,7 +62,7 @@ func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *d
return return
} }
perm := space.Permission{} perm := permission.Permission{}
perm.OrgID = sp.OrgID perm.OrgID = sp.OrgID
perm.Who = "user" perm.Who = "user"
perm.WhoID = userID perm.WhoID = userID
@ -70,7 +71,7 @@ func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *d
perm.RefID = sp.RefID perm.RefID = sp.RefID
perm.Action = "" // we send array for actions below perm.Action = "" // we send array for actions below
err = s.Space.AddPermissions(ctx, perm, space.SpaceView) err = s.Permission.AddPermissions(ctx, perm, permission.SpaceView)
if err != nil { if err != nil {
return return
} }

View file

@ -22,6 +22,7 @@ import (
"github.com/documize/community/model/link" "github.com/documize/community/model/link"
"github.com/documize/community/model/org" "github.com/documize/community/model/org"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/pin" "github.com/documize/community/model/pin"
"github.com/documize/community/model/search" "github.com/documize/community/model/search"
"github.com/documize/community/model/space" "github.com/documize/community/model/space"
@ -40,6 +41,7 @@ type Store struct {
Organization OrganizationStorer Organization OrganizationStorer
Page PageStorer Page PageStorer
Pin PinStorer Pin PinStorer
Permission PermissionStorer
Search SearchStorer Search SearchStorer
Setting SettingStorer Setting SettingStorer
Space SpaceStorer Space SpaceStorer
@ -55,14 +57,17 @@ type SpaceStorer interface {
Update(ctx RequestContext, sp space.Space) (err error) Update(ctx RequestContext, sp space.Space) (err error)
Viewers(ctx RequestContext) (v []space.Viewer, err error) Viewers(ctx RequestContext) (v []space.Viewer, err error)
Delete(ctx RequestContext, id string) (rows int64, err error) Delete(ctx RequestContext, id string) (rows int64, err error)
}
AddPermission(ctx RequestContext, r space.Permission) (err error) // PermissionStorer defines required methods for space/document permission management
AddPermissions(ctx RequestContext, r space.Permission, actions ...space.PermissionAction) (err error) type PermissionStorer interface {
GetUserPermissions(ctx RequestContext, spaceID string) (r []space.Permission, err error) AddPermission(ctx RequestContext, r permission.Permission) (err error)
GetPermissions(ctx RequestContext, spaceID string) (r []space.Permission, err error) AddPermissions(ctx RequestContext, r permission.Permission, actions ...permission.Action) (err error)
DeletePermissions(ctx RequestContext, spaceID string) (rows int64, err error) GetUserSpacePermissions(ctx RequestContext, spaceID string) (r []permission.Permission, err error)
DeleteUserPermissions(ctx RequestContext, spaceID, userID string) (rows int64, err error) GetSpacePermissions(ctx RequestContext, spaceID string) (r []permission.Permission, err error)
DeleteAllUserPermissions(ctx RequestContext, userID string) (rows int64, err error) DeleteSpacePermissions(ctx RequestContext, spaceID string) (rows int64, err error)
DeleteUserSpacePermissions(ctx RequestContext, spaceID, userID string) (rows int64, err error)
DeleteUserPermissions(ctx RequestContext, userID string) (rows int64, err error)
} }
// UserStorer defines required methods for user management // UserStorer defines required methods for user management

View file

@ -27,12 +27,13 @@ import (
"github.com/documize/community/core/stringutil" "github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/document" "github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search" indexer "github.com/documize/community/domain/search"
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/doc" "github.com/documize/community/model/doc"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
pm "github.com/documize/community/model/permission"
"github.com/documize/community/model/template" "github.com/documize/community/model/template"
uuid "github.com/nu7hatch/gouuid" uuid "github.com/nu7hatch/gouuid"
) )
@ -112,7 +113,7 @@ func (h *Handler) SaveAs(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, model.DocumentID) { if !permission.HasDocumentAction(ctx, *h.Store, model.DocumentID, pm.DocumentTemplate) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }

View file

@ -378,7 +378,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
} }
// remove all associated roles for this user // remove all associated roles for this user
_, err = h.Store.Space.DeleteAllUserPermissions(ctx, userID) _, err = h.Store.Permission.DeleteUserPermissions(ctx, userID)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)

View file

@ -24,6 +24,7 @@ import (
link "github.com/documize/community/domain/link/mysql" link "github.com/documize/community/domain/link/mysql"
org "github.com/documize/community/domain/organization/mysql" org "github.com/documize/community/domain/organization/mysql"
page "github.com/documize/community/domain/page/mysql" page "github.com/documize/community/domain/page/mysql"
permission "github.com/documize/community/domain/permission/mysql"
pin "github.com/documize/community/domain/pin/mysql" pin "github.com/documize/community/domain/pin/mysql"
search "github.com/documize/community/domain/search/mysql" search "github.com/documize/community/domain/search/mysql"
setting "github.com/documize/community/domain/setting/mysql" setting "github.com/documize/community/domain/setting/mysql"
@ -43,6 +44,7 @@ func StoreMySQL(r *env.Runtime, s *domain.Store) {
s.Organization = org.Scope{Runtime: r} s.Organization = org.Scope{Runtime: r}
s.Page = page.Scope{Runtime: r} s.Page = page.Scope{Runtime: r}
s.Pin = pin.Scope{Runtime: r} s.Pin = pin.Scope{Runtime: r}
s.Permission = permission.Scope{Runtime: r}
s.Search = search.Scope{Runtime: r} s.Search = search.Scope{Runtime: r}
s.Setting = setting.Scope{Runtime: r} s.Setting = setting.Scope{Runtime: r}
s.Space = space.Scope{Runtime: r} s.Space = space.Scope{Runtime: r}

View file

@ -19,6 +19,12 @@
<div class="round-button-mono" id="page-menu-{{page.id}}"> <div class="round-button-mono" id="page-menu-{{page.id}}">
<i class="material-icons color-gray">more_vert</i> <i class="material-icons color-gray">more_vert</i>
</div> </div>
{{/if}}
<div class="round-button-mono" {{action 'toggleExpand'}}>
<i class="material-icons color-gray">expand_less</i>
</div>
{{#if hasMenuPermissions}}
{{#dropdown-menu target=menuTarget position="top right" open="click" onOpenCallback=(action 'onMenuOpen') onCloseCallback=(action 'onMenuOpen')}} {{#dropdown-menu target=menuTarget position="top right" open="click" onOpenCallback=(action 'onMenuOpen') onCloseCallback=(action 'onMenuOpen')}}
<ul class="menu"> <ul class="menu">
{{#if permissions.documentCopy}} {{#if permissions.documentCopy}}
@ -36,9 +42,6 @@
</ul> </ul>
{{/dropdown-menu}} {{/dropdown-menu}}
{{/if}} {{/if}}
<div class="round-button-mono" {{action 'toggleExpand'}}>
<i class="material-icons color-gray">expand_less</i>
</div>
{{#if menuOpen}} {{#if menuOpen}}
{{#if permissions.documentDelete}} {{#if permissions.documentDelete}}

View file

@ -9,12 +9,52 @@
// //
// https://documize.com // https://documize.com
package space package permission
// PermissionRecord represents space permissions for a user on a space. import "time"
// Permission represents a permission for a space and is persisted to the database.
type Permission struct {
ID uint64 `json:"id"`
OrgID string `json:"-"`
Who string `json:"who"` // user, role
WhoID string `json:"whoId"` // either a user or role ID
Action Action `json:"action"` // view, edit, delete
Scope string `json:"scope"` // object, table
Location string `json:"location"` // table name
RefID string `json:"refId"` // id of row in table / blank when scope=table
Created time.Time `json:"created"`
}
// Action details type of action
type Action string
const (
// SpaceView action means you can view a space and documents therein
SpaceView Action = "view"
// SpaceManage action means you can add, remove users, set permissions, but not delete that space
SpaceManage Action = "manage"
// SpaceOwner action means you can delete a space and do all SpaceManage functions
SpaceOwner Action = "own"
// DocumentAdd action means you can create/upload documents to a space
DocumentAdd Action = "doc-add"
// DocumentEdit action means you can edit documents in a space
DocumentEdit Action = "doc-edit"
// DocumentDelete means you can delete documents in a space
DocumentDelete Action = "doc-delete"
// DocumentMove means you can move documents between spaces
DocumentMove Action = "doc-move"
// DocumentCopy means you can copy documents within and between spaces
DocumentCopy Action = "doc-copy"
// DocumentTemplate means you can create, edit and delete document templates and content blocks
DocumentTemplate Action = "doc-template"
)
// Record represents space permissions for a user on a space.
// 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 PermissionRecord struct { type Record struct {
OrgID string `json:"orgId"` OrgID string `json:"orgId"`
SpaceID string `json:"folderId"` SpaceID string `json:"folderId"`
UserID string `json:"userId"` UserID string `json:"userId"`
@ -31,8 +71,8 @@ type PermissionRecord struct {
// DecodeUserPermissions returns a flat, usable permission summary record // DecodeUserPermissions returns a flat, usable permission summary record
// from multiple user permission records for a given space. // from multiple user permission records for a given space.
func DecodeUserPermissions(perm []Permission) (r PermissionRecord) { func DecodeUserPermissions(perm []Permission) (r Record) {
r = PermissionRecord{} r = Record{}
if len(perm) > 0 { if len(perm) > 0 {
r.OrgID = perm[0].OrgID r.OrgID = perm[0].OrgID
@ -67,9 +107,26 @@ func DecodeUserPermissions(perm []Permission) (r PermissionRecord) {
return return
} }
// PermissionsModel details which users have what permissions on a given space.
type PermissionsModel struct {
Message string
Permissions []Record
}
// HasPermission checks if action matches one of the required actions?
func HasPermission(action Action, actions ...Action) bool {
for _, a := range actions {
if action == a {
return true
}
}
return false
}
// EncodeUserPermissions returns multiple user permission records // EncodeUserPermissions returns multiple user permission records
// for a given space, using flat permission summary record. // for a given space, using flat permission summary record.
func EncodeUserPermissions(r PermissionRecord) (perm []Permission) { func EncodeUserPermissions(r Record) (perm []Permission) {
if r.SpaceView { if r.SpaceView {
perm = append(perm, EncodeRecord(r, SpaceView)) perm = append(perm, EncodeRecord(r, SpaceView))
} }
@ -103,13 +160,13 @@ func EncodeUserPermissions(r PermissionRecord) (perm []Permission) {
} }
// HasAnyPermission returns true if user has at least one permission. // HasAnyPermission returns true if user has at least one permission.
func HasAnyPermission(p PermissionRecord) bool { func HasAnyPermission(p Record) bool {
return p.SpaceView || p.SpaceManage || p.SpaceOwner || p.DocumentAdd || p.DocumentEdit || return p.SpaceView || p.SpaceManage || p.SpaceOwner || p.DocumentAdd || p.DocumentEdit ||
p.DocumentDelete || p.DocumentMove || p.DocumentCopy || p.DocumentTemplate p.DocumentDelete || p.DocumentMove || p.DocumentCopy || p.DocumentTemplate
} }
// EncodeRecord creates standard permission record representing user permissions for a space. // EncodeRecord creates standard permission record representing user permissions for a space.
func EncodeRecord(r PermissionRecord, a PermissionAction) (p Permission) { func EncodeRecord(r Record, a Action) (p Permission) {
p = Permission{} p = Permission{}
p.OrgID = r.OrgID p.OrgID = r.OrgID
p.Who = "user" p.Who = "user"

View file

@ -12,8 +12,6 @@
package space package space
import ( import (
"time"
"github.com/documize/community/model" "github.com/documize/community/model"
) )
@ -55,44 +53,6 @@ func (l *Space) IsRestricted() bool {
return l.Type == ScopeRestricted return l.Type == ScopeRestricted
} }
// Permission represents a permission for a space and is persisted to the database.
type Permission struct {
ID uint64 `json:"id"`
OrgID string `json:"-"`
Who string `json:"who"` // user, role
WhoID string `json:"whoId"` // either a user or role ID
Action PermissionAction `json:"action"` // view, edit, delete
Scope string `json:"scope"` // object, table
Location string `json:"location"` // table name
RefID string `json:"refId"` // id of row in table / blank when scope=table
Created time.Time `json:"created"`
}
// PermissionAction details type of action
type PermissionAction string
const (
// SpaceView action means you can view a space and documents therein
SpaceView PermissionAction = "view"
// SpaceManage action means you can add, remove users, set permissions, but not delete that space
SpaceManage PermissionAction = "manage"
// SpaceOwner action means you can delete a space and do all SpaceManage functions
SpaceOwner PermissionAction = "own"
// DocumentAdd action means you can create/upload documents to a space
DocumentAdd PermissionAction = "doc-add"
// DocumentEdit action means you can edit documents in a space
DocumentEdit PermissionAction = "doc-edit"
// DocumentDelete means you can delete documents in a space
DocumentDelete PermissionAction = "doc-delete"
// DocumentMove means you can move documents between spaces
DocumentMove PermissionAction = "doc-move"
// DocumentCopy means you can copy documents within and between spaces
DocumentCopy PermissionAction = "doc-copy"
// DocumentTemplate means you can create, edit and delete document templates and content blocks
DocumentTemplate PermissionAction = "doc-template"
)
// Viewer details who can see a particular space // Viewer details who can see a particular space
type Viewer struct { type Viewer struct {
Name string `json:"name"` Name string `json:"name"`
@ -104,12 +64,6 @@ type Viewer struct {
Email string `json:"email"` Email string `json:"email"`
} }
// PermissionsModel details which users have what permissions on a given space.
type PermissionsModel struct {
Message string
Permissions []PermissionRecord
}
// AcceptShareModel is used to setup a user who has accepted a shared space. // AcceptShareModel is used to setup a user who has accepted a shared space.
type AcceptShareModel struct { type AcceptShareModel struct {
Serial string `json:"serial"` Serial string `json:"serial"`
@ -132,14 +86,3 @@ type NewSpaceRequest struct {
CopyPermission bool `json:"copyPermission"` // copy uer permissions CopyPermission bool `json:"copyPermission"` // copy uer permissions
CopyDocument bool `json:"copyDocument"` // copy all documents! CopyDocument bool `json:"copyDocument"` // copy all documents!
} }
// HasPermission checks if action matches one of the required actions?
func HasPermission(action PermissionAction, actions ...PermissionAction) bool {
for _, a := range actions {
if action == a {
return true
}
}
return false
}

View file

@ -26,6 +26,7 @@ import (
"github.com/documize/community/domain/meta" "github.com/documize/community/domain/meta"
"github.com/documize/community/domain/organization" "github.com/documize/community/domain/organization"
"github.com/documize/community/domain/page" "github.com/documize/community/domain/page"
"github.com/documize/community/domain/permission"
"github.com/documize/community/domain/pin" "github.com/documize/community/domain/pin"
"github.com/documize/community/domain/search" "github.com/documize/community/domain/search"
"github.com/documize/community/domain/section" "github.com/documize/community/domain/section"
@ -58,6 +59,7 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
document := document.Handler{Runtime: rt, Store: s, Indexer: indexer} document := document.Handler{Runtime: rt, Store: s, Indexer: indexer}
attachment := attachment.Handler{Runtime: rt, Store: s, Indexer: indexer} attachment := attachment.Handler{Runtime: rt, Store: s, Indexer: indexer}
conversion := conversion.Handler{Runtime: rt, Store: s, Indexer: indexer} conversion := conversion.Handler{Runtime: rt, Store: s, Indexer: indexer}
permission := permission.Handler{Runtime: rt, Store: s}
organization := organization.Handler{Runtime: rt, Store: s} organization := organization.Handler{Runtime: rt, Store: s}
//************************************************** //**************************************************
@ -113,9 +115,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, space.SetPermissions) Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions", []string{"PUT", "OPTIONS"}, nil, permission.SetSpacePermissions)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions/user", []string{"GET", "OPTIONS"}, nil, space.GetUserPermissions) Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions/user", []string{"GET", "OPTIONS"}, nil, permission.GetUserSpacePermissions)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions", []string{"GET", "OPTIONS"}, nil, space.GetPermissions) 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", []string{"GET", "OPTIONS"}, []string{"filter", "viewers"}, space.GetSpaceViewers) Add(rt, RoutePrefixPrivate, "space", []string{"GET", "OPTIONS"}, []string{"filter", "viewers"}, space.GetSpaceViewers)
Add(rt, RoutePrefixPrivate, "space", []string{"POST", "OPTIONS"}, nil, space.Add) Add(rt, RoutePrefixPrivate, "space", []string{"POST", "OPTIONS"}, nil, space.Add)