Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletions .github/workflows/oidc-integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,44 @@
name: "OIDC Integration Test"
on:
workflow_dispatch:
env:
UBUNTU_VERSION: "24.04"
PYTHON_VERSION: "3.10"
jobs:
cache-pyenv-builder:
name: "Cache layers of the pyenv-builder stage"
runs-on: "ubuntu-24.04"
steps:
- name: "Check out repository"
uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
- name: "Set up Docker Buildx"
uses: "docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f" # v3.12.0
with:
driver-opts: |
network=host
- name: "Generate cache key from requirements"
id: cache_key
run: |
echo "requirements_hash=${{ hashFiles('requirements*.txt', 'requirements*.in', 'pyproject.toml') }}" >> $GITHUB_OUTPUT
- name: "Build base layers"
uses: "docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8" # v6.19.2
with:
context: .
file: ./hack/Dockerfile
target: pyenv-builder
build-args: |
UBUNTU_VERSION=${{ env.UBUNTU_VERSION }}
PYTHON_VERSION=${{ env.PYTHON_VERSION }}
BUILDKIT_INLINE_CACHE=1
push: false
cache-from: |
type=gha,scope=archivematica-tests-base-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}
type=gha,scope=archivematica-tests-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}-${{ steps.cache_key.outputs.requirements_hash }}
cache-to: |
type=gha,scope=archivematica-tests-base-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }},mode=max
type=gha,scope=archivematica-tests-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}-${{ steps.cache_key.outputs.requirements_hash }},mode=max
test:
needs: [cache-pyenv-builder]
name: "Test"
runs-on: "ubuntu-24.04"
steps:
Expand All @@ -17,8 +53,38 @@ jobs:
id: group_id
run: |
echo "group_id=$(id -g)" >> $GITHUB_OUTPUT
- name: "Generate cache key from requirements"
id: cache_key
run: |
echo "requirements_hash=${{ hashFiles('requirements*.txt', 'requirements*.in', 'pyproject.toml') }}" >> $GITHUB_OUTPUT
- name: "Set up buildx"
uses: "docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f" # v3.12.0
with:
driver-opts: |
network=host
- name: "Build integration test image"
uses: "docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8" # v6.19.2
with:
context: .
file: ./hack/Dockerfile
target: archivematica-dashboard-integration-tests
build-args: |
USER_ID=${{ steps.user_id.outputs.user_id }}
GROUP_ID=${{ steps.group_id.outputs.group_id }}
UBUNTU_VERSION=${{ env.UBUNTU_VERSION }}
PYTHON_VERSION=${{ env.PYTHON_VERSION }}
BUILDKIT_INLINE_CACHE=1
tags: archivematica-dashboard-integration-tests:latest
push: false
load: true
cache-from: |
type=gha,scope=archivematica-tests-base-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}
type=gha,scope=archivematica-oidc-integration-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}
type=gha,scope=archivematica-tests-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}-${{ steps.cache_key.outputs.requirements_hash }}
cache-to: |
type=gha,scope=archivematica-tests-base-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }},mode=max
type=gha,scope=archivematica-oidc-integration-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }},mode=max
type=gha,scope=archivematica-tests-${{ env.UBUNTU_VERSION }}-${{ env.PYTHON_VERSION }}-${{ steps.cache_key.outputs.requirements_hash }},mode=max
- name: "Run tests"
run: |
./run.sh
Expand All @@ -27,8 +93,9 @@ jobs:
env:
USER_ID: ${{ steps.user_id.outputs.user_id }}
GROUP_ID: ${{ steps.group_id.outputs.group_id }}
UBUNTU_VERSION: "24.04"
PYTHON_VERSION: "3.10"
UBUNTU_VERSION: ${{ env.UBUNTU_VERSION }}
PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
SKIP_DOCKER_BUILD: 1
COMPOSE_DOCKER_CLI_BUILD: 1
DOCKER_BUILDKIT: 1
PYTEST_ADDOPTS: -vv
5 changes: 5 additions & 0 deletions hack/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@
USER archivematica

COPY --chown=${USER_ID}:${GROUP_ID} --from=pyenv-builder --link ${PYENV_DIR} ${PYENV_DIR}
COPY --chown=${USER_ID}:${GROUP_ID} --link . /src

Check warning on line 221 in hack/Dockerfile

View workflow job for this annotation

GitHub Actions / Test archivematica on 3.12

Attempting to Copy file that is excluded by .dockerignore

CopyIgnoredFile: Attempting to Copy file "." that is excluded by .dockerignore More info: https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/

Check warning on line 221 in hack/Dockerfile

View workflow job for this annotation

GitHub Actions / Test archivematica on 3.10

Attempting to Copy file that is excluded by .dockerignore

