Skip to content

Move sensitive configuration options into separate files #93

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/news.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
Release Notes
=============

**1.8.0 (unreleased)**

* **BREAKING CHANGE:** Moved cloud testing provider credentials into separate
files for improved security.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice to mention the changes in a little more detail, perhaps mentioning #60, and a little example of what users should do to migrate their current configuration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I've made some updates and rebased/squashed in preparation for a merge and release later today. I really appreciate your time @nicoddemus!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely, my pleasure! 😁

Thanks for all the time and effort you put in your excellent plugins as well! 👍


* If you are using the environment variables for specifying cloud testing
provider credentials, then you will not be affected.
* If you are storing credentials from any of the cloud testing providers in
one of the default configuration files then they will no longer be used.
These files are often checked into source code repositories, so it was
previously very easy to accidentally expose your credentials.
* Each cloud provider now has their own configuration file, such as
``.browserstack``, ``.crossbrowsertesting``, ``.saucelabs``,
``.testingbot`` and these can be located in the working directory or in the
user's home directory. This provides a convenient way to set up these files
globally, and override them for individual projects.
* To migrate, check ``pytest.ini``, ``tox.ini``, and ``setup.cfg`` for any
keys starting with ``browserstack_``, ``crossbrowsertesting_``,
``saucelabs_``, or ``testingbot_``. If you find any, create a new
configuration file for the appropriate cloud testing provider with your
credentials, and remove the entries from the original file.
* The configuration keys can differ between cloud testing providers, so
please check the :doc:`user_guide` for details.
* See `#60 <https://github.com/pytest-dev/pytest-selenium/issues/60>`_ for
for original issue and related patch.

**1.7.0 (2016-11-29)**

* Introduced a ``firefox_options`` fixture.
Expand Down
57 changes: 30 additions & 27 deletions docs/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -218,19 +218,20 @@ Sauce Labs

To run your automated tests using `Sauce Labs <https://saucelabs.com/>`_, you
must provide a valid username and API key. This can be done either by using
a :ref:`configuration file <configuration-files>`, or by setting the
``SAUCELABS_USERNAME`` and ``SAUCELABS_API_KEY`` environment variables.
a ``.saucelabs`` configuration file in the working directory or your home
directory, or by setting the ``SAUCELABS_USERNAME`` and ``SAUCELABS_API_KEY``
environment variables.

Configuration
~~~~~~~~~~~~~

Below is an example :ref:`configuration file <configuration-files>`:
Below is an example ``.saucelabs`` configuration file:

.. code-block:: ini

[pytest]
sauce_labs_username = username
sauce_labs_api_key = secret
[credentials]
username = username
key = secret

Running tests
~~~~~~~~~~~~~
Expand All @@ -252,20 +253,21 @@ BrowserStack

To run your automated tests using
`BrowserStack <https://www.browserstack.com/>`_, you must provide a valid
username and access key. This can be done either by using a
:ref:`configuration file <configuration-files>`, or by setting the
``BROWSERSTACK_USERNAME`` and ``BROWSERSTACK_ACCESS_KEY`` environment variables.
username and access key. This can be done either by using
a ``.browserstack`` configuration file in the working directory or your home
directory, or by setting the ``BROWSERSTACK_USERNAME`` and
``BROWSERSTACK_ACCESS_KEY`` environment variables.

Configuration
~~~~~~~~~~~~~

Below is an example :ref:`configuration file <configuration-files>`:
Below is an example ``.browserstack`` configuration file:

.. code-block:: ini

[pytest]
browserstack_username = username
browserstack_access_key = secret
[credentials]
username = username
key = secret

Running tests
~~~~~~~~~~~~~
Expand All @@ -285,20 +287,21 @@ TestingBot
----------

To run your automated tests using `TestingBot <http://testingbot.com/>`_, you
must provide a valid key and secret. This can be done either by using a
:ref:`configuration file <configuration-files>`, or by setting the
``TESTINGBOT_KEY`` and ``TESTINGBOT_SECRET`` environment variables.
must provide a valid key and secret. This can be done either by using
a ``.testingbot`` configuration file in the working directory or your home
directory, or by setting the ``TESTINGBOT_KEY`` and ``TESTINGBOT_SECRET``
environment variables.

Configuration
~~~~~~~~~~~~~

Below is an example :ref:`configuration file <configuration-files>`:
Below is an example ``.testingbot`` configuration file:

.. code-block:: ini

[pytest]
testingbot_key = key
testingbot_secret = secret
[credentials]
key = key
secret = secret

Running tests
~~~~~~~~~~~~~
Expand All @@ -321,20 +324,20 @@ CrossBrowserTesting
To run your automated tests using
`CrossBrowserTesting <https://crossbrowsertesting.com/>`_, you must provide a
valid username and auth key. This can be done either by using
a :ref:`configuration file <configuration-files>`, or by setting the
``CROSSBROWSERTESTING_USERNAME`` and ``CROSSBROWSERTESTING_AUTH_KEY``
environment variables.
a ``.crossbrowsertesting`` configuration file in the working directory or your
home directory, or by setting the ``CROSSBROWSERTESTING_USERNAME`` and
``CROSSBROWSERTESTING_AUTH_KEY`` environment variables.

