1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-19 05:09:42 +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"
"database/sql"
"fmt"
"github.com/documize/community/domain/auth"
"github.com/documize/community/model/space"
"io"
"mime"
"net/http"
"strings"
"github.com/documize/community/core/env"
"github.com/documize/community/core/request"
@ -47,9 +50,22 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
method := "attachment.Download"
ctx := domain.GetRequestContext(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 {
response.WriteNotFoundError(w, method, request.Param(r, "fileID"))
return
@ -60,6 +76,86 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
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)
if typ == "" {
typ = "application/octet-stream"

View file

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

View file

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

View file

@ -20,11 +20,13 @@ export default Component.extend(Modals, Notifier, {
documentService: service('document'),
browserSvc: service('browser'),
appMeta: service(),
session: service(),
hasAttachments: notEmpty('files'),
canEdit: computed('permissions.documentEdit', 'document.protection', function() {
return this.get('document.protection') !== this.get('constants').ProtectionType.Lock && this.get('permissions.documentEdit');
}),
showDialog: false,
downloadQuery: '',
init() {
this._super(...arguments);
@ -50,7 +52,7 @@ export default Component.extend(Modals, Notifier, {
let dzone = new Dropzone("#upload-document-files", {
headers: {
'Authorization': 'Bearer ' + self.get('session.session.content.authenticated.token')
'Authorization': 'Bearer ' + self.get('session.authToken')
},
url: uploadUrl,
method: "post",
@ -67,16 +69,16 @@ export default Component.extend(Modals, Notifier, {
});
this.on("queuecomplete", function () {
self.notifySuccess('Saved');
self.notifySuccess('Uploaded file');
self.getAttachments();
});
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);
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);
// 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() {

View file

@ -356,7 +356,7 @@ export default Service.extend({
},
//**************************************************
// Export
// Export content to HTML
//**************************************************
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
//**************************************************

View file

@ -85,6 +85,16 @@ export default SimpleAuthSession.extend({
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() {
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">
{{#each files key="id" as |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}}
</a>
{{#if canEdit}}
@ -27,6 +27,8 @@
{{/each}}
</ul>
</div>
{{else}}
<p class="empty-label">No attachments</p>
{{/if}}
{{#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, "reset/{token}", []string{"POST", "OPTIONS"}, nil, user.ResetPassword)
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)