From ae05cacf3f33cd36b5473c7e1f0e70135eb9c55b Mon Sep 17 00:00:00 2001 From: Harvey Kandola Date: Wed, 13 Sep 2017 19:22:38 +0100 Subject: [PATCH] re-working space permissions -- WIP --- domain/account/mysql/store.go | 2 +- domain/document/permission.go | 37 +-- domain/mail/mailer.go | 20 +- ...er.html => share-space-existing-user.html} | 0 ...ew-user.html => share-space-new-user.html} | 0 domain/space/endpoint.go | 211 ++++++++++-------- domain/space/mysql/store.go | 111 +++++---- domain/space/permission.go | 28 +-- domain/space/space.go | 51 ++--- domain/space/space_test.go | 129 +++-------- domain/storer.go | 14 +- domain/user/endpoint.go | 25 --- gui/app/components/folder/folder-toolbar.js | 4 +- .../{sidebar-share.js => invite-user.js} | 5 +- ...bar-permissions.js => permission-admin.js} | 41 +++- gui/app/models/user-permission.js | 24 ++ gui/app/pods/folder/controller.js | 74 +----- gui/app/pods/folder/index/controller.js | 87 ++++++++ gui/app/pods/folder/index/route.js | 28 +++ gui/app/pods/folder/index/template.hbs | 14 ++ gui/app/pods/folder/route.js | 5 - gui/app/pods/folder/settings/controller.js | 20 ++ gui/app/pods/folder/settings/route.js | 26 +++ gui/app/pods/folder/settings/template.hbs | 36 +++ gui/app/pods/folder/template.hbs | 15 +- gui/app/router.js | 4 + gui/app/serializers/user-permission.js | 13 ++ gui/app/services/folder.js | 31 ++- gui/app/styles/view/folder/all.scss | 2 +- gui/app/styles/view/folder/settings.scss | 42 ++++ gui/app/styles/view/folder/sidebar.scss | 57 ----- .../components/folder/folder-toolbar.hbs | 14 +- .../{sidebar-share.hbs => invite-user.hbs} | 13 +- .../components/folder/permission-admin.hbs | 42 ++++ .../components/folder/sidebar-permissions.hbs | 30 --- model/space/space.go | 59 ++++- server/routing/routes.go | 22 +- 37 files changed, 735 insertions(+), 601 deletions(-) rename domain/mail/{share-folder-existing-user.html => share-space-existing-user.html} (100%) rename domain/mail/{share-folder-new-user.html => share-space-new-user.html} (100%) rename gui/app/components/folder/{sidebar-share.js => invite-user.js} (93%) rename gui/app/components/folder/{sidebar-permissions.js => permission-admin.js} (83%) create mode 100644 gui/app/models/user-permission.js create mode 100644 gui/app/pods/folder/index/controller.js create mode 100644 gui/app/pods/folder/index/route.js create mode 100644 gui/app/pods/folder/index/template.hbs create mode 100644 gui/app/pods/folder/settings/controller.js create mode 100644 gui/app/pods/folder/settings/route.js create mode 100644 gui/app/pods/folder/settings/template.hbs create mode 100644 gui/app/serializers/user-permission.js create mode 100644 gui/app/styles/view/folder/settings.scss delete mode 100644 gui/app/styles/view/folder/sidebar.scss rename gui/app/templates/components/folder/{sidebar-share.hbs => invite-user.hbs} (62%) create mode 100644 gui/app/templates/components/folder/permission-admin.hbs delete mode 100644 gui/app/templates/components/folder/sidebar-permissions.hbs diff --git a/domain/account/mysql/store.go b/domain/account/mysql/store.go index 355a7583..e0189a23 100644 --- a/domain/account/mysql/store.go +++ b/domain/account/mysql/store.go @@ -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 { diff --git a/domain/document/permission.go b/domain/document/permission.go index 5d8619bf..d39c5f9e 100644 --- a/domain/document/permission.go +++ b/domain/document/permission.go @@ -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 } } diff --git a/domain/mail/mailer.go b/domain/mail/mailer.go index 324e1295..3377d6dc 100644 --- a/domain/mail/mailer.go +++ b/domain/mail/mailer.go @@ -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) diff --git a/domain/mail/share-folder-existing-user.html b/domain/mail/share-space-existing-user.html similarity index 100% rename from domain/mail/share-folder-existing-user.html rename to domain/mail/share-space-existing-user.html diff --git a/domain/mail/share-folder-new-user.html b/domain/mail/share-space-new-user.html similarity index 100% rename from domain/mail/share-folder-new-user.html rename to domain/mail/share-space-new-user.html diff --git a/domain/space/endpoint.go b/domain/space/endpoint.go index 3deff3ae..cf0ca02e 100644 --- a/domain/space/endpoint.go +++ b/domain/space/endpoint.go @@ -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 diff --git a/domain/space/mysql/store.go b/domain/space/mysql/store.go index eb38bae9..e14f9384 100644 --- a/domain/space/mysql/store.go +++ b/domain/space/mysql/store.go @@ -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 -} diff --git a/domain/space/permission.go b/domain/space/permission.go index f71d139d..358b1f94 100644 --- a/domain/space/permission.go +++ b/domain/space/permission.go @@ -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 } } diff --git a/domain/space/space.go b/domain/space/space.go index 99862286..a48ab4ee 100644 --- a/domain/space/space.go +++ b/domain/space/space.go @@ -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 } diff --git a/domain/space/space_test.go b/domain/space/space_test.go index 05031fd3..5f5a2cb2 100644 --- a/domain/space/space_test.go +++ b/domain/space/space_test.go @@ -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() - }) } diff --git a/domain/storer.go b/domain/storer.go index 7a1f0998..4c30e1e9 100644 --- a/domain/storer.go +++ b/domain/storer.go @@ -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 diff --git a/domain/user/endpoint.go b/domain/user/endpoint.go index aad0faf3..05e97269 100644 --- a/domain/user/endpoint.go +++ b/domain/user/endpoint.go @@ -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. diff --git a/gui/app/components/folder/folder-toolbar.js b/gui/app/components/folder/folder-toolbar.js index fc64a8b2..79681996 100644 --- a/gui/app/components/folder/folder-toolbar.js +++ b/gui/app/components/folder/folder-toolbar.js @@ -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")); } } }, diff --git a/gui/app/components/folder/sidebar-share.js b/gui/app/components/folder/invite-user.js similarity index 93% rename from gui/app/components/folder/sidebar-share.js rename to gui/app/components/folder/invite-user.js index 2c8b69c3..7a2b0469 100644 --- a/gui/app/components/folder/sidebar-share.js +++ b/gui/app/components/folder/invite-user.js @@ -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'); }); } } diff --git a/gui/app/components/folder/sidebar-permissions.js b/gui/app/components/folder/permission-admin.js similarity index 83% rename from gui/app/components/folder/sidebar-permissions.js rename to gui/app/components/folder/permission-admin.js index a8e64403..f9df03cd 100644 --- a/gui/app/components/folder/sidebar-permissions.js +++ b/gui/app/components/folder/permission-admin.js @@ -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'); }); } } diff --git a/gui/app/models/user-permission.js b/gui/app/models/user-permission.js new file mode 100644 index 00000000..9ca130bb --- /dev/null +++ b/gui/app/models/user-permission.js @@ -0,0 +1,24 @@ +// Copyright 2016 Documize Inc. . 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 . +// +// 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') +}); diff --git a/gui/app/pods/folder/controller.js b/gui/app/pods/folder/controller.js index 84788b7d..bef4c71c 100644 --- a/gui/app/pods/folder/controller.js +++ b/gui/app/pods/folder/controller.js @@ -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({ }); diff --git a/gui/app/pods/folder/index/controller.js b/gui/app/pods/folder/index/controller.js new file mode 100644 index 00000000..f0c8f376 --- /dev/null +++ b/gui/app/pods/folder/index/controller.js @@ -0,0 +1,87 @@ +// Copyright 2016 Documize Inc. . 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 . +// +// 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(); + } + } +}); diff --git a/gui/app/pods/folder/index/route.js b/gui/app/pods/folder/index/route.js new file mode 100644 index 00000000..59966be1 --- /dev/null +++ b/gui/app/pods/folder/index/route.js @@ -0,0 +1,28 @@ +// Copyright 2016 Documize Inc. . 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 . +// +// 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 + }); + } +}); diff --git a/gui/app/pods/folder/index/template.hbs b/gui/app/pods/folder/index/template.hbs new file mode 100644 index 00000000..1353e57c --- /dev/null +++ b/gui/app/pods/folder/index/template.hbs @@ -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}} \ No newline at end of file diff --git a/gui/app/pods/folder/route.js b/gui/app/pods/folder/route.js index 6d93dd62..3fc2b9be 100644 --- a/gui/app/pods/folder/route.js +++ b/gui/app/pods/folder/route.js @@ -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 diff --git a/gui/app/pods/folder/settings/controller.js b/gui/app/pods/folder/settings/controller.js new file mode 100644 index 00000000..7e1552f1 --- /dev/null +++ b/gui/app/pods/folder/settings/controller.js @@ -0,0 +1,20 @@ +// Copyright 2016 Documize Inc. . 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 . +// +// 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'), +}); diff --git a/gui/app/pods/folder/settings/route.js b/gui/app/pods/folder/settings/route.js new file mode 100644 index 00000000..6019e69c --- /dev/null +++ b/gui/app/pods/folder/settings/route.js @@ -0,0 +1,26 @@ +// Copyright 2016 Documize Inc. . 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 . +// +// 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 + }); + } +}); diff --git a/gui/app/pods/folder/settings/template.hbs b/gui/app/pods/folder/settings/template.hbs new file mode 100644 index 00000000..e4b954e8 --- /dev/null +++ b/gui/app/pods/folder/settings/template.hbs @@ -0,0 +1,36 @@ +{{#layout/zone-container}} + + {{#layout/zone-sidebar}} + + + + {{/layout/zone-sidebar}} + + {{#layout/zone-content}} +
+

