/usr/lib/python2.7/dist-packages/keystoneclient/session.py is in python-keystoneclient 1:0.7.1-ubuntu1.2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import requests
import six
from six.moves import urllib
from keystoneclient import exceptions
from keystoneclient.openstack.common import jsonutils
from keystoneclient import utils
USER_AGENT = 'python-keystoneclient'
_logger = logging.getLogger(__name__)
def request(url, method='GET', **kwargs):
return Session().request(url, method=method, **kwargs)
class _FakeRequestSession(object):
"""This object is a temporary hack that should be removed later.
Keystoneclient has a cyclical dependency with its managers which is
preventing it from being cleaned up correctly. This is always bad but when
we switched to doing connection pooling this object wasn't getting cleaned
either and so we had left over TCP connections hanging around.
Until we can fix the client cleanup we rollback the use of a requests
session and do individual connections like we used to.
"""
def request(self, *args, **kwargs):
return requests.request(*args, **kwargs)
class Session(object):
user_agent = None
REDIRECT_STATUSES = (301, 302, 303, 305, 307)
DEFAULT_REDIRECT_LIMIT = 30
@utils.positional(2, enforcement=utils.positional.WARN)
def __init__(self, auth=None, session=None, original_ip=None, verify=True,
cert=None, timeout=None, user_agent=None,
redirect=DEFAULT_REDIRECT_LIMIT):
"""Maintains client communication state and common functionality.
As much as possible the parameters to this class reflect and are passed
directly to the requests library.
:param auth: An authentication plugin to authenticate the session with.
(optional, defaults to None)
:param requests.Session session: A requests session object that can be
used for issuing requests. (optional)
:param string original_ip: The original IP of the requesting user
which will be sent to identity service in a
'Forwarded' header. (optional)
:param verify: The verification arguments to pass to requests. These
are of the same form as requests expects, so True or
False to verify (or not) against system certificates or
a path to a bundle or CA certs to check against or None
for requests to attempt to locate and use certificates.
(optional, defaults to True)
:param cert: A client certificate to pass to requests. These are of the
same form as requests expects. Either a single filename
containing both the certificate and key or a tuple
containing the path to the certificate then a path to the
key. (optional)
:param float timeout: A timeout to pass to requests. This should be a
numerical value indicating some amount
(or fraction) of seconds or 0 for no timeout.
(optional, defaults to 0)
:param string user_agent: A User-Agent header string to use for the
request. If not provided a default is used.
(optional, defaults to
'python-keystoneclient')
:param int/bool redirect: Controls the maximum number of redirections
that can be followed by a request. Either an
integer for a specific count or True/False
for forever/never. (optional, default to 30)
"""
if not session:
session = _FakeRequestSession()
self.auth = auth
self.session = session
self.original_ip = original_ip
self.verify = verify
self.cert = cert
self.timeout = None
self.redirect = redirect
if timeout is not None:
self.timeout = float(timeout)
# don't override the class variable if none provided
if user_agent is not None:
self.user_agent = user_agent
@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, **kwargs):
"""Send an HTTP request with the specified characteristics.
Wrapper around `requests.Session.request` to handle tasks such as
setting headers, JSON encoding/decoding, and error handling.
Arguments that are not handled are passed through to the requests
library.
:param string url: Path or fully qualified URL of HTTP request. If only
a path is provided then endpoint_filter must also be
provided such that the base URL can be determined.
If a fully qualified URL is provided then
endpoint_filter will be ignored.
:param string method: The http method to use. (eg. 'GET', 'POST')
:param string original_ip: Mark this request as forwarded for this ip.
(optional)
:param dict headers: Headers to be included in the request. (optional)
:param json: Some data to be represented as JSON. (optional)
:param string user_agent: A user_agent to use for the request. If
present will override one present in headers.
(optional)
:param int/bool redirect: the maximum number of redirections that
can be followed by a request. Either an
integer for a specific count or True/False
for forever/never. (optional)
:param bool authenticated: True if a token should be attached to this
request, False if not or None for attach if
an auth_plugin is available.
(optional, defaults to None)
:param dict endpoint_filter: Data to be provided to an auth plugin with
which it should be able to determine an
endpoint to use for this request. If not
provided then URL is expected to be a
fully qualified URL. (optional)
: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.
'allow_redirects' is ignored as redirects are handled
by the session.
:raises exceptions.ClientException: For connection failure, or to
indicate an error response code.
:returns: The response to the request.
"""
headers = kwargs.setdefault('headers', dict())
if authenticated is None:
authenticated = self.auth is not None
if authenticated:
token = self.get_token()
if not token:
raise exceptions.AuthorizationFailure("No token Available")
headers['X-Auth-Token'] = token
# if we are passed a fully qualified URL and a endpoint_filter we
# should ignore the filter. This will make it easier for clients who
# want to overrule the default endpoint_filter data added to all client
# requests. We check fully qualified here by the presence of a host.
url_data = urllib.parse.urlparse(url)
if endpoint_filter and not url_data.netloc:
base_url = self.get_endpoint(**endpoint_filter)
if not base_url:
raise exceptions.EndpointNotFound()
url = '%s/%s' % (base_url.rstrip('/'), url.lstrip('/'))
if self.cert:
kwargs.setdefault('cert', self.cert)
if self.timeout is not None:
kwargs.setdefault('timeout', self.timeout)
if user_agent:
headers['User-Agent'] = user_agent
elif self.user_agent:
user_agent = headers.setdefault('User-Agent', self.user_agent)
else:
user_agent = headers.setdefault('User-Agent', USER_AGENT)
if self.original_ip:
headers.setdefault('Forwarded',
'for=%s;by=%s' % (self.original_ip, user_agent))
if json is not None:
headers['Content-Type'] = 'application/json'
kwargs['data'] = jsonutils.dumps(json)
kwargs.setdefault('verify', self.verify)
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))
# Force disable requests redirect handling. We will manage this below.
kwargs['allow_redirects'] = False
if redirect is None:
redirect = self.redirect
resp = self._send_request(url, method, redirect, **kwargs)
# NOTE(jamielennox): we create a tuple here to be the same as what is
# returned by the requests library.
resp.history = tuple(resp.history)
if resp.status_code >= 400:
_logger.debug('Request returned failure status: %s',
resp.status_code)
raise exceptions.from_response(resp, method, url)
return resp
def _send_request(self, url, method, redirect, **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
# API. See: https://en.wikipedia.org/wiki/Post/Redirect/Get
try:
resp = self.session.request(method, url, **kwargs)
except requests.exceptions.SSLError:
msg = 'SSL exception connecting to %s' % url
raise exceptions.SSLError(msg)
except requests.exceptions.Timeout:
msg = 'Request to %s timed out' % url
raise exceptions.Timeout(msg)
except requests.exceptions.ConnectionError:
msg = 'Unable to establish connection to %s' % url
raise exceptions.ConnectionError(msg)
_logger.debug('RESP: [%s] %s\nRESP BODY: %s\n',
resp.status_code, resp.headers, resp.text)
if resp.status_code in self.REDIRECT_STATUSES:
# be careful here in python True == 1 and False == 0
if isinstance(redirect, bool):
redirect_allowed = redirect
else:
redirect -= 1
redirect_allowed = redirect >= 0
if not redirect_allowed:
return resp
try:
location = resp.headers['location']
except KeyError:
_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,
**kwargs)
if not isinstance(new_resp.history, list):
new_resp.history = list(new_resp.history)
new_resp.history.insert(0, resp)
resp = new_resp
return resp
def head(self, url, **kwargs):
return self.request(url, 'HEAD', **kwargs)
def get(self, url, **kwargs):
return self.request(url, 'GET', **kwargs)
def post(self, url, **kwargs):
return self.request(url, 'POST', **kwargs)
def put(self, url, **kwargs):
return self.request(url, 'PUT', **kwargs)
def delete(self, url, **kwargs):
return self.request(url, 'DELETE', **kwargs)
def patch(self, url, **kwargs):
return self.request(url, 'PATCH', **kwargs)
@classmethod
def construct(cls, kwargs):
"""Handles constructing a session from the older HTTPClient args as
well as the new request style arguments.
*DEPRECATED*: This function is purely for bridging the gap between
older client arguments and the session arguments that they relate to.
It is not intended to be used as a generic Session Factory.
This function purposefully modifies the input kwargs dictionary so that
the remaining kwargs dict can be reused and passed on to other
functionswithout session arguments.
"""
verify = kwargs.pop('verify', None)
cacert = kwargs.pop('cacert', None)
cert = kwargs.pop('cert', None)
key = kwargs.pop('key', None)
insecure = kwargs.pop('insecure', False)
if verify is None:
if insecure:
verify = False
else:
verify = cacert or True
if cert and key:
# passing cert and key together is deprecated in favour of the
# requests lib form of having the cert and key as a tuple
cert = (cert, key)
return cls(verify=verify, cert=cert,
timeout=kwargs.pop('timeout', None),
session=kwargs.pop('session', None),
original_ip=kwargs.pop('original_ip', None),
user_agent=kwargs.pop('user_agent', None))
def get_token(self):
"""Return a token as provided by the auth plugin.
:raises AuthorizationFailure: if a new token fetch fails.
:returns string: A valid token.
"""
if not self.auth:
raise exceptions.MissingAuthPlugin("Token Required")
try:
return self.auth.get_token(self)
except exceptions.HTTPError as exc:
raise exceptions.AuthorizationFailure("Authentication failure: "
"%s" % exc)
def get_endpoint(self, **kwargs):
"""Get an endpoint as provided by the auth plugin."""
if not self.auth:
raise exceptions.MissingAuthPlugin('An auth plugin is required to '
'determine the endpoint URL.')
return self.auth.get_endpoint(self, **kwargs)
|