package rest import ( "context" "fmt" "github.com/cortezaproject/corteza-server/pkg/api" "io" "net/http" "net/url" "github.com/cortezaproject/corteza-server/compose/rest/request" "github.com/cortezaproject/corteza-server/compose/service" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/pkg/errors" ) var _ = errors.Wrap type ( attachmentPayload struct { *types.Attachment } attachmentSetPayload struct { Filter types.AttachmentFilter `json:"filter"` Set []*attachmentPayload `json:"set"` } Attachment struct { attachment service.AttachmentService } ) func (Attachment) New() *Attachment { return &Attachment{ attachment: service.DefaultAttachment, } } // Attachments returns list of all files attached to records func (ctrl Attachment) List(ctx context.Context, r *request.AttachmentList) (interface{}, error) { if !auth.GetIdentityFromContext(ctx).Valid() { return nil, errors.New("Unauthorized") } f := types.AttachmentFilter{ NamespaceID: r.NamespaceID, Kind: r.Kind, ModuleID: r.ModuleID, RecordID: r.RecordID, FieldName: r.FieldName, } set, filter, err := ctrl.attachment.With(ctx).Find(f) return ctrl.makeFilterPayload(ctx, set, filter, err) } func (ctrl Attachment) Read(ctx context.Context, r *request.AttachmentRead) (interface{}, error) { if !auth.GetIdentityFromContext(ctx).Valid() { return nil, errors.New("Unauthorized") } a, err := ctrl.attachment.With(ctx).FindByID(r.NamespaceID, r.AttachmentID) return makeAttachmentPayload(ctx, a, err) } func (ctrl Attachment) Delete(ctx context.Context, r *request.AttachmentDelete) (interface{}, error) { if !auth.GetIdentityFromContext(ctx).Valid() { return nil, errors.New("Unauthorized") } _, err := ctrl.attachment.With(ctx).FindByID(r.NamespaceID, r.AttachmentID) if err != nil { return nil, err } return api.OK(), ctrl.attachment.With(ctx).DeleteByID(r.NamespaceID, r.AttachmentID) } func (ctrl Attachment) Original(ctx context.Context, r *request.AttachmentOriginal) (interface{}, error) { if err := ctrl.isAccessible(r.NamespaceID, r.AttachmentID, r.UserID, r.Sign); err != nil { return nil, err } return ctrl.serve(ctx, r.NamespaceID, r.AttachmentID, false, r.Download) } func (ctrl Attachment) Preview(ctx context.Context, r *request.AttachmentPreview) (interface{}, error) { if err := ctrl.isAccessible(r.NamespaceID, r.AttachmentID, r.UserID, r.Sign); err != nil { return nil, err } return ctrl.serve(ctx, r.NamespaceID, r.AttachmentID, true, false) } func (ctrl Attachment) isAccessible(namespaceID, attachmentID, userID uint64, signature string) error { if signature == "" { return errors.New("Unauthorized") } if userID == 0 { return errors.New("missing or invalid user ID") } if attachmentID == 0 { return errors.New("missing or invalid attachment ID") } if !auth.DefaultSigner.Verify(signature, userID, namespaceID, attachmentID) { return errors.New("missing or invalid signature") } return nil } func (ctrl Attachment) serve(ctx context.Context, namespaceID, attachmentID uint64, preview, download bool) (interface{}, error) { return func(w http.ResponseWriter, req *http.Request) { att, err := ctrl.attachment.With(ctx).FindByID(namespaceID, attachmentID) if err != nil { // Simplify error handling for now w.WriteHeader(http.StatusNotFound) return } var fh io.ReadSeeker if preview { fh, err = ctrl.attachment.OpenPreview(att) } else { fh, err = ctrl.attachment.OpenOriginal(att) } if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } name := url.QueryEscape(att.Name) if download { w.Header().Add("Content-Disposition", "attachment; filename="+name) } else { w.Header().Add("Content-Disposition", "inline; filename="+name) } http.ServeContent(w, req, name, att.CreatedAt, fh) }, nil } func (ctrl Attachment) makeFilterPayload(ctx context.Context, aa types.AttachmentSet, f types.AttachmentFilter, err error) (*attachmentSetPayload, error) { if err != nil { return nil, err } asp := &attachmentSetPayload{Filter: f, Set: make([]*attachmentPayload, len(aa))} for i := range aa { asp.Set[i], _ = makeAttachmentPayload(ctx, aa[i], nil) } return asp, nil } func makeAttachmentPayload(ctx context.Context, a *types.Attachment, err error) (*attachmentPayload, error) { if err != nil || a == nil { return nil, err } var ( userID = auth.GetIdentityFromContext(ctx).Identity() signParams = fmt.Sprintf("?sign=%s&userID=%d", auth.DefaultSigner.Sign(userID, a.NamespaceID, a.ID), userID) preview string baseURL = fmt.Sprintf("/namespace/%d/attachment/%s/%d/", a.NamespaceID, a.Kind, a.ID) ) if a.Meta.Preview != nil { var ext = a.Meta.Preview.Extension if ext == "" { ext = "jpg" } preview = baseURL + fmt.Sprintf("preview.%s", ext) } ap := &attachmentPayload{a} ap.Url = baseURL + fmt.Sprintf("original/%s", url.PathEscape(a.Name)) + signParams ap.PreviewUrl = preview + signParams return ap, nil }