Merge pull request #1033 from dod-ccpo/ajax-uploads

Get presigned cloud upload token through an ajax call
This commit is contained in:
richard-dds 2019-08-29 10:07:40 -04:00 committed by GitHub
commit b3e48aa6e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 80 additions and 52 deletions

View File

@ -1,9 +1,9 @@
{
"exclude": {
"files": null,
"files": "^.secrets.baseline$",
"lines": null
},
"generated_at": "2019-08-21T18:40:15Z",
"generated_at": "2019-08-28T19:50:39Z",
"plugins_used": [
{
"base64_limit": 4.5,
@ -38,7 +38,7 @@
"hashed_secret": "d141ce86b0584abb29ee7c24af9afb1e3d871f04",
"is_secret": false,
"is_verified": false,
"line_number": 145,
"line_number": 156,
"type": "Secret Keyword"
}
],
@ -60,6 +60,22 @@
"type": "Hex High Entropy String"
}
],
"alembic/versions/fda6bd7e1b65_clin_delete_cascade.py": [
{
"hashed_secret": "61d8937fb12b982e07b933c083d9014c34159723",
"is_secret": false,
"is_verified": false,
"line_number": 13,
"type": "Hex High Entropy String"
},
{
"hashed_secret": "999a22300a564f9d2bdca555c2170465fd760ae3",
"is_secret": false,
"is_verified": false,
"line_number": 14,
"type": "Hex High Entropy String"
}
],
"atst.ini.example": [
{
"hashed_secret": "abcdb568713c255c81376829da20004ba9463fd3",
@ -178,10 +194,11 @@
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
"is_secret": false,
"is_verified": false,
"line_number": 481,
"line_number": 523,
"type": "Hex High Entropy String"
}
]
},
"version": "0.12.5"
}

View File

@ -4,7 +4,8 @@ from flask import (
render_template,
request as http_request,
url_for,
current_app,
current_app as app,
jsonify,
)
from . import task_orders_bp
@ -17,9 +18,7 @@ from atst.utils.flash import formatted_flash as flash
def render_task_orders_edit(template, portfolio_id=None, task_order_id=None, form=None):
(token, object_name) = current_app.csp.files.get_token()
render_args = {"token": token, "object_name": object_name}
render_args = {}
if task_order_id:
task_order = TaskOrders.get(task_order_id)
portfolio_id = task_order.portfolio_id
@ -73,6 +72,15 @@ def update_task_order(
)
@task_orders_bp.route("/task_orders/<portfolio_id>/upload-token")
@user_can(Permissions.CREATE_TASK_ORDER, message="edit task order form")
def upload_token(portfolio_id):
(token, object_name) = app.csp.files.get_token()
render_args = {"token": token, "objectName": object_name}
return jsonify(render_args)
@task_orders_bp.route("/task_orders/<task_order_id>/edit")
@user_can(Permissions.CREATE_TASK_ORDER, message="edit task order form")
def edit(task_order_id):

View File

