This commit is contained in:
Jens L
2020-09-11 23:21:11 +02:00
committed by GitHub
parent 3f5d30e6fe
commit 23cccebb96
59 changed files with 403 additions and 254 deletions

View File

@ -2,7 +2,7 @@ version: '3.7'
services:
chrome:
image: selenium/standalone-chrome-debug:3.141.59-20200525
image: selenium/standalone-chrome-debug:3.141.59-20200719
volumes:
- /dev/shm:/dev/shm
network_mode: host

View File

@ -1,13 +1,12 @@
"""Test Enroll flow"""
from time import sleep
from sys import platform
from typing import Any, Dict, Optional
from unittest.case import skipUnless
from django.test import override_settings
from docker import DockerClient, from_env
from docker.models.containers import Container
from docker.types import Healthcheck
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec
from structlog import get_logger
from e2e.utils import USER, SeleniumTestCase
from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
@ -18,41 +17,23 @@ from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage
from passbook.stages.user_login.models import UserLoginStage
from passbook.stages.user_write.models import UserWriteStage
LOGGER = get_logger()
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestFlowsEnroll(SeleniumTestCase):
"""Test Enroll flow"""
def setUp(self):
self.container = self.setup_client()
super().setUp()
def setup_client(self) -> Container:
"""Setup test IdP container"""
client: DockerClient = from_env()
container = client.containers.run(
image="mailhog/mailhog:v1.0.1",
detach=True,
network_mode="host",
auto_remove=True,
healthcheck=Healthcheck(
def get_container_specs(self) -> Optional[Dict[str, Any]]:
return {
"image": "mailhog/mailhog:v1.0.1",
"detach": True,
"network_mode": "host",
"auto_remove": True,
"healthcheck": Healthcheck(
test=["CMD", "wget", "--spider", "http://localhost:8025"],
interval=5 * 100 * 1000000,
start_period=1 * 100 * 1000000,
),
)
while True:
container.reload()
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
if status == "healthy":
return container
LOGGER.info("Container failed healthcheck")
sleep(1)
def tearDown(self):
self.container.kill()
super().tearDown()
}
def test_enroll_2_step(self):
"""Test 2-step enroll flow"""
@ -220,21 +201,25 @@ class TestFlowsEnroll(SeleniumTestCase):
self.driver.find_element(By.ID, "id_name").send_keys("some name")
self.driver.find_element(By.ID, "id_email").send_keys("foo@bar.baz")
self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click()
sleep(3)
# Wait for the success message so we know the email is sent
self.wait.until(
ec.presence_of_element_located((By.CSS_SELECTOR, ".pf-c-form > p"))
)
# Open Mailhog
self.driver.get("http://localhost:8025")
# Click on first message
self.wait.until(
ec.presence_of_element_located((By.CLASS_NAME, "msglist-message"))
)
self.driver.find_element(By.CLASS_NAME, "msglist-message").click()
sleep(3)
self.driver.switch_to.frame(self.driver.find_element(By.CLASS_NAME, "tab-pane"))
self.driver.find_element(By.ID, "confirm").click()
self.driver.close()
self.driver.switch_to.window(self.driver.window_handles[0])
# We're now logged in
sleep(3)
self.wait.until(
ec.presence_of_element_located(
(By.XPATH, "//a[contains(@href, '/-/user/')]")

View File

@ -1,10 +1,14 @@
"""test default login flow"""
from sys import platform
from unittest.case import skipUnless
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from e2e.utils import USER, SeleniumTestCase
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestFlowsLogin(SeleniumTestCase):
"""test default login flow"""

View File

@ -1,7 +1,6 @@
"""test stage setup flows (password change)"""
import string
from random import SystemRandom
from time import sleep
from sys import platform
from unittest.case import skipUnless
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
@ -9,9 +8,11 @@ from selenium.webdriver.common.keys import Keys
from e2e.utils import USER, SeleniumTestCase
from passbook.core.models import User
from passbook.flows.models import Flow, FlowDesignation
from passbook.providers.oauth2.generators import generate_client_secret
from passbook.stages.password.models import PasswordStage
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestFlowsStageSetup(SeleniumTestCase):
"""test stage setup flows"""
@ -27,10 +28,7 @@ class TestFlowsStageSetup(SeleniumTestCase):
stage.change_flow = flow
stage.save()
new_password = "".join(
SystemRandom().choice(string.ascii_uppercase + string.digits)
for _ in range(8)
)
new_password = generate_client_secret()
self.driver.get(
f"{self.live_server_url}/flows/default-authentication-flow/?next=%2F"
@ -48,7 +46,7 @@ class TestFlowsStageSetup(SeleniumTestCase):
self.driver.find_element(By.ID, "id_password_repeat").send_keys(new_password)
self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click()
sleep(2)
self.wait_for_url(self.url("passbook_core:user-settings"))
# Because USER() is cached, we need to get the user manually here
user = User.objects.get(username=USER().username)
self.assertTrue(user.check_password(new_password))

View File

@ -1,12 +1,11 @@
"""test OAuth Provider flow"""
from time import sleep
from sys import platform
from typing import Any, Dict, Optional
from unittest.case import skipUnless
from docker import DockerClient, from_env
from docker.models.containers import Container
from docker.types import Healthcheck
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from structlog import get_logger
from e2e.utils import USER, SeleniumTestCase
from passbook.core.models import Application
@ -19,32 +18,29 @@ from passbook.providers.oauth2.generators import (
)
from passbook.providers.oauth2.models import ClientTypes, OAuth2Provider, ResponseTypes
LOGGER = get_logger()
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestProviderOAuth2Github(SeleniumTestCase):
"""test OAuth Provider flow"""
def setUp(self):
self.client_id = generate_client_id()
self.client_secret = generate_client_secret()
self.container = self.setup_client()
super().setUp()
def setup_client(self) -> Container:
def get_container_specs(self) -> Optional[Dict[str, Any]]:
"""Setup client grafana container which we test OAuth against"""
client: DockerClient = from_env()
container = client.containers.run(
image="grafana/grafana:7.1.0",
detach=True,
network_mode="host",
auto_remove=True,
healthcheck=Healthcheck(
return {
"image": "grafana/grafana:7.1.0",
"detach": True,
"network_mode": "host",
"auto_remove": True,
"healthcheck": Healthcheck(
test=["CMD", "wget", "--spider", "http://localhost:3000"],
interval=5 * 100 * 1000000,
start_period=1 * 100 * 1000000,
),
environment={
"environment": {
"GF_AUTH_GITHUB_ENABLED": "true",
"GF_AUTH_GITHUB_ALLOW_SIGN_UP": "true",
"GF_AUTH_GITHUB_CLIENT_ID": self.client_id,
@ -61,22 +57,10 @@ class TestProviderOAuth2Github(SeleniumTestCase):
),
"GF_LOG_LEVEL": "debug",
},
)
while True:
container.reload()
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
if status == "healthy":
return container
LOGGER.info("Container failed healthcheck")
sleep(1)
def tearDown(self):
self.container.kill()
super().tearDown()
}
def test_authorization_consent_implied(self):
"""test OAuth Provider flow (default authorization flow with implied consent)"""
sleep(1)
# Bootstrap all needed objects
authorization_flow = Flow.objects.get(
slug="default-provider-authorization-implicit-consent"
@ -129,7 +113,6 @@ class TestProviderOAuth2Github(SeleniumTestCase):
def test_authorization_consent_explicit(self):
"""test OAuth Provider flow (default authorization flow with explicit consent)"""
sleep(1)
# Bootstrap all needed objects
authorization_flow = Flow.objects.get(
slug="default-provider-authorization-explicit-consent"
@ -167,8 +150,13 @@ class TestProviderOAuth2Github(SeleniumTestCase):
By.XPATH, "/html/body/div[2]/div/main/div/form/div[2]/ul/li[1]"
).text,
)
sleep(1)
self.driver.find_element(By.CSS_SELECTOR, "[type=submit]").click()
self.driver.find_element(
By.CSS_SELECTOR,
(
"form[action='/flows/b/default-provider-authorization-explicit-consent/'] "
"[type=submit]"
),
).click()
self.wait_for_url("http://localhost:3000/?orgId=1")
self.driver.find_element(By.XPATH, "//a[contains(@href, '/profile')]").click()
@ -197,7 +185,6 @@ class TestProviderOAuth2Github(SeleniumTestCase):
def test_denied(self):
"""test OAuth Provider flow (default authorization flow, denied)"""
sleep(1)
# Bootstrap all needed objects
authorization_flow = Flow.objects.get(
slug="default-provider-authorization-explicit-consent"

View File

@ -1,8 +1,9 @@
"""test OAuth2 OpenID Provider flow"""
from sys import platform
from time import sleep
from typing import Any, Dict, Optional
from unittest.case import skipUnless
from docker import DockerClient, from_env
from docker.models.containers import Container
from docker.types import Healthcheck
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
@ -34,29 +35,27 @@ from passbook.providers.oauth2.models import (
LOGGER = get_logger()
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestProviderOAuth2OIDC(SeleniumTestCase):
"""test OAuth with OpenID Provider flow"""
def setUp(self):
self.client_id = generate_client_id()
self.client_secret = generate_client_secret()
self.container = self.setup_client()
super().setUp()
def setup_client(self) -> Container:
"""Setup client grafana container which we test OIDC against"""
client: DockerClient = from_env()
container = client.containers.run(
image="grafana/grafana:7.1.0",
detach=True,
network_mode="host",
auto_remove=True,
healthcheck=Healthcheck(
def get_container_specs(self) -> Optional[Dict[str, Any]]:
return {
"image": "grafana/grafana:7.1.0",
"detach": True,
"network_mode": "host",
"auto_remove": True,
"healthcheck": Healthcheck(
test=["CMD", "wget", "--spider", "http://localhost:3000"],
interval=5 * 100 * 1000000,
start_period=1 * 100 * 1000000,
),
environment={
"environment": {
"GF_AUTH_GENERIC_OAUTH_ENABLED": "true",
"GF_AUTH_GENERIC_OAUTH_CLIENT_ID": self.client_id,
"GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET": self.client_secret,
@ -72,18 +71,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
),
"GF_LOG_LEVEL": "debug",
},
)
while True:
container.reload()
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
if status == "healthy":
return container
LOGGER.info("Container failed healthcheck")
sleep(1)
def tearDown(self):
self.container.kill()
super().tearDown()
}
def test_redirect_uri_error(self):
"""test OpenID Provider flow (invalid redirect URI, check error message)"""

View File

@ -1,5 +1,7 @@
"""test SAML Provider flow"""
from sys import platform
from time import sleep
from unittest.case import skipUnless
from docker import DockerClient, from_env
from docker.models.containers import Container
@ -23,6 +25,7 @@ from passbook.providers.saml.models import (
LOGGER = get_logger()
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestProviderSAML(SeleniumTestCase):
"""test SAML Provider flow"""
@ -60,10 +63,6 @@ class TestProviderSAML(SeleniumTestCase):
LOGGER.info("Container failed healthcheck")
sleep(1)
def tearDown(self):
self.container.kill()
super().tearDown()
def test_sp_initiated_implicit(self):
"""test SAML Provider flow SP-initiated flow (implicit consent)"""
# Bootstrap all needed objects

View File

@ -1,5 +1,7 @@
"""test SAML Source"""
from sys import platform
from time import sleep
from unittest.case import skipUnless
from docker import DockerClient, from_env
from docker.models.containers import Container
@ -68,6 +70,7 @@ Sm75WXsflOxuTn08LbgGc4s=
-----END PRIVATE KEY-----"""
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestSourceSAML(SeleniumTestCase):
"""test SAML Source flow"""
@ -103,10 +106,6 @@ class TestSourceSAML(SeleniumTestCase):
LOGGER.info("Container failed healthcheck")
sleep(1)
def tearDown(self):
self.container.kill()
super().tearDown()
def test_idp_redirect(self):
"""test SAML Source With redirect binding"""
sleep(1)

View File

@ -1,8 +1,9 @@
"""test OAuth Source"""
from os.path import abspath
from time import sleep
from sys import platform
from typing import Any, Dict, Optional
from unittest.case import skipUnless
from docker import DockerClient, from_env
from docker.models.containers import Container
from docker.types import Healthcheck
from selenium.webdriver.common.by import By
@ -21,6 +22,7 @@ CONFIG_PATH = "/tmp/dex.yml"
LOGGER = get_logger()
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestSourceOAuth(SeleniumTestCase):
"""test OAuth Source flow"""
@ -28,7 +30,7 @@ class TestSourceOAuth(SeleniumTestCase):
def setUp(self):
self.client_secret = generate_client_secret()
self.container = self.setup_client()
self.prepare_dex_config()
super().setUp()
def prepare_dex_config(self):
@ -66,34 +68,23 @@ class TestSourceOAuth(SeleniumTestCase):
with open(CONFIG_PATH, "w+") as _file:
safe_dump(config, _file)
def setup_client(self) -> Container:
"""Setup test Dex container"""
self.prepare_dex_config()
client: DockerClient = from_env()
container = client.containers.run(
image="quay.io/dexidp/dex:v2.24.0",
detach=True,
network_mode="host",
auto_remove=True,
command="serve /config.yml",
healthcheck=Healthcheck(
def get_container_specs(self) -> Optional[Dict[str, Any]]:
return {
"image": "quay.io/dexidp/dex:v2.24.0",
"detach": True,
"network_mode": "host",
"auto_remove": True,
"command": "serve /config.yml",
"healthcheck": Healthcheck(
test=["CMD", "wget", "--spider", "http://localhost:5556/dex/healthz"],
interval=5 * 100 * 1000000,
start_period=1 * 100 * 1000000,
),
volumes={abspath(CONFIG_PATH): {"bind": "/config.yml", "mode": "ro"}},
)
while True:
container.reload()
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
if status == "healthy":
return container
LOGGER.info("Container failed healthcheck")
sleep(1)
"volumes": {abspath(CONFIG_PATH): {"bind": "/config.yml", "mode": "ro"}},
}
def create_objects(self):
"""Create required objects"""
sleep(1)
# Bootstrap all needed objects
authentication_flow = Flow.objects.get(slug="default-source-authentication")
enrollment_flow = Flow.objects.get(slug="default-source-enrollment")
@ -111,10 +102,6 @@ class TestSourceOAuth(SeleniumTestCase):
consumer_secret=self.client_secret,
)
def tearDown(self):
self.container.kill()
super().tearDown()
def test_oauth_enroll(self):
"""test OAuth Source With With OIDC"""
self.create_objects()
@ -141,6 +128,7 @@ class TestSourceOAuth(SeleniumTestCase):
)
self.driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
self.wait.until(ec.presence_of_element_located((By.NAME, "username")))
# At this point we've been redirected back
# and we're asked for the username
self.driver.find_element(By.NAME, "username").click()

View File

@ -4,13 +4,16 @@ from glob import glob
from importlib.util import module_from_spec, spec_from_file_location
from inspect import getmembers, isfunction
from os import environ, makedirs
from time import time
from time import sleep, time
from typing import Any, Dict, Optional
from django.apps import apps
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.db import connection, transaction
from django.db.utils import IntegrityError
from django.shortcuts import reverse
from docker import DockerClient, from_env
from docker.models.containers import Container
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.remote.webdriver import WebDriver
@ -30,15 +33,35 @@ def USER() -> User: # noqa
class SeleniumTestCase(StaticLiveServerTestCase):
"""StaticLiveServerTestCase which automatically creates a Webdriver instance"""
container: Optional[Container] = None
def setUp(self):
super().setUp()
makedirs("selenium_screenshots/", exist_ok=True)
self.driver = self._get_driver()
self.driver.maximize_window()
self.driver.implicitly_wait(30)
self.wait = WebDriverWait(self.driver, 50)
self.driver.implicitly_wait(10)
self.wait = WebDriverWait(self.driver, 30)
self.apply_default_data()
self.logger = get_logger()
if specs := self.get_container_specs():
self.container = self._start_container(specs)
def _start_container(self, specs: Dict[str, Any]) -> Container:
client: DockerClient = from_env()
container = client.containers.run(**specs)
while True:
container.reload()
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
if status == "healthy":
return container
self.logger.info("Container failed healthcheck")
sleep(1)
def get_container_specs(self) -> Optional[Dict[str, Any]]:
"""Optionally get container specs which will launched on setup, wait for the container to
be healthy, and deleted again on tearDown"""
return None
def _get_driver(self) -> WebDriver:
return webdriver.Remote(
@ -57,6 +80,8 @@ class SeleniumTestCase(StaticLiveServerTestCase):
self.logger.warning(
line["message"], source=line["source"], level=line["level"]
)
if self.container:
self.container.kill()
self.driver.quit()
super().tearDown()