def test_500_raises_exception(self): """ If the server fails or cannot continue it may return a 500, ensure this is handled :return: """ response_headers = {'ContentLength': '0'} responses.add(responses.POST, 'http://server:5985', adding_headers=response_headers, status=500) service = Service('http://server:5985', 'username', 'password', False) with self.assertRaises(WSManException) as context: service.invoke('headers', 'body') self.assertRegexpMatches(str(context.exception), 'the remote host returned an unexpected http status code.*')
def test_200_with_invalid_body_raises_exception(self): """ Ensure a 200 response with an empty or non-xml response is handled """ responses.add(responses.POST, 'http://server:5985', body='invalid_xml_response', status=200, content_type='application/soap+xml') service = Service('http://server:5985', 'username', 'password', False) with self.assertRaises(WSManException) as context: service.invoke('headers', 'body') self.assertEqual('the remote host returned an invalid soap response', str(context.exception))
def test_302_raises_exception(self): """ We should not follow HTTP 302 redirects """ response_headers = {'ContentLength': '0'} responses.add(responses.POST, 'http://server:5985', adding_headers=response_headers, status=302) service = Service('http://server:5985', 'username', 'password', False) with self.assertRaises(WSManException) as context: service.invoke('headers', 'body') self.assertRegexpMatches(str(context.exception), 'the remote host returned an unexpected http status code.*')
def test_200_soap_fault_to_exception_translation(self): """ SOAP Faults should be translated to WSManOperationException's """ responses.add(responses.POST, 'http://server:5985', body=self.response.operation_timeout, status=200, content_type='application/soap+xml') service = Service('http://server:5985', 'username', 'password', False) with self.assertRaises(WSManOperationException) as context: service.invoke('headers', 'body') error = 'The WS-Management service cannot complete the operation within the time specified in OperationTimeout.' self.assertRegexpMatches(str(context.exception), error)
def test_401_no_body_exception_translation(self): """ If authentication fails a 401 is returned to requests, this should be handled :return: """ response_headers = {'ContentLength': '0'} responses.add(responses.POST, 'http://server:5985', adding_headers=response_headers, status=401) service = Service('http://server:5985', 'username', 'password', False) with self.assertRaises(WSManAuthenticationException) as context: service.invoke('headers', 'body') self.assertEqual(str(context.exception), 'the remote host rejected authentication')
def test_headers_namespaces_are_translated_to_xml(self, mock_post): """ The service should translate a dictionary to xml, this is a casual test because the logic is mostly implemented in xmltodict """ headers = {'a': 'test', 'b': 'test', 'n': 'test', 'x': 'test', 'w': 'test'} mock_response = mock.Mock() mock_response.status_code = 200 mock_response.content = self.response.command_response mock_post.return_value = mock_response service = Service('http://server:5985', 'username', 'password', False) service.invoke(headers, {}) args, kwargs = mock_post.call_args self.assertTrue('<a>test</a>' in kwargs['data']) self.assertTrue('<b>test</b>' in kwargs['data']) self.assertTrue('<n>test</n>' in kwargs['data']) self.assertTrue('<x>test</x>' in kwargs['data']) self.assertTrue('<w>test</w>' in kwargs['data'])
class Session(object): """ Factory object for building sessions and connection options """ def __init__(self, endpoint, username, password, **kwargs): # transport = Session._build_transport(endpoint, auth, username, password) # Store the endpoint and the service we will use to invoke it self.endpoint = endpoint # False == No CredSSP self.service = Service(endpoint, username, password, True) # The user can set override some defaults for the Session, they can also be overridden on each request self.max_envelope = self._build_max_envelope(kwargs.get('max_envelope_size', Session.MaxEnvelopeSize)) self.locale = self._build_locale(kwargs.get('locale', Session.Locale)) # The operation timeout header overrides the timeout set on the server. Some users may prefer to # use the servers default timeout, so this header will only be included if the user explicitly sets # an operation timeout. if 'operation_timeout' in kwargs: self.default_operation_timeout = self._build_operation_timeout(kwargs.get('operation_timeout')) else: self.default_operation_timeout = None def get(self, resource, operation_timeout=None, max_envelope_size=None, locale=None): """ resource can be a URL or a ResourceLocator """ if isinstance(resource, str): resource = ResourceLocator(resource) headers = self._build_headers(resource, Session.GetAction, operation_timeout, max_envelope_size, locale) self.service.invoke.set_options(tsoapheaders=headers) return self.service.invoke def put(self, resource, obj, operation_timeout=None, max_envelope_size=None, locale=None): """ resource can be a URL or a ResourceLocator """ headers = None return self.service.invoke(headers, obj) def delete(self, resource, operation_timeout=None, max_envelope_size=None, locale=None): """ resource can be a URL or a ResourceLocator """ if isinstance(resource, str): resource = ResourceLocator(resource) headers = self._build_headers(resource, Session.DeleteAction, operation_timeout, max_envelope_size, locale) return self.service.invoke(headers, None) def create(self, resource, obj, operation_timeout=None, max_envelope_size=None, locale=None): """ resource can be a URL or a ResourceLocator """ if isinstance(resource, str): resource = ResourceLocator(resource) headers = self._build_headers(resource, Session.CreateAction, operation_timeout, max_envelope_size, locale) return self.service.invoke(headers, obj) def command(self, resource, obj, operation_timeout=None, max_envelope_size=None, locale=None): """ resource can be a URL or a ResourceLocator """ if isinstance(resource, str): resource = ResourceLocator(resource) headers = self._build_headers(resource, Session.CommandAction, operation_timeout, max_envelope_size, locale) return self.service.invoke(headers, obj) def recieve(self, resource, obj, operation_timeout=None, max_envelope_size=None, locale=None): """ resource can be a URL or a ResourceLocator """ if isinstance(resource, str): resource = ResourceLocator(resource) headers = self._build_headers(resource, Session.ReceiveAction, operation_timeout, max_envelope_size, locale) return self.service.invoke(headers, obj) @staticmethod def _build_selectors(selectors): # Build the WSMan SelectorSet Element from the selector dictionary selector_set = [] for selector_name in selectors.iterkeys(): selector_value = selectors[selector_name] selector_set.append({'#text': str(selector_value), '@Name': selector_name}) return {'w:SelectorSet': {'w:Selector': selector_set}} @staticmethod # TODO add mustcomply attribute to element def _build_options(options): option_set = [] for name, (value, must_comply) in options.iteritems(): must_comply = bool(must_comply) option_set.append({'#text': str(value), '@Name': name}) return {'w:OptionSet': {'w:Option': option_set}} def _build_operation_timeout(self, operation_timeout): if operation_timeout is None: return self.default_operation_timeout else: return {'w:OperationTimeout': 'PT{0}S'.format(operation_timeout)} def _build_max_envelope(self, max_envelope_size): if max_envelope_size is None: return self.max_envelope else: return {'w:MaxEnvelopeSize': '{0}'.format(max_envelope_size)} def _build_locale(self, locale): if locale is None: return self.locale else: return {'Locale': {"@xml:lang": "en-US"}} def _build_headers(self, resource, action, operation_timeout, max_envelope_size, locale): headers = OrderedDict([ ('a:To', self.endpoint), ('a:ReplyTo', Session.Address), ('w:ResourceURI', resource.url), ('a:MessageID', format(uuid.uuid4())), ('a:Action', action)] ) # TODO: Implement support for Microsoft XPRESS compression # https://social.msdn.microsoft.com/Forums/en-US/501e4f29-edfc-4240-af3b-344264060b99/ # wsman-xpress-remote-shell-compression?forum=os_windowsprotocols # headers.update({'rsp:CompressionType': {'@soap:mustUnderstand': 'true', '#text': 'xpress'}}) # only include the operation timeout if the user specified one when the class was instantiated # or if the user explicitly set one when invoking a method. if operation_timeout is not None: headers.update(self._build_operation_timeout(operation_timeout)) elif self.default_operation_timeout is not None: headers.update(self.default_operation_timeout) headers.update(self._build_selectors(resource.selectors)) headers.update(self._build_options(resource.options)) headers.update(self._build_max_envelope(max_envelope_size)) headers.update(self._build_locale(locale)) return headers
class Session(object): """ Factory object for building sessions and connection options """ def __init__(self, endpoint, username, password, **kwargs): # transport = Session._build_transport(endpoint, auth, username, password) # Store the endpoint and the service we will use to invoke it self.endpoint = endpoint # False == No CredSSP self.service = Service(endpoint, username, password, True) # The user can set override some defaults for the Session, they can also be overridden on each request self.max_envelope = self._build_max_envelope(kwargs.get("max_envelope_size", Session.MaxEnvelopeSize)) self.locale = self._build_locale(kwargs.get("locale", Session.Locale)) # The operation timeout header overrides the timeout set on the server. Some users may prefer to # use the servers default timeout, so this header will only be included if the user explicitly sets # an operation timeout. if "operation_timeout" in kwargs: self.default_operation_timeout = self._build_operation_timeout(kwargs.get("operation_timeout")) else: self.default_operation_timeout = None def get(self, resource, operation_timeout=None, max_envelope_size=None, locale=None): """ resource can be a URL or a ResourceLocator """ if isinstance(resource, str): resource = ResourceLocator(resource) headers = self._build_headers(resource, Session.GetAction, operation_timeout, max_envelope_size, locale) self.service.invoke.set_options(tsoapheaders=headers) return self.service.invoke def put(self, resource, obj, operation_timeout=None, max_envelope_size=None, locale=None): """ resource can be a URL or a ResourceLocator """ headers = None return self.service.invoke(headers, obj) def delete(self, resource, operation_timeout=None, max_envelope_size=None, locale=None): """ resource can be a URL or a ResourceLocator """ if isinstance(resource, str): resource = ResourceLocator(resource) headers = self._build_headers(resource, Session.DeleteAction, operation_timeout, max_envelope_size, locale) return self.service.invoke(headers, None) def create(self, resource, obj, operation_timeout=None, max_envelope_size=None, locale=None): """ resource can be a URL or a ResourceLocator """ if isinstance(resource, str): resource = ResourceLocator(resource) headers = self._build_headers(resource, Session.CreateAction, operation_timeout, max_envelope_size, locale) return self.service.invoke(headers, obj) def command(self, resource, obj, operation_timeout=None, max_envelope_size=None, locale=None): """ resource can be a URL or a ResourceLocator """ if isinstance(resource, str): resource = ResourceLocator(resource) headers = self._build_headers(resource, Session.CommandAction, operation_timeout, max_envelope_size, locale) return self.service.invoke(headers, obj) def recieve(self, resource, obj, operation_timeout=None, max_envelope_size=None, locale=None): """ resource can be a URL or a ResourceLocator """ if isinstance(resource, str): resource = ResourceLocator(resource) headers = self._build_headers(resource, Session.ReceiveAction, operation_timeout, max_envelope_size, locale) return self.service.invoke(headers, obj) @staticmethod def _build_selectors(selectors): # Build the WSMan SelectorSet Element from the selector dictionary selector_set = [] for selector_name in selectors.iterkeys(): selector_value = selectors[selector_name] selector_set.append({"#text": str(selector_value), "@Name": selector_name}) return {"w:SelectorSet": {"w:Selector": selector_set}} @staticmethod # TODO add mustcomply attribute to element def _build_options(options): option_set = [] for name, (value, must_comply) in options.iteritems(): must_comply = bool(must_comply) option_set.append({"#text": str(value), "@Name": name}) return {"w:OptionSet": {"w:Option": option_set}} def _build_operation_timeout(self, operation_timeout): if operation_timeout is None: return self.default_operation_timeout else: return {"w:OperationTimeout": "PT{0}S".format(operation_timeout)} def _build_max_envelope(self, max_envelope_size): if max_envelope_size is None: return self.max_envelope else: return {"w:MaxEnvelopeSize": "{0}".format(max_envelope_size)} def _build_locale(self, locale): if locale is None: return self.locale else: return {"Locale": {"@xml:lang": "en-US"}} def _build_headers(self, resource, action, operation_timeout, max_envelope_size, locale): headers = OrderedDict( [ ("a:To", self.endpoint), ("a:ReplyTo", Session.Address), ("w:ResourceURI", resource.url), ("a:MessageID", format(uuid.uuid4())), ("a:Action", action), ] ) # TODO: Implement support for Microsoft XPRESS compression # https://social.msdn.microsoft.com/Forums/en-US/501e4f29-edfc-4240-af3b-344264060b99/ # wsman-xpress-remote-shell-compression?forum=os_windowsprotocols # headers.update({'rsp:CompressionType': {'@soap:mustUnderstand': 'true', '#text': 'xpress'}}) # only include the operation timeout if the user specified one when the class was instantiated # or if the user explicitly set one when invoking a method. if operation_timeout is not None: headers.update(self._build_operation_timeout(operation_timeout)) elif self.default_operation_timeout is not None: headers.update(self.default_operation_timeout) headers.update(self._build_selectors(resource.selectors)) headers.update(self._build_options(resource.options)) headers.update(self._build_max_envelope(max_envelope_size)) headers.update(self._build_locale(locale)) return headers