Logging in via SOAP API

Hi.

I’m having some issues with logging in to a Milestone server with python using the SOAP API.

In our dev environment, everything seems to work fine. Eg:

D:\>type mtest.py

import requests

import uuid

from configparser import ConfigParser

from requests.auth import HTTPBasicAuth

from zeep import Client

from zeep.transports import Transport

if __name__ == “__main__”:

cp = ConfigParser()

cp.read(‘milestone.conf’)

username = cp[‘Milestone’][‘USERNAME’]

password = cp[‘Milestone’][‘PASSWORD’]

hostname = cp[‘Milestone’][‘HOSTNAME’]

session = requests.Session()

session.auth = HTTPBasicAuth(username, password)

client = Client(f’http://{hostname}/ManagementServer/ServerCommandService.svc?singleWsdl’,

               transport=Transport(session=session))

print(‘[+] Connected to Server’)

guid = uuid.uuid4()

login_info = client.service.Login(guid, ‘’)

token = login_info[‘Token’]

print(f’[+] Token = {token.replace(hostname, “XXXXXX.XXX.XXX”)}')

exit(0)

D:\>python mtest.py

[+] Connected to Server

[+] Token = TOKEN#a2eaee5d-38d9-4a00-a18d-9727d91728b0#XXXXXX.XXX.XXX//ServerConnector#

D:\>

However, when we run the same code in production:

ks0mh:/tmp> cat mtest.py

import requests

import uuid

from configparser import ConfigParser

from requests.auth import HTTPBasicAuth

from zeep import Client

from zeep.transports import Transport

if __name__ == “__main__”:

cp = ConfigParser()

cp.read(‘milestone.conf’)

username = cp[‘Milestone’][‘USERNAME’]

password = cp[‘Milestone’][‘PASSWORD’]

hostname = cp[‘Milestone’][‘HOSTNAME’]

session = requests.Session()

session.auth = HTTPBasicAuth(username, password)

client = Client(f’http://{hostname}/ManagementServer/ServerCommandService.svc?singleWsdl’,

               transport=Transport(session=session))

print(‘[+] Connected to Server’)

guid = uuid.uuid4()

login_info = client.service.Login(guid, ‘’)

token = login_info[‘Token’]

print(f’[+] Token = {token.replace(hostname, “XXXXXX.XXX.XXX”)}')

exit(0)

ks0mh:/tmp> python3 mtest.py

[+] Connected to Server

Traceback (most recent call last):

File “mtest.py”, line 23, in

login_info = client.service.Login(guid, ‘’)

File “/home/ks0mh/.local/lib/python3.6/site-packages/zeep/proxy.py”, line 51, in __call__

kwargs,

File “/home/ks0mh/.local/lib/python3.6/site-packages/zeep/wsdl/bindings/soap.py”, line 135, in send

return self.process_reply(client, operation_obj, response)

File “/home/ks0mh/.local/lib/python3.6/site-packages/zeep/wsdl/bindings/soap.py”, line 229, in process_reply

return self.process_error(doc, operation)

File “/home/ks0mh/.local/lib/python3.6/site-packages/zeep/wsdl/bindings/soap.py”, line 333, in process_error

detail=fault_node.find(“detail”, namespaces=fault_node.nsmap),

zeep.exceptions.Fault: Value cannot be null.

Parameter name: parameters

ks0mh:/tmp>

The only differences I am aware of are the following:

- The dev environment script is being run in Windows, while the production one is being run in Linux. I don’t believe this should have any impact on the outcome.

- The Windows version of python is 3.11.1, while the Linux version is 3.6.15; this could potentially be an issue, however at this point I believe it’s more likely to be something else

- The dev environment version of Milestone is 23.1.56.1, while the one running in production is 22.3.207.3

We have tried using a different SOAP client library, with the same results.

We have tried Ntlm authentication instead of Basic, and the outcome was the same.

Do you have any idea what could be causing this discrepancy in results? Any suggestion on what else to try would be welcome. We have tried everything we could think of, both reasonable and ridiculous ideas, short of upgrading the Linux version of python, because of internal policy.

Thanks!

We have no or very little experience with Python. We suspect that the exception (nullref) is not really anything to do with the Milestone api/service but rather something in the library/environment you have, at any rate you must try to chase down what is null.

Hi Bo.

Thank you so much for your reply.

Like I mentioned above, we tried everything we could think of. We debugged with different tools. We’ve been battling this for over 2 weeks.

Here’s an example of the same error without using Python. We’re using ReadyAPI (from https://www.soapui.org)

(image also included as an attachment)

We also tried with setting currentToken to something other than empty string. Same result.

Do you still believe it’s not a Milestone api/service issue? If so, any suggestion of how we can tackle this further?

I really appreciate your help. Thanks again!

Hi Robert,

In a sample where I used SOAP and Basic login to get a token, I completely left out currentToken, only supplying an instanceId:

Will a similar reduced Body/Login remove the error in your case?

The sample code is available here: https://developer.milestonesys.com/s/question/0D53X00009QOrhvSAD/samples-for-devcon21-session-on-protocol-integrations-from-linux-tips-and-tricks

Hi Hans

Thanks for the suggestion and the sample code!

I tried porting the authentication part to python, and I seem to get the same error response

Edit: attached screenshots instead of code snippet as it’s much easier to read

Edit2: added screenshots for ReadyAPI for both NTLM and Basic auth



Hi Robert,

It actually looks like you are on the right path. The login contains an authentication/negotiation flow (that in my code is handled by the used libraries):

First POST is “anonymous”:

POST https://myHost/ManagementServer/ServerCommandService.svc HTTP/1.1

Host: myHost

SOAPAction: http://videoos.net/2/XProtectCSServerCommand/IServerCommandService/Login

Connection: Keep-Alive

Content-Type: text/xml

Content-Length: 253

<s:Envelope xmlns:s=“http://schemas.xmlsoap.org/soap/envelope/”>

<s:Body>

<Login xmlns=“http://videoos.net/2/XProtectCSServerCommand”>

26a21886-a710-4308-9d28-a46844903a73

</s:Body>

</s:Envelope>

and the response is a 401 + a hint on how to authenticate, here Basic:

HTTP/1.1 401 Unauthorized

Cache-Control: private

Content-Type: text/xml; charset=utf-8

Server: Microsoft-IIS/10.0

WWW-Authenticate: Basic realm=“localhost”

X-AspNet-Version: 4.0.30319

X-Powered-By: ASP.NET

X-Frame-Options: SAMEORIGIN

Date: Thu, 27 Jun 2024 08:39:37 GMT

Content-Length: 1355

<s:Envelope xmlns:s=“http://schemas.xmlsoap.org/soap/envelope/”><s:Body><s:Fault><faultcode xmlns:a=“http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher”>a:InternalServiceFaultValue cannot be null.&[xd](javascript:void(0); “xd”); Parameter name: parameters<ExceptionDetail xmlns=“http://schemas.datacontract.org/2004/07/System.ServiceModel” xmlns:i=“http://www.w3.org/2001/XMLSchema-instance”>Value cannot be null.&[xd](javascript:void(0); “xd”); Parameter name: parameters at System.ServiceModel.Dispatcher.OperationFormatter.SerializeReply(MessageVersion messageVersion, Object[] parameters, Object result)&[xd](javascript:void(0); “xd”); at System.ServiceModel.Dispatcher.DispatchOperationRuntime.SerializeOutputs(MessageRpc& rpc)&[xd](javascript:void(0); “xd”); at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)&[xd](javascript:void(0); “xd”); at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)&[xd](javascript:void(0); “xd”); at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)&[xd](javascript:void(0); “xd”); at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)System.ArgumentNullException</s:Fault></s:Body></s:Envelope>

(the odd thing here is the InternalServiceFault, but we should be able to disregard that…)

The next POST uses the authentication hint to authorize with the Basic credentials:

POST https://myHost/ManagementServer/ServerCommandService.svc HTTP/1.1

Host: myHost

SOAPAction: http://videoos.net/2/XProtectCSServerCommand/IServerCommandService/Login

Connection: Keep-Alive

Authorization: Basic SHFupBYfMTIzNA==

Content-Type: text/xml

Content-Length: 253

<s:Envelope xmlns:s=“http://schemas.xmlsoap.org/soap/envelope/”>

<s:Body>

<Login xmlns=“http://videoos.net/2/XProtectCSServerCommand”>

26a21886-a710-4308-9d28-a46844903a73

</s:Body>

</s:Envelope>

and we get a 200 + the wanted response with the Token

HTTP/1.1 200 OK

Cache-Control: private

Content-Type: text/xml; charset=utf-8

Server: Microsoft-IIS/10.0

X-AspNet-Version: 4.0.30319

X-Powered-By: ASP.NET

X-Frame-Options: SAMEORIGIN

Date: Thu, 27 Jun 2024 08:39:38 GMT

Content-Length: 527

<s:Envelope xmlns:s=“http://schemas.xmlsoap.org/soap/envelope/”><s:Body><LoginResponse xmlns=“http://videoos.net/2/XProtectCSServerCommand”><LoginResult xmlns:i=“http://www.w3.org/2001/XMLSchema-instance”>2024-06-27T08:39:38.993Z14400000000false<Token>TOKEN#5e4a7a36-fe82-4422-bce1-3a89d5db9#myHosty//ServerConnector#</Token></s:Body></s:Envelope>

I case you get another 401 in this POST with the Authorization header, it is probably because the supplied credentials are not useful for this operation, on this installation.

Hi Hans!

Thank you so much for your reply.

I get the same error as before. So, when I send both requests like you do (first without auth, and the second one with basic auth headers), I get the exact same response both times - the 401 you get on the first one.

I could be wrong, but my guess is that you’re getting the 401 the first time because you’re not sending a Basic Auth on the first request. I bet that if you sent only 1 request and included the Basic Auth header (so, sending only the second one), you would get the token right away – that’s what happens on our development server: the authentication works there just with a single request, as you can see on my original post.

This is probably too much of a tall order to ask, but you don’t happen to have chance of testing your code against a Milestone version 22.3.207.3, do you?

Thanks again for your help, I really appreciate it.

Edit: added the requests and response

=== REQUEST 1 ===

https://[REDACTED HOST]/ManagementServer/ServerCommandService.svc

{‘User-Agent’: ‘python-requests/2.25.1’, ‘Accept-Encoding’: ‘gzip, deflate’, ‘Accept’: ‘*/*’, ‘Connection’: ‘keep-alive’, ‘Content-Type’: ‘text/xml’, ‘SOAPAction’: ‘http://videoos.net/2/XProtectCSServerCommand/IServerCommandService/Login’, ‘Content-Length’: ‘247’}

<s:Envelope xmlns:s=“http://schemas.xmlsoap.org/soap/envelope/”>

<s:Body>

099156bd-1b96-42b8-ab1f-f5628984de9a

</s:Body>

</s:Envelope>

=== RESPONSE 1 ===

URL: https://[REDACTED HOST]/ManagementServer/ServerCommandService.svc

HEADERS: {‘Cache-Control’: ‘private’, ‘Content-Type’: ‘text/xml; charset=utf-8’, ‘Server’: ‘Microsoft-IIS/10.0’, ‘WWW-Authenticate’: ‘Basic realm=“localhost”’, ‘X-AspNet-Version’: ‘4.0.30319’, ‘X-Powered-By’: ‘ASP.NET’, ‘X-Frame-Options’: ‘SAMEORIGIN’, ‘Date’: ‘Thu, 27 Jun 2024 11:41:48 GMT’, ‘Content-Length’: ‘1355’}

BODY: <s:Envelope xmlns:s=“http://schemas.xmlsoap.org/soap/envelope/”><s:Body><s:Fault>a:InternalServiceFaultValue cannot be null.&[xd](javascript:void(0); “xd”);

Parameter name: parametersValue cannot be null.&[xd](javascript:void(0); “xd”);

Parameter name: parameters at System.ServiceModel.Dispatcher.OperationFormatter.SerializeReply(MessageVersion messageVersion, Object[] parameters, Object result)&[xd](javascript:void(0); “xd”);

at System.ServiceModel.Dispatcher.DispatchOperationRuntime.SerializeOutputs(MessageRpc& rpc)&[xd](javascript:void(0); “xd”);

at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)&[xd](javascript:void(0); “xd”);

at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)&[xd](javascript:void(0); “xd”);

at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)&[xd](javascript:void(0); “xd”);

at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)System.ArgumentNullException</s:Fault></s:Body></s:Envelope>

=== REQUEST 2 ===

https://[REDACTED HOST]/ManagementServer/ServerCommandService.svc

{‘User-Agent’: ‘python-requests/2.25.1’, ‘Accept-Encoding’: ‘gzip, deflate’, ‘Accept’: ‘*/*’, ‘Connection’: ‘keep-alive’, ‘Content-Type’: ‘text/xml’, ‘SOAPAction’: ‘http://videoos.net/2/XProtectCSServerCommand/IServerCommandService/Login’, ‘Content-Length’: ‘247’, ‘Authorization’: ‘Basic [REDACTED CREDS]==’}

<s:Envelope xmlns:s=“http://schemas.xmlsoap.org/soap/envelope/”>

<s:Body>

099156bd-1b96-42b8-ab1f-f5628984de9a

</s:Body>

</s:Envelope>

=== RESPONSE 2 ===

URL: https://[REDACTED HOST]/ManagementServer/ServerCommandService.svc

STATUS CODE: 401

HEADERS: {‘Cache-Control’: ‘private’, ‘Content-Type’: ‘text/xml; charset=utf-8’, ‘Server’: ‘Microsoft-IIS/10.0’, ‘WWW-Authenticate’: ‘Basic realm=“localhost”’, ‘X-AspNet-Version’: ‘4.0.30319’, ‘X-Powered-By’: ‘ASP.NET’, ‘X-Frame-Options’: ‘SAMEORIGIN’, ‘Date’: ‘Thu, 27 Jun 2024 11:41:48 GMT’, ‘Content-Length’: ‘1355’}

BODY: <s:Envelope xmlns:s=“http://schemas.xmlsoap.org/soap/envelope/”><s:Body><s:Fault>a:InternalServiceFaultValue cannot be null.&[xd](javascript:void(0); “xd”);

Parameter name: parametersValue cannot be null.&[xd](javascript:void(0); “xd”);

Parameter name: parameters at System.ServiceModel.Dispatcher.OperationFormatter.SerializeReply(MessageVersion messageVersion, Object[] parameters, Object result)&[xd](javascript:void(0); “xd”);

at System.ServiceModel.Dispatcher.DispatchOperationRuntime.SerializeOutputs(MessageRpc& rpc)&[xd](javascript:void(0); “xd”);

at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)&[xd](javascript:void(0); “xd”);

at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)&[xd](javascript:void(0); “xd”);

at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)&[xd](javascript:void(0); “xd”);

at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)System.ArgumentNullException</s:Fault></s:Body></s:Envelope>