/usr/lib/python3/dist-packages/swiftclient/authv1.py is in python3-swiftclient 1:3.5.0-0ubuntu1.
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 | # Copyright 2016 OpenStack Foundation
#
# 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.
"""
Authentication plugin for keystoneauth to support v1 endpoints.
Way back in the long-long ago, there was no Keystone. Swift used an auth
mechanism now known as "v1", which used only HTTP headers. Auth requests
and responses would look something like::
> GET /auth/v1.0 HTTP/1.1
> Host: <swift server>
> X-Auth-User: <tenant>:<user>
> X-Auth-Key: <password>
>
< HTTP/1.1 200 OK
< X-Storage-Url: http://<swift server>/v1/<tenant account>
< X-Auth-Token: <token>
< X-Storage-Token: <token>
<
This plugin provides a way for Keystone sessions (and clients that
use them, like python-openstackclient) to communicate with old auth
endpoints that still use this mechanism, such as tempauth, swauth,
or https://identity.api.rackspacecloud.com/v1.0
"""
import datetime
import json
import time
from six.moves.urllib.parse import urljoin
# Note that while we import keystoneauth1 here, we *don't* need to add it to
# requirements.txt -- this entire module only makes sense (and should only be
# loaded) if keystoneauth is already installed.
from keystoneauth1 import plugin
from keystoneauth1 import exceptions
from keystoneauth1 import loading
from keystoneauth1.identity import base
# stupid stdlib...
class _UTC(datetime.tzinfo):
def utcoffset(self, dt):
return datetime.timedelta(0)
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return datetime.timedelta(0)
UTC = _UTC()
del _UTC
class ServiceCatalogV1(object):
def __init__(self, auth_url, storage_url, account):
self.auth_url = auth_url
self._storage_url = storage_url
self._account = account
@property
def storage_url(self):
if self._account:
return urljoin(self._storage_url.rstrip('/'), self._account)
return self._storage_url
@property
def catalog(self):
# openstackclient wants this for the `catalog list` and
# `catalog show` commands
endpoints = [{
'region': 'default',
'publicURL': self._storage_url,
}]
if self.storage_url != self._storage_url:
endpoints.insert(0, {
'region': 'override',
'publicURL': self.storage_url,
})
return [
{
'name': 'swift',
'type': 'object-store',
'endpoints': endpoints,
},
{
'name': 'auth',
'type': 'identity',
'endpoints': [{
'region': 'default',
'publicURL': self.auth_url,
}],
}
]
def url_for(self, **kwargs):
kwargs.setdefault('interface', 'public')
kwargs.setdefault('service_type', None)
if kwargs['service_type'] == 'object-store':
return self.storage_url
# Although our "catalog" includes an identity entry, nothing that uses
# url_for() (including `openstack endpoint list`) will know what to do
# with it. Better to just raise the exception, cribbing error messages
# from keystoneauth1/access/service_catalog.py
if 'service_name' in kwargs and 'region_name' in kwargs:
msg = ('%(interface)s endpoint for %(service_type)s service '
'named %(service_name)s in %(region_name)s region not '
'found' % kwargs)
elif 'service_name' in kwargs:
msg = ('%(interface)s endpoint for %(service_type)s service '
'named %(service_name)s not found' % kwargs)
elif 'region_name' in kwargs:
msg = ('%(interface)s endpoint for %(service_type)s service '
'in %(region_name)s region not found' % kwargs)
else:
msg = ('%(interface)s endpoint for %(service_type)s service '
'not found' % kwargs)
raise exceptions.EndpointNotFound(msg)
class AccessInfoV1(object):
"""An object for encapsulating a raw v1 auth token."""
def __init__(self, auth_url, storage_url, account, username, auth_token,
token_life):
self.auth_url = auth_url
self.storage_url = storage_url
self.account = account
self.service_catalog = ServiceCatalogV1(auth_url, storage_url, account)
self.username = username
self.auth_token = auth_token
self._issued = time.time()
try:
self._expires = self._issued + float(token_life)
except (TypeError, ValueError):
self._expires = None
# following is used by openstackclient
self.project_id = None
@property
def expires(self):
if self._expires is None:
return None
return datetime.datetime.fromtimestamp(self._expires, UTC)
@property
def issued(self):
return datetime.datetime.fromtimestamp(self._issued, UTC)
@property
def user_id(self):
# openstackclient wants this for the `token issue` command
return self.username
def will_expire_soon(self, stale_duration):
"""Determines if expiration is about to occur.
:returns: true if expiration is within the given duration
"""
if self._expires is None:
return False # assume no expiration
return time.time() + stale_duration > self._expires
def get_state(self):
"""Serialize the current state."""
return json.dumps({
'auth_url': self.auth_url,
'storage_url': self.storage_url,
'account': self.account,
'username': self.username,
'auth_token': self.auth_token,
'issued': self._issued,
'expires': self._expires}, sort_keys=True)
@classmethod
def from_state(cls, data):
"""Deserialize the given state.
:returns: a new AccessInfoV1 object with the given state
"""
data = json.loads(data)
access = cls(
data['auth_url'],
data['storage_url'],
data['account'],
data['username'],
data['auth_token'],
token_life=None)
access._issued = data['issued']
access._expires = data['expires']
return access
class PasswordPlugin(base.BaseIdentityPlugin):
"""A plugin for authenticating with a username and password.
Subclassing from BaseIdentityPlugin gets us a few niceties, like handling
token invalidation and locking during authentication.
:param string auth_url: Identity v1 endpoint for authorization.
:param string username: Username for authentication.
:param string password: Password for authentication.
:param string project_name: Swift account to use after authentication.
We use 'project_name' to be consistent with
other auth plugins.
:param string reauthenticate: Whether to allow re-authentication.
"""
access_class = AccessInfoV1
def __init__(self, auth_url, username, password, project_name=None,
reauthenticate=True):
super(PasswordPlugin, self).__init__(
auth_url=auth_url,
reauthenticate=reauthenticate)
self.user = username
self.key = password
self.account = project_name
def get_auth_ref(self, session, **kwargs):
"""Obtain a token from a v1 endpoint.
This function should not be called independently and is expected to be
invoked via the do_authenticate function.
This function will be invoked if the AcessInfo object cached by the
plugin is not valid. Thus plugins should always fetch a new AccessInfo
when invoked. If you are looking to just retrieve the current auth
data then you should use get_access.
:param session: A session object that can be used for communication.
:returns: Token access information.
"""
headers = {'X-Auth-User': self.user,
'X-Auth-Key': self.key}
resp = session.get(self.auth_url, headers=headers,
authenticated=False, log=False)
if resp.status_code // 100 != 2:
raise exceptions.InvalidResponse(response=resp)
if 'X-Storage-Url' not in resp.headers:
raise exceptions.InvalidResponse(response=resp)
if 'X-Auth-Token' not in resp.headers and \
'X-Storage-Token' not in resp.headers:
raise exceptions.InvalidResponse(response=resp)
token = resp.headers.get('X-Storage-Token',
resp.headers.get('X-Auth-Token'))
return AccessInfoV1(
auth_url=self.auth_url,
storage_url=resp.headers['X-Storage-Url'],
account=self.account,
username=self.user,
auth_token=token,
token_life=resp.headers.get('X-Auth-Token-Expires'))
def get_cache_id_elements(self):
"""Get the elements for this auth plugin that make it unique."""
return {'auth_url': self.auth_url,
'user': self.user,
'key': self.key,
'account': self.account}
def get_endpoint(self, session, interface='public', **kwargs):
"""Return an endpoint for the client."""
if interface is plugin.AUTH_INTERFACE:
return self.auth_url
else:
return self.get_access(session).service_catalog.url_for(
interface=interface, **kwargs)
def get_auth_state(self):
"""Retrieve the current authentication state for the plugin.
:returns: raw python data (which can be JSON serialized) that can be
moved into another plugin (of the same type) to have the
same authenticated state.
"""
if self.auth_ref:
return self.auth_ref.get_state()
def set_auth_state(self, data):
"""Install existing authentication state for a plugin.
Take the output of get_auth_state and install that authentication state
into the current authentication plugin.
"""
if data:
self.auth_ref = self.access_class.from_state(data)
else:
self.auth_ref = None
def get_sp_auth_url(self, *args, **kwargs):
raise NotImplementedError()
def get_sp_url(self, *args, **kwargs):
raise NotImplementedError()
def get_discovery(self, *args, **kwargs):
raise NotImplementedError()
class PasswordLoader(loading.BaseLoader):
"""Option handling for the ``v1password`` plugin."""
plugin_class = PasswordPlugin
def get_options(self):
"""Return the list of parameters associated with the auth plugin.
This list may be used to generate CLI or config arguments.
"""
return [
loading.Opt('auth-url', required=True,
help='Authentication URL'),
# overload project-name as a way to specify an alternate account,
# since:
# - in a world of just users & passwords, this seems the closest
# analog to a project, and
# - openstackclient will (or used to?) still require that you
# provide one anyway
loading.Opt('project-name', required=False,
help='Swift account to use'),
loading.Opt('username', required=True,
deprecated=[loading.Opt('user-name')],
help='Username to login with'),
loading.Opt('password', required=True, secret=True,
help='Password to use'),
]
|