def _require_domain_xor_project(self, domain, project): if domain and project: msg = _('Specify either a domain or project, not both') raise exceptions.ValidationError(msg) elif not (domain or project): msg = _('Must specify either a domain or project') raise exceptions.ValidationError(msg)
def _require_user_xor_group(self, user, group): if user and group: msg = _('Specify either a user or group, not both') raise exceptions.ValidationError(msg) elif not (user or group): msg = _('Must specify either a user or group') raise exceptions.ValidationError(msg)
def _process_communicate_handle_oserror(process, data, files): """Wrapper around process.communicate that checks for OSError.""" try: output, err = process.communicate(data) except OSError as e: if e.errno != errno.EPIPE: raise # OSError with EPIPE only occurs with old Python 2.7.x versions # http://bugs.python.org/issue10963 # The quick exit is typically caused by the openssl command not being # able to read an input file, so check ourselves if can't read a file. retcode, err = _check_files_accessible(files) if process.stderr: msg = process.stderr.read() if isinstance(msg, six.binary_type): msg = msg.decode("utf-8") if err: err = _( "Hit OSError in " "_process_communicate_handle_oserror(): " "%(stderr)s\nLikely due to %(file)s: %(error)s" ) % {"stderr": msg, "file": err[0], "error": err[1]} else: err = _("Hit OSError in " "_process_communicate_handle_oserror(): %s") % msg output = "" else: retcode = process.poll() if err is not None: if isinstance(err, six.binary_type): err = err.decode("utf-8") return output, err, retcode
def generate(self, credentials): """Generate auth string according to what SignatureVersion is given.""" signature_version = credentials['params'].get('SignatureVersion') if signature_version == '0': return self._calc_signature_0(credentials['params']) if signature_version == '1': return self._calc_signature_1(credentials['params']) if signature_version == '2': return self._calc_signature_2(credentials['params'], credentials['verb'], credentials['host'], credentials['path']) if self._v4_creds(credentials): return self._calc_signature_4(credentials['params'], credentials['verb'], credentials['host'], credentials['path'], credentials['headers'], credentials['body_hash']) if signature_version is not None: raise Exception(_('Unknown signature version: %s') % signature_version) else: raise Exception(_('Unexpected signature format'))
def load_from_argparse_arguments(cls, namespace, **kwargs): token = kwargs.get('token') or namespace.os_token endpoint = kwargs.get('endpoint') or namespace.os_endpoint auth_url = kwargs.get('auth_url') or namespace.os_auth_url if token and not endpoint: # if a user provides a token then they must also provide an # endpoint because we aren't fetching a token to get a catalog from msg = _('A service URL must be provided with a token') raise exc.CommandError(msg) elif (not token) and (not auth_url): # if you don't provide a token you are going to provide at least an # auth_url with which to authenticate. raise exc.CommandError(_('Expecting an auth URL via either ' '--os-auth-url or env[OS_AUTH_URL]')) plugin = super(DefaultCLI, cls).load_from_argparse_arguments(namespace, **kwargs) if (not token) and (not plugin._password): # we do this after the load so that the base plugin has an # opportunity to prompt the user for a password raise exc.CommandError(_('Expecting a password provided via ' 'either --os-password, env[OS_PASSWORD], ' 'or prompted response')) return plugin
def do_discover(cs, args): """Discover Keystone servers, supported API versions and extensions. """ if cs.endpoint: versions = cs.discover(cs.endpoint) elif cs.auth_url: versions = cs.discover(cs.auth_url) else: versions = cs.discover() if versions: if 'message' in versions: print(versions['message']) for key, version in six.iteritems(versions): if key != 'message': print(_(" - supports version %(id)s (%(status)s) here " "%(url)s") % version) extensions = cs.discover_extensions(version['url']) if extensions: for key, extension in six.iteritems(extensions): if key != 'message': print(_(" - and %(key)s: %(extension)s") % {'key': key, 'extension': extension}) else: print(_("No Keystone-compatible endpoint found"))
def do_endpoint_delete(kc, args): """Delete a service endpoint.""" try: kc.endpoints.delete(args.id) print(_('Endpoint has been deleted.')) except Exception: print(_('Unable to delete endpoint.'))
def process_token(self, region_name=None): """Extract and process information from the new auth_ref. And set the relevant authentication information. """ # if we got a response without a service catalog, set the local # list of tenants for introspection, and leave to client user # to determine what to do. Otherwise, load up the service catalog if self.auth_ref.project_scoped: if not self.auth_ref.tenant_id: raise exceptions.AuthorizationFailure( _("Token didn't provide tenant_id")) self._process_management_url(region_name) self.project_name = self.auth_ref.tenant_name self.project_id = self.auth_ref.tenant_id if not self.auth_ref.user_id: raise exceptions.AuthorizationFailure( _("Token didn't provide user_id")) self.user_id = self.auth_ref.user_id self.auth_domain_id = self.auth_ref.domain_id self.auth_tenant_id = self.auth_ref.tenant_id self.auth_user_id = self.auth_ref.user_id
def get_raw_token_from_identity_service(self, auth_url, username=None, api_key=None, tenant_id=None, password=None, project_id=None, **kwargs): """Authenticate against the v2 Identity API using an API key. :returns: access.AccessInfo if authentication was successful. :raises keystoneclient.AuthorizationFailure: if unable to authenticate or validate the existing authorization token """ if api_key is None: api_key = self._api_key if password is None: password = self.password try: if auth_url is None: raise ValueError(_("Cannot authenticate without an auth_url")) plugin = self.get_auth_plugin(auth_url, api_key, username, password, project_id, tenant_id) return plugin.get_auth_ref(self.session) except (AuthorizationFailure, Unauthorized) as exc: LOG.debug("Authorization Failed.", exc_info=exc) raise except EndpointNotFound as exc: msg = _( 'There was no suitable authentication url for this request') six.raise_from(AuthorizationFailure(msg), exc) except Exception as exc: msg = _("Authorization Failed: {0}".format(exc)) six.raise_from(AuthorizationFailure(msg), exc)
def get_token(self, auth=None): """Return a token as provided by the auth plugin. :param auth: The auth plugin to use for token. Overrides the plugin on the session. (optional) :type auth: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :returns: A valid token. :rtype: string """ if not auth: auth = self.auth if not auth: raise exceptions.MissingAuthPlugin(_("Token Required")) try: return auth.get_token(self) except exceptions.HttpError as exc: raise exceptions.AuthorizationFailure( _("Authentication failure: %s") % exc)
def do_ec2_credentials_delete(kc, args): """Delete EC2-compatible credentials.""" if not args.user_id: # use the authenticated user id as a default args.user_id = kc.auth_user_id try: kc.ec2.delete(args.user_id, args.access) print(_('Credential has been deleted.')) except Exception as e: print(_('Unable to delete credential: %s') % e)
def get_auth_ref(self, session, **kwargs): headers = {'Accept': 'application/json'} body = {'auth': {'identity': {}}} ident = body['auth']['identity'] rkwargs = {} for method in self.auth_methods: name, auth_data = method.get_auth_data(session, self, headers, request_kwargs=rkwargs) ident.setdefault('methods', []).append(name) ident[name] = auth_data if not ident: raise exceptions.AuthorizationFailure( _('Authentication method required (e.g. password)')) mutual_exclusion = [bool(self.domain_id or self.domain_name), bool(self.project_id or self.project_name), bool(self.trust_id)] if sum(mutual_exclusion) > 1: raise exceptions.AuthorizationFailure( _('Authentication cannot be scoped to multiple targets. Pick ' 'one of: project, domain or trust')) if self.domain_id: body['auth']['scope'] = {'domain': {'id': self.domain_id}} elif self.domain_name: body['auth']['scope'] = {'domain': {'name': self.domain_name}} elif self.project_id: body['auth']['scope'] = {'project': {'id': self.project_id}} elif self.project_name: scope = body['auth']['scope'] = {'project': {}} scope['project']['name'] = self.project_name if self.project_domain_id: scope['project']['domain'] = {'id': self.project_domain_id} elif self.project_domain_name: scope['project']['domain'] = {'name': self.project_domain_name} 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, log=False, **rkwargs) try: resp_data = resp.json()['token'] except (KeyError, ValueError): raise exceptions.InvalidResponse(response=resp) return access.AccessInfoV3(resp.headers['X-Subject-Token'], **resp_data)
def _positive_non_zero_float(argument_value): if argument_value is None: return None try: value = float(argument_value) except ValueError: msg = _("%s must be a float") % argument_value raise argparse.ArgumentTypeError(msg) if value <= 0: msg = _("%s must be greater than 0") % argument_value raise argparse.ArgumentTypeError(msg) return value
def __init__(self, api_key=None, **kwargs): """Initialize a new client for the Keystone v2.0 API using an API key or password.""" if not any((api_key, kwargs.get('password'))): raise ValueError(_( "Cannot authenticate without an api_key or password")) if kwargs.get('username') is None: raise ValueError(_("Cannot authenticate without a username")) self._api_key = api_key super(Client, self).__init__(**kwargs)
def update_password(self, old_password, new_password): """Update the password for the user the token belongs to.""" if not (old_password and new_password): msg = _("Specify both the current password and a new password") raise exceptions.ValidationError(msg) if old_password == new_password: msg = _("Old password and new password must be different.") raise exceptions.ValidationError(msg) params = {"user": {"password": new_password, "original_password": old_password}} base_url = "/users/%s/password" % self.client.user_id return self._update(base_url, params, method="POST", log=False)
def _check_files_accessible(files): err = None retcode = -1 try: for try_file in files: with open(try_file, 'r'): pass except IOError as e: # Catching IOError means there is an issue with # the given file. err = _('Hit OSError in _process_communicate_handle_oserror()\n' 'Likely due to %(file)s: %(error)s') % {'file': try_file, 'error': e.strerror} # Emulate openssl behavior, which returns with code 2 when # access to a file failed: # You can get more from # http://www.openssl.org/docs/apps/cms.html#EXIT_CODES # # $ openssl cms -verify -certfile not_exist_file -CAfile \ # not_exist_file -inform PEM -nosmimecap -nodetach \ # -nocerts -noattr # Error opening certificate file not_exist_file retcode = 2 return retcode, err
def authenticate(self, username=None, tenant_id=None, tenant_name=None, password=None, token=None, return_raw=False): if token: params = {"auth": {"token": {"id": token}}} elif username and password: params = {"auth": {"passwordCredentials": {"username": username, "password": password}}} else: raise ValueError( _('A username and password or token is required.')) if tenant_id: params['auth']['tenantId'] = tenant_id elif tenant_name: params['auth']['tenantName'] = tenant_name args = ['/tokens', params, 'access'] kwargs = {'return_raw': return_raw, 'log': False} # NOTE(jamielennox): try doing a regular admin query first. If there is # no endpoint that can satisfy the request (eg an unscoped token) then # issue it against the auth_url. try: token_ref = self._post(*args, **kwargs) except exceptions.EndpointNotFound: kwargs['endpoint_filter'] = {'interface': auth.AUTH_INTERFACE} token_ref = self._post(*args, **kwargs) return token_ref
def get_version_data(session, url, authenticated=None): """Retrieve raw version data from a url.""" headers = {'Accept': 'application/json'} resp = session.get(url, headers=headers, authenticated=authenticated) try: body_resp = resp.json() except ValueError: pass else: # In the event of querying a root URL we will get back a list of # available versions. try: return body_resp['versions']['values'] except (KeyError, TypeError): pass # Most servers don't have a 'values' element so accept a simple # versions dict if available. try: return body_resp['versions'] except KeyError: pass # Otherwise if we query an endpoint like /v2.0 then we will get back # just the one available version. try: return [body_resp['version']] except KeyError: pass err_text = resp.text[:50] + '...' if len(resp.text) > 50 else resp.text msg = _('Invalid Response - Bad version data returned: %s') % err_text raise exceptions.DiscoveryFailure(msg)
def list(self, fallback_to_auth=False, **kwargs): if 'id' in kwargs.keys(): # Ensure that users are not trying to call things like # ``domains.list(id='default')`` when they should have used # ``[domains.get(domain_id='default')]`` instead. Keystone supports # ``GET /v3/domains/{domain_id}``, not ``GET # /v3/domains?id={domain_id}``. raise TypeError( _("list() got an unexpected keyword argument 'id'. To " "retrieve a single object using a globally unique " "identifier, try using get() instead.")) url = self.build_url(dict_args_in_out=kwargs) try: query = self._build_query(kwargs) url_query = '%(url)s%(query)s' % {'url': url, 'query': query} return self._list( url_query, self.collection_key) except exceptions.EmptyCatalog: if fallback_to_auth: return self._list( url_query, self.collection_key, endpoint_filter={'interface': auth.AUTH_INTERFACE}) else: raise
def __init__(self, **kwargs): for param in self._method_parameters: setattr(self, param, kwargs.pop(param, None)) if kwargs: msg = _("Unexpected Attributes: %s") % ", ".join(kwargs.keys()) raise AttributeError(msg)
def _check_keystone_extensions(self, url): """Call Keystone URL and detects the available extensions.""" try: if not url.endswith("/"): url += '/' resp, body = self._request("%sextensions" % url, "GET", headers={'Accept': 'application/json'}) if resp.status_code in (200, 204): # some cases we get No Content if 'extensions' in body and 'values' in body['extensions']: # Parse correct format (per contract) extensions = body['extensions']['values'] elif 'extensions' in body: # Support incorrect, but prevalent format extensions = body['extensions'] else: return dict(message=( _('Unrecognized extensions response from %s') % url)) return dict(self._get_extension_info(e) for e in extensions) elif resp.status_code == 305: return self._check_keystone_extensions(resp['location']) else: raise exceptions.from_response( resp, "GET", "%sextensions" % url) except Exception: _logger.exception('Failed to check keystone extensions.')
def _access_service_provider(self, session): """Access protected endpoint and fetch unscoped token. After federated authentication workflow a protected endpoint should be accessible with the session object. The access is granted basing on the cookies stored within the session object. If, for some reason no cookies are present (quantity test) it means something went wrong and user will not be able to fetch an unscoped token. In that case an ``exceptions.AuthorizationFailure` exception is raised and no HTTP call is even made. :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session :raises keystoneclient.exceptions.AuthorizationFailure: in case session object has empty cookie jar. """ if self._cookies(session) is False: raise exceptions.AuthorizationFailure( _( "Session object doesn't contain a cookie, therefore you are " "not allowed to enter the Identity Provider's protected " "area." ) ) self.authenticated_response = session.get(self.token_url, authenticated=False)
def __getattr__(self, name): try: var_name = self.deprecated_session_variables[name] except KeyError: pass else: warnings.warn( "The %s session variable is deprecated as of the 1.7.0 " "release and may be removed in the 2.0.0 release" % name, DeprecationWarning, ) return getattr(self.session, var_name or name) try: var_name = self.deprecated_adapter_variables[name] except KeyError: pass else: warnings.warn( "The %s adapter variable is deprecated as of the 1.7.0 " "release and may be removed in the 2.0.0 release" % name, DeprecationWarning, ) return getattr(self._adapter, var_name or name) raise AttributeError(_("Unknown Attribute: %s") % name)
def find(self, **kwargs): """Find a single item with attributes matching ``**kwargs``.""" url = self.build_url(dict_args_in_out=kwargs) query = self._build_query(kwargs) url_query = '%(url)s%(query)s' % { 'url': url, 'query': query } elements = self._list( url_query, self.collection_key) if self.client.include_metadata: base_response = elements elements = elements.data base_response.data = elements[0] if not elements: msg = _("No %(name)s matching %(kwargs)s.") % { 'name': self.resource_class.__name__, 'kwargs': kwargs} raise ksa_exceptions.NotFound(404, msg) elif len(elements) > 1: raise ksc_exceptions.NoUniqueMatch else: return (base_response if self.client.include_metadata else elements[0])
def __getattribute__(self, name): """Return error when name is related to oauthlib and not exist.""" if name in ('access_tokens', 'consumers', 'request_tokens'): raise NotImplementedError( _('To use %r oauthlib must be installed') % name) return super(OAuthManagerOptionalImportProxy, self).__getattribute__(name)
def _check_consumer_urls(self, session, sp_response_consumer_url, idp_sp_response_consumer_url): """Check if consumer URLs issued by SP and IdP are equal. In the initial SAML2 authn Request issued by a Service Provider there is a url called ``consumer url``. A trusted Identity Provider should issue identical url. If the URLs are not equal the federated authn process should be interrupted and the user should be warned. :param session: session object to send out HTTP requests. :type session: keystoneclient.session.Session :param sp_response_consumer_url: consumer URL issued by a SP :type sp_response_consumer_url: string :param idp_sp_response_consumer_url: consumer URL issued by an IdP :type idp_sp_response_consumer_url: string """ if sp_response_consumer_url != idp_sp_response_consumer_url: # send fault message to the SP, discard the response session.post(sp_response_consumer_url, data=self.SOAP_FAULT, headers=self.ECP_SP_SAML2_REQUEST_HEADERS, authenticated=False) # prepare error message and raise an exception. msg = _("Consumer URLs from Service Provider %(service_provider)s " "%(sp_consumer_url)s and Identity Provider " "%(identity_provider)s %(idp_consumer_url)s are not equal") msg = msg % { 'service_provider': self.token_url, 'sp_consumer_url': sp_response_consumer_url, 'identity_provider': self.identity_provider, 'idp_consumer_url': idp_sp_response_consumer_url } raise exceptions.ValidationError(msg)
def __init__(self, session=None, authenticated=None, **kwargs): if not session: warnings.warn( 'Constructing a Discover instance without using a session is ' 'deprecated as of the 1.7.0 release and may be removed in the ' '2.0.0 release.', DeprecationWarning) session = client_session.Session._construct(kwargs) kwargs['session'] = session url = None endpoint = kwargs.pop('endpoint', None) auth_url = kwargs.pop('auth_url', None) if endpoint: self._use_endpoint = True url = endpoint elif auth_url: self._use_endpoint = False url = auth_url if not url: raise exceptions.DiscoveryFailure( _('Not enough information to determine URL. Provide either ' 'auth_url or endpoint')) self._client_kwargs = kwargs super(Discover, self).__init__(session, url, authenticated=authenticated)
def _send_idp_saml2_authn_request(self, session): """Present modified SAML2 authn assertion from the Service Provider.""" self._prepare_idp_saml2_request(self.saml2_authn_request) idp_saml2_authn_request = self.saml2_authn_request # Currently HTTPBasicAuth method is hardcoded into the plugin idp_response = session.post( self.identity_provider_url, headers={"Content-type": "text/xml"}, data=etree.tostring(idp_saml2_authn_request), requests_auth=(self.username, self.password), authenticated=False, log=False, ) try: self.saml2_idp_authn_response = etree.XML(idp_response.content) except etree.XMLSyntaxError as e: msg = _("SAML2: Error parsing XML returned " "from Identity Provider, reason: %s") % e raise exceptions.AuthorizationFailure(msg) idp_response_consumer_url = self.saml2_idp_authn_response.xpath( self.ECP_IDP_CONSUMER_URL, namespaces=self.ECP_SAML2_NAMESPACES ) self.idp_response_consumer_url = self._first(idp_response_consumer_url) self._check_consumer_urls(session, self.idp_response_consumer_url, self.sp_response_consumer_url)
def update_password(self, old_password, new_password): """Update the password for the user the token belongs to.""" if not (old_password and new_password): msg = _('Specify both the current password and a new password') raise exceptions.ValidationError(msg) if old_password == new_password: msg = _('Old password and new password must be different.') raise exceptions.ValidationError(msg) params = {'user': {'password': new_password, 'original_password': old_password}} base_url = '/users/%s/password' % self.client.user_id return self._update(base_url, params, method='POST', log=False)
def normalize_version_number(version): """Turn a version representation into a tuple.""" # trim the v from a 'v2.0' or similar try: version = version.lstrip('v') except AttributeError: pass # if it's an integer or a numeric as a string then normalize it # to a string, this ensures 1 decimal point try: num = float(version) except Exception: pass else: version = str(num) # if it's a string (or an integer) from above break it on . try: return tuple(map(int, version.split('.'))) except Exception: pass # last attempt, maybe it's a list or iterable. try: return tuple(map(int, version)) except Exception: pass raise TypeError(_('Invalid version specified: %s') % version)
def do_user_password_update(kc, args): """Update user password.""" user = utils.find_resource(kc.users, args.user) new_passwd = args.passwd or utils.prompt_for_password() if new_passwd is None: msg = (_("\nPlease specify password using the --pass option " "or using the prompt")) sys.exit(msg) kc.users.update_password(user, new_passwd)
def _auth_required(self, auth, msg): if not auth: auth = self.auth if not auth: msg_fmt = _('An auth plugin is required to %s') raise exceptions.MissingAuthPlugin(msg_fmt % msg) return auth
def add_endpoint_group_to_project(self, project, endpoint_group): """Create a project-endpoint_group association. PUT /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects/{project_id} """ if not (project and endpoint_group): raise ValueError(_('project and endpoint_group are required')) base_url = self._build_base_url(project=project, endpoint_group=endpoint_group) return super(EndpointGroupFilterManager, self)._put(url=base_url)
def list_projects_for_endpoint(self, endpoint): """List all projects for a given endpoint.""" if not endpoint: raise ValueError(_('endpoint is required')) base_url = self._build_base_url(endpoint=endpoint) return super(EndpointFilterManager, self)._list( base_url, projects.ProjectManager.collection_key, obj_class=projects.ProjectManager.resource_class)
def _get_data_blob(self, blob, data): # Ref bug #1259461, the <= 0.4.1 keystoneclient calling convention was # to pass "data", but the underlying API expects "blob", so # support both in the python API for backwards compatibility if blob is not None: return blob elif data is not None: return data else: raise ValueError(_("Credential requires blob to be specified"))
def list_endpoints_for_project(self, project): """List all endpoints for a given project.""" if not project: raise ValueError(_('project is required')) base_url = self._build_base_url(project=project) return super(EndpointFilterManager, self)._list( base_url, endpoints.EndpointManager.collection_key, obj_class=endpoints.EndpointManager.resource_class)
def list_projects_for_endpoint_group(self, endpoint_group): """List all projects associated with a given endpoint group.""" if not endpoint_group: raise ValueError(_('endpoint_group is required')) base_url = self._build_group_base_url(endpoint_group=endpoint_group) return super(EndpointFilterManager, self)._list( base_url, projects.ProjectManager.collection_key, obj_class=projects.ProjectManager.resource_class)
def check_endpoint_group_in_project(self, project, endpoint_group): """Checks if project-endpoint_group association exist. HEAD /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects/{project_id} """ if not (project and endpoint_group): raise ValueError(_('project and endpoint_group are required')) base_url = self._build_base_url(project=project, endpoint_group=endpoint_group) return super(EndpointGroupFilterManager, self)._head(url=base_url)
def delete_endpoint_group_from_project(self, project, endpoint_group): """Remove a project-endpoint_group association. DELETE /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects/{project_id} """ if not (project and endpoint_group): raise ValueError(_('project and endpoint_group are required')) base_url = self._build_base_url(project=project, endpoint_group=endpoint_group) return super(EndpointGroupFilterManager, self)._delete(url=base_url)
def list_endpoint_groups_for_project(self, project): """List all endpoint groups for a given project.""" if not project: raise ValueError(_('project is required')) base_url = self._build_group_base_url(project=project) return super(EndpointFilterManager, self)._list( base_url, 'endpoint_groups', obj_class=endpoint_groups.EndpointGroupManager.resource_class)
def _encoding_for_form(inform): if inform == PKI_ASN1_FORM: encoding = 'UTF-8' elif inform == PKIZ_CMS_FORM: encoding = 'hex' else: raise ValueError( _('"inform" must be one of: %s') % ','.join( (PKI_ASN1_FORM, PKIZ_CMS_FORM))) return encoding
def invalidate(self, auth=None): """Invalidate an authentication plugin. :param auth: The auth plugin to invalidate. Overrides the plugin on the session. (optional) :type auth: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` """ msg = _('An auth plugin is required to validate') auth = self._auth_required(auth, msg) return auth.invalidate()
def _get_adfs_security_token(self, session): """Send ADFS Security token to the ADFS server. Store the result in the instance attribute and raise an exception in case the response is not valid XML data. If a user cannot authenticate due to providing bad credentials, the ADFS2.0 server will return a HTTP 500 response and a XML Fault message. If ``exceptions.InternalServerError`` is caught, the method tries to parse the XML response. If parsing is unsuccessful, an ``exceptions.AuthorizationFailure`` is raised with a reason from the XML fault. Otherwise an original ``exceptions.InternalServerError`` is re-raised. :param session : a session object to send out HTTP requests. :type session: keystoneclient.session.Session :raises keystoneclient.exceptions.AuthorizationFailure: when HTTP response from the ADFS server is not a valid XML ADFS security token. :raises keystoneclient.exceptions.InternalServerError: If response status code is HTTP 500 and the response XML cannot be recognized. """ def _get_failure(e): xpath = '/s:Envelope/s:Body/s:Fault/s:Code/s:Subcode/s:Value' content = e.response.content try: obj = self.str_to_xml(content).xpath( xpath, namespaces=self.NAMESPACES) obj = self._first(obj) return obj.text # NOTE(marek-denis): etree.Element.xpath() doesn't raise an # exception, it just returns an empty list. In that case, _first() # will raise IndexError and we should treat it as an indication XML # is not valid. exceptions.AuthorizationFailure can be raised from # str_to_xml(), however since server returned HTTP 500 we should # re-raise exceptions.InternalServerError. except (IndexError, exceptions.AuthorizationFailure): raise e request_security_token = self.xml_to_str(self.prepared_request) try: response = session.post(url=self.identity_provider_url, headers=self.HEADER_SOAP, data=request_security_token, authenticated=False) except exceptions.InternalServerError as e: reason = _get_failure(e) raise exceptions.AuthorizationFailure(reason) msg = _("Error parsing XML returned from " "the ADFS Identity Provider, reason: %s") self.adfs_token = self.str_to_xml(response.content, msg)
def factory(cls, resp=None, body=None, region_name=None, auth_token=None, **kwargs): """Factory function to create a new AccessInfo object. Create AccessInfo object given a successful auth response & body or a user-provided dict. .. warning:: Use of the region_name argument is deprecated as of the 1.7.0 release and may be removed in the 2.0.0 release. """ if region_name: warnings.warn( 'Use of the region_name argument is deprecated as of the ' '1.7.0 release and may be removed in the 2.0.0 release.', DeprecationWarning) auth_ref = None if body is not None or len(kwargs): if AccessInfoV3.is_valid(body, **kwargs): if resp and not auth_token: auth_token = resp.headers['X-Subject-Token'] # NOTE(jamielennox): these return AccessInfo because they # already have auth_token installed on them. if body: if region_name: body['token']['region_name'] = region_name return AccessInfoV3(auth_token, **body['token']) else: return AccessInfoV3(auth_token, **kwargs) elif AccessInfoV2.is_valid(body, **kwargs): if body: if region_name: body['access']['region_name'] = region_name auth_ref = AccessInfoV2(**body['access']) else: auth_ref = AccessInfoV2(**kwargs) else: raise NotImplementedError(_('Unrecognized auth response')) else: auth_ref = AccessInfoV2(**kwargs) if auth_token: auth_ref.auth_token = auth_token return auth_ref
def _enforce_mutually_exclusive_group(self, system, domain, project): if not system: if domain and project: msg = _('Specify either a domain or project, not both') raise exceptions.ValidationError(msg) elif not (domain or project): msg = _('Must specify either system, domain, or project') raise exceptions.ValidationError(msg) elif system: if domain and project: msg = _( 'Specify either system, domain, or project, not all three.' ) raise exceptions.ValidationError(msg) if domain: msg = _('Specify either system or a domain, not both') raise exceptions.ValidationError(msg) if project: msg = _('Specify either a system or project, not both') raise exceptions.ValidationError(msg)
def update_password(self, old_password, new_password): """Update the password for the user the token belongs to.""" if not (old_password and new_password): msg = _('Specify both the current password and a new password') raise exceptions.ValidationError(msg) if old_password == new_password: msg = _('Old password and new password must be different.') raise exceptions.ValidationError(msg) params = { 'user': { 'password': new_password, 'original_password': old_password } } base_url = '/users/%s/password' % self.client.user_id return self._update(base_url, params, method='POST', log=False)
def do_user_update(kc, args): """Update user's name, email, and enabled status.""" kwargs = {} if args.name: kwargs['name'] = args.name if args.email is not None: kwargs['email'] = args.email if args.enabled: kwargs['enabled'] = strutils.bool_from_string(args.enabled) if not len(kwargs): print(_("User not updated, no arguments present.")) return user = utils.find_resource(kc.users, args.user) try: kc.users.update(user, **kwargs) print(_('User has been updated.')) except Exception as e: print(_('Unable to update user: %s') % e)
def get_auth_connection_params(self, auth=None, **kwargs): """Return auth connection params as provided by the auth plugin. An auth plugin may specify connection parameters to the request like providing a client certificate for communication. We restrict the values that may be returned from this function to prevent an auth plugin overriding values unrelated to connection parameters. The values that are currently accepted are: - `cert`: a path to a client certificate, or tuple of client certificate and key pair that are used with this request. - `verify`: a boolean value to indicate verifying SSL certificates against the system CAs or a path to a CA file to verify with. These values are passed to the requests library and further information on accepted values may be found there. :param auth: The auth plugin to use for tokens. Overrides the plugin on the session. (optional) :type auth: keystoneclient.auth.base.BaseAuthPlugin :raises keystoneclient.exceptions.AuthorizationFailure: if a new token fetch fails. :raises keystoneclient.exceptions.MissingAuthPlugin: if a plugin is not available. :raises keystoneclient.exceptions.UnsupportedParameters: if the plugin returns a parameter that is not supported by this session. :returns: Authentication headers or None for failure. :rtype: dict """ msg = _('An auth plugin is required to fetch connection params') auth = self._auth_required(auth, msg) params = auth.get_connection_params(self, **kwargs) # NOTE(jamielennox): There needs to be some consensus on what # parameters are allowed to be modified by the auth plugin here. # Ideally I think it would be only the send() parts of the request # flow. For now lets just allow certain elements. params_copy = params.copy() for arg in ('cert', 'verify'): try: kwargs[arg] = params_copy.pop(arg) except KeyError: # nosec(cjschaef): we are brute force # identifying and removing values in params_copy pass if params_copy: raise exceptions.UnsupportedParameters(list(params_copy)) return params
def _calculate_version(self, version, unstable): version_data = None if version: version_data = self.data_for(version) else: # if no version specified pick the latest one all_versions = self.version_data(unstable=unstable) if all_versions: version_data = all_versions[-1] if not version_data: msg = _('Could not find a suitable endpoint') if version: msg = _('Could not find a suitable endpoint for client ' 'version: %s') % str(version) raise exceptions.VersionNotAvailable(msg) return version_data
def _act_on_policy_association_for_service(self, policy, service, action): if not (policy and service): raise ValueError(_('policy and service are required')) policy_id = base.getid(policy) service_id = base.getid(service) url = ('/policies/%(policy_id)s/%(ext_name)s' '/services/%(service_id)s') % { 'policy_id': policy_id, 'ext_name': self.OS_EP_POLICY_EXT, 'service_id': service_id } return action(url=url)
def list_endpoint_groups_for_project(self, project): """List all endpoints for a given project. GET /OS-EP-FILTER/projects/{project_id}/endpoint_groups """ if not project: raise ValueError(_('project is required')) base_url = self._build_base_url(project=project) return super(EndpointGroupFilterManager, self)._list( base_url, self.collection_key, obj_class=self.resource_class)
def _process_communicate_handle_oserror(process, data, files): """Wrapper around process.communicate that checks for OSError.""" try: output, err = process.communicate(data) except OSError as e: if e.errno != errno.EPIPE: raise # OSError with EPIPE only occurs with old Python 2.7.x versions # http://bugs.python.org/issue10963 # The quick exit is typically caused by the openssl command not being # able to read an input file, so check ourselves if can't read a file. retcode, err = _check_files_accessible(files) if process.stderr: msg = process.stderr.read() if isinstance(msg, six.binary_type): msg = msg.decode('utf-8') if err: err = (_('Hit OSError in ' '_process_communicate_handle_oserror(): ' '%(stderr)s\nLikely due to %(file)s: %(error)s') % { 'stderr': msg, 'file': err[0], 'error': err[1] }) else: err = (_('Hit OSError in ' '_process_communicate_handle_oserror(): %s') % msg) output = '' else: retcode = process.poll() if err is not None: if isinstance(err, six.binary_type): err = err.decode('utf-8') return output, err, retcode
def get_raw_token_from_identity_service(self, auth_url, username=None, api_key=None, tenant_id=None, password=None, project_id=None, **kwargs): """Authenticate against the v2 Identity API using an API key. :returns: access.AccessInfo if authentication was successful. :raises keystoneclient.AuthorizationFailure: if unable to authenticate or validate the existing authorization token """ if api_key is None: api_key = self._api_key if password is None: password = self.password try: if auth_url is None: raise ValueError(_("Cannot authenticate without an auth_url")) plugin = self.get_auth_plugin(auth_url, api_key, username, password, project_id, tenant_id) return plugin.get_auth_ref(self.session) except (AuthorizationFailure, Unauthorized) as exc: LOG.debug("Authorization Failed.", exc_info=exc) raise except EndpointNotFound as exc: msg = _( 'There was no suitable authentication url for this request') six.raise_from(AuthorizationFailure(msg), exc) except Exception as exc: msg = _("Authorization Failed: {0}".format(exc)) six.raise_from(AuthorizationFailure(msg), exc)
def _act_on_policy_association_for_endpoint(self, policy, endpoint, action): if not (policy and endpoint): raise ValueError(_('policy and endpoint are required')) policy_id = base.getid(policy) endpoint_id = base.getid(endpoint) url = ('/policies/%(policy_id)s/%(ext_name)s' '/endpoints/%(endpoint_id)s') % { 'policy_id': policy_id, 'ext_name': self.OS_EP_POLICY_EXT, 'endpoint_id': endpoint_id } return action(url=url)
def _update(self, url, body=None, response_key=None, method="PUT", **kwargs): methods = {"PUT": self.client.put, "POST": self.client.post, "PATCH": self.client.patch} try: resp, body = methods[method](url, body=body, **kwargs) except KeyError: raise exceptions.ClientException(_("Invalid update method: %s") % method) # PUT requests may not return a body if body: return self.resource_class(self, body[response_key])
def require_service_catalog(f): msg = _('Configuration error: Client configured to run without a service ' 'catalog. Run the client using --os-auth-url or OS_AUTH_URL, ' 'instead of --os-endpoint or OS_SERVICE_ENDPOINT, for example.') def wrapped(kc, args): if not kc.has_service_catalog(): raise Exception(msg) return f(kc, args) # Change __doc__ attribute back to origin function's __doc__ wrapped.__doc__ = f.__doc__ return wrapped
def get_auth_plugin(self, auth_url, api_key, username, password, project_id, tenant_id): if api_key is not None: return ApiKeyAuth(auth_url, api_key, username=username or self.username, tenant_id=project_id or tenant_id) if password is not None: return PasswordAuth(auth_url, username=username or self.username, password=password, tenant_id=project_id or tenant_id) raise ValueError( _("Cannot authenticate without an api_key or password"))
def process_token(self, **kwargs): """Extract and process information from the new auth_ref. And set the relevant authentication information. """ super(Client, self).process_token(**kwargs) if self.auth_ref.domain_scoped: if not self.auth_ref.domain_id: raise exceptions.AuthorizationFailure( _("Token didn't provide domain_id")) self._process_management_url(kwargs.get('region_name')) self.domain_name = self.auth_ref.domain_name self.domain_id = self.auth_ref.domain_id if self._management_url: self._management_url = self._management_url.replace('/v2.0', '/v3')
def do_tenant_update(kc, args): """Update tenant name, description, enabled status.""" tenant = utils.find_resource(kc.tenants, args.tenant) kwargs = {} if args.name: kwargs.update({'name': args.name}) if args.description is not None: kwargs.update({'description': args.description}) if args.enabled: kwargs.update({'enabled': strutils.bool_from_string(args.enabled)}) if kwargs == {}: print(_("Tenant not updated, no arguments present.")) return tenant.update(**kwargs)
def do_endpoint_get(kc, args): """Find endpoint filtered by a specific attribute or service type.""" kwargs = { 'service_type': args.service, 'endpoint_type': args.endpoint_type, } if args.attr and args.value: kwargs.update({'attr': args.attr, 'filter_value': args.value}) elif args.attr or args.value: print(_('Both --attr and --value required.')) return url = kc.service_catalog.url_for(**kwargs) utils.print_dict({'%s.%s' % (args.service, args.endpoint_type): url})