i'm having a hard time understanding why i can't load the "/ticket" page properly, but i can load the "/" fine. I should have the /ticket page running and able to type the information under username and email but i'm missing something.
I'm running Python 3.10.4
"localhost:8080/"
"localhost:8080/ticket"
import os
import bottle
import base64
import traceback
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from binascii import hexlify
from html import escape
from bottle import route, request, get, post, response
page = '''
<html>
<head><title>Welcome</title></head>
<body>
<p><h2>Welcome, %s!</h2></p>
<p><h3>There are %s tickets left</h3></p>
<p>%s</p>
</body>
</html>
'''
key = os.urandom(32)
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
def get_ticket(username, email, amount):
encryptor = cipher.encryptor()
padder = padding.PKCS7(128).padder()
message = padder.update("%s&%s&%d" % (username, email, amount)) + padder.finalize()
ct = encryptor.update(message) + encryptor.finalize()
return base64.b64encode(ct)
#bottle.route('/', method=('GET', 'POST'))
def default():
decryptor = cipher.decryptor()
unpadder = padding.PKCS7(128).unpadder()
try:
ticket = bottle.request.get_cookie('ticket')
if ticket:
message = decryptor.update(base64.b64decode(ticket)) + decryptor.finalize()
username, email, amount = (unpadder.update(message) + unpadder.finalize()).split('&')
return page % (escape(username), escape(amount),
'Already done' if amount == '1' else
'Go ahead')
else:
return '<html><body><p><a href=/ticket>Fill in the forms</a></p></body><html>\n'
except Exception as e:
print(traceback.format_exc())
raise bottle.HTTPError(status=401, body=e.message)
#bottle.post('/ticket', ['GET', 'POST'])
def ticket():
username = bottle.request.forms.get('username')
email = bottle.request.forms.get('email')
if username and email:
ticket = get_ticket(username, email, 1)
bottle.response.set_cookie('ticket', ticket)
return '<html><body><p>Thank you!</p></body></html>\n'
raise bottle.HTTPError(status=400, body='Please fill in the form')
bottle.run(host='localhost', port=8080)
The reason this code hits an error is that you never actually serve a form for the user to fill in (and thus your code correctly errors). I don't really understand your flow at all, but I think it's supposed to look something like this:
user navigates to /
if user doesn't have a ticket, user is sent to a login page
user logs in and gets a ticket
user goes back to / and requests it, this time with a ticket (authentication cookie) in the get request.
Mind you I don't understand what these tickets are, as they appear to be a hashed form of whatever the user submits and the constant 1, but I guess there's more logic coming along later.
Currently there are quite a few problems with the setup:
/ accepts post requests, but never does anything with them. It also doesn't make sense for it to accept post requests, since POST is to send data to a server.
your /ticket endpoint would do what you want if you sent it a form, but you never serve a form for the user to fill in.
your /ticket endpoint needs to return one thing if you make a GET request to it, another completely different thing if you make a POST request. This is possible, but it's a bad way to build these endpoints in general.
after getting the cookie the browser is simply going to throw it away. If you want to do cookie-based authentication, you need to store the cookie and handle it (normally you use this with JS in the webbrowser, and take care to add it to all requests).
If we add a login endpoint:
#bottle.route("/login")
def login():
return """<html><body>
<form action="./ticket" method="POST">
Username: <input type="text" name="username"><br>
Email: <input type="text" name="email"><br>
<input type="submit">
</form>
</body></html>"""
And edit the previous href to point us to /login, we now get a form. Filling that form in and pressing 'submit' correctly sends the data to the /ticket endpoint, calling the code to get the ticket. Unfortunately that fails for unrelated reasons (your hashing needs bytes, not strings) but that's a different question.
I suggest you have a think about the overall flow of this application---what are these tickets for, and where is the user supposed to end up? Regardless you will need to serve some kind of login form at some point. You probably want this to be a static file, by the way, and served as such---see the bottle docs.
Related
I am implementing a paypal server side payment button. I managed to create the order and capture it.
Now I would like to render a success template when the order is captured, but I don't know where, because here im returning the json response but how can i render a template when the payment is successful? How should this be done?
def capture(request, order_id, mail):
if request.method == "POST":
capture_order = OrdersCaptureRequest(order_id)
environment = SandboxEnvironment(client_id=value, client_secret=value)
client = PayPalHttpClient(environment)
response = client.execute(capture_order)
data = response.result.__dict__['_dict']
letter = Letter.objects.filter(mail=mail).first()
return JsonResponse(data)
else:
return JsonResponse({'details': "invalid request"})
Here is the best front-end sample for a server-side integration: https://developer.paypal.com/demo/checkout/#/pattern/server
This capture sample correctly handles the 3 cases of retrying, showing an error, and showing a success message.
For your implementation, the success case can be to manipulate the DOM to show whatever message / "template" you want to appear.
(You could even use actions.redirect() if you must, although redirects are a poor design choice and to be avoided as much as possible.)
I have two different pages, one (A) that displays data taken from a model object, and one (B) that changes its fields.
I would like that when the post data is sent from B to the server, the server changes the values in A.
What is the best way to do it?
This example could work for me but it's in PHP... there is a way to replicate it whit Python?
https://www.w3schools.com/html/html5_serversentevents.asp
This is working example from w3schools in Django:
template
<!DOCTYPE html>
<html>
<body>
<h1>Getting server updates</h1>
<div id="result"></div>
<script>
if(typeof(EventSource) !== "undefined") {
var source = new EventSource("stream/");
source.onmessage = function(event) {
document.getElementById("result").innerHTML += event.data + "<br>";
};
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
}
</script>
</body>
</html>
views
import datetime
import time
from django.http import StreamingHttpResponse
def stream(request):
def event_stream():
while True:
time.sleep(3)
yield 'data: The server time is: %s\n\n' % datetime.datetime.now()
return StreamingHttpResponse(event_stream(), content_type='text/event-stream')
urls
urlpatterns = [
path('stream/', views.stream, name='stream')
]
Update:
If you want to manage your notifications you can create the model like:
from django.db import models
class Notification(models.Model):
text = models.CharField(max_length=200)
user = models.ForeignKey(User, on_delete=models.CASCADE)
sent = models.BooleanField(default=False)
Then create the view that is looking for the first unsent notification and sends it:
#login_required
def stream(request):
def event_stream():
while True:
time.sleep(3)
notification = Notification.objects.filter(
sent=False, user=request.user
).first()
text = ''
if notification:
text = notification.text
notification.sent = True
notification.save()
yield 'data: %s\n\n' % text
return StreamingHttpResponse(event_stream(), content_type='text/event-stream')
And the send_notification function that creates an entry in the Notification model (just call this function from anywhere in your code):
def send_notification(user, text):
Notification.objects.create(
user=user, text=text
)
After reading this, I think I understood the whole thing (please comment if I’m wrong).
Django does NOT natively support keep-alive connections.
This means, when the client gets the message from the server, the connection is immediately closed after (like any classic HTTP request/response cycle).
What’s different with text/event-stream request, is that the client automatically tries to reconnect to the server every second (the length can be changed with a retry parameter).
Unfortunately, it seems using SSE in that case has no interest since it has the same con’s that polling (ie. a request/response cycle occurs each X seconds).
As expected and mentioned in other answers, I would need django-channels to create a persistent connection that prevent HTTP request/response overheads and ensure the message is sent immediately.
As mentioned in other answers, you will need to use Django Channels to properly handle asynchronous communication without tying up threads.
For an example, see the django-eventstream library which uses Channels to implement SSE.
The below code successfully generates a token and sends a link to the user's inbox for confirmation. But when the user clicks on it, Flask is not recognizing the token it just created. Here is the error message:
"Got exception from ts.loads: 404 Not Found: The requested URL was not
found on the server. If you entered the URL manually please check
your spelling and try again."
The bottom line is that this is what should execute if I could make the confirmation procedure work properly:
return redirect(url_for('tutorials'))
But, as you can piece together by noting the error message that is coming out of #app.errorhandler(404), something is going wrong. I'm really stuck. These tests are being done way before the max_age of 86400 seconds is reached. Any help would be much appreciated!!!
from itsdangerous import URLSafeTimedSerializer
ts = URLSafeTimedSerializer(SECRET_KEY, salt='email-confirm-key')
#app.route('/signup', methods=['GET', 'POST'])
def signup():
#skipping unrelated lines of code
token = ts.dumps(form.email.data, salt='email-confirm-key')
subject = 'subject goes here'
msg = Message(subject=subject, sender='name#email.com', recipients=form.email.data.split())
link = url_for('confirm_email', token=token, _external=True)
msg.html = render_template("email_confirmationemail.html", link=link, name=request.form['first_name'])
with app.app_context():
mail.send(msg)
return redirect(url_for('checkyouremail'))
#app.route('/confirmemail/<token>')
def confirm_email(token):
try:
email = ts.loads(token, salt='email-confirm-key', max_age=86400)
#skipping unrelated lines of code
return redirect(url_for('tutorials'))
#app.errorhandler(404)
def not_found(e):
print('Got exception from ts.loads: {}'.format(e))
return render_template('404.html')
In models.py, my __init__ method for the User class has this line:
self.email = email.lower()
When users create a profile on a phone, their email address often starts with an uppercase letter.
So I just needed to change
token = ts.dumps(form.email.data, salt='email-confirm-key')
to
token = ts.dumps(form.email.data.lower(), salt='email-confirm-key')
so that the email held in the token matched with the email in the database when the user clicked on the confirmation link I sent them. (In short, adding .lower() as shown above in my call do dumps solved my problem).
for some reason I'm unable to retrieve a secure cookie I've set with tornado. Using firebug I can see the cookie and it's expiration date, but when I try to print or retrieve it, it keeps coming up as None. Is there some way I'm invalidating it that I can't see. This is the code I'm using:
class loginHandler(tornado.web.RequestHandler):
def post(self):
# first type of request made to this page is a post
userEmail = self.get_argument("username")
password = self.get_argument("password")
deviceType = self.get_argument("deviceType")
# get some info from the client header
userIp = self.request.headers['X-Real-Ip']
userAgentInfo = self.request.headers['User-Agent']
result = pumpkinsdb.loginUser(userEmail, password, deviceType, userIp, userAgentInfo)
if result == None:
self.redirect("/")
else:
fullname = pumpkinsdb.pumpkinsdb_user['fullName']
this_page_title = fullname if fullname else pumpkinsdb.pumpkinsdb_user['userEmail']
# successful login set up user's cookies
# self.set_secure_cookie("memberId", str(user['memberId']), expires_days=0.1, secure=True, httponly=True)
self.set_secure_cookie("memberId", str(pumpkinsdb.pumpkinsdb_user['memberId']))
self.write(str(self.get_secure_cookie("memberId")))
time_now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print "{} [{}::get] pid[{}] login requested for user: [{}] from [{}] using [{}]".format(
time_now, self.__class__.__name__, os.getpid(), pumpkinsdb.pumpkinsdb_user['emailAddress'],
pumpkinsdb.pumpkinsdb_user['userIp'], pumpkinsdb.pumpkinsdb_user['userAgentInfo'])
self.render('calendar.html', title = this_page_title)
def get(self):
validSession = self.get_secure_cookie("memberId")
if validSession:
this_page_title = pumpkinsdb.pumpkinsdb_user['fullName']
self.render('calendar.html', title = this_page_title)
else:
print self.get_secure_cookie("memberId")
self.write(str(validSession))
Is your cookie secret changing somehow when you restart the server? If the cookie secret changes, all existing cookies are invalidated. Note that even though the cookie secret should be randomly generated, this doesn't mean you should have something like cookie_secret=os.urandom(16) in your code, because that will generate a new secret every time. Instead, you need to call os.urandom once and save its output somewhere (keep it safe and private, like your TLS keys).
so basically the problem was I had four tornado processes running behind nginx and for each tornado process I generated a unique random string with:
cookie_secret = base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
obviously that was my problem because each tornado process had a different secret so when i tried to read it tornado thought it was invalid.
The key is to generate a unique random string but then store it somewhere secure such as in your options:
define(cookie_secret, "934893012jer9834jkkx;#$592920231####")
or whatever string you deem fit.
Thank you to everyone that responded. sorry about that.
IMDB allows you to create a watchlist, which can be easily exported in CSV format. I would like to be able to do this programmatically using Python.
The problem I am facing is that I can obviously not access it without logging in. If I try to access it directly I get the 404 response. So I figure, I will need to log in first, and attempt to get fetch the data afterwards.
Looking at the HTML code, I find that at least one of the login forms has these inputs:
<input type="hidden" name="49e6c" value="898d" />
<input id="usernameprompt" type="text" size="20" name="login" value="" >
<input id="passwordprompt" type="password" size="20" name="password">
<input type="submit" class="linkasbutton-primary" value="Login!">
The values in the first input have not changed yet, so I figure that is not yet an issue.
The location of the form is at https://secure.imdb.com/register-imdb/login?ref_=nv_usr_lgin_3, designated IMDBLOGIN in the code.
Now I would like to use this information to log in, using the name of each input as key and value as value:
form = OrderedDict([("49e6c", "898d"), ("login", username), ("password", password), ("submit", "submit")])
url = urlsplit(IMDBLOGIN)
try:
conn = httplib.HTTPSConnection(url.netloc)
request = url.path + "?" + url.query + "&" + urlencode(form)
conn.putrequest("POST", request)
conn.putheader("Content-Type", "application/x-www-form-urlencoded")
conn.endheaders()
loggedin = conn.getresponse()
logger.debug("Log in first %s %s %s", loggedin.status, loggedin.reason, loggedin.getheaders())
except:
logger.exception("Can't log in via HTTPS")
finally:
conn.close()
The problem is that I am unsure what to do with the submit input. The result I am now getting is 400 (Bad request).
Furthermore I don't know if I am on the right track anyway. Any considerations are welcome!
Your best bet is probably to use a inspector, like Chrome's "F12" developer tools, to take a look at the request that IMDB sends to itself in response to the user filling out the login form. When I did this, I noticed similar form values to the ones you had, though there are also cookies and other information that IMDB may be relying on to allow the authentication to complete. This is, of course, a notoriously fragile type of code.
If this is just for your own personal use, you could also consider simply signing in to IMDB from your browser, then finding the cookies that are set in your browser session and use them in your requests. This is the technique used by IMDbPY, which you might consider looking at.
It turns out that it is not actually that difficult. To make things easier I have switched from httplib to urllib2.
IMDBLOGIN = "https://secure.imdb.com/register/login?ref_=nv_usr_lgin_3"
form = OrderedDict([("49e6c", "3478"), ("login", self.username), ("password", self.password)])
cj = CookieJar()
opener = build_opener(HTTPHandler(), HTTPSHandler(), HTTPErrorProcessor(),
HTTPRedirectHandler(), HTTPCookieProcessor(cj))
params = urlencode(form)
response = opener.open(IMDBLOGIN, params)
# cookies automatically sent
response2 = opener.open(csv_url)
content = response2.read()
Basically, I do not need to add the submit input, and so far I have not been able to figure out what purpose the first input has, because I seem to be able to fill in any value. (Though not extensively tested)
After logging in I make sure that I keep the cookie for the next request, and retrieve the file.