{{model.folder.name}}

+
+ {{#link-to 'folder' model.folder.id model.folder.slug class="vertical-top"}} + arrow_back back to space + {{/link-to}} +
+ + {{#if isAuthProviderDocumize}} + {{folder/invite-user folders=model.folders folder=model.folder}} +
+ {{/if}} + + {{folder/permission-admin folders=model.folders folder=model.folder}} +
+{{/layout/zone-content}} + +{{/layout/zone-container}} \ No newline at end of file diff --git a/gui/app/pods/folder/template.hbs b/gui/app/pods/folder/template.hbs index 6707783f..6583eac5 100644 --- a/gui/app/pods/folder/template.hbs +++ b/gui/app/pods/folder/template.hbs @@ -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}} \ No newline at end of file +{{outlet}} diff --git a/gui/app/router.js b/gui/app/router.js index 5a04cd24..3fad9b5a 100644 --- a/gui/app/router.js +++ b/gui/app/router.js @@ -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', { diff --git a/gui/app/serializers/user-permission.js b/gui/app/serializers/user-permission.js new file mode 100644 index 00000000..2c2733a5 --- /dev/null +++ b/gui/app/serializers/user-permission.js @@ -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 + } + }; + } +}); diff --git a/gui/app/services/folder.js b/gui/app/services/folder.js index 195a1000..244820b3 100644 --- a/gui/app/services/folder.js +++ b/gui/app/services/folder.js @@ -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) { diff --git a/gui/app/styles/view/folder/all.scss b/gui/app/styles/view/folder/all.scss index c6d7a06c..86ae57c4 100644 --- a/gui/app/styles/view/folder/all.scss +++ b/gui/app/styles/view/folder/all.scss @@ -1,4 +1,4 @@ @import "document.scss"; @import "folder.scss"; @import "wizard.scss"; -@import "sidebar.scss"; +@import "settings.scss"; diff --git a/gui/app/styles/view/folder/settings.scss b/gui/app/styles/view/folder/settings.scss new file mode 100644 index 00000000..c5d92fb0 --- /dev/null +++ b/gui/app/styles/view/folder/settings.scss @@ -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; + } + } + } + } +} + diff --git a/gui/app/styles/view/folder/sidebar.scss b/gui/app/styles/view/folder/sidebar.scss deleted file mode 100644 index a5fdd89c..00000000 --- a/gui/app/styles/view/folder/sidebar.scss +++ /dev/null @@ -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; - } - } - } - } -} - diff --git a/gui/app/templates/components/folder/folder-toolbar.hbs b/gui/app/templates/components/folder/folder-toolbar.hbs index a4e3cd2c..1b80f4e4 100644 --- a/gui/app/templates/components/folder/folder-toolbar.hbs +++ b/gui/app/templates/components/folder/folder-toolbar.hbs @@ -28,7 +28,19 @@ {{/dropdown-dialog}} {{else}} -
+ {{#if isFolderOwner}} + {{#link-to 'folder.settings' folder.id folder.slug}}{{model.document.name}} +
+ settings +
+ {{/link-to}} +
+
+ delete +
+ {{else}} +
+ {{/if}} {{/if}}
{{/if}} diff --git a/gui/app/templates/components/folder/sidebar-share.hbs b/gui/app/templates/components/folder/invite-user.hbs similarity index 62% rename from gui/app/templates/components/folder/sidebar-share.hbs rename to gui/app/templates/components/folder/invite-user.hbs index 3c0e27b4..57ac70dd 100644 --- a/gui/app/templates/components/folder/sidebar-share.hbs +++ b/gui/app/templates/components/folder/invite-user.hbs @@ -1,6 +1,9 @@ -