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

re-working space permissions -- WIP

This commit is contained in:
Harvey Kandola 2017-09-13 19:22:38 +01:00
parent c51ba65b1d
commit ae05cacf3f
37 changed files with 735 additions and 601 deletions

View file

@ -34,7 +34,7 @@ func (s Scope) Add(ctx domain.RequestContext, account account.Account) (err erro
account.Created = time.Now().UTC()
account.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO account (refid, orgid, userid, admin, editor, users, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
stmt, err := ctx.Transaction.Preparex("INSERT INTO account (refid, orgid, userid, admin, editor, users, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
if err != nil {

View file

@ -15,12 +15,12 @@ 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) (hasPermission bool) {
roles, err := s.Space.GetUserRoles(ctx)
func CanViewDocumentInFolder(ctx domain.RequestContext, s domain.Store, labelID string) bool {
roles, err := s.Space.GetUserPermissions(ctx, labelID)
if err == sql.ErrNoRows {
err = nil
}
@ -29,7 +29,8 @@ func CanViewDocumentInFolder(ctx domain.RequestContext, s domain.Store, labelID
}
for _, role := range roles {
if role.LabelID == labelID && (role.CanView || role.CanEdit) {
if role.RefID == labelID && role.Location == "space" && role.Scope == "object" &&
sp.HasPermission(role.Action, sp.SpaceView, sp.SpaceManage, sp.SpaceOwner) {
return true
}
}
@ -37,10 +38,9 @@ func CanViewDocumentInFolder(ctx domain.RequestContext, s domain.Store, labelID
return false
}
// CanViewDocument returns if the clinet has permission to view a given document.
func CanViewDocument(ctx domain.RequestContext, s domain.Store, documentID string) (hasPermission bool) {
// 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
}
@ -48,8 +48,7 @@ func CanViewDocument(ctx domain.RequestContext, s domain.Store, documentID strin
return false
}
roles, err := s.Space.GetUserRoles(ctx)
roles, err := s.Space.GetUserPermissions(ctx, document.LabelID)
if err == sql.ErrNoRows {
err = nil
}
@ -58,7 +57,8 @@ func CanViewDocument(ctx domain.RequestContext, s domain.Store, documentID strin
}
for _, role := range roles {
if role.LabelID == document.LabelID && (role.CanView || role.CanEdit) {
if role.RefID == document.LabelID && role.Location == "space" && role.Scope == "object" &&
sp.HasPermission(role.Action, sp.SpaceView, sp.SpaceManage, sp.SpaceOwner) {
return true
}
}
@ -67,7 +67,7 @@ func CanViewDocument(ctx domain.RequestContext, s domain.Store, documentID strin
}
// CanChangeDocument returns if the clinet has permission to change a given document.
func CanChangeDocument(ctx domain.RequestContext, s domain.Store, documentID string) (hasPermission bool) {
func CanChangeDocument(ctx domain.RequestContext, s domain.Store, documentID string) bool {
document, err := s.Document.Get(ctx, documentID)
if err == sql.ErrNoRows {
@ -77,7 +77,7 @@ func CanChangeDocument(ctx domain.RequestContext, s domain.Store, documentID str
return false
}
roles, err := s.Space.GetUserRoles(ctx)
roles, err := s.Space.GetUserPermissions(ctx, document.LabelID)
if err == sql.ErrNoRows {
err = nil
@ -87,7 +87,8 @@ func CanChangeDocument(ctx domain.RequestContext, s domain.Store, documentID str
}
for _, role := range roles {
if role.LabelID == document.LabelID && role.CanEdit {
if role.RefID == document.LabelID && role.Location == "space" && role.Scope == "object" &&
sp.HasPermission(role.Action, sp.DocumentEdit) {
return true
}
}
@ -95,10 +96,9 @@ func CanChangeDocument(ctx domain.RequestContext, s domain.Store, documentID str
return false
}
// CanUploadDocument returns if the client has permission to upload documents to the given folderID.
func CanUploadDocument(ctx domain.RequestContext, s domain.Store, folderID string) (hasPermission bool) {
roles, err := s.Space.GetUserRoles(ctx)
// 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
}
@ -107,7 +107,8 @@ func CanUploadDocument(ctx domain.RequestContext, s domain.Store, folderID strin
}
for _, role := range roles {
if role.LabelID == folderID && role.CanEdit {
if role.RefID == spaceID && role.Location == "space" && role.Scope == "object" &&
sp.HasPermission(role.Action, sp.DocumentAdd) {
return true
}
}

View file

@ -168,12 +168,12 @@ func (m *Mailer) PasswordReset(recipient, url string) {
}
}
// ShareFolderExistingUser provides an existing user with a link to a newly shared folder.
func (m *Mailer) ShareFolderExistingUser(recipient, inviter, url, folder, intro string) {
method := "ShareFolderExistingUser"
// ShareSpaceExistingUser provides an existing user with a link to a newly shared space.
func (m *Mailer) ShareSpaceExistingUser(recipient, inviter, url, folder, intro string) {
method := "ShareSpaceExistingUser"
m.LoadCredentials()
file, err := web.ReadFile("mail/share-folder-existing-user.html")
file, err := web.ReadFile("mail/share-space-existing-user.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
@ -218,12 +218,12 @@ func (m *Mailer) ShareFolderExistingUser(recipient, inviter, url, folder, intro
}
}
// ShareFolderNewUser invites new user providing Credentials, explaining the product and stating who is inviting them.
func (m *Mailer) ShareFolderNewUser(recipient, inviter, url, folder, invitationMessage string) {
method := "ShareFolderNewUser"
// ShareSpaceNewUser invites new user providing Credentials, explaining the product and stating who is inviting them.
func (m *Mailer) ShareSpaceNewUser(recipient, inviter, url, space, invitationMessage string) {
method := "ShareSpaceNewUser"
m.LoadCredentials()
file, err := web.ReadFile("mail/share-folder-new-user.html")
file, err := web.ReadFile("mail/share-space-new-user.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
@ -236,7 +236,7 @@ func (m *Mailer) ShareFolderNewUser(recipient, inviter, url, folder, invitationM
inviter = "Your colleague"
}
subject := fmt.Sprintf("%s has shared %s with you on Documize", inviter, folder)
subject := fmt.Sprintf("%s has shared %s with you on Documize", inviter, space)
e := NewEmail()
e.From = m.Credentials.SMTPsender
@ -254,7 +254,7 @@ func (m *Mailer) ShareFolderNewUser(recipient, inviter, url, folder, invitationM
inviter,
url,
invitationMessage,
folder,
space,
}
buffer := new(bytes.Buffer)

View file

@ -106,15 +106,16 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return
}
role := space.Role{}
role.LabelID = sp.RefID
role.OrgID = sp.OrgID
role.UserID = ctx.UserID
role.CanEdit = true
role.CanView = true
role.RefID = uniqueid.Generate()
perm := space.Permission{}
perm.OrgID = sp.OrgID
perm.Who = "user"
perm.WhoID = ctx.UserID
perm.Scope = "object"
perm.Location = "space"
perm.RefID = sp.RefID
perm.Action = "" // we send array for actions below
err = h.Store.Space.AddRole(ctx, role)
err = h.Store.Space.AddPermissions(ctx, perm, space.SpaceOwner, space.SpaceManage, space.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.GetRoles(ctx, model.CloneID)
spCloneRoles, err := h.Store.Space.GetPermissions(ctx, model.CloneID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
@ -147,10 +148,9 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
if model.CopyPermission {
for _, r := range spCloneRoles {
r.RefID = uniqueid.Generate()
r.LabelID = sp.RefID
r.RefID = sp.RefID
err = h.Store.Space.AddRole(ctx, r)
err = h.Store.Space.AddPermission(ctx, r)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -279,9 +279,9 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
method := "Get"
ctx := domain.GetRequestContext(r)
id := request.Param(r, "folderID")
id := request.Param(r, "spaceID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID")
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -349,9 +349,9 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
folderID := request.Param(r, "folderID")
if len(folderID) == 0 {
response.WriteMissingDataError(w, method, "folderID")
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -377,7 +377,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
sp.RefID = folderID
sp.RefID = spaceID
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
@ -401,7 +401,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, sp)
}
// Remove moves documents to another folder before deleting it
// Remove moves documents to another space before deleting it
func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
method := "space.Remove"
ctx := domain.GetRequestContext(r)
@ -416,9 +416,9 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
return
}
id := request.Param(r, "folderID")
id := request.Param(r, "spaceID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID")
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -436,14 +436,6 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
return
}
_, err = h.Store.Space.Delete(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
err = h.Store.Document.MoveDocumentSpace(ctx, id, move)
if err != nil {
ctx.Transaction.Rollback()
@ -452,7 +444,15 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
return
}
err = h.Store.Space.MoveSpaceRoles(ctx, id, move)
_, err = h.Store.Space.Delete(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
_, err = h.Store.Space.DeletePermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -490,9 +490,9 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
id := request.Param(r, "folderID")
id := request.Param(r, "spaceID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID")
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -512,7 +512,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
_, err = h.Store.Space.DeleteSpaceRoles(ctx, id)
_, err = h.Store.Space.DeletePermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -545,9 +545,9 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) {
return
}
id := request.Param(r, "folderID")
id := request.Param(r, "spaceID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID")
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -586,8 +586,8 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) {
}
// We compare new permisions to what we had before.
// Why? So we can send out folder invitation emails.
previousRoles, err := h.Store.Space.GetRoles(ctx, id)
// 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)
@ -599,10 +599,10 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) {
previousRoleUsers := make(map[string]bool)
for _, v := range previousRoles {
previousRoleUsers[v.UserID] = true
previousRoleUsers[v.WhoID] = true
}
// Who is sharing this folder?
// Who is sharing this space?
inviter, err := h.Store.User.Get(ctx, ctx.UserID)
if err != nil {
ctx.Transaction.Rollback()
@ -611,8 +611,8 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) {
return
}
// Nuke all previous permissions for this folder
_, err = h.Store.Space.DeleteSpaceRoles(ctx, id)
// 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)
@ -626,44 +626,40 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) {
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
for _, role := range model.Roles {
role.OrgID = ctx.OrgID
role.LabelID = id
for _, perm := range model.Permissions {
perm.OrgID = ctx.OrgID
perm.RefID = id
// Ensure the folder owner always has access!
if role.UserID == ctx.UserID {
// Ensure the space owner always has access!
if perm.WhoID == ctx.UserID {
me = true
role.CanView = true
role.CanEdit = true
}
if len(role.UserID) == 0 && (role.CanView || role.CanEdit) {
if len(perm.WhoID) == 0 {
hasEveryoneRole = true
}
// Only persist if there is a role!
if role.CanView || role.CanEdit {
roleID := uniqueid.Generate()
role.RefID = roleID
err = h.Store.Space.AddRole(ctx, role)
if perm.Action == "TBC" {
err = h.Store.Space.AddPermission(ctx, perm)
if err != nil {
h.Runtime.Log.Error("add role", err)
}
roleCount++
// We send out folder invitation emails to those users
// We send out space invitation emails to those users
// that have *just* been given permissions.
if _, isExisting := previousRoleUsers[role.UserID]; !isExisting {
if _, isExisting := previousRoleUsers[perm.WhoID]; !isExisting {
// we skip 'everyone' (user id != empty string)
if len(role.UserID) > 0 {
if len(perm.WhoID) > 0 {
var existingUser user.User
existingUser, err = h.Store.User.Get(ctx, role.UserID)
existingUser, err = h.Store.User.Get(ctx, perm.WhoID)
if err == nil {
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.ShareFolderExistingUser(existingUser.Email, inviter.Fullname(), url, sp.Name, model.Message)
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))
} else {
response.WriteServerError(w, method, err)
@ -675,16 +671,16 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) {
// Do we need to ensure permissions for space owner when shared?
if !me {
role := space.Role{}
role.LabelID = id
role.OrgID = ctx.OrgID
role.UserID = ctx.UserID
role.CanEdit = true
role.CanView = true
roleID := uniqueid.Generate()
role.RefID = roleID
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.AddRole(ctx, role)
err = h.Store.Space.AddPermission(ctx, perm)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -692,7 +688,7 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) {
}
}
// Mark up folder type as either public, private or restricted access.
// Mark up space type as either public, private or restricted access.
if hasEveryoneRole {
sp.Type = space.ScopePublic
} else {
@ -718,28 +714,52 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) {
response.WriteEmpty(w)
}
// GetPermissions returns user permissions for the requested folder.
// GetPermissions returns permissions for the requested space, for all users.
func (h *Handler) GetPermissions(w http.ResponseWriter, r *http.Request) {
method := "space.GetPermissions"
ctx := domain.GetRequestContext(r)
folderID := request.Param(r, "folderID")
if len(folderID) == 0 {
response.WriteMissingDataError(w, method, "folderID")
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
roles, err := h.Store.Space.GetRoles(ctx, folderID)
perms, err := h.Store.Space.GetPermissions(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(roles) == 0 {
roles = []space.Role{}
if len(perms) == 0 {
perms = []space.Permission{}
}
response.WriteJSON(w, roles)
response.WriteJSON(w, perms)
}
// 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{}
}
response.WriteJSON(w, perms)
}
// AcceptInvitation records the fact that a user has completed space onboard process.
@ -747,10 +767,10 @@ func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
method := "space.AcceptInvitation"
ctx := domain.GetRequestContext(r)
ctx.Subdomain = organization.GetSubdomainFromHost(r)
folderID := request.Param(r, "folderID")
if len(folderID) == 0 {
response.WriteMissingDataError(w, method, "folderID")
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -831,14 +851,14 @@ func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, u)
}
// Invite sends users folder invitation emails.
// Invite sends users space invitation emails.
func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
method := "space.Invite"
ctx := domain.GetRequestContext(r)
id := request.Param(r, "folderID")
id := request.Param(r, "spaceID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID")
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -917,6 +937,7 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
a.OrgID = ctx.OrgID
a.Admin = false
a.Editor = false
a.Users = false
a.Active = true
accountID := uniqueid.Generate()
a.RefID = accountID
@ -931,18 +952,18 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
}
// Ensure they have space roles
h.Store.Space.DeleteUserSpaceRoles(ctx, sp.RefID, u.RefID)
h.Store.Space.DeleteUserPermissions(ctx, sp.RefID, u.RefID)
role := space.Role{}
role.LabelID = sp.RefID
role.OrgID = ctx.OrgID
role.UserID = u.RefID
role.CanEdit = false
role.CanView = true
roleID := uniqueid.Generate()
role.RefID = roleID
perm := space.Permission{}
perm.OrgID = sp.OrgID
perm.Who = "user"
perm.WhoID = u.RefID
perm.Scope = "object"
perm.Location = "space"
perm.RefID = sp.RefID
perm.Action = "" // we send array for actions below
err = h.Store.Space.AddRole(ctx, role)
err = h.Store.Space.AddPermissions(ctx, perm, space.SpaceView)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -952,7 +973,7 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.ShareFolderExistingUser(email, inviter.Fullname(), url, sp.Name, model.Message)
go mailer.ShareSpaceExistingUser(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, email))
} else {
@ -973,7 +994,7 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
}
}
// We ensure that the folder is marked as restricted as a minimum!
// We ensure that the space is marked as restricted as a minimum!
if len(model.Recipients) > 0 && sp.Type == space.ScopePrivate {
sp.Type = space.ScopeRestricted

View file

@ -184,109 +184,98 @@ func (s Scope) Delete(ctx domain.RequestContext, id string) (rows int64, err err
return b.DeleteConstrained(ctx.Transaction, "label", ctx.OrgID, id)
}
// AddRole inserts the given record into the labelrole database table.
func (s Scope) AddRole(ctx domain.RequestContext, r space.Role) (err error) {
// AddPermission inserts the given record into the labelrole database table.
func (s Scope) AddPermission(ctx domain.RequestContext, r space.Permission) (err error) {
r.Created = time.Now().UTC()
r.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO labelrole (refid, labelid, orgid, userid, canview, canedit, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
stmt, err := ctx.Transaction.Preparex("INSERT INTO labelrole (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 role")
err = errors.Wrap(err, "unable to prepare insert for space permission")
return
}
_, err = stmt.Exec(r.RefID, r.LabelID, r.OrgID, r.UserID, r.CanView, r.CanEdit, r.Created, r.Revised)
_, err = stmt.Exec(r.OrgID, r.Who, r.WhoID, r.Action, r.Scope, r.Location, r.RefID, r.Created)
if err != nil {
err = errors.Wrap(err, "unable to execute insert for space role")
err = errors.Wrap(err, "unable to execute insert for space permission")
return
}
return
}
// GetRoles returns a slice of labelrole records, for the given labelID in the client's organization, grouped by user.
func (s Scope) GetRoles(ctx domain.RequestContext, labelID string) (r []space.Role, err error) {
query := `SELECT id, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? AND labelid=?` // was + "GROUP BY userid"
err = s.Runtime.Db.Select(&r, query, ctx.OrgID, labelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select for space roles %s", labelID))
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 = string(a)
s.AddPermission(ctx, r)
}
return
}
// GetUserRoles returns a slice of role records, for both the client's user and organization, and
// those space roles that exist for all users in the client's organization.
func (s Scope) GetUserRoles(ctx domain.RequestContext) (r []space.Role, err error) {
// 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, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? and userid=?
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 id, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? AND userid=''`,
ctx.OrgID, ctx.UserID, ctx.OrgID)
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 for user space roles %s", ctx.UserID))
err = errors.Wrap(err, fmt.Sprintf("unable to execute select user permissions %s", ctx.UserID))
return
}
return
}
// DeleteRole deletes the labelRoleID record from the labelrole table.
func (s Scope) DeleteRole(ctx domain.RequestContext, roleID string) (rows int64, err error) {
// 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' 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 p.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 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 labelrole WHERE orgid='%s' AND refid='%s'", ctx.OrgID, roleID)
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND location='space' AND refid='%s'",
ctx.OrgID, spaceID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteSpaceRoles deletes records from the labelrole table which have the given space ID.
func (s Scope) DeleteSpaceRoles(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
// 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 labelrole WHERE orgid='%s' AND labelid='%s'", ctx.OrgID, spaceID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteUserSpaceRoles removes all roles for the specified user, for the specified space.
func (s Scope) DeleteUserSpaceRoles(ctx domain.RequestContext, spaceID, userID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM labelrole WHERE orgid='%s' AND labelid='%s' AND userid='%s'",
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)
}
// MoveSpaceRoles changes the space ID for space role records from previousLabel to newLabel.
func (s Scope) MoveSpaceRoles(ctx domain.RequestContext, previousLabel, newLabel string) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE labelrole SET labelid=? WHERE labelid=? AND orgid=?")
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare move space roles for label %s", previousLabel))
return
}
_, err = stmt.Exec(newLabel, previousLabel, ctx.OrgID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute move space roles for label %s", previousLabel))
}
return
}

View file

@ -17,11 +17,12 @@ 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) (hasPermission bool) {
roles, err := s.Space.GetRoles(ctx, 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
}
@ -30,27 +31,8 @@ func CanViewSpace(ctx domain.RequestContext, s domain.Store, spaceID string) (ha
}
for _, role := range roles {
if role.LabelID == spaceID && (role.CanView || role.CanEdit) {
return true
}
}
return false
}
// CanViewSpaceDocuments returns if the user has permission to view a document within the specified space.
func CanViewSpaceDocuments(ctx domain.RequestContext, s domain.Store, spaceID string) (hasPermission bool) {
roles, err := s.Space.GetRoles(ctx, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.LabelID == spaceID && (role.CanView || role.CanEdit) {
if role.RefID == spaceID && role.Location == "space" && role.Scope == "object" &&
space.HasPermission(role.Action, space.SpaceView, space.SpaceManage, space.SpaceOwner) {
return true
}
}

View file

@ -24,33 +24,10 @@ import (
"github.com/documize/community/model/user"
)
// addSpace prepares and creates space record.
func addSpace(ctx domain.RequestContext, s *domain.Store, sp space.Space) (err error) {
sp.Type = space.ScopePrivate
sp.UserID = ctx.UserID
err = s.Space.Add(ctx, sp)
if err != nil {
return
}
role := space.Role{}
role.LabelID = sp.RefID
role.OrgID = sp.OrgID
role.UserID = ctx.UserID
role.CanEdit = true
role.CanView = true
role.RefID = uniqueid.Generate()
err = s.Space.AddRole(ctx, role)
return
}
// Invite new user to a folder that someone has shared with them.
// Invite new user to a space that someone has shared with them.
// We create the user account with default values and then take them
// through a welcome process designed to capture profile data.
// We add them to the organization and grant them view-only folder access.
// We add them to the organization and grant them view-only space access.
func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *domain.Store, email string, invitedBy user.User,
baseURL string, sp space.Space, invitationMessage string) (err error) {
@ -75,25 +52,25 @@ func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *d
a.OrgID = ctx.OrgID
a.Admin = false
a.Editor = false
a.Users = false
a.Active = true
accountID := uniqueid.Generate()
a.RefID = accountID
a.RefID = uniqueid.Generate()
err = s.Account.Add(ctx, a)
if err != nil {
return
}
role := space.Role{}
role.LabelID = sp.RefID
role.OrgID = ctx.OrgID
role.UserID = userID
role.CanEdit = false
role.CanView = true
roleID := uniqueid.Generate()
role.RefID = roleID
perm := space.Permission{}
perm.OrgID = sp.OrgID
perm.Who = "user"
perm.WhoID = userID
perm.Scope = "object"
perm.Location = "space"
perm.RefID = sp.RefID
perm.Action = "" // we send array for actions below
err = s.Space.AddRole(ctx, role)
err = s.Space.AddPermissions(ctx, perm, space.SpaceView)
if err != nil {
return
}
@ -101,7 +78,7 @@ func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *d
mailer := mail.Mailer{Runtime: rt, Store: s, Context: ctx}
url := fmt.Sprintf("%s/%s", baseURL, u.Salt)
go mailer.ShareFolderNewUser(u.Email, invitedBy.Fullname(), url, sp.Name, invitationMessage)
go mailer.ShareSpaceNewUser(u.Email, invitedBy.Fullname(), url, sp.Name, invitationMessage)
return
}

View file

@ -1,7 +1,6 @@
package space
import (
"database/sql"
"testing"
"github.com/documize/community/core/uniqueid"
@ -39,17 +38,19 @@ func TestSpace(t *testing.T) {
t.Error("failed to add sp space")
}
r.RefID = uniqueid.Generate()
r.LabelID = spaceID
r.OrgID = ctx.OrgID
r.UserID = "testAddSpace"
r.CanView = true
r.CanEdit = true
perm := space.Permission{}
perm.OrgID = ctx.OrgID
perm.Who = "user"
perm.WhoID = ctx.UserID
perm.Scope = "object"
perm.Location = "space"
perm.RefID = spaceID
perm.Action = "" // we send array for actions below
err = s.Space.AddRole(ctx, r)
err = s.Space.AddPermissions(ctx, perm, space.SpaceOwner, space.SpaceManage, space.SpaceView)
if err != nil {
ctx.Transaction.Rollback()
t.Error("failed to add role r")
t.Error("failed to add permission")
}
ctx.Transaction.Commit()
@ -104,17 +105,19 @@ func TestSpace(t *testing.T) {
t.Error("failed to add sp2")
}
r2.RefID = uniqueid.Generate()
r2.LabelID = spaceID2
r2.OrgID = ctx.OrgID
r2.UserID = ctx.UserID
r2.CanView = true
r2.CanEdit = true
perm := space.Permission{}
perm.OrgID = ctx.OrgID
perm.Who = "user"
perm.WhoID = ctx.UserID
perm.Scope = "object"
perm.Location = "space"
perm.RefID = spaceID2
perm.Action = "" // we send array for actions below
err = s.Space.AddRole(ctx, r2)
err = s.Space.AddPermissions(ctx, perm, space.SpaceOwner, space.SpaceManage, space.SpaceView)
if err != nil {
ctx.Transaction.Rollback()
t.Error("failed to add role")
t.Error("failed to add permission")
}
ctx.Transaction.Commit()
@ -171,22 +174,24 @@ func TestSpace(t *testing.T) {
t.Run("Add Role", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
r3.CanView = true
r3.CanEdit = true
r3.RefID = uniqueid.Generate()
r3.LabelID = spaceID
r3.OrgID = ctx.OrgID
r3.UserID = "testAddRole"
perm := space.Permission{}
perm.OrgID = ctx.OrgID
perm.Who = "user"
perm.WhoID = ctx.UserID
perm.Scope = "object"
perm.Location = "space"
perm.RefID = spaceID
perm.Action = "" // we send array for actions below
err = s.Space.AddRole(ctx, r3)
err = s.Space.AddPermissions(ctx, perm, space.DocumentAdd, space.DocumentDelete, space.DocumentMove)
if err != nil {
ctx.Transaction.Rollback()
t.Error("failed to add role")
return
t.Error("failed to add permission")
}
ctx.Transaction.Commit()
roles, err := s.Space.GetRoles(ctx, spaceID)
roles, err := s.Space.GetUserPermissions(ctx, spaceID)
if err != nil || roles == nil {
t.Error("Could not get any roles")
return
@ -194,61 +199,16 @@ func TestSpace(t *testing.T) {
// TODO: could we Verify the role was added with the if r3.UserID == Returned.UserID?
})
t.Run("Get User Roles", func(t *testing.T) {
userRoles, err := s.Space.GetUserRoles(ctx)
t.Run("Get User Permissions", func(t *testing.T) {
userRoles, err := s.Space.GetUserPermissions(ctx, spaceID)
if err != nil || userRoles == nil {
t.Error("failed to get user roles")
return
}
})
t.Run("Move space Roles", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
err := s.Space.MoveSpaceRoles(ctx, spaceID, spaceID2)
if err != nil {
ctx.Transaction.Rollback()
t.Error("failed to move space roles")
return
}
ctx.Transaction.Commit()
})
t.Run("Delete Role", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
rowsDeleted, err := s.Space.DeleteRole(ctx, r3.RefID)
if err != nil || rowsDeleted == 0 {
ctx.Transaction.Rollback()
t.Error("failed to delete roles")
return
}
ctx.Transaction.Commit()
})
t.Run("Delete space Roles", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
_, err := s.Space.DeleteSpaceRoles(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
ctx.Transaction.Rollback()
t.Error("failed to delete space roles")
return
}
ctx.Transaction.Commit()
})
t.Run("Delete user space Roles", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
_, err := s.Space.DeleteUserSpaceRoles(ctx, spaceID2, ctx.UserID)
if err != nil && err != sql.ErrNoRows {
ctx.Transaction.Rollback()
t.Error("failed to delete user space roles")
return
}
ctx.Transaction.Commit()
})
//Delete spaces last, otherwise tests may fail
t.Run("Delete Space", func(t *testing.T) {
// teardown
t.Run("Delete space", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
_, err = s.Space.Delete(ctx, spaceID)
@ -261,11 +221,7 @@ func TestSpace(t *testing.T) {
ctx.Transaction.Commit()
})
//
// teardown code goes here
//
t.Run("Delete sp2 Space", func(t *testing.T) {
t.Run("Delete space 2", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
_, err = s.Space.Delete(ctx, spaceID2)
@ -277,15 +233,4 @@ func TestSpace(t *testing.T) {
ctx.Transaction.Commit()
})
t.Run("Delete r Role", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
rowsDeleted, err := s.Space.DeleteRole(ctx, r.RefID)
if err != nil || rowsDeleted == 0 {
ctx.Transaction.Rollback()
t.Error("failed to delete role r in teardown")
return
}
ctx.Transaction.Commit()
})
}

View file

@ -56,13 +56,13 @@ type SpaceStorer interface {
ChangeOwner(ctx RequestContext, currentOwner, newOwner string) (err error)
Viewers(ctx RequestContext) (v []space.Viewer, err error)
Delete(ctx RequestContext, id string) (rows int64, err error)
AddRole(ctx RequestContext, r space.Role) (err error)
GetRoles(ctx RequestContext, labelID string) (r []space.Role, err error)
GetUserRoles(ctx RequestContext) (r []space.Role, err error)
DeleteRole(ctx RequestContext, roleID string) (rows int64, err error)
DeleteSpaceRoles(ctx RequestContext, spaceID string) (rows int64, err error)
DeleteUserSpaceRoles(ctx RequestContext, spaceID, userID string) (rows int64, err error)
MoveSpaceRoles(ctx RequestContext, previousLabel, newLabel string) (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)
DeleteUserPermissions(ctx RequestContext, spaceID, userID string) (rows int64, err error)
DeletePermissions(ctx RequestContext, spaceID string) (rows int64, err error)
}
// UserStorer defines required methods for user management

View file

@ -538,31 +538,6 @@ func (h *Handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
response.WriteEmpty(w)
}
// UserSpacePermissions returns folder permission for authenticated user.
func (h *Handler) UserSpacePermissions(w http.ResponseWriter, r *http.Request) {
method := "user.UserSpacePermissions"
ctx := domain.GetRequestContext(r)
userID := request.Param(r, "userID")
if userID != ctx.UserID {
response.WriteForbiddenError(w)
return
}
roles, err := h.Store.Space.GetUserRoles(ctx)
if err == sql.ErrNoRows {
err = nil
roles = []space.Role{}
}
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
response.WriteJSON(w, roles)
}
// ForgotPassword initiates the change password procedure.
// Generates a reset token and sends email to the user.
// User has to click link in email and then provide a new password.

View file

@ -48,8 +48,8 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, AuthMixin, {
this.addTooltip(document.getElementById("delete-documents-button"));
} else {
if (this.get('isFolderOwner')) {
this.addTooltip(document.getElementById("folder-share-button"));
this.addTooltip(document.getElementById("folder-settings-button"));
this.addTooltip(document.getElementById("space-delete-button"));
this.addTooltip(document.getElementById("space-settings-button"));
}
}
},

View file

@ -23,7 +23,7 @@ export default Ember.Component.extend(NotifierMixin, {
inviteMessage: '',
getDefaultInvitationMessage() {
return "Hey there, I am sharing the " + this.folder.get('name') + " space (in " + this.get("appMeta.title") + ") with you so we can both access the same documents.";
return "Hey there, I am sharing the " + this.folder.get('name') + " space (in " + this.get("appMeta.title") + ") with you so we can both collaborate on documents.";
},
willRender() {
@ -67,7 +67,8 @@ export default Ember.Component.extend(NotifierMixin, {
this.set('inviteEmail', '');
this.get('folderService').share(this.folder.get('id'), result).then(() => {
this.showNotification('Shared');
this.showNotification('Invietd co-workers');
$('#inviteEmail').removeClass('error');
});
}
}

View file

@ -32,13 +32,22 @@ export default Ember.Component.extend(NotifierMixin, {
let isActive = user.get('active');
let u = {
userId: user.get('id'),
fullname: user.get('fullname'),
orgId: this.get('folder.orgId'),
folderId: this.get('folder.id'),
canEdit: false,
canView: false,
canViewPrevious: false
who: 'user',
whoId: user.get('id'),
location: 'space',
scope: 'object',
refId: this.get('folder.id'),
spaceView: false,
spaceManage: false,
spaceOwner: false,
docAdd: false,
docEdit: false,
docDelete: false,
docMove: false,
docCopy: false,
docTemplate: false,
};
if (isActive) {
@ -47,13 +56,23 @@ export default Ember.Component.extend(NotifierMixin, {
});
var u = {
userId: "",
fullname: " Everyone",
orgId: this.get('folder.orgId'),
folderId: this.get('folder.id'),
canEdit: false,
canView: false
};
who: 'user',
whoId: '',
location: 'space',
scope: 'object',
refId: this.get('folder.id'),
spaceView: false,
spaceManage: false,
spaceOwner: false,
docAdd: false,
docEdit: false,
docDelete: false,
docMove: false,
docCopy: false,
docTemplate: false,
};
folderPermissions.pushObject(u);
@ -111,6 +130,7 @@ export default Ember.Component.extend(NotifierMixin, {
var payload = { Message: message, Roles: data };
this.get('folderService').savePermissions(folder.get('id'), payload).then(() => {
this.showNotification('Saved permissions');
});
var hasEveryone = _.find(data, function (permission) {
@ -128,7 +148,6 @@ export default Ember.Component.extend(NotifierMixin, {
}
this.get('folderService').save(folder).then(function () {
// window.location.href = "/folder/" + folder.get('id') + "/" + folder.get('slug');
});
}
}

View file

@ -0,0 +1,24 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
// import { belongsTo, hasMany } from 'ember-data/relationships';
export default Model.extend({
orgId: attr('string'),
who: attr('string'),
whoId: attr('string'),
action: attr('string'),
scope: attr('string'),
location: attr('string'),
refId: attr('string')
});

View file

@ -10,78 +10,6 @@
// https://documize.com
import Ember from 'ember';
import NotifierMixin from '../../mixins/notifier';
export default Ember.Controller.extend(NotifierMixin, {
documentService: Ember.inject.service('document'),
folderService: Ember.inject.service('folder'),
localStorage: Ember.inject.service('localStorage'),
selectedDocuments: [],
hasSelectedDocuments: Ember.computed.gt('selectedDocuments.length', 0),
queryParams: ['tab'],
tab: 'index',
actions: {
onMoveDocument(folder) {
let self = this;
let documents = this.get('selectedDocuments');
documents.forEach(function (documentId) {
self.get('documentService').getDocument(documentId).then(function (doc) {
doc.set('folderId', folder);
doc.set('selected', !doc.get('selected'));
self.get('documentService').save(doc).then(function () {
self.get('target._routerMicrolib').refresh();
});
});
});
this.set('selectedDocuments', []);
this.send("showNotification", "Moved");
},
onDeleteDocument() {
let documents = this.get('selectedDocuments');
let self = this;
let promises = [];
documents.forEach(function (document, index) {
promises[index] = self.get('documentService').deleteDocument(document);
});
Ember.RSVP.all(promises).then(() => {
let documents = this.get('model.documents');
documents.forEach(function (document) {
document.set('selected', false);
});
this.set('model.documents', documents);
this.set('selectedDocuments', []);
this.send("showNotification", "Deleted");
this.get('target._routerMicrolib').refresh();
});
},
onAddSpace(payload) {
let self = this;
this.showNotification("Added");
this.get('folderService').add(payload).then(function (newFolder) {
self.get('folderService').setCurrentFolder(newFolder);
self.transitionToRoute('folder', newFolder.get('id'), newFolder.get('slug'));
});
},
onDeleteSpace() {
this.get('folderService').delete(this.get('model.folder.id')).then(() => { /* jshint ignore:line */
this.showNotification("Deleted");
this.get('localStorage').clearSessionItem('folder');
this.transitionToRoute('application');
});
},
onImport() {
this.get('target._routerMicrolib').refresh();
}
}
export default Ember.Controller.extend({
});

View file

@ -0,0 +1,87 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
import NotifierMixin from '../../../mixins/notifier';
export default Ember.Controller.extend(NotifierMixin, {
documentService: Ember.inject.service('document'),
folderService: Ember.inject.service('folder'),
localStorage: Ember.inject.service('localStorage'),
selectedDocuments: [],
hasSelectedDocuments: Ember.computed.gt('selectedDocuments.length', 0),
queryParams: ['tab'],
tab: 'index',
actions: {
onMoveDocument(folder) {
let self = this;
let documents = this.get('selectedDocuments');
documents.forEach(function (documentId) {
self.get('documentService').getDocument(documentId).then(function (doc) {
doc.set('folderId', folder);
doc.set('selected', !doc.get('selected'));
self.get('documentService').save(doc).then(function () {
self.get('target._routerMicrolib').refresh();
});
});
});
this.set('selectedDocuments', []);
this.send("showNotification", "Moved");
},
onDeleteDocument() {
let documents = this.get('selectedDocuments');
let self = this;
let promises = [];
documents.forEach(function (document, index) {
promises[index] = self.get('documentService').deleteDocument(document);
});
Ember.RSVP.all(promises).then(() => {
let documents = this.get('model.documents');
documents.forEach(function (document) {
document.set('selected', false);
});
this.set('model.documents', documents);
this.set('selectedDocuments', []);
this.send("showNotification", "Deleted");
this.get('target._routerMicrolib').refresh();
});
},
onAddSpace(payload) {
let self = this;
this.showNotification("Added");
this.get('folderService').add(payload).then(function (newFolder) {
self.get('folderService').setCurrentFolder(newFolder);
self.transitionToRoute('folder', newFolder.get('id'), newFolder.get('slug'));
});
},
onDeleteSpace() {
this.get('folderService').delete(this.get('model.folder.id')).then(() => { /* jshint ignore:line */
this.showNotification("Deleted");
this.get('localStorage').clearSessionItem('folder');
this.transitionToRoute('application');
});
},
onImport() {
this.get('target._routerMicrolib').refresh();
}
}
});

View file

@ -0,0 +1,28 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model() {
this.get('browser').setTitle(this.modelFor('folder').folder.get('name'));
return Ember.RSVP.hash({
folder: this.modelFor('folder').folder,
isEditor: this.modelFor('folder').isEditor,
isFolderOwner: this.modelFor('folder').isFolderOwner,
folders: this.modelFor('folder').folders,
documents: this.modelFor('folder').documents,
templates: this.modelFor('folder').templates
});
}
});

View file

@ -0,0 +1,14 @@
{{#layout/zone-container}}
{{#layout/zone-sidebar}}
{{folder/sidebar-zone folders=model.folders folder=model.folder isFolderOwner=model.isFolderOwner isEditor=model.isEditor tab=tab
onAddSpace=(action 'onAddSpace')}}
{{/layout/zone-sidebar}}
{{#layout/zone-content}}
{{folder/folder-heading folder=model.folder isFolderOwner=model.isFolderOwner isEditor=model.isEditor}}
{{folder/folder-toolbar folders=model.folders isFolderOwner=model.isFolderOwner folder=model.folder hasSelectedDocuments=hasSelectedDocuments
onDeleteDocument=(action 'onDeleteDocument') onMoveDocument=(action 'onMoveDocument')}}
{{folder/documents-list documents=model.documents folders=model.folders folder=model.folder templates=model.templates
isFolderOwner=model.isFolderOwner isEditor=model.isEditor selectedDocuments=(mut selectedDocuments)
onDeleteSpace=(action 'onDeleteSpace') onImport=(action 'onImport')}}
{{/layout/zone-content}}
{{/layout/zone-container}}

View file

@ -47,11 +47,6 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
});
},
setupController(controller, model) {
controller.set('model', model);
this.browser.setTitle(model.folder.get('name'));
},
actions: {
error(error /*, transition*/ ) {
console.log(error); // eslint-disable-line no-console

View file

@ -0,0 +1,20 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
import NotifierMixin from '../../../mixins/notifier';
import AuthProvider from '../../../mixins/auth';
export default Ember.Controller.extend(AuthProvider, NotifierMixin, {
documentService: Ember.inject.service('document'),
folderService: Ember.inject.service('folder'),
localStorage: Ember.inject.service('localStorage'),
});

View file

@ -0,0 +1,26 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model() {
this.get('browser').setTitle(this.modelFor('folder').folder.get('name'));
return Ember.RSVP.hash({
folder: this.modelFor('folder').folder,
isEditor: this.modelFor('folder').isEditor,
isFolderOwner: this.modelFor('folder').isFolderOwner,
folders: this.modelFor('folder').folders
});
}
});

View file

@ -0,0 +1,36 @@
{{#layout/zone-container}}
{{#layout/zone-sidebar}}
<div class="sidebar-toolbar"></div>
<div class="sidebar-common">
{{layout/sidebar-intro title="Space Settings" message="Invite users and configure space permissions. Set up categories to sub-divide the space."}}
</div>
<div class="sidebar-wrapper">
<div class="sidebar-menu">
<ul class="options">
<div class="option selected">Users</div>
<div class="option ">Categories</div>
</ul>
</div>
</div>
{{/layout/zone-sidebar}}
{{#layout/zone-content}}
<div class="folder-heading">
<h1 class="folder-title">{{model.folder.name}}</h1>
</div>
{{#link-to 'folder' model.folder.id model.folder.slug class="vertical-top"}}
<i class="material-icons">arrow_back</i>&nbsp;back to space
{{/link-to}}
<div class="margin-top-30" />
{{#if isAuthProviderDocumize}}
{{folder/invite-user folders=model.folders folder=model.folder}}
<div class="margin-top-50" />
{{/if}}
{{folder/permission-admin folders=model.folders folder=model.folder}}
<div class="margin-top-50" />
{{/layout/zone-content}}
{{/layout/zone-container}}

View file

@ -1,15 +1,2 @@
{{layout/zone-navigation}}
{{#layout/zone-container}}
{{#layout/zone-sidebar}}
{{folder/sidebar-zone folders=model.folders folder=model.folder isFolderOwner=model.isFolderOwner isEditor=model.isEditor tab=tab
onAddSpace=(action 'onAddSpace')}}
{{/layout/zone-sidebar}}
{{#layout/zone-content}}
{{folder/folder-heading folder=model.folder isFolderOwner=model.isFolderOwner isEditor=model.isEditor}}
{{folder/folder-toolbar folders=model.folders folder=model.folder hasSelectedDocuments=hasSelectedDocuments
onDeleteDocument=(action 'onDeleteDocument') onMoveDocument=(action 'onMoveDocument')}}
{{folder/documents-list documents=model.documents folders=model.folders folder=model.folder templates=model.templates
isFolderOwner=model.isFolderOwner isEditor=model.isEditor selectedDocuments=(mut selectedDocuments)
onDeleteSpace=(action 'onDeleteSpace') onImport=(action 'onImport')}}
{{/layout/zone-content}}
{{/layout/zone-container}}
{{outlet}}

View file

@ -23,6 +23,10 @@ export default Router.map(function () {
this.route('folder', {
path: 's/:folder_id/:folder_slug'
}, function() {
this.route('settings', {
path: 'settings'
});
});
this.route('document', {

View file

@ -0,0 +1,13 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
normalize(modelClass, resourceHash) {
return {
data: {
id: resourceHash.userId ? resourceHash.userId : 0,
type: modelClass.modelName,
attributes: resourceHash
}
};
}
});

View file

@ -13,7 +13,6 @@ import Ember from 'ember';
import BaseService from '../services/base';
const {
get,
RSVP,
inject: { service }
} = Ember;
@ -30,7 +29,7 @@ export default BaseService.extend({
// Add a new folder.
add(payload) {
return this.get('ajax').post(`folders`, {
return this.get('ajax').post(`space`, {
contentType: 'json',
data: JSON.stringify(payload)
}).then((folder) => {
@ -41,7 +40,7 @@ export default BaseService.extend({
// Returns folder model for specified folder id.
getFolder(id) {
return this.get('ajax').request(`folders/${id}`, {
return this.get('ajax').request(`space/${id}`, {
method: 'GET'
}).then((folder) => {
let data = this.get('store').normalize('folder', folder);
@ -54,7 +53,7 @@ export default BaseService.extend({
// Returns all folders that user can see.
getAll() {
let folders = this.get('folders');
let folders = this.get('space');
if (folders != null) {
return new RSVP.resolve(folders);
@ -67,7 +66,7 @@ export default BaseService.extend({
save(folder) {
let id = folder.get('id');
return this.get('ajax').request(`folders/${id}`, {
return this.get('ajax').request(`space/${id}`, {
method: 'PUT',
contentType: 'json',
data: JSON.stringify(folder)
@ -75,7 +74,7 @@ export default BaseService.extend({
},
remove(folderId, moveToId) {
let url = `folders/${folderId}/move/${moveToId}`;
let url = `space/${folderId}/move/${moveToId}`;
return this.get('ajax').request(url, {
method: 'DELETE'
@ -83,7 +82,7 @@ export default BaseService.extend({
},
delete(folderId) {
return this.get('ajax').request(`folders/${folderId}`, {
return this.get('ajax').request(`space/${folderId}`, {
method: 'DELETE'
});
},
@ -99,7 +98,7 @@ export default BaseService.extend({
// getProtectedFolderInfo returns non-private folders and who has access to them.
getProtectedFolderInfo() {
return this.get('ajax').request(`folders?filter=viewers`, {
return this.get('ajax').request(`space?filter=viewers`, {
method: "GET"
}).then((response) => {
let data = [];
@ -116,7 +115,7 @@ export default BaseService.extend({
// reloads and caches folders.
reload() {
return this.get('ajax').request(`folders`, {
return this.get('ajax').request(`space`, {
method: "GET"
}).then((response) => {
let data = [];
@ -132,14 +131,13 @@ export default BaseService.extend({
// so who can see/edit this folder?
getPermissions(folderId) {
return this.get('ajax').request(`folders/${folderId}/permissions`, {
return this.get('ajax').request(`space/${folderId}/permissions`, {
method: "GET"
}).then((response) => {
let data = [];
data = response.map((obj) => {
let data = this.get('store').normalize('folder-permission', obj);
let data = this.get('store').normalize('user-permission', obj);
return this.get('store').push(data);
});
@ -149,7 +147,7 @@ export default BaseService.extend({
// persist folder permissions
savePermissions(folderId, payload) {
return this.get('ajax').request(`folders/${folderId}/permissions`, {
return this.get('ajax').request(`space/${folderId}/permissions`, {
method: 'PUT',
contentType: 'json',
data: JSON.stringify(payload)
@ -158,7 +156,6 @@ export default BaseService.extend({
// share this folder with new users!
share(folderId, invitation) {
return this.get('ajax').post(`folders/${folderId}/invitation`, {
contentType: 'json',
data: JSON.stringify(invitation)
@ -171,8 +168,9 @@ export default BaseService.extend({
return;
}
let folderId = folder.get('id');
this.set('currentFolder', folder);
this.get('localStorage').storeSessionItem("folder", get(folder, 'id'));
this.get('localStorage').storeSessionItem("folder", folderId);
this.set('canEditCurrentFolder', false);
let userId = this.get('sessionService.user.id');
@ -180,7 +178,7 @@ export default BaseService.extend({
userId = "0";
}
let url = `users/${userId}/permissions`;
let url = `space/${folderId}/permissions/user`;
return this.get('ajax').request(url).then((folderPermissions) => {
// safety check
@ -191,7 +189,6 @@ export default BaseService.extend({
}
let result = [];
let folderId = folder.get('id');
folderPermissions.forEach((item) => {
if (item.folderId === folderId) {

View file

@ -1,4 +1,4 @@
@import "document.scss";
@import "folder.scss";
@import "wizard.scss";
@import "sidebar.scss";
@import "settings.scss";

View file

@ -0,0 +1,42 @@
.space-settings {
> .panel {
@include content-container();
@include ease-in();
@extend .transition-all;
}
.permissions-table {
padding: 0;
margin: 0 0 30px 0;
width: 100%;
> .row {
padding: 8px 0;
> .permission-name-cell {
padding: 10px 5px;
font-size: 1.1rem;
}
> .permission-roles-cell {
background-color: $color-off-white;
margin-top: 10px;
margin-left: 40px;
padding: 10px 10px;
display: inline-block;
> .role-category {
color: $color-gray;
font-weight: bold;
display: inline-block;
}
> label {
color: $color-gray;
font-weight: normal;
}
}
}
}
}

View file

@ -1,57 +0,0 @@
.sidebar-folder-share {
> .input-control {
margin-bottom: 30px;
}
}
.sidebar-permissions {
> .input-control {
margin-bottom: 30px;
}
> .permissions-table {
border: none;
padding: 0;
margin: 0 0 30px 0;
table-layout: fixed;
width: 100%;
white-space: nowrap;
> thead {
> tr {
> th {
font-weight: bold;
text-align: center;
}
> th:nth-child(1) {
width: 70%;
}
> th:nth-child(2), td:nth-child(3) {
width: 20%;
}
}
}
> tbody {
width: 300px;
> tr {
> td {
padding: 8px 0;
@extend .truncate;
}
> td:nth-child(1) {
text-align: left;
}
> td:nth-child(2), td:nth-child(3) {
text-align: center;
}
}
}
}
}

View file

@ -28,7 +28,19 @@
</ul>
{{/dropdown-dialog}}
{{else}}
<div class="margin-top-35"></div>
{{#if isFolderOwner}}
{{#link-to 'folder.settings' folder.id folder.slug}}{{model.document.name}}
<div class="round-button button-gray" id="space-settings-button" data-tooltip="Manage permissions" data-tooltip-position="top center">
<i class="material-icons">settings</i>
</div>
{{/link-to}}
<div class="button-gap"></div>
<div class="round-button button-gray" id="space-delete-button" data-tooltip="Delete everything" data-tooltip-position="top center">
<i class="material-icons">delete</i>
</div>
{{else}}
<div class="margin-top-35"></div>
{{/if}}
{{/if}}
</div>
{{/if}}

View file

@ -1,6 +1,9 @@
<div class="sidebar-panel">
<div class="title">Invite Users</div>
<div class="sidebar-folder-share folder-sidebar-form-wrapper">
<div class="space-settings">
<div class="panel">
<div class="form-header">
<div class="title">Invite</div>
<div class="tip">Share this space with co-workers</div>
</div>
<div class="input-control">
<label>Email</label>
<div class="tip">Comma separate multiple email addresses</div>
@ -8,9 +11,9 @@
</div>
<div class="input-control">
<label>Message</label>
<div class="tip">Explain why they are being invited</div>
<div class="tip">Explain why they are being invited to this space</div>
{{textarea id="explainInvite" value=inviteMessage class="input-transparent" rows="5"}}
</div>
<div class="regular-button button-blue" {{ action 'onShare' }}>Share</div>
<div class="regular-button button-blue" {{ action 'onShare' }}>Invite</div>
</div>
</div>

View file

@ -0,0 +1,42 @@
<div class="space-settings">
<div class="panel">
<div class="form-header">
<div class="title">Permissions</div>
<div class="tip">Define who can do what in this space</div>
</div>
<div class="input-control">
<div class="permissions-table">
{{#each permissions key="@index" as |permission|}}
<div class="row">
<div class="permission-name-cell">{{permission.fullname}}</div>
<div class="permission-roles-cell">
<span class="role-category">Space:&nbsp;</span>
<input type="checkbox" id="space-role-view-{{permission.userId}}" checked={{permission.canView}} />
<label for="space-role-view-{{permission.userId}}">view</label>&nbsp;&nbsp;
<input type="checkbox" id="space-role-manage-{{permission.userId}}" checked={{permission.canView}} />
<label for="space-role-manage-{{permission.userId}}">manage</label>&nbsp;&nbsp;
<input type="checkbox" id="space-role-owner-{{permission.userId}}" checked={{permission.canView}} />
<label for="space-role-owner-{{permission.userId}}">owner</label>&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;
<span class="role-category">Document:&nbsp;</span>
<input type="checkbox" id="doc-role-add-{{permission.userId}}" checked={{permission.canView}} />
<label for="doc-role-add-{{permission.userId}}">create</label>&nbsp;&nbsp;
<input type="checkbox" id="doc-role-edit-{{permission.userId}}" checked={{permission.canView}} />
<label for="doc-role-edit-{{permission.userId}}">edit</label>&nbsp;&nbsp;
<input type="checkbox" id="doc-role-delete-{{permission.userId}}" checked={{permission.canView}} />
<label for="doc-role-delete-{{permission.userId}}">delete</label>&nbsp;&nbsp;
<input type="checkbox" id="doc-role-move-{{permission.userId}}" checked={{permission.canView}} />
<label for="doc-role-move-{{permission.userId}}">move</label>&nbsp;&nbsp;
<input type="checkbox" id="doc-role-copy-{{permission.userId}}" checked={{permission.canView}} />
<label for="doc-role-copy-{{permission.userId}}">copy</label>&nbsp;&nbsp;
<input type="checkbox" id="doc-role-template-{{permission.userId}}" checked={{permission.canView}} />
<label for="doc-role-template-{{permission.userId}}">templates</label>&nbsp;&nbsp;
</div>
</div>
{{/each}}
</div>
</div>
<div class="regular-button button-blue" {{action 'setPermissions'}}>GRANT</div>
</div>
</div>

View file

@ -1,30 +0,0 @@
<div class="sidebar-panel">
<div class="title">Space Permissions</div>
<div class="sidebar-permissions folder-sidebar-form-wrapper">
<table class="permissions-table">
<thead>
<tr>
<th>&nbsp;</th>
<th>View&nbsp;</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
{{#each permissions key="@index" as |permission|}}
<tr>
<td>{{permission.fullname}}</td>
<td>
<input type="checkbox" id="canView-{{permission.userId}}" checked={{permission.canView}} />
<label for="canView-{{permission.userId}}">&nbsp;</label>
</td>
<td>
<input type="checkbox" id="canEdit-{{permission.userId}}" checked={{permission.canEdit}} />
<label for="canEdit-{{permission.userId}}">&nbsp;</label>
</td>
</tr>
{{/each}}
</tbody>
</table>
<div class="regular-button button-blue" {{action 'setPermissions'}}>Set</div>
</div>
</div>

View file

@ -11,7 +11,11 @@
package space
import "github.com/documize/community/model"
import (
"time"
"github.com/documize/community/model"
)
// Space defines a container for documents.
type Space struct {
@ -51,6 +55,44 @@ 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 string `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 = "owner"
// 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"
)
// Role determines user permissions for a folder.
type Role struct {
model.BaseEntityObfuscated
@ -74,8 +116,8 @@ type Viewer struct {
// RolesModel details which users have what permissions on a given space.
type RolesModel struct {
Message string
Roles []Role
Message string
Permissions []Permission
}
// AcceptShareModel is used to setup a user who has accepted a shared space.
@ -100,3 +142,14 @@ 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 string, actions ...PermissionAction) bool {
for _, a := range actions {
if action == string(a) {
return true
}
}
return false
}

View file

@ -111,19 +111,19 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
Add(rt, RoutePrefixPrivate, "organizations/{orgID}", []string{"GET", "OPTIONS"}, nil, organization.Get)
Add(rt, RoutePrefixPrivate, "organizations/{orgID}", []string{"PUT", "OPTIONS"}, nil, organization.Update)
Add(rt, RoutePrefixPrivate, "folders/{folderID}", []string{"DELETE", "OPTIONS"}, nil, space.Delete)
Add(rt, RoutePrefixPrivate, "folders/{folderID}/move/{moveToId}", []string{"DELETE", "OPTIONS"}, nil, space.Remove)
Add(rt, RoutePrefixPrivate, "folders/{folderID}/permissions", []string{"PUT", "OPTIONS"}, nil, space.SetPermissions)
Add(rt, RoutePrefixPrivate, "folders/{folderID}/permissions", []string{"GET", "OPTIONS"}, nil, space.GetPermissions)
Add(rt, RoutePrefixPrivate, "folders/{folderID}/invitation", []string{"POST", "OPTIONS"}, nil, space.Invite)
Add(rt, RoutePrefixPrivate, "folders", []string{"GET", "OPTIONS"}, []string{"filter", "viewers"}, space.GetSpaceViewers)
Add(rt, RoutePrefixPrivate, "folders", []string{"POST", "OPTIONS"}, nil, space.Add)
Add(rt, RoutePrefixPrivate, "folders", []string{"GET", "OPTIONS"}, nil, space.GetAll)
Add(rt, RoutePrefixPrivate, "folders/{folderID}", []string{"GET", "OPTIONS"}, nil, space.Get)
Add(rt, RoutePrefixPrivate, "folders/{folderID}", []string{"PUT", "OPTIONS"}, nil, space.Update)
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}/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)
Add(rt, RoutePrefixPrivate, "space", []string{"GET", "OPTIONS"}, nil, space.GetAll)
Add(rt, RoutePrefixPrivate, "space/{spaceID}", []string{"GET", "OPTIONS"}, nil, space.Get)
Add(rt, RoutePrefixPrivate, "space/{spaceID}", []string{"PUT", "OPTIONS"}, nil, space.Update)
Add(rt, RoutePrefixPrivate, "users/{userID}/password", []string{"POST", "OPTIONS"}, nil, user.ChangePassword)
Add(rt, RoutePrefixPrivate, "users/{userID}/permissions", []string{"GET", "OPTIONS"}, nil, user.UserSpacePermissions)
Add(rt, RoutePrefixPrivate, "users", []string{"POST", "OPTIONS"}, nil, user.Add)
Add(rt, RoutePrefixPrivate, "users/folder/{folderID}", []string{"GET", "OPTIONS"}, nil, user.GetSpaceUsers)
Add(rt, RoutePrefixPrivate, "users", []string{"GET", "OPTIONS"}, nil, user.GetOrganizationUsers)