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

Migrate document attachments view to new UI framework

This commit is contained in:
sauls8t 2018-12-19 12:41:36 +00:00
parent 7cdf97aa86
commit 3d2060ca60
9 changed files with 148 additions and 9 deletions

View file

@ -15,9 +15,12 @@ import (
"bytes" "bytes"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/documize/community/domain/auth"
"github.com/documize/community/model/space"
"io" "io"
"mime" "mime"
"net/http" "net/http"
"strings"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/request" "github.com/documize/community/core/request"
@ -47,9 +50,22 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
method := "attachment.Download" method := "attachment.Download"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
ctx.Subdomain = organization.GetSubdomainFromHost(r) ctx.Subdomain = organization.GetSubdomainFromHost(r)
ctx.OrgID = request.Param(r, "orgID")
a, err := h.Store.Attachment.GetAttachment(ctx, request.Param(r, "orgID"), request.Param(r, "attachmentID")) // Is caller permitted to download this attachment?
canDownload := false
// Do e have user authentication token?
authToken := strings.TrimSpace(request.Query(r, "token"))
// Do we have secure sharing token (for external users)?
secureToken := strings.TrimSpace(request.Query(r, "secure"))
// We now fetch attachment, the document and space it lives inside.
// Any data loading issue spells the end of this request.
// Get attachment being requested.
a, err := h.Store.Attachment.GetAttachment(ctx, ctx.OrgID, request.Param(r, "attachmentID"))
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, request.Param(r, "fileID")) response.WriteNotFoundError(w, method, request.Param(r, "fileID"))
return return
@ -60,6 +76,86 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
return return
} }
// Get the document for this attachment
doc, err := h.Store.Document.Get(ctx, a.DocumentID)
if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, a.DocumentID)
return
}
if err != nil {
h.Runtime.Log.Error("get attachment document", err)
response.WriteServerError(w, method, err)
return
}
// Get the space for this attachment
sp, err := h.Store.Space.Get(ctx, doc.SpaceID)
if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, a.DocumentID)
return
}
if err != nil {
h.Runtime.Log.Error("get attachment document", err)
response.WriteServerError(w, method, err)
return
}
// At this point, all data associated data is loaded.
// We now begin security checks based upon the request.
// If attachment is in public space then anyone can download
if sp.Type == space.ScopePublic {
canDownload = true
}
// If an user authentication token was provided we check to see
// if user can view document.
// This check only applies to attachments NOT in public spaces.
if sp.Type != space.ScopePublic && len(authToken) > 0 {
// Decode and check incoming token
creds, _, err := auth.DecodeJWT(h.Runtime, authToken)
if err != nil {
h.Runtime.Log.Error("get attachment decode auth token", err)
response.WriteForbiddenError(w)
return
}
// Check for tampering.
if ctx.OrgID != creds.OrgID {
h.Runtime.Log.Error("get attachment org ID mismatch", err)
response.WriteForbiddenError(w)
return
}
// Use token-based user ID for subsequent processing.
ctx.UserID = creds.UserID
// Check to see if user can view BOTH space and document.
if !permission.CanViewSpace(ctx, *h.Store, sp.RefID) || !permission.CanViewDocument(ctx, *h.Store, a.DocumentID) {
h.Runtime.Log.Error("get attachment cannot view document", err)
response.WriteServerError(w, method, err)
return
}
// Authenticated user can view attachment.
canDownload = true
}
// External users can be sent secure document viewing links.
// Those documents may contain attachments that external viewers
// can download as required.
// Such secure document viewing links can have expiry dates.
if len(authToken) == 0 && len(secureToken) > 0 {
}
// Send back error if caller unable view attachment
if !canDownload {
h.Runtime.Log.Error("get attachment refused", err)
response.WriteForbiddenError(w)
return
}
// At this point, user can view attachment so we send it back!
typ := mime.TypeByExtension("." + a.Extension) typ := mime.TypeByExtension("." + a.Extension)
if typ == "" { if typ == "" {
typ = "application/octet-stream" typ = "application/octet-stream"

View file

@ -112,6 +112,10 @@ Disallow: /auth/*
Disallow: /auth/** Disallow: /auth/**
Disallow: /share Disallow: /share
Disallow: /share/* Disallow: /share/*
Disallow: /attachments
Disallow: /attachments/*
Disallow: /attachment
Disallow: /attachment/*
Sitemap: %s`, sitemap) Sitemap: %s`, sitemap)
} }

View file

@ -62,7 +62,6 @@ export default Component.extend(Notifier, {
} }
this.set('model.general.maxTags', this.get('maxTags')); this.set('model.general.maxTags', this.get('maxTags'));
this.model.general.set('allowAnonymousAccess', $("#allowAnonymousAccess").prop('checked'));
this.get('save')().then(() => { this.get('save')().then(() => {
this.notifySuccess('Saved'); this.notifySuccess('Saved');

View file

@ -20,11 +20,13 @@ export default Component.extend(Modals, Notifier, {
documentService: service('document'), documentService: service('document'),
browserSvc: service('browser'), browserSvc: service('browser'),
appMeta: service(), appMeta: service(),
session: service(),
hasAttachments: notEmpty('files'), hasAttachments: notEmpty('files'),
canEdit: computed('permissions.documentEdit', 'document.protection', function() { canEdit: computed('permissions.documentEdit', 'document.protection', function() {
return this.get('document.protection') !== this.get('constants').ProtectionType.Lock && this.get('permissions.documentEdit'); return this.get('document.protection') !== this.get('constants').ProtectionType.Lock && this.get('permissions.documentEdit');
}), }),
showDialog: false, showDialog: false,
downloadQuery: '',
init() { init() {
this._super(...arguments); this._super(...arguments);
@ -50,7 +52,7 @@ export default Component.extend(Modals, Notifier, {
let dzone = new Dropzone("#upload-document-files", { let dzone = new Dropzone("#upload-document-files", {
headers: { headers: {
'Authorization': 'Bearer ' + self.get('session.session.content.authenticated.token') 'Authorization': 'Bearer ' + self.get('session.authToken')
}, },
url: uploadUrl, url: uploadUrl,
method: "post", method: "post",
@ -67,16 +69,16 @@ export default Component.extend(Modals, Notifier, {
}); });
this.on("queuecomplete", function () { this.on("queuecomplete", function () {
self.notifySuccess('Saved'); self.notifySuccess('Uploaded file');
self.getAttachments(); self.getAttachments();
}); });
this.on("addedfile", function ( /*file*/ ) { this.on("addedfile", function ( /*file*/ ) {
}); });
this.on("error", function (error, msg) { // // eslint-disable-line no-unused-vars this.on("error", function (error, msg) {
self.notifyError(msg); self.notifyError(msg);
console.log(msg); // eslint-disable-line no-console self.notifyError(error);
}); });
} }
}); });
@ -86,6 +88,13 @@ export default Component.extend(Modals, Notifier, {
}); });
this.set('drop', dzone); this.set('drop', dzone);
// For authenticated users we send server auth token.
let qry = '';
if (this.get('session.authenticated')) {
qry = '?token=' + this.get('session.authToken');
}
this.set('downloadQuery', qry);
}, },
getAttachments() { getAttachments() {

View file

@ -356,7 +356,7 @@ export default Service.extend({
}, },
//************************************************** //**************************************************
// Export // Export content to HTML
//************************************************** //**************************************************
export(spec) { export(spec) {
@ -367,6 +367,14 @@ export default Service.extend({
}); });
}, },
//**************************************************
// Secure document attachment download
//**************************************************
downloadAttachment(fileId) {
return this.get('ajax').get(`attachment/${fileId}`, {});
},
//************************************************** //**************************************************
// Fetch bulk data // Fetch bulk data
//************************************************** //**************************************************

