Skip to content

Commit

Permalink
Merge pull request #2512 from liberapay/various
Browse files Browse the repository at this point in the history
  • Loading branch information
Changaco authored Dec 14, 2024
2 parents 10a22be + 5654f06 commit bc6308b
Show file tree
Hide file tree
Showing 17 changed files with 3,700 additions and 3,133 deletions.
3 changes: 1 addition & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ updates:
- package-ecosystem: "pip"
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
day: "tuesday"
interval: "monthly"
ignore:
- dependency-name: "aspen"
- dependency-name: "botocore"
Expand Down
31 changes: 17 additions & 14 deletions js/10-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,21 +79,24 @@ Liberapay.init = function() {
$(this).children('input[type="radio"]').prop('checked', true).trigger('change');
});

$('[data-toggle="enable"]').each(function() {
if (this.tagName == 'OPTION') {
var $option = $(this);
var $select = $option.parent();
$select.on('change', function() {
var $target = $($option.data('target'));
$target.prop('disabled', !$option.prop('selected'));
$('[data-toggle="enable"], [data-toggle="disable"]').each(function() {
var enable = this.getAttribute('data-toggle') == 'enable';
var $target = $(this.getAttribute('data-target'));
var $control = $(this);
(this.tagName == 'OPTION' ? $control.parent() : $control).on('change', function() {
var disable = enable ^ ($control.prop('checked') || $control.prop('selected'));
$target.prop('disabled', disable);
$target.find('input[type="checkbox"]').each(function() {
var $subelement = $(this);
if (disable) {
$subelement.data('was-checked', $subelement.prop('checked'));
$subelement.prop('checked', false);
} else {
$subelement.prop('checked', $subelement.data('was-checked'));
}
$subelement.prop('disabled', disable);
});
} else {
var $control = $(this);
$control.on('change', function() {
var $target = $($control.data('target'));
$target.prop('disabled', !$control.prop('checked'));
});
}
});
});

$('[data-email]').one('mouseover click', function () {
Expand Down
30 changes: 30 additions & 0 deletions liberapay/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,12 +336,14 @@ def _Querystring_derive(self, **kw):
new_qs[k] = v
return '?' + urlencode(new_qs, doseq=True)
aspen.http.request.Querystring.derive = _Querystring_derive
del _Querystring_derive

if hasattr(aspen.http.request.Querystring, 'serialize'):
raise Warning('aspen.http.request.Querystring.serialize() already exists')
def _Querystring_serialize(self, **kw):
return ('?' + urlencode(self, doseq=True)) if self else ''
aspen.http.request.Querystring.serialize = _Querystring_serialize
del _Querystring_serialize

pando.http.request.Headers.__init__ = pando.http.mapping.CaseInsensitiveMapping.__init__

Expand All @@ -364,6 +366,7 @@ def _cookies(self):
self.__dict__['cookies'] = cookies
return cookies
pando.http.request.Request.cookies = property(_cookies)
del _cookies

if hasattr(pando.http.request.Request, 'queued_success_messages'):
raise Warning('pando.http.request.Request.queued_success_messages already exists')
Expand All @@ -372,6 +375,7 @@ def _queued_success_messages(self):
self._queued_success_messages = map(b64decode_s, self.qs.all('success'))
return self._queued_success_messages
pando.http.request.Request.queued_success_messages = property(_queued_success_messages)
del _queued_success_messages

if hasattr(pando.http.request.Request, 'source'):
raise Warning('pando.http.request.Request.source already exists')
Expand All @@ -388,6 +392,7 @@ def _source(self):
self.__dict__['source'] = ip_address(addr)
return self.__dict__['source']
pando.http.request.Request.source = property(_source)
del _source

if hasattr(pando.http.request.Request, 'find_input_name'):
raise Warning('pando.http.request.Request.find_input_name already exists')
Expand All @@ -397,6 +402,7 @@ def _find_input_name(self, value):
if any(map(value.__eq__, values)):
return k
pando.http.request.Request.find_input_name = _find_input_name
del _find_input_name

if hasattr(pando.Response, 'csp_allow'):
raise Warning('pando.Response.csp_allow() already exists')
Expand All @@ -411,6 +417,7 @@ def _find_input_name(self, value):
def _encode_url(url):
return maybe_encode(urlquote(url, string.punctuation))
pando.Response.encode_url = staticmethod(_encode_url)
del _encode_url

if hasattr(pando.Response, 'error'):
raise Warning('pando.Response.error() already exists')
Expand All @@ -419,6 +426,7 @@ def _error(self, code, msg=''):
self.body = msg
return self
pando.Response.error = _error
del _error

if hasattr(pando.Response, 'invalid_input'):
raise Warning('pando.Response.invalid_input() already exists')
Expand All @@ -431,6 +439,7 @@ def _invalid_input(self, input_value, input_name, input_location, code=400,
self.body = msg % (input_name, input_value, input_location)
raise self
pando.Response.invalid_input = _invalid_input
del _invalid_input

if hasattr(pando.Response, 'success'):
raise Warning('pando.Response.success() already exists')
Expand All @@ -439,6 +448,7 @@ def _success(self, code=200, msg=''):
self.body = msg
raise self
pando.Response.success = _success
del _success

if hasattr(pando.Response, 'json'):
raise Warning('pando.Response.json() already exists')
Expand All @@ -448,6 +458,7 @@ def _json(self, obj, code=200):
self.headers[b'Content-Type'] = b'application/json'
raise self
pando.Response.json = _json
del _json

if hasattr(pando.Response, 'sanitize_untrusted_url'):
raise Warning('pando.Response.sanitize_untrusted_url() already exists')
Expand All @@ -460,6 +471,7 @@ def _sanitize_untrusted_url(response, url):
# ^ this is safe because we don't accept requests with unknown hosts
return response.website.canonical_scheme + '://' + host + url
pando.Response.sanitize_untrusted_url = _sanitize_untrusted_url
del _sanitize_untrusted_url

if hasattr(pando.Response, 'redirect'):
raise Warning('pando.Response.redirect() already exists')
Expand All @@ -470,13 +482,15 @@ def _redirect(response, url, code=302, trusted_url=True):
response.headers[b'Location'] = response.encode_url(url)
raise response
pando.Response.redirect = _redirect
del _redirect

if hasattr(pando.Response, 'refresh'):
raise Warning('pando.Response.refresh() already exists')
def _refresh(response, state, **extra):
# https://en.wikipedia.org/wiki/Meta_refresh
raise response.render('simplates/refresh.spt', state, **extra)
pando.Response.refresh = _refresh
del _refresh

if hasattr(pando.Response, 'render'):
raise Warning('pando.Response.render() already exists')
Expand All @@ -491,26 +505,42 @@ def _render(response, path, state, **extra):
render_response(state, resource, response, website)
raise response
pando.Response.render = _render
del _render

if hasattr(pando.Response, 'set_cookie'):
raise Warning('pando.Response.set_cookie() already exists')
def _set_cookie(response, *args, **kw):
set_cookie(response.headers.cookie, *args, **kw)
pando.Response.set_cookie = _set_cookie
del _set_cookie

if hasattr(pando.Response, 'erase_cookie'):
raise Warning('pando.Response.erase_cookie() already exists')
def _erase_cookie(response, *args, **kw):
erase_cookie(response.headers.cookie, *args, **kw)
pando.Response.erase_cookie = _erase_cookie
del _erase_cookie

if hasattr(pando.Response, 'text'):
raise Warning('pando.Response.text already exists')
def _decode_body(self):
body = self.body
return body.decode('utf8') if isinstance(body, bytes) else body
pando.Response.text = property(_decode_body)
del _decode_body

def _str(self):
r = f"{self.code} {self._status()}"
if self.code >= 301 and self.code < 400 and b'Location' in self.headers:
r += f" (Location: {self.headers[b'Location'].decode('ascii', 'backslashreplace')})"
body = self.body
if body:
if isinstance(body, bytes):
body = body.decode('ascii', 'backslashreplace')
r += f":\n{body}"
return r
pando.Response.__str__ = _str
del _str

# Log some performance information
# ================================
Expand Down
55 changes: 12 additions & 43 deletions liberapay/models/exchange_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ def attach_stripe_payment_method(cls, participant, pm, one_off):
else:
customer_id = stripe.Customer.create(
email=participant.get_email_address(),
metadata={'participant_id': participant.id},
payment_method=pm.id,
preferred_locales=[participant.email_lang],
idempotency_key='create_customer_for_participant_%i_with_%s' % (
participant.id, pm.id
),
Expand Down Expand Up @@ -177,46 +179,6 @@ def attach_stripe_payment_method(cls, participant, pm, one_off):
assert not si.next_action, si.next_action
return route

@classmethod
def attach_stripe_source(cls, participant, source, one_off):
if source.type == 'sepa_debit':
network = 'stripe-sdd'
elif source.type == 'card':
network = 'stripe-card'
else:
raise NotImplementedError(source.type)
customer_id = cls.db.one("""
SELECT remote_user_id
FROM exchange_routes
WHERE participant = %s
AND network::text LIKE 'stripe-%%'
LIMIT 1
""", (participant.id,))
if customer_id:
customer = stripe.Customer.retrieve(customer_id)
customer.sources.create(
source=source.id,
idempotency_key='attach_%s_to_%s' % (source.id, customer_id),
)
del customer
else:
customer_id = stripe.Customer.create(
email=source.owner.email,
source=source.id,
idempotency_key='create_customer_for_participant_%i_with_%s' % (
participant.id, source.id
),
).id
source_country = getattr(getattr(source, source.type), 'country', None)
source_currency = getattr(getattr(source, source.type), 'currency', None)
route = ExchangeRoute.insert(
participant, network, source.id, source.status,
one_off=one_off, remote_user_id=customer_id,
country=source_country, currency=source_currency,
)
route.stripe_source = source
return route

def invalidate(self):
if self.network.startswith('stripe-'):
if self.address.startswith('pm_'):
Expand Down Expand Up @@ -329,12 +291,19 @@ def get_mandate_url(self):
return
elif self.network == 'stripe-sdd':
if self.address.startswith('pm_'):
mandate = stripe.Mandate.retrieve(self.mandate)
return mandate.payment_method_details.sepa_debit.url
if self.mandate:
mandate = stripe.Mandate.retrieve(self.mandate)
return mandate.payment_method_details.sepa_debit.url
else:
website.tell_sentry(Warning(
f"{self!r}.mandate is unexpectedly None"
))
return
else:
return self.stripe_source.sepa_debit.mandate_url
else:
raise NotImplementedError(self.network)
website.tell_sentry(NotImplementedError(self.network))
return

def get_partial_number(self):
if self.network == 'stripe-card':
Expand Down
19 changes: 0 additions & 19 deletions liberapay/payin/stripe.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,25 +61,6 @@ def get_partial_iban(sepa_debit):
return '%s⋯%s' % (sepa_debit.country, sepa_debit.last4)


def create_source_from_token(token_id, one_off, amount, owner_info, return_url):
token = stripe.Token.retrieve(token_id)
if token.type == 'bank_account':
source_type = 'sepa_debit'
elif token.type == 'card':
source_type = 'card'
else:
raise NotImplementedError(token.type)
return stripe.Source.create(
amount=Money_to_int(amount) if one_off and amount else None,
owner=owner_info,
redirect={'return_url': return_url},
token=token.id,
type=source_type,
usage=('single_use' if one_off and amount and source_type == 'card' else 'reusable'),
idempotency_key='create_source_from_%s' % token.id,
)


def charge(db, payin, payer, route, update_donor=True):
"""Initiate the Charge for the given payin.
Expand Down
2 changes: 1 addition & 1 deletion liberapay/security/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ def start_user_as_anon():
def authenticate_user_if_possible(csrf_token, request, response, state, user, _):
"""This signs the user in.
"""
if state.get('etag'):
if state.get('etag') or request.path.raw.startswith('/callbacks/'):
return

db = state['website'].db
Expand Down
2 changes: 2 additions & 0 deletions liberapay/testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import unittest

import html5lib
import pando
from pando.testing.client import Client
from pando.utils import utcnow
from psycopg2 import IntegrityError, InternalError
Expand Down Expand Up @@ -62,6 +63,7 @@ def __init__(self, *a, **kw):
def build_wsgi_environ(self, method, *a, **kw):
"""Extend base class to support authenticating as a certain user.
"""
kw.setdefault('HTTP_USER_AGENT', f"Pando-test-client/{pando.__version__}")

# csrf - for both anon and authenticated
csrf_token = kw.get('csrf_token', 'ThisIsATokenThatIsThirtyTwoBytes')
Expand Down
9 changes: 7 additions & 2 deletions style/base/forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,13 @@ form.buttons > .btn, span.buttons > .btn {
white-space: normal;
}

form[disabled] button {
pointer-events: none;
form:disabled, fieldset:disabled {
button {
pointer-events: none;
}
label {
color: $gray-light;
}
}

.btn-block + p {
Expand Down
Loading

0 comments on commit bc6308b

Please sign in to comment.