1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-08-02 20:15:26 +02:00

document level permissions

This commit is contained in:
Harvey Kandola 2017-12-26 13:25:10 +00:00
parent f4f32bcfcb
commit b9394a4967
28 changed files with 983 additions and 355 deletions

View file

@ -414,6 +414,17 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
record := pm.DecodeUserPermissions(perms)
roles, err := h.Store.Permission.GetUserDocumentPermissions(ctx, document.RefID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(roles) == 0 {
roles = []pm.Permission{}
}
rolesRecord := pm.DecodeUserDocumentPermissions(roles)
// links
l, err := h.Store.Link.GetDocumentOutboundLinks(ctx, id)
if len(l) == 0 {
@ -439,6 +450,7 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
data := documentData{}
data.Document = document
data.Permissions = record
data.Roles = rolesRecord
data.Links = l
data.Spaces = sp
@ -469,8 +481,9 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
// documentData represents all data associated for a single document.
// Used by FetchDocumentData() bulk data load call.
type documentData struct {
Document doc.Document `json:"document"`
Permissions pm.Record `json:"permissions"`
Spaces []space.Space `json:"folders"`
Links []link.Link `json:"link"`
Document doc.Document `json:"document"`
Permissions pm.Record `json:"permissions"`
Roles pm.DocumentRecord `json:"roles"`
Spaces []space.Space `json:"folders"`
Links []link.Link `json:"link"`
}

View file

@ -0,0 +1,98 @@
<html xmlns="http://www.w3.org/1999/xhtml" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>{{.Subject}}</title>
<style type="text/css">
img {
max-width: 100%;
}
body {
-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6;
}
body {
background-color: #f6f6f6;
}
@media only screen and (max-width: 640px) {
h1 {
font-weight: 600 !important; margin: 20px 0 5px !important;
}
h2 {
font-weight: 600 !important; margin: 20px 0 5px !important;
}
h3 {
font-weight: 600 !important; margin: 20px 0 5px !important;
}
h4 {
font-weight: 600 !important; margin: 20px 0 5px !important;
}
h1 {
font-size: 22px !important;
}
h2 {
font-size: 18px !important;
}
h3 {
font-size: 16px !important;
}
.container {
width: 100% !important;
}
.content {
padding: 10px !important;
}
.content-wrap {
padding: 10px !important;
}
.invoice {
width: 100% !important;
}
}
</style>
</head>
<body style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6; background: #f6f6f6; margin: 0; padding: 0;">
<table class="body-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background: #f6f6f6; margin: 0; padding: 0;">
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<td style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0;" valign="top"></td>
<td class="container" width="600" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto; padding: 0;" valign="top">
<div class="content" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;">
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
Document Approval Role Granted
</td>
</tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
<td class="content-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;" valign="top">
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
<p>You are requested to approve all changes to the following document:</p>
<p style="font-weight: bold;">{{.Document}}</p>
<p>{{.Inviter}}</p>
</td>
</tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
<a href="{{.Url}}" class="btn-primary" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">View document</a>
</td>
</tr>
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px; color: #7a8184;" valign="top">
Have any questions? <a href="mailto:team@documize.com" style="color: #7a8184;">Contact Documize</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</td>
<td style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0;" valign="top"></td>
</tr>
</table>
</body>
</html>

70
domain/mail/document.go Normal file
View file

@ -0,0 +1,70 @@
// 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
// jshint ignore:start
package mail
import (
"bytes"
"fmt"
"html/template"
"github.com/documize/community/server/web"
)
// DocumentApprover notifies user who has just been granted document approval rights.
func (m *Mailer) DocumentApprover(recipient, inviter, url, document string) {
method := "DocumentApprover"
m.LoadCredentials()
file, err := web.ReadFile("mail/document-approver.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
}
emailTemplate := string(file)
// check inviter name
if inviter == "Hello You" || len(inviter) == 0 {
inviter = "Your colleague"
}
subject := fmt.Sprintf("%s has granted you document approval", inviter)
e := NewEmail()
e.From = m.Credentials.SMTPsender
e.To = []string{recipient}
e.Subject = subject
parameters := struct {
Subject string
Inviter string
Url string
Document string
}{
subject,
inviter,
url,
document,
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes()
err = e.Send(m.GetHost(), m.GetAuth())
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
}
}

View file

@ -14,15 +14,11 @@
package mail
import (
"bytes"
"fmt"
"html/template"
"net/smtp"
"strings"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/server/web"
)
// Mailer provides emailing facilities
@ -33,241 +29,6 @@ type Mailer struct {
Credentials Credentials
}
// InviteNewUser invites someone new providing credentials, explaining the product and stating who is inviting them.
func (m *Mailer) InviteNewUser(recipient, inviter, url, username, password string) {
method := "InviteNewUser"
m.LoadCredentials()
file, err := web.ReadFile("mail/invite-new-user.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
}
emailTemplate := string(file)
// check inviter name
if inviter == "Hello You" || len(inviter) == 0 {
inviter = "Your colleague"
}
subject := fmt.Sprintf("%s has invited you to Documize", inviter)
e := NewEmail()
e.From = m.Credentials.SMTPsender
e.To = []string{recipient}
e.Subject = subject
parameters := struct {
Subject string
Inviter string
Url string
Username string
Password string
}{
subject,
inviter,
url,
recipient,
password,
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes()
err = e.Send(m.GetHost(), m.GetAuth())
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
}
}
// InviteExistingUser invites a known user to an organization.
func (m *Mailer) InviteExistingUser(recipient, inviter, url string) {
method := "InviteExistingUser"
m.LoadCredentials()
file, err := web.ReadFile("mail/invite-existing-user.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
}
emailTemplate := string(file)
// check inviter name
if inviter == "Hello You" || len(inviter) == 0 {
inviter = "Your colleague"
}
subject := fmt.Sprintf("%s has invited you to their Documize account", inviter)
e := NewEmail()
e.From = m.Credentials.SMTPsender
e.To = []string{recipient}
e.Subject = subject
parameters := struct {
Subject string
Inviter string
Url string
}{
subject,
inviter,
url,
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes()
err = e.Send(m.GetHost(), m.GetAuth())
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
}
}
// PasswordReset sends a reset email with an embedded token.
func (m *Mailer) PasswordReset(recipient, url string) {
method := "PasswordReset"
m.LoadCredentials()
file, err := web.ReadFile("mail/password-reset.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
}
emailTemplate := string(file)
subject := "Documize password reset request"
e := NewEmail()
e.From = m.Credentials.SMTPsender //e.g. "Documize <hello@documize.com>"
e.To = []string{recipient}
e.Subject = subject
parameters := struct {
Subject string
Url string
}{
subject,
url,
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes()
err = e.Send(m.GetHost(), m.GetAuth())
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
}
}
// 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-space-existing-user.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
}
emailTemplate := string(file)
// check inviter name
if inviter == "Hello You" || len(inviter) == 0 {
inviter = "Your colleague"
}
subject := fmt.Sprintf("%s has shared %s with you", inviter, folder)
e := NewEmail()
e.From = m.Credentials.SMTPsender
e.To = []string{recipient}
e.Subject = subject
parameters := struct {
Subject string
Inviter string
Url string
Folder string
Intro string
}{
subject,
inviter,
url,
folder,
intro,
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes()
err = e.Send(m.GetHost(), m.GetAuth())
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
}
}
// 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-space-new-user.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
}
emailTemplate := string(file)
// check inviter name
if inviter == "Hello You" || len(inviter) == 0 {
inviter = "Your colleague"
}
subject := fmt.Sprintf("%s has shared %s with you on Documize", inviter, space)
e := NewEmail()
e.From = m.Credentials.SMTPsender
e.To = []string{recipient}
e.Subject = subject
parameters := struct {
Subject string
Inviter string
Url string
Invitation string
Folder string
}{
subject,
inviter,
url,
invitationMessage,
space,
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes()
err = e.Send(m.GetHost(), m.GetAuth())
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
}
}
// Credentials holds SMTP endpoint and authentication methods
type Credentials struct {
SMTPuserid string

122
domain/mail/space.go Normal file
View file

@ -0,0 +1,122 @@
// 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
// jshint ignore:start
package mail
import (
"bytes"
"fmt"
"html/template"
"github.com/documize/community/server/web"
)
// 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-space-existing-user.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
}
emailTemplate := string(file)
// check inviter name
if inviter == "Hello You" || len(inviter) == 0 {
inviter = "Your colleague"
}
subject := fmt.Sprintf("%s has shared %s with you", inviter, folder)
e := NewEmail()
e.From = m.Credentials.SMTPsender
e.To = []string{recipient}
e.Subject = subject
parameters := struct {
Subject string
Inviter string
Url string
Folder string
Intro string
}{
subject,
inviter,
url,
folder,
intro,
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes()
err = e.Send(m.GetHost(), m.GetAuth())
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
}
}
// 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-space-new-user.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
}
emailTemplate := string(file)
// check inviter name
if inviter == "Hello You" || len(inviter) == 0 {
inviter = "Your colleague"
}
subject := fmt.Sprintf("%s has shared %s with you on Documize", inviter, space)
e := NewEmail()
e.From = m.Credentials.SMTPsender
e.To = []string{recipient}
e.Subject = subject
parameters := struct {
Subject string
Inviter string
Url string
Invitation string
Folder string
}{
subject,
inviter,
url,
invitationMessage,
space,
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes()
err = e.Send(m.GetHost(), m.GetAuth())
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
}
}

