diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 22e0da880712d38d7a31d6c4fdc9f5166ae6cc1b..1dc9f1348ff252c11ab41d51a6812e5795323208 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,26 +29,26 @@ login_provider: - login_provider/**/* - .gitlab-ci.yml -login_logout: +integration_test_app: stage: build-test-images variables: - KANIKO_CONTEXT: "test/login_logout" + KANIKO_CONTEXT: "test/integration_tests" KANIKO_BUILD_IMAGENAME: $CI_JOB_NAME extends: .kaniko_build only: changes: - - ./test/login_logout/**/* + - ./test/integration_tests/**/* - .gitlab-ci.yml integration_test: stage: build-test-images variables: - KANIKO_CONTEXT: "test/login_logout/test" + KANIKO_CONTEXT: "test/integration_tests/test" KANIKO_BUILD_IMAGENAME: $CI_JOB_NAME extends: .kaniko_build only: changes: - - ./test/login_logout/test/**/* + - ./test/integration_tests/test/**/* - .gitlab-ci.yml @@ -71,7 +71,7 @@ behave-integration: - http://oauth:5000/callback - name: open.greenhost.net:4567/openappstack/user-panel/backend:master alias: backend - - name: ${CI_REGISTRY_IMAGE}/login_logout:${CI_COMMIT_REF_NAME} + - name: ${CI_REGISTRY_IMAGE}/integration_test_app:${CI_COMMIT_REF_NAME} alias: oauth variables: # Feature Flag FF_NETWORK_PER_BUILD Enables creation of a docker network per build @@ -125,7 +125,7 @@ behave-integration: # Wait for 60s for hydra to become available. Then create the oauth2 client object - while [[ $HYDRASTATUS -ne "200" && 60 -ge $TIMER ]]; do HYDRASTATUS=`curl http://hydra:4445/health/ready -o /dev/null -w "%{http_code}"` || TIMER=$TIMER+5 && sleep 5 ; done - /bin/bash test/create-hydra-client.bash ${KEY} ${SECRET} http://hydra:4445 http://oauth:5000/callback - - cd test/login_logout/test/behave/ + - cd test/integration_tests/test/behave/ - > python3 -m behave -D headless=True @@ -137,6 +137,6 @@ behave-integration: -D role=${ROLE} artifacts: paths: - - test/login_logout/test/behave/screenshots/ + - test/integration_tests/test/behave/screenshots/ expire_in: 1 month when: on_failure diff --git a/docker-compose.yml b/docker-compose.yml index 696e6b0bc2a2adfdebd07d5bae4fef994e516d66..8ed729d76d821a2449a56e26fec6ddc442329f45 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -64,7 +64,7 @@ services: ports: - "5432:5432" oauth: - build: ./test/login_logout + build: ./test/integration_tests environment: - BASE_URL=http://localhost:4444/ - ACCESS_TOKEN_URL=http://hydra:4444/oauth2/token diff --git a/login_provider/app.py b/login_provider/app.py index bf8052e1d50658e556f3cdce2fc37a3a9ef85fdf..67ccce1db6a9e614455b6dd6575f79e38471c8d2 100644 --- a/login_provider/app.py +++ b/login_provider/app.py @@ -89,7 +89,7 @@ def login(): HYDRA.invalidate_login_sessions(login_request.subject); return redirect(login_request.reject( "Login cancelled", - error_description="Login was cancelled and user session was terminated ")) + error_description="Login was cancelled and user session was terminated")) else: return render_template('skip.html', challenge=challenge, logo=login_request.client.logo_uri, application_name=login_request.client.client_name, username=login_request.subject) diff --git a/login_provider/templates/skip.html b/login_provider/templates/skip.html index 3a867bd4c9e910a12d9a19ecc1a29ec92f0dbe4b..64b2474979f4b35b9f0527b28da08bafa907c127 100644 --- a/login_provider/templates/skip.html +++ b/login_provider/templates/skip.html @@ -8,7 +8,7 @@ {% endif %} <h1>Log in to {{ application_name }}</h1> <div style="width: 100%; margin-bottom: 5px; overflow: auto"> - <div style="width:60%; float:left"><button onclick="window.location.href = '/login?login_challenge={{ challenge }}&skip=true';">Continue with {{ username }}</button></div> - <div style="width:40%; float:left;"><button onclick="window.location.href = '/login?login_challenge={{ challenge }}&logout=true';">Logout</button></div> + <div style="width:60%; float:left"><button id="continue" onclick="window.location.href = '/login?login_challenge={{ challenge }}&skip=true';">Continue with {{ username }}</button></div> + <div style="width:40%; float:left;"><button id="logout" onclick="window.location.href = '/login?login_challenge={{ challenge }}&logout=true';">Logout</button></div> </div> </div> diff --git a/test/login_logout/Dockerfile b/test/integration_tests/Dockerfile similarity index 100% rename from test/login_logout/Dockerfile rename to test/integration_tests/Dockerfile diff --git a/test/login_logout/README.md b/test/integration_tests/README.md similarity index 100% rename from test/login_logout/README.md rename to test/integration_tests/README.md diff --git a/test/login_logout/app.py b/test/integration_tests/app.py similarity index 100% rename from test/login_logout/app.py rename to test/integration_tests/app.py diff --git a/test/login_logout/requirements.txt b/test/integration_tests/requirements.txt similarity index 100% rename from test/login_logout/requirements.txt rename to test/integration_tests/requirements.txt diff --git a/test/login_logout/test/Dockerfile b/test/integration_tests/test/Dockerfile similarity index 100% rename from test/login_logout/test/Dockerfile rename to test/integration_tests/test/Dockerfile diff --git a/test/login_logout/test/behave/features/environment.py b/test/integration_tests/test/behave/features/environment.py similarity index 100% rename from test/login_logout/test/behave/features/environment.py rename to test/integration_tests/test/behave/features/environment.py diff --git a/test/integration_tests/test/behave/features/login.feature b/test/integration_tests/test/behave/features/login.feature new file mode 100644 index 0000000000000000000000000000000000000000..93da3043866eb0cff6066964de101c2ca04c8195 --- /dev/null +++ b/test/integration_tests/test/behave/features/login.feature @@ -0,0 +1,28 @@ +@oauth +Feature: Test login-provider function + As an OAS user + I want to be able to login to an OAS App + And verify my userdata that is provided by OpenID Connect + +Scenario: Open the oAuth application and Login witha valid user + Given the oauth client "home" URL was opened + And the element "input#username" is visible + When I enter the "username" in the inputfield "input#username" + And I enter the "password" in the inputfield "input#password" + And I click on the button "input#submit" + Then I wait on element "input#password" for 1000ms to not exist + And I expect that the path is "/callback" + And I expect that element "body" contains the text "access_token" + +Scenario: Get OpenID Connect userdata for testuser + When I open the oauth client "userinfo" URL + Then I expect that the "preferred_username" in the json output is the same as oauth variable "username" + And I expect that the "email" in the json output is the same as oauth variable "email" + And I expect that the "openappstack_roles" in the json output contains the value of oauth variable "role" + And I expect that the "name" in the json output is the same as oauth variable "username" + +Scenario: Logout + When I open the oauth client "logout" URL + Then I wait on element "input#username" for 1000ms to be visible + And I expect that element "input#password" is visible + And I expect that element "input#submit" is visible diff --git a/test/login_logout/test/behave/features/login.feature b/test/integration_tests/test/behave/features/reject_unauthorized_logins.feature similarity index 50% rename from test/login_logout/test/behave/features/login.feature rename to test/integration_tests/test/behave/features/reject_unauthorized_logins.feature index 84c8d00d8e6f80c7a2bb0169ba3b73324aadd657..c95b995944b5ffd4d59cea4033981a9d789f180e 100644 --- a/test/login_logout/test/behave/features/login.feature +++ b/test/integration_tests/test/behave/features/reject_unauthorized_logins.feature @@ -1,31 +1,8 @@ @oauth -Feature: Test login-provider function - As an OAS user - I want to be able to login to an OAS App - And verify my userdata that is provided by OpenID Connect - -Scenario: Open the oAuth application and Login witha valid user - Given the oauth client "home" URL was opened - And the element "input#username" is visible - When I enter the "username" in the inputfield "input#username" - And I enter the "password" in the inputfield "input#password" - And I click on the button "input#submit" - Then I wait on element "input#password" for 1000ms to not exist - And I expect that the path is "/callback" - And I expect that element "body" contains the text "access_token" - -Scenario: Get OpenID Connect userdata for testuser - When I open the oauth client "userinfo" URL - Then I expect that the "preferred_username" in the json output is "username" - And I expect that the "email" in the json output is "email" - And I expect that the "openappstack_roles" in the json output contains "role" - And I expect that the "name" in the json output is "username" - -Scenario: Logout - When I open the oauth client "logout" URL - Then I wait on element "input#username" for 1000ms to be visible - And I expect that element "input#password" is visible - And I expect that element "input#submit" is visible +Feature: Test features that prohibit unauthorized access + As an attacker or unauthorized user + I want to to login to an OAS App + And the single sign-on will block my login attempts Scenario: Login with a valid user without access to an application Given the oauth client "home" URL was opened @@ -40,7 +17,7 @@ Scenario: Login with a valid user without access to an application And I expect that element "body" contains the text "Permission denied" And I expect that element "body" contains the text "missing application permission" -Scenario: Login with an invalid user without +Scenario: Login with an invalid user Given the oauth client "home" URL was opened And the element "input#username" is visible When I set "not_a_valid_user" to the inputfield "input#username" diff --git a/test/integration_tests/test/behave/features/remember_me.feature b/test/integration_tests/test/behave/features/remember_me.feature new file mode 100644 index 0000000000000000000000000000000000000000..81ad49e2abfc9cad524e7429239fa5a3767eb8b7 --- /dev/null +++ b/test/integration_tests/test/behave/features/remember_me.feature @@ -0,0 +1,37 @@ +@oauth +Feature: Testing single sign-on sessions + As an OAS user + I want to login once to use an application + And I use my active single sign-on session to login again without providing credentials + +Scenario: Login with a valid user and remember session + Given the oauth client "home" URL was opened + And the element "input#username" is visible + And the element "input#remember" is visible + When I enter the "username" in the inputfield "input#username" + And I enter the "password" in the inputfield "input#password" + And I click on the element "input#remember" + And I click on the button "input#submit" + Then I wait on element "input#password" for 1000ms to not exist + And I expect that element "input#username" does not exist + And I expect that the path is "/callback" + And I expect that element "body" contains the text "access_token" + +Scenario: Login without providing credentials + Given the oauth client "logout" URL was opened + And I pause for 1000ms + And there is no element "input#username" on the page + And there is no element "input#password" on the page + And the element "button#continue" is visible + When I click on the element "button#continue" + Then I wait on element "button#continue" for 1000ms to not exist + And I expect that the path is "/callback" + And I expect that element "body" contains the text "access_token" + +Scenario: Terminate single sign-on session + Given the oauth client "logout" URL was opened + And I pause for 1000ms + And the element "button#logout" is visible + When I click on the element "button#logout" + Then I expect that the "error" in the json output is "Login cancelled" + And I expect that the "error_description" in the json output is "Login was cancelled and user session was terminated" diff --git a/test/integration_tests/test/behave/features/steps/compare_json_values.py b/test/integration_tests/test/behave/features/steps/compare_json_values.py new file mode 100644 index 0000000000000000000000000000000000000000..da73242d5460566c36e9820fdf7b7984de38fa93 --- /dev/null +++ b/test/integration_tests/test/behave/features/steps/compare_json_values.py @@ -0,0 +1,24 @@ +"""Custom steps for tests that anaylize a website that returns a json object.""" +import json +from behave import given, when, then +from behave_webdriver.steps import * + +@then(u'I expect that the "{variable}" in the json output is the same as oauth variable "{value}"') +def step_impl(context, variable, value): + assert context.oauth[value] == get_value_from_json_body(context, variable) + +@then(u'I expect that the "{variable}" in the json output is "{value}"') +def step_impl(context, variable, value): + assert value == get_value_from_json_body(context, variable) + +@then(u'I expect that the "{variable}" in the json output contains the value of oauth variable "{value}"') +def step_impl(context, variable, value): + assert context.oauth[value] in get_value_from_json_body(context, variable) + +@then(u'I expect that the "{variable}" in the json output contains "{value}"') +def step_impl(context, variable, value): + assert value in get_value_from_json_body(context, variable) + +def get_value_from_json_body(context, key): + obj_serialized = context.behave_driver.get_element("body").text + return json.loads(obj_serialized)[key] diff --git a/test/login_logout/test/behave/features/steps/login.py b/test/integration_tests/test/behave/features/steps/webdriver_with_variables.py similarity index 57% rename from test/login_logout/test/behave/features/steps/login.py rename to test/integration_tests/test/behave/features/steps/webdriver_with_variables.py index 4be73552ca95064a017dd68b060e1ffdb87886c4..e913d60f2680a5609b49d728ee41256266eb61fc 100644 --- a/test/login_logout/test/behave/features/steps/login.py +++ b/test/integration_tests/test/behave/features/steps/webdriver_with_variables.py @@ -1,9 +1,5 @@ -"""Custom steps for login tests.""" - -import string -import json - -from behave import given, when +"""Custom steps for login tests that use oauth environment variables""" +from behave import given, when, then from behave_webdriver.steps import * @@ -29,16 +25,3 @@ def step_impl(context, element, variable): elem = context.behave_driver.get_element(element) value = context.oauth[variable] assert value in elem.text - -@then(u'I expect that the "{variable}" in the json output is "{value}"') -def step_impl(context, variable, value): - assert context.oauth[value] == get_value_from_json_body(context, variable) - -@then(u'I expect that the "{variable}" in the json output contains "{value}"') -def step_impl(context, variable, value): - assert context.oauth[value] in get_value_from_json_body(context, variable) - -def get_value_from_json_body(context, key): - obj_serialized = context.behave_driver.get_element("body").text - return json.loads(obj_serialized)[key] -