diff options
author | MS <ms@taler.net> | 2023-04-28 18:27:47 +0200 |
---|---|---|
committer | MS <ms@taler.net> | 2023-04-28 18:27:47 +0200 |
commit | 943100018fc087e3293e93722c2ebc183607bb2f (patch) | |
tree | 6deb8da37827cf7d79d15f8186b536833f30d7cf | |
parent | 6fa8d80b8d6873935ab14956b4ba62932bdd2a14 (diff) | |
download | taler-merchant-demos-943100018fc087e3293e93722c2ebc183607bb2f.tar.gz taler-merchant-demos-943100018fc087e3293e93722c2ebc183607bb2f.tar.bz2 taler-merchant-demos-943100018fc087e3293e93722c2ebc183607bb2f.zip |
Addressing #6860
-rw-r--r-- | talermerchantdemos/blog/blog.py | 91 |
1 files changed, 77 insertions, 14 deletions
diff --git a/talermerchantdemos/blog/blog.py b/talermerchantdemos/blog/blog.py index 263c7cf..3074f91 100644 --- a/talermerchantdemos/blog/blog.py +++ b/talermerchantdemos/blog/blog.py @@ -46,6 +46,25 @@ from talermerchantdemos.httpcommon import ( get_locale, ) +def req_add_cookie_check(): + current_url = list(urllib.parse.urlparse(flask.request.base_url)) + args_writable = flask.request.args.copy() + # Adding the used param. + args_writable.update(dict(expect_state="yes")) + current_url[4] = urllib.parse.urlencode(args_writable) + # Stringify the result. + return urllib.parse.urlunparse(current_url) + + +def req_rm_cookie_check(): + current_url = list(urllib.parse.urlparse(flask.request.base_url)) + args_writable = flask.request.args.copy() + # Stripping the used param. + args_writable.pop("expect_state") + current_url[4] = urllib.parse.urlencode(args_writable) + # Stringify the result. + return urllib.parse.urlunparse(current_url) + def err_abort(abort_status_code, **params): """ @@ -351,22 +370,42 @@ def post_order(article_name, lang): def article(article_name, lang=None, data=None): # We use an explicit session ID so that each payment (or payment replay) is # bound to a browser. This forces re-play and prevents sharing the article - # by just sharing the URL. + # by just sharing the URL. flask.session is transparently set by Flask, when + # the user agent supports cookies. All the key-value pairs associated to it + # are only stored in the server. session_id = flask.session.get("session_id") order_id = flask.request.cookies.get("order_id") + # Check if cookies are expected for this request. + maybe_expect_state = request.args.get("expect_state") + if maybe_expect_state == "yes": + if not order_id: + error_page = flask.render_template( + "blog-error.html.j2", + page_title=gettext("GNU Taler Demo: Error"), + message=gettext("Please enable cookies."), + ) + return flask.make_response(error_page, 412) + # Cookies enabled and found, redirect once again by + # stripping the "expect_state" parameter. + return flask.redirect(req_rm_cookie_check(), code=302) + + # Whenever we set one session ID, we nullify the order ID (regardless + # of it being found in the cookies or not). if not session_id: session_id = flask.session["session_id"] = str(uuid.uuid4()) + # This command ensures that fresh sessions are tied to fresh order IDs. order_id = None - ## - # First-timer; generate order first. + + # Merchant backend wasn't asked already to generate the order under + # this session, so doing it now. if not order_id: if not lang: err_abort(403, message=gettext("Direct access forbidden")) order_resp = post_order(article_name, lang) order_id = order_resp["order_id"] - # Ask the backend for the status of the payment + # Ask the backend for the status of the payment. pay_status = backend_get( BACKEND_URL, f"private/orders/{order_id}", @@ -374,6 +413,7 @@ def article(article_name, lang=None, data=None): auth_token=APIKEY, ) order_status = pay_status.get("order_status") + if order_status == "claimed": if not lang: err_abort(403, message=gettext("Direct access forbidden")) @@ -387,7 +427,6 @@ def article(article_name, lang=None, data=None): auth_token=APIKEY, ) order_status = pay_status.get("order_status") - # This really must be 'unpaid' now... if order_status == "paid": refunded = pay_status["refunded"] @@ -399,15 +438,28 @@ def article(article_name, lang=None, data=None): order_id=order_id, ) response = render_article( - article_name, lang, data, order_id, refundable(pay_status) + article_name, + lang, + data, + order_id, + refundable(pay_status) ) return response - # Check if the customer came on this page via the - # re-purchase detection mechanism + # Checking repurchase case. That happens when the client + # visits this page in the same session where the article + # was paid already. ai = pay_status.get("already_paid_order_id") au = pay_status.get("already_paid_fulfillment_url") + + # If the condition below holds, then the browser gets + # the paid order ID in the cookies, and the protocol starts + # again from that associated fulfillment URL (likely to be + # this page). if ai is not None and au is not None: + # NOT appending the "expect_state" URI param to check + # cookies, because at this point the user agent DID show + # a session, so their cookies must be enabled. response = flask.redirect(au) response.set_cookie( "order_id", @@ -421,13 +473,23 @@ def article(article_name, lang=None, data=None): ) return response - # Redirect the browser to a page where the wallet can - # run the payment protocol. + # No claim, nor repurchase, nor paid statuses were found so far. + # The actual payment protocol needs to be run, and the following + # URL instructs the browser+wallet to run it. redirect_url = pay_status["order_status_url"] - LOGGER.info( - "Redirecting (with order_id cookies) to", redirect_url - ) - response = flask.redirect(redirect_url) + # The order_id cookies MIGHT already be set, if the browser + # passed successfully the cookie support check (see at the very + # top of this function), and got redirected here thereafter. + # In this case, we can now redirect the browser to the URL that + # triggers the wallet. + + if flask.request.cookies.get("order_id") and order_status != "paid": + LOGGER.info("Redirecting (with order_id cookies) to", redirect_url) + return flask.redirect(redirect_url) + + # Order ID is fresh, thus setting the cookies now, and + # redirecting the client to the cookie support check. + response = flask.redirect(req_add_cookie_check()) response.set_cookie( "order_id", order_id, @@ -438,6 +500,7 @@ def article(article_name, lang=None, data=None): order_id, path=urllib.parse.quote(url_for ('index') + f"{lang}/essay/{article_name}") ) + return response |