Search code examples
pythonsoapws-securityzeepwsse

UsernameToken with Timestamp token || Python || WS-Security (WSSE)


I should recreate this part of the payload using python.

<wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
     <wsu:Timestamp wsu:Id="TS-C85E4BAAC54A3C164416475054038092">
        <wsu:Created>2022-03-17T08:23:23.809Z</wsu:Created>
        <wsu:Expires>2022-03-17T08:24:23.809Z</wsu:Expires>
     </wsu:Timestamp>
     <wsse:UsernameToken wsu:Id="UsernameToken-C85E4BAAC54A3C164416475053981971">
        <wsse:Username>XXXXXXXXXXXXXXX</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">XXXXXXXXXXXXXXXXXXXXXXXX</wsse:Password>
        <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">zL/iJlH2YPm83y+t0wd3Dw==</wsse:Nonce>
        <wsu:Created>2022-03-17T08:23:18.195Z</wsu:Created>
     </wsse:UsernameToken>
  </wsse:Security>

After some research I discovered that there is a library, called "zeep" that deals with this, the only problem is that from what I saw there is very little documentation about it.

UsernameToken with Timestamp token

To use UsernameToken with Timestamp token, first you need an instance of WSU.Timestamp(), then extend it with a list containing WSU.Created() and WSU.Expired() elements, finally pass it as timestamp_token keyword argument to UsernameToken().

>>> import datetime
>>> from zeep import Client
>>> from zeep.wsse.username import UsernameToken
>>> from zeep.wsse.utils import WSU
>>> timestamp_token = WSU.Timestamp()
>>> today_datetime = datetime.datetime.today()
>>> expires_datetime = today_datetime + datetime.timedelta(minutes=10)
>>> timestamp_elements = [
...         WSU.Created(today_datetime.strftime("%Y-%m-%dT%H:%M:%SZ")),
...         WSU.Expires(expires_datetime.strftime("%Y-%m-%dT%H:%M:%SZ"))
...]
>>> timestamp_token.extend(timestamp_elements)
>>> user_name_token = UsernameToken('username', 'password', timestamp_token=timestamp_token)
>>> client = Client(
...     'http://www.webservicex.net/ConvertSpeed.asmx?WSDL', wsse=user_name_token
...)

Output

<Element {http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp at 0x7f46e09032c0>
2022-03-17 09:38:20.627353
2022-03-17 09:48:20.627353
[<Element {http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Created at 0x7f46e0903400>, <Element {http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Expires at 0x7f46e0916f40>]
None
<zeep.wsse.username.UsernameToken object at 0x7f46e0911fa0>
<zeep.client.Client object at 0x7f46e0911f40>

This is all that is said regarding my case, does anyone know how I could build this piece of code?


Solution

  • I'm not sure what issue you are facing, but that code should be sufficient to get something working. Maybe the output is not what you expect, so I'll expand a bit on that.

    Assuming this is the WSDL of that service (since the webservice itself isn't working), in order to call it and also add the security header, you could do this:

    import datetime
    from zeep import Client
    from zeep.wsse.username import UsernameToken
    from zeep.wsse.utils import WSU
    from zeep.plugins import HistoryPlugin
    from lxml import etree
    
    def print_history(h):
        print(etree.tostring(h.last_sent["envelope"], encoding = "unicode", pretty_print = True))
        print(etree.tostring(h.last_received["envelope"], encoding = "unicode", pretty_print = True))
    
    timestamp_token = WSU.Timestamp()
    today_datetime = datetime.datetime.today()
    expires_datetime = today_datetime + datetime.timedelta(minutes = 10)
    
    timestamp_elements = [
        WSU.Created(today_datetime.strftime("%Y-%m-%dT%H:%M:%SZ")),
        WSU.Expires(expires_datetime.strftime("%Y-%m-%dT%H:%M:%SZ"))
    ]
    
    timestamp_token.extend(timestamp_elements)
    user_name_token = UsernameToken('username', 'password', timestamp_token = timestamp_token)
    
    history = HistoryPlugin()
    client = Client(
         'http://www.webservicex.net/ConvertSpeed.asmx?WSDL', 
         wsse = user_name_token,
         plugins = [history]
    )
    
    response = client.service.ConvertSpeed(100.00, 'kilometersPerhour', 'milesPerhour')
    
    print_history(history)
    

    The call to the service will produce the following SOAP message:

    <soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
      <soap-env:Header>
        <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
          <wsse:UsernameToken>
            <wsse:Username>username</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">password</wsse:Password>
          </wsse:UsernameToken>
          <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
            <wsu:Created>2022-03-20T14:33:15Z</wsu:Created>
            <wsu:Expires>2022-03-20T14:43:15Z</wsu:Expires>
          </wsu:Timestamp>
        </wsse:Security>
      </soap-env:Header>
      <soap-env:Body>
        <ns0:ConvertSpeed xmlns:ns0="http://www.webserviceX.NET/">
          <ns0:speed>100.0</ns0:speed>
          <ns0:FromUnit>kilometersPerhour</ns0:FromUnit>
          <ns0:ToUnit>milesPerhour</ns0:ToUnit>
        </ns0:ConvertSpeed>
      </soap-env:Body>
    </soap-env:Envelope>
    

    If you want to use zeep, I suggest you use it with the real service you are trying to call, not with some sample from the Internet that isn't available. I guess in the zeep documentation they needed some example service to call, but I'm not even sure that service wanted an authentication header.