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 13from keystoneauth1 import discover 14 15# NOTE(jamielennox): The AUTH_INTERFACE is a special value that can be 16# requested from get_endpoint. If a plugin receives this as the value of 17# 'interface' it should return the initial URL that was passed to the plugin. 18AUTH_INTERFACE = object() 19 20IDENTITY_AUTH_HEADER_NAME = 'X-Auth-Token' 21 22 23class BaseAuthPlugin(object): 24 """The basic structure of an authentication plugin. 25 26 .. note:: 27 See :doc:`/authentication-plugins` for a description of plugins 28 provided by this library. 29 30 """ 31 32 def __init__(self): 33 self._discovery_cache = {} 34 35 def get_token(self, session, **kwargs): 36 """Obtain a token. 37 38 How the token is obtained is up to the plugin. If it is still valid 39 it may be re-used, retrieved from cache or invoke an authentication 40 request against a server. 41 42 There are no required kwargs. They are passed directly to the auth 43 plugin and they are implementation specific. 44 45 Returning None will indicate that no token was able to be retrieved. 46 47 This function is misplaced as it should only be required for auth 48 plugins that use the 'X-Auth-Token' header. However due to the way 49 plugins evolved this method is required and often called to trigger an 50 authentication request on a new plugin. 51 52 When implementing a new plugin it is advised that you implement this 53 method, however if you don't require the 'X-Auth-Token' header override 54 the `get_headers` method instead. 55 56 :param session: A session object so the plugin can make HTTP calls. 57 :type session: keystoneauth1.session.Session 58 59 :return: A token to use. 60 :rtype: string 61 """ 62 return None 63 64 def get_auth_ref(self, session, **kwargs): 65 """Return the authentication reference of an auth plugin. 66 67 There are no required kwargs. They are passed directly to the auth 68 plugin and they are implementation specific. 69 70 :param session: A session object to be used for communication 71 :type session: keystoneauth1.session.session 72 """ 73 return None 74 75 def get_headers(self, session, **kwargs): 76 """Fetch authentication headers for message. 77 78 This is a more generalized replacement of the older get_token to allow 79 plugins to specify different or additional authentication headers to 80 the OpenStack standard 'X-Auth-Token' header. 81 82 How the authentication headers are obtained is up to the plugin. If the 83 headers are still valid they may be re-used, retrieved from cache or 84 the plugin may invoke an authentication request against a server. 85 86 The default implementation of get_headers calls the `get_token` method 87 to enable older style plugins to continue functioning unchanged. 88 Subclasses should feel free to completely override this function to 89 provide the headers that they want. 90 91 There are no required kwargs. They are passed directly to the auth 92 plugin and they are implementation specific. 93 94 Returning None will indicate that no token was able to be retrieved and 95 that authorization was a failure. Adding no authentication data can be 96 achieved by returning an empty dictionary. 97 98 :param session: The session object that the auth_plugin belongs to. 99 :type session: keystoneauth1.session.Session 100 101 :returns: Headers that are set to authenticate a message or None for 102 failure. Note that when checking this value that the empty 103 dict is a valid, non-failure response. 104 :rtype: dict 105 """ 106 token = self.get_token(session) 107 108 if not token: 109 return None 110 111 return {IDENTITY_AUTH_HEADER_NAME: token} 112 113 def get_endpoint_data(self, session, 114 endpoint_override=None, 115 discover_versions=True, 116 **kwargs): 117 """Return a valid endpoint data for a the service. 118 119 :param session: A session object that can be used for communication. 120 :type session: keystoneauth1.session.Session 121 :param str endpoint_override: URL to use for version discovery. 122 :param bool discover_versions: Whether to get version metadata from 123 the version discovery document even 124 if it major api version info can be 125 inferred from the url. 126 (optional, defaults to True) 127 :param kwargs: Ignored. 128 129 :raises keystoneauth1.exceptions.http.HttpError: An error from an 130 invalid HTTP response. 131 132 :return: Valid EndpointData or None if not available. 133 :rtype: `keystoneauth1.discover.EndpointData` or None 134 """ 135 if not endpoint_override: 136 return None 137 endpoint_data = discover.EndpointData(catalog_url=endpoint_override) 138 139 if endpoint_data.api_version and not discover_versions: 140 return endpoint_data 141 142 return endpoint_data.get_versioned_data( 143 session, cache=self._discovery_cache, 144 discover_versions=discover_versions) 145 146 def get_api_major_version(self, session, endpoint_override=None, **kwargs): 147 """Get the major API version from the endpoint. 148 149 :param session: A session object that can be used for communication. 150 :type session: keystoneauth1.session.Session 151 :param str endpoint_override: URL to use for version discovery. 152 :param kwargs: Ignored. 153 154 :raises keystoneauth1.exceptions.http.HttpError: An error from an 155 invalid HTTP response. 156 157 :return: Valid EndpointData or None if not available. 158 :rtype: `keystoneauth1.discover.EndpointData` or None 159 """ 160 endpoint_data = self.get_endpoint_data( 161 session, endpoint_override=endpoint_override, 162 discover_versions=False, **kwargs) 163 if endpoint_data is None: 164 return 165 166 if endpoint_data.api_version is None: 167 # No version detected from the URL, trying full discovery. 168 endpoint_data = self.get_endpoint_data( 169 session, endpoint_override=endpoint_override, 170 discover_versions=True, **kwargs) 171 172 if endpoint_data and endpoint_data.api_version: 173 return endpoint_data.api_version 174 175 return None 176 177 def get_endpoint(self, session, **kwargs): 178 """Return an endpoint for the client. 179 180 There are no required keyword arguments to ``get_endpoint`` as a plugin 181 implementation should use best effort with the information available to 182 determine the endpoint. However there are certain standard options that 183 will be generated by the clients and should be used by plugins: 184 185 - ``service_type``: what sort of service is required. 186 - ``service_name``: the name of the service in the catalog. 187 - ``interface``: what visibility the endpoint should have. 188 - ``region_name``: the region the endpoint exists in. 189 190 :param session: The session object that the auth_plugin belongs to. 191 :type session: keystoneauth1.session.Session 192 193 :returns: The base URL that will be used to talk to the required 194 service or None if not available. 195 :rtype: string 196 """ 197 endpoint_data = self.get_endpoint_data( 198 session, discover_versions=False, **kwargs) 199 if not endpoint_data: 200 return None 201 return endpoint_data.url 202 203 def get_connection_params(self, session, **kwargs): 204 """Return any additional connection parameters required for the plugin. 205 206 :param session: The session object that the auth_plugin belongs to. 207 :type session: keystoneauth1.session.Session 208 209 :returns: Headers that are set to authenticate a message or None for 210 failure. Note that when checking this value that the empty 211 dict is a valid, non-failure response. 212 :rtype: dict 213 """ 214 return {} 215 216 def invalidate(self): 217 """Invalidate the current authentication data. 218 219 This should result in fetching a new token on next call. 220 221 A plugin may be invalidated if an Unauthorized HTTP response is 222 returned to indicate that the token may have been revoked or is 223 otherwise now invalid. 224 225 :returns: True if there was something that the plugin did to 226 invalidate. This means that it makes sense to try again. If 227 nothing happens returns False to indicate give up. 228 :rtype: bool 229 """ 230 return False 231 232 def get_user_id(self, session, **kwargs): 233 """Return a unique user identifier of the plugin. 234 235 Wherever possible the user id should be inferred from the token however 236 there are certain URLs and other places that require access to the 237 currently authenticated user id. 238 239 :param session: A session object so the plugin can make HTTP calls. 240 :type session: keystoneauth1.session.Session 241 242 :returns: A user identifier or None if one is not available. 243 :rtype: str 244 """ 245 return None 246 247 def get_project_id(self, session, **kwargs): 248 """Return the project id that we are authenticated to. 249 250 Wherever possible the project id should be inferred from the token 251 however there are certain URLs and other places that require access to 252 the currently authenticated project id. 253 254 :param session: A session object so the plugin can make HTTP calls. 255 :type session: keystoneauth1.session.Session 256 257 :returns: A project identifier or None if one is not available. 258 :rtype: str 259 """ 260 return None 261 262 def get_sp_auth_url(self, session, sp_id, **kwargs): 263 """Return auth_url from the Service Provider object. 264 265 This url is used for obtaining unscoped federated token from remote 266 cloud. 267 268 :param sp_id: ID of the Service Provider to be queried. 269 :type sp_id: string 270 271 :returns: A Service Provider auth_url or None if one is not available. 272 :rtype: str 273 274 """ 275 return None 276 277 def get_sp_url(self, session, sp_id, **kwargs): 278 """Return sp_url from the Service Provider object. 279 280 This url is used for passing SAML2 assertion to the remote cloud. 281 282 :param sp_id: ID of the Service Provider to be queried. 283 :type sp_id: str 284 285 :returns: A Service Provider sp_url or None if one is not available. 286 :rtype: str 287 288 """ 289 return None 290 291 def get_cache_id(self): 292 """Fetch an identifier that uniquely identifies the auth options. 293 294 The returned identifier need not be decomposable or otherwise provide 295 anyway to recreate the plugin. It should not contain sensitive data in 296 plaintext. 297 298 This string MUST change if any of the parameters that are used to 299 uniquely identity this plugin change. 300 301 If get_cache_id returns a str value suggesting that caching is 302 supported then get_auth_cache and set_auth_cache must also be 303 implemented. 304 305 :returns: A unique string for the set of options 306 :rtype: str or None if this is unsupported or unavailable. 307 """ 308 return None 309 310 def get_auth_state(self): 311 """Retrieve the current authentication state for the plugin. 312 313 Retrieve any internal state that represents the authenticated plugin. 314 315 This should not fetch any new data if it is not present. 316 317 :raises NotImplementedError: if the plugin does not support this 318 feature. 319 320 :returns: raw python data (which can be JSON serialized) that can be 321 moved into another plugin (of the same type) to have the 322 same authenticated state. 323 :rtype: object or None if unauthenticated. 324 """ 325 raise NotImplementedError() 326 327 def set_auth_state(self, data): 328 """Install existing authentication state for a plugin. 329 330 Take the output of get_auth_state and install that authentication state 331 into the current authentication plugin. 332 333 :raises NotImplementedError: if the plugin does not support this 334 feature. 335 """ 336 raise NotImplementedError() 337 338 339class FixedEndpointPlugin(BaseAuthPlugin): 340 """A base class for plugins that have one fixed endpoint.""" 341 342 def __init__(self, endpoint=None): 343 super(FixedEndpointPlugin, self).__init__() 344 self.endpoint = endpoint 345 346 def get_endpoint(self, session, **kwargs): 347 """Return the supplied endpoint. 348 349 Using this plugin the same endpoint is returned regardless of the 350 parameters passed to the plugin. endpoint_override overrides the 351 endpoint specified when constructing the plugin. 352 """ 353 return kwargs.get('endpoint_override') or self.endpoint 354 355 def get_endpoint_data(self, session, 356 endpoint_override=None, 357 discover_versions=True, 358 **kwargs): 359 """Return a valid endpoint data for a the service. 360 361 :param session: A session object that can be used for communication. 362 :type session: keystoneauth1.session.Session 363 :param str endpoint_override: URL to use for version discovery. 364 :param bool discover_versions: Whether to get version metadata from 365 the version discovery document even 366 if it major api version info can be 367 inferred from the url. 368 (optional, defaults to True) 369 :param kwargs: Ignored. 370 371 :raises keystoneauth1.exceptions.http.HttpError: An error from an 372 invalid HTTP response. 373 374 :return: Valid EndpointData or None if not available. 375 :rtype: `keystoneauth1.discover.EndpointData` or None 376 """ 377 return super(FixedEndpointPlugin, self).get_endpoint_data( 378 session, 379 endpoint_override=endpoint_override or self.endpoint, 380 discover_versions=discover_versions, 381 **kwargs) 382