diff --git a/.gitignore b/.gitignore index d7337889..c6c093e6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ db.sqlite3 pyrightconfig.json dist/ .vscode/ -.idea +.idea/ env/ doc/html data/ diff --git a/club/tests.py b/club/tests.py index 63b48f47..b7f3226f 100644 --- a/club/tests.py +++ b/club/tests.py @@ -33,6 +33,8 @@ from django.core.exceptions import ValidationError, NON_FIELD_ERRORS from core.models import User from club.models import Club, Membership, Mailing from club.forms import MailingForm +from sith.settings import SITH_BAR_MANAGER + # Create your tests here. @@ -43,7 +45,7 @@ class ClubTest(TestCase): self.skia = User.objects.filter(username="skia").first() self.rbatsbak = User.objects.filter(username="rbatsbak").first() self.guy = User.objects.filter(username="guy").first() - self.bdf = Club.objects.filter(unix_name="bdf").first() + self.bdf = Club.objects.filter(unix_name=SITH_BAR_MANAGER["unix_name"]).first() def test_create_add_user_to_club_from_root_ok(self): self.client.login(username="root", password="plop") @@ -390,7 +392,7 @@ class MailingFormTest(TestCase): self.rbatsbak = User.objects.filter(username="rbatsbak").first() self.krophil = User.objects.filter(username="krophil").first() self.comunity = User.objects.filter(username="comunity").first() - self.bdf = Club.objects.filter(unix_name="bdf").first() + self.bdf = Club.objects.filter(unix_name=SITH_BAR_MANAGER["unix_name"]).first() Membership( user=self.rbatsbak, club=self.bdf, diff --git a/core/static/core/js/shorten.min.js b/core/static/core/js/shorten.min.js index 5e79d040..9aeaadf1 100644 --- a/core/static/core/js/shorten.min.js +++ b/core/static/core/js/shorten.min.js @@ -19,4 +19,4 @@ // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -!function(e){e.fn.shorten=function(s){"use strict";var t={showChars:100,minHideChars:10,ellipsesText:"...",moreText:"more",lessText:"less",onLess:function(){},onMore:function(){},errMsg:null,force:!1};return s&&e.extend(t,s),e(this).data("jquery.shorten")&&!t.force?!1:(e(this).data("jquery.shorten",!0),e(document).off("click",".morelink"),e(document).on({click:function(){var s=e(this);return s.hasClass("less")?(s.removeClass("less"),s.html(t.moreText),s.parent().prev().animate({height:"0%"},function(){s.parent().prev().prev().show()}).hide("fast",function(){t.onLess()})):(s.addClass("less"),s.html(t.lessText),s.parent().prev().animate({height:"100%"},function(){s.parent().prev().prev().hide()}).show("fast",function(){t.onMore()})),!1}},".morelink"),this.each(function(){var s=e(this),n=s.html(),r=s.text().length;if(r>t.showChars+t.minHideChars){var o=n.substr(0,t.showChars);if(o.indexOf("<")>=0){for(var a=!1,i="",h=0,l=[],c=null,f=0,u=0;u<=t.showChars;f++)if("<"!=n[f]||a||(a=!0,c=n.substring(f+1,n.indexOf(">",f)),"/"==c[0]?c!="/"+l[0]?t.errMsg="ERROR en HTML: the top of the stack should be the tag that closes":l.shift():"br"!=c.toLowerCase()&&l.unshift(c)),a&&">"==n[f]&&(a=!1),a)i+=n.charAt(f);else if(u++,h<=t.showChars)i+=n.charAt(f),h++;else if(l.length>0){for(j=0;j";break}o=e("
").html(i+''+t.ellipsesText+"").html()}else o+=t.ellipsesText;var m='
'+o+'
'+n+'
'+t.moreText+"";s.html(m),s.find(".allcontent").hide(),e(".shortcontent p:last",s).css("margin-bottom",0)}}))}}(jQuery); +!function(e){e.fn.shorten=function(s){"use strict";var t={showChars:100,minHideChars:10,ellipsesText:"...",moreText:"more",lessText:"less",onLess:function(){},onMore:function(){},errMsg:null,force:!1};return s&&e.extend(t,s),(!e(this).data("jquery.shorten")||!!t.force)&&(e(this).data("jquery.shorten",!0),e(document).off("click",".morelink"),e(document).on({click:function(){var s=e(this);return s.hasClass("less")?(s.removeClass("less"),s.html(t.moreText),s.parent().prev().animate({},function(){s.parent().prev().prev().show()}).hide("fast",function(){t.onLess()})):(s.addClass("less"),s.html(t.lessText),s.parent().prev().animate({},function(){s.parent().prev().prev().hide()}).show("fast",function(){t.onMore()})),!1}},".morelink"),this.each(function(){var s=e(this),n=s.html();if(s.text().length>t.showChars+t.minHideChars){var r=n.substr(0,t.showChars);if(r.indexOf("<")>=0){for(var a=!1,o="",i=0,l=[],h=null,c=0,f=0;f<=t.showChars;c++)if("<"!=n[c]||a||(a=!0,"/"==(h=n.substring(c+1,n.indexOf(">",c)))[0]?h!="/"+l[0]?t.errMsg="ERROR en HTML: the top of the stack should be the tag that closes":l.shift():"br"!=h.toLowerCase()&&l.unshift(h)),a&&">"==n[c]&&(a=!1),a)o+=n.charAt(c);else if(f++,i<=t.showChars)o+=n.charAt(c),i++;else if(l.length>0){for(j=0;j";break}r=e("
").html(o+''+t.ellipsesText+"").html()}else r+=t.ellipsesText;var p='
'+r+'
'+n+'
'+t.moreText+"";s.html(p),s.find(".allcontent").hide(),e(".shortcontent p:last",s).css("margin-bottom",0)}}))}}(jQuery); \ No newline at end of file diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 04fac15e..a9e19f1a 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -381,6 +381,7 @@ header { color: $white-color; border-radius: 6px 6px 0 0; box-shadow: $shadow-color 0 0 15px; + align-items: center; a { flex: auto; diff --git a/core/static/election/election.scss b/core/static/election/election.scss index 0af2286d..c6f7cc26 100644 --- a/core/static/election/election.scss +++ b/core/static/election/election.scss @@ -166,14 +166,15 @@ $min_col_width: 100px; flex-direction: row; flex-wrap: wrap; justify-content: center; + width: 100%; gap: $gap; >.candidate { display: flex; flex-direction: column; align-items: center; - list-style-type: none; + width: 100%; gap: $gap; >input[type="radio"]:checked + label, @@ -186,6 +187,10 @@ $min_col_width: 100px; } } + >label { + width: 100%; + } + >label>figure, >figure { position: relative; @@ -194,17 +199,22 @@ $min_col_width: 100px; align-items: center; gap: $gap; padding: 10px; + max-width: 100%; >img { - max-width: 100%; + max-width: 100% !important; } >figcaption { + width: 100%; + max-width: inherit !important; + overflow: hidden; + h5 { margin: 0; text-align: center; } - q { + .candidate_program { margin: 5px 0; } } diff --git a/eboutic/forms.py b/eboutic/forms.py index 91356b3d..20e99797 100644 --- a/eboutic/forms.py +++ b/eboutic/forms.py @@ -26,8 +26,10 @@ import json import re import typing +from urllib.parse import unquote from django.http import HttpRequest from django.utils.translation import gettext as _ +from sentry_sdk import capture_message from eboutic.models import get_eboutic_products @@ -97,23 +99,34 @@ class BasketForm: - all the ids refer to products the user is allowed to buy - all the quantities are positive integers """ - basket = self.cookies.get("basket_items", None) + basket = unquote(self.cookies.get("basket_items", None)) if basket is None or basket in ("[]", ""): self.error_messages.add(_("You have no basket.")) return + # check that the json is not nested before parsing it to make sure - # malicious user can't ddos the server with deeply nested json + # malicious user can't DDoS the server with deeply nested json if not BasketForm.json_cookie_re.match(basket): + # As the validation of the cookie goes through a rather boring regex, + # we can regularly have to deal with subtle errors that we hadn't forecasted, + # so we explicitly lay a Sentry message capture here. + capture_message( + "Eboutic basket regex checking failed to validate basket json", + level="error", + ) self.error_messages.add(_("The request was badly formatted.")) return + try: basket = json.loads(basket) except json.JSONDecodeError: self.error_messages.add(_("The basket cookie was badly formatted.")) return + if type(basket) is not list or len(basket) == 0: self.error_messages.add(_("Your basket is empty.")) return + for item in basket: expected_keys = {"id", "quantity", "name", "unit_price"} if type(item) is not dict or set(item.keys()) != expected_keys: diff --git a/eboutic/static/eboutic/js/eboutic.js b/eboutic/static/eboutic/js/eboutic.js index 4df6b02e..8662d79f 100644 --- a/eboutic/static/eboutic/js/eboutic.js +++ b/eboutic/static/eboutic/js/eboutic.js @@ -63,7 +63,7 @@ document.addEventListener('alpine:init', () => { edit_cookies() { // a cookie survives an hour - document.cookie = "basket_items=" + JSON.stringify(this.items) + ";Max-Age=3600"; + document.cookie = "basket_items=" + encodeURIComponent(JSON.stringify(this.items)) + ";Max-Age=3600"; }, /** diff --git a/eboutic/views.py b/eboutic/views.py index 811872ec..8c816db6 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -24,9 +24,10 @@ import base64 import json -from datetime import datetime - import sentry_sdk + +from datetime import datetime +from urllib.parse import unquote from OpenSSL import crypto from django.conf import settings from django.contrib.auth.decorators import login_required @@ -104,7 +105,7 @@ class EbouticCommand(TemplateView): request.session["basket_id"] = basket.id request.session.modified = True - items = json.loads(request.COOKIES["basket_items"]) + items = json.loads(unquote(request.COOKIES["basket_items"])) items.sort(key=lambda item: item["id"]) ids = [item["id"] for item in items] quantities = [item["quantity"] for item in items] diff --git a/election/templates/election/election_detail.jinja b/election/templates/election/election_detail.jinja index 853fab78..51eeb704 100644 --- a/election/templates/election/election_detail.jinja +++ b/election/templates/election/election_detail.jinja @@ -100,7 +100,7 @@ - + {%- if role.max_choice == 1 and election.can_vote(user) %}
@@ -118,7 +118,7 @@ {%- endif %} {%- for election_list in election_lists %} - +
    {%- for candidature in election_list.candidatures.filter(role=role) %}
  • @@ -141,7 +141,7 @@ {%- endif %} {%- if user.can_edit(candidature) -%} - {% if election.is_vote_editable %} + {%- if election.is_vote_editable -%}
    {% trans %}✏️{% endtrans %} {% trans %}❌{% endtrans %} diff --git a/sith/settings.py b/sith/settings.py index 3f15ad35..1a2ed664 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -278,8 +278,8 @@ SITH_MAIN_CLUB = { # Bar managers SITH_BAR_MANAGER = { - "name": "BdF", - "unix_name": "bdf", + "name": "Pdf", + "unix_name": "pdfesti", "address": "6 Boulevard Anatole France, 90000 Belfort", } @@ -376,7 +376,7 @@ SITH_SUBSCRIPTION_LOCATIONS = [ SITH_COUNTER_BARS = [(1, "MDE"), (2, "Foyer"), (35, "La Gommette")] -SITH_COUNTER_OFFICES = {2: "BdF", 1: "AE"} +SITH_COUNTER_OFFICES = {2: "PdF", 1: "AE"} SITH_COUNTER_PAYMENT_METHOD = [ ("CHECK", _("Check")),