CopyIgnoredFile: Attempting to Copy file "." that is excluded by .dockerignore More info: https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/

Check warning on line 221 in hack/Dockerfile

View workflow job for this annotation

GitHub Actions / Test migrations on 3.10

Attempting to Copy file that is excluded by .dockerignore

CopyIgnoredFile: Attempting to Copy file "." that is excluded by .dockerignore More info: https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/

Check warning on line 221 in hack/Dockerfile

View workflow job for this annotation

GitHub Actions / Test archivematica on 3.14

Attempting to Copy file that is excluded by .dockerignore

CopyIgnoredFile: Attempting to Copy file "." that is excluded by .dockerignore More info: https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/

Check warning on line 221 in hack/Dockerfile

View workflow job for this annotation

GitHub Actions / Test archivematica on 3.11

Attempting to Copy file that is excluded by .dockerignore

CopyIgnoredFile: Attempting to Copy file "." that is excluded by .dockerignore More info: https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/

Check warning on line 221 in hack/Dockerfile

View workflow job for this annotation

GitHub Actions / Test storage-service on 3.10

Attempting to Copy file that is excluded by .dockerignore

CopyIgnoredFile: Attempting to Copy file "." that is excluded by .dockerignore More info: https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/

Check warning on line 221 in hack/Dockerfile

View workflow job for this annotation

GitHub Actions / Test archivematica on 3.13

Attempting to Copy file that is excluded by .dockerignore

CopyIgnoredFile: Attempting to Copy file "." that is excluded by .dockerignore More info: https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/

Check warning on line 221 in hack/Dockerfile

View workflow job for this annotation

GitHub Actions / Test

Attempting to Copy file that is excluded by .dockerignore

CopyIgnoredFile: Attempting to Copy file "." that is excluded by .dockerignore More info: https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/

ENV PYTHONPATH=/src/src

Expand Down Expand Up @@ -283,6 +283,8 @@
FROM base AS archivematica-dashboard-integration-tests

USER root
ARG USER_ID
ARG GROUP_ID

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
Expand All @@ -295,6 +297,9 @@
&& mkdir -p /var/archivematica/.cache/ms-playwright \
&& python3 -m playwright install firefox

# Copy frontend assets out of where /src is bind-mounted during tests.
COPY --chown=${USER_ID}:${GROUP_ID} --from=archivematica-dashboard-vue-builder --link /src/src/archivematica/dashboard/vue/dist /opt/am-dashboard-dist

# -----------------------------------------------------------------------------

FROM ${TARGET}
7 changes: 6 additions & 1 deletion src/archivematica/dashboard/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,11 @@ def _get_settings_from_file(path):
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = "/media/"

# Allow tests to serve frontend assets from a path not shadowed by bind mounts over /src.
FRONTEND_DIST_DIR = os.environ.get(
"FRONTEND_DIST_DIR", os.path.join(BASE_PATH, "vue", "dist")
)

# Additional locations of static files
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
Expand All @@ -394,7 +399,7 @@ def _get_settings_from_file(path):
("css", os.path.join(BASE_PATH, "media", "css")),
("images", os.path.join(BASE_PATH, "media", "images")),
("vendor", os.path.join(BASE_PATH, "media", "vendor")),
("vue", os.path.join(BASE_PATH, "vue", "dist")),
("vue", FRONTEND_DIST_DIR),
)

# List of finder classes that know how to find static files in
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ services:
start_period: 15s

archivematica-dashboard:
image: "archivematica-dashboard-integration-tests:latest"
build:
context: "../../"
dockerfile: "hack/Dockerfile"
Expand Down Expand Up @@ -85,6 +86,8 @@ services:
OIDC_ACCESS_ATTRIBUTE_MAP_SECONDARY: >
{"given_name": "first_name", "family_name": "last_name", "realm_access": "realm_access"}
OIDC_RP_SIGN_ALGO: "RS256"
# /src is bind-mounted, so point Django staticfiles to the image-owned dist copy.
FRONTEND_DIST_DIR: "/opt/am-dashboard-dist"
volumes:
- "../../:/src"
depends_on:
Expand Down
10 changes: 6 additions & 4 deletions tests/integration/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

cd ${__dir}

docker compose build archivematica-dashboard
if [ -z "${SKIP_DOCKER_BUILD}" ]; then
docker compose build archivematica-dashboard

status=$?
status=$?

if [ $status -ne 0 ]; then
exit $status
if [ $status -ne 0 ]; then
exit $status
fi
fi

docker compose run --rm archivematica-dashboard
Expand Down
12 changes: 10 additions & 2 deletions tests/integration/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,26 @@
from django.contrib.auth.models import AbstractUser
from django.urls import reverse
from playwright.sync_api import Page
from playwright.sync_api import expect
from pytest_django.live_server_helper import LiveServer

