OpenStack Identity Authentication Library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

base.py 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  2. # not use this file except in compliance with the License. You may obtain
  3. # a copy of the License at
  4. #
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. #
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. # License for the specific language governing permissions and limitations
  11. # under the License.
  12. import abc
  13. import base64
  14. import functools
  15. import hashlib
  16. import json
  17. import threading
  18. import six
  19. from keystoneauth1 import _utils as utils
  20. from keystoneauth1 import access
  21. from keystoneauth1 import discover
  22. from keystoneauth1 import exceptions
  23. from keystoneauth1 import plugin
  24. LOG = utils.get_logger(__name__)
  25. @six.add_metaclass(abc.ABCMeta)
  26. class BaseIdentityPlugin(plugin.BaseAuthPlugin):
  27. # we count a token as valid (not needing refreshing) if it is valid for at
  28. # least this many seconds before the token expiry time
  29. MIN_TOKEN_LIFE_SECONDS = 120
  30. def __init__(self, auth_url=None, reauthenticate=True):
  31. super(BaseIdentityPlugin, self).__init__()
  32. self.auth_url = auth_url
  33. self.auth_ref = None
  34. self.reauthenticate = reauthenticate
  35. self._lock = threading.Lock()
  36. @abc.abstractmethod
  37. def get_auth_ref(self, session, **kwargs):
  38. """Obtain a token from an OpenStack Identity Service.
  39. This method is overridden by the various token version plugins.
  40. This function should not be called independently and is expected to be
  41. invoked via the do_authenticate function.
  42. This function will be invoked if the AcessInfo object cached by the
  43. plugin is not valid. Thus plugins should always fetch a new AccessInfo
  44. when invoked. If you are looking to just retrieve the current auth
  45. data then you should use get_access.
  46. :param session: A session object that can be used for communication.
  47. :type session: keystoneauth1.session.Session
  48. :raises keystoneauth1.exceptions.response.InvalidResponse:
  49. The response returned wasn't appropriate.
  50. :raises keystoneauth1.exceptions.http.HttpError:
  51. An error from an invalid HTTP response.
  52. :returns: Token access information.
  53. :rtype: :class:`keystoneauth1.access.AccessInfo`
  54. """
  55. def get_token(self, session, **kwargs):
  56. """Return a valid auth token.
  57. If a valid token is not present then a new one will be fetched.
  58. :param session: A session object that can be used for communication.
  59. :type session: keystoneauth1.session.Session
  60. :raises keystoneauth1.exceptions.http.HttpError: An error from an
  61. invalid HTTP response.
  62. :return: A valid token.
  63. :rtype: string
  64. """
  65. return self.get_access(session).auth_token
  66. def _needs_reauthenticate(self):
  67. """Return if the existing token needs to be re-authenticated.
  68. The token should be refreshed if it is about to expire.
  69. :returns: True if the plugin should fetch a new token. False otherwise.
  70. """
  71. if not self.auth_ref:
  72. # authentication was never fetched.
  73. return True
  74. if not self.reauthenticate:
  75. # don't re-authenticate if it has been disallowed.
  76. return False
  77. if self.auth_ref.will_expire_soon(self.MIN_TOKEN_LIFE_SECONDS):
  78. # if it's about to expire we should re-authenticate now.
  79. return True
  80. # otherwise it's fine and use the existing one.
  81. return False
  82. def get_access(self, session, **kwargs):
  83. """Fetch or return a current AccessInfo object.
  84. If a valid AccessInfo is present then it is returned otherwise a new
  85. one will be fetched.
  86. :param session: A session object that can be used for communication.
  87. :type session: keystoneauth1.session.Session
  88. :raises keystoneauth1.exceptions.http.HttpError: An error from an
  89. invalid HTTP response.
  90. :returns: Valid AccessInfo
  91. :rtype: :class:`keystoneauth1.access.AccessInfo`
  92. """
  93. # Hey Kids! Thread safety is important particularly in the case where
  94. # a service is creating an admin style plugin that will then proceed
  95. # to make calls from many threads. As a token expires all the threads
  96. # will try and fetch a new token at once, so we want to ensure that
  97. # only one thread tries to actually fetch from keystone at once.
  98. with self._lock:
  99. if self._needs_reauthenticate():
  100. self.auth_ref = self.get_auth_ref(session)
  101. return self.auth_ref
  102. def invalidate(self):
  103. """Invalidate the current authentication data.
  104. This should result in fetching a new token on next call.
  105. A plugin may be invalidated if an Unauthorized HTTP response is
  106. returned to indicate that the token may have been revoked or is
  107. otherwise now invalid.
  108. :returns: True if there was something that the plugin did to
  109. invalidate. This means that it makes sense to try again. If
  110. nothing happens returns False to indicate give up.
  111. :rtype: bool
  112. """
  113. if self.auth_ref:
  114. self.auth_ref = None
  115. return True
  116. return False
  117. def get_endpoint_data(self, session, service_type=None, interface=None,
  118. region_name=None, service_name=None, allow=None,
  119. allow_version_hack=True, discover_versions=True,
  120. skip_discovery=False, min_version=None,
  121. max_version=None, endpoint_override=None, **kwargs):
  122. """Return a valid endpoint data for a service.
  123. If a valid token is not present then a new one will be fetched using
  124. the session and kwargs.
  125. version, min_version and max_version can all be given either as a
  126. string or a tuple.
  127. Valid interface types: `public` or `publicURL`,
  128. `internal` or `internalURL`,
  129. `admin` or 'adminURL`
  130. :param session: A session object that can be used for communication.
  131. :type session: keystoneauth1.session.Session
  132. :param string service_type: The type of service to lookup the endpoint
  133. for. This plugin will return None (failure)
  134. if service_type is not provided.
  135. :param interface: Type of endpoint. Can be a single value or a list
  136. of values. If it's a list of values, they will be
  137. looked for in order of preference. Can also be
  138. `keystoneauth1.plugin.AUTH_INTERFACE` to indicate
  139. that the auth_url should be used instead of the
  140. value in the catalog. (optional, defaults to public)
  141. :param string region_name: The region the endpoint should exist in.
  142. (optional)
  143. :param string service_name: The name of the service in the catalog.
  144. (optional)
  145. :param dict allow: Extra filters to pass when discovering API
  146. versions. (optional)
  147. :param bool allow_version_hack: Allow keystoneauth to hack up catalog
  148. URLS to support older schemes.
  149. (optional, default True)
  150. :param bool discover_versions: Whether to get version metadata from
  151. the version discovery document even
  152. if it's not neccessary to fulfill the
  153. major version request. (optional,
  154. defaults to True)
  155. :param bool skip_discovery: Whether to skip version discovery even
  156. if a version has been given. This is useful
  157. if endpoint_override or similar has been
  158. given and grabbing additional information
  159. about the endpoint is not useful.
  160. :param min_version: The minimum version that is acceptable. Mutually
  161. exclusive with version. If min_version is given
  162. with no max_version it is as if max version is
  163. 'latest'. (optional)
  164. :param max_version: The maximum version that is acceptable. Mutually
  165. exclusive with version. If min_version is given
  166. with no max_version it is as if max version is
  167. 'latest'. (optional)
  168. :param str endpoint_override: URL to use instead of looking in the
  169. catalog. Catalog lookup will be skipped,
  170. but version discovery will be run.
  171. Sets allow_version_hack to False
  172. (optional)
  173. :param kwargs: Ignored.
  174. :raises keystoneauth1.exceptions.http.HttpError: An error from an
  175. invalid HTTP response.
  176. :return: Valid EndpointData or None if not available.
  177. :rtype: `keystoneauth1.discover.EndpointData` or None
  178. """
  179. allow = allow or {}
  180. min_version, max_version = discover._normalize_version_args(
  181. None, min_version, max_version)
  182. # NOTE(jamielennox): if you specifically ask for requests to be sent to
  183. # the auth url then we can ignore many of the checks. Typically if you
  184. # are asking for the auth endpoint it means that there is no catalog to
  185. # query however we still need to support asking for a specific version
  186. # of the auth_url for generic plugins.
  187. if interface is plugin.AUTH_INTERFACE:
  188. endpoint_data = discover.EndpointData(
  189. service_url=self.auth_url,
  190. service_type=service_type or 'identity')
  191. project_id = None
  192. elif endpoint_override:
  193. # TODO(mordred) Make a code path that will look for a
  194. # matching entry in the catalog if the catalog
  195. # exists and fill in the interface, region_name, etc.
  196. # For now, just use any information the use has
  197. # provided.
  198. endpoint_data = discover.EndpointData(
  199. catalog_url=endpoint_override,
  200. interface=interface,
  201. region_name=region_name,
  202. service_name=service_name)
  203. # Setting an endpoint_override then calling get_endpoint_data means
  204. # you absolutely want the discovery info for the URL in question.
  205. # There are no code flows where this will happen for any other
  206. # reasons.
  207. allow_version_hack = False
  208. project_id = self.get_project_id(session)
  209. else:
  210. if not service_type:
  211. LOG.warning('Plugin cannot return an endpoint without '
  212. 'knowing the service type that is required. Add '
  213. 'service_type to endpoint filtering data.')
  214. return None
  215. # It's possible for things higher in the stack, because of
  216. # defaults, to explicitly pass None.
  217. if not interface:
  218. interface = 'public'
  219. service_catalog = self.get_access(session).service_catalog
  220. project_id = self.get_project_id(session)
  221. # NOTE(mordred): service_catalog.url_data_for raises if it can't
  222. # find a match, so this will always be a valid object.
  223. endpoint_data = service_catalog.endpoint_data_for(
  224. service_type=service_type,
  225. interface=interface,
  226. region_name=region_name,
  227. service_name=service_name)
  228. if not endpoint_data:
  229. return None
  230. if skip_discovery:
  231. return endpoint_data
  232. try:
  233. return endpoint_data.get_versioned_data(
  234. session,
  235. project_id=project_id,
  236. min_version=min_version,
  237. max_version=max_version,
  238. cache=self._discovery_cache,
  239. discover_versions=discover_versions,
  240. allow_version_hack=allow_version_hack, allow=allow)
  241. except (exceptions.DiscoveryFailure,
  242. exceptions.HttpError,
  243. exceptions.ConnectionError):
  244. # If a version was requested, we didn't find it, return
  245. # None.
  246. if max_version or min_version:
  247. return None
  248. # If one wasn't, then the endpoint_data we already have
  249. # should be fine
  250. return endpoint_data
  251. def get_endpoint(self, session, service_type=None, interface=None,
  252. region_name=None, service_name=None, version=None,
  253. allow=None, allow_version_hack=True,
  254. skip_discovery=False,
  255. min_version=None, max_version=None,
  256. **kwargs):
  257. """Return a valid endpoint for a service.
  258. If a valid token is not present then a new one will be fetched using
  259. the session and kwargs.
  260. version, min_version and max_version can all be given either as a
  261. string or a tuple.
  262. Valid interface types: `public` or `publicURL`,
  263. `internal` or `internalURL`,
  264. `admin` or 'adminURL`
  265. :param session: A session object that can be used for communication.
  266. :type session: keystoneauth1.session.Session
  267. :param string service_type: The type of service to lookup the endpoint
  268. for. This plugin will return None (failure)
  269. if service_type is not provided.
  270. :param interface: Type of endpoint. Can be a single value or a list
  271. of values. If it's a list of values, they will be
  272. looked for in order of preference. Can also be
  273. `keystoneauth1.plugin.AUTH_INTERFACE` to indicate
  274. that the auth_url should be used instead of the
  275. value in the catalog. (optional, defaults to public)
  276. :param string region_name: The region the endpoint should exist in.
  277. (optional)
  278. :param string service_name: The name of the service in the catalog.
  279. (optional)
  280. :param version: The minimum version number required for this
  281. endpoint. (optional)
  282. :param dict allow: Extra filters to pass when discovering API
  283. versions. (optional)
  284. :param bool allow_version_hack: Allow keystoneauth to hack up catalog
  285. URLS to support older schemes.
  286. (optional, default True)
  287. :param bool skip_discovery: Whether to skip version discovery even
  288. if a version has been given. This is useful
  289. if endpoint_override or similar has been
  290. given and grabbing additional information
  291. about the endpoint is not useful.
  292. :param min_version: The minimum version that is acceptable. Mutually
  293. exclusive with version. If min_version is given
  294. with no max_version it is as if max version is
  295. 'latest'. (optional)
  296. :param max_version: The maximum version that is acceptable. Mutually
  297. exclusive with version. If min_version is given
  298. with no max_version it is as if max version is
  299. 'latest'. (optional)
  300. :raises keystoneauth1.exceptions.http.HttpError: An error from an
  301. invalid HTTP response.
  302. :return: A valid endpoint URL or None if not available.
  303. :rtype: string or None
  304. """
  305. # Explode `version` into min_version and max_version - everything below
  306. # here uses the latter rather than the former.
  307. min_version, max_version = discover._normalize_version_args(
  308. version, min_version, max_version)
  309. # Set discover_versions to False since we're only going to return
  310. # a URL. Fetching the microversion data would be needlessly
  311. # expensive in the common case. However, discover_versions=False
  312. # will still run discovery if the version requested is not the
  313. # version in the catalog.
  314. endpoint_data = self.get_endpoint_data(
  315. session, service_type=service_type, interface=interface,
  316. region_name=region_name, service_name=service_name,
  317. allow=allow, min_version=min_version, max_version=max_version,
  318. discover_versions=False, skip_discovery=skip_discovery,
  319. allow_version_hack=allow_version_hack, **kwargs)
  320. return endpoint_data.url if endpoint_data else None
  321. def get_api_major_version(self, session, service_type=None, interface=None,
  322. region_name=None, service_name=None,
  323. version=None, allow=None,
  324. allow_version_hack=True, skip_discovery=False,
  325. discover_versions=False, min_version=None,
  326. max_version=None, **kwargs):
  327. """Return the major API version for a service.
  328. If a valid token is not present then a new one will be fetched using
  329. the session and kwargs.
  330. version, min_version and max_version can all be given either as a
  331. string or a tuple.
  332. Valid interface types: `public` or `publicURL`,
  333. `internal` or `internalURL`,
  334. `admin` or 'adminURL`
  335. :param session: A session object that can be used for communication.
  336. :type session: keystoneauth1.session.Session
  337. :param string service_type: The type of service to lookup the endpoint
  338. for. This plugin will return None (failure)
  339. if service_type is not provided.
  340. :param interface: Type of endpoint. Can be a single value or a list
  341. of values. If it's a list of values, they will be
  342. looked for in order of preference. Can also be
  343. `keystoneauth1.plugin.AUTH_INTERFACE` to indicate
  344. that the auth_url should be used instead of the
  345. value in the catalog. (optional, defaults to public)
  346. :param string region_name: The region the endpoint should exist in.
  347. (optional)
  348. :param string service_name: The name of the service in the catalog.
  349. (optional)
  350. :param version: The minimum version number required for this
  351. endpoint. (optional)
  352. :param dict allow: Extra filters to pass when discovering API
  353. versions. (optional)
  354. :param bool allow_version_hack: Allow keystoneauth to hack up catalog
  355. URLS to support older schemes.
  356. (optional, default True)
  357. :param bool skip_discovery: Whether to skip version discovery even
  358. if a version has been given. This is useful
  359. if endpoint_override or similar has been
  360. given and grabbing additional information
  361. about the endpoint is not useful.
  362. :param bool discover_versions: Whether to get version metadata from
  363. the version discovery document even
  364. if it's not neccessary to fulfill the
  365. major version request. Defaults to False
  366. because get_endpoint doesn't need
  367. metadata. (optional, defaults to False)
  368. :param min_version: The minimum version that is acceptable. Mutually
  369. exclusive with version. If min_version is given
  370. with no max_version it is as if max version is
  371. 'latest'. (optional)
  372. :param max_version: The maximum version that is acceptable. Mutually
  373. exclusive with version. If min_version is given
  374. with no max_version it is as if max version is
  375. 'latest'. (optional)
  376. :raises keystoneauth1.exceptions.http.HttpError: An error from an
  377. invalid HTTP response.
  378. :return: The major version of the API of the service discovered.
  379. :rtype: tuple or None
  380. .. note:: Implementation notes follow. Users should not need to wrap
  381. their head around these implementation notes.
  382. `get_api_major_version` should do what is expected with the
  383. least possible cost while still consistently returning a
  384. value if possible.
  385. There are many cases when major version can be satisfied
  386. without actually calling the discovery endpoint (like when the version
  387. is in the url). If the user has a cloud with the versioned endpoint
  388. ``https://volume.example.com/v3`` in the catalog for the
  389. ``block-storage`` service and they do::
  390. client = adapter.Adapter(
  391. session, service_type='block-storage', min_version=2,
  392. max_version=3)
  393. volume_version = client.get_api_major_version()
  394. The version actually be returned with no api calls other than getting
  395. the token. For that reason, :meth:`.get_api_major_version` first
  396. calls :meth:`.get_endpoint_data` with ``discover_versions=False``.
  397. If their catalog has an unversioned endpoint
  398. ``https://volume.example.com`` for the ``block-storage`` service
  399. and they do this::
  400. client = adapter.Adapter(session, service_type='block-storage')
  401. client is now set up to "use whatever is in the catalog". Since the
  402. url doesn't have a version, :meth:`.get_endpoint_data` with
  403. ``discover_versions=False`` will result in ``api_version=None``.
  404. (No version was requested so it didn't need to do the round trip)
  405. In order to find out what version the endpoint actually is, we must
  406. make a round trip. Therefore, if ``api_version`` is ``None`` after
  407. the first call, :meth:`.get_api_major_version` will make a second
  408. call to :meth:`.get_endpoint_data` with ``discover_versions=True``.
  409. """
  410. allow = allow or {}
  411. # Explode `version` into min_version and max_version - everything below
  412. # here uses the latter rather than the former.
  413. min_version, max_version = discover._normalize_version_args(
  414. version, min_version, max_version)
  415. # Using functools.partial here just to reduce copy-pasta of params
  416. get_endpoint_data = functools.partial(
  417. self.get_endpoint_data,
  418. session, service_type=service_type, interface=interface,
  419. region_name=region_name, service_name=service_name,
  420. allow=allow, min_version=min_version, max_version=max_version,
  421. skip_discovery=skip_discovery,
  422. allow_version_hack=allow_version_hack, **kwargs)
  423. data = get_endpoint_data(discover_versions=discover_versions)
  424. if (not data or not data.api_version) and not discover_versions:
  425. # It's possible that no version was requested and the endpoint
  426. # in the catalog has no version in the URL. A version has been
  427. # requested, so now it's ok to run discovery.
  428. data = get_endpoint_data(discover_versions=True)
  429. if not data:
  430. return None
  431. return data.api_version
  432. def get_all_version_data(self, session, interface='public',
  433. region_name=None, **kwargs):
  434. """Get version data for all services in the catalog.
  435. :param session: A session object that can be used for communication.
  436. :type session: keystoneauth1.session.Session
  437. :param interface:
  438. Type of endpoint to get version data for. Can be a single value
  439. or a list of values. A value of None indicates that all interfaces
  440. should be queried. (optional, defaults to public)
  441. :param string region_name:
  442. Region of endpoints to get version data for. A valueof None
  443. indicates that all regions should be queried. (optional, defaults
  444. to None)
  445. :returns: A dictionary keyed by region_name with values containing
  446. dictionaries keyed by interface with values being a list of
  447. :class:`~keystoneauth1.discover.VersionData`.
  448. """
  449. service_types = discover._SERVICE_TYPES
  450. catalog = self.get_access(session).service_catalog
  451. version_data = {}
  452. endpoints_data = catalog.get_endpoints_data(
  453. interface=interface, region_name=region_name)
  454. for service_type, services in endpoints_data.items():
  455. if service_types.is_known(service_type):
  456. service_type = service_types.get_service_type(service_type)
  457. for service in services:
  458. versions = service.get_all_version_string_data(
  459. session=session,
  460. project_id=self.get_project_id(session),
  461. )
  462. if service.region_name not in version_data:
  463. version_data[service.region_name] = {}
  464. regions = version_data[service.region_name]
  465. interface = service.interface.rstrip('URL')
  466. if interface not in regions:
  467. regions[interface] = {}
  468. regions[interface][service_type] = versions
  469. return version_data
  470. def get_user_id(self, session, **kwargs):
  471. return self.get_access(session).user_id
  472. def get_project_id(self, session, **kwargs):
  473. return self.get_access(session).project_id
  474. def get_sp_auth_url(self, session, sp_id, **kwargs):
  475. try:
  476. return self.get_access(
  477. session).service_providers.get_auth_url(sp_id)
  478. except exceptions.ServiceProviderNotFound:
  479. return None
  480. def get_sp_url(self, session, sp_id, **kwargs):
  481. try:
  482. return self.get_access(
  483. session).service_providers.get_sp_url(sp_id)
  484. except exceptions.ServiceProviderNotFound:
  485. return None
  486. def get_discovery(self, session, url, authenticated=None):
  487. """Return the discovery object for a URL.
  488. Check the session and the plugin cache to see if we have already
  489. performed discovery on the URL and if so return it, otherwise create
  490. a new discovery object, cache it and return it.
  491. This function is expected to be used by subclasses and should not
  492. be needed by users.
  493. :param session: A session object to discover with.
  494. :type session: keystoneauth1.session.Session
  495. :param str url: The url to lookup.
  496. :param bool authenticated: Include a token in the discovery call.
  497. (optional) Defaults to None (use a token
  498. if a plugin is installed).
  499. :raises keystoneauth1.exceptions.discovery.DiscoveryFailure:
  500. if for some reason the lookup fails.
  501. :raises keystoneauth1.exceptions.http.HttpError: An error from an
  502. invalid HTTP response.
  503. :returns: A discovery object with the results of looking up that URL.
  504. """
  505. return discover.get_discovery(session=session, url=url,
  506. cache=self._discovery_cache,
  507. authenticated=authenticated)
  508. def get_cache_id_elements(self):
  509. """Get the elements for this auth plugin that make it unique.
  510. As part of the get_cache_id requirement we need to determine what
  511. aspects of this plugin and its values that make up the unique elements.
  512. This should be overridden by plugins that wish to allow caching.
  513. :returns: The unique attributes and values of this plugin.
  514. :rtype: A flat dict with a str key and str or None value. This is
  515. required as we feed these values into a hash. Pairs where the
  516. value is None are ignored in the hashed id.
  517. """
  518. raise NotImplementedError()
  519. def get_cache_id(self):
  520. """Fetch an identifier that uniquely identifies the auth options.
  521. The returned identifier need not be decomposable or otherwise provide
  522. any way to recreate the plugin.
  523. This string MUST change if any of the parameters that are used to
  524. uniquely identity this plugin change. It should not change upon a
  525. reauthentication of the plugin.
  526. :returns: A unique string for the set of options
  527. :rtype: str or None if this is unsupported or unavailable.
  528. """
  529. try:
  530. elements = self.get_cache_id_elements()
  531. except NotImplementedError:
  532. return None
  533. hasher = hashlib.sha256()
  534. for k, v in sorted(elements.items()):
  535. if v is not None:
  536. # NOTE(jamielennox): in python3 you need to pass bytes to hash
  537. if isinstance(k, six.string_types):
  538. k = k.encode('utf-8')
  539. if isinstance(v, six.string_types):
  540. v = v.encode('utf-8')
  541. hasher.update(k)
  542. hasher.update(v)
  543. return base64.b64encode(hasher.digest()).decode('utf-8')
  544. def get_auth_state(self):
  545. """Retrieve the current authentication state for the plugin.
  546. Retrieve any internal state that represents the authenticated plugin.
  547. This should not fetch any new data if it is not present.
  548. :returns: a string that can be stored or None if there is no auth state
  549. present in the plugin. This string can be reloaded with
  550. set_auth_state to set the same authentication.
  551. :rtype: str or None if no auth present.
  552. """
  553. if self.auth_ref:
  554. data = {'auth_token': self.auth_ref.auth_token,
  555. 'body': self.auth_ref._data}
  556. return json.dumps(data)
  557. def set_auth_state(self, data):
  558. """Install existing authentication state for a plugin.
  559. Take the output of get_auth_state and install that authentication state
  560. into the current authentication plugin.
  561. """
  562. if data:
  563. auth_data = json.loads(data)
  564. self.auth_ref = access.create(body=auth_data['body'],
  565. auth_token=auth_data['auth_token'])
  566. else:
  567. self.auth_ref = None