@ -1,6 +1,3 @@
import createNumberMask from 'text-mask-addons/dist/createNumberMask'
import { conformToMask } from 'vue-text-mask'
import { emitEvent } from '../lib/emitters'
import FormMixin from '../mixins/form'
import textinput from './text_input'
@ -20,12 +17,6 @@ export default {
props: {
name: String,
token: {
type: Object,
},
objectName: {
type: String,
},
initialData: {
type: String,
},
@ -40,6 +31,9 @@ export default {
type: Boolean,
default: true,
},
portfolioId: {
type: String,
},
},
data: function() {
@ -52,8 +46,7 @@ export default {
}
},
created: function() {
this.uploader = buildUploader(this.token)
created: async function() {
emitEvent('field-mount', this, {
optional: this.optional,
name: this.name,
@ -71,13 +64,12 @@ export default {
return
}
const response = await this.uploader.upload(file, this.objectName)
const uploader = await this.getUploader()
const response = await uploader.upload(file)
if (response.ok) {
this.attachment = e.target.value
this.$refs.attachmentFilename.value = file.name
this.$refs.attachmentObjectName.value = this.objectName
this.$refs.attachmentInput.disabled = true
this.$refs.attachmentObjectName.value = response.objectName
} else {
this.uploadError = true
}
@ -111,6 +103,13 @@ export default {
this.uploadError = false
this.sizeError = false
},
getUploader: async function() {
return fetch(`/task_orders/${this.portfolioId}/upload-token`, {
credentials: 'include',
})
.then(response => response.json())
.then(({ token, objectName }) => buildUploader(token, objectName))
},
},
computed: {

View File

@ -2,13 +2,14 @@ import Azure from 'azure-storage'
import 'whatwg-fetch'
class AzureUploader {
constructor(accountName, containerName, sasToken) {
constructor(accountName, containerName, sasToken, objectName) {
this.accountName = accountName
this.containerName = containerName
this.sasToken = sasToken.token
this.objectName = objectName
}
async upload(file, objectName) {
async upload(file) {
const blobService = Azure.createBlobServiceWithSas(
`https://${this.accountName}.blob.core.windows.net`,
this.sasToken
@ -27,14 +28,14 @@ class AzureUploader {
fileReader.addEventListener('load', f => {
blobService.createBlockBlobFromText(
this.containerName,
`${objectName}`,
`${this.objectName}`,
f.target.result,
options,
function(err, result) {
(err, result) => {
if (err) {
resolve({ ok: false })
} else {
resolve({ ok: true })
resolve({ ok: true, objectName: this.objectName })
}
}
)
@ -45,11 +46,12 @@ class AzureUploader {
}
class AwsUploader {
constructor(presignedPost) {
constructor(presignedPost, objectName) {
this.presignedPost = presignedPost
this.objectName = objectName
}
async upload(file, objectName) {
async upload(file) {
const form = new FormData()
Object.entries(this.presignedPost.fields).forEach(([k, v]) => {
form.append(k, v)
@ -57,34 +59,38 @@ class AwsUploader {
form.append('file', file)
form.set('x-amz-meta-filename', file.name)
return fetch(this.presignedPost.url, {
const response = await fetch(this.presignedPost.url, {
method: 'POST',
body: form,
})
return { ok: response.ok, objectName: this.objectName }
}
}
class MockUploader {
constructor(token) {
constructor(token, objectName) {
this.token = token
this.objectName = objectName
}
async upload(file, objectName) {
return Promise.resolve({ ok: true })
return Promise.resolve({ ok: true, objectName: this.objectName })
}
}
export const buildUploader = token => {
export const buildUploader = (token, objectName) => {
const cloudProvider = process.env.CLOUD_PROVIDER || 'mock'
if (cloudProvider === 'aws') {
return new AwsUploader(token)
return new AwsUploader(token, objectName)
} else if (cloudProvider === 'azure') {
return new AzureUploader(
process.env.AZURE_ACCOUNT_NAME,
process.env.AZURE_CONTAINER_NAME,
token
token,
objectName
)
} else {
return new MockUploader(token)
return new MockUploader(token, objectName)
}
}

View File

@ -4,10 +4,9 @@
v-bind:initial-errors='true'
v-bind:watch='false'
v-bind:portfolio-id="''"
name='pdf'
:optional='false'
v-bind:token='token'
v-bind:object-name='"object_name"'
>
<div>
<div v-show="hasAttachment" class="uploaded-file">
@ -41,6 +40,9 @@
<template v-if="uploadError">
<span class="usa-input__message">There was an error uploading your file. Please try again. If you encounter repeated problems uploading this file, please contact CCPO.</span>
</template>
<template v-if="sizeError">
<span class="usa-input__message">The file you have selected is too large. Please choose a file no larger than 64MB.</span>
</template>
<span class="usa-input__message">Test Error Message</span>

View File

@ -4,10 +4,9 @@
v-bind:initial-data='initialvalue'
v-bind:watch='false'
v-bind:portfolio-id="''"
name='pdf'
:optional='false'
v-bind:token='token'
v-bind:object-name='"object_name"'
>
<div>
<div v-show="hasAttachment" class="uploaded-file">
@ -41,6 +40,9 @@
<template v-if="uploadError">
<span class="usa-input__message">There was an error uploading your file. Please try again. If you encounter repeated problems uploading this file, please contact CCPO.</span>
</template>
<template v-if="sizeError">
<span class="usa-input__message">The file you have selected is too large. Please choose a file no larger than 64MB.</span>
</template>
</div>
</div>

View File

@ -1,6 +1,6 @@
{% from "components/icon.html" import Icon %}
{% macro UploadInput(field, show_label=False, watch=False, token="", object_name="") -%}
{% macro UploadInput(field, portfolio_id, show_label=False, watch=False) -%}
<uploadinput
inline-template
{% if not field.errors %}
@ -9,10 +9,9 @@
v-bind:initial-errors='true'
{% endif %}
v-bind:watch='{{ watch | string | lower }}'
v-bind:portfolio-id="'{{ portfolio_id }}'"
name='{{ field.name }}'
:optional='false'
v-bind:token='{{ token | tojson }}'
v-bind:object-name='"{{ object_name | string }}"'
>
<div>
<div v-show="hasAttachment" class="uploaded-file">

View File

@ -14,8 +14,7 @@
{% set next_button_text = "Next: Add TO Number" %}
{% set step = "1" %}
{% block to_builder_form_field %}
{{ TOFormStepHeader('task_orders.form.supporting_docs_header' | translate, 'task_orders.form.supporting_docs_text' | translate) }}
{{ UploadInput(form.pdf, watch=True, token=token, object_name=object_name) }}
{{ UploadInput(form.pdf, portfolio.id, watch=True) }}
{% endblock %}

View File

@ -74,15 +74,11 @@ def test_make_checkbox_input_template(checkbox_input_macro, initial_value_form):
def test_make_upload_input_template(upload_input_macro, task_order_form):
rendered_upload_macro = upload_input_macro(
task_order_form.pdf, token="token", object_name="object_name"
)
rendered_upload_macro = upload_input_macro(task_order_form.pdf)
write_template(rendered_upload_macro, "upload_input_template.html")
def test_make_upload_input_error_template(upload_input_macro, task_order_form):
task_order_form.validate()
rendered_upload_macro = upload_input_macro(
task_order_form.pdf, token="token", object_name="object_name"
)
rendered_upload_macro = upload_input_macro(task_order_form.pdf)
write_template(rendered_upload_macro, "upload_input_error_template.html")