157
domain/mail/user.go Normal file
View file

@ -0,0 +1,157 @@
// 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
// jshint ignore:start
package mail
import (
"bytes"
"fmt"
"html/template"
"github.com/documize/community/server/web"
)
// InviteNewUser invites someone new providing credentials, explaining the product and stating who is inviting them.
func (m *Mailer) InviteNewUser(recipient, inviter, url, username, password string) {
method := "InviteNewUser"
m.LoadCredentials()
file, err := web.ReadFile("mail/invite-new-user.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
}
emailTemplate := string(file)
// check inviter name
if inviter == "Hello You" || len(inviter) == 0 {
inviter = "Your colleague"
}
subject := fmt.Sprintf("%s has invited you to Documize", inviter)
e := NewEmail()
e.From = m.Credentials.SMTPsender
e.To = []string{recipient}
e.Subject = subject
parameters := struct {
Subject string
Inviter string
Url string
Username string
Password string
}{
subject,
inviter,
url,
recipient,
password,
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes()
err = e.Send(m.GetHost(), m.GetAuth())
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
}
}
// InviteExistingUser invites a known user to an organization.
func (m *Mailer) InviteExistingUser(recipient, inviter, url string) {
method := "InviteExistingUser"
m.LoadCredentials()
file, err := web.ReadFile("mail/invite-existing-user.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
}
emailTemplate := string(file)
// check inviter name
if inviter == "Hello You" || len(inviter) == 0 {
inviter = "Your colleague"
}
subject := fmt.Sprintf("%s has invited you to their Documize account", inviter)
e := NewEmail()
e.From = m.Credentials.SMTPsender
e.To = []string{recipient}
e.Subject = subject
parameters := struct {
Subject string
Inviter string
Url string
}{
subject,
inviter,
url,
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes()
err = e.Send(m.GetHost(), m.GetAuth())
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
}
}
// PasswordReset sends a reset email with an embedded token.
func (m *Mailer) PasswordReset(recipient, url string) {
method := "PasswordReset"
m.LoadCredentials()
file, err := web.ReadFile("mail/password-reset.html")
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
}
emailTemplate := string(file)
subject := "Documize password reset request"
e := NewEmail()
e.From = m.Credentials.SMTPsender //e.g. "Documize <hello@documize.com>"
e.To = []string{recipient}
e.Subject = subject
parameters := struct {
Subject string
Url string
}{
subject,
url,
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes()
err = e.Send(m.GetHost(), m.GetAuth())
if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
}
}

