I am working through the ReCaptcha Enterprise tutorial and have gotten stuck with a 400 Request contains an invalid argument error. I am using Django.
I am following the instructions here https://cloud.google.com/recaptcha-enterprise/docs/create-assessment#python to authenticate the recaptcha.
I have been able to load the recaptcha on the front-end with this code
{% block content %}
<script type="text/javascript">
var onloadCallback = function() {
grecaptcha.enterprise.render('html_element', {
'sitekey' : '{{ sitekey }}',
});
};
</script>
<form action="{% url 'captcha-authentication' %}" method="post">
{% csrf_token %}
<div id="html_element"></div>
<input type="submit" value="Submit">
</form>
<script src="https://www.google.com/recaptcha/enterprise.js?onload=onloadCallback&render=explicit"
async defer>
</script>
I have verified in my view that request.POST contains the 'g-recaptcha-response' key. For my site_key, I am using the key id from https://console.cloud.google.com/security/recaptcha.
def confirm_captcha(token, site_key, recaptcha_action):
parent_project = "t-commerce-321516"
client = recaptchaenterprise_v1.RecaptchaEnterpriseServiceClient()
event = recaptchaenterprise_v1.Event()
event.site_key = site_key
event.token = token
event.expected_action = recaptcha_action
assessment = recaptchaenterprise_v1.Assessment()
assessment.event = event
request = recaptchaenterprise_v1.CreateAssessmentRequest()
request.assessment = assessment
request.parent = parent_project
# this is the line that is throwing an error
response = client.create_assessment(request)
if not response.token_properties.valid:
print("The CreateAssessment() call failed because the token was " +
"invalid for the following reasons: "
+ str(response.token_properties.invalid_reason))
else:
if response.event.expected_action == recaptcha_action:
print("The reCAPTCHA score for this token is: " +
str(response.risk_analysis.score))
return True
else:
print("The action attribute in your reCAPTCHA tag does" +
"not match the action you are expecting to score")
return False
The error from creating the client assessment is 400 Request contains an invalid argument.
Is there a way to tell why Google is angry?
I am using a checkbox on the front-end and maybe that means that the recaptcha_action argument isn't needed, but I'm not sure how to do that.
Any help or pointers is appreciated!
I think you need:
request.parent = `projects/${parent_project}`
per: CreateAssessmentRequest
Related
I have simple Flask App that takes a CSV upload, makes some changes, and streams the results back to the user's download folder as CSV.
HTML Form
<form action = {{uploader_page}} method = "POST" enctype = "multipart/form-data">
<label>CSV file</label><br>
<input type = "file" name = "input_file" required></input><br><br>
<!-- some other inputs -->
<div id = "submit_btn_container">
<input id="submit_btn" onclick = "this.form.submit(); this.disabled = true; this.value = 'Processing';" type = "submit"></input>
</div>
</form>
PYTHON
from flask import Flask, request, Response, redirect, flash, render_template
from io import BytesIO
import pandas as pd
#app.route('/uploader', methods = ['POST'])
def uploadFile():
uploaded_file = request.files['input_file']
data_df = pd.read_csv(BytesIO(uploaded_file.read()))
# do stuff
# stream the pandas df as a csv to the user download folder
return Response(data_df.to_csv(index = False),
mimetype = "text/csv",
headers = {"Content-Disposition": "attachment; filename=result.csv"})
This works great and I see the file in my downloads folder.
However, I'd like to display "Download Complete" page after it finishes.
How can I do this? Normally I use return redirect("some_url") to change pages.
Consider using send_file() or send_from_directory() for sending files.
Getting 2 responses from 1 request is not possible, but you can split the problem into chunks with the help of some JS, following this simple diagram (not very UML precise, but that's it):
this diagram refers to a previous and simpler version of the code, which was later updated after the OP asked for flash() to be called
POST to /uploader through a function called from the form by onsubmit, so that besides saving the file you can also have some logic there, like checking the response status
process the file (I did a mockup of your processing through upper())
if the server responds with 201 ("Created") then you can save the file and print "Download Complete" (I used window.document.body.innerHTML because it's only one tag and we can replace all the previous DOM; it shouldn't be used to change complex HTML)
else, if the server responds with other status codes (like 500), POST to /something-went-wrong to get the new - possibly flashed - HTML to be rendered. The POST step is not shown in the diagram.
To test the error page, make some syntax error in the processing inside upload_file(), like data_df = pd.read_csv(BytesIO(uploaded_file.aread()))
In the something-went-wrong response I addeed a CSP header to mitigate a possible malicious attack, because we can't trust the user enough.
Here's the code:
main.py
from flask import (Flask,
request,
redirect,
render_template,
send_file,
url_for,
Response, jsonify, flash, make_response)
from flask_wtf.csrf import CSRFProtect
from io import BytesIO
import pandas as pd
app = Flask(__name__)
app.secret_key = "your_secret"
csrf = CSRFProtect(app)
#app.route('/')
def index():
return render_template("index.html")
#app.route("/uploader", methods=['POST'])
def upload_file():
try:
uploaded_file = request.files['input_file']
data_df = pd.read_csv(BytesIO(uploaded_file.read()))
# do stuff
data_df["foo"] = data_df["foo"].str.upper()
# Stream buffer:
io_buffer = BytesIO()
data_df.to_csv(io_buffer)
io_buffer.seek(0)
except Exception as ex:
print(ex) # and log if needed
# Here I return the exception string just as an example! Not good in real usage.
return jsonify({"message": str(ex)}), 500
else:
return send_file(io_buffer,
download_name="result.csv",
as_attachment=True), 201
#app.route("/something-went-wrong", methods=["POST"])
def something_went_wrong():
flash(request.get_json()["message"])
response = make_response(render_template("something-went-wrong.html"), 200)
response.headers['Content-Security-Policy'] = "default-src 'self'"
return response
The form with the JS handler:
<form id="myForm" enctype="multipart/form-data" onsubmit="return submitHandler()">
<input type="hidden" name="csrfToken" value="{{ csrf_token() }}"/>
<label>CSV file</label><br>
<input type="file" id="inputFile" name="input_file" required/><br><br>
<!-- some other inputs -->
<div id="submitBtnContainer">
<input id="submitBtn" type="submit"/>
</div>
</form>
<script>
function submitHandler() {
const csrf_token = "{{ csrf_token() }}";
let formData = new FormData();
const file = document.getElementById('inputFile').files[0];
formData.append("input_file", file);
fetch("/uploader", {
method: "POST",
body: formData,
headers: {
"X-CSRFToken": csrf_token,
},
})
.then(response => {
if (response.status != 201) {
response.json().then(data => {
fetch("/something-went-wrong", {
method: "POST",
body: JSON.stringify({"message": data["message"]}),
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrf_token,
},
})
.then(response => response.text())
.then(text => {
window.document.body.innerHTML = text;
})
});
}
else {
return response.blob().then(blob => {
const file = new Blob([blob], { type: 'text/csv' });
const fileURL = URL.createObjectURL(file);
let fileLink = document.createElement('a');
fileLink.href = fileURL;
fileLink.download = "result.csv";
fileLink.click();
window.document.body.innerHTML = "<h1>Download Complete</h1>";
});
}
})
return false;
}
</script>
For completeness, my dummy csv "file.csv":
foo
bar
Here is some changes.
set window.open('') in input onclick event.
HTML Form
<form action ="/uploader" method = "POST" enctype = "multipart/form-data">
<label>CSV file</label><br>
<input type = "file" name = "input_file" required></input><br><br>
<!-- some other inputs -->
<div id = "submit_btn_container">
<input id="submit_btn" onclick = "this.form.submit(); this.disabled = true; this.value = 'Processing'; window.open('your_url');" type = "submit"></input>
</div>
</form>
You need two functions, one to deal with the processing such as uploadFile(), and another in the same app route to return render template.
When the uploadFile() function is completed: completed = True
Then, code another function which tests the global variable if completed: to return render template.
See: How can I use the same route for multiple functions in Flask
Finally, return a variable to the page with Jinja2 and use Javascript to identify if that variable exists to load your 'download completed' page by Javascript.
Python:
from flask import Flask, request, Response, redirect, flash, render_template
from io import BytesIO
import pandas as pd
completed = False
#app.route('/uploader', methods = ['POST'])
def uploadFile():
uploaded_file = request.files['input_file']
data_df = pd.read_csv(BytesIO(uploaded_file.read()))
# do stuff
# When stuff is done
global completed
completed = True
# stream the pandas df as a csv to the user download folder
return Response(data_df.to_csv(index = False),
mimetype = "text/csv",
headers = {"Content-Disposition": "attachment; filename=result.csv"})
How to load a new page: https://www.geeksforgeeks.org/how-can-a-page-be-forced-to-load-another-page-in-javascript/
Javascript conditionals: https://www.w3docs.com/learn-javascript/conditional-operators-if.html
Using Jinja2 to render a variable: https://jinja.palletsprojects.com/en/3.0.x/templates/
Also, you should really wrap your uploadFile() function with try and except to catch upload errors.
I am quite inexperienced in both HTML, JS and flask but I am working on a chatbot that able to detect sentimental analysis of the sender.
My HTML code:
<div class="bottom_wrapper clearfix">
<div class="message_input_wrapper">
<form action = "{{ url_for('reply') }}" method = "POST">
<input
class="message_input"
id="text_message"
name = "sentimental_name"
placeholder="Tell me how you feel today..."
onkeydown="if (event.keyCode == 13)document.getElementById('send').click()">
</div>
<!--div class = "send_message1" id = 'audio' onclick = "start_dictation()">
<span style="font-size: 32px; color:black;">
<i class="fas fa-microphone"></i>
</span>
</div-->
<div class="send_message" id="send" onclick="get_message()">
<!--<div class="icon"></div>-->
<div class="text">Send</div>
</div>
</form>
</div>
This is my python-flask code:
#app.route('/senti', methods = ['POST'])
def reply():
if request.method == 'POST':
message = request.form['text_message']
a = TextBlob(message).sentiment.polarity
b = TextBlob(message).sentiment.subjectivity
My js that links to the onlick =
function get_message(){
var message = document.getElementById("text_message").value;
var json_data = {"msg":message}
var sender = JSON.stringify(json_data)
console.log(sender)
console.log(message);
insert_chat('me',message);
interact(sender);
}
Console log:
POST http://127.0.0.1:5000/senti 500 (INTERNAL SERVER ERROR)
send # jquery-3.4.1.js:9837
ajax # jquery-3.4.1.js:9434
interact # chat.js:34
get_message # chat.js:55
onclick # chat:58
It seems really simple but it is like I miss something. Thank you so much!
You would have to use "sentimental_name" in
request.form["sentimental_name"]
because you have
But it uses JavaScript function get_message() to get data when you click ENTER
<input ... onkeydown="if (event.keyCode == 13)document.getElementById('send').click()">
<div class="send_message" id="send" onclick="get_message()">
and converts to JSON with field "msg" so it sends it as data or json, not form.
function get_message(){
var message = document.getElementById("text_message").value;
var json_data = {"msg":message}
var sender = JSON.stringify(json_data)
console.log(sender)
console.log(message);
insert_chat('me',message);
interact(sender);
In flask reply() you can check this using:
print(request.args)
print(request.data)
print(request.form)
print(request.json)
JavaScript may expect that reply() returns also JSON - ie.
return jsonify(list_or_dictionary).
In JavaScript I see interact(sender); so you would have to find this function and see what it sends and what result it may expect.
BTW: you can also use requests.data.get("msg") and request.form.get("msg") instead of ["msg"] becauses .get() returns None when it can't find "msg" and you can use if not message: to catch this problem. And ["msg"] raises error when there is no "msg" and you would have to use try:/except: to catch it.
First of all I know there are many similar threads, I red all of them and the S3 Docu (please dont close this thread). The fix is everywhere the same:
Simply change the sugnature_version to v4, because eu central was created after 2014 and does not support v2 anymore.
I have tried every syntax now and I am still getting the error.
session = boto3.Session(
aws_access_key_id=app.config['MY_AWS_ID'],
aws_secret_access_key=app.config['MY_AWS_SECRET'],
region_name='eu-central-1'
)
s3 = session.client('s3', config=Config(signature_version='s3v4'))
presigned_post = s3.generate_presigned_post(
Bucket = 'mybucket',
Key = 'videos/' + file_name,
Fields = {"acl": "public-read", "Content-Type": file_type},
Conditions = [
{"acl": "public-read"},
{"Content-Type": file_type}
],
ExpiresIn = 3600
)
I have tried changing it everywhere. I also downgraded my boto3 installation to versions 1.6.6 and 1.4.4, did not work aswell. I upgarded it back to the newest version, which is boto3==1.7.26
The Error:
InvalidRequest
The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.
Every thread suggests the same fix, probably it does not work because I use Python / Flask. Something has to be done in a different way?
I am trying to upload huge video files via clientside directly to S3, therefore I need to sign the request.
EDIT
I thought maybe there is an SSL issue. I am testing everything on localhost and the default option for use_ssl is true.
I tried to upload this version to the live site (there is SSL enabled). Did not work, still the same error.
I also tried to use use_ssl = False on localhost, still the same error.
The problem was in the HTML and how I named the input fields. I took an example from an older tutorial, but you have to build your form the way it is explained here by amazon
I have used every input they provided. I have checked my response, which was generated by my sign_s3 function and pupulated all corresponding fields in the form.
Here is my sign function:
# Sign request for direct file upload through client for video
#app.route('/sign_s3/<path:file_name_data>/<path:file_type_data>/<up_type>', methods=["GET", "POST"])
#login_required
#check_confirmed
def sign_s3(file_name_data, file_type_data, up_type):
if "localhost" in request.url_root:
if up_type == "profile_vid":
file_name = str(current_user.id) + get_random_code(5) + "local-profil-video." + file_name_data.split(".")[-1]
else:
file_name = str(current_user.id) + str(randint(1,100)) + "local-post-video-temp." + file_name_data.split(".")[-1]
else:
if up_type == "profile_vid":
file_name = str(current_user.id) + get_random_code(5) + "-profil-video." + file_name_data.split(".")[-1]
else:
file_name = str(current_user.id) + str(randint(1,100)) + "-post-video-temp." + file_name_data.split(".")[-1]
file_type = file_type_data
session = boto3.Session(
aws_access_key_id=app.config['MY_AWS_ID'],
aws_secret_access_key=app.config['MY_AWS_SECRET'],
region_name='eu-central-1'
)
s3 = session.client('s3', config=Config(signature_version='s3v4'))
presigned_post = s3.generate_presigned_post(
Bucket = 'mybucket',
Key = 'videos/' + file_name,
Fields = {"acl": "public-read", "Content-Type": file_type},
Conditions = [
{"acl": "public-read"},
{"Content-Type": file_type}
],
ExpiresIn = 3600
)
if up_type == "profile_vid":
if current_user.profile_video != None:
delete_file_from_aws("videos/", current_user.profile_video)
setattr(current_user, "profile_video", file_name)
else:
print ('post video has been uploaded, no need to delete or set here')
db_session.commit()
return json.dumps({'data': presigned_post, 'url': 'https://s3.eu-central-1.amazonaws.com/mybucket/' + 'videos/' + file_name, 'created_file_name' : file_name})
I looked at the generated response in the dev console, there I had these values:
The HTML form I used is here, all the input fields which are uncommented have not been used by me. I simply include them as amazon shows them all in their example:
<form id="direct_s3_profile_video_form" class="form-horizontal" role="form" method="POST" enctype="multipart/form-data">
<!-- Content-Type: -->
<input type="hidden" name="Content-Type">
<!-- <input type="hidden" name="x-amz-meta-uuid"> -->
<!-- <input type="hidden" name="x-amz-server-side-encryption"> -->
<input type="hidden" name="X-Amz-Credential">
<input type="hidden" name="X-Amz-Algorithm">
<input type="hidden" name="X-Amz-Date">
<!-- Tags for File: -->
<!-- <input type="hidden" name="x-amz-meta-tag"> -->
<input type="hidden" name="Policy">
<input type="hidden" name="X-Amz-Signature">
<input id="NEW_fileupload_video" type="file" name="file" accept="video/*">
<button type="submit"> Upload </button>
</form>
Also note here that the file input must be at the bottom because:
elements after this will be ignored
In my case the values for the form were dynamically created, so I populated the form with JS:
$('#direct_s3_profile_video_form').find('input[name="key"]').val(response_json_data.data.fields['key']);
$('#direct_s3_profile_video_form').find('input[name="acl"]').val(response_json_data.data.fields['acl']);
$('#direct_s3_profile_video_form').find('input[name="Content-Type"]').val(response_json_data.data.fields['Content-Type']);
$('#direct_s3_profile_video_form').find('input[name="X-Amz-Credential"]').val(response_json_data.data.fields['x-amz-credential']);
$('#direct_s3_profile_video_form').find('input[name="X-Amz-Algorithm"]').val(response_json_data.data.fields['x-amz-algorithm']);
$('#direct_s3_profile_video_form').find('input[name="X-Amz-Date"]').val(response_json_data.data.fields['x-amz-date']);
$('#direct_s3_profile_video_form').find('input[name="Policy"]').val(response_json_data.data.fields['policy']);
$('#direct_s3_profile_video_form').find('input[name="X-Amz-Signature"]').val(response_json_data.data.fields['x-amz-signature']);
$('#direct_s3_profile_video_form').attr('action', 'https://mybucket.s3.amazonaws.com');
I have a problem submitting a HIT to Amazon Mechanical Turk sandbox.
I'm using the following code to submit a HIT:
external_content = """"
<ExternalQuestion xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd">
<ExternalURL>https://MY_HOST_GOES_HERE/</ExternalURL>
<FrameHeight>400</FrameHeight>
</ExternalQuestion>
"""
import boto3
import os
region_name = 'us-east-1'
aws_access_key_id = 'MYKEY'
aws_secret_access_key = 'MYSECRETKEY'
endpoint_url = 'https://mturk-requester-sandbox.us-east-1.amazonaws.com'
# Uncomment this line to use in production
# endpoint_url = 'https://mturk-requester.us-east-1.amazonaws.com'
client = boto3.client('mturk',
endpoint_url=endpoint_url,
region_name=region_name,
aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key,
)
# This will return $10,000.00 in the MTurk Developer Sandbox
print(client.get_account_balance()['AvailableBalance'])
response = client.create_hit(Question=external_content,
LifetimeInSeconds=60 * 60 * 24,
Title="Answer a simple question",
Description="Help research a topic",
Keywords="question, answer, research",
AssignmentDurationInSeconds=120,
Reward='0.05')
# The response included several helpful fields
hit_group_id = response['HIT']['HITGroupId']
hit_id = response['HIT']['HITId']
# Let's construct a URL to access the HIT
sb_path = "https://workersandbox.mturk.com/mturk/preview?groupId={}"
hit_url = sb_path.format(hit_group_id)
print(hit_url)
The error message I get is:
botocore.exceptions.ClientError: An error occurred (ParameterValidationError) when calling the CreateHIT operation: There was an error parsing the XML question or answer data in your request. Please make sure the data is well-formed and validates against the appropriate schema. Details: Content is not allowed in prolog. (1493572622889 s)
What might be the reason here? The xml fully agrees with xml schema located on amazon servers.
The html returned by the external host is:
<!DOCTYPE html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/>
<script src='https://s3.amazonaws.com/mturk-public/externalHIT_v1.js' type='text/javascript'></script>
</head>
<body>
<!-- HTML to handle creating the HIT form -->
<form name='mturk_form' method='post' id='mturk_form' action='https://workersandbox.mturk.com/mturk/externalSubmit'>
<input type='hidden' value='' name='assignmentId' id='assignmentId'/>
<!-- This is where you define your question(s) -->
<h1>Please name the company that created the iPhone</h1>
<p><textarea name='answer' rows=3 cols=80></textarea></p>
<!-- HTML to handle submitting the HIT -->
<p><input type='submit' id='submitButton' value='Submit' /></p></form>
<script language='Javascript'>turkSetAssignmentID();</script>
</body>
</html>
Thank you
This message "Details: Content is not allowed in prolog." is the clue. It turns out that what this is saying is that you can't have content outside of where it is expected. This is what usually happens when a junk character (think smart-quotes or non-printable ASCII value) appears in there. These can be a real pain in the butt to diagnose.
In your case, it's a little easier to debug but still just as frustrating. Check out this line:
external_content = """"
It turns out that Python only needs three quotes (""") in order to acknowledge a multi-line string definition. Thus your fourth " was actually rendering as part of the XML. Change that line to this:
external_content = """
And you're golden. I just tested it and it works. Sorry for all the frustration, but hopefully this unblocks you. Happy Sunday!
I'm having some issues getting AJAX communication working using the Bottle framework. This is my first time using AJAX, so it's likely I just have the basics wrong. Hopefully a Bottle/AJAX guru can point this novice in the right direction. Here is the code I'm using:
#!/usr/bin/env python
from bottle import route, request, run, get
# Form constructor route
#route('/form')
def construct_form():
return '''
<html>
<head>
<script type="text/javascript">
function loadXMLDoc()
{
xmlhttp = new XMLHTTPRequest();
xmlhttp.onReadyStateChange = function()
{
if(xmlhttp.readyState == 4 && xmlhttp.status == 200)
{
document.getElementById("responseDiv").innerHTML = xmlhttp.responseText;
}
}
xmlhttp.open("GET", "/ajax", true);
xmlhttp.send();
}
</script>
</head>
<body>
<form>
<input name="username" type="text"/>
<input type="button" value="Submit" onclick="loadXMLDoc()"/>
</form>
<div id="responseDiv">Change this text to what you type in the box above.</div>
</body>
</html>
'''
# Server response generator
#route('/ajax', method='GET')
def ajaxtest():
inputname = request.forms.username
if inputname:
return 'You typed %s.' % (inputname)
return "You didn't type anything."
run(host = 'localhost', port = '8080')
There are a few issues here.
Javascript is case sensitive. XMLHTTPRequest should be XMLHttpRequest. You should have seen an error about this in your Javascript console.
onReadyStateChange should be onreadystatechange.
If you fix the above two issues your AJAX call will work, but you will only ever get the 'You didn't type anything.' response. This is because you are using GET. You need to change your code so the form values are posted using the POST method.
Also, why aren't you using jQuery to do AJAX? It would make your life much easier. :)