From 32246a8dc7f24103a252f39e46a8d9476d880e38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Borgsm=C3=BCller?= Date: Mon, 23 Oct 2023 21:50:50 +0200 Subject: [PATCH 1/3] Fix exception on incorrect SSO response --- src/ekklesia_portal/app.py | 15 ++++++++++++--- .../translations/de/LC_MESSAGES/messages.po | 7 +++++++ .../translations/en/LC_MESSAGES/messages.po | 7 +++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/ekklesia_portal/app.py b/src/ekklesia_portal/app.py index 47fd5d7f..5c7b6061 100644 --- a/src/ekklesia_portal/app.py +++ b/src/ekklesia_portal/app.py @@ -106,7 +106,18 @@ def verify_identity(identity): @App.after_oauth_callback() def create_or_update_user(request, ekklesia_auth: EkklesiaAuth) -> None: - userinfo = ekklesia_auth.data + _ = request.i18n.gettext + + try: + userinfo = ekklesia_auth.data + except TypeError: + request.flash( + _( + "alert_ekklesia_auth_failed_parsing" + ), "danger" + ) + return + sub = userinfo.sub token = ekklesia_auth.token @@ -119,8 +130,6 @@ def create_or_update_user(request, ekklesia_auth: EkklesiaAuth) -> None: required_role_for_login = auth_settings.required_role_for_login - _ = request.i18n.gettext - if required_role_for_login is not None and required_role_for_login not in userinfo.roles: request.flash( _( diff --git a/src/ekklesia_portal/translations/de/LC_MESSAGES/messages.po b/src/ekklesia_portal/translations/de/LC_MESSAGES/messages.po index f4495e47..2a068af7 100644 --- a/src/ekklesia_portal/translations/de/LC_MESSAGES/messages.po +++ b/src/ekklesia_portal/translations/de/LC_MESSAGES/messages.po @@ -1480,6 +1480,13 @@ msgstr "" "zu einer Stunde dauern, bis dein Account überprüft wurde und du das " "Antragsportal nutzen kannst." +#: src/ekklesia_portal/app.py:116 +msgid "alert_ekklesia_auth_failed_parsing" +msgstr "" +"Der Login ist fehlgeschlagen, da die notwendigen Daten nicht in der Antwort " +"des Single-Sign-On Provider enthalten waren. " +"Das Problem kann nur ein Administrator lösen." + #: src/ekklesia_portal/helper/missing_translations.py:4 msgid "alert_logged_in_to" msgstr "Du hast dich erfolgreich per %(name)s angemeldet." diff --git a/src/ekklesia_portal/translations/en/LC_MESSAGES/messages.po b/src/ekklesia_portal/translations/en/LC_MESSAGES/messages.po index bf51bc52..d6b338f0 100644 --- a/src/ekklesia_portal/translations/en/LC_MESSAGES/messages.po +++ b/src/ekklesia_portal/translations/en/LC_MESSAGES/messages.po @@ -1461,6 +1461,13 @@ msgstr "" "You cannot use this application via %(name)s because you don't have the " "role '%(role)s'" +#: src/ekklesia_portal/app.py:116 +msgid "alert_ekklesia_auth_failed_parsing" +msgstr "" +"Login failed, as the required information was not included in the" +"response of the Single-Sign-On provider. " +"This problem can only be solved by an administrator." + #: src/ekklesia_portal/helper/missing_translations.py:4 msgid "alert_logged_in_to" msgstr "Logged in successfully via %(name)s." From 88f4cea70a849f7611f4ab7ad2d3226f63d4b66c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Borgsm=C3=BCller?= Date: Mon, 23 Oct 2023 21:55:25 +0200 Subject: [PATCH 2/3] Improve VVVote integration - Open links to VVVote in new tabs - Use random order for multiple propositions per ballot - Use qualification date to order ballots - Send ballot id and rand part of proposition id to VVVote instead of sequential numbers --- .../templates/voting_phase.j2.jade | 6 ++-- .../voting_phase/voting_phase_views.py | 10 +++--- .../lib/vvvote/election_config.py | 31 ++++++++++++++----- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/ekklesia_portal/concepts/voting_phase/templates/voting_phase.j2.jade b/src/ekklesia_portal/concepts/voting_phase/templates/voting_phase.j2.jade index 9d262701..16d347ec 100644 --- a/src/ekklesia_portal/concepts/voting_phase/templates/voting_phase.j2.jade +++ b/src/ekklesia_portal/concepts/voting_phase/templates/voting_phase.j2.jade @@ -56,7 +56,7 @@ ul.votings for title, url in votings li - a.btn.btn-primary.btn-sm(href=url) + a.btn.btn-primary.btn-sm(href=url,target='_blank') i.fas.fa-sign-in-alt   = _('register_now_with_voting_module', title=title) @@ -65,7 +65,7 @@ ul.votings for title, url in votings li - a.btn.btn-primary.btn-sm(href=url) + a.btn.btn-primary.btn-sm(href=url,target='_blank') i.fas.fa-person-booth   = _('vote_now_with_voting_module', title=title) @@ -74,7 +74,7 @@ ul.votings for title, url in voting_results li - a.btn.btn-secondary.btn-sm(href=url) + a.btn.btn-secondary.btn-sm(href=url,target='_blank') i.fas.fa-poll-h   = _('show_results_with_voting_module', title=title) diff --git a/src/ekklesia_portal/concepts/voting_phase/voting_phase_views.py b/src/ekklesia_portal/concepts/voting_phase/voting_phase_views.py index 1330a904..1dd2e412 100644 --- a/src/ekklesia_portal/concepts/voting_phase/voting_phase_views.py +++ b/src/ekklesia_portal/concepts/voting_phase/voting_phase_views.py @@ -192,12 +192,12 @@ def retrieve_voting(self, request): with start_action(action_type="apply_election_results") as action: result_objs = [] - for ii, ballot in enumerate(self.ballots, start=1): - result = results.get(str(ii)) - if result: + for ballot in self.ballots: + if str(ballot.id) in results: + result = results.get(str(ballot.id)) result_obj = {} - for option_id, proposition in enumerate(ballot.propositions, start=1): - if option_id in result: + for proposition in ballot.propositions: + if int(proposition.id) & ((2 ** 22) - 1) in result: res = OpenSlidesVotingResult.ACCEPTED else: res = OpenSlidesVotingResult.REJECTED diff --git a/src/ekklesia_portal/lib/vvvote/election_config.py b/src/ekklesia_portal/lib/vvvote/election_config.py index 4aa66351..3a223f84 100644 --- a/src/ekklesia_portal/lib/vvvote/election_config.py +++ b/src/ekklesia_portal/lib/vvvote/election_config.py @@ -1,22 +1,29 @@ +import random from uuid import uuid4 import ekklesia_portal.lib.vvvote.schema as vvvote_schema -def ballot_to_vvvote_question(ballot, question_id=1): +def ballot_to_vvvote_question(ballot): options = [] voting_scheme_yes_no = vvvote_schema.YesNoScheme( - name='yesNo', abstention=True, abstentionAsNo=False, quorum=2, mode=vvvote_schema.SchemeMode.QUORUM + name=vvvote_schema.SchemeName.YES_NO, abstention=True, abstentionAsNo=False, quorum=2, mode=vvvote_schema.SchemeMode.QUORUM ) - voting_scheme_score = vvvote_schema.ScoreScheme(name='score', minScore=0, maxScore=3) + proposition_count = len(ballot.propositions) + voting_scheme_score = vvvote_schema.ScoreScheme( + name=vvvote_schema.SchemeName.SCORE, minScore=0, maxScore=3 if proposition_count <= 5 else 9) voting_scheme = [voting_scheme_yes_no, voting_scheme_score] - for option_id, proposition in enumerate(ballot.propositions, start=1): + # Random order of propositions in ballot + propositions = list(ballot.propositions) + random.shuffle(propositions) + + for proposition in propositions: proponents = [s.member.name for s in proposition.propositions_member if s.submitter] option = vvvote_schema.Option( - optionID=option_id, + optionID=int(proposition.id) & ((2 ** 22) - 1), # Only use the random bits (64bit not supported in JSON) proponents=proponents, optionTitle=proposition.title, optionDesc=proposition.content, @@ -31,17 +38,25 @@ def ballot_to_vvvote_question(ballot, question_id=1): question = vvvote_schema.Question( questionWording=question_wording, - questionID=question_id, + questionID=ballot.id, scheme=voting_scheme, options=options, - findWinner=['yesNo', 'score', 'random'] + findWinner=[vvvote_schema.SchemeName.YES_NO, vvvote_schema.SchemeName.SCORE, vvvote_schema.SchemeName.RANDOM] ) return question +def get_ballot_sort_key(ballot): + props = list(ballot.propositions) + props.sort(key=lambda prop: prop.qualified_at) + return props[0].qualified_at + + def voting_phase_to_vvvote_election_config(module_config, phase) -> vvvote_schema.ElectionConfig: - questions = [ballot_to_vvvote_question(b, ii) for ii, b in enumerate(phase.ballots, start=1)] + ballots = list(phase.ballots) + ballots.sort(key=get_ballot_sort_key) + questions = [ballot_to_vvvote_question(ballot) for ballot in ballots] if phase.registration_start is None: raise ValueError("Cannot create voting for phase {phase}, registration_start is None") From 5790dbc9dbbff4ccfabceb3c369816145884d262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Borgsm=C3=BCller?= Date: Mon, 23 Oct 2023 22:31:22 +0200 Subject: [PATCH 3/3] Fix tests and create_test_db script --- src/ekklesia_portal/lib/vvvote/election_config.py | 6 ++++-- tests/create_test_db.py | 3 +-- tests/lib/vvvote/test_election_config.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ekklesia_portal/lib/vvvote/election_config.py b/src/ekklesia_portal/lib/vvvote/election_config.py index 3a223f84..c250724d 100644 --- a/src/ekklesia_portal/lib/vvvote/election_config.py +++ b/src/ekklesia_portal/lib/vvvote/election_config.py @@ -1,3 +1,5 @@ +from datetime import datetime + import random from uuid import uuid4 @@ -49,8 +51,8 @@ def ballot_to_vvvote_question(ballot): def get_ballot_sort_key(ballot): props = list(ballot.propositions) - props.sort(key=lambda prop: prop.qualified_at) - return props[0].qualified_at + props.sort(key=lambda prop: prop.qualified_at or datetime.now()) + return props[0].qualified_at or datetime.now() def voting_phase_to_vvvote_election_config(module_config, phase) -> vvvote_schema.ElectionConfig: diff --git a/tests/create_test_db.py b/tests/create_test_db.py index bfea84b7..ff3cc8b7 100644 --- a/tests/create_test_db.py +++ b/tests/create_test_db.py @@ -13,7 +13,6 @@ from typer import Option, confirm, Exit import ekklesia_common.logging -from ekklesia_common.ekklesia_auth import OAuthToken DOCUMENT_WP = '''# Wahlprogramm @@ -115,7 +114,7 @@ def main( # local import because we have to set up the database stuff before that from ekklesia_portal.datamodel import ( Argument, ArgumentRelation, ArgumentVote, Ballot, CustomizableText, Department, DepartmentMember, Document, - Group, Policy, Proposition, PropositionType, SubjectArea, Supporter, Tag, User, UserPassword, UserProfile, + Group, OAuthToken, Policy, Proposition, PropositionType, SubjectArea, Supporter, Tag, User, UserPassword, UserProfile, VotingPhase, VotingPhaseType ) diff --git a/tests/lib/vvvote/test_election_config.py b/tests/lib/vvvote/test_election_config.py index a163aae9..75760097 100644 --- a/tests/lib/vvvote/test_election_config.py +++ b/tests/lib/vvvote/test_election_config.py @@ -6,7 +6,7 @@ def test_ballot_to_vvvote_question(db_session, ballot, proposition_factory): proposition = proposition_factory(ballot=ballot) question = ballot_to_vvvote_question(ballot) - assert question.questionID == 1 + assert question.questionID == ballot.id assert question.options[0].optionTitle == proposition.title assert question.options[0].optionDesc == proposition.content assert question.options[0].reasons == proposition.motivation