1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-24 07:39:43 +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
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`;
CREATE TABLE IF NOT EXISTS `category` (
@ -32,7 +32,7 @@ CREATE TABLE IF NOT EXISTS `category` (
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
`orgid` 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,
UNIQUE INDEX `idx_category_id` (`id` ASC),
INDEX `idx_category_refid` (`refid` ASC),
@ -45,6 +45,7 @@ DROP TABLE IF EXISTS `categorymember`;
CREATE TABLE IF NOT EXISTS `categorymember` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
`categoryid` 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/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/document"
"github.com/documize/community/domain/organization"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search"
"github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit"
@ -89,7 +89,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanViewDocument(ctx, *h.Store, documentID) {
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -125,7 +125,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -177,7 +177,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}

View file

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

View file

@ -28,7 +28,7 @@ import (
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"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/attachment"
"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")
if !document.CanUploadDocument(ctx, *h.Store, folderID) {
if !permission.CanUploadDocument(ctx, *h.Store, folderID) {
response.WriteForbiddenError(w)
return "", "", ""
}

View file

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

View file

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

View file

@ -35,6 +35,7 @@ import (
"github.com/documize/community/model/audit"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/page"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/space"
uuid "github.com/nu7hatch/gouuid"
)
@ -105,7 +106,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return
}
perm := space.Permission{}
perm := permission.Permission{}
perm.OrgID = sp.OrgID
perm.Who = "user"
perm.WhoID = ctx.UserID
@ -114,7 +115,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
perm.RefID = sp.RefID
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 {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -138,7 +139,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return
}
spCloneRoles, err := h.Store.Space.GetPermissions(ctx, model.CloneID)
spCloneRoles, err := h.Store.Permission.GetSpacePermissions(ctx, model.CloneID)
if err != nil {
response.WriteServerError(w, 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 {
r.RefID = sp.RefID
err = h.Store.Space.AddPermission(ctx, r)
err = h.Store.Permission.AddPermission(ctx, r)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -451,7 +452,7 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
return
}
_, err = h.Store.Space.DeletePermissions(ctx, id)
_, err = h.Store.Permission.DeleteSpacePermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -519,7 +520,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
_, err = h.Store.Space.DeletePermissions(ctx, id)
_, err = h.Store.Permission.DeleteSpacePermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -542,245 +543,6 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
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.
func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
method := "space.AcceptInvitation"
@ -971,9 +733,9 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
}
// 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.Who = "user"
perm.WhoID = u.RefID
@ -982,7 +744,7 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
perm.RefID = sp.RefID
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 {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)

View file

@ -13,7 +13,6 @@
package mysql
import (
"database/sql"
"fmt"
"time"
@ -163,108 +162,3 @@ func (s Scope) Delete(ctx domain.RequestContext, id string) (rows int64, err err
b := mysql.BaseQuery{}
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/mail"
"github.com/documize/community/model/account"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/space"
"github.com/documize/community/model/user"
)
@ -61,7 +62,7 @@ func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *d
return
}
perm := space.Permission{}
perm := permission.Permission{}
perm.OrgID = sp.OrgID
perm.Who = "user"
perm.WhoID = userID
@ -70,7 +71,7 @@ func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *d
perm.RefID = sp.RefID
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 {
return
}

View file

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

View file

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

View file

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

View file

@ -24,6 +24,7 @@ import (
link "github.com/documize/community/domain/link/mysql"
org "github.com/documize/community/domain/organization/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"
search "github.com/documize/community/domain/search/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.Page = page.Scope{Runtime: r}
s.Pin = pin.Scope{Runtime: r}
s.Permission = permission.Scope{Runtime: r}
s.Search = search.Scope{Runtime: r}
s.Setting = setting.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}}">
<i class="material-icons color-gray">more_vert</i>
</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')}}
<ul class="menu">
{{#if permissions.documentCopy}}
@ -36,9 +42,6 @@
</ul>
{{/dropdown-menu}}
{{/if}}
<div class="round-button-mono" {{action 'toggleExpand'}}>
<i class="material-icons color-gray">expand_less</i>
</div>
{{#if menuOpen}}
{{#if permissions.documentDelete}}

View file

@ -9,12 +9,52 @@
//
// 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,
// and it is designed to be sent to HTTP clients (web, mobile).
type PermissionRecord struct {
type Record struct {
OrgID string `json:"orgId"`
SpaceID string `json:"folderId"`
UserID string `json:"userId"`
@ -31,8 +71,8 @@ type PermissionRecord struct {
// DecodeUserPermissions returns a flat, usable permission summary record
// from multiple user permission records for a given space.
func DecodeUserPermissions(perm []Permission) (r PermissionRecord) {
r = PermissionRecord{}
func DecodeUserPermissions(perm []Permission) (r Record) {
r = Record{}
if len(perm) > 0 {
r.OrgID = perm[0].OrgID
@ -67,9 +107,26 @@ func DecodeUserPermissions(perm []Permission) (r PermissionRecord) {
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
// for a given space, using flat permission summary record.
func EncodeUserPermissions(r PermissionRecord) (perm []Permission) {
func EncodeUserPermissions(r Record) (perm []Permission) {
if 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.
func HasAnyPermission(p PermissionRecord) bool {
func HasAnyPermission(p Record) bool {
return p.SpaceView || p.SpaceManage || p.SpaceOwner || p.DocumentAdd || p.DocumentEdit ||
p.DocumentDelete || p.DocumentMove || p.DocumentCopy || p.DocumentTemplate
}
// 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.OrgID = r.OrgID
p.Who = "user"

View file

@ -12,8 +12,6 @@
package space
import (
"time"
"github.com/documize/community/model"
)
@ -55,44 +53,6 @@ func (l *Space) IsRestricted() bool {
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
type Viewer struct {
Name string `json:"name"`
@ -104,12 +64,6 @@ type Viewer struct {
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.
type AcceptShareModel struct {
Serial string `json:"serial"`
@ -132,14 +86,3 @@ type NewSpaceRequest struct {
CopyPermission bool `json:"copyPermission"` // copy uer permissions
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/organization"
"github.com/documize/community/domain/page"
"github.com/documize/community/domain/permission"
"github.com/documize/community/domain/pin"
"github.com/documize/community/domain/search"
"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}
attachment := attachment.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}
//**************************************************
@ -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}/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/user", []string{"GET", "OPTIONS"}, nil, space.GetUserPermissions)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions", []string{"GET", "OPTIONS"}, nil, space.GetPermissions)
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", []string{"GET", "OPTIONS"}, []string{"filter", "viewers"}, space.GetSpaceViewers)
Add(rt, RoutePrefixPrivate, "space", []string{"POST", "OPTIONS"}, nil, space.Add)