Commit 5ca1b0fc authored by Tanner Prestegard's avatar Tanner Prestegard

Add ability to specify basic auth creds via environment variable

Useful for testing and running against a local GraceDB instance.
parent 17912053
Pipeline #88916 passed with stages
in 8 minutes and 37 seconds
......@@ -2,7 +2,7 @@ Changelog
=========
2.4.0 (August 21, 2019)
---------------------
-----------------------
- Change minimum required version of ``six`` to 1.9.0
- Add client method for updating GRB event parameters
......
......@@ -210,14 +210,19 @@ class GsiRest(object):
# If the user didn't provide credentials in the constructor,
# we try to look up the credentials
if not force_noauth and not credentials_provided:
# Look for X509 certificate and key
cred = self._find_x509_credentials()
if cred:
self.credentials['cert_file'], self.credentials['key_file'] = \
cred
self.auth_type = 'x509'
else:
# Look for basic auth credentials in .netrc file
# Look for credentials in environment variables
creds, auth_type = self._get_creds_from_envvars()
# Look for X509 certificate and key in default location
if creds is None:
creds = self._find_x509_credentials()
if creds is not None:
auth_type = 'x509'
# If no X509 credentials found, look for basic auth
# credentials in .netrc file
if creds is None:
try:
basic_auth_tuple = safe_netrc().authenticators(host)
except IOError:
......@@ -226,9 +231,17 @@ class GsiRest(object):
else:
# If credentials were found for host, set them up!
if basic_auth_tuple is not None:
self.credentials['username'] = basic_auth_tuple[0]
self.credentials['password'] = basic_auth_tuple[2]
self.auth_type = 'basic'
creds = (basic_auth_tuple[0], basic_auth_tuple[2])
auth_type = 'basic'
# Process final credentials
if creds is not None:
if auth_type == 'x509':
creds_dict = {'cert_file': creds[0], 'key_file': creds[1]}
elif auth_type == 'basic':
creds_dict = {'username': creds[0], 'password': creds[1]}
self.auth_type = auth_type
self.credentials.update(creds_dict)
if (fail_if_noauth and not self.credentials):
raise RuntimeError('No authentication credentials found.')
......@@ -386,21 +399,47 @@ class GsiRest(object):
return credentials_provided
def _find_x509_credentials(self):
"""
Tries to find a user's X509 certificate and key. Checks environment
variables first, then expected location for default proxy.
def _get_creds_from_envvars(self):
"""
proxyFile = os.environ.get('X509_USER_PROXY')
certFile = os.environ.get('X509_USER_CERT')
keyFile = os.environ.get('X509_USER_KEY')
Tries to look up user credentials from environment variables in the
following order:
if certFile and keyFile:
return certFile, keyFile
if proxyFile:
return proxyFile, proxyFile
#. X.509 certificate and key (`X509_USER_CERT`, `X509_USER_KEY`)
#. X.509 proxy (`X509_USER_PROXY`)
#. Basic auth user and password (`GRACEDB_USER`, `GRACEDB_PASSWORD`)
"""
creds = None
auth_type = None
# Check for X509 certificate and key first
cert_file = os.environ.get('X509_USER_CERT', None)
key_file = os.environ.get('X509_USER_KEY', None)
if cert_file and key_file:
creds = (cert_file, key_file)
auth_type = 'x509'
# Next, check for X509 proxy
if creds is None:
proxy_file = os.environ.get('X509_USER_PROXY', None)
if proxy_file:
creds = (proxy_file, proxy_file)
auth_type = 'x509'
# Finally, look for basic auth credentials
if creds is None:
username = os.environ.get('GRACEDB_USER', None)
password = os.environ.get('GRACEDB_PASSWORD', None)
if username and password:
creds = (username, password)
auth_type = 'basic'
return creds, auth_type
def _find_x509_credentials(self):
"""
Tries to find a user's X509 certificate and key in some
default locations.
"""
# Try default proxy
proxyFile = os.path.join('/tmp', "x509up_u%d" % os.getuid())
if os.path.exists(proxyFile):
......@@ -415,6 +454,8 @@ class GsiRest(object):
if os.path.exists(certFile) and os.path.exists(keyFile):
return certFile, keyFile
return None
def show_credentials(self, print_output=True):
"""Prints authentication type and credentials information."""
output = {'auth_type': self.auth_type}
......@@ -670,6 +711,8 @@ class GraceDb(GsiRest):
are set, load the corresponding certificate and key.
#. If the ``X509_USER_PROXY`` environment variable is set, load the \
corresponding proxy file.
#. If the ``GRACEDB_USER`` and ``GRACEDB_PASSWORD`` environment variables \
are set, use those credentials for basic authentication.
#. Look for a X.509 proxy from ligo-proxy-init in the default location \
(``/tmp/x509up_u${UID}``).
#. Look for a certificate and key file in ``$HOME/.globus/usercert.pem`` \
......
......@@ -104,6 +104,7 @@ def test_x509_credentials_lookup():
load_cert_func = 'ligo.gracedb.rest.GraceDb._load_certificate'
find_x509_func = 'ligo.gracedb.rest.GraceDb._find_x509_credentials'
with mock.patch('ligo.gracedb.rest.GraceDb.set_up_connector'), \
mock.patch.dict('ligo.gracedb.rest.os.environ', clear=True), \
mock.patch(load_cert_func), \
mock.patch(find_x509_func) as mock_find_x509: # noqa: E127
mock_find_x509.return_value = (cert_file, key_file)
......@@ -116,53 +117,102 @@ def test_x509_credentials_lookup():
assert g.credentials.get('key_file') == key_file
def test_x509_lookup_cert_key_from_envvars():
"""Test lookup of X509 cert and key from environment variables"""
# Setup
cert_file = '/tmp/cert_file'
key_file = '/tmp/key_file'
# Initialize client
fake_creds_dict = {
@pytest.mark.parametrize(
"cert_file,key_file,proxy_file,username,password",
[
('/tmp/cert_file', '/tmp/key_file', None, None, None),
(None, None, '/tmp/proxy_file', None, None),
(None, None, None, 'user', 'pass'),
('/tmp/cert_file', '/tmp/key_file', '/tmp/proxy_file', None, None),
('/tmp/cert_file', '/tmp/key_file', None, 'username', 'password'),
(None, None, '/tmp/proxy_file', 'username', 'password'),
('/tmp/cert_file', '/tmp/key_file', '/tmp/proxy_file', 'username',
'password'),
]
)
def test_lookup_creds_from_envvars(cert_file, key_file, proxy_file, username,
password):
# Set up environment
creds = {
'X509_USER_CERT': cert_file,
'X509_USER_KEY': key_file,
'X509_USER_PROXY': proxy_file,
'GRACEDB_USER': username,
'GRACEDB_PASSWORD': password,
}
# mock can't handle Nones in os.environ, so we remove them
creds = {k: v for k, v in creds.items() if v is not None}
load_cert_func = 'ligo.gracedb.rest.GraceDb._load_certificate'
os_environ_func = 'ligo.gracedb.rest.os.environ'
with mock.patch('ligo.gracedb.rest.GraceDb.set_up_connector'), \
mock.patch(load_cert_func), \
mock.patch.dict(os_environ_func, fake_creds_dict): # noqa: E127
mock.patch.dict(os_environ_func, creds, clear=True): # noqa: E127
g = GraceDb()
# Check credentials - should prioritze x509 credentials
# Check credentials - should prioritize x509 credentials
assert len(g.credentials) == 2
assert g.auth_type == 'x509'
assert g.credentials.get('cert_file') == cert_file
assert g.credentials.get('key_file') == key_file
if (cert_file and key_file) or proxy_file:
assert g.auth_type == 'x509'
if cert_file and key_file:
assert g.credentials.get('cert_file') == cert_file
assert g.credentials.get('key_file') == key_file
else:
assert g.credentials.get('cert_file') == proxy_file
assert g.credentials.get('key_file') == proxy_file
else:
assert g.auth_type == 'basic'
assert g.credentials.get('username') == username
assert g.credentials.get('password') == password
def test_x509_lookup_proxy_from_envvars():
"""Test lookup of X509 combined provxy file from environment variables"""
# Setup
proxy_file = '/tmp/proxy_file'
@pytest.mark.parametrize(
"creds_dict",
[
{'cred': ('c1', 'k1')},
{'username': 'u1', 'password': 'p1'},
{'cred': ('c1', 'k1'), 'username': 'u1', 'password': 'p1'},
{},
]
)
def test_creds_and_envvars(creds_dict):
"""
Test different combinations of directly-provided credentials and
credentials provided in envvars
"""
# Set up environment - only need to test with cert and key since
# we have another test which checks how the different environment
# variables interact
env_creds = {
'X509_USER_CERT': 'c2',
'X509_USER_KEY': 'k2',
}
# Initialize client
os_environ_func = 'ligo.gracedb.rest.os.environ'
load_cert_func = 'ligo.gracedb.rest.GraceDb._load_certificate'
mock_environ_dict = {'X509_USER_PROXY': proxy_file}
os_environ_func = 'ligo.gracedb.rest.os.environ'
with mock.patch('ligo.gracedb.rest.GraceDb.set_up_connector'), \
mock.patch(load_cert_func), \
mock.patch.dict(os_environ_func, mock_environ_dict): # noqa: E127
g = GraceDb()
mock.patch.dict(os_environ_func, env_creds, clear=True): # noqa: E127
g = GraceDb(**creds_dict)
# Check credentials - should prioritze x509 credentials
# Check credentials - should prioritize directly-provided credentials
assert len(g.credentials) == 2
assert g.auth_type == 'x509'
assert g.credentials.get('cert_file') == proxy_file
assert g.credentials.get('key_file') == proxy_file
if creds_dict:
if 'cred' in creds_dict:
assert g.auth_type == 'x509'
assert g.credentials.get('cert_file') == creds_dict['cred'][0]
assert g.credentials.get('key_file') == creds_dict['cred'][1]
else:
assert g.auth_type == 'basic'
assert g.credentials.get('username') == creds_dict['username']
assert g.credentials.get('password') == creds_dict['password']
else:
assert g.auth_type == 'x509'
assert g.credentials.get('cert_file') == env_creds['X509_USER_CERT']
assert g.credentials.get('key_file') == env_creds['X509_USER_KEY']
def test_basic_credentials_lookup():
def test_basic_credentials_lookup_from_netrc():
"""Test client instantiation - look up basic auth creds from .netrc file"""
# Set up credentials and mock_netrc return
fake_creds = {
......@@ -175,6 +225,7 @@ def test_basic_credentials_lookup():
safe_netrc = 'ligo.gracedb.rest.safe_netrc'
with mock.patch(conn_func), \
mock.patch(find_x509_func) as mock_find_x509, \
mock.patch.dict('ligo.gracedb.rest.os.environ', clear=True), \
mock.patch(safe_netrc) as mock_netrc: # noqa: E127
# Force lookup to not find any X509 credentials
......@@ -201,6 +252,7 @@ def test_no_credentials(fail_if_noauth):
safe_netrc = 'ligo.gracedb.rest.safe_netrc'
with mock.patch(conn_func), \
mock.patch(find_x509_func) as mock_find_x509, \
mock.patch.dict('ligo.gracedb.rest.os.environ', clear=True), \
mock.patch(safe_netrc) as mock_netrc: # noqa: E127
# Force lookup to not find any X509 credentials
......@@ -259,6 +311,7 @@ def test_fail_if_noauth(creds_found):
with mock.patch(conn_func), \
mock.patch(load_cert_func), \
mock.patch(find_x509_func) as mock_find_x509, \
mock.patch.dict('ligo.gracedb.rest.os.environ', clear=True), \
mock.patch(safe_netrc) as mock_netrc: # noqa: E127
if not creds_found:
......
......@@ -4,7 +4,13 @@ skip_missing_interpreters = True
[testenv]
passenv =
integration_test: TEST_SERVICE
integration_test:
TEST_SERVICE
X509_USER_CERT
X509_USER_KEY
X509_USER_PROXY
GRACEDB_USER
GRACEDB_PASSWORD
commands =
unit_test: python setup.py test
integration_test: python setup.py test --addopts "-m integration"
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment