diff --git a/README.md b/README.md index df766a12e1..bb0a425c2d 100644 --- a/README.md +++ b/README.md @@ -513,6 +513,10 @@ Skipping the cluster provisioning and running a single test: `robot -b debug.txt -v SERVER_VERSION:4.1.1 -v SYNC_GATEWAY_VERSION:1.2.0-79 -t "test sync sanity" testsuites/syncgateway/functional/1sg_1ac_1cbs/1sg_1ac_1cbs.robot` +Running a subsuite with parent suite setup + +`robot --loglevel DEBUG -v SERVER_VERSION:4.1.0 -v SYNC_GATEWAY_VERSION:42fc10bbc819fe34940c66abd1fd02a8d51490ca -s 1sg_1cbs-openid-connect testsuites/syncgateway/functional/1sg_1cbs` + **Running Performance Tests** - [Spin up a AWS CloudFormation stack](#Spin=Up-Machines-on-AWS) diff --git a/libraries/testkit/cluster.py b/libraries/testkit/cluster.py index b01dbfd69e..40fcd97472 100644 --- a/libraries/testkit/cluster.py +++ b/libraries/testkit/cluster.py @@ -69,9 +69,7 @@ def reset(self, config_path): # Stop sync_gateways log.info(">>> Stopping sync_gateway") status = ansible_runner.run_ansible_playbook("stop-sync-gateway.yml") - if status != 0: - log.error("Error in provisioning!! Verify your ssh user is correct in 'libraries/provision/playbooks/ansible.cfg'") - raise Exception("Failed to run provisioning") + assert status == 0, "Failed to stop sync gateway" # Stop sync_gateways log.info(">>> Stopping sg_accel") diff --git a/requirements.txt b/requirements.txt index 92a7ac45b1..34e077d2ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,7 @@ paramiko==1.16.0 pyasn1==0.1.9 pycparser==2.14 pycrypto==2.6.1 +PyJWT==1.4.0 python-dateutil==2.5.1 PyYAML==3.11 requests==2.9.1 diff --git a/resources/sync_gateway_configs/sync_gateway_openid_connect_cc.json b/resources/sync_gateway_configs/sync_gateway_openid_connect_cc.json new file mode 100644 index 0000000000..f4eb591e2a --- /dev/null +++ b/resources/sync_gateway_configs/sync_gateway_openid_connect_cc.json @@ -0,0 +1,78 @@ +{ + "log":[ + "*" + ], + "compressResponses": false, + "AdminInterface":"0.0.0.0:4985", + "databases":{ + "db":{ + "oidc":{ + "default_provider":"test", + "providers":{ + "testinvalidclientid":{ + "issuer":"http://localhost:4984/db/_oidc_testing", + "client_id":"invalid", + "validation_key":"R75hfd9lasdwertwerutecw8", + "callback_url":"http://localhost:4984/db/_oidc_callback", + "register":true + }, + "test":{ + "issuer":"http://localhost:4984/db/_oidc_testing", + "client_id":"sync_gateway", + "validation_key":"R75hfd9lasdwertwerutecw8", + "callback_url":"http://localhost:4984/db/_oidc_callback", + "register":true + }, + "testnosessions":{ + "issuer":"http://localhost:4984/db/_oidc_testing", + "client_id":"sync_gateway", + "validation_key":"R75hfd9lasdwertwerutecw8", + "callback_url":"http://localhost:4984/db/_oidc_callback", + "register":true, + "disable_session":true + }, + "testinvalidscope":{ + "issuer":"http://localhost:4984/db/_oidc_testing", + "client_id":"sync_gateway", + "validation_key":"R75hfd9lasdwertwerutecw8", + "callback_url":"http://localhost:4984/db/_oidc_callback", + "register":true, + "scope":["invalid_scope"] + }, + "testsmallscope":{ + "issuer":"http://localhost:4984/db/_oidc_testing", + "client_id":"sync_gateway", + "validation_key":"R75hfd9lasdwertwerutecw8", + "callback_url":"http://localhost:4984/db/_oidc_callback", + "register":true, + "scope":["openid"] + }, + "testlargescope":{ + "issuer":"http://localhost:4984/db/_oidc_testing", + "client_id":"sync_gateway", + "validation_key":"R75hfd9lasdwertwerutecw8", + "callback_url":"http://localhost:4984/db/_oidc_callback", + "register":true, + "scope":["openid", "email", "profile"] + } + } + }, + "unsupported":{ + "oidc_test_provider":{ + "enabled":true + } + }, + "server":"http://{{ couchbase_server_primary_node }}:8091", + "bucket":"data-bucket", + "users":{ + "GUEST":{ + "disabled":true, + "admin_channels":[ + "*" + ] + } + } + } + } +} + diff --git a/testsuites/syncgateway/functional/1sg_1cbs/1sg_1cbs-openid-connect.robot b/testsuites/syncgateway/functional/1sg_1cbs/1sg_1cbs-openid-connect.robot new file mode 100644 index 0000000000..c88753bf8f --- /dev/null +++ b/testsuites/syncgateway/functional/1sg_1cbs/1sg_1cbs-openid-connect.robot @@ -0,0 +1,122 @@ +*** Settings *** +Resource resources/common.robot + +Library Process +Library OperatingSystem +Library ../test_openid_connect.py +Library DebugLibrary +Library ${Libraries}/NetworkUtils.py +Library ${KEYWORDS}/Logging.py + +Test Timeout 10 minutes + +Test Setup Setup Test +Test Teardown Teardown Test + +*** Variables *** + + +*** Test Cases *** +# Cluster has been setup + +Test OpenIdConnect Basic Test User Port + [Documentation] + ... Tests the basic OpenIDConnect login flow against the non-admin port + [Tags] sanity + Test OpenIdConnect Basic Test sg_url=${sg_url} sg_db=${sg_db} is_admin_port=${False} + +Test OpenIdConnect Basic Test Admin Port + [Documentation] + ... Tests the basic OpenIDConnect login flow against the admin port + [Tags] sanity + Test OpenIdConnect Basic Test sg_url=${sg_url_admin} sg_db=${sg_db} is_admin_port=${True} + +Test OpenIDConnect Notauthenticated + [Documentation] + ... Simulate a failed authentication and make sure no session is created + [Tags] sanity + Test OpenIDConnect Notauthenticated sg_url=${sg_url} sg_db=${sg_db} + +Test OpenIDConnect No Session + [Documentation] + ... Authenticate with a test openid provider that is configured to NOT add a Set-Cookie header + [Tags] sanity + Test OpenIDConnect No Session sg_url=${sg_url} sg_db=${sg_db} + +Test OpenIDConnect Oidc Challenge Invalid Provider Name + [Documentation] + ... Authenticate with a non-default provider using an invalid provider name and expect an error + [Tags] sanity + Test OpenIDConnect Oidc Challenge Invalid Provider Name sg_url=${sg_url} sg_db=${sg_db} + +Test OpenIDConnect Expired Token + [Documentation] + ... Authenticate and create an ID token that only lasts for 5 seconds, wait 10 seconds + ... and make sure the token is rejected + [Tags] sanity + Test OpenIDConnect Expired Token sg_url=${sg_url} sg_db=${sg_db} + + +Test OpenIDConnect Negative Token Expiry + [Documentation] + ... Create a token with a negative expiry time and expect that authentication + ... is not possible + [Tags] sanity + Test OpenIDConnect Negative Token Expiry sg_url=${sg_url} sg_db=${sg_db} + + +Test OpenIDConnect Garbage Token + [Documentation] + ... Send a garbage/invalid token and make sure it cannot be used + [Tags] sanity + Test OpenIDConnect Garbage Token sg_url=${sg_url} sg_db=${sg_db} + +Test OpenIDConnect Invalid Scope + [Documentation] + ... Try to discover the authenticate endpoint URL with a test provider that has an + ... invalid scope, and expect an error + [Tags] sanity + Test OpenIDConnect Invalid Scope sg_url=${sg_url} sg_db=${sg_db} + +Test OpenIDConnect Small Scope + [Documentation] + ... Use the smallest OpenIDConnect scope possible, and make sure + ... certain claims like "email" are not present in the JWT returned + [Tags] sanity + Test OpenIDConnect Small Scope sg_url=${sg_url} sg_db=${sg_db} + +Test OpenIDConnect Large Scope + [Documentation] + ... Authenticate against a test provider config that only has a larger scope than the default, + ... and make sure things like the nickname are returned in the jwt token returned back + [Tags] sanity + Test OpenIDConnect Large Scope sg_url=${sg_url} sg_db=${sg_db} + +Test OpenIDConnect Public Session Endpoint + [Documentation] + ... Create a new session from the OpenID Connect token returned by hitting + ... the public _session endpoint and make sure the response contains the Set-Cookie header. + [Tags] sanity + Test OpenIDConnect Public Session Endpoint sg_url=${sg_url} sg_db=${sg_db} + +*** Keywords *** +Setup Test + + # Provisioning happens in the __init__.robot file + + Log Using cluster %{CLUSTER_CONFIG} console=True + Set Environment Variable CLUSTER_CONFIG ${CLUSTER_CONFIGS}/1sg_1cbs + + Reset Cluster ${SYNC_GATEWAY_CONFIGS}/sync_gateway_openid_connect_cc.json + + ${cluster_hosts} = Get Cluster Topology %{CLUSTER_CONFIG} + + Set Test Variable ${sg_url} ${cluster_hosts["sync_gateways"][0]["public"]} + Set Test Variable ${sg_url_admin} ${cluster_hosts["sync_gateways"][0]["admin"]} + + Set Test Variable ${sg_db} db + +Teardown Test + Log Tearing down test ... console=True + List Connections + Run Keyword If Test Failed Fetch And Analyze Logs ${TEST_NAME} diff --git a/testsuites/syncgateway/functional/1sg_1cbs/__init__.robot b/testsuites/syncgateway/functional/1sg_1cbs/__init__.robot index 2bdef7c77f..2c589df4d7 100644 --- a/testsuites/syncgateway/functional/1sg_1cbs/__init__.robot +++ b/testsuites/syncgateway/functional/1sg_1cbs/__init__.robot @@ -1,3 +1,7 @@ + +# This will not get run if the test is run with a direct reference to the test robot +# file, unless you define a keyword to explicitly run the Suite Setup in your robot test. + *** Settings *** Resource resources/common.robot @@ -22,6 +26,5 @@ Suite Setup ... sync_gateway_version=${SYNC_GATEWAY_VERSION} ... sync_gateway_config=${SYNC_GATEWAY_CONFIG} - Suite Teardown Log Tearing down suite ... console=True diff --git a/testsuites/syncgateway/functional/test_openid_connect.py b/testsuites/syncgateway/functional/test_openid_connect.py new file mode 100644 index 0000000000..f2755e83b7 --- /dev/null +++ b/testsuites/syncgateway/functional/test_openid_connect.py @@ -0,0 +1,495 @@ +from testkit.cluster import Cluster +import requests +import logging +import time +from urlparse import urlparse +from keywords.utils import log_r +from keywords.utils import log_info +from HTMLParser import HTMLParser +from requests import HTTPError +import jwt +import json + + +DEFAULT_PROVIDER = "test" + + +class FormActionHTMLParser(HTMLParser): + """ + Given some HTML, looks for a