View file

@ -85,6 +85,16 @@ export default SimpleAuthSession.extend({
this.get('session.content.authenticated.user.viewUsers') === true; this.get('session.content.authenticated.user.viewUsers') === true;
}), }),
authToken: computed('session.content.authenticated.user', function () {
if (is.null(this.get('session.authenticator')) ||
this.get('appMeta.secureMode')) return '';
if (this.get('session.authenticator') === 'authenticator:anonymous' ||
this.get('session.content.authenticated.user.id') === '0') return '';
return this.get('session.content.authenticated.token');
}),
init() { init() {
this._super(...arguments); this._super(...arguments);

View file

@ -84,4 +84,15 @@
} }
} }
} }
.empty-label {
margin: 10px 0;
font-size: 1rem;
font-weight: 500;
color: map-get($gray-shades, 700);
font-style: italic;
padding: 0 7px;
}
} }

View file

@ -12,7 +12,7 @@
<ul class="files"> <ul class="files">
{{#each files key="id" as |file|}} {{#each files key="id" as |file|}}
<li class="file"> <li class="file">
<a href="{{appMeta.endpoint}}/public/attachments/{{appMeta.orgId}}/{{file.id}}"> <a href="{{appMeta.endpoint}}/public/attachment/{{appMeta.orgId}}/{{file.id}}{{downloadQuery}}">
{{file.filename}} {{file.filename}}
</a> </a>
{{#if canEdit}} {{#if canEdit}}
@ -27,6 +27,8 @@
{{/each}} {{/each}}
</ul> </ul>
</div> </div>
{{else}}
<p class="empty-label">No attachments</p>
{{/if}} {{/if}}
{{#ui/ui-dialog title="Delete Attachment" confirmCaption="Delete" buttonColor=constants.Color.Red show=showDialog onAction=(action "onDelete")}} {{#ui/ui-dialog title="Delete Attachment" confirmCaption="Delete" buttonColor=constants.Color.Red show=showDialog onAction=(action "onDelete")}}

View file

@ -92,7 +92,7 @@ func RegisterEndpoints(rt *env.Runtime, s *store.Store) {
AddPublic(rt, "forgot", []string{"POST", "OPTIONS"}, nil, user.ForgotPassword) AddPublic(rt, "forgot", []string{"POST", "OPTIONS"}, nil, user.ForgotPassword)
AddPublic(rt, "reset/{token}", []string{"POST", "OPTIONS"}, nil, user.ResetPassword) AddPublic(rt, "reset/{token}", []string{"POST", "OPTIONS"}, nil, user.ResetPassword)
AddPublic(rt, "share/{spaceID}", []string{"POST", "OPTIONS"}, nil, space.AcceptInvitation) AddPublic(rt, "share/{spaceID}", []string{"POST", "OPTIONS"}, nil, space.AcceptInvitation)
AddPublic(rt, "attachments/{orgID}/{attachmentID}", []string{"GET", "OPTIONS"}, nil, attachment.Download) AddPublic(rt, "attachment/{orgID}/{attachmentID}", []string{"GET", "OPTIONS"}, nil, attachment.Download)
//************************************************** //**************************************************
// Secured private routes (require authentication) // Secured private routes (require authentication)