Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various #2512

Merged
merged 10 commits into from
Dec 14, 2024
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 @@
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 @@
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 @@
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 @@
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 @@
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 _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 @@
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 @@
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 @@
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 @@
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 @@
# ^ 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 @@
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 @@
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

Check warning on line 541 in liberapay/main.py

View check run for this annotation

Codecov / codecov/patch

liberapay/main.py#L533-L541

Added lines #L533 - L541 were not covered by tests
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 @@
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 @@
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 @@
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(

Check warning on line 298 in liberapay/models/exchange_route.py

View check run for this annotation

Codecov / codecov/patch

liberapay/models/exchange_route.py#L298

Added line #L298 was not covered by tests
f"{self!r}.mandate is unexpectedly None"
))
return

Check warning on line 301 in liberapay/models/exchange_route.py

View check run for this annotation

Codecov / codecov/patch

liberapay/models/exchange_route.py#L301

Added line #L301 was not covered by tests
else:
return self.stripe_source.sepa_debit.mandate_url
else:
raise NotImplementedError(self.network)
website.tell_sentry(NotImplementedError(self.network))
return

Check warning on line 306 in liberapay/models/exchange_route.py

View check run for this annotation

Codecov / codecov/patch

liberapay/models/exchange_route.py#L305-L306

Added lines #L305 - L306 were not covered by tests

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
Loading