summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--keystoneclient/auth/identity/v2.py6
-rw-r--r--keystoneclient/auth/identity/v3.py3
-rw-r--r--keystoneclient/base.py5
-rw-r--r--keystoneclient/contrib/auth/v3/saml2.py2
-rw-r--r--keystoneclient/session.py101
-rw-r--r--keystoneclient/tests/auth/test_identity_v2.py12
-rw-r--r--keystoneclient/tests/auth/test_identity_v3.py13
-rw-r--r--keystoneclient/tests/v2_0/test_users.py21
-rw-r--r--keystoneclient/tests/v3/test_users.py41
-rw-r--r--keystoneclient/v2_0/tokens.py2
-rw-r--r--keystoneclient/v2_0/users.py7
-rw-r--r--keystoneclient/v3/users.py47
12 files changed, 198 insertions, 62 deletions
diff --git a/keystoneclient/auth/identity/v2.py b/keystoneclient/auth/identity/v2.py
index 4342251..ad93dba 100644
--- a/keystoneclient/auth/identity/v2.py
+++ b/keystoneclient/auth/identity/v2.py
@@ -11,6 +11,7 @@
# under the License.
import abc
+import logging
from oslo.config import cfg
import six
@@ -20,6 +21,8 @@ from keystoneclient.auth.identity import base
from keystoneclient import exceptions
from keystoneclient import utils
+_logger = logging.getLogger(__name__)
+
@six.add_metaclass(abc.ABCMeta)
class Auth(base.BaseIdentityPlugin):
@@ -66,8 +69,9 @@ class Auth(base.BaseIdentityPlugin):
if self.trust_id:
params['auth']['trust_id'] = self.trust_id
+ _logger.debug('Making authentication request to %s', url)
resp = session.post(url, json=params, headers=headers,
- authenticated=False)
+ authenticated=False, log=False)
try:
resp_data = resp.json()['access']
diff --git a/keystoneclient/auth/identity/v3.py b/keystoneclient/auth/identity/v3.py
index 63a0230..010c80d 100644
--- a/keystoneclient/auth/identity/v3.py
+++ b/keystoneclient/auth/identity/v3.py
@@ -105,8 +105,9 @@ class Auth(base.BaseIdentityPlugin):
elif self.trust_id:
body['auth']['scope'] = {'OS-TRUST:trust': {'id': self.trust_id}}
+ _logger.debug('Making authentication request to %s', self.token_url)
resp = session.post(self.token_url, json=body, headers=headers,
- authenticated=False)
+ authenticated=False, log=False)
try:
resp_data = resp.json()['token']
diff --git a/keystoneclient/base.py b/keystoneclient/base.py
index a8c6d7b..f94c16b 100644
--- a/keystoneclient/base.py
+++ b/keystoneclient/base.py
@@ -46,6 +46,11 @@ def getid(obj):
return obj
+def filter_none(**kwargs):
+ """Remove any entries from a dictionary where the value is None."""
+ return dict((k, v) for k, v in six.iteritems(kwargs) if v is not None)
+
+
def filter_kwargs(f):
@functools.wraps(f)
def func(*args, **kwargs):
diff --git a/keystoneclient/contrib/auth/v3/saml2.py b/keystoneclient/contrib/auth/v3/saml2.py
index 03323a5..f2b458e 100644
--- a/keystoneclient/contrib/auth/v3/saml2.py
+++ b/keystoneclient/contrib/auth/v3/saml2.py
@@ -246,7 +246,7 @@ class Saml2UnscopedToken(v3.AuthConstructor):
headers={'Content-type': 'text/xml'},
data=etree.tostring(idp_saml2_authn_request),
requests_auth=(self.username, self.password),
- authenticated=False)
+ authenticated=False, log=False)
try:
self.saml2_idp_authn_response = etree.XML(idp_response.content)
diff --git a/keystoneclient/session.py b/keystoneclient/session.py
index 5426d99..c74f752 100644
--- a/keystoneclient/session.py
+++ b/keystoneclient/session.py
@@ -130,11 +130,69 @@ class Session(object):
if user_agent is not None:
self.user_agent = user_agent
+ @utils.positional()
+ def _http_log_request(self, url, method=None, data=None,
+ json=None, headers=None):
+ if not _logger.isEnabledFor(logging.DEBUG):
+ # NOTE(morganfainberg): This whole debug section is expensive,
+ # there is no need to do the work if we're not going to emit a
+ # debug log.
+ return
+
+ string_parts = ['REQ: curl -i']
+
+ # NOTE(jamielennox): None means let requests do its default validation
+ # so we need to actually check that this is False.
+ if self.verify is False:
+ string_parts.append('--insecure')
+
+ if method:
+ string_parts.extend(['-X', method])
+
+ string_parts.append(url)
+
+ if headers:
+ for header in six.iteritems(headers):
+ string_parts.append('-H "%s: %s"' % header)
+ if json:
+ data = jsonutils.dumps(json)
+ if data:
+ string_parts.append("-d '%s'" % data)
+
+ _logger.debug(' '.join(string_parts))
+
+ @utils.positional()
+ def _http_log_response(self, response=None, json=None,
+ status_code=None, headers=None, text=None):
+ if not _logger.isEnabledFor(logging.DEBUG):
+ return
+
+ if response:
+ if not status_code:
+ status_code = response.status_code
+ if not headers:
+ headers = response.headers
+ if not text:
+ text = response.text
+ if json:
+ text = jsonutils.dumps(json)
+
+ string_parts = ['RESP:']
+
+ if status_code:
+ string_parts.append('[%s]' % status_code)
+ if headers:
+ string_parts.append('%s' % headers)
+ if text:
+ string_parts.append('\nRESP BODY: %s\n' % text)
+
+ _logger.debug(' '.join(string_parts))
+
@utils.positional(enforcement=utils.positional.WARN)
def request(self, url, method, json=None, original_ip=None,
user_agent=None, redirect=None, authenticated=None,
endpoint_filter=None, auth=None, requests_auth=None,
- raise_exc=True, allow_reauth=True, **kwargs):
+ raise_exc=True, allow_reauth=True, log=True, **kwargs):
"""Send an HTTP request with the specified characteristics.
Wrapper around `requests.Session.request` to handle tasks such as
@@ -183,6 +241,8 @@ class Session(object):
:param bool allow_reauth: Allow fetching a new token and retrying the
request on receiving a 401 Unauthorized
response. (optional, default True)
+ :param bool log: If True then log the request and response data to the
+ debug log. (optional, default True)
:param kwargs: any other parameter that can be passed to
requests.Session.request (such as `headers`). Except:
'data' will be overwritten by the data in 'json' param.
@@ -250,28 +310,10 @@ class Session(object):
if requests_auth:
kwargs['auth'] = requests_auth
- string_parts = ['curl -i']
-
- # NOTE(jamielennox): None means let requests do its default validation
- # so we need to actually check that this is False.
- if self.verify is False:
- string_parts.append('--insecure')
-
- if method:
- string_parts.extend(['-X', method])
-
- string_parts.append(url)
-
- if headers:
- for header in six.iteritems(headers):
- string_parts.append('-H "%s: %s"' % header)
-
- try:
- string_parts.append("-d '%s'" % kwargs['data'])
- except KeyError:
- pass
-
- _logger.debug('REQ: %s', ' '.join(string_parts))
+ if log:
+ self._http_log_request(url, method=method,
+ data=kwargs.get('data'),
+ headers=headers)
# Force disable requests redirect handling. We will manage this below.
kwargs['allow_redirects'] = False
@@ -279,7 +321,7 @@ class Session(object):
if redirect is None:
redirect = self.redirect
- resp = self._send_request(url, method, redirect, **kwargs)
+ resp = self._send_request(url, method, redirect, log, **kwargs)
# handle getting a 401 Unauthorized response by invalidating the plugin
# and then retrying the request. This is only tried once.
@@ -288,7 +330,8 @@ class Session(object):
token = self.get_token(auth)
if token:
headers['X-Auth-Token'] = token
- resp = self._send_request(url, method, redirect, **kwargs)
+ resp = self._send_request(url, method, redirect, log,
+ **kwargs)
if raise_exc and resp.status_code >= 400:
_logger.debug('Request returned failure status: %s',
@@ -297,7 +340,7 @@ class Session(object):
return resp
- def _send_request(self, url, method, redirect, **kwargs):
+ def _send_request(self, url, method, redirect, log, **kwargs):
# NOTE(jamielennox): We handle redirection manually because the
# requests lib follows some browser patterns where it will redirect
# POSTs as GETs for certain statuses which is not want we want for an
@@ -315,8 +358,8 @@ class Session(object):
msg = 'Unable to establish connection to %s' % url
raise exceptions.ConnectionRefused(msg)
- _logger.debug('RESP: [%s] %s\nRESP BODY: %s\n',
- resp.status_code, resp.headers, resp.text)
+ if log:
+ self._http_log_response(response=resp)
if resp.status_code in self.REDIRECT_STATUSES:
# be careful here in python True == 1 and False == 0
@@ -335,7 +378,7 @@ class Session(object):
_logger.warn("Failed to redirect request to %s as new "
"location was not provided.", resp.url)
else:
- new_resp = self._send_request(location, method, redirect,
+ new_resp = self._send_request(location, method, redirect, log,
**kwargs)
if not isinstance(new_resp.history, list):
diff --git a/keystoneclient/tests/auth/test_identity_v2.py b/keystoneclient/tests/auth/test_identity_v2.py
index a264edd..0beea83 100644
--- a/keystoneclient/tests/auth/test_identity_v2.py
+++ b/keystoneclient/tests/auth/test_identity_v2.py
@@ -11,6 +11,7 @@
# under the License.
import copy
+import uuid
import httpretty
from six.moves import urllib
@@ -255,3 +256,14 @@ class V2IdentityPlugin(utils.TestCase):
self.assertEqual('token1', s.get_token())
a.invalidate()
self.assertEqual('token2', s.get_token())
+
+ @httpretty.activate
+ def test_doesnt_log_password(self):
+ self.stub_auth(json=self.TEST_RESPONSE_DICT)
+ password = uuid.uuid4().hex
+
+ a = v2.Password(self.TEST_URL, username=self.TEST_USER,
+ password=password)
+ s = session.Session(auth=a)
+ self.assertEqual(self.TEST_TOKEN, s.get_token())
+ self.assertNotIn(password, self.logger.output)
diff --git a/keystoneclient/tests/auth/test_identity_v3.py b/keystoneclient/tests/auth/test_identity_v3.py
index d44c8e7..a147a31 100644
--- a/keystoneclient/tests/auth/test_identity_v3.py
+++ b/keystoneclient/tests/auth/test_identity_v3.py
@@ -11,6 +11,7 @@
# under the License.
import copy
+import uuid
import httpretty
from six.moves import urllib
@@ -408,3 +409,15 @@ class V3IdentityPlugin(utils.TestCase):
self.assertEqual('token1', s.get_token())
a.invalidate()
self.assertEqual('token2', s.get_token())
+
+ @httpretty.activate
+ def test_doesnt_log_password(self):
+ self.stub_auth(json=self.TEST_RESPONSE_DICT)
+
+ password = uuid.uuid4().hex
+ a = v3.Password(self.TEST_URL, username=self.TEST_USER,
+ password=password)
+ s = session.Session(a)
+ self.assertEqual(self.TEST_TOKEN, s.get_token())
+
+ self.assertNotIn(password, self.logger.output)
diff --git a/keystoneclient/tests/v2_0/test_users.py b/keystoneclient/tests/v2_0/test_users.py
index 4246a51..89901e4 100644
--- a/keystoneclient/tests/v2_0/test_users.py
+++ b/keystoneclient/tests/v2_0/test_users.py
@@ -47,10 +47,11 @@ class UserTests(utils.TestCase):
def test_create(self):
tenant_id = uuid.uuid4().hex
user_id = uuid.uuid4().hex
+ password = uuid.uuid4().hex
req_body = {
"user": {
"name": "gabriel",
- "password": "test",
+ "password": password,
"tenantId": tenant_id,
"email": "test@example.com",
"enabled": True,
@@ -63,7 +64,7 @@ class UserTests(utils.TestCase):
"enabled": True,
"tenantId": tenant_id,
"id": user_id,
- "password": "test",
+ "password": password,
"email": "test@example.com",
}
}
@@ -80,6 +81,7 @@ class UserTests(utils.TestCase):
self.assertEqual(user.name, "gabriel")
self.assertEqual(user.email, "test@example.com")
self.assertRequestBodyIs(json=req_body)
+ self.assertNotIn(password, self.logger.output)
@httpretty.activate
def test_create_user_without_email(self):
@@ -210,10 +212,11 @@ class UserTests(utils.TestCase):
"name": "gabriel",
}
}
+ password = uuid.uuid4().hex
req_2 = {
"user": {
"id": self.DEMO_USER_ID,
- "password": "swordfish",
+ "password": password,
}
}
tenant_id = uuid.uuid4().hex
@@ -245,18 +248,22 @@ class UserTests(utils.TestCase):
name='gabriel',
email='gabriel@example.com')
self.assertRequestBodyIs(json=req_1)
- self.client.users.update_password(self.DEMO_USER_ID, 'swordfish')
+ self.client.users.update_password(self.DEMO_USER_ID, password)
self.assertRequestBodyIs(json=req_2)
self.client.users.update_tenant(self.DEMO_USER_ID, tenant_id)
self.assertRequestBodyIs(json=req_3)
self.client.users.update_enabled(self.DEMO_USER_ID, False)
self.assertRequestBodyIs(json=req_4)
+ self.assertNotIn(password, self.logger.output)
@httpretty.activate
def test_update_own_password(self):
+ old_password = uuid.uuid4().hex
+ new_password = uuid.uuid4().hex
req_body = {
'user': {
- 'password': 'ABCD', 'original_password': 'DCBA'
+ 'password': new_password,
+ 'original_password': old_password
}
}
resp_body = {
@@ -267,8 +274,10 @@ class UserTests(utils.TestCase):
json=resp_body)
self.client.user_id = user_id
- self.client.users.update_own_password('DCBA', 'ABCD')
+ self.client.users.update_own_password(old_password, new_password)
self.assertRequestBodyIs(json=req_body)
+ self.assertNotIn(old_password, self.logger.output)
+ self.assertNotIn(new_password, self.logger.output)
@httpretty.activate
def test_user_role_listing(self):
diff --git a/keystoneclient/tests/v3/test_users.py b/keystoneclient/tests/v3/test_users.py
index 153e27a..0841e9d 100644
--- a/keystoneclient/tests/v3/test_users.py
+++ b/keystoneclient/tests/v3/test_users.py
@@ -97,6 +97,25 @@ class UserTests(utils.TestCase, utils.CrudTests):
group=None)
@httpretty.activate
+ def test_create_doesnt_log_password(self):
+ password = uuid.uuid4().hex
+ ref = self.new_ref()
+
+ self.stub_entity(httpretty.POST, [self.collection_key],
+ status=201, entity=ref)
+
+ req_ref = ref.copy()
+ req_ref.pop('id')
+ param_ref = req_ref.copy()
+
+ param_ref['password'] = password
+ params = utils.parameterize(param_ref)
+
+ self.manager.create(**params)
+
+ self.assertNotIn(password, self.logger.output)
+
+ @httpretty.activate
def test_create_with_project(self):
# Can create a user with the deprecated project option rather than
# default_project_id.
@@ -149,6 +168,26 @@ class UserTests(utils.TestCase, utils.CrudTests):
self.assertEntityRequestBodyIs(req_ref)
@httpretty.activate
+ def test_update_doesnt_log_password(self):
+ password = uuid.uuid4().hex
+ ref = self.new_ref()
+
+ req_ref = ref.copy()
+ req_ref.pop('id')
+ param_ref = req_ref.copy()
+
+ self.stub_entity(httpretty.PATCH,
+ [self.collection_key, ref['id']],
+ status=200, entity=ref)
+
+ param_ref['password'] = password
+ params = utils.parameterize(param_ref)
+
+ self.manager.update(ref['id'], **params)
+
+ self.assertNotIn(password, self.logger.output)
+
+ @httpretty.activate
def test_update_with_project(self):
# Can update a user with the deprecated project option rather than
# default_project_id.
@@ -217,6 +256,8 @@ class UserTests(utils.TestCase, utils.CrudTests):
self.assertEqual('/v3/users/test/password',
httpretty.last_request().path)
self.assertRequestBodyIs(json=exp_req_body)
+ self.assertNotIn(old_password, self.logger.output)
+ self.assertNotIn(new_password, self.logger.output)
def test_update_password_with_bad_inputs(self):
old_password = uuid.uuid4().hex
diff --git a/keystoneclient/v2_0/tokens.py b/keystoneclient/v2_0/tokens.py
index 53ea1da..dc1b0a2 100644
--- a/keystoneclient/v2_0/tokens.py
+++ b/keystoneclient/v2_0/tokens.py
@@ -53,7 +53,7 @@ class TokenManager(base.Manager):
reset = 1
self.api.management_url = self.api.auth_url
token_ref = self._create('/tokens', params, "access",
- return_raw=return_raw)
+ return_raw=return_raw, log=False)
if reset:
self.api.management_url = None
return token_ref
diff --git a/keystoneclient/v2_0/users.py b/keystoneclient/v2_0/users.py
index 75a1f9b..df488f5 100644
--- a/keystoneclient/v2_0/users.py
+++ b/keystoneclient/v2_0/users.py
@@ -68,7 +68,7 @@ class UserManager(base.ManagerWithFind):
"password": password}}
return self._update("/users/%s/OS-KSADM/password" % base.getid(user),
- params, "user")
+ params, "user", log=False)
def update_own_password(self, origpasswd, passwd):
"""Update password."""
@@ -78,7 +78,8 @@ class UserManager(base.ManagerWithFind):
return self._update("/OS-KSCRUD/users/%s" % self.api.user_id, params,
response_key="access",
method="PATCH",
- management=False)
+ management=False,
+ log=False)
def update_tenant(self, user, tenant):
"""Update default tenant."""
@@ -98,7 +99,7 @@ class UserManager(base.ManagerWithFind):
"tenantId": tenant_id,
"email": email,
"enabled": enabled}}
- return self._create('/users', params, "user")
+ return self._create('/users', params, "user", log=not bool(password))
def delete(self, user):
"""Delete a user."""
diff --git a/keystoneclient/v3/users.py b/keystoneclient/v3/users.py
index 3deed18..140c785 100644
--- a/keystoneclient/v3/users.py
+++ b/keystoneclient/v3/users.py
@@ -61,15 +61,17 @@ class UserManager(base.CrudManager):
LOG.warning("The project argument is deprecated, "
"use default_project instead.")
default_project_id = base.getid(default_project) or base.getid(project)
- return super(UserManager, self).create(
- name=name,
- domain_id=base.getid(domain),
- default_project_id=default_project_id,
- password=password,
- email=email,
- description=description,
- enabled=enabled,
- **kwargs)
+ user_data = base.filter_none(name=name,
+ domain_id=base.getid(domain),
+ default_project_id=default_project_id,
+ password=password,
+ email=email,
+ description=description,
+ enabled=enabled,
+ **kwargs)
+
+ return self._create('/users', {'user': user_data}, 'user',
+ log=not bool(password))
@utils.positional(enforcement=utils.positional.WARN)
def list(self, project=None, domain=None, group=None, default_project=None,
@@ -125,16 +127,20 @@ class UserManager(base.CrudManager):
LOG.warning("The project argument is deprecated, "
"use default_project instead.")
default_project_id = base.getid(default_project) or base.getid(project)
- return super(UserManager, self).update(
- user_id=base.getid(user),
- name=name,
- domain_id=base.getid(domain),
- default_project_id=default_project_id,
- password=password,
- email=email,
- description=description,
- enabled=enabled,
- **kwargs)
+ user_data = base.filter_none(name=name,
+ domain_id=base.getid(domain),
+ default_project_id=default_project_id,
+ password=password,
+ email=email,
+ description=description,
+ enabled=enabled,
+ **kwargs)
+
+ return self._update('/users/%s' % base.getid(user),
+ {'user': user_data},
+ 'user',
+ method='PATCH',
+ log=False)
def update_password(self, old_password, new_password):
"""Update the password for the user the token belongs to."""
@@ -151,7 +157,8 @@ class UserManager(base.CrudManager):
base_url = '/users/%s/password' % self.api.user_id
- return self._update(base_url, params, method='POST', management=False)
+ return self._update(base_url, params, method='POST', management=False,
+ log=False)
def add_to_group(self, user, group):
self._require_user_and_group(user, group)