View file

@ -54,8 +54,8 @@ func (s Scope) Add(ctx domain.RequestContext, model page.NewPage) (err error) {
model.Page.Sequence = maxSeq * 2
}
_, err = ctx.Transaction.Exec("INSERT INTO page (refid, orgid, documentid, userid, contenttype, pagetype, level, title, body, revisions, sequence, blockid, protected, approval, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
model.Page.RefID, model.Page.OrgID, model.Page.DocumentID, model.Page.UserID, model.Page.ContentType, model.Page.PageType, model.Page.Level, model.Page.Title, model.Page.Body, model.Page.Revisions, model.Page.Sequence, model.Page.BlockID, model.Page.Protection, model.Page.Approval, model.Page.Created, model.Page.Revised)
_, err = ctx.Transaction.Exec("INSERT INTO page (refid, orgid, documentid, userid, contenttype, pagetype, level, title, body, revisions, sequence, blockid, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
model.Page.RefID, model.Page.OrgID, model.Page.DocumentID, model.Page.UserID, model.Page.ContentType, model.Page.PageType, model.Page.Level, model.Page.Title, model.Page.Body, model.Page.Revisions, model.Page.Sequence, model.Page.BlockID, model.Page.Created, model.Page.Revised)
_, err = ctx.Transaction.Exec("INSERT INTO pagemeta (pageid, orgid, userid, documentid, rawbody, config, externalsource, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
model.Meta.PageID, model.Meta.OrgID, model.Meta.UserID, model.Meta.DocumentID, model.Meta.RawBody, model.Meta.Config, model.Meta.ExternalSource, model.Meta.Created, model.Meta.Revised)
@ -69,7 +69,7 @@ func (s Scope) Add(ctx domain.RequestContext, model page.NewPage) (err error) {
// Get returns the pageID page record from the page table.
func (s Scope) Get(ctx domain.RequestContext, pageID string) (p page.Page, err error) {
err = s.Runtime.Db.Get(&p, "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.pagetype, a.level, a.sequence, a.title, a.body, a.revisions, a.blockid, a.protection, a.approval, a.created, a.revised FROM page a WHERE a.orgid=? AND a.refid=?",
err = s.Runtime.Db.Get(&p, "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.pagetype, a.level, a.sequence, a.title, a.body, a.revisions, a.blockid, a.created, a.revised FROM page a WHERE a.orgid=? AND a.refid=?",
ctx.OrgID, pageID)
if err != nil {
@ -81,7 +81,7 @@ func (s Scope) Get(ctx domain.RequestContext, pageID string) (p page.Page, err e
// GetPages returns a slice containing all the page records for a given documentID, in presentation sequence.
func (s Scope) GetPages(ctx domain.RequestContext, documentID string) (p []page.Page, err error) {
err = s.Runtime.Db.Select(&p, "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.pagetype, a.level, a.sequence, a.title, a.body, a.revisions, a.blockid, a.protection, a.approval, a.created, a.revised FROM page a WHERE a.orgid=? AND a.documentid=? ORDER BY a.sequence", ctx.OrgID, documentID)
err = s.Runtime.Db.Select(&p, "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.pagetype, a.level, a.sequence, a.title, a.body, a.revisions, a.blockid, a.created, a.revised FROM page a WHERE a.orgid=? AND a.documentid=? ORDER BY a.sequence", ctx.OrgID, documentID)
if err != nil {
err = errors.Wrap(err, "execute get pages")
@ -93,7 +93,7 @@ func (s Scope) GetPages(ctx domain.RequestContext, documentID string) (p []page.
// GetPagesWithoutContent returns a slice containing all the page records for a given documentID, in presentation sequence,
// but without the body field (which holds the HTML content).
func (s Scope) GetPagesWithoutContent(ctx domain.RequestContext, documentID string) (pages []page.Page, err error) {
err = s.Runtime.Db.Select(&pages, "SELECT id, refid, orgid, documentid, userid, contenttype, pagetype, sequence, level, title, revisions, blockid, protection, approval, created, revised FROM page WHERE orgid=? AND documentid=? ORDER BY sequence", ctx.OrgID, documentID)
err = s.Runtime.Db.Select(&pages, "SELECT id, refid, orgid, documentid, userid, contenttype, pagetype, sequence, level, title, revisions, blockid, created, revised FROM page WHERE orgid=? AND documentid=? ORDER BY sequence", ctx.OrgID, documentID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("Unable to execute select pages for org %s and document %s", ctx.OrgID, documentID))
@ -119,7 +119,7 @@ func (s Scope) Update(ctx domain.RequestContext, page page.Page, refID, userID s
}
// Update page
_, err = ctx.Transaction.NamedExec("UPDATE page SET documentid=:documentid, level=:level, title=:title, body=:body, revisions=:revisions, sequence=:sequence, protection=:protection, approval=:approval, revised=:revised WHERE orgid=:orgid AND refid=:refid",
_, err = ctx.Transaction.NamedExec("UPDATE page SET documentid=:documentid, level=:level, title=:title, body=:body, revisions=:revisions, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid",
&page)
if err != nil {

View file

@ -69,7 +69,7 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
return
}
var model = permission.PermissionsModel{}
var model = permission.SpaceRequestModel{}
err = json.Unmarshal(body, &model)
if err != nil {
response.WriteServerError(w, method, err)
@ -398,3 +398,192 @@ func (h *Handler) SetCategoryPermissions(w http.ResponseWriter, r *http.Request)
response.WriteEmpty(w)
}
// GetDocumentPermissions returns permissions for all users for given document.
func (h *Handler) GetDocumentPermissions(w http.ResponseWriter, r *http.Request) {
method := "space.GetDocumentPermissions"
ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
perms, err := h.Store.Permission.GetDocumentPermissions(ctx, documentID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(perms) == 0 {
perms = []permission.Permission{}
}
userPerms := make(map[string][]permission.Permission)
for _, p := range perms {
userPerms[p.WhoID] = append(userPerms[p.WhoID], p)
}
records := []permission.DocumentRecord{}
for _, up := range userPerms {
records = append(records, permission.DecodeUserDocumentPermissions(up))
}
response.WriteJSON(w, records)
}
// GetUserDocumentPermissions returns permissions for the requested space, for current user.
func (h *Handler) GetUserDocumentPermissions(w http.ResponseWriter, r *http.Request) {
method := "space.GetUserDocumentPermissions"
ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
perms, err := h.Store.Permission.GetUserDocumentPermissions(ctx, documentID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(perms) == 0 {
perms = []permission.Permission{}
}
record := permission.DecodeUserDocumentPermissions(perms)
response.WriteJSON(w, record)
}
// SetDocumentPermissions persists specified document permissions
// These permissions override document permissions
func (h *Handler) SetDocumentPermissions(w http.ResponseWriter, r *http.Request) {
method := "space.SetDocumentPermissions"
ctx := domain.GetRequestContext(r)
id := request.Param(r, "documentID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
doc, err := h.Store.Document.Get(ctx, id)
if err != nil {
response.WriteNotFoundError(w, method, "document not found")
return
}
sp, err := h.Store.Space.Get(ctx, doc.LabelID)
if err != nil {
response.WriteNotFoundError(w, method, "space not found")
return
}
// if !HasPermission(ctx, *h.Store, doc.LabelID, permission.SpaceManage, permission.SpaceOwner) {
// response.WriteForbiddenError(w)
// return
// }
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
h.Runtime.Log.Error(method, err)
return
}
var model = []permission.DocumentRecord{}
err = json.Unmarshal(body, &model)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// We compare new permisions to what we had before.
// Why? So we can send out space invitation emails.
previousRoles, err := h.Store.Permission.GetDocumentPermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Store all previous approval roles as map for easy querying
previousRoleUsers := make(map[string]bool)
for _, v := range previousRoles {
if v.Action == permission.DocumentApprove {
previousRoleUsers[v.WhoID] = true
}
}
// Get user who is setting document permissions so we can send out emails with context
inviter, err := h.Store.User.Get(ctx, ctx.UserID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Nuke all previous permissions for this document
_, err = h.Store.Permission.DeleteDocumentPermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s/d/%s/%s",
sp.RefID, stringutil.MakeSlug(sp.Name), doc.RefID, stringutil.MakeSlug(doc.Title)))
for _, perm := range model {
perm.OrgID = ctx.OrgID
perm.DocumentID = id
// Only persist if there is a role!
if permission.HasAnyDocumentPermission(perm) {
r := permission.EncodeUserDocumentPermissions(perm)
for _, p := range r {
err = h.Store.Permission.AddPermission(ctx, p)
if err != nil {
h.Runtime.Log.Error("set document permission", err)
}
}
// Send email notification to users who have been given document approver role
if _, isExisting := previousRoleUsers[perm.UserID]; !isExisting {
// we skip 'everyone' (user id != empty string)
if perm.UserID != "0" && perm.UserID != "" && perm.DocumentRoleApprove {
existingUser, err := h.Store.User.Get(ctx, perm.UserID)
if err != nil {
response.WriteServerError(w, method, err)
break
}
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.DocumentApprover(existingUser.Email, inviter.Fullname(), url, doc.Title)
h.Runtime.Log.Info(fmt.Sprintf("%s has made %s document approver for: %s", inviter.Email, existingUser.Email, doc.Title))
}
}
}
}
h.Store.Audit.Record(ctx, audit.EventTypeDocumentPermission)
ctx.Transaction.Commit()
response.WriteEmpty(w)
}

View file

@ -212,3 +212,55 @@ func (s Scope) GetUserCategoryPermissions(ctx domain.RequestContext, userID stri
return
}
// GetUserDocumentPermissions returns document permissions for user.
// Context is used to for user ID.
func (s Scope) GetUserDocumentPermissions(ctx domain.RequestContext, documentID string) (r []permission.Permission, err error) {
err = s.Runtime.Db.Select(&r, `
SELECT id, orgid, who, whoid, action, scope, location, refid
FROM permission WHERE orgid=? AND location='document' AND refid=? AND who='user' AND (whoid=? OR whoid='0')
UNION ALL
SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid
FROM permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.location='document' AND refid=?
AND p.who='role' AND (r.userid=? OR r.userid='0')`,
ctx.OrgID, documentID, ctx.UserID, ctx.OrgID, documentID, ctx.OrgID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select user document permissions %s", ctx.UserID))
}
return
}
// GetDocumentPermissions returns documents permissions for all users.
func (s Scope) GetDocumentPermissions(ctx domain.RequestContext, documentID string) (r []permission.Permission, err error) {
err = s.Runtime.Db.Select(&r, `
SELECT id, orgid, who, whoid, action, scope, location, refid
FROM permission WHERE orgid=? AND location='document' AND refid=? AND who='user'
UNION ALL
SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid
FROM permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.location='document' AND p.refid=?
AND p.who='role'`,
ctx.OrgID, documentID, ctx.OrgID, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select document permissions %s", ctx.UserID))
}
return
}
// DeleteDocumentPermissions removes records from permissions table for given document.
func (s Scope) DeleteDocumentPermissions(ctx domain.RequestContext, documentID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND location='document' AND refid='%s'", ctx.OrgID, documentID)
return b.DeleteWhere(ctx.Transaction, sql)
}

View file

@ -94,6 +94,9 @@ type PermissionStorer interface {
GetCategoryPermissions(ctx RequestContext, catID string) (r []permission.Permission, err error)
GetCategoryUsers(ctx RequestContext, catID string) (u []user.User, err error)
GetUserCategoryPermissions(ctx RequestContext, userID string) (r []permission.Permission, err error)
GetUserDocumentPermissions(ctx RequestContext, documentID string) (r []permission.Permission, err error)
GetDocumentPermissions(ctx RequestContext, documentID string) (r []permission.Permission, err error)
DeleteDocumentPermissions(ctx RequestContext, documentID string) (rows int64, err error)
}
// UserStorer defines required methods for user management