diff --git a/.flake8 b/.flake8 index 02142048..80899e90 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,3 @@ [flake8] extend-ignore = E501, W504, W503, E126 +exclude = lib*, bin diff --git a/.gitignore b/.gitignore index 876720ae..da4f8123 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ -/test cert* .*.sw* /docs /repos +bin/ +lib*/ +pyvenv.cfg +CACHEDIR.TAG diff --git a/.pytest.ini b/.pytest.ini new file mode 100644 index 00000000..049e247a --- /dev/null +++ b/.pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = + test diff --git a/README.md b/README.md index 80ac9608..c0e7b06b 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,17 @@ Section 3: Testing and basic functionality - In order to verify that singularity is behaving correctly, you should run the test suite. - - You will need to install some host packages needed by the testing script: flake8, shellcheck, jq, and curl. -On Fedora these packages can be obtained by running `sudo dnf install -y python3-flake8 shellcheck jq curl`. + - You will need to install some host packages needed by the tests: shellcheck and jq - - Now you can run `./test.sh`. If you followed the directions, the tests should pass. + On Fedora these packages can be obtained by running `sudo dnf install -y ShellCheck jq` - - Note that recent versions of Fedora ship with `curl-minimal` rather than `curl`, and that the former does not support pop3s. -If, when running the tests, you encounter an error like `"pop3s: protocol not supported"` you can first try `dnf swap libcurl-minimal libcurl`-- -failing that, you may need to run `dnf download libcurl.$(uname -m) && sudo dnf install --allowerasing ./libcurl*.rpm && rm libcurl*.rpm`. + - You will need to install some python packages needed by the tests + + - Use a python virtual environment to obtain the correct versions: `virtualenv . && pip install -r requirements.txt`, assuming you have both `virtualenv` and `pip` installed + +On Fedora the packages needed to run those last two commands can be obtained by running `sudo dnf install -y python-virtualenv python-pip` + + - Now you can run `pytest`. If you followed the directions, the tests should pass. - You will not be able to access the services using a normal web browser or email client without one more step as the services listening on unix sockets instead of TCP ports. diff --git a/extenginx/Containerfile b/extenginx/Containerfile index 82bb2a0f..e4ea34ab 100644 --- a/extenginx/Containerfile +++ b/extenginx/Containerfile @@ -5,6 +5,9 @@ RUN apk update && apk upgrade && apk add \ openssl \ ; + +ARG NGINX_HOSTNAME=localhost + RUN mkdir /etc/ssl/nginx && \ cd /etc/ssl/nginx && \ openssl \ @@ -12,23 +15,25 @@ RUN mkdir /etc/ssl/nginx && \ -genparam \ -algorithm DH \ -pkeyopt dh_paramgen_prime_len:2048 \ - -out ssl-dhparams.pem \ + -out ssl-dhparams.pem && \ + printf "[req]\n \ +distinguished_name=req\n \ +[ v3_ext ]\n \ +subjectAltName=DNS:${NGINX_HOSTNAME},IP:127.0.0.1\n" > openssl.cnf \ && \ : -ARG NGINX_HOSTNAME=localhost - RUN cd /etc/ssl/nginx && \ - openssl \ - req \ - -sha256 \ - -newkey rsa:4096 \ + openssl req \ -x509 \ + -nodes \ -days 133337 \ - -subj "/CN=${NGINX_HOSTNAME}" \ - -noenc \ + -newkey rsa:2048 \ -keyout privkey.pem \ -out fullchain.pem \ + -subj "/CN=${NGINX_HOSTNAME}" \ + -extensions v3_ext \ + -config openssl.cnf \ && \ : diff --git a/git/admin.sh b/git/admin.sh index f3a9e46e..bbd5189a 100755 --- a/git/admin.sh +++ b/git/admin.sh @@ -1,6 +1,6 @@ #!/bin/sh set -e -DOCKER_COMPOSE=${DOCKER_COMPOSE:-podman-compose} +PODMAN_COMPOSE=${PODMAN_COMPOSE:-podman-compose} -$DOCKER_COMPOSE exec git ./create-repo.sh "$@" +$PODMAN_COMPOSE exec git ./create-repo.sh "$@" diff --git a/orbit/warpdrive.sh b/orbit/warpdrive.sh index 8d92572d..c78e8158 100755 --- a/orbit/warpdrive.sh +++ b/orbit/warpdrive.sh @@ -4,6 +4,6 @@ set -e -DOCKER_COMPOSE=${DOCKER_COMPOSE:-podman-compose} +PODMAN_COMPOSE=${PODMAN_COMPOSE:-podman-compose} -$DOCKER_COMPOSE exec orbit ./hyperspace.py "$@" +$PODMAN_COMPOSE exec orbit ./hyperspace.py "$@" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..6b7ac8b6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +certifi==2025.1.31 +charset-normalizer==3.4.1 +flake8==7.1.2 +idna==3.10 +iniconfig==2.0.0 +mccabe==0.7.0 +packaging==24.2 +pluggy==1.5.0 +pycodestyle==2.12.1 +pyflakes==3.2.0 +pytest==8.3.4 +requests==2.31.0 +requests-unixsocket==0.3.0 +urllib3==2.3.0 diff --git a/script-lint.sh b/script-lint.sh index 10cf94fa..ebcec035 100755 --- a/script-lint.sh +++ b/script-lint.sh @@ -6,7 +6,6 @@ require shellcheck set -ex shellcheck script-lint.sh -shellcheck test.sh shellcheck orbit/warpdrive.sh shellcheck denis/configure.sh shellcheck mailman/inspector.sh diff --git a/test.sh b/test.sh deleted file mode 100755 index 7e13f23f..00000000 --- a/test.sh +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/env bash - -# Testing script for singularity and orbit - -# This line: -# - aborts the script after any pipeline returns nonzero (e) -# - shows all commands as they are run (x) -# - sets any dereference of an unset variable to trigger an error (u) -# - causes the return value of a pipeline to be the nonzero return value -# of the furthest right failing command or zero if no command failed (o pipefail) -set -exuo pipefail - -DOCKER=${DOCKER:-podman} -DOCKER_COMPOSE=${DOCKER_COMPOSE:-podman-compose} - -require() { command -v "$1" > /dev/null || { echo "error: $1 command required yet absent" ; exit 1 ; } ; } -require curl -require jq -require flake8 -require "${DOCKER}" -require "${DOCKER_COMPOSE}" - -# Check for shell script style compliance with shellcheck -./script-lint.sh - -# Check python style compliance -flake8 - -# Create test dir if it does not exist yet -mkdir -p test - -# Reset the test directory -rm -f test/* - -HOSTNAME_FROM_DOTENV="$(sh -c ' -set -o allexport -. ./.env -exec jq -r -n "env.SINGULARITY_HOSTNAME" -')" - -SINGULARITY_HOSTNAME=${SINGULARITY_HOSTNAME:-"${HOSTNAME_FROM_DOTENV}"} - -${DOCKER} cp singularity_nginx_1:/etc/ssl/nginx/fullchain.pem test/ca_cert.pem - -CURL_OPTS=( \ ---verbose \ ---cacert test/ca_cert.pem \ ---fail \ ---no-progress-meter \ -) - -# Check that registration fails before user creation -curl --url "https://$SINGULARITY_HOSTNAME/register" \ - --unix-socket ./socks/https.sock \ - "${CURL_OPTS[@]}" \ - --data "student_id=1234" \ - | tee test/register_fail_no_user \ - | grep "msg = no such student" - -# Check that login fails before user creation -curl --url "https://$SINGULARITY_HOSTNAME/login" \ - --unix-socket ./socks/https.sock \ - "${CURL_OPTS[@]}" \ - --data "username=user&password=pass" \ - | tee test/login_fail_no_user \ - | grep "msg = authentication failure" - -# Check that we can create a user -orbit/warpdrive.sh -u user -i 1234 -n - -# Check that registration fails with incorrect student id -curl --url "https://$SINGULARITY_HOSTNAME/register" \ - --unix-socket ./socks/https.sock \ - "${CURL_OPTS[@]}" \ - --data "student_id=123" \ - | tee test/register_fail_wrong \ - | grep "msg = no such student" - -# Check that registration succeeds with correct student id -curl --url "https://$SINGULARITY_HOSTNAME/register" \ - --unix-socket ./socks/https.sock \ - "${CURL_OPTS[@]}" \ - --data "student_id=1234" \ - | tee test/register_success \ - | grep "msg = welcome to the classroom" - -REGISTER_PASS="$(sed -nr 's/.*Password: ([^<]*).*/\1/p' < test/register_success | tr -d '\n')" - -# Check that registration fails when student id is used for a second time -curl --url "https://$SINGULARITY_HOSTNAME/register" \ - --unix-socket ./socks/https.sock \ - "${CURL_OPTS[@]}" \ - --data "student_id=1234" \ - | tee test/register_fail_duplicate \ - | grep "msg = no such student" - -# Check that login fails when credentials are invalid -curl --url "https://$SINGULARITY_HOSTNAME/login" \ - --unix-socket ./socks/https.sock \ - "${CURL_OPTS[@]}" \ - --data "username=user&password=invalid" \ - | tee test/login_fail_invalid \ - | grep "msg = authentication failure" - -# Check that login succeeds when credentials are valid -curl --url "https://$SINGULARITY_HOSTNAME/login" \ - --unix-socket ./socks/https.sock \ - "${CURL_OPTS[@]}" \ - --data "username=user&password=${REGISTER_PASS}" \ - | tee test/login_success \ - | grep "msg = user authenticated by password" - -# Check that the user can get the empty list of email on the server -curl --url "pop3s://$SINGULARITY_HOSTNAME" \ - --unix-socket ./socks/pop3s.sock \ - "${CURL_OPTS[@]}" \ - --user "user:${REGISTER_PASS}" \ - | tee test/pop_get_empty \ - | diff <(printf '\r\n') /dev/stdin - -CR=$(printf "\r") -# Check that the user can send a message to the server -( -curl --url "smtps://$SINGULARITY_HOSTNAME" \ - --unix-socket ./socks/smtps.sock \ - "${CURL_OPTS[@]}" \ - --mail-from "user@$SINGULARITY_HOSTNAME" \ - --mail-rcpt "other@$SINGULARITY_HOSTNAME" \ - --upload-file - \ - --user "user:${REGISTER_PASS}" < 0 + # Check the last line of the first message, whici will be returned in the second entry of an array from POP3.retr() + assert "Bottom text" in str(pop.retr(1)[1][-1]) + + +def test_freshly_unrestricted_user_obtains_access_to_messages(): + execute_denis('/usr/local/bin/restrict_access /var/lib/email/journal/journal -a resu') + assert len(crew.mkpop('resu')) == 1 + + +def test_matrix_login_success(): + response = crew.post('/_matrix/client/r0/login', json={"type": "m.login.password", "user": f"@user:{SINGULARITY_HOSTNAME}", "password": crew.list['user']}) + assert "access_token" in response.text + + +def test_matrix_login_invalid(): + response = crew.post('/_matrix/client/r0/login', json={"type": "m.login.password", "user": f"@user:{SINGULARITY_HOSTNAME}", "password": "wrongpass"}) + assert "errcode" in response.text and "M_FORBIDDEN" in response.text