if "RUN_INTEGRATION_TESTS" not in os.environ:
pytest.skip("Skipping integration tests", allow_module_level=True)


def click_logout_from_user_menu(page: Page) -> None:
user_menu = page.locator("li.user.dropdown")
expect(user_menu).to_be_visible()
user_menu.evaluate("node => node.classList.add('open')")
page.get_by_role("button", name="Log out").click()


@pytest.mark.django_db
def test_logout_link_logs_out_user(
page: Page, live_server: LiveServer, dashboard_uuid: uuid.UUID, user: AbstractUser
) -> None:
page.goto(live_server.url)

assert page.url == f"{live_server.url}{reverse('accounts:login')}"

page.get_by_label("Username").fill("foobar")
Expand All @@ -24,7 +33,6 @@ def test_logout_link_logs_out_user(

assert page.url == f"{live_server.url}/transfer/"

page.get_by_text("foobar").click()
page.get_by_role("button", name="Log out").click()
click_logout_from_user_menu(page)

assert page.url == f"{live_server.url}{reverse('accounts:login')}"
35 changes: 23 additions & 12 deletions tests/integration/test_oidc_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,30 @@
from django.contrib.auth.models import User
from django.urls import reverse
from playwright.sync_api import Page
from playwright.sync_api import expect
from pytest_django.fixtures import SettingsWrapper
from pytest_django.live_server_helper import LiveServer

if "RUN_INTEGRATION_TESTS" not in os.environ:
pytest.skip("Skipping integration tests", allow_module_level=True)


def open_user_menu(page: Page) -> None:
user_menu = page.locator("li.user.dropdown")
expect(user_menu).to_be_visible()
user_menu.evaluate("node => node.classList.add('open')")


def click_profile_from_user_menu(page: Page) -> None:
open_user_menu(page)
page.get_by_role("link", name="Your profile").click()


def click_logout_from_user_menu(page: Page) -> None:
open_user_menu(page)
page.get_by_role("button", name="Log out").click()


@pytest.mark.django_db
def test_oidc_backend_creates_local_user(
page: Page,
Expand All @@ -27,8 +44,7 @@ def test_oidc_backend_creates_local_user(
page.get_by_role("button", name="Sign In").click()

assert page.url == f"{live_server.url}/transfer/"
page.get_by_text("demo@example.com").click()
page.get_by_role("link", name="Your profile").click()
click_profile_from_user_menu(page)

assert page.url == f"{live_server.url}{reverse('accounts:profile')}"
assert [
Expand Down Expand Up @@ -66,8 +82,7 @@ def test_local_authentication_backend_authenticates_existing_user(

assert page.url == f"{live_server.url}/transfer/"

page.get_by_text("foobar").click()
page.get_by_role("link", name="Your profile").click()
click_profile_from_user_menu(page)

assert page.url == f"{live_server.url}{reverse('accounts:profile')}"
assert [
Expand Down Expand Up @@ -147,8 +162,7 @@ def test_setting_request_parameter_in_local_login_url_redirects_to_secondary_pro
page.get_by_role("button", name="Sign In").click()

assert page.url == f"{live_server.url}/transfer/"
page.get_by_text("supportadmin@example.com").click()
page.get_by_role("link", name="Your profile").click()
click_profile_from_user_menu(page)

assert page.url == f"{live_server.url}{reverse('accounts:profile')}"

Expand All @@ -172,8 +186,7 @@ def test_setting_request_parameter_in_local_login_url_redirects_to_secondary_pro

assert page.url == f"{live_server.url}/transfer/"

page.get_by_text("supportdefault@example.com").click()
page.get_by_role("link", name="Your profile").click()
click_profile_from_user_menu(page)

assert page.url == f"{live_server.url}{reverse('accounts:profile')}"
assert [
Expand Down Expand Up @@ -211,8 +224,7 @@ def test_logging_out_logs_out_user_from_secondary_provider_admin_role(
assert page.url == f"{live_server.url}/transfer/"

# Logging out redirects the user to the login url.
page.get_by_text("supportadmin@example.com").click()
page.get_by_role("button", name="Log out").click()
click_logout_from_user_menu(page)
assert page.url == f"{live_server.url}{reverse('accounts:login')}"

# Logging in through the OIDC provider requires to authenticate again.
Expand Down Expand Up @@ -244,8 +256,7 @@ def test_logging_out_logs_out_user_from_secondary_provider_default_role(
assert page.url == f"{live_server.url}/transfer/"

# Logging out redirects the user to the login url.
page.get_by_text("supportdefault@example.com").click()
page.get_by_role("button", name="Log out").click()
click_logout_from_user_menu(page)
assert page.url == f"{live_server.url}{reverse('accounts:login')}"

# Logging in through the OIDC provider requires to authenticate again.
Expand Down