mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-10-01 22:04:54 +02:00
Remove Cypress based UI tests (#1035)
This commit is contained in:
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
# Test with no features, default features ("") and all except UI tests.
|
||||
# Order: fewest features to most features.
|
||||
args: ["--no-default-features", "", "--features all-except-ui-tests"]
|
||||
args: ["--no-default-features", "", "--features all"]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
@@ -60,13 +60,6 @@ jobs:
|
||||
rust-version: ${{ matrix.rust }}
|
||||
- run: cargo build --verbose ${{ matrix.args }} --locked
|
||||
- run: cargo test --verbose ${{ matrix.args }} -- --test-threads=1 2>&1
|
||||
- name: Archive Cypress UI test image & video captures
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: cypress-ui-test-captures ${{ matrix.os }} ${{ matrix.rust }}
|
||||
path: target/ui/
|
||||
if-no-files-found: ignore
|
||||
|
||||
pykmip-test:
|
||||
name: pykmip-test
|
||||
|
||||
@@ -28,10 +28,7 @@ jobs:
|
||||
with:
|
||||
rust-version: ${{ matrix.rust }}
|
||||
- run: cargo install cargo-tarpaulin
|
||||
# use a long timeout with tarpaulin as UI tests have to docker pull the
|
||||
# cypress.io image which can cause a test timeout with the default tarpaulin
|
||||
# timeout of 1 minute.
|
||||
- run: cargo tarpaulin --locked --verbose --out Html --timeout 900 ${{ matrix.args }}
|
||||
- run: cargo tarpaulin --locked --verbose --out Html ${{ matrix.args }}
|
||||
- name: Archive code coverage results
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
||||
Generated
-49
@@ -50,12 +50,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ascii"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109"
|
||||
|
||||
[[package]]
|
||||
name = "ascii-canvas"
|
||||
version = "3.0.0"
|
||||
@@ -214,12 +208,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chunked_transfer"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.2.5"
|
||||
@@ -336,16 +324,6 @@ dependencies = [
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b37feaa84e6861e00a1f5e5aa8da3ee56d605c9992d33e082786754828e20865"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.79"
|
||||
@@ -975,7 +953,6 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"cryptoki",
|
||||
"ctrlc",
|
||||
"fern",
|
||||
"fslock",
|
||||
"futures",
|
||||
@@ -1003,7 +980,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syslog",
|
||||
"tiny_http",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"toml",
|
||||
@@ -1203,17 +1179,6 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.3"
|
||||
@@ -2168,20 +2133,6 @@ dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny_http"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce51b50006056f590c9b7c3808c3bd70f0d1101666629713866c227d6e58d39"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"chrono",
|
||||
"chunked_transfer",
|
||||
"log",
|
||||
"openssl",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
|
||||
+2
-6
@@ -74,16 +74,15 @@ syslog = "^4.0"
|
||||
|
||||
[features]
|
||||
default = [ "multi-user", "hsm" ]
|
||||
hsm = ["backoff", "kmip", "once_cell", "cryptoki", "r2d2"]
|
||||
multi-user = [ "basic-cookies", "jmespatch/sync", "regex", "oso", "openidconnect", "rpassword", "scrypt", "unicode-normalization", "urlparse" ]
|
||||
static-openssl = [ "openssl/vendored" ]
|
||||
|
||||
# Preview features - not ready for production use
|
||||
rta = []
|
||||
hsm = ["backoff", "kmip", "once_cell", "cryptoki", "r2d2"]
|
||||
|
||||
# Internal features - not for external use
|
||||
all-except-ui-tests = [ "multi-user", "rta", "static-openssl" ]
|
||||
ui-tests = []
|
||||
all = [ "multi-user", "rta", "static-openssl" ]
|
||||
hsm-tests-kmip = [ "hsm" ]
|
||||
hsm-tests-pkcs11 = [ "hsm" ]
|
||||
|
||||
@@ -95,9 +94,6 @@ panic = "abort"
|
||||
[dev-dependencies]
|
||||
regex = "1.5.5"
|
||||
urlparse = "^0.7"
|
||||
# For user management
|
||||
ctrlc = "^3.1"
|
||||
tiny_http = { version = "^0.8", features = ["ssl"] }
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# START DEBIAN PACKAGING
|
||||
|
||||
@@ -506,10 +506,10 @@ In no particular order:
|
||||
|
||||
#### Testing
|
||||
|
||||
Testing the provider code in isolation cannot ensure that the chain of communication from Lagosta
|
||||
via Krill to the OP and back again works as expected and yields an acceptable end user experience. Therefore the
|
||||
majority of the tests use Cypress to drive Lagosta in a browser connected to an instance of Krill which in turn connects
|
||||
to a locally deployed mock OP.
|
||||
Testing of the UI interaction is handled by dedicated tests in the UI repository. Further tests are implemented
|
||||
inline in the Oso rule files, with a few explicit Oso tests also invoked by Krill immediately after Oso is
|
||||
initialized and rule files have been loaded.
|
||||
|
||||
#### Flow
|
||||
|
||||
This implementation supports the [OpenID Connect Authorization Code Flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth)
|
||||
|
||||
@@ -24,8 +24,6 @@ The feature adds several related but distinct capabilities to Krill:
|
||||
- (De)Serialization
|
||||
- (En/De)cryption _(powered by [rust-openssl](https://crates.io/crates/openssl))_
|
||||
|
||||
- Automated browser based testing _(powered by [Cypress](https://www.cypress.io/))_
|
||||
|
||||
- Password hashing support in `krillc`
|
||||
|
||||
- Propagation of the current identity all the way down to the event history
|
||||
|
||||
@@ -7,51 +7,8 @@ At the lowest level, any test that exercises Krill from the outside will exercis
|
||||
external and internal actions require an `Actor` instance, and all external requests must pass authentication and
|
||||
authorization checks.
|
||||
|
||||
However, not all tests will exercise login as that is only used by Lagosta. The existing Krill integration tests use a
|
||||
However, not all tests will exercise login as that is only used by the UI. The existing Krill integration tests use a
|
||||
fixed admin API token without login because that was all Krill supported before, and must continue to work for the
|
||||
direct REST API clients and indirect REST API client via `krillc`.
|
||||
|
||||
Also, as the multi-user feature is aimed entirely (at present) at Lagosta users, it only works if it works from the UI
|
||||
all the way down to the event log, testing at the Krill internal library boundary or the Krill REST API boundary is
|
||||
not sufficient.
|
||||
|
||||
As such most of the multi-user tests use [Cypress](https://cypress.io) to drive a headless browser connecting to a
|
||||
locally launched instance of Krill, and for the OpenID Connect tests a homebrewed mock Rust OpenID Connect provider is
|
||||
run in-process alongside Krill as well.
|
||||
|
||||
For ease Cypress is invoked via Docker as it has an official Docker image which contains everything needed. However,
|
||||
this has not yet been verified as working on Mac OS. Cypress in turn is driven by test suites defined in Javascript.
|
||||
|
||||
As UI based tests can be quite slow they are gated behind their own `ui-tests` feature, which also has the benefit
|
||||
that users without a working Docker setup are still able to run `cargo test`.
|
||||
|
||||
To run the UI tests one must therefore do:
|
||||
|
||||
```
|
||||
cargo test --features ui-tests
|
||||
```
|
||||
|
||||
Cypress has a very useful interactive test run mode which can be launched like so:
|
||||
|
||||
```
|
||||
$ xhost +
|
||||
$ CYPRESS_INTERACTIVE=1 cargo test --features ui-tests <some_test_name>
|
||||
```
|
||||
|
||||
You want the `<some_test_name>` because you want Krill to be setup correctly to run a particular test that you will then run interactively, you don't want Krill to run all tests and constantly be changing the backend state as a result while you try to use Cypress to run a single test suite that has expectations about the state that Krill is in.
|
||||
|
||||
For example you might do:
|
||||
```
|
||||
$ xhost +
|
||||
$ CYPRESS_INTERACTIVE=1 cargo test --features ui-tests multi_user_config_file_with_ta
|
||||
```
|
||||
|
||||
After a short delay a browser window should open with a Cypress welcome message something like this:
|
||||
|
||||

|
||||
|
||||
Dismiss the message and then click on the `multi_user_config_file_with_ta.js` test in the tree of tests that is shown to you, i.e. run the same test as you invoked with `cargo test` so that Krill has the expected configuration and starting conditions.
|
||||
|
||||
You should then see something like this:
|
||||
|
||||

|
||||
Testing of the UI specific aspects of multi-user support is handled by dedicated tests in the UI repository.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,294 +0,0 @@
|
||||
import logging
|
||||
import pytest
|
||||
import rtrlib
|
||||
|
||||
from retrying import retry, RetryError
|
||||
from time import time
|
||||
from operator import attrgetter
|
||||
import krill_ca_api as krill_ca_api_lib
|
||||
import krill_pub_api as krill_pub_api_lib
|
||||
|
||||
from tests.util import krill
|
||||
from tests.util.docker import docker_project, class_service_manager, function_service_manager, run_command, docker_host_fqdn
|
||||
from tests.util.krill import krill_api_config
|
||||
from tests.util.rtr import rtr_fetch_one, roa_to_roa_string
|
||||
from tests.util.relyingparties import *
|
||||
|
||||
from data import *
|
||||
|
||||
|
||||
# Test classes that use this fixture will cause Krill and its dependencies to
|
||||
# be started (and torn down at the end of all tests in the class), and to be
|
||||
# configured with CAs and ROas. If you want many test classes to use the
|
||||
# created resources before they are torn down module scope might then be more
|
||||
# appropriate.
|
||||
@pytest.fixture(scope="class")
|
||||
def krill_with_roas(docker_project, krill_api_config, class_service_manager):
|
||||
#
|
||||
# Define some retry helpers for situations where the API call to Krill
|
||||
# can succeed but Krill may not yet be in the expected state.
|
||||
#
|
||||
def no_retry_if_forbidden(e):
|
||||
"""Return True if we should retry, False otherwise"""
|
||||
return not (isinstance(e, krill_ca_api_lib.ApiException) and e.status == 403)
|
||||
|
||||
def retry_if_not(result):
|
||||
"""Return True if we should retry, False otherwise"""
|
||||
# e.g. return True for empty lists, empty strings, None
|
||||
return not bool(result)
|
||||
|
||||
@retry(
|
||||
stop_max_attempt_number=10,
|
||||
wait_fixed=2000,
|
||||
retry_on_exception=no_retry_if_forbidden,
|
||||
wrap_exception=True)
|
||||
def wait_until_ready():
|
||||
return krill_other_api.is_authorized()
|
||||
|
||||
@retry(
|
||||
stop_max_attempt_number=10,
|
||||
wait_exponential_multiplier=1000,
|
||||
wait_exponential_max=10000,
|
||||
retry_on_result=retry_if_not,
|
||||
wrap_exception=True)
|
||||
def wait_until_ca_has(ca_handle, property, matcher_func):
|
||||
ca = krill_ca_api.get_ca(ca_handle)
|
||||
f = attrgetter(property)
|
||||
cas = f(ca)
|
||||
return [ca for ca in cas if matcher_func(ca)]
|
||||
|
||||
@retry(
|
||||
stop_max_attempt_number=10,
|
||||
wait_exponential_multiplier=1000,
|
||||
wait_exponential_max=10000,
|
||||
retry_on_result=retry_if_not,
|
||||
wrap_exception=True)
|
||||
def wait_until_child_ca_has_at_least_one(parent_handle, child_handle, property):
|
||||
ca = krill_ca_api.get_child_ca(parent_handle, child_handle)
|
||||
f = attrgetter(property)
|
||||
return f(ca)
|
||||
|
||||
@retry(
|
||||
stop_max_attempt_number=10,
|
||||
wait_exponential_multiplier=1000,
|
||||
wait_exponential_max=10000,
|
||||
retry_on_result=retry_if_not,
|
||||
wrap_exception=True)
|
||||
def wait_until_ca_has_resources(ca_handle, asn, ipv4, ipv6):
|
||||
ca = krill_ca_api.get_ca(ca_handle)
|
||||
f = attrgetter("resources")
|
||||
res = f(ca)
|
||||
return set(res.asn) == set(asn) and set(res.ipv4) == set(ipv4) and set(res.ipv6) == set(ipv6)
|
||||
|
||||
#
|
||||
# define some helper functions
|
||||
#
|
||||
def add_ca(ca_handle):
|
||||
logging.info(f'-> Adding CA "{ca_handle}"')
|
||||
krill_ca_api.add_ca(krill_ca_api_lib.AddCARequest(ca_handle))
|
||||
|
||||
logging.info(f'-> Getting RFC 8183 publisher request for CA "{ca_handle}" (API call `get_ca_publisher_request()`)')
|
||||
rfc8183_request = krill_ca_api.get_ca_publisher_request(ca_handle, format='json')
|
||||
|
||||
logging.info(f'-> Submitting RFC 8183 publisher request for CA "{ca_handle}" in exchange for an RFC 8183 repository_response (API call `add_publisher()`)')
|
||||
rfc8183_response = krill_pub_api.add_publisher(rfc8183_request)
|
||||
|
||||
logging.info(f'-> Submitting RFC 8181 repository response for CA "{ca_handle}" (API call `update_ca_repository()`)')
|
||||
krill_ca_api.update_ca_repository(
|
||||
ca_handle,
|
||||
inline_object=krill_ca_api_lib.InlineObject(repository_response=rfc8183_response))
|
||||
logging.info(f'-> Added CA "{ca_handle}"')
|
||||
|
||||
def link_child_ca_under_parent_ca(child_ca_handle, parent_ca_handle, resources):
|
||||
logging.info(f'-> Getting RFC 8183 child request for CA "{child_ca_handle}" (API call `get_ca_child_request()`)')
|
||||
rfc8183_request = krill_ca_api.get_ca_child_request(child_ca_handle, format="json")
|
||||
|
||||
logging.info(f'-> Adding CA "{child_ca_handle}" as a child of "{parent_ca_handle}" (API call `add_child_ca()`)')
|
||||
req = krill_ca_api_lib.AddCAChildRequest(
|
||||
handle=child_ca_handle,
|
||||
resources=resources,
|
||||
id_cert=rfc8183_request.id_cert)
|
||||
krill_ca_api.add_child_ca(parent_ca_handle, req)
|
||||
logging.info(f'-> Added CA "{child_ca_handle} as a child of "{parent_ca_handle}"')
|
||||
|
||||
logging.info(f'-> Waiting for CA "{child_ca_handle}" to be registered as a child of "{parent_ca_handle}"')
|
||||
wait_until_ca_has(parent_ca_handle, 'children', lambda handle: handle == child_ca_handle)
|
||||
|
||||
logging.info(f'-> Waiting for resources of child CA "{child_ca_handle}" to be registered')
|
||||
wait_until_child_ca_has_at_least_one(parent_ca_handle, child_ca_handle, 'entitled_resources.asn')
|
||||
|
||||
def link_parent_ca_above_child_ca(parent_ca_handle, child_ca_handle, resources):
|
||||
logging.info(f'-> Getting RFC 8183 parent response for CA "{child_ca_handle}" (API call `get_child_ca_parent_contact()`)')
|
||||
rfc8183parentresponse = krill_ca_api.get_child_ca_parent_contact(parent_ca_handle, child_ca_handle)
|
||||
|
||||
logging.info(f'-> Adding CA "{parent_ca_handle}" as a parent of "{child_ca_handle}" (API call `add_ca_parent()`)')
|
||||
req = krill_ca_api_lib.AddParentCARequest(
|
||||
handle=parent_ca_handle,
|
||||
contact=rfc8183parentresponse)
|
||||
krill_ca_api.add_ca_parent(child_ca_handle, req)
|
||||
logging.info(f'-> Added CA "{parent_ca_handle}" as a parent of "{child_ca_handle}"')
|
||||
|
||||
logging.info(f'-> Waiting for CA "{parent_ca_handle}" to be registered as a parent of "{child_ca_handle}"')
|
||||
wait_until_ca_has(child_ca_handle, 'parents', lambda ca: ca.handle == parent_ca_handle)
|
||||
|
||||
logging.info(f'-> Waiting for resources of CA "{child_ca_handle}" to be issued:')
|
||||
wait_until_ca_has_resources(child_ca_handle, resources.asn, resources.ipv4, resources.ipv6)
|
||||
|
||||
#
|
||||
# Go!
|
||||
#
|
||||
|
||||
try:
|
||||
# Bring up Krill and its dependencies
|
||||
krill.select_krill_config_file(docker_project, 'krill.conf')
|
||||
class_service_manager.start_services_with_dependencies(docker_project, ['krill'])
|
||||
|
||||
# Strategy: Test then add, don't add then handle failure because that will
|
||||
# cause errors to appear in the Krill server log which can be confusing
|
||||
# when investigating problems.
|
||||
|
||||
# Get the API helper objects we need
|
||||
krill_ca_api_client = krill_ca_api_lib.ApiClient(krill_api_config)
|
||||
krill_ca_api = krill_ca_api_lib.CertificateAuthoritiesApi(krill_ca_api_client)
|
||||
krill_roa_api = krill_ca_api_lib.RouteAuthorizationsApi(krill_ca_api_client)
|
||||
krill_other_api = krill_ca_api_lib.OtherApi(krill_ca_api_client)
|
||||
|
||||
krill_pub_api_client = krill_pub_api_lib.ApiClient(krill_api_config)
|
||||
krill_pub_api = krill_pub_api_lib.PublishersApi(krill_pub_api_client)
|
||||
|
||||
# Define the CA handles that we will work with
|
||||
ta_handle = 'ta'
|
||||
parent_handle = 'parent'
|
||||
child_handle = 'child'
|
||||
|
||||
# Ensure that Krill is ready for our attempts to communicate with it
|
||||
logging.info('Wait till we can connect to Krill...')
|
||||
wait_until_ready()
|
||||
|
||||
#
|
||||
# Create the desired state inside Krill
|
||||
#
|
||||
|
||||
parent_resources = krill_ca_api_lib.Resources(asn=KRILL_PARENT_ASNS, ipv4=KRILL_PARENT_IPV4S, ipv6=KRILL_PARENT_IPV6S)
|
||||
child_resources = krill_ca_api_lib.Resources(asn=KRILL_CHILD_ASNS, ipv4=KRILL_CHILD_IPV4S, ipv6=KRILL_CHILD_IPV6S)
|
||||
|
||||
logging.info(f'Checking if Krill has an embedded TA "{ta_handle}"')
|
||||
ca_handles = [ca.handle for ca in krill_ca_api.list_cas().cas]
|
||||
|
||||
if ta_handle in ca_handles:
|
||||
logging.info(f'Configuring Krill for use with embedded TA "{ta_handle}"')
|
||||
|
||||
logging.info(f'Adding CA "{parent_handle}" if not already present')
|
||||
if not parent_handle in ca_handles:
|
||||
add_ca(parent_handle)
|
||||
|
||||
logging.info(f'Creating TA "{ta_handle}" -> CA "{parent_handle}" relationship if not already present')
|
||||
ta_children = krill_ca_api.get_ca(ta_handle).children
|
||||
if not parent_handle in ta_children:
|
||||
link_child_ca_under_parent_ca(parent_handle, ta_handle, parent_resources)
|
||||
|
||||
logging.info(f'Creating TA "{ta_handle}" <- CA "{parent_handle}" relationship if not already present')
|
||||
if len(krill_ca_api.get_ca(parent_handle).parents) == 0:
|
||||
link_parent_ca_above_child_ca(ta_handle, parent_handle, parent_resources)
|
||||
|
||||
logging.info(f'Adding CA "{child_handle}" if not already present')
|
||||
if not child_handle in ca_handles:
|
||||
add_ca(child_handle)
|
||||
|
||||
logging.info(f'Creating CA "{parent_handle}" -> CA "{child_handle}" relationship if not already present')
|
||||
if len(krill_ca_api.get_ca(parent_handle).children) == 0:
|
||||
link_child_ca_under_parent_ca(child_handle, parent_handle, child_resources)
|
||||
|
||||
logging.info(f'Creating CA "{parent_handle}" <- CA "{child_handle}" relationship if not already present')
|
||||
if len(krill_ca_api.get_ca(child_handle).parents) == 0:
|
||||
link_parent_ca_above_child_ca(parent_handle, child_handle, child_resources)
|
||||
|
||||
logging.info(f'Creating CA "{child_handle}" ROAs if not already present')
|
||||
if len(krill_roa_api.list_route_authorizations(child_handle)) == 0:
|
||||
delta = krill_ca_api_lib.ROADelta(added=TEST_ROAS, removed=[])
|
||||
|
||||
@retry(
|
||||
stop_max_attempt_number=10,
|
||||
wait_exponential_multiplier=1000,
|
||||
wait_exponential_max=10000,
|
||||
wrap_exception=True)
|
||||
def update_roas():
|
||||
logging.info('Updating ROAs...')
|
||||
krill_roa_api.update_route_authorizations(child_handle, delta)
|
||||
|
||||
update_roas()
|
||||
|
||||
logging.info('Krill configuration complete')
|
||||
|
||||
except RetryError as e:
|
||||
if e.last_attempt.has_exception:
|
||||
(ex_type, ex_value, traceback) = e.last_attempt.value
|
||||
pytest.fail(f'Retries exhausted while configuring Krill: {ex_value} caused by {e}')
|
||||
else:
|
||||
pytest.fail(f'Retries exhausted while configuring Krill: {e}')
|
||||
|
||||
yield (krill_ca_api_client, krill_pub_api_client)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("krill_with_roas")
|
||||
class TestKrillWithRelyingParties:
|
||||
def test_setup(self):
|
||||
# Cause the krill_with_roas and dependent fixtures to be setup once
|
||||
# before the tests below run, otherwise the first real test also
|
||||
# includes the work and output of creating the fixtures.
|
||||
pass
|
||||
|
||||
#@pytest.mark.parametrize("service", [Routinator, RoutinatorUnstable, FortValidator, OctoRPKI, Rcynic, RPKIClient, RPKIValidator3])
|
||||
@pytest.mark.parametrize("service", [Routinator, RoutinatorUnstable, FortValidator, OctoRPKI, Rcynic, RPKIClient])
|
||||
def test_rtr(self, docker_host_fqdn, docker_project, function_service_manager, service, metadata):
|
||||
#
|
||||
# Use Docker Compose to deploy the given Relying Party service and its dependencies.
|
||||
# On tear down the service container and its dependent containers will be killed and removed.
|
||||
#
|
||||
function_service_manager.start_services_with_dependencies(docker_project, service.name)
|
||||
|
||||
class UpdateWasEmpty(Exception):
|
||||
pass
|
||||
|
||||
def retry_if_incomplete_update(exception):
|
||||
return isinstance(exception, rtrlib.exceptions.SyncTimeout) or \
|
||||
isinstance(exception, UpdateWasEmpty)
|
||||
|
||||
@retry(
|
||||
stop_max_attempt_number=10,
|
||||
wait_exponential_multiplier=5000,
|
||||
wait_exponential_max=20000,
|
||||
retry_on_exception=retry_if_incomplete_update,
|
||||
wrap_exception=True)
|
||||
def fetch_from_rtr_server():
|
||||
try:
|
||||
rtr_start_time = int(time())
|
||||
logging.info(f'Connecting RTR client to {docker_host_fqdn}:{service.rtr_port}')
|
||||
received_roas = set(rtr_fetch_one(docker_host_fqdn, service.rtr_port, service.rtr_timeout_seconds))
|
||||
rtr_elapsed_time = int(time()) - rtr_start_time
|
||||
|
||||
# r is now a list of PFXRecord
|
||||
# see: https://python-rtrlib.readthedocs.io/en/latest/api.html#rtrlib.records.PFXRecord
|
||||
logging.info(f'Received {len(received_roas)} ROAs via RTR from {service.name} in {rtr_elapsed_time} seconds')
|
||||
|
||||
if len(received_roas) == 0:
|
||||
# retry, maybe the ROAs are not available yet
|
||||
raise UpdateWasEmpty()
|
||||
|
||||
# are each of the TEST_ROAS items in r?
|
||||
# i.e. is the intersection of the two sets equal to that of the TEST_ROAS set?
|
||||
|
||||
logging.info(f'Comparing {len(received_roas)} received ROAs to {len(TEST_ROAS)} expected ROAs...')
|
||||
expected_roas = set([roa_to_roa_string(r) for r in TEST_ROAS])
|
||||
assert received_roas == expected_roas
|
||||
except rtrlib.exceptions.SyncTimeout as e:
|
||||
logging.error(f'Timeout (>{service.rtr_timeout_seconds} seconds) while syncing RTR with {service.name} at {docker_host_fqdn}:{service.rtr_port}')
|
||||
try:
|
||||
if not service.is_ready():
|
||||
logging.error(f'{service.name} is not ready')
|
||||
except Exception as innerE:
|
||||
logging.error(f'Unable to determine if {service.name} is ready: {innerE}')
|
||||
|
||||
raise e
|
||||
|
||||
fetch_from_rtr_server()
|
||||
@@ -1,8 +0,0 @@
|
||||
#[cfg(feature = "ui-tests")]
|
||||
mod ui;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(feature = "ui-tests")]
|
||||
async fn multi_user_admin_token_test() {
|
||||
ui::run_krill_ui_test("multi_user_admin_token", ui::OpenIDConnectMockConfig::do_not_start()).await;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
mod ui;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
async fn multi_user_config_file_test() {
|
||||
ui::run_krill_ui_test("multi_user_config_file", ui::OpenIDConnectMockConfig::do_not_start()).await
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
mod ui;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
async fn multi_user_config_file_with_ta_test() {
|
||||
use log::info;
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use rpki::ca::idexchange::Handle;
|
||||
|
||||
use krill::{
|
||||
cli::{
|
||||
options::{CaCommand, Command, HistoryOptions},
|
||||
report::ApiResponse,
|
||||
},
|
||||
test::*,
|
||||
};
|
||||
|
||||
ui::run_krill_ui_test(
|
||||
"multi_user_config_file_with_ta",
|
||||
ui::OpenIDConnectMockConfig::do_not_start(),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Check the Krill event history after the actions performed against Krill
|
||||
// by the Cypress browser driving test script we just executed. Expect at
|
||||
// least one action to be attributed to the logged in user who interacted
|
||||
// with the CA allocated to them and at least one action with the same CA to
|
||||
// be attributed to the internal 'krill' user.
|
||||
// TODO: improve this to match the exact sequence of expected actions and
|
||||
// attributed actors.
|
||||
info!("Verifying that CAs were modified by the expected users according to the history log");
|
||||
|
||||
let mut cas_and_users = HashMap::new();
|
||||
cas_and_users.insert("ca_admin", vec!["krill", "user:admin@krill"]);
|
||||
cas_and_users.insert(
|
||||
"ca_readwrite",
|
||||
vec!["krill", "user:readwrite@krill", "user:joe", "user:sally"],
|
||||
);
|
||||
cas_and_users.insert("ca_readonly", vec!["krill", "user:rohelper@krill"]);
|
||||
|
||||
for (ca, expected_users) in cas_and_users {
|
||||
let r = krill_admin(Command::CertAuth(CaCommand::ShowHistoryCommands(
|
||||
Handle::from_str(ca).unwrap(),
|
||||
HistoryOptions::default(),
|
||||
)))
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
matches!(r, ApiResponse::CertAuthHistory(_)),
|
||||
"Expected a history API response"
|
||||
);
|
||||
|
||||
if let ApiResponse::CertAuthHistory(history) = r {
|
||||
// each expected user should be present at least once in the history of the CA
|
||||
// no other users should be present in the CA history
|
||||
let expected_users_set: HashSet<String> = expected_users.iter().map(|u| u.to_string()).collect();
|
||||
let found_users_set: HashSet<String> = history.commands().iter().map(|r| r.actor.clone()).collect();
|
||||
|
||||
assert_eq!(
|
||||
expected_users_set, found_users_set,
|
||||
"One or more users in the history of CA '{}' is missing or unexpected",
|
||||
ca
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
mod ui;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
async fn multi_user_openid_connect_test() {
|
||||
use crate::ui::{OpenIDConnectMockConfig, OpenIDConnectMockMode::*};
|
||||
|
||||
ui::run_krill_ui_test(
|
||||
"multi_user_openid_connect",
|
||||
OpenIDConnectMockConfig::enabled(WithRPInitiatedLogout),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
mod ui;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
async fn multi_user_openid_connect_provider_not_available_test() {
|
||||
use crate::ui::{OpenIDConnectMockConfig, OpenIDConnectMockMode::*};
|
||||
|
||||
ui::run_krill_ui_test(
|
||||
"multi_user_openid_connect_provider_not_available",
|
||||
OpenIDConnectMockConfig::disabled(WithRPInitiatedLogout),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
mod ui;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
async fn multi_user_openid_connect_provider_with_custom_logout() {
|
||||
use crate::ui::{OpenIDConnectMockConfig, OpenIDConnectMockMode::*};
|
||||
|
||||
ui::run_krill_ui_test(
|
||||
"multi_user_openid_connect_provider_with_custom_logout",
|
||||
OpenIDConnectMockConfig::enabled(WithRPInitiatedLogout),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
mod ui;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
async fn multi_user_openid_connect_provider_with_fallback_logout_test() {
|
||||
use crate::ui::{OpenIDConnectMockConfig, OpenIDConnectMockMode::*};
|
||||
|
||||
ui::run_krill_ui_test(
|
||||
"multi_user_openid_connect_provider_with_fallback_logout",
|
||||
OpenIDConnectMockConfig::enabled(WithNoLogoutEndpoints),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
mod ui;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
async fn multi_user_openid_connect_provider_with_revocation() {
|
||||
use crate::ui::{OpenIDConnectMockConfig, OpenIDConnectMockMode::*};
|
||||
|
||||
ui::run_krill_ui_test(
|
||||
"multi_user_openid_connect_provider_with_revocation",
|
||||
OpenIDConnectMockConfig::enabled(WithOAuth2Revocation),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#[cfg(feature = "ui-tests")]
|
||||
mod ui;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
async fn multi_user_team_based_access_test() {
|
||||
ui::run_krill_ui_test(
|
||||
"multi_user_team_based_access",
|
||||
ui::OpenIDConnectMockConfig::do_not_start(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
mod ui;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(all(feature = "ui-tests", feature = "multi-user"))]
|
||||
async fn testbed_ui_test() {
|
||||
ui::run_krill_ui_test("testbed_ui", ui::OpenIDConnectMockConfig::do_not_start()).await;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/* eslint-disable arrow-body-style */
|
||||
// https://docs.cypress.io/guides/guides/plugins-guide.html
|
||||
|
||||
// if you need a custom webpack configuration you can uncomment the following import
|
||||
// and then use the `file:preprocessor` event
|
||||
// as explained in the cypress docs
|
||||
// https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples
|
||||
|
||||
// /* eslint-disable import/no-extraneous-dependencies, global-require */
|
||||
// const webpack = require('@cypress/webpack-preprocessor')
|
||||
|
||||
module.exports = (on, config) => {
|
||||
// on('file:preprocessor', webpack({
|
||||
// webpackOptions: require('@vue/cli-service/webpack.config'),
|
||||
// watchOptions: {}
|
||||
// }))
|
||||
|
||||
return Object.assign({}, config, {
|
||||
fixturesFolder: 'test-resources',
|
||||
integrationFolder: 'tests/ui/cypress/specs',
|
||||
screenshotsFolder: 'target/ui/screenshots',
|
||||
videosFolder: 'target/ui/videos',
|
||||
supportFile: 'tests/ui/cypress/support/index.js'
|
||||
})
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
let admin = { u: 'admin-token', p: 'secret' };
|
||||
|
||||
describe('admin API token', () => {
|
||||
it('The correct login form is shown', () => {
|
||||
cy.visit('/')
|
||||
|
||||
// make sure we haven't been redirected away from Krill (as would be the
|
||||
// case if an OpenID Connect login form were shown)
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
|
||||
// make sure that no user name field exists (as would be the case if the
|
||||
// built-in config file based local user login form were shown)
|
||||
cy.contains('Username').should('not.exist')
|
||||
|
||||
// check that a password form input field and the text Password are shown on
|
||||
// the page
|
||||
cy.get(':password')
|
||||
cy.contains('Password')
|
||||
})
|
||||
|
||||
it('Cannot login with empty password', () => {
|
||||
cy.visit('/')
|
||||
cy.get(':password').clear()
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('Please enter your password')
|
||||
})
|
||||
|
||||
it('Cannot login with incorrect password', () => {
|
||||
cy.visit('/')
|
||||
cy.get(':password').clear().type('abc')
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('The credentials you specified are wrong')
|
||||
})
|
||||
|
||||
it('Can login with correct password', () => {
|
||||
cy.visit('/')
|
||||
cy.get(':password').type(admin.p)
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(admin.u)
|
||||
})
|
||||
|
||||
it('Can logout', () => {
|
||||
cy.visit('/')
|
||||
cy.get(':password').type(admin.p)
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(admin.u)
|
||||
cy.get('.logout').click()
|
||||
cy.contains('Sign In')
|
||||
})
|
||||
})
|
||||
@@ -1,120 +0,0 @@
|
||||
// A note about strong password hashing login delays
|
||||
// -----------------------------------------------------------------------------
|
||||
// The strong password hashing on the client and server side when logging in with
|
||||
// config file users causes the login process to take a few seconds. As such we
|
||||
// extend the default timeout when checking for Sign In completion, like so:
|
||||
//
|
||||
// cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
|
||||
let admin = { u: 'admin@krill', p: 'admin_pass' };
|
||||
let readonly = { u: 'readonly@krill', p: 'readonly_pass' };
|
||||
let readwrite = { u: 'readwrite@krill', p: 'readwrite_pass' };
|
||||
let ca_name = 'dummy-ca-name';
|
||||
|
||||
let login_test_settings = [
|
||||
{ d: 'empty', u: '', p: '', o: false },
|
||||
{ d: 'admin token', u: 'secret', p: 'secret', o: false },
|
||||
{ d: 'incorrect', u: 'wrong_user_name', p: 'wrong_password', o: false },
|
||||
{ d: 'admin', u: admin.u, p: admin.p, o: true },
|
||||
{ d: 'readonly', u: readonly.u, p: readonly.p, o: true },
|
||||
{ d: 'readwrite', u: readwrite.u, p: readwrite.p, o: true },
|
||||
];
|
||||
|
||||
describe('Config File users', () => {
|
||||
it('The correct login form is shown', () => {
|
||||
cy.visit('/')
|
||||
|
||||
// make sure we haven't been redirected away from Krill (as would be the
|
||||
// case if an OpenID Connect login form were shown)
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
|
||||
// make sure that user name field exists (which would not be the case if the
|
||||
// built-in admin token based login form were shown)
|
||||
cy.contains('Username')
|
||||
|
||||
// check that a password form input field and the text Password are shown on
|
||||
// the page
|
||||
cy.get(':password')
|
||||
cy.contains('Password')
|
||||
})
|
||||
|
||||
login_test_settings.forEach(function (ts) {
|
||||
it('Login with ' + ts.d + ' credentials should ' + (ts.o ? 'succeed' : 'fail'), () => {
|
||||
// Work around the "Login with incorrect credentials should fail" test failing with ESOCKETTIMEDOUT by increasing
|
||||
// the response timeout as mentioned on https://github.com/cypress-io/cypress/issues/7062. This isn't anything to
|
||||
// do with incorrect credentials as re-ordering the tests causes a different test to fail. Rather, on a 2-vcpu
|
||||
// GitHub Actions runner Azure VM Krill is apparently busy around the time of the 4th test and takes longer to
|
||||
// respond. The issue is reproducible on a 1-vcpu AWS t2.small EC2 instance but not on a 2-vcpu AWS EC2 instance.
|
||||
cy.visit('/', { responseTimeout: 31000 })
|
||||
cy.contains('Username')
|
||||
cy.contains('Password')
|
||||
|
||||
cy.get('input[placeholder="Your username"]').clear()
|
||||
cy.get(':password').clear()
|
||||
|
||||
if (ts.u != '') cy.get('input[placeholder="Your username"]').type(ts.u)
|
||||
if (ts.p != '') cy.get(':password').type(ts.p)
|
||||
|
||||
cy.contains('Sign In').click()
|
||||
|
||||
if (ts.u == '') cy.contains('Please enter your username')
|
||||
if (ts.p == '') cy.contains('Please enter your password')
|
||||
|
||||
if (ts.o) {
|
||||
cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(ts.u)
|
||||
} else {
|
||||
cy.contains('Sign In')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('Can logout', () => {
|
||||
cy.visit('/')
|
||||
cy.get('input[placeholder="Your username"]').type(admin.u)
|
||||
cy.get(':password').type(admin.p)
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(admin.u)
|
||||
cy.get('.logout').click()
|
||||
cy.contains('Sign In')
|
||||
})
|
||||
|
||||
it('Should be timed out', () => {
|
||||
// take manual control of time in the browser
|
||||
cy.clock()
|
||||
|
||||
// login
|
||||
cy.visit('/')
|
||||
cy.get('input[placeholder="Your username"]').type(admin.u)
|
||||
cy.get(':password').type(admin.p)
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(admin.u)
|
||||
|
||||
// Skip ahead a minute and check that we are still logged in
|
||||
cy.tick(1 * 60 * 1000)
|
||||
cy.visit('/')
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(admin.u)
|
||||
|
||||
// Skip ahead till just before the idle timeout and check that we are still
|
||||
// logged in.
|
||||
cy.tick(28 * 60 * 1000)
|
||||
cy.visit('/')
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(admin.u)
|
||||
|
||||
// Skip ahead another 31 minutes to just beyond the UI 30 minute idle
|
||||
// timeout threshold and verify that we have been logged out
|
||||
cy.tick(31 * 60 * 1000)
|
||||
cy.visit('/')
|
||||
cy.get('#userinfo').should('not.exist')
|
||||
cy.contains('Sign In')
|
||||
})
|
||||
})
|
||||
@@ -1,482 +0,0 @@
|
||||
// This demonstrates use of config-file based users with Krill and Lagosta.
|
||||
// It also demonstrates the custom role-per-ca demo policy because that
|
||||
// requires multiple CAs and registered parents and repositories to demo
|
||||
// ability to create ROAs but not to perform other CA "write" operations,
|
||||
// which is already setup by this demo.
|
||||
|
||||
// A note about difficulty entering XML into Lagosta XML input fields:
|
||||
// -----------------------------------------------------------------------------
|
||||
// Using cy.type() to enter XML into these fields is extremely slow, one
|
||||
// animated character at a time. I haven't yet found a way to copy-paste into
|
||||
// them. Setting the text directly can be done but there is a challenge that has
|
||||
// to be worked around which is that the fields use Prism Editor JS to syntax
|
||||
// highlight the XML. Prism Editor manages the content as a rich HTML child node
|
||||
// structure. Just replacing the content with a new text node doesn't work as
|
||||
// the Lagosta JS code reading the field content doesn't get the content as
|
||||
// set for some reason. The set text also doesn't get syntax highlighted. What
|
||||
// seems to work however is causing a keyboard event in the field after the text
|
||||
// has been set, e.g. pressing the End key.
|
||||
//
|
||||
// To summarize, the following works quickly:
|
||||
// cy.get('... pre[contenteditable="true"]').invoke('text', xml)
|
||||
// cy.get('... pre[contenteditable="true"]').type('{end}')
|
||||
//
|
||||
// This is less hacky but very slow: (even with "type(xml, {delay: 0}))")
|
||||
// cy.get('... pre[contenteditable="true"]').clear().type(xml)
|
||||
|
||||
// A note about strong password hashing login delays
|
||||
// -----------------------------------------------------------------------------
|
||||
// The strong password hashing on the client and server side when logging in with
|
||||
// config file users causes the login process to take a few seconds. As such we
|
||||
// extend the default timeout when checking for Sign In completion, like so:
|
||||
//
|
||||
// cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
|
||||
let admin = { u: 'admin@krill', p: 'admin' };
|
||||
let readonly = { u: 'readonly@krill', p: 'readonly' };
|
||||
let readwrite = { u: 'readwrite@krill', p: 'readwrite' };
|
||||
let rohelper = { u: 'rohelper@krill', p: 'rohelper' };
|
||||
let joe = { u: 'joe', p: 'abc' };
|
||||
let sally = { u: 'sally', p: 'abc' };
|
||||
|
||||
// For the tests below to work these users must only have access to a single CA,
|
||||
// at the time of CA creation, otherwise only the first user gets to create a CA,
|
||||
// after that Lagosta doesn't prompt to create a CA as the user can already see
|
||||
// that one exists.
|
||||
//
|
||||
// For the read only user to be able to see the repository and parent management
|
||||
// UI they must be able to get past the "Welcome to Krill" screen which prompts
|
||||
// to create a CA, something the read only user cannot do. Therefore the CA for
|
||||
// the read only user needs to be created for it by another user, and should be
|
||||
// tested for CA creation failure *before* that CA is created.
|
||||
let create_ca_test_settings = [
|
||||
{ d: 'readonly', u: readonly.u, p: readonly.p, o: false, ca: 'ca_readonly' },
|
||||
{ d: 'readwrite', u: readwrite.u, p: readwrite.p, o: true, ca: 'ca_readwrite' },
|
||||
{ d: 'admin', u: admin.u, p: admin.p, o: true, ca: 'ca_admin' },
|
||||
{ d: 'rohelper', u: rohelper.u, p: rohelper.p, o: true, ca: 'ca_readonly' }, // create the CA for the readonly user as they cannot do it themselves
|
||||
];
|
||||
|
||||
let register_publisher_test_settings = [
|
||||
{ d: 'readonly', u: readonly.u, p: readonly.p, o: false, a: 'Register', ca: 'ca_readonly' },
|
||||
{ d: 'readwrite', u: readwrite.u, p: readwrite.p, o: true, a: 'Register', ca: 'ca_readwrite' },
|
||||
{ d: 'admin', u: admin.u, p: admin.p, o: true, a: 'Register', ca: 'ca_admin' },
|
||||
{ d: 'rohelper', u: rohelper.u, p: rohelper.p, o: true, a: 'Unregister', ca: 'ca_readonly' }, // unregister the half-registered publisher created by the readonly user
|
||||
{ d: 'rohelper', u: rohelper.u, p: rohelper.p, o: true, a: 'Register', ca: 'ca_readonly' }, // re-register it properly now
|
||||
];
|
||||
|
||||
let register_parent_test_settings = [
|
||||
{ d: 'readonly', u: readonly.u, p: readonly.p, o: false, a: 'Register', ca: 'ca_readonly' },
|
||||
{ d: 'readwrite', u: readwrite.u, p: readwrite.p, o: true, a: 'Register', ca: 'ca_readwrite' },
|
||||
{ d: 'admin', u: admin.u, p: admin.p, o: true, a: 'Register', ca: 'ca_admin' },
|
||||
{ d: 'rohelper', u: rohelper.u, p: rohelper.p, o: true, a: 'Unregister', ca: 'ca_readonly' }, // unregister the half-registered parent created by the readonly user
|
||||
{ d: 'rohelper', u: rohelper.u, p: rohelper.p, o: true, a: 'Register', ca: 'ca_readonly' }, // re-register it properly now
|
||||
];
|
||||
|
||||
let add_roa_test_settings = [
|
||||
{ d: 'readonly', u: readonly.u, p: readonly.p, o: false, ca: 'ca_readonly' },
|
||||
{ d: 'readwrite', u: readwrite.u, p: readwrite.p, o: true, ca: 'ca_readwrite' },
|
||||
{ d: 'admin', u: admin.u, p: admin.p, o: true, ca: 'ca_admin' },
|
||||
];
|
||||
|
||||
let joe_cas = ['ta', 'testbed', 'ca_admin', 'ca_readwrite', 'ca_readonly'];
|
||||
let sally_cas = ['ca_readwrite', 'ca_readonly'];
|
||||
|
||||
describe('Config File Users with TA', () => {
|
||||
create_ca_test_settings.forEach(function (ts) {
|
||||
it('Create CA as ' + ts.d + ' user should ' + (ts.o ? 'succeed' : 'fail'), () => {
|
||||
// sign in
|
||||
cy.visit('/')
|
||||
cy.get('#login_id').type(ts.u)
|
||||
cy.get('#login_password').type(ts.p)
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
cy.contains(ts.u)
|
||||
cy.contains('Welcome to Krill')
|
||||
|
||||
// create a CA
|
||||
cy.contains('CA Handle')
|
||||
cy.get('form input[type="text"]').type(ts.ca)
|
||||
cy.contains('Create CA').click()
|
||||
cy.contains('OK').click()
|
||||
|
||||
// no longer on the welcome page
|
||||
if (ts.o) {
|
||||
cy.contains('Welcome to Krill').should('not.exist')
|
||||
} else {
|
||||
cy.contains('Welcome to Krill')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
register_publisher_test_settings.forEach(function (ts) {
|
||||
it(ts.a + ' CA ' + ts.ca + ' with repository as ' + ts.d + ' user should ' + (ts.o ? 'succeed' : 'fail'), () => {
|
||||
if (ts.a == 'Register') {
|
||||
cy.intercept({ method: 'GET', path: '/api/v1/cas/' + ts.ca + '/id/publisher_request.xml'}).as('getRepoRequestXML')
|
||||
|
||||
// sign in
|
||||
cy.visit('/')
|
||||
cy.get('#login_id').type(ts.u)
|
||||
cy.get('#login_password').type(ts.p)
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
cy.contains(ts.u)
|
||||
|
||||
// wait for Lagosta to finish fetching the repository request XML
|
||||
cy.wait('@getRepoRequestXML').its('response.statusCode').should('eq', 200)
|
||||
|
||||
// grab the repository tab publisher request XML from the Krill UI
|
||||
cy.get('div#tab-repo').click()
|
||||
cy.get('div#pane-repo pre[contenteditable="false"] code').contains('<publisher_request')
|
||||
cy.get('div#pane-repo pre[contenteditable="false"] code').invoke('text').then(pub_req_xml => {
|
||||
// use the local testbed UI to submit the request to register the publisher
|
||||
cy.visit("/index.html#/testbed")
|
||||
|
||||
// enter the request XML into the testbed UI edit field
|
||||
cy.get('div#tab-addPublisher').contains('Register Publisher').click()
|
||||
cy.get('#addPublisher pre[contenteditable="true"]').invoke('text', pub_req_xml)
|
||||
cy.get('#addPublisher pre[contenteditable="true"]').type('{end}')
|
||||
cy.get('#addPublisher button').contains('Register publisher').click()
|
||||
cy.get('div[role="dialog"] button').contains('OK').click()
|
||||
cy.contains('has been added to the testbed')
|
||||
|
||||
// note: publisher registration succeeds even for the readonly user
|
||||
// because for the testbed half of the XML exchange the readonly user is
|
||||
// automatically promoted for the duration of the request to the
|
||||
// internal 'testbed' user, so that the testbed is usable without
|
||||
// requiring user accounts.
|
||||
|
||||
// grab the repository response XML from the testbed UI
|
||||
cy.get('#addPublisher pre[contenteditable="false"]').contains('<repository_response')
|
||||
cy.get('#addPublisher pre[contenteditable="false"]').invoke('text').then(repo_resp_xml => {
|
||||
// navigate back to Krill
|
||||
cy.visit("/")
|
||||
|
||||
// enter the response XML into the Krill UI edit field
|
||||
cy.get('div#tab-repo').click()
|
||||
cy.get('div#pane-repo pre[contenteditable="true"]').invoke('text', repo_resp_xml)
|
||||
cy.get('div#pane-repo pre[contenteditable="true"]').type('{end}')
|
||||
cy.get('div#pane-repo button').contains('Confirm').click()
|
||||
|
||||
if (ts.o) {
|
||||
cy.contains('Success')
|
||||
cy.contains('Error').should('not.exist')
|
||||
} else {
|
||||
cy.contains('Success').should('not.exist')
|
||||
cy.contains('Error')
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// use the local testbed UI to unregister the publisher
|
||||
cy.visit("/index.html#/testbed")
|
||||
|
||||
// enter the registered publisher name into the testbed UI edit field
|
||||
cy.get('div#tab-removePublisher').contains('Unregister Publisher').click()
|
||||
cy.get('#removePublisher input[placeholder="Enter the Publisher name to remove"]').type(ts.ca)
|
||||
cy.get('#removePublisher button').contains('Remove publisher').click()
|
||||
cy.get('div[role="dialog"] button').contains('OK').click()
|
||||
|
||||
if (ts.o) {
|
||||
cy.contains('has been removed')
|
||||
} else {
|
||||
cy.contains('has been removed').should('not.exist')
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
register_parent_test_settings.forEach(function (ts) {
|
||||
it(ts.a + ' CA ' + ts.ca + ' with parent as ' + ts.d + ' user should ' + (ts.o ? 'succeed' : 'fail'), () => {
|
||||
if (ts.a == 'Register') {
|
||||
cy.intercept({ method: 'GET', path: '/api/v1/cas/' + ts.ca + '/id/child_request.xml'}).as('getChildRequestXML')
|
||||
|
||||
// sign in
|
||||
cy.visit('/')
|
||||
cy.get('input[placeholder="Your username"]').type(ts.u)
|
||||
cy.get(':password').type(ts.p)
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
cy.contains(ts.u)
|
||||
|
||||
// wait for Lagosta to finish fetching the repository request XML
|
||||
cy.wait('@getChildRequestXML').its('response.statusCode').should('eq', 200)
|
||||
|
||||
// grab the parents tab child request XML from the Krill UI
|
||||
cy.get('div#tab-parents').click()
|
||||
cy.get('div#pane-parents pre[contenteditable="false"] code').contains("<child_request")
|
||||
cy.get('div#pane-parents pre[contenteditable="false"] code').invoke('text').then(child_req_xml => {
|
||||
// use the local testbed UI to submit the request to register the child
|
||||
cy.visit("/index.html#/testbed")
|
||||
|
||||
// enter the request XML into the testbed UI edit field
|
||||
cy.get('div#tab-addChild').contains('Register CA').click()
|
||||
cy.get('#addChild pre[contenteditable="true"]').invoke('text', child_req_xml)
|
||||
cy.get('#addChild pre[contenteditable="true"]').type('{end}')
|
||||
cy.get('#addChild input[placeholder^="The AS resources"]').type('AS18')
|
||||
cy.get('#addChild input[placeholder^="The IPv4 resources"]').type('10.0.0.0/24')
|
||||
cy.get('#addChild button').contains('Register child CA').click()
|
||||
cy.get('div[role="dialog"] button').contains('OK').click()
|
||||
cy.contains('has been added to the testbed')
|
||||
|
||||
// grab the parent response XML from the testbed UI
|
||||
cy.get('#addChild pre[contenteditable="false"]').contains("<parent_response")
|
||||
cy.get('#addChild pre[contenteditable="false"]').invoke('text').then(parent_resp_xml => {
|
||||
// navigate back to Krill
|
||||
cy.visit("/")
|
||||
|
||||
// enter the response XML into the Krill UI edit field
|
||||
cy.get('div#tab-parents').click()
|
||||
cy.get('div#pane-parents pre[contenteditable="true"]').invoke('text', parent_resp_xml)
|
||||
cy.get('div#pane-parents pre[contenteditable="true"]').type('{end}')
|
||||
cy.get('div#pane-parents button').contains('Confirm').click()
|
||||
|
||||
if (ts.o) {
|
||||
cy.contains('Success')
|
||||
cy.contains('Error').should('not.exist')
|
||||
// wait for the parent registration to complete inside Krill and
|
||||
// for the details to appear in the Lagosta UI
|
||||
cy.get('div#tab-parents').click().get('body').contains('Add an additional parent')
|
||||
} else {
|
||||
cy.contains('Success').should('not.exist')
|
||||
cy.contains('Error')
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// use the local testbed UI to unregister the parent
|
||||
cy.visit("/index.html#/testbed")
|
||||
|
||||
// enter the registered parent name into the testbed UI edit field
|
||||
cy.get('div#tab-removeChild').contains('Unregister CA').click()
|
||||
cy.get('#removeChild input[placeholder="Enter the CA name to remove"]').type(ts.ca)
|
||||
cy.get('#removeChild button').contains('Remove child CA').click()
|
||||
cy.get('div[role="dialog"] button').contains('OK').click()
|
||||
|
||||
if (ts.o) {
|
||||
cy.contains('has been removed')
|
||||
} else {
|
||||
cy.contains('has been removed').should('not.exist')
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
add_roa_test_settings.forEach(function (ts) {
|
||||
it('Add ROA for CA ' + ts.ca + ' as ' + ts.d + ' user should ' + (ts.o ? 'succeed' : 'fail'), () => {
|
||||
cy.intercept({ method: 'GET', path: '/api/v1/cas/' + ts.ca + '/routes/analysis/full'}).as('analyzeRoutes')
|
||||
|
||||
// sign in
|
||||
cy.visit('/')
|
||||
cy.get('input[placeholder="Your username"]').type(ts.u)
|
||||
cy.get(':password').type(ts.p)
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
cy.contains(ts.u)
|
||||
|
||||
// add a ROA
|
||||
cy.get('div#tab-roas').click()
|
||||
cy.get('body').then(($body) => {
|
||||
// Check if Krill has issued the resources to the CA yet by seeing if the UI was able to fetch them, if it
|
||||
// wasn't then it shows a "Click here to refresh" link. If the link exists, don't click it immediately as that
|
||||
// will just result in the same lack of resources, instead give Krill some time (5 seconds) in this case then
|
||||
// click the refresh link and then make sure that the link no longer exists (because resources were found).
|
||||
// Ideally we would not wait 5 seconds but instead keep retrying until the link disappears, but according to
|
||||
// Cypress docs it explicitly will NOT retry a .click() command. See:
|
||||
// https://docs.cypress.io/guides/core-concepts/retry-ability.html#Why-are-some-commands-NOT-retried
|
||||
// https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/
|
||||
// The latter suggests to use a 3rd party cypress-pipe plugin and not to use waits. That would be nice, but to
|
||||
// use a plugin we then need a custom Docker image which is something I'd rather not build, publish and maintain
|
||||
// the moment. TODO: don't publish an image, instead build it on the test runner just before running the tests?
|
||||
if ($body.find('#no_resources_click_to_refresh').length > 0) {
|
||||
cy.get('#no_resources_click_to_refresh').wait(5000).click().get('body').get('#no_resources_click_to_refresh').should('not.exist')
|
||||
}
|
||||
})
|
||||
|
||||
// wait for Lagosta to finish fetching the route analysis details
|
||||
cy.wait('@analyzeRoutes').its('response.statusCode').should('eq', 200)
|
||||
|
||||
cy.get('div#pane-roas button').contains('Add ROA').click()
|
||||
cy.get('div[role="dialog"]')
|
||||
cy.contains('Add ROA')
|
||||
cy.get('#add_roa_asn').clear().type('AS18')
|
||||
cy.get('#add_roa_prefix').clear().type('10.0.0.1/32')
|
||||
cy.get('div[role="dialog"] button').contains('Confirm').click()
|
||||
|
||||
if (ts.o) {
|
||||
cy.contains('ROA added')
|
||||
} else {
|
||||
cy.contains('Your user does not have sufficient rights to perform this action. Please contact your administrator.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// This test exercises the custom role-per-ca demo policy.
|
||||
// As Joe should only be able to do write operations to the CA called 'ca_readwrite', so we test:
|
||||
// - Which CAs can Joe see in the CA dropdown list? Joe should be able to see them all.
|
||||
// - Can Joe create a ROA on ca_readonly? This should fail.
|
||||
// - Can Joe create a ROA on ca_readwrite? This should succeed.
|
||||
// - Can Joe add an additional parent to ca_readwrite? This should succeed.
|
||||
it('CUSTOM POLICY: Joe can see all CAs but only write to ca_readwrite', () => {
|
||||
cy.intercept({ method: 'GET', path: '/api/v1/cas/ca_readonly/repo/status'}).as('statusRO')
|
||||
cy.intercept({ method: 'GET', path: '/api/v1/cas/ca_readwrite/repo/status'}).as('statusRW')
|
||||
cy.intercept({ method: 'GET', path: '/api/v1/cas/ca_readwrite/id/child_request.xml'}).as('getChildRequestXML')
|
||||
|
||||
// sign in
|
||||
cy.visit('/')
|
||||
cy.get('input[placeholder="Your username"]').type(joe.u)
|
||||
cy.get(':password').type(joe.p)
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
cy.contains(joe.u)
|
||||
|
||||
// the CA drop down should contain all the CAs
|
||||
// click the dropdown to open it and show the list
|
||||
cy.get('.switcher > .el-select > .el-input > .el-input__inner').click()
|
||||
// check that the list contains every CA
|
||||
joe_cas.forEach(function (ca_name) {
|
||||
cy.get('.el-select-dropdown__wrap.el-scrollbar__wrap > ul').contains(ca_name)
|
||||
})
|
||||
|
||||
// ensure we are working with CA ca_readonly
|
||||
cy.url().then(($url) => {
|
||||
if (!$url.includes('#/cas/ca_readonly')) {
|
||||
// only change the current CA and wait for an update from the backend if the current CA isn't the one we want
|
||||
cy.get('.el-select-dropdown__wrap.el-scrollbar__wrap > ul').contains('ca_readonly').click()
|
||||
cy.wait('@statusRO')
|
||||
}
|
||||
})
|
||||
|
||||
// attempting to create a ROA on ca_readonly should fail
|
||||
cy.get('#tab-roas').click()
|
||||
cy.contains('Add ROA').click()
|
||||
cy.get('div[role="dialog"]')
|
||||
cy.contains('Add ROA')
|
||||
cy.get('#add_roa_asn').clear().type('AS18')
|
||||
cy.get('#add_roa_prefix').clear().type('10.0.0.1/32')
|
||||
cy.get('div[role="dialog"] button').contains('Confirm').click()
|
||||
cy.contains('Your user does not have sufficient rights to perform this action. Please contact your administrator.')
|
||||
cy.get('div[role="dialog"] button').contains('Cancel').click()
|
||||
|
||||
// attempting to create a ROA on ca_readwrite should succeed
|
||||
cy.get('.switcher > .el-select > .el-input > .el-input__inner').click()
|
||||
cy.get('.el-select-dropdown__wrap.el-scrollbar__wrap > ul').contains('ca_readwrite').click()
|
||||
cy.wait('@statusRW')
|
||||
cy.get('#tab-roas').click()
|
||||
cy.contains('Add ROA').click()
|
||||
cy.get('div[role="dialog"]')
|
||||
cy.contains('Add ROA')
|
||||
cy.get('#add_roa_asn').clear().type('AS19')
|
||||
cy.get('#add_roa_prefix').clear().type('10.0.0.1/32')
|
||||
cy.get('div[role="dialog"] button').contains('Confirm').click()
|
||||
cy.contains('ROA added')
|
||||
|
||||
// attempting to add a parent on ca_readwrite should succeed
|
||||
cy.get('#tab-parents').click()
|
||||
cy.contains('Add an additional parent').click()
|
||||
|
||||
// grab the parents tab child request XML from the Krill UI
|
||||
cy.wait('@getChildRequestXML').its('response.statusCode').should('eq', 200)
|
||||
cy.get('div#pane-parents pre[contenteditable="false"] code').contains("<child_request")
|
||||
cy.get('div#pane-parents pre[contenteditable="false"] code').invoke('text').then(child_req_xml => {
|
||||
// use the local testbed UI to submit the request to register the child
|
||||
cy.visit("/index.html#/testbed")
|
||||
|
||||
// enter the request XML into the testbed UI edit field
|
||||
cy.get('div#tab-addChild').contains('Register CA').click()
|
||||
cy.get('#addChild pre[contenteditable="true"]').invoke('text', child_req_xml)
|
||||
cy.get('#addChild pre[contenteditable="true"]').type('{end}')
|
||||
cy.get('#addChild input[placeholder^="The AS resources"]').type('AS192')
|
||||
cy.get('#addChild input[placeholder^="The IPv4 resources"]').type('192.168.0.0/24')
|
||||
cy.get('#addChild button').contains('Register child CA').click()
|
||||
cy.get('div[role="dialog"] button').contains('OK').click()
|
||||
cy.contains('has been added to the testbed')
|
||||
|
||||
// grab the parent response XML from the testbed UI
|
||||
cy.get('#addChild pre[contenteditable="false"]').contains("<parent_response")
|
||||
cy.get('#addChild pre[contenteditable="false"]').invoke('text').then(parent_resp_xml => {
|
||||
// navigate back to Krill
|
||||
cy.visit("/")
|
||||
|
||||
// enter the response XML into the Krill UI edit field
|
||||
cy.get('#tab-parents').click()
|
||||
cy.contains('Add an additional parent').click()
|
||||
cy.get('div#pane-parents pre[contenteditable="true"]').invoke('text', parent_resp_xml)
|
||||
cy.get('div#pane-parents pre[contenteditable="true"]').type('{end}')
|
||||
|
||||
// change the default name for the parent as there is already a parent named testbed
|
||||
// TODO: this CSS selector is unreadable and unreliable, give the input field an ID
|
||||
// and select that instead
|
||||
cy.get('.mt-3 > .el-col > .el-input > .el-input__inner').type('otherparent')
|
||||
|
||||
cy.get('div#pane-parents button').contains('Confirm').click()
|
||||
|
||||
cy.contains('Success')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// This test exercises the custom role-per-ca demo policy.
|
||||
// As Sally should only be able to see two CAs and only be able to add ROAs to one other CA, we test:
|
||||
// - Which CAs can Sally see in the CA dropdown list? Sally should only be able to see a specific subset.
|
||||
// - Can Sally create a ROA on ca_readonly? This should fail.
|
||||
// - Can Sally create a ROA on ca_readwrite? This should succeed.
|
||||
// - Can Sally add an additional parent to ca_readwrite? This should fail.
|
||||
//
|
||||
// TODO: the CA dropdown box CSS selector is unreadable and unreliable, give the custom dropdown inner input field an
|
||||
// ID and select that instead. Similarly, the CA title selector is a weak selector, it can easily break later or match
|
||||
// the wrong thing if the UI design is changed and should alos be given its own ID to match on.
|
||||
it('CUSTOM POLICY: Sally can only see two CAs and only make ROA changes in one CA', () => {
|
||||
cy.intercept({ method: 'GET', path: '/api/v1/cas/ca_readonly/repo/status'}).as('statusRO')
|
||||
cy.intercept({ method: 'GET', path: '/api/v1/cas/ca_readwrite/repo/status'}).as('statusRW')
|
||||
|
||||
// sign in
|
||||
cy.visit('/')
|
||||
cy.get('input[placeholder="Your username"]').type(sally.u)
|
||||
cy.get(':password').type(sally.p)
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
cy.contains(sally.u)
|
||||
|
||||
// the CA drop down should contain all the CAs
|
||||
// click the dropdown to open it and show the list
|
||||
cy.get('.switcher > .el-select > .el-input > .el-input__inner').click()
|
||||
// check that the list contains every CA
|
||||
sally_cas.forEach(function (ca_name) {
|
||||
cy.get('.el-select-dropdown__wrap.el-scrollbar__wrap > ul').contains(ca_name)
|
||||
})
|
||||
|
||||
// attempting to create a ROA on ca_readonly should fail
|
||||
cy.get('.el-select-dropdown__wrap.el-scrollbar__wrap > ul').contains('ca_readonly').click()
|
||||
cy.wait('@statusRO')
|
||||
cy.get('h3 > strong').contains('ca_readonly')
|
||||
cy.get('#tab-roas').click()
|
||||
cy.contains('Add ROA').click()
|
||||
cy.get('div[role="dialog"]')
|
||||
cy.contains('Add ROA')
|
||||
cy.get('#add_roa_asn').clear().type('AS18')
|
||||
cy.get('#add_roa_prefix').clear().type('10.0.0.1/32')
|
||||
cy.get('div[role="dialog"] button').contains('Confirm').click()
|
||||
cy.contains('Your user does not have sufficient rights to perform this action. Please contact your administrator.')
|
||||
cy.get('div[role="dialog"] button').contains('Cancel').click()
|
||||
|
||||
// attempting to create a ROA on ca_readwrite should succeed
|
||||
cy.get('.switcher > .el-select > .el-input > .el-input__inner').click()
|
||||
cy.get('.el-select-dropdown__wrap.el-scrollbar__wrap > ul').contains('ca_readwrite').click()
|
||||
cy.wait('@statusRW')
|
||||
cy.get('h3 > strong').contains('ca_readwrite')
|
||||
cy.get('#tab-roas').click()
|
||||
cy.contains('Add ROA').click()
|
||||
cy.get('div[role="dialog"]')
|
||||
cy.contains('Add ROA')
|
||||
cy.get('#add_roa_asn').clear().type('AS22')
|
||||
cy.get('#add_roa_prefix').clear().type('10.0.0.1/32')
|
||||
cy.get('div[role="dialog"] button').contains('Confirm').click()
|
||||
cy.contains('ROA added')
|
||||
|
||||
// attempting to add an additional parent on ca_readwrite should fail
|
||||
cy.get('#tab-parents').click()
|
||||
cy.contains('Add an additional parent').click()
|
||||
cy.contains('Confirm').click()
|
||||
cy.contains('Your user does not have sufficient rights to perform this action. Please contact your administrator.')
|
||||
})
|
||||
})
|
||||
@@ -1,424 +0,0 @@
|
||||
// Matches daemon::auth::providers::openid_connect::http_client::openid_connect_provider_timeout() when test mode is
|
||||
// enabled.
|
||||
const KRILL_TEST_HTTP_CLIENT_TIMEOUT_SECS = 5;
|
||||
|
||||
// The mock OpenID Connect provider only checks usernames, not passwords.
|
||||
const admin = { u: 'adm@krill' }
|
||||
const readonly = { u: 'ro@krill' }
|
||||
const readwrite = { u: 'rw@krill' }
|
||||
const shorttoken = { u: 'shorttokenwithoutrefresh@krill' }
|
||||
const shortrefresh = { u: 'shorttokenwithrefresh@krill' }
|
||||
const badidtoken = { u: 'non-spec-compliant-idtoken-payload' }
|
||||
const badrole = { u: 'user-with-unknown-role' }
|
||||
const refreshinvalidrequest = { u: 'user-with-invalid-request-on-refresh' }
|
||||
const refreshinvalidclient = { u: 'user-with-invalid-client-on-refresh' }
|
||||
const wrongcsrfstate = { u: 'user-with-wrong-csrf-state-value' }
|
||||
const ca_name = 'dummy-ca-name'
|
||||
|
||||
// d: description, u: user, o: outcome, fm: failure mode, r: role
|
||||
const login_test_settings = [
|
||||
{ d: 'empty', u: '', o: false },
|
||||
{ d: 'incorrect', u: 'wrong_user_name', o: false, fm: 'unknown_user' },
|
||||
{ d: 'admin', u: admin.u, o: true, r: 'admin' },
|
||||
{ d: 'readonly', u: readonly.u, o: true, r: 'readonly' },
|
||||
{ d: 'readwrite', u: readwrite.u, o: true, r: 'readwrite' },
|
||||
{ d: 'badidtoken', u: badidtoken.u, o: false, fm: 'malformed_id_token' },
|
||||
{ d: 'badrole', u: badrole.u, o: false },
|
||||
{ d: 'wrongcsrfstate', u: wrongcsrfstate.u, o: false, fm: 'wrong_csrf_state' },
|
||||
]
|
||||
|
||||
// o: outcome
|
||||
const short_token_test_settings = [
|
||||
{ ca: 'some-handle-name', o: true, token_secs: 5, create_ca_after_secs: 0 }, // should succeed with a freshly issued token
|
||||
{ ca: 'some-other-handle-name', o: true, token_secs: 10, create_ca_after_secs: 5 }, // should succeed with a token due to expire but not yet expired
|
||||
{ ca: 'yet-another-handle-name', o: false, token_secs: 5, create_ca_after_secs: 10 }, // should fail after token expiration
|
||||
]
|
||||
|
||||
// fm: failure mode
|
||||
const create_ca_settings_401 = [
|
||||
'invalid_request',
|
||||
'invalid_grant',
|
||||
'invalid_client',
|
||||
'http_500',
|
||||
'http_503',
|
||||
].map((fm) => ({
|
||||
fm: fm,
|
||||
responseCode: 401,
|
||||
}))
|
||||
|
||||
// fm: failure mode
|
||||
const create_ca_settings_403 = [
|
||||
'unauthorized_client',
|
||||
'invalid_scope',
|
||||
'unsupported_grant_type',
|
||||
].map((fm) => ({
|
||||
fm: fm,
|
||||
responseCode: 403,
|
||||
}))
|
||||
|
||||
describe('OpenID Connect provider with RP-Initiated logout', () => {
|
||||
it('The correct login form is shown', () => {
|
||||
// cy.intercept({ method: 'GET', path: '/api/v1/authorized'}).as('isAuthorized')
|
||||
// cy.intercept({ method: 'GET', path: '/auth/login'}).as('getLoginURL')
|
||||
// cy.intercept({ method: 'GET', url: /^https:\/\/localhost:1818\/authorize.+/}).as('oidcLoginForm')
|
||||
cy.visit('/')
|
||||
// cy.wait(['@isAuthorized', '@getLoginURL', '@oidcLoginForm'])
|
||||
|
||||
// make sure we haven't been redirected away from Krill (as would be the
|
||||
// case if an OpenID Connect login form were shown)
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
|
||||
// make sure that this is our mock OpenID Connect provider
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
|
||||
// check that a username input field is shown on the page
|
||||
cy.get('input[name="username"]')
|
||||
})
|
||||
|
||||
login_test_settings.forEach(function (ts) {
|
||||
it(
|
||||
'Login with ' +
|
||||
ts.d +
|
||||
' credentials should ' +
|
||||
(ts.o ? 'succeed with the expected user info' : 'fail with the expected error'),
|
||||
() => {
|
||||
cy.visit('/')
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
|
||||
// Login, and while doing so specify the behaviour we want the OpenID Connect mock to exhibit for this user
|
||||
if (ts.u != '') {
|
||||
cy.get('input[name="username"]').clear().type(ts.u)
|
||||
}
|
||||
if (ts.fm) {
|
||||
// Cause the mock to exhibit the requested failure mode
|
||||
cy.get('select[name="failure_mode"]').select(ts.fm)
|
||||
}
|
||||
if (ts.r) {
|
||||
// Force the mock to respond with a role attribute for this user
|
||||
cy.get('input[name="userattr1"]').clear().type('role')
|
||||
cy.get('input[name="userattrval1"]').clear().type(ts.r)
|
||||
}
|
||||
|
||||
cy.contains('Sign In').click()
|
||||
|
||||
// We should end up back in the Krill UI
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
|
||||
if (ts.o) {
|
||||
// A good outcome, i.e. login should have succeeded
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(ts.u)
|
||||
cy.get('#userinfo_table').contains(ts.r) // assumes that ts.r is not a substring of ts.u
|
||||
|
||||
// check the source="config-file" "extra" property claim mapping
|
||||
// for the only user for which a value is defined in krill.conf: 'admin'
|
||||
if (ts.d == 'admin') {
|
||||
cy.get('#userinfo_table').contains('extra_val')
|
||||
}
|
||||
} else if (ts.d == 'badidtoken') {
|
||||
cy.contains('OpenID Connect: Code exchange failed: Failed to parse server response')
|
||||
cy.contains('return to the login page')
|
||||
} else if (ts.d == 'badrole') {
|
||||
cy.contains(
|
||||
'Your user does not have sufficient rights to perform this action. Please contact your administrator.'
|
||||
)
|
||||
cy.contains('return to the login page')
|
||||
} else if (ts.d == 'wrongcsrfstate') {
|
||||
cy.contains('CSRF token mismatch')
|
||||
} else {
|
||||
cy.contains('The supplied login credentials were incorrect')
|
||||
cy.contains('return to the login page')
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('Can logout', () => {
|
||||
// login
|
||||
cy.visit('/')
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]').clear().type(admin.u)
|
||||
cy.get('input[name="userattr1"]').clear().type('role') // a role is required to be able to login
|
||||
cy.get('input[name="userattrval1"]').clear().type('admin')
|
||||
cy.contains('Sign In').click()
|
||||
|
||||
// verify that we are shown to be logged in to the Krill UI
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(admin.u)
|
||||
|
||||
// verify that the mock provider thinks the user is logged in
|
||||
cy.request({ url: 'https://127.0.0.1:1818/test/is_user_logged_in?username=' + admin.u, failOnStatusCode: false }).its('status').should('eq', 200)
|
||||
|
||||
// logout
|
||||
cy.intercept({ method: 'GET', url: /^https:\/\/localhost:1818\/logout.+/}).as('oidcLogout')
|
||||
cy.get('.logout').click()
|
||||
cy.wait('@oidcLogout').its('response.statusCode').should('eq', 302)
|
||||
|
||||
// verify that the mock provider thinks the user is now logged out
|
||||
cy.request({ url: 'https://127.0.0.1:1818/test/is_user_logged_in?username=' + admin.u, failOnStatusCode: false }).its('status').should('eq', 400)
|
||||
|
||||
// verify that we are shown the OpenID Connect provider login page
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]')
|
||||
})
|
||||
|
||||
it('Login with short-lived non-refreshable token and try to refresh page', () => {
|
||||
// login
|
||||
cy.visit('/')
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]').clear().type(shorttoken.u)
|
||||
cy.get('input[name="userattr1"]').clear().type('role') // a role is required to be able to login
|
||||
cy.get('input[name="userattrval1"]').clear().type('readwrite')
|
||||
cy.get('input[name="refresh"]').uncheck() // prevent issuing of refresh tokens for this user
|
||||
cy.contains('Sign In').click()
|
||||
|
||||
// verify that we are shown to be logged in to the Krill UI
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(shorttoken.u)
|
||||
|
||||
// the token has a lifetime of 5 second and no refresh token
|
||||
// wait 6 seconds...
|
||||
// note: a shorter token with a 1 second lifetime doesn't work in the GitHub
|
||||
// Action runner environment because the token has sometimes already expired
|
||||
// by the time Krill verifies it!
|
||||
cy.wait(6000)
|
||||
|
||||
// verify that if we reload the Krill UI we are shown the OpenID Connect
|
||||
// provider login page
|
||||
// cy.intercept({ method: 'GET', path: '/auth/login'}).as('getLoginURL')
|
||||
// cy.intercept({ method: 'GET', url: /^https:\/\/localhost:1818\/authorize.+/}).as('oidcLoginForm')
|
||||
cy.visit('/')
|
||||
// cy.wait(['@getLoginURL', '@oidcLoginForm'])
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
})
|
||||
|
||||
short_token_test_settings.forEach((ts) =>
|
||||
it('Login with short-lived non-refreshable token and try to create a CA after ' + ts.create_ca_after_secs + ' out of ' + ts.token_secs + ' secs' + ' should ' + (ts.o ? 'succeed' : 'fail'), () => {
|
||||
// note: a short token with a 1 second lifetime doesn't work in the GitHub
|
||||
// Action runner environment because the token has sometimes already expired
|
||||
// by the time Krill verifies it! And we also want to test that we still have
|
||||
// rights when less than half the token lifetime is remaining (as at this
|
||||
// point Krill switches from considering the token to be ACTIVE to NEEDS
|
||||
// REFRESH), and for a short lifetime like 5 seconds window in which to time
|
||||
// the test to check after 3 seconds but before 5 seconds is just too small,
|
||||
// so we use a longer lifetime for this test.
|
||||
|
||||
// login
|
||||
cy.visit('/')
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]').clear().type(shorttoken.u + '_delay_' + ts.create_ca_after_secs)
|
||||
cy.get('input[name="userattr1"]').clear().type('role') // a role is required to be able to login
|
||||
cy.get('input[name="userattrval1"]').clear().type('readwrite')
|
||||
cy.get('input[name="userattr2"]').clear().type('inc_cas') // force the create CA welcome page to show
|
||||
cy.get('input[name="userattrval2"]').clear().type(ts.ca) // (by making Lagosta think there are no CAs)
|
||||
cy.get('input[name="refresh"]').uncheck() // prevent issuing of refresh tokens for this user
|
||||
cy.get('input[name="token_secs"]').clear().type(ts.token_secs) // control the lifetime of the issued access token
|
||||
cy.contains('Sign In').click()
|
||||
|
||||
// record the approximate time at which the token was issued
|
||||
let issued_at_ms = Date.now()
|
||||
|
||||
// verify that we are shown to be logged in to the Krill UI
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(shorttoken.u)
|
||||
|
||||
// verify that we are shown the CA create page
|
||||
cy.contains('Welcome to Krill')
|
||||
|
||||
// calculate the remaining time necessary to wait until the
|
||||
// create_ca_after_secs moment
|
||||
let time_elapsed_ms = Date.now() - issued_at_ms
|
||||
let time_remaining_ms = ts.create_ca_after_secs*1000 - time_elapsed_ms
|
||||
cy.wait(time_remaining_ms)
|
||||
|
||||
// Try to create a CA, by typing in the input, clicking the 'Create CA' button
|
||||
// and then clicking 'Ok'. This should fail, since the token can't be refreshed.
|
||||
cy.intercept({ method: 'POST', path: '/api/v1/cas'}).as('createCA')
|
||||
cy.contains('CA Handle')
|
||||
cy.get('form input[type="text"]').type(ts.ca)
|
||||
cy.contains('Create CA').click()
|
||||
cy.contains('OK').click()
|
||||
|
||||
if (ts.o) {
|
||||
cy.wait('@createCA').its('response.statusCode').should('eq', 200)
|
||||
} else {
|
||||
cy.wait('@createCA').its('response.statusCode').should('eq', 401)
|
||||
cy.contains('Your login session has expired. Please login again.')
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
[...create_ca_settings_401, ...create_ca_settings_403].forEach((ts) =>
|
||||
it('Try to create a CA with mock failure mode ' + ts.fm + ' enabled', () => {
|
||||
let user_name = 'user_' + ts.fm;
|
||||
let ca_name = 'some-unique-handle-name-' + Date.now();
|
||||
|
||||
// login
|
||||
cy.visit('/')
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]').clear().type(user_name)
|
||||
cy.get('input[name="userattr1"]').clear().type('role') // a role is required to be able to login
|
||||
cy.get('input[name="userattrval1"]').clear().type('readwrite')
|
||||
cy.get('input[name="userattr2"]').clear().type('inc_cas') // force the create CA welcome page to show
|
||||
cy.get('input[name="userattrval2"]').clear().type(ca_name) // (by making Lagosta think there are no CAs)
|
||||
cy.get('select[name="failure_mode"]').select(ts.fm)
|
||||
cy.get('select[name="failure_endpoint"]').select('token')
|
||||
cy.contains('Sign In').click()
|
||||
|
||||
// verify that we are shown to be logged in to the Krill UI
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(user_name)
|
||||
|
||||
// verify that we are shown the create CA welcome page
|
||||
cy.contains('Welcome to Krill')
|
||||
|
||||
// the token has a lifetime of 5 second and no refresh token
|
||||
// wait 6 seconds...
|
||||
// note: a shorter token with a 1 second lifetime doesn't work in the GitHub
|
||||
// Action runner environment because the token has sometimes already expired
|
||||
// by the time Krill verifies it!
|
||||
cy.wait(6000)
|
||||
|
||||
// Try to create a CA, by typing in the input, clicking the 'Create CA' button
|
||||
// and then clicking 'Ok'. This should fail, since the mock server should return a
|
||||
// exhibit the undesirable behaviour we configured which should result in an
|
||||
// error from Krill.
|
||||
cy.intercept({ method: 'POST', path: '/api/v1/cas'}).as('createCA')
|
||||
cy.contains('CA Handle')
|
||||
cy.get('form input[type="text"]').type(ca_name)
|
||||
cy.contains('Create CA').click()
|
||||
cy.contains('OK').click()
|
||||
|
||||
cy.wait('@createCA').its('response.statusCode').should('eq', ts.responseCode)
|
||||
})
|
||||
)
|
||||
|
||||
it('Login with short-lived refreshable token and try to refresh page', () => {
|
||||
let token_secs = 2;
|
||||
|
||||
// login
|
||||
cy.visit('/')
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]').clear().type(shortrefresh.u)
|
||||
cy.get('input[name="userattr1"]').clear().type('role') // a role is required to be able to login
|
||||
cy.get('input[name="userattrval1"]').clear().type('readonly')
|
||||
cy.get('input[name="token_secs"]').clear().type(token_secs) // control the lifetime of the issued access token
|
||||
cy.contains('Sign In').click()
|
||||
|
||||
// verify that we are shown to be logged in to the Krill UI
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(shortrefresh.u)
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// the token has a lifetime of 2 seconds and has a refresh token
|
||||
// wait 3 seconds..
|
||||
// note: a shorter token with a 1 second lifetime doesn't work in the
|
||||
// GitHub Action runner environment because the token has sometimes
|
||||
// already expired by the time Krill verifies it!
|
||||
cy.wait(1000 * (token_secs + 1))
|
||||
|
||||
// verify that we are still logged in to Krill
|
||||
cy.visit('/')
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(shortrefresh.u)
|
||||
}
|
||||
});
|
||||
|
||||
[-2, +2].forEach((timeout_adjust_secs) =>
|
||||
it('Slow provider response (' + (timeout_adjust_secs < 0 ? 'within' : 'beyond') + ' Krill HTTP client timeout) is handled correctly', () => {
|
||||
let name_prefix = 'slow-response-';
|
||||
let name_postfix = (timeout_adjust_secs < 0 ? 'within' : 'beyond') + '-krill-max';
|
||||
let user_name = name_prefix + name_postfix;
|
||||
let ca_name = name_prefix + 'ca-' + name_postfix;
|
||||
let delay_secs = KRILL_TEST_HTTP_CLIENT_TIMEOUT_SECS + timeout_adjust_secs;
|
||||
|
||||
// Pick a token expiration time that is not too long so we don't have to wait unnecessarily, but not too short as
|
||||
// on a slow system like GitHub Actions it can take a few seconds just for login to complete and then one of the
|
||||
// static asset fetches from the browser to Krill causes the token refresh attempt to occur already (and we don't
|
||||
// want it to occur until we try to create a CA).
|
||||
let token_secs = 5;
|
||||
|
||||
// login
|
||||
cy.visit('/')
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]').clear().type(user_name)
|
||||
cy.get('input[name="userattr1"]').clear().type('role') // a role is required to be able to login
|
||||
cy.get('input[name="userattrval1"]').clear().type('readwrite')
|
||||
cy.get('input[name="userattr2"]').clear().type('inc_cas') // force the create CA welcome page to show
|
||||
cy.get('input[name="userattrval2"]').clear().type(ca_name) // (by making Lagosta think there are no CAs)
|
||||
cy.get('select[name="failure_mode"]').select('slow_response')
|
||||
cy.get('select[name="failure_endpoint"]').select('token')
|
||||
cy.get('input[name="failure_param"]').clear().type(delay_secs) // control the delay at the provider
|
||||
cy.get('input[name="token_secs"]').clear().type(token_secs) // control the lifetime of the issued access token
|
||||
cy.contains('Sign In').click()
|
||||
|
||||
// record the approximate time at which the token was issued
|
||||
let issued_at_ms = Date.now()
|
||||
|
||||
// verify that we are shown to be logged in to the Krill UI
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(user_name)
|
||||
|
||||
// verify that we are shown the create CA welcome page
|
||||
cy.contains('Welcome to Krill')
|
||||
|
||||
// wait for the access token issued to Krill to expire so that it is forced to use the provider token endpoint to
|
||||
// exchange the refresh token for a new access token
|
||||
let time_elapsed_ms = Date.now() - issued_at_ms
|
||||
let time_till_after_expiration_ms = (token_secs * 1000) - time_elapsed_ms + 1000
|
||||
cy.log('Waiting ' + time_till_after_expiration_ms + 'ms until the Krill access token ' + token_secs*1000 + 'ms expiration point should have passed')
|
||||
cy.wait(time_till_after_expiration_ms)
|
||||
|
||||
// Try to create a CA, by typing in the input, clicking the 'Create CA' button and then clicking 'Ok'.
|
||||
cy.intercept({ method: 'POST', path: '/api/v1/cas'}).as('createCA')
|
||||
cy.contains('CA Handle')
|
||||
cy.get('form input[type="text"]').type(ca_name)
|
||||
cy.contains('Create CA').click()
|
||||
cy.contains('OK').click()
|
||||
|
||||
// Verify that the attempt to create the CA occurred.
|
||||
//
|
||||
// In the case where we configure the provider to respond slowly, but still within the Krill HTTP client timeout,
|
||||
// the CA creation attempt should be successful, and the response should have a new bearer token piggybacked on it
|
||||
// (which resulted from the token refresh attempt).
|
||||
//
|
||||
// In the case where we configure the provider to take longer to respond than Krill will wait, the CA creation
|
||||
// attempt should fail because Krill should have been unable to refresh its expired access token and thus should
|
||||
// deny the CA creation request.
|
||||
let expected_status_code = (timeout_adjust_secs < 0 ? 200 : 401);
|
||||
let time_till_after_provider_delay_is_over_ms = delay_secs * 1000;
|
||||
let time_to_wait_ms = time_till_after_provider_delay_is_over_ms + 3000;
|
||||
if (timeout_adjust_secs < 0) {
|
||||
cy.log('Expecting within ' + time_till_after_provider_delay_is_over_ms + 'ms the provider to finish delaying and for Krill to create the CA')
|
||||
} else {
|
||||
cy.log('Expecting Krill to timeout the provider before the ' + time_till_after_provider_delay_is_over_ms + 'ms remaining provider delay elapses')
|
||||
}
|
||||
cy.log('Waiting max ' + time_to_wait_ms + 'ms for Krill to respond to the CA create request')
|
||||
cy.wait('@createCA', { responseTimeout: time_to_wait_ms }).its('response.statusCode').should('eq', expected_status_code)
|
||||
})
|
||||
)
|
||||
})
|
||||
@@ -1,61 +0,0 @@
|
||||
describe('OpenID Connect provider connection issues are tolerated', () => {
|
||||
it('The login form should not be available', () => {
|
||||
cy.request('POST', 'https://127.0.0.1:1818/test/disable')
|
||||
cy.wait(500)
|
||||
|
||||
cy.visit('/')
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form').should('not.exist')
|
||||
cy.contains('An error occurred while logging you in: OpenID Connect: Cannot get login URL: Failed to connect to provider')
|
||||
})
|
||||
|
||||
it('Login and logout should succeed', () => {
|
||||
cy.request('POST', 'https://127.0.0.1:1818/test/enable')
|
||||
cy.wait(500)
|
||||
|
||||
// Login
|
||||
cy.visit('/')
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]').clear().type('admin')
|
||||
cy.get('input[name="userattr1"]').clear().type('role') // a role is required to be able to login
|
||||
cy.get('input[name="userattrval1"]').clear().type('admin')
|
||||
cy.contains('Sign In').click()
|
||||
|
||||
// verify that we are shown to be logged in to the Krill UI
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains('admin')
|
||||
|
||||
// verify that the mock provider thinks the user is logged in
|
||||
cy.request({ url: 'https://127.0.0.1:1818/test/is_user_logged_in?username=admin', failOnStatusCode: false }).its('status').should('eq', 200)
|
||||
|
||||
// logout
|
||||
cy.intercept({ method: 'GET', url: /^https:\/\/localhost:1818\/logout.+/}).as('oidcLogout')
|
||||
cy.get('.logout').click()
|
||||
cy.wait('@oidcLogout').its('response.statusCode').should('eq', 302)
|
||||
|
||||
// verify that the mock provider thinks the user is now logged out
|
||||
cy.request({ url: 'https://127.0.0.1:1818/test/is_user_logged_in?username=admin', failOnStatusCode: false }).its('status').should('eq', 400)
|
||||
|
||||
// verify that we are shown the OpenID Connect provider login page
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]')
|
||||
})
|
||||
|
||||
it('Login should fail to redirect to the discovered but unavailable login page', () => {
|
||||
cy.request('POST', 'https://127.0.0.1:1818/test/disable')
|
||||
cy.wait(500)
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
it('The login page should be reachable again', () => {
|
||||
cy.request('POST', 'https://127.0.0.1:1818/test/enable')
|
||||
cy.wait(500)
|
||||
cy.visit('/')
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
})
|
||||
})
|
||||
@@ -1,35 +0,0 @@
|
||||
let username = 'admin@krill';
|
||||
|
||||
describe('OpenID Connect provider with custom logout URL', () => {
|
||||
it('Logout when logged in behaves as expected', () => {
|
||||
cy.visit('/')
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]').clear().type(username)
|
||||
cy.get('input[name="userattr1"]').clear().type('role') // a role is required to be able to login
|
||||
cy.get('input[name="userattrval1"]').clear().type('admin')
|
||||
cy.contains('Sign In').click()
|
||||
|
||||
// We should end up back in the Krill UI
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(username)
|
||||
cy.get('#userinfo_table').contains("role")
|
||||
|
||||
// verify that the mock provider thinks the user is logged in
|
||||
cy.request('https://127.0.0.1:1818/test/is_user_logged_in?username=' + username).its('status').should('eq', 200)
|
||||
|
||||
// logout
|
||||
cy.intercept('https://example.net/', 'custom logout page requested').as('getCustomLogoutURL')
|
||||
cy.get('.logout').click()
|
||||
|
||||
// verify that we are directed to the custom logout URL stub
|
||||
cy.wait('@getCustomLogoutURL').its('response.statusCode').should('eq', 200)
|
||||
cy.url().should('eq', 'https://example.net/')
|
||||
|
||||
// verify that the mock provider thinks the user is STILL logged in because due to the use of a custom logout URL
|
||||
// we deliberately did NOT tell the OpenID Connect mock provider that the user should be logged out
|
||||
cy.request('https://127.0.0.1:1818/test/is_user_logged_in?username=' + username).its('status').should('eq', 200)
|
||||
})
|
||||
})
|
||||
@@ -1,40 +0,0 @@
|
||||
let username = 'admin@krill';
|
||||
|
||||
describe('OpenID Connect provider with fallback logout URL', () => {
|
||||
it('Logout when logged in behaves as expected', () => {
|
||||
cy.visit('/')
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]').clear().type(username)
|
||||
cy.get('input[name="userattr1"]').clear().type('role') // a role is required to be able to login
|
||||
cy.get('input[name="userattrval1"]').clear().type('admin')
|
||||
cy.contains('Sign In').click()
|
||||
|
||||
// We should end up back in the Krill UI
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(username)
|
||||
cy.get('#userinfo_table').contains("role")
|
||||
|
||||
// verify that the mock provider thinks the user is logged in
|
||||
cy.request('https://127.0.0.1:1818/test/is_user_logged_in?username=' + username).its('status').should('eq', 200)
|
||||
|
||||
// logout
|
||||
cy.intercept({ method: 'POST', path: '/auth/logout'}).as('getLogoutURL')
|
||||
cy.intercept({ method: 'GET', path: '/index.html'}).as('getLoginForm')
|
||||
cy.get('.logout').click()
|
||||
|
||||
// verify that we are shown the OpenID Connect provider login page
|
||||
cy.wait('@getLogoutURL').its('response.statusCode').should('eq', 200)
|
||||
cy.wait('@getLoginForm').its('response.statusCode').should('eq', 200)
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]')
|
||||
|
||||
// verify that the mock provider thinks the user is STILL logged in because due to the OpenID Connect mock being
|
||||
// configured to NOT support end_session_endpoint or revocation_endpoint there is no way to tell the mock that we
|
||||
// are logging the user out
|
||||
cy.request('https://127.0.0.1:1818/test/is_user_logged_in?username=' + username).its('status').should('eq', 200)
|
||||
})
|
||||
})
|
||||
@@ -1,61 +0,0 @@
|
||||
let login_test_settings = [
|
||||
{ u: 'shorttokenwithrefresh@krill', o: true, refresh: true },
|
||||
{ u: 'shorttokenwithoutrefresh@krill', o: false, refresh: false }
|
||||
];
|
||||
|
||||
describe('OpenID Connect provider with OAuth 2 revocation', () => {
|
||||
login_test_settings.forEach(function (ts) {
|
||||
it('Logout when logged in as user ' + ts.u + ' should ' + (ts.o ? 'successfully' : 'fail to') + ' revoke the token', () => {
|
||||
cy.intercept({ url: /^https:\/\/localhost:1818\/authorize/ }).as('getLoginForm')
|
||||
cy.intercept({ url: /^https:\/\/localhost:1818\/login_form_submit/ }).as('submitLoginForm')
|
||||
cy.intercept({ url: /^https:\/\/localhost:3000\/auth\/callback/ }).as('completeTheLoginInKrill')
|
||||
cy.intercept({ url: /^https:\/\/localhost:3000\/index\.html/ }).as('afterLoginCompleteInKrill')
|
||||
|
||||
cy.visit('/')
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]').clear().type(ts.u)
|
||||
cy.get('input[name="userattr1"]').clear().type('role') // a role is required to be able to login
|
||||
cy.get('input[name="userattrval1"]').clear().type('readonly')
|
||||
if (ts.refresh) {
|
||||
cy.get('input[name="refresh"]').check() // ensure issuing of refresh tokens for this user
|
||||
} else {
|
||||
cy.get('input[name="refresh"]').uncheck() // prevent issuing of refresh tokens for this user
|
||||
}
|
||||
cy.contains('Sign In').click()
|
||||
|
||||
cy.wait(['@getLoginForm', '@submitLoginForm', '@completeTheLoginInKrill', '@afterLoginCompleteInKrill'])
|
||||
|
||||
// We should end up back in the Krill UI
|
||||
cy.url().should('include', Cypress.config('baseUrl'))
|
||||
cy.contains('Sign In').should('not.exist')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(ts.u)
|
||||
cy.get('#userinfo_table').contains("role")
|
||||
|
||||
// verify that the mock provider thinks the user is logged in
|
||||
cy.request({ url: 'https://127.0.0.1:1818/test/is_user_logged_in?username=' + ts.u, failOnStatusCode: false }).its('status').should('eq', 200)
|
||||
|
||||
// logout, and thus trigger the invocation of the OAuth 2.0 token revocation endpoint
|
||||
// for users with both a refresh token and an access token first Krill will try to revoke the refresh token
|
||||
// then will retry if that fails with the access token
|
||||
cy.intercept({ url: /^https:\/\/localhost:3000\/auth\/logout/ }).as('getLogoutURL')
|
||||
cy.get('.logout').click()
|
||||
|
||||
// verify that we are shown the OpenID Connect provider login page
|
||||
cy.wait('@getLogoutURL').its('response.statusCode').should('eq', 200)
|
||||
cy.url().should('not.include', Cypress.config('baseUrl'))
|
||||
cy.contains('Mock OpenID Connect login form')
|
||||
cy.get('input[name="username"]')
|
||||
|
||||
if (ts.o) {
|
||||
// verify that the mock provider thinks the user is now logged out
|
||||
cy.request({ url: 'https://127.0.0.1:1818/test/is_user_logged_in?username=' + ts.u, failOnStatusCode: false }).its('status').should('eq', 400)
|
||||
} else {
|
||||
// verify that the mock provider thinks the user is still logged in (because it only supports revocation by
|
||||
// refresh token, not by access token)
|
||||
cy.request({ url: 'https://127.0.0.1:1818/test/is_user_logged_in?username=' + ts.u, failOnStatusCode: false }).its('status').should('eq', 200)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,56 +0,0 @@
|
||||
// Team names and CAs they can access are defined in doc/policies/team-based-access-demo.polar.
|
||||
// Team memberships and user roles within teams are defined in test-resources/ui/multi_user_team_based_access.conf.
|
||||
// This test verifies that team roles and team CA rights work as expected, as defined in those files.
|
||||
|
||||
// A note about strong password hashing login delays
|
||||
// -----------------------------------------------------------------------------
|
||||
// The strong password hashing on the client and server side when logging in with
|
||||
// config file users causes the login process to take a few seconds. As such we
|
||||
// extend the default timeout when checking for Sign In completion, like so:
|
||||
//
|
||||
// cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
|
||||
let t1ro = { u: 'team1ro@krill', p: 'team1ro' };
|
||||
let t1rw = { u: 'team1rw@krill', p: 'team1rw' };
|
||||
let t2ro = { u: 'team2ro@krill', p: 'team2ro' };
|
||||
let t2rw = { u: 'team2rw@krill', p: 'team2rw' };
|
||||
|
||||
let create_ca_test_settings = [
|
||||
{ d: 't1ro', u: t1ro.u, p: t1ro.p, o: false, ca: 'ca1', t: 'Red Team', tr: 'Read Only' },
|
||||
{ d: 't1rw', u: t1rw.u, p: t1rw.p, o: true, ca: 'ca1', t: 'Red Team', tr: 'Read Write' },
|
||||
{ d: 't2ro', u: t2ro.u, p: t2ro.p, o: false, ca: 'ca2', t: 'Blue Team', tr: 'Read Only' },
|
||||
{ d: 't2rw', u: t2rw.u, p: t2rw.p, o: true, ca: 'ca2', t: 'Blue Team', tr: 'Read Write' },
|
||||
];
|
||||
|
||||
describe('Config File users with custom team policy', () => {
|
||||
create_ca_test_settings.forEach(function (ts) {
|
||||
it('Create CA as ' + ts.d + ' user should ' + (ts.o ? 'succeed' : 'fail'), () => {
|
||||
cy.visit('/')
|
||||
cy.get('#login_id').type(ts.u)
|
||||
cy.get('#login_password').type(ts.p)
|
||||
cy.contains('Sign In').click()
|
||||
cy.contains('Sign In', { timeout: 10000 }).should('not.exist')
|
||||
cy.contains(ts.u)
|
||||
cy.contains('Welcome to Krill')
|
||||
|
||||
// verify our team and role
|
||||
cy.get('#userinfo')
|
||||
cy.get('#userinfo').click()
|
||||
cy.get('#userinfo_table').contains(ts.t)
|
||||
cy.get('#userinfo_table').contains(ts.tr)
|
||||
|
||||
// create a CA
|
||||
cy.contains('CA Handle')
|
||||
cy.get('form input[type="text"]').type(ts.ca)
|
||||
cy.contains('Create CA').click()
|
||||
cy.contains('OK').click()
|
||||
|
||||
// no longer on the welcome page
|
||||
if (ts.o) {
|
||||
cy.contains('Welcome to Krill').should('not.exist')
|
||||
} else {
|
||||
cy.contains('Welcome to Krill')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,99 +0,0 @@
|
||||
// Difficulty entering XML into Lagosta XML input fields:
|
||||
// ------------------------------------------------------
|
||||
// Using cy.type() to enter XML into these fields is extremely slow, one
|
||||
// animated character at a time. I haven't yet found a way to copy-paste into
|
||||
// them. Setting the text directly can be done but there is a challenge that has
|
||||
// to be worked around which is that the fields use Prism Editor JS to syntax
|
||||
// highlight the XML. Prism Editor manages the content as a rich HTML child node
|
||||
// structure. Just replacing the content with a new text node doesn't work as
|
||||
// the Lagosta JS code reading the field content doesn't get the content as
|
||||
// set for some reason. The set text also doesn't get syntax highlighted. What
|
||||
// seems to work however is causing a keyboard event in the field after the text
|
||||
// has been set, e.g. pressing the End key.
|
||||
//
|
||||
// To summarize, the following works quickly:
|
||||
// cy.get('... pre[contenteditable="true"]').invoke('text', xml)
|
||||
// cy.get('... pre[contenteditable="true"]').type('{end}')
|
||||
//
|
||||
// This is less hacky but very slow: (even with "type(xml, {delay: 0}))")
|
||||
// cy.get('... pre[contenteditable="true"]').clear().type(xml)
|
||||
|
||||
let publisher_request_test_settings = [
|
||||
{ desc: 'Compact publisher request XML is accepted', fixture: 'testbed/publisher_request_compact.xml', httpCode: 200 },
|
||||
{ desc: 'Publisher request with whitespace is accepted', fixture: 'testbed/publisher_request_with_whitespace.xml', httpCode: 200 },
|
||||
{ desc: 'Publisher request with invalid Base64 certificate is rejected by Lagosta', fixture: 'testbed/publisher_request_invalid_base64.xml', httpCode: 'n/a', errMsg: 'Element <publisher_bpki_ta> must contain a correctly Base64 encoded self-signed X.509 BPKI certificate' },
|
||||
{ desc: 'Publisher request with unicode space char is rejected by Lagosta', fixture: 'testbed/publisher_request_with_unicode_space_char.xml', httpCode: 'n/a', errMsg: 'Element <publisher_bpki_ta> cannot contain non-ASCII characters' },
|
||||
{ desc: 'Publisher request with unicode space entity reference is rejected by Lagosta', fixture: 'testbed/publisher_request_with_unicode_space_entity_reference.xml', httpCode: 'n/a', errMsg: 'Element <publisher_bpki_ta> cannot contain non-ASCII characters' },
|
||||
{ desc: 'Publisher request with unicode space entity reference in handle is rejected by Krill', fixture: 'testbed/publisher_request_with_unicode_space_entity_reference_in_handle.xml', httpCode: 400, errMsg: 'Input contains non-ASCII chars (maybe whitespace?)' },
|
||||
];
|
||||
|
||||
let child_request_test_settings = [
|
||||
{ desc: 'Compact child request XML is accepted', fixture: 'testbed/child_request_compact.xml', httpCode: 200 },
|
||||
{ desc: 'Child request with whitespace is accepted', fixture: 'testbed/child_request_with_whitespace.xml', httpCode: 200 },
|
||||
{ desc: 'Child request with invalid Base64 certificate is rejected by Lagosta', fixture: 'testbed/child_request_invalid_base64.xml', httpCode: 'n/a', errMsg: 'Element <child_bpki_ta> must contain a correctly Base64 encoded self-signed X.509 BPKI certificate' },
|
||||
{ desc: 'Child request with unicode space char is rejected by Lagosta', fixture: 'testbed/child_request_with_unicode_space_char.xml', httpCode: 'n/a', errMsg: 'Element <child_bpki_ta> cannot contain non-ASCII characters' },
|
||||
{ desc: 'Child request with unicode space entity reference is rejected by Lagosta', fixture: 'testbed/child_request_with_unicode_space_entity_reference.xml', httpCode: 'n/a', errMsg: 'Element <child_bpki_ta> cannot contain non-ASCII characters' },
|
||||
{ desc: 'Child request with unicode space entity reference in handle is rejected by Krill', fixture: 'testbed/child_request_with_unicode_space_entity_reference_in_handle.xml', httpCode: 400, errMsg: 'Input contains non-ASCII chars (maybe whitespace?)' },
|
||||
];
|
||||
|
||||
describe('Testbed UI test', () => {
|
||||
publisher_request_test_settings.forEach(function (ts) {
|
||||
it(ts.desc, () => {
|
||||
cy.fixture(ts.fixture).then((xml) => {
|
||||
// use the local testbed UI to submit the request to register the publisher
|
||||
cy.visit("/index.html#/testbed")
|
||||
|
||||
// verify that the register child tab is active by default
|
||||
cy.get('#addChild').contains('Child Request XML').should('be.visible')
|
||||
|
||||
// enter the request XML into the testbed UI edit field
|
||||
cy.get('div#tab-addPublisher').contains('Register Publisher').click()
|
||||
cy.get('#addPublisher pre[contenteditable="true"]').invoke('text', xml)
|
||||
cy.get('#addPublisher pre[contenteditable="true"]').type('{end}')
|
||||
|
||||
cy.intercept({ method: 'POST', path: '/testbed/publishers'}).as('addPublisher')
|
||||
cy.get('#addPublisher button').contains('Register publisher').click()
|
||||
|
||||
if (ts.httpCode != 'n/a') {
|
||||
cy.get('div[role="dialog"] button').contains('OK').click()
|
||||
cy.wait('@addPublisher').its('response.statusCode').should('eq', ts.httpCode)
|
||||
}
|
||||
|
||||
if (ts.httpCode == 200) {
|
||||
cy.contains('has been added to the testbed')
|
||||
} else {
|
||||
cy.contains(ts.errMsg)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
child_request_test_settings.forEach(function (ts) {
|
||||
it(ts.desc, () => {
|
||||
cy.fixture(ts.fixture).then((xml) => {
|
||||
// use the local testbed UI to submit the request to register the child
|
||||
cy.visit("/index.html#/testbed")
|
||||
|
||||
// enter the request XML into the testbed UI edit field
|
||||
cy.get('div#tab-addChild').contains('Register CA').click()
|
||||
cy.get('#addChild pre[contenteditable="true"]').invoke('text', xml)
|
||||
cy.get('#addChild pre[contenteditable="true"]').type('{end}')
|
||||
cy.get('#addChild input[placeholder^="The AS resources"]').type('AS18')
|
||||
cy.get('#addChild input[placeholder^="The IPv4 resources"]').type('10.0.0.0/24')
|
||||
|
||||
cy.intercept({ method: 'POST', path: '/testbed/children'}).as('addChild')
|
||||
cy.get('#addChild button').contains('Register child CA').click()
|
||||
|
||||
if (ts.httpCode != 'n/a') {
|
||||
cy.get('div[role="dialog"] button').contains('OK').click()
|
||||
cy.wait('@addChild').its('response.statusCode').should('eq', ts.httpCode)
|
||||
}
|
||||
if (ts.httpCode == 200) {
|
||||
cy.contains('has been added to the testbed')
|
||||
} else {
|
||||
cy.contains(ts.errMsg)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,58 +0,0 @@
|
||||
// This file is loaded by Cypress because cypress.js (in the root of the Krill
|
||||
// repository) sets "supportFile" to point to this file.
|
||||
//
|
||||
// As advised by Cypress *1, prevent Cypress sometimes failing tests due to
|
||||
// error "ResizeObserver loop limit exceeded" errors.
|
||||
//
|
||||
// *1: jennifer@cypress.io aka https://github.com/jennifer-shehane who wrote
|
||||
// the following at *2 which was linked from *3:
|
||||
//
|
||||
// const resizeObserverLoopErrRe = /^ResizeObserver loop limit exceeded/
|
||||
//
|
||||
// Cypress.on('uncaught:exception', (err) => {
|
||||
// if (resizeObserverLoopErrRe.test(err.message)) {
|
||||
// // returning false here prevents Cypress from
|
||||
// // failing the test
|
||||
// return false
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// *2: https://github.com/quasarframework/quasar/issues/2233#issuecomment-492975745
|
||||
// *3: https://github.com/WICG/resize-observer/issues/38#issuecomment-493014026
|
||||
//
|
||||
// See also:
|
||||
// - https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/fundamentals__errors/cypress/integration/app-error.js
|
||||
// - https://docs.cypress.io/api/events/catalog-of-events.html#Uncaught-Exceptions
|
||||
// - https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Support-file
|
||||
// - https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded/63519375#63519375
|
||||
// - https://github.com/WICG/resize-observer/issues/38
|
||||
|
||||
// Define a custom uncaught exception handling policy for Cypress.
|
||||
// Returning false prevents Cypress from failing the test.
|
||||
Cypress.on('uncaught:exception', (err, runnable, promise) => {
|
||||
console.log("Krill UI Test: Examining uncaught exception..")
|
||||
console.log("Krill UI Test: err: ", err)
|
||||
|
||||
if (promise) {
|
||||
console.log("Krill UI Test: Ignoring unhandled promise rejection.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (err.message) {
|
||||
if (err.message.includes('ResizeObserver loop limit exceeded')) {
|
||||
console.log("Krill UI Test: Ignoring 'ResizeObserver loop limit exceeded' exception")
|
||||
return false
|
||||
}
|
||||
if (err.message.includes('Redirected when going from')) {
|
||||
// This happens when going from "/onboarding" to "/interstitial" via a navigation guard and is triggered
|
||||
// when logging out of Krill.
|
||||
// TODO: Is it safe to ignore this or is this pointing to a real bug in Lagosta?
|
||||
console.log("Krill UI Test: Ignoring 'Redirected when going from' exception")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// on any other error message the test fails
|
||||
console.log("Krill UI Test: Failing the test")
|
||||
})
|
||||
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
#[cfg(feature = "multi-user")]
|
||||
mod openid_connect_mock;
|
||||
|
||||
use tokio::task;
|
||||
use OpenIDConnectMockMode::NotStarted;
|
||||
|
||||
use std::process::Command;
|
||||
use std::{env, process::ExitStatus};
|
||||
|
||||
use krill::daemon::config::Config;
|
||||
use krill::test::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum OpenIDConnectMockMode {
|
||||
NotStarted,
|
||||
WithRPInitiatedLogout,
|
||||
WithOAuth2Revocation,
|
||||
WithNoLogoutEndpoints,
|
||||
}
|
||||
|
||||
pub struct OpenIDConnectMockConfig {
|
||||
mode: OpenIDConnectMockMode,
|
||||
enabled_on_startup: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl OpenIDConnectMockConfig {
|
||||
/// Don't start the OpenID Connect mock.
|
||||
pub fn do_not_start() -> OpenIDConnectMockConfig {
|
||||
Self {
|
||||
mode: NotStarted,
|
||||
enabled_on_startup: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the OpenID Mock and enable it ready for use.
|
||||
pub fn enabled(mode: OpenIDConnectMockMode) -> OpenIDConnectMockConfig {
|
||||
Self {
|
||||
mode,
|
||||
enabled_on_startup: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the OpenID Mock initially disabled. This can be useful to prevent initial OpenID Connect Discovery
|
||||
/// succeeding before the first test runs.
|
||||
pub fn disabled(mode: OpenIDConnectMockMode) -> OpenIDConnectMockConfig {
|
||||
Self {
|
||||
mode,
|
||||
enabled_on_startup: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mode(&self) -> OpenIDConnectMockMode {
|
||||
self.mode
|
||||
}
|
||||
|
||||
pub fn enabled_on_startup(&self) -> bool {
|
||||
self.enabled_on_startup
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "multi-user"))]
|
||||
pub async fn run_krill_ui_test(test_name: &str, _: OpenIDConnectMockConfig) {
|
||||
assert!(do_run_krill_ui_test(test_name).await);
|
||||
}
|
||||
|
||||
#[cfg(feature = "multi-user")]
|
||||
pub async fn run_krill_ui_test(test_name: &str, openid_connect_mock_config: OpenIDConnectMockConfig) {
|
||||
let op_handle = match openid_connect_mock_config.mode() {
|
||||
NotStarted => None,
|
||||
_ => Some(openid_connect_mock::start(openid_connect_mock_config, 1).await),
|
||||
};
|
||||
|
||||
let test_result = do_run_krill_ui_test(test_name).await;
|
||||
|
||||
if let Some(handle) = op_handle {
|
||||
openid_connect_mock::stop(handle).await;
|
||||
}
|
||||
|
||||
assert!(test_result);
|
||||
}
|
||||
|
||||
struct CypressRunner {
|
||||
status: ExitStatus,
|
||||
}
|
||||
impl CypressRunner {
|
||||
pub async fn run(test_name: &str) -> Self {
|
||||
let test_name = test_name.to_string();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
// If `cargo test` is stopped with CTRL-C the background Cypress Docker container continues to run. This
|
||||
// prevents the next run of `cargo test` from working as the container unexpectedly already exists. Tell
|
||||
// Docker to kill it to avoid leaving it lying around.
|
||||
Command::new("docker")
|
||||
.arg("kill")
|
||||
.arg("cypress")
|
||||
.spawn()
|
||||
.expect("Failed to kill Cypress Docker container");
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
|
||||
let task = task::spawn_blocking(move || {
|
||||
// NOTE: the directory mentioned here must be the same as the directory
|
||||
// mentioned in the tests/ui/cypress/plugins/index.js file in the
|
||||
// "integrationFolder" property otherwise Cypress mysteriously complains
|
||||
// that it cannot find the spec file.
|
||||
let cypress_spec_path = format!("tests/ui/cypress/specs/{}.js", test_name);
|
||||
|
||||
let mut cmd = Command::new("docker");
|
||||
|
||||
cmd.arg("run")
|
||||
.arg("--name")
|
||||
.arg("cypress")
|
||||
.arg("--rm")
|
||||
.arg("--net=host")
|
||||
.arg("--ipc=host")
|
||||
.arg("-v")
|
||||
.arg(format!("{}:/e2e", env::current_dir().unwrap().display()))
|
||||
.arg("-w")
|
||||
.arg("/e2e");
|
||||
|
||||
if let Ok(debug_level) = std::env::var("CYPRESS_DEBUG") {
|
||||
// Example values:
|
||||
// - To get LOTS of Cypress logging: CYPRESS_DEBUG=cypress:*
|
||||
// - To get logging relating to HTTP requests: CYPRESS_DEBUG=cypress:proxy:http:*
|
||||
cmd.arg("-e").arg(format!("DEBUG={}", debug_level));
|
||||
}
|
||||
|
||||
if std::env::var("CYPRESS_INTERACTIVE").is_ok() {
|
||||
// After running `cargo test` a Chrome browser should open from the Cypress Docker container on your local
|
||||
// X server. For this to work you might need to run this command in your shell prior to `cargo test`:
|
||||
// xhost +
|
||||
cmd.arg("-v")
|
||||
.arg("/tmp/.X11-unix:/tmp/.X11-unix")
|
||||
.arg("-e")
|
||||
.arg("DISPLAY")
|
||||
.arg("--entrypoint")
|
||||
.arg("cypress");
|
||||
}
|
||||
|
||||
cmd.arg("cypress/included:8.1.0");
|
||||
|
||||
if std::env::var("CYPRESS_INTERACTIVE").is_ok() {
|
||||
cmd.arg("open").arg("--project").arg(".");
|
||||
} else {
|
||||
cmd.arg("--spec").arg(cypress_spec_path);
|
||||
}
|
||||
|
||||
cmd.arg("--browser")
|
||||
.arg("chrome")
|
||||
.status()
|
||||
.expect("Failed to run Cypress Docker UI test suite")
|
||||
})
|
||||
.await;
|
||||
|
||||
Self { status: task.unwrap() }
|
||||
}
|
||||
|
||||
pub fn success(self) -> bool {
|
||||
self.status.success()
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_run_krill_ui_test(test_name: &str) -> bool {
|
||||
krill::constants::enable_test_mode();
|
||||
let config_path = &format!("test-resources/ui/{}.conf", test_name);
|
||||
let config = Config::read_config(config_path).unwrap();
|
||||
|
||||
// Start Krill as a Tokio task in the background and wait just until we can tell that it has started.
|
||||
start_krill_with_custom_config(config).await;
|
||||
|
||||
// Run the specified Cypress UI test suite and wait for it to finish
|
||||
CypressRunner::run(test_name).await.success()
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user