Configuration
~~~~~~~~~~~~~

Below is an example :ref:`configuration file <configuration-files>`:
Below is an example ``.crossbrowsertesting`` configuration file:

.. code-block:: ini

[pytest]
crossbrowsertesting_username = username
crossbrowsertesting_auth_key = secret
[credentials]
username = username
key = secret

Running tests
~~~~~~~~~~~~~
Expand Down
70 changes: 32 additions & 38 deletions pytest_selenium/drivers/browserstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,56 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import os

import pytest
import requests

DRIVER = 'BrowserStack'
API_JOB_URL = 'https://www.browserstack.com/automate/sessions/{session}.json'
EXECUTOR_URL = 'http://{username}:{key}@hub.browserstack.com:80/wd/hub'
from pytest_selenium.drivers.cloud import Provider


class BrowserStack(Provider):

API = 'https://www.browserstack.com/automate/sessions/{session}.json'

@property
def auth(self):
return (self.username, self.key)

@property
def executor(self):
return 'http://{0}:{1}@hub.browserstack.com:80/wd/hub'.format(
self.username, self.key)

def pytest_addoption(parser):
parser.addini('browserstack_username',
help='browserstack username',
default=os.getenv('BROWSERSTACK_USERNAME'))
parser.addini('browserstack_access_key',
help='browserstack access key',
default=os.getenv('BROWSERSTACK_ACCESS_KEY'))
@property
def username(self):
return self.get_credential('username', 'BROWSERSTACK_USERNAME')

@property
def key(self):
return self.get_credential('key', 'BROWSERSTACK_ACCESS_KEY')


@pytest.mark.optionalhook
def pytest_selenium_runtest_makereport(item, report, summary, extra):
if item.config.getoption('driver') != DRIVER:
provider = BrowserStack()
if item.config.getoption('driver') != provider.driver:
return

passed = report.passed or (report.failed and hasattr(report, 'wasxfail'))
session_id = item._driver.session_id
auth = (_username(item.config), _access_key(item.config))
api_url = API_JOB_URL.format(session=session_id)
api_url = provider.API.format(session=session_id)

try:
job_info = requests.get(api_url, auth=auth, timeout=10).json()
job_info = requests.get(api_url, auth=provider.auth, timeout=10).json()
job_url = job_info['automation_session']['browser_url']
# Add the job URL to the summary
summary.append('{0} Job: {1}'.format(DRIVER, job_url))
summary.append('{0} Job: {1}'.format(provider.name, job_url))
pytest_html = item.config.pluginmanager.getplugin('html')
# Add the job URL to the HTML report
extra.append(pytest_html.extras.url(job_url, '{0} Job'.format(DRIVER)))
extra.append(pytest_html.extras.url(job_url, '{0} Job'.format(
provider.name)))
except Exception as e:
summary.append('WARNING: Failed to determine {0} job URL: {1}'.format(
DRIVER, e))
provider.name, e))

try:
# Update the job result
Expand All @@ -55,32 +65,16 @@ def pytest_selenium_runtest_makereport(item, report, summary, extra):
api_url,
headers={'Content-Type': 'application/json'},
params={'status': status},
auth=auth,
auth=provider.auth,
timeout=10)
except Exception as e:
summary.append('WARNING: Failed to update job status: {0}'.format(e))


def driver_kwargs(request, test, capabilities, **kwargs):
provider = BrowserStack()
capabilities.setdefault('name', test)
executor = EXECUTOR_URL.format(
username=_username(request.config),
key=_access_key(request.config))
kwargs = {
'command_executor': executor,
'command_executor': provider.executor,
'desired_capabilities': capabilities}
return kwargs


def _access_key(config):
access_key = config.getini('browserstack_access_key')
if not access_key:
raise pytest.UsageError('BrowserStack access key must be set')
return access_key


def _username(config):
username = config.getini('browserstack_username')
if not username:
raise pytest.UsageError('BrowserStack username must be set')
return username
42 changes: 42 additions & 0 deletions pytest_selenium/drivers/cloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import os
import sys

from pytest_selenium.exceptions import MissingCloudCredentialError

if sys.version_info[0] == 2:
import ConfigParser as configparser
else:
import configparser


class Provider(object):

@property
def driver(self):
return type(self).__name__

@property
def name(self):
return self.driver

@property
def config(self):
name = '.{0}'.format(self.driver.lower())
config = configparser.ConfigParser()
config.read([name, os.path.join(os.path.expanduser('~'), name)])
return config

def get_credential(self, key, env):
try:
value = self.config.get('credentials', key)
except (configparser.NoSectionError,
configparser.NoOptionError,
KeyError):
value = os.getenv(env)
if not value:
raise MissingCloudCredentialError(self.name, key, env)
return value
Loading