From 6456923efdc423cd5ba4a9514c39a79f033d3cf1 Mon Sep 17 00:00:00 2001 From: leio10 Date: Mon, 6 Sep 2021 09:26:06 +0200 Subject: [PATCH] Add tally missing trustees feature (#202) * style: remove unused imports * feat: add command to report missing trustees * feat: implement missing trustee for dummy scheme * feat: implement missing trustee for electionguard scheme * feat: add missing trustee to elections sandbox * feat: add missing trustees report to clients * fix : minor fixes and improvements * chore: bump versions * doc: update changelog * style: fix lint issues * chore: bump python and EG versions * fix: solve lint issues and broken tests * chore: upload test server log for e2e tests * test: don't use chrome for e2e tests * chore: increate e2e tests consistency * test: increase e2e timeout for compensation --- .devcontainer/Dockerfile | 4 +- .devcontainer/devcontainer.json | 2 +- .../library-scripts/common-debian.sh | 20 +- .../library-scripts/docker-debian.sh | 20 +- .github/workflows/server-workflow.yml | 34 +- ...-electionguard-python-wrapper-workflow.yml | 6 +- CHANGELOG.md | 4 + Dockerfile.release | 8 +- Dockerfile.web | 8 +- bulletin_board/js-client/package-lock.json | 4 +- bulletin_board/js-client/package.json | 2 +- bulletin_board/js-client/src/types.d.ts | 14 + bulletin_board/ruby-client/Gemfile.lock | 156 +++--- .../decidim-bulletin_board.gemspec | 3 +- .../authority/report_missing_trustee.rb | 59 +++ .../lib/decidim/bulletin_board/client.rb | 13 +- .../bulletin_board/graphql/bb_schema.json | 82 ++++ .../lib/decidim/bulletin_board/version.rb | 2 +- .../authority/report_missing_trustee_spec.rb | 53 +++ .../decidim/bulletin_board/client_spec.rb | 28 ++ bulletin_board/server.mk | 26 +- bulletin_board/server/Gemfile.lock | 10 +- .../server/app/assets/config/manifest.js | 6 +- .../sandbox/{elections => }/new.js | 0 .../app/assets/javascripts/sandbox/tally.js | 18 + .../sandbox/{elections => }/new.scss | 0 .../app/assets/stylesheets/sandbox/tally.scss | 9 + .../commands/concerns/log_entry_command.rb | 4 +- .../app/commands/report_missing_trustee.rb | 47 ++ .../sandbox/elections_controller.rb | 6 + .../report_missing_trustee_mutation.rb | 27 ++ .../server/app/graphql/types/mutation_type.rb | 1 + .../app/jobs/report_missing_trustee_job.rb | 9 + .../voting_scheme/dummy/bulletin_board.rb | 57 ++- .../electionguard/bulletin_board.rb | 2 +- .../app/views/sandbox/elections/new.html.erb | 4 +- .../views/sandbox/elections/tally.html.erb | 5 +- bulletin_board/server/config/routes.rb | 1 + .../server/cypress/app_commands/log_fail.rb | 6 +- .../server/cypress/cypress_helper.rb | 2 +- .../integration/sandbox/election.page.js | 82 ++-- .../integration/sandbox/election.spec.js | 16 +- .../commands/report_missing_trustee_spec.rb | 78 +++ .../server/spec/commands/start_tally_spec.rb | 4 +- .../server/spec/factories/messages.rb | 11 + .../server/spec/factories/models.rb | 8 +- .../jobs/report_missing_trustee_job_spec.rb | 23 + .../mutations/report_missing_trustee_spec.rb | 86 ++++ .../voting_scheme/bulletin_board_spec.rb | 24 + .../spec/support/electionguard_test_data.rb | 1 - .../dummy/js-adapter/package-lock.json | 4 +- voting_schemes/dummy/js-adapter/package.json | 2 +- .../dummy/js-adapter/src/trustee_wrapper.js | 30 +- .../js-adapter/src/trustee_wrapper.test.js | 6 +- .../dummy/ruby-adapter/Gemfile.lock | 136 +++--- .../lib/voting_schemes/dummy/version.rb | 2 +- .../ruby-node-python-electionguard/Dockerfile | 4 +- .../electionguard/electionguard-python | 2 +- .../js-adapter/package-lock.json | 4 +- .../electionguard/js-adapter/package.json | 2 +- voting_schemes/electionguard/makefile.mk | 4 +- .../electionguard/python-to-js/build | 2 +- .../packages/electionguard/meta.yaml | 4 +- .../patches/no-cryptography.patch | 48 +- .../python-wrapper/.devcontainer/Dockerfile | 2 +- .../electionguard/python-wrapper/Makefile | 6 +- .../electionguard/python-wrapper/Pipfile | 4 +- .../electionguard/python-wrapper/Pipfile.lock | 443 ++++++++++-------- .../python-wrapper/integration_results.jsonl | 116 ++--- .../electionguard/python-wrapper/setup.py | 4 +- .../electionguard/bulletin_board.py | 71 +-- .../bulletin_board/electionguard/messages.py | 2 +- .../electionguard/tally_decryptor.py | 28 +- .../bulletin_board/electionguard/trustee.py | 78 ++- .../python-wrapper/tests/test_integration.py | 30 +- .../python-wrapper/tests/utils.py | 6 + .../electionguard/ruby-adapter/Gemfile.lock | 8 +- .../voting_schemes/electionguard/version.rb | 2 +- 78 files changed, 1455 insertions(+), 690 deletions(-) create mode 100644 bulletin_board/ruby-client/lib/decidim/bulletin_board/authority/report_missing_trustee.rb create mode 100644 bulletin_board/ruby-client/spec/decidim/bulletin_board/authority/report_missing_trustee_spec.rb rename bulletin_board/server/app/assets/javascripts/sandbox/{elections => }/new.js (100%) rename bulletin_board/server/app/assets/stylesheets/sandbox/{elections => }/new.scss (100%) create mode 100644 bulletin_board/server/app/commands/report_missing_trustee.rb create mode 100644 bulletin_board/server/app/graphql/mutations/report_missing_trustee_mutation.rb create mode 100644 bulletin_board/server/app/jobs/report_missing_trustee_job.rb create mode 100644 bulletin_board/server/spec/commands/report_missing_trustee_spec.rb create mode 100644 bulletin_board/server/spec/jobs/report_missing_trustee_job_spec.rb create mode 100644 bulletin_board/server/spec/mutations/report_missing_trustee_spec.rb diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 80b0a8db..12abbd03 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.8-electionguard-1.2.0 +FROM codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.11-electionguard-1.2.1 LABEL author="david@codegram.com" # [Option] Install zsh @@ -19,7 +19,7 @@ ARG USERNAME=automatic ARG USER_UID=1000 ARG USER_GID=$USER_UID COPY library-scripts/*.sh /tmp/library-scripts/ -RUN apt-get update \ +RUN apt-get update --allow-releaseinfo-change \ && /bin/bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \ # Use Docker script from script library to set things up && /bin/bash /tmp/library-scripts/docker-debian.sh "${ENABLE_NONROOT_DOCKER}" "/var/run/docker-host.sock" "/var/run/docker.sock" "${USERNAME}" \ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3b419f63..4168a7d9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -21,7 +21,7 @@ "forwardPorts": [8000], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "apt-get update && apt-get install -y libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb && make install build" + "postCreateCommand": "apt-get update --allow-releaseinfo-change && apt-get install -y libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb && make install build install_test_e2e" // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. // "remoteUser": "vscode" diff --git a/.devcontainer/library-scripts/common-debian.sh b/.devcontainer/library-scripts/common-debian.sh index ccd019e1..b4d2ae3c 100644 --- a/.devcontainer/library-scripts/common-debian.sh +++ b/.devcontainer/library-scripts/common-debian.sh @@ -62,7 +62,7 @@ apt-get-update-if-needed() { if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then echo "Running apt-get update..." - apt-get update + apt-get update --allow-releaseinfo-change else echo "Skipping apt-get update." fi @@ -113,7 +113,7 @@ if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then PACKAGE_LIST="${PACKAGE_LIST} libssl1.1" fi - + # Install appropriate version of libssl1.0.x if available LIBSSL=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '') if [ "$(echo "$LIBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then @@ -128,7 +128,7 @@ if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then echo "Packages to verify are installed: ${PACKAGE_LIST}" apt-get -y install --no-install-recommends ${PACKAGE_LIST} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) - + PACKAGES_ALREADY_INSTALLED="true" fi @@ -142,7 +142,7 @@ fi # Ensure at least the en_US.UTF-8 UTF-8 locale is available. # Common need for both applications and things like the agnoster ZSH theme. if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then - echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen + echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen locale-gen LOCALE_ALREADY_SET="true" fi @@ -150,11 +150,11 @@ fi # Create or update a non-root user to match UID/GID. if id -u ${USERNAME} > /dev/null 2>&1; then # User exists, update if needed - if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -G $USERNAME)" ]; then - groupmod --gid $USER_GID $USERNAME + if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -G $USERNAME)" ]; then + groupmod --gid $USER_GID $USERNAME usermod --gid $USER_GID $USERNAME fi - if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then + if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then usermod --uid $USER_UID $USERNAME fi else @@ -164,7 +164,7 @@ else else groupadd --gid $USER_GID $USERNAME fi - if [ "${USER_UID}" = "automatic" ]; then + if [ "${USER_UID}" = "automatic" ]; then useradd -s /bin/bash --gid $USERNAME -m $USERNAME else useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME @@ -179,7 +179,7 @@ if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" fi # ** Shell customization section ** -if [ "${USERNAME}" = "root" ]; then +if [ "${USERNAME}" = "root" ]; then USER_RC_PATH="/root" else USER_RC_PATH="/home/${USERNAME}" @@ -240,7 +240,7 @@ CODESPACES_ZSH="$(cat \ <<'EOF' __zsh_prompt() { local prompt_username - if [ ! -z "${GITHUB_USER}" ]; then + if [ ! -z "${GITHUB_USER}" ]; then prompt_username="@${GITHUB_USER}" else prompt_username="%n" diff --git a/.devcontainer/library-scripts/docker-debian.sh b/.devcontainer/library-scripts/docker-debian.sh index dad1feb9..d6b9d096 100644 --- a/.devcontainer/library-scripts/docker-debian.sh +++ b/.devcontainer/library-scripts/docker-debian.sh @@ -43,7 +43,7 @@ apt-get-update-if-needed() { if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then echo "Running apt-get update..." - apt-get update + apt-get update --allow-releaseinfo-change else echo "Skipping apt-get update." fi @@ -55,7 +55,7 @@ export DEBIAN_FRONTEND=noninteractive # Install apt-transport-https, curl, lsb-release, gpg if missing if ! dpkg -s apt-transport-https curl ca-certificates lsb-release > /dev/null 2>&1 || ! type gpg > /dev/null 2>&1; then apt-get-update-if-needed - apt-get -y install --no-install-recommends apt-transport-https curl ca-certificates lsb-release gnupg2 + apt-get -y install --no-install-recommends apt-transport-https curl ca-certificates lsb-release gnupg2 fi # Install Docker / Moby CLI if not already installed @@ -67,17 +67,17 @@ else CODENAME=$(lsb_release -cs) curl -s https://packages.microsoft.com/keys/microsoft.asc | (OUT=$(apt-key add - 2>&1) || echo $OUT) echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-${DISTRO}-${CODENAME}-prod ${CODENAME} main" > /etc/apt/sources.list.d/microsoft.list - apt-get update + apt-get update --allow-releaseinfo-change apt-get -y install --no-install-recommends moby-cli else curl -fsSL https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/gpg | (OUT=$(apt-key add - 2>&1) || echo $OUT) echo "deb [arch=amd64] https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]') $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list - apt-get update + apt-get update --allow-releaseinfo-change apt-get -y install --no-install-recommends docker-ce-cli fi fi -# Install Docker Compose if not already installed +# Install Docker Compose if not already installed if type docker-compose > /dev/null 2>&1; then echo "Docker Compose already installed." else @@ -105,13 +105,13 @@ if [ "${ENABLE_NONROOT_DOCKER}" = "false" ] || [ "${USERNAME}" = "root" ]; then fi # If enabling non-root access and specified user is found, setup socat and add script -chown -h "${USERNAME}":root "${TARGET_SOCKET}" +chown -h "${USERNAME}":root "${TARGET_SOCKET}" if ! dpkg -s socat > /dev/null 2>&1; then apt-get-update-if-needed apt-get -y install socat fi tee /usr/local/share/docker-init.sh > /dev/null \ -<< EOF +<< EOF #!/usr/bin/env bash #------------------------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. @@ -143,8 +143,8 @@ log() echo -e "\n** \$(date) **" | sudoIf tee -a \${SOCAT_LOG} > /dev/null log "Ensuring ${USERNAME} has access to ${SOURCE_SOCKET} via ${TARGET_SOCKET}" -# If enabled, try to add a docker group with the right GID. If the group is root, -# fall back on using socat to forward the docker socket to another unix socket so +# If enabled, try to add a docker group with the right GID. If the group is root, +# fall back on using socat to forward the docker socket to another unix socket so # that we can set permissions on it without affecting the host. if [ "${ENABLE_NONROOT_DOCKER}" = "true" ] && [ "${SOURCE_SOCKET}" != "${TARGET_SOCKET}" ] && [ "${USERNAME}" != "root" ] && [ "${USERNAME}" != "0" ]; then SOCKET_GID=\$(stat -c '%g' ${SOURCE_SOCKET}) @@ -171,7 +171,7 @@ if [ "${ENABLE_NONROOT_DOCKER}" = "true" ] && [ "${SOURCE_SOCKET}" != "${TARGET_ log "Success" fi -# Execute whatever commands were passed in (if any). This allows us +# Execute whatever commands were passed in (if any). This allows us # to set this script to ENTRYPOINT while still executing the default CMD. set +e exec "\$@" diff --git a/.github/workflows/server-workflow.yml b/.github/workflows/server-workflow.yml index d59088d9..d24765a8 100644 --- a/.github/workflows/server-workflow.yml +++ b/.github/workflows/server-workflow.yml @@ -19,7 +19,7 @@ jobs: name: Build electionguard runs-on: ubuntu-latest container: - image: codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.8-electionguard-1.2.0 + image: codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.11-electionguard-1.2.1 steps: - uses: actions/checkout@v2.0.0 with: @@ -74,7 +74,7 @@ jobs: runs-on: ubuntu-latest if: ${{ needs.check_python_to_js_cache.outputs.cache-hit != 'true' }} container: - image: codegram/pyodide-electionguard:pyodide-0.16.1-electionguard-1.2.0 + image: codegram/pyodide-electionguard:pyodide-0.16.1-electionguard-1.2.1 steps: - uses: actions/checkout@v2.0.0 with: @@ -121,7 +121,7 @@ jobs: needs: build_python_to_js runs-on: ubuntu-latest container: - image: codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.8-electionguard-1.2.0 + image: codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.11-electionguard-1.2.1 services: postgres: image: postgres:11 @@ -234,7 +234,7 @@ jobs: needs: [check_python_to_js_cache, build_python_to_js] runs-on: ubuntu-latest container: - image: codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.8-electionguard-1.2.0 + image: codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.11-electionguard-1.2.1 services: postgres: image: postgres:11 @@ -314,26 +314,20 @@ jobs: cd bulletin_board/server bundler exec rails db:create bundler exec rails db:migrate - - name: Install Chrome + - name: Install e2e tests dependencies run: | - sudo apt-get update - sudo apt-get install -y unzip xvfb libxi6 libgconf-2-4 - sudo curl -sS -o - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - sudo echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list - sudo apt-get -y update - sudo apt-get -y install google-chrome-stable - wget https://chromedriver.storage.googleapis.com/90.0.4430.24/chromedriver_linux64.zip - unzip chromedriver_linux64.zip - sudo mv chromedriver /usr/bin/chromedriver - sudo chown root:root /usr/bin/chromedriver - sudo chmod +x /usr/bin/chromedriver + make install_test_e2e - name: Run e2e tests run: | - cd bulletin_board/server - npm run e2e:install - bundle exec rails s -e test -p 5017 & + make serve_test & sleep 5 - npm run e2e:tests -- --browser chrome --headless + make test_e2e + - name: Upload artifacts (server logs) + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: test server logs + path: bulletin_board/server/log/ - name: Upload artifacts (screenshots) if: ${{ always() }} uses: actions/upload-artifact@v2 diff --git a/.github/workflows/voting-scheme-electionguard-python-wrapper-workflow.yml b/.github/workflows/voting-scheme-electionguard-python-wrapper-workflow.yml index 314e813e..dad4e41d 100644 --- a/.github/workflows/voting-scheme-electionguard-python-wrapper-workflow.yml +++ b/.github/workflows/voting-scheme-electionguard-python-wrapper-workflow.yml @@ -13,7 +13,6 @@ env: CI: "true" SIMPLECOV: "true" ACTIONS_ALLOW_UNSECURE_COMMANDS: "true" - PYTHON_VERSION: "3.8.8" defaults: run: @@ -23,6 +22,8 @@ jobs: tests: name: Test code runs-on: ubuntu-latest + container: + image: codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.11-electionguard-1.2.1 steps: - uses: rokroskar/workflow-run-cleanup-action@v0.3.2 if: "github.ref != 'refs/heads/develop'" @@ -32,9 +33,6 @@ jobs: with: fetch-depth: 1 submodules: true - - uses: actions/setup-python@v2 - with: - python-version: ${{ env.PYTHON_VERSION }} - name: Add Poetry to path run: echo "$HOME/.poetry/bin" >> $GITHUB_PATH - name: Install dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index a2a416b9..09e693ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## Added + +- The bulletin board client now includes a `report_missing_trustee` method to report the absence of a trustee during the Tally phase. + ## [0.21.2] - 2021-05-28 ## [0.21.1] - 2021-04-23 diff --git a/Dockerfile.release b/Dockerfile.release index 5760bed2..bcc4d3f3 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -1,5 +1,5 @@ # This stage builds the python-wrapper package -FROM codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.8-electionguard-1.2.0 as python-wrapper-builder +FROM codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.11-electionguard-1.2.1 as python-wrapper-builder LABEL author="david@codegram.com" # Add Makefiles @@ -17,7 +17,7 @@ ADD voting_schemes/electionguard/python-wrapper /code/voting_schemes/electiongua RUN cd /code && make build_electionguard_python_wrapper # This stage builds the pyodide packages for the previous python packages -FROM codegram/pyodide-electionguard:pyodide-0.16.1-electionguard-1.2.0 as python-to-js-builder +FROM codegram/pyodide-electionguard:pyodide-0.16.1-electionguard-1.2.1 as python-to-js-builder LABEL author="david@codegram.com" ENV PYODIDE_PACKAGES "electionguard,bulletin_board-electionguard" @@ -43,7 +43,7 @@ RUN make RUN cp -rf /code/voting_schemes/electionguard/python-to-js/override/* /src/pyodide/build/ # This stage builds the bulletin-board application -FROM codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.8-electionguard-1.2.0 +FROM codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.11-electionguard-1.2.1 LABEL author="david@codegram.com" # Environment variables @@ -51,7 +51,7 @@ ENV SECRET_KEY_BASE 1234 ENV RAILS_ENV production # Install system dependencies -RUN apt-get update && \ +RUN apt-get update --allow-releaseinfo-change && \ apt-get install -y postgresql postgresql-client postgresql-contrib libpq-dev \ redis-server memcached imagemagick ffmpeg mupdf mupdf-tools libxml2-dev && \ rm -rf /var/lib/apt/lists/* diff --git a/Dockerfile.web b/Dockerfile.web index d764c41c..2ec73627 100644 --- a/Dockerfile.web +++ b/Dockerfile.web @@ -1,5 +1,5 @@ # This stage builds the python-wrapper package -FROM codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.8-electionguard-1.2.0 as python-wrapper-builder +FROM codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.11-electionguard-1.2.1 as python-wrapper-builder LABEL author="david@codegram.com" # Add Makefiles @@ -17,7 +17,7 @@ ADD voting_schemes/electionguard/python-wrapper /code/voting_schemes/electiongua RUN cd /code && make build_electionguard_python_wrapper # This stage builds the pyodide packages for the previous python packages -FROM codegram/pyodide-electionguard:pyodide-0.16.1-electionguard-1.2.0 as python-to-js-builder +FROM codegram/pyodide-electionguard:pyodide-0.16.1-electionguard-1.2.1 as python-to-js-builder LABEL author="david@codegram.com" ENV PYODIDE_PACKAGES "electionguard,bulletin_board-electionguard" @@ -43,7 +43,7 @@ RUN make RUN cp -rf /code/voting_schemes/electionguard/python-to-js/override/* /src/pyodide/build/ # This stage builds the bulletin-board application -FROM codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.8-electionguard-1.2.0 +FROM codegram/ruby-node-python-electionguard:ruby-2.6.6-node-15-python-3.8.11-electionguard-1.2.1 LABEL author="david@codegram.com" # Environment variables @@ -51,7 +51,7 @@ ENV SECRET_KEY_BASE 1234 ENV RAILS_ENV production # Install system dependencies -RUN apt-get update && \ +RUN apt-get update --allow-releaseinfo-change && \ apt-get install -y postgresql postgresql-client postgresql-contrib libpq-dev \ redis-server memcached imagemagick ffmpeg mupdf mupdf-tools libxml2-dev && \ rm -rf /var/lib/apt/lists/* diff --git a/bulletin_board/js-client/package-lock.json b/bulletin_board/js-client/package-lock.json index aca523ae..9e708470 100644 --- a/bulletin_board/js-client/package-lock.json +++ b/bulletin_board/js-client/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codegram/decidim-bulletin_board", - "version": "0.21.2", + "version": "0.21.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@codegram/decidim-bulletin_board", - "version": "0.21.2", + "version": "0.21.3", "license": "ISC", "dependencies": { "@apollo/client": "^3.2.7", diff --git a/bulletin_board/js-client/package.json b/bulletin_board/js-client/package.json index eea55def..b778ed4a 100644 --- a/bulletin_board/js-client/package.json +++ b/bulletin_board/js-client/package.json @@ -1,6 +1,6 @@ { "name": "@codegram/decidim-bulletin_board", - "version": "0.21.2", + "version": "0.21.3", "description": "", "main": "src/index.js", "scripts": { diff --git a/bulletin_board/js-client/src/types.d.ts b/bulletin_board/js-client/src/types.d.ts index 0d8cd13b..41c6c60b 100644 --- a/bulletin_board/js-client/src/types.d.ts +++ b/bulletin_board/js-client/src/types.d.ts @@ -84,6 +84,7 @@ export type Mutation = { processKeyCeremonyStep?: Maybe; processTallyStep?: Maybe; publishResults?: Maybe; + reportMissingTrustee?: Maybe; resetTestDatabase?: Maybe; startKeyCeremony?: Maybe; startTally?: Maybe; @@ -122,6 +123,12 @@ export type MutationPublishResultsArgs = { }; +export type MutationReportMissingTrusteeArgs = { + messageId: Scalars['String']; + signedData: Scalars['String']; +}; + + export type MutationStartKeyCeremonyArgs = { messageId: Scalars['String']; signedData: Scalars['String']; @@ -209,6 +216,13 @@ export type QueryPendingMessageArgs = { messageId?: Maybe; }; +/** Autogenerated return type of ReportMissingTrusteeMutation */ +export type ReportMissingTrusteeMutationPayload = { + __typename?: 'ReportMissingTrusteeMutationPayload'; + error?: Maybe; + pendingMessage?: Maybe; +}; + /** Autogenerated return type of ResetTestDatabaseMutation */ export type ResetTestDatabaseMutationPayload = { __typename?: 'ResetTestDatabaseMutationPayload'; diff --git a/bulletin_board/ruby-client/Gemfile.lock b/bulletin_board/ruby-client/Gemfile.lock index 2e16c33f..fa041642 100644 --- a/bulletin_board/ruby-client/Gemfile.lock +++ b/bulletin_board/ruby-client/Gemfile.lock @@ -1,70 +1,70 @@ PATH remote: . specs: - decidim-bulletin_board (0.21.2) + decidim-bulletin_board (0.21.3) byebug (~> 11.0) graphlient (~> 0.4.0) jwt (~> 2.2.2) - rails (>= 5.0.0) + rails (~> 6.0, >= 5.0.0) wisper (~> 2.0.0) GEM remote: https://rubygems.org/ specs: - actioncable (6.1.3.2) - actionpack (= 6.1.3.2) - activesupport (= 6.1.3.2) + actioncable (6.1.4.1) + actionpack (= 6.1.4.1) + activesupport (= 6.1.4.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.3.2) - actionpack (= 6.1.3.2) - activejob (= 6.1.3.2) - activerecord (= 6.1.3.2) - activestorage (= 6.1.3.2) - activesupport (= 6.1.3.2) + actionmailbox (6.1.4.1) + actionpack (= 6.1.4.1) + activejob (= 6.1.4.1) + activerecord (= 6.1.4.1) + activestorage (= 6.1.4.1) + activesupport (= 6.1.4.1) mail (>= 2.7.1) - actionmailer (6.1.3.2) - actionpack (= 6.1.3.2) - actionview (= 6.1.3.2) - activejob (= 6.1.3.2) - activesupport (= 6.1.3.2) + actionmailer (6.1.4.1) + actionpack (= 6.1.4.1) + actionview (= 6.1.4.1) + activejob (= 6.1.4.1) + activesupport (= 6.1.4.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.3.2) - actionview (= 6.1.3.2) - activesupport (= 6.1.3.2) + actionpack (6.1.4.1) + actionview (= 6.1.4.1) + activesupport (= 6.1.4.1) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.3.2) - actionpack (= 6.1.3.2) - activerecord (= 6.1.3.2) - activestorage (= 6.1.3.2) - activesupport (= 6.1.3.2) + actiontext (6.1.4.1) + actionpack (= 6.1.4.1) + activerecord (= 6.1.4.1) + activestorage (= 6.1.4.1) + activesupport (= 6.1.4.1) nokogiri (>= 1.8.5) - actionview (6.1.3.2) - activesupport (= 6.1.3.2) + actionview (6.1.4.1) + activesupport (= 6.1.4.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.3.2) - activesupport (= 6.1.3.2) + activejob (6.1.4.1) + activesupport (= 6.1.4.1) globalid (>= 0.3.6) - activemodel (6.1.3.2) - activesupport (= 6.1.3.2) - activerecord (6.1.3.2) - activemodel (= 6.1.3.2) - activesupport (= 6.1.3.2) - activestorage (6.1.3.2) - actionpack (= 6.1.3.2) - activejob (= 6.1.3.2) - activerecord (= 6.1.3.2) - activesupport (= 6.1.3.2) + activemodel (6.1.4.1) + activesupport (= 6.1.4.1) + activerecord (6.1.4.1) + activemodel (= 6.1.4.1) + activesupport (= 6.1.4.1) + activestorage (6.1.4.1) + actionpack (= 6.1.4.1) + activejob (= 6.1.4.1) + activerecord (= 6.1.4.1) + activesupport (= 6.1.4.1) marcel (~> 1.0.0) - mini_mime (~> 1.0.2) - activesupport (6.1.3.2) + mini_mime (>= 1.1.0) + activesupport (6.1.4.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -83,28 +83,34 @@ GEM erubi (1.10.0) faker (2.15.1) i18n (>= 1.6, < 2) - faraday (1.4.2) + faraday (1.7.1) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0.1) faraday-net_http (~> 1.0) faraday-net_http_persistent (~> 1.1) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) multipart-post (>= 1.2, < 3) ruby2_keywords (>= 0.0.4) faraday-em_http (1.0.0) faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) + faraday-httpclient (1.0.1) faraday-net_http (1.0.1) - faraday-net_http_persistent (1.1.0) - faraday_middleware (1.0.0) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday_middleware (1.1.0) faraday (~> 1.0) - globalid (0.4.2) - activesupport (>= 4.2.0) + globalid (0.5.2) + activesupport (>= 5.0) graphlient (0.4.0) faraday (>= 1.0) faraday_middleware graphql-client - graphql (1.12.10) + graphql (1.12.16) graphql-client (0.16.0) activesupport (>= 3.0) graphql (~> 1.8) @@ -112,24 +118,24 @@ GEM i18n (1.8.10) concurrent-ruby (~> 1.0) jwt (2.2.3) - loofah (2.9.1) + loofah (2.12.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) mini_mime (>= 0.1.1) marcel (1.0.1) method_source (1.0.0) - mini_mime (1.0.3) - mini_portile2 (2.5.1) + mini_mime (1.1.1) + mini_portile2 (2.6.1) minitest (5.14.4) multipart-post (2.1.1) - nio4r (2.5.7) - nokogiri (1.11.6) - mini_portile2 (~> 2.5.0) + nio4r (2.5.8) + nokogiri (1.12.4) + mini_portile2 (~> 2.6.1) racc (~> 1.4) - nokogiri (1.11.6-x86_64-darwin) + nokogiri (1.12.4-x86_64-darwin) racc (~> 1.4) - nokogiri (1.11.6-x86_64-linux) + nokogiri (1.12.4-x86_64-linux) racc (~> 1.4) parallel (1.20.1) parser (2.7.2.0) @@ -139,31 +145,31 @@ GEM rack (2.2.3) rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.1.3.2) - actioncable (= 6.1.3.2) - actionmailbox (= 6.1.3.2) - actionmailer (= 6.1.3.2) - actionpack (= 6.1.3.2) - actiontext (= 6.1.3.2) - actionview (= 6.1.3.2) - activejob (= 6.1.3.2) - activemodel (= 6.1.3.2) - activerecord (= 6.1.3.2) - activestorage (= 6.1.3.2) - activesupport (= 6.1.3.2) + rails (6.1.4.1) + actioncable (= 6.1.4.1) + actionmailbox (= 6.1.4.1) + actionmailer (= 6.1.4.1) + actionpack (= 6.1.4.1) + actiontext (= 6.1.4.1) + actionview (= 6.1.4.1) + activejob (= 6.1.4.1) + activemodel (= 6.1.4.1) + activerecord (= 6.1.4.1) + activestorage (= 6.1.4.1) + activesupport (= 6.1.4.1) bundler (>= 1.15.0) - railties (= 6.1.3.2) + railties (= 6.1.4.1) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) + rails-html-sanitizer (1.4.2) loofah (~> 2.3) - railties (6.1.3.2) - actionpack (= 6.1.3.2) - activesupport (= 6.1.3.2) + railties (6.1.4.1) + actionpack (= 6.1.4.1) + activesupport (= 6.1.4.1) method_source - rake (>= 0.8.7) + rake (>= 0.13) thor (~> 1.0) rainbow (3.0.0) rake (13.0.3) @@ -200,7 +206,7 @@ GEM rubocop (~> 0.87) rubocop-ast (>= 0.7.1) ruby-progressbar (1.10.1) - ruby2_keywords (0.0.4) + ruby2_keywords (0.0.5) sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -216,7 +222,7 @@ GEM addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - websocket-driver (0.7.4) + websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) wisper (2.0.1) @@ -239,4 +245,4 @@ DEPENDENCIES wisper-rspec (~> 1.1.0) BUNDLED WITH - 2.2.15 + 2.2.18 diff --git a/bulletin_board/ruby-client/decidim-bulletin_board.gemspec b/bulletin_board/ruby-client/decidim-bulletin_board.gemspec index efd56b89..35cf06d5 100644 --- a/bulletin_board/ruby-client/decidim-bulletin_board.gemspec +++ b/bulletin_board/ruby-client/decidim-bulletin_board.gemspec @@ -24,8 +24,7 @@ Gem::Specification.new do |s| s.bindir = "exe" s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) } s.require_paths = ["lib"] - - s.add_dependency "rails", ">= 5.0.0" + s.add_dependency "rails", "~> 6.0", ">= 5.0.0" s.add_dependency "byebug", "~> 11.0" s.add_dependency "graphlient", "~> 0.4.0" diff --git a/bulletin_board/ruby-client/lib/decidim/bulletin_board/authority/report_missing_trustee.rb b/bulletin_board/ruby-client/lib/decidim/bulletin_board/authority/report_missing_trustee.rb new file mode 100644 index 00000000..12e0b844 --- /dev/null +++ b/bulletin_board/ruby-client/lib/decidim/bulletin_board/authority/report_missing_trustee.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Decidim + module BulletinBoard + module Authority + # This command uses the GraphQL client to report a missing trustee during the tally process. + class ReportMissingTrustee < Decidim::BulletinBoard::Command + # Public: Initializes the command. + # + # election_id - The local election identifier + # trustee_id - The trustee identifier + def initialize(election_id, trustee_id) + @election_id = election_id + @trustee_id = trustee_id + end + + # Returns the message_id related to the operation + def message_id + @message_id ||= build_message_id(unique_election_id(election_id), "tally.missing_trustee") + end + + # Executes the command. Broadcasts these events: + # + # - :ok when everything is valid and the query operation is successful. + # - :error if query operation was not successful. + # + # Returns nothing. + def call + # arguments used inside the graphql operation + args = { + message_id: message_id, + signed_data: sign_message(message_id, { trustee_id: trustee_id }) + } + + response = graphql.query do + mutation do + reportMissingTrustee(messageId: args[:message_id], signedData: args[:signed_data]) do + pendingMessage do + status + end + error + end + end + end + + return broadcast(:error, response.data.report_missing_trustee.error) if response.data.report_missing_trustee.error.present? + + broadcast(:ok, response.data.report_missing_trustee.pending_message) + rescue Graphlient::Errors::ServerError + broadcast(:error, "Sorry, something went wrong") + end + + private + + attr_reader :election_id, :trustee_id + end + end + end +end diff --git a/bulletin_board/ruby-client/lib/decidim/bulletin_board/client.rb b/bulletin_board/ruby-client/lib/decidim/bulletin_board/client.rb index 746a6fbd..223ad741 100644 --- a/bulletin_board/ruby-client/lib/decidim/bulletin_board/client.rb +++ b/bulletin_board/ruby-client/lib/decidim/bulletin_board/client.rb @@ -6,12 +6,13 @@ require "decidim/bulletin_board/authority/create_election" require "decidim/bulletin_board/authority/end_vote" +require "decidim/bulletin_board/authority/get_election_results" require "decidim/bulletin_board/authority/get_election_status" +require "decidim/bulletin_board/authority/publish_results" +require "decidim/bulletin_board/authority/report_missing_trustee" require "decidim/bulletin_board/authority/start_key_ceremony" require "decidim/bulletin_board/authority/start_tally" require "decidim/bulletin_board/authority/start_vote" -require "decidim/bulletin_board/authority/publish_results" -require "decidim/bulletin_board/authority/get_election_results" require "decidim/bulletin_board/voter/cast_vote" require "decidim/bulletin_board/voter/get_pending_message_status" require "decidim/bulletin_board/voter/in_person_vote" @@ -101,6 +102,14 @@ def start_tally(election_id) start_tally.call end + def report_missing_trustee(election_id, trustee_id) + report_missing_trustee = configure Authority::ReportMissingTrustee.new(election_id, trustee_id) + yield report_missing_trustee.message_id if block_given? + report_missing_trustee.on(:ok) { |pending_message| return pending_message } + report_missing_trustee.on(:error) { |error_message| raise StandardError, error_message } + report_missing_trustee.call + end + def get_election_results(election_id) get_log_entries = configure Authority::GetElectionResults.new(election_id) get_log_entries.on(:ok) { |result| return result } diff --git a/bulletin_board/ruby-client/lib/decidim/bulletin_board/graphql/bb_schema.json b/bulletin_board/ruby-client/lib/decidim/bulletin_board/graphql/bb_schema.json index 44795a56..8e5df612 100644 --- a/bulletin_board/ruby-client/lib/decidim/bulletin_board/graphql/bb_schema.json +++ b/bulletin_board/ruby-client/lib/decidim/bulletin_board/graphql/bb_schema.json @@ -885,6 +885,47 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "reportMissingTrustee", + "description": null, + "args": [ + { + "name": "messageId", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "signedData", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "ReportMissingTrusteeMutationPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "resetTestDatabase", "description": null, @@ -1499,6 +1540,47 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "ReportMissingTrusteeMutationPayload", + "description": "Autogenerated return type of ReportMissingTrusteeMutation", + "fields": [ + { + "name": "error", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pendingMessage", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "PendingMessage", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "ResetTestDatabaseMutationPayload", diff --git a/bulletin_board/ruby-client/lib/decidim/bulletin_board/version.rb b/bulletin_board/ruby-client/lib/decidim/bulletin_board/version.rb index ccd97bc0..a3607609 100644 --- a/bulletin_board/ruby-client/lib/decidim/bulletin_board/version.rb +++ b/bulletin_board/ruby-client/lib/decidim/bulletin_board/version.rb @@ -2,6 +2,6 @@ module Decidim module BulletinBoard - VERSION = "0.21.2" + VERSION = "0.21.3" end end diff --git a/bulletin_board/ruby-client/spec/decidim/bulletin_board/authority/report_missing_trustee_spec.rb b/bulletin_board/ruby-client/spec/decidim/bulletin_board/authority/report_missing_trustee_spec.rb new file mode 100644 index 00000000..e855c1fc --- /dev/null +++ b/bulletin_board/ruby-client/spec/decidim/bulletin_board/authority/report_missing_trustee_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module BulletinBoard + module Authority + describe ReportMissingTrustee do + subject(:command) { described_class.new(election_id, trustee_id) } + + include_context "with a configured bulletin board" + + let(:election_id) { "decidim-test-authority.1" } + let(:trustee_id) { "alice" } + + let(:bulletin_board_response) do + { + reportMissingTrustee: { + pendingMessage: { + status: "enqueued" + } + } + } + end + + context "when everything is ok" do + it "broadcasts ok with the result of the graphql mutation" do + expect { subject.call }.to broadcast(:ok) + end + + it "build the right message_id" do + expect(subject.message_id).to eq("decidim-test-authority.decidim-test-authority.1.tally.missing_trustee+a.decidim-test-authority") + end + + it "uses the graphql client to register the missing trustee and returns its result" do + subject.on(:ok) do |pending_message| + expect(pending_message.status).to eq("enqueued") + end + subject.call + end + end + + context "when the graphql operation returns an unexpected error" do + let(:error_response) { true } + + it "broadcasts error with the unexpected error" do + expect { subject.call }.to broadcast(:error, "Sorry, something went wrong") + end + end + end + end + end +end diff --git a/bulletin_board/ruby-client/spec/decidim/bulletin_board/client_spec.rb b/bulletin_board/ruby-client/spec/decidim/bulletin_board/client_spec.rb index 5987786d..4ee67d0e 100644 --- a/bulletin_board/ruby-client/spec/decidim/bulletin_board/client_spec.rb +++ b/bulletin_board/ruby-client/spec/decidim/bulletin_board/client_spec.rb @@ -227,6 +227,34 @@ module BulletinBoard end end + describe "report_missing_trustee" do + subject { instance.report_missing_trustee(election_id, trustee_id) } + + before do + stub_command("Decidim::BulletinBoard::Authority::ReportMissingTrustee", :call, *result) + end + + let(:election_id) { "test.1" } + let(:trustee_id) { "alice" } + let(:result) { [:ok, double(status: "enqueued")] } + + it "yields the message_id" do + expect { |block| instance.report_missing_trustee(election_id, trustee_id, &block) }.to yield_with_args("a.message+id") + end + + it "calls the ReportMissingTrustee command and returns the result" do + expect(subject.status).to eq("enqueued") + end + + context "when something went wrong" do + let(:result) { [:error, "something went wrong"] } + + it "calls the ReportMissingTrustee command and throws an error" do + expect { subject }.to raise_error("something went wrong") + end + end + end + describe "get_election_results" do subject { instance.get_election_results(election_id) } diff --git a/bulletin_board/server.mk b/bulletin_board/server.mk index 87c3544c..84aa50b0 100644 --- a/bulletin_board/server.mk +++ b/bulletin_board/server.mk @@ -22,16 +22,38 @@ help_server: @echo ' serve - Starts the bulletin board rails server.' @echo ' replant_serve - Reseeds the server database and starts the bulletin board rails server.' @echo ' serve_test - Starts the bulletin board rails server in test mode.' + @echo ' install_test_e2e - Install dependencies to run E2E tests.' + @echo ' test_e2e - Run E2E tests.' install_server: install_bulletin_board_server_ruby_dependencies \ install_bulletin_board_server_js_dependencies +install_test_e2e: install_test_e2e_deps install_test_e2e_chrome install_test_e2e_chromedriver + cd ${BULLETIN_BOARD_SERVER_PATH} && npm run e2e:install + +install_test_e2e_deps: + apt-get update && apt-get install -y unzip xvfb libxi6 libgconf-2-4 + +install_test_e2e_chrome: + curl -sS -o - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add && \ + echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list && \ + apt-get -y update && apt-get -y install google-chrome-stable + +install_test_e2e_chromedriver: + wget https://chromedriver.storage.googleapis.com/90.0.4430.24/chromedriver_linux64.zip && unzip chromedriver_linux64.zip && \ + mv chromedriver /usr/bin/chromedriver && chown root:root /usr/bin/chromedriver && chmod +x /usr/bin/chromedriver + clean_server: build_server: -test_server: - cd ${BULLETIN_BOARD_SERVER_PATH} && bundle exec rspec && npm run e2e:tests +test_server: test_rails test_e2e + +test_rails: + cd ${BULLETIN_BOARD_SERVER_PATH} && bundle exec rspec + +test_e2e: + cd ${BULLETIN_BOARD_SERVER_PATH} && npm run e2e:tests -- --browser chrome --headless release_server: docker image build -t ${DOCKER_WEB_IMAGE} -f Dockerfile.web . && \ diff --git a/bulletin_board/server/Gemfile.lock b/bulletin_board/server/Gemfile.lock index f5ca6afc..54298711 100644 --- a/bulletin_board/server/Gemfile.lock +++ b/bulletin_board/server/Gemfile.lock @@ -1,23 +1,23 @@ PATH remote: ../../voting_schemes/dummy/ruby-adapter specs: - voting_schemes-dummy (0.21.2) + voting_schemes-dummy (0.21.3) rails (>= 5.0.0) PATH remote: ../../voting_schemes/electionguard/ruby-adapter specs: - voting_schemes-electionguard (0.21.2) + voting_schemes-electionguard (0.21.3) rails (>= 5.0.0) PATH remote: ../ruby-client specs: - decidim-bulletin_board (0.21.2) + decidim-bulletin_board (0.21.3) byebug (~> 11.0) graphlient (~> 0.4.0) jwt (~> 2.2.2) - rails (>= 5.0.0) + rails (~> 6.0, >= 5.0.0) wisper (~> 2.0.0) GEM @@ -401,4 +401,4 @@ RUBY VERSION ruby 2.6.6p146 BUNDLED WITH - 2.2.15 + 2.2.18 diff --git a/bulletin_board/server/app/assets/config/manifest.js b/bulletin_board/server/app/assets/config/manifest.js index 4cc1263d..9bbe382f 100644 --- a/bulletin_board/server/app/assets/config/manifest.js +++ b/bulletin_board/server/app/assets/config/manifest.js @@ -10,6 +10,9 @@ //= link sandbox/index.js //= link sandbox/index.css +//= link sandbox/new.js +//= link sandbox/new.css + //= link sandbox/key_ceremony.js //= link sandbox/key_ceremony.css @@ -23,6 +26,3 @@ //= link sandbox/tally.css //= link sandbox/results.css - -//= link sandbox/elections/new.css -//= link sandbox/elections/new.js diff --git a/bulletin_board/server/app/assets/javascripts/sandbox/elections/new.js b/bulletin_board/server/app/assets/javascripts/sandbox/new.js similarity index 100% rename from bulletin_board/server/app/assets/javascripts/sandbox/elections/new.js rename to bulletin_board/server/app/assets/javascripts/sandbox/new.js diff --git a/bulletin_board/server/app/assets/javascripts/sandbox/tally.js b/bulletin_board/server/app/assets/javascripts/sandbox/tally.js index 035bd2fd..4d96e24a 100644 --- a/bulletin_board/server/app/assets/javascripts/sandbox/tally.js +++ b/bulletin_board/server/app/assets/javascripts/sandbox/tally.js @@ -38,6 +38,7 @@ $(() => { ); const $startButton = $trustee.find(".start-button"); + const $reportMissing = $trustee.find(".report-missing"); const $generateBackupButton = $trustee.find(".generate-backup-button"); const $restoreButton = $trustee.find(".restore-button"); const $doneMessage = $trustee.find(".done-message"); @@ -78,9 +79,25 @@ $(() => { onEvent(_event) {}, onBindStartButton(onEventTriggered) { $startButton.on("click", onEventTriggered); + $reportMissing.on("click", () => { + $reportMissing.hide(); + $.ajax({ + url: $reportMissing.data("url"), + method: "POST", + contentType: "application/json", + data: JSON.stringify({ + trustee_id: trusteeContext.uniqueId, + }), // eslint-disable-line camelcase + headers: { + "X-CSRF-Token": $("meta[name=csrf-token]").attr("content"), + }, + }); + $trustee.addClass("missing"); + }); }, onStart() { $startButton.hide(); + $reportMissing.hide(); }, onComplete() { $doneMessage.show(); @@ -103,6 +120,7 @@ $(() => { }); $startButton.show(); + $reportMissing.show(); $generateBackupButton.on("click", (event) => { $generateBackupButton.attr( "href", diff --git a/bulletin_board/server/app/assets/stylesheets/sandbox/elections/new.scss b/bulletin_board/server/app/assets/stylesheets/sandbox/new.scss similarity index 100% rename from bulletin_board/server/app/assets/stylesheets/sandbox/elections/new.scss rename to bulletin_board/server/app/assets/stylesheets/sandbox/new.scss diff --git a/bulletin_board/server/app/assets/stylesheets/sandbox/tally.scss b/bulletin_board/server/app/assets/stylesheets/sandbox/tally.scss index 87cccfdd..4281fb40 100644 --- a/bulletin_board/server/app/assets/stylesheets/sandbox/tally.scss +++ b/bulletin_board/server/app/assets/stylesheets/sandbox/tally.scss @@ -9,8 +9,17 @@ .start-button, .generate-backup-button, .restore-button, + .report-missing, .done-message { display: none; } } + .missing { + .report-missing { + display: none; + } + .button { + background-color: gray; + } + } } diff --git a/bulletin_board/server/app/commands/concerns/log_entry_command.rb b/bulletin_board/server/app/commands/concerns/log_entry_command.rb index c3c97096..eea4ddad 100644 --- a/bulletin_board/server/app/commands/concerns/log_entry_command.rb +++ b/bulletin_board/server/app/commands/concerns/log_entry_command.rb @@ -27,7 +27,7 @@ def run_validations @error.blank? end - def valid_log_entry?(type) + def valid_log_entry?(type, subtype = nil) run_validations do if decoded_data.blank? "Invalid signature" @@ -37,6 +37,8 @@ def valid_log_entry?(type) "The message identifier given doesn't match the signed data" elsif message_identifier.type != type "The message is not valid for this endpoint" + elsif subtype && message_identifier.subtype != subtype + "The message subtype is not valid for this endpoint" elsif invalid_timestamp? "Message is too old to be accepted" end diff --git a/bulletin_board/server/app/commands/report_missing_trustee.rb b/bulletin_board/server/app/commands/report_missing_trustee.rb new file mode 100644 index 00000000..980236c2 --- /dev/null +++ b/bulletin_board/server/app/commands/report_missing_trustee.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# A command with all the business logic to report a missing trustee during tally +class ReportMissingTrustee < Rectify::Command + include LogEntryCommand + + # Public: Initializes the command. + # + # authority - The authority sender of the missing trustee report + # message_id - The message identifier + # signed_data - The signed message received + def initialize(authority, message_id, signed_data) + @client = @authority = authority + @message_id = message_id + @signed_data = signed_data + end + + # Executes the command. Broadcasts these events: + # + # - :processed when everything is valid. + # - :invalid if the received data wasn't valid. + # + # Returns nothing. + def call + return broadcast(:invalid, error) unless + valid_log_entry?("tally", "missing_trustee") + + election.with_lock do + return broadcast(:invalid, error) unless + valid_client?(authority.authority? && election.authority == authority) && + valid_author?(message_identifier.from_authority?) && + valid_step?(election.tally?) && + process_message + + log_entry.election = election + log_entry.save! + create_response_log_entries! + election.save! + end + + broadcast(:ok) + end + + private + + attr_accessor :authority +end diff --git a/bulletin_board/server/app/controllers/sandbox/elections_controller.rb b/bulletin_board/server/app/controllers/sandbox/elections_controller.rb index e582f792..33da6307 100644 --- a/bulletin_board/server/app/controllers/sandbox/elections_controller.rb +++ b/bulletin_board/server/app/controllers/sandbox/elections_controller.rb @@ -82,6 +82,12 @@ def start_tally def tally; end + def report_missing_trustee + pending_message = bulletin_board_client.report_missing_trustee(election_id, params[:trustee_id]) + + render json: pending_message.to_json + end + def publish_results bulletin_board_client.publish_results(election_id) go_back diff --git a/bulletin_board/server/app/graphql/mutations/report_missing_trustee_mutation.rb b/bulletin_board/server/app/graphql/mutations/report_missing_trustee_mutation.rb new file mode 100644 index 00000000..1ecb884b --- /dev/null +++ b/bulletin_board/server/app/graphql/mutations/report_missing_trustee_mutation.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Mutations + class ReportMissingTrusteeMutation < BaseMutation + argument :message_id, String, required: true + argument :signed_data, String, required: true + + field :pending_message, Types::PendingMessageType, null: true + field :error, String, null: true + + def resolve(message_id:, signed_data:) + authority = find_authority(message_id) + + return { error: "Authority not found" } unless authority + + result = { error: "There was an error reporting the missing trustee." } + + EnqueueMessage.call(authority, message_id, signed_data, ReportMissingTrusteeJob) do + on(:ok) do |pending_message| + result = { pending_message: pending_message } + end + end + + result + end + end +end diff --git a/bulletin_board/server/app/graphql/types/mutation_type.rb b/bulletin_board/server/app/graphql/types/mutation_type.rb index e9e6d9d2..667bf742 100644 --- a/bulletin_board/server/app/graphql/types/mutation_type.rb +++ b/bulletin_board/server/app/graphql/types/mutation_type.rb @@ -10,6 +10,7 @@ class MutationType < Types::BaseObject field :end_vote, mutation: Mutations::EndVoteMutation field :start_tally, mutation: Mutations::StartTallyMutation field :process_tally_step, mutation: Mutations::ProcessTallyStepMutation + field :report_missing_trustee, mutation: Mutations::ReportMissingTrusteeMutation field :publish_results, mutation: Mutations::PublishResultsMutation field :reset_test_database, mutation: Mutations::ResetTestDatabaseMutation end diff --git a/bulletin_board/server/app/jobs/report_missing_trustee_job.rb b/bulletin_board/server/app/jobs/report_missing_trustee_job.rb new file mode 100644 index 00000000..5249d719 --- /dev/null +++ b/bulletin_board/server/app/jobs/report_missing_trustee_job.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# A job to process missing trustee reports +class ReportMissingTrusteeJob < ApplicationJob + include ProcessEnqueuedMessage + + command_class ReportMissingTrustee + queue_as :tally +end diff --git a/bulletin_board/server/app/services/voting_scheme/dummy/bulletin_board.rb b/bulletin_board/server/app/services/voting_scheme/dummy/bulletin_board.rb index 7467bb27..6ac56f43 100644 --- a/bulletin_board/server/app/services/voting_scheme/dummy/bulletin_board.rb +++ b/bulletin_board/server/app/services/voting_scheme/dummy/bulletin_board.rb @@ -18,7 +18,7 @@ module Dummy class BulletinBoard < VotingScheme::BulletinBoard include Dummy - RESULTS = ["tally.share", "end_tally"].freeze + RESULTS = ["tally.share", "tally.compensation", "end_tally"].freeze private @@ -86,6 +86,9 @@ def process_start_tally_message(_message_identifier, _message, _content) end end + state[:compensations] = {} + state[:joint_compensations] = {} + state[:compensated] = 0 state[:shares] = [] state[:joint_shares] = build_questions_struct(1) @@ -93,8 +96,36 @@ def process_start_tally_message(_message_identifier, _message, _content) append_content results end - def process_tally_message(message_identifier, _message, content) + def process_tally_message(message_identifier, message, content) + if message_identifier.subtype == "missing_trustee" + state[:compensations][message[:trustee_id]] = [] + state[:joint_compensations][message[:trustee_id]] = build_questions_struct(1) + + return + end + raise RejectedMessage, "The owner_id doesn't match the sender trustee" if content[:owner_id] != message_identifier.author_id + + case message_identifier.subtype + when "share" + process_tally_share_message content + when "compensation" + process_compensation_message content + end + + return unless state[:shares].count + state[:compensated] == election.trustees.count + + results = build_questions_struct(0) + state[:joint_shares].each do |question, answers| + answers.each do |answer, joint_share| + results[question][answer] = ((joint_share / state[:joint_election_key])**(1.0 / state[:trustees].count)).round + end + end + + emit_response "end_tally", results: results + end + + def process_tally_share_message(content) raise RejectedMessage, "The trustee already sent their share" if state[:shares].include?(content[:owner_id]) state[:shares] << content[:owner_id] @@ -104,17 +135,27 @@ def process_tally_message(message_identifier, _message, content) state[:joint_shares][question][answer] *= share end end + end - return unless state[:shares].count == election.trustees.count + def process_compensation_message(content) + compensation = state[:compensations][content[:trustee_id]] + raise RejectedMessage, "The trustee already sent their compensation for #{content[:trustee_id]}" if compensation.include?(content[:owner_id]) - results = build_questions_struct(0) - state[:joint_shares].each do |question, answers| - answers.each do |answer, joint_share| - results[question][answer] = ((joint_share / state[:joint_election_key])**(1.0 / state[:trustees].count)).round + compensation << content[:owner_id] + content[:contests].each do |question, answers| + answers.each do |answer, value| + state[:joint_compensations][content[:trustee_id]][question][answer] *= value end end - emit_response "end_tally", results: results + return unless state[:compensations].count + state[:compensations][content[:trustee_id]].count == election.trustees.count + + state[:joint_compensations][content[:trustee_id]].each do |question, answers| + answers.each do |answer, value| + state[:joint_shares][question][answer] *= (value.round**(1.0 / state[:shares].count)).round + end + end + state[:compensated] += 1 end def build_questions_struct(initial_value) diff --git a/bulletin_board/server/app/services/voting_scheme/electionguard/bulletin_board.rb b/bulletin_board/server/app/services/voting_scheme/electionguard/bulletin_board.rb index 799ea869..b2eb85af 100644 --- a/bulletin_board/server/app/services/voting_scheme/electionguard/bulletin_board.rb +++ b/bulletin_board/server/app/services/voting_scheme/electionguard/bulletin_board.rb @@ -10,7 +10,7 @@ module Electionguard class BulletinBoard < VotingScheme::BulletinBoard include Electionguard - RESULTS = ["tally.trustee_share", "end_tally"].freeze + RESULTS = ["tally.trustee_share", "tally.compensations", "end_tally"].freeze delegate :backup, to: :state diff --git a/bulletin_board/server/app/views/sandbox/elections/new.html.erb b/bulletin_board/server/app/views/sandbox/elections/new.html.erb index fc82328e..10e91044 100644 --- a/bulletin_board/server/app/views/sandbox/elections/new.html.erb +++ b/bulletin_board/server/app/views/sandbox/elections/new.html.erb @@ -1,4 +1,4 @@ -<%= stylesheet_link_tag "sandbox/elections/new" %> +<%= stylesheet_link_tag "sandbox/new" %>
@@ -135,4 +135,4 @@
-<%= javascript_include_tag "sandbox/elections/new" %> \ No newline at end of file +<%= javascript_include_tag "sandbox/new" %> \ No newline at end of file diff --git a/bulletin_board/server/app/views/sandbox/elections/tally.html.erb b/bulletin_board/server/app/views/sandbox/elections/tally.html.erb index b0ad81d0..21c4d3a2 100644 --- a/bulletin_board/server/app/views/sandbox/elections/tally.html.erb +++ b/bulletin_board/server/app/views/sandbox/elections/tally.html.erb @@ -31,9 +31,10 @@ <%= trustee.unique_id %> <%= trustee.name %> - <%= link_to "Start", "#", class: "start-button" %> + <%= link_to "Start", "#", class: "start-button button" %> + <%= link_to "Report as missing", "#", class: "report-missing button", data: { url: report_missing_trustee_sandbox_election_path(election, trustee_id: trustee.slug) } %> <%= link_to "Generate backup", "#", class: "generate-backup-button button" %> -