mvantellingen / python-zeep

A Python SOAP client
http://docs.python-zeep.org
Other
1.88k stars 578 forks source link

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

Open mattiamarteddu opened 2 years ago

mattiamarteddu commented 2 years ago

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>

I thought about using this piece of code that I found in the documentation

`>>> 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
...)`

But when I try to print the various parts this is all I get:

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>
yeramirez commented 1 year ago

Did you manage to solve it, I have a similar problem, the code does not generate the nonce, it only generates it if it is with the use_digest=True option, the problem is that I need the password in plain text, and also the nonce with the hash

yeramirez commented 1 year ago

I solved the problem this way, I'm not a programmer, I just adapted the class so that it would return the xml in the format you need. this worked for me.

from zeep.wsse import utils import base64 import hashlib import os from zeep import ns

Esta clase es original de zeep pero la modifique para que me devolviera el xml

con el formato que espera el servicio, username password tex y nonce.

class UsernameToken:

username_token_profile_ns = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0"  # noqa
soap_message_secutity_ns = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0"  # noqa

def __init__(
    self,
    username,
    password=None,
    password_digest=None,
    use_digest=False,
    nonce=None,
    created=None,
    timestamp_token=None,
    zulu_timestamp=None,
    hash_password=None,
):
    """
    Some SOAP services want zulu timestamps with Z in timestamps and
    in password digests they may want password to be hashed before
    adding it to nonce and created.
    """
    self.username = username
    self.password = password
    self.password_digest = password_digest
    self.nonce = nonce
    self.created = created
    self.use_digest = use_digest
    self.timestamp_token = timestamp_token
    self.zulu_timestamp = zulu_timestamp
    self.hash_password = hash_password

def apply(self, envelope, headers):
    security = utils.get_security_header(envelope)
    # The token placeholder might already exists since it is specified in
    # the WSDL.
    token = security.find("{%s}UsernameToken" % ns.WSSE)
    if token is None:
        token = utils.WSSE.UsernameToken()
        security.append(token)

    if self.timestamp_token is not None:
        security.append(self.timestamp_token)

    # Create the sub elements of the UsernameToken element
    elements = [utils.WSSE.Username(self.username)]
    if self.password is not None or self.password_digest is not None:
        if self.use_digest:
            pass
        else:

            elements.extend(self._create_password_digest())

    token.extend(elements)
    return envelope, headers

def verify(self, envelope):
    pass

def _create_password_digest(self):

    if self.nonce:
        nonce = self.nonce.encode("utf-8")
    else:
        nonce = os.urandom(16)
    timestamp = utils.get_timestamp(self.created, self.zulu_timestamp)

    if isinstance(self.password, str):
        password = self.password.encode("utf-8")
    else:
        password = self.password
    # digest = Base64 ( SHA-1 ( nonce + created + password ) )
    if not self.password_digest and self.hash_password:
        digest = base64.b64encode(
            hashlib.sha1(
                nonce + timestamp.encode("utf-8") + hashlib.sha1(password).digest()
            ).digest()
        ).decode("ascii")
    elif not self.password_digest:
        digest = base64.b64encode(
            hashlib.sha1(nonce + timestamp.encode("utf-8") + password).digest()
        ).decode("ascii")
    else:
        digest = self.password_digest
    return [
         utils.WSSE.Password(
            self.password, Type="%s#PasswordText" % self.username_token_profile_ns
        ),
        utils.WSSE.Nonce(
            base64.b64encode(nonce).decode("utf-8"),
            EncodingType="%s#Base64Binary" % self.soap_message_secutity_ns,
        ),
        utils.WSU.Created(timestamp),
    ]