Assert multiple methods being called, only constructor works - python

I'm trying to test the following function
def send_mail(config, message, raw_object):
smtp_config = config['handlers']['smtp']
session = smtplib.SMTP(smtp_config['host'], smtp_config['port'])
if smtp_config['tls']:
session.starttls()
session.login(smtp_config['from'], smtp_config['password'])
for to in smtp_config['to']:
mail = MIMEMultipart()
mail['From'] = smtp_config['from']
mail['To'] = to
mail['Subject'] = message
body = yaml.safe_dump(raw_object)
mail.attach(MIMEText(body, 'plain'))
try:
session.sendmail(smtp_config['from'], to, mail.as_string())
logging.info(f"Handler:SMTP {to}: {message}")
except smtplib.SMTPException as exc:
logging.error("SMTPException:")
logging.error(exc)
session.quit()
I have the following test
from unittest import TestCase
from unittest.mock import patch
from kubewatcher.handlers import send_mail
class Test(TestCase):
#patch("smtplib.SMTP")
def test_handle__send_mail(self, smtp):
from_ = "from"
password = "password"
host = "host"
port = 587
tls = True
to = ["to"]
config = {
"handlers": {
"smtp": {
"from": from_,
"password": password,
"host": host,
"port": port,
"tls": tls,
"to": to
}
}
}
message = "message"
raw_object = {}
send_mail(config, message, raw_object)
smtp.assert_called_once_with(host, port)
smtp.starttls.assert_called_once()
smtp.login.assert_called_once_with(from_, password)
The first assertion, smtp.assert_called_once_with(host, port), works just fine. But the entire test fails with the following error
...
AssertionError: Expected 'starttls' to have been called once. Called 0 times.

Here's the code you're testing:
session = smtplib.SMTP(smtp_config['host'], smtp_config['port'])
if smtp_config['tls']:
session.starttls()
session.login(smtp_config['from'], smtp_config['password'])
This test is working:
smtp = patch("smtplib.SMTP") # Sorta; this is just shorthand
smtp.assert_called_once_with(host, port)
The problem is here:
smtp.starttls.assert_called_once()
But it's actually correct for it to fail. Your code isn't calling smtplib.SMTP.starttls, but session.starttls, which is the thing that smtplib.SMTP returns.
You can work around that with something like:
from unittest.mock import patch, Mock
mock_session = Mock() # This is the thing we'll be inspecting later
smtp.return_value = mock_session # `smtplib.SMTP` will return this object
send_mail(config, message, raw_object)
smtp.assert_called_once_with(host, port)
mock_session.starttls.assert_called_once()
mock_session.login.assert_called_once_with(from_, password)

Related

LDAP python 3 (not working) but in PHP (working) where is the problem?

Python 3 code - not working:
import ldap3 as ldap
SERVER_NAME = 'serverip'
DN = 'dc=mydomain,dc=com,dc=br,ou=people'
USERNAME = 'no-reply'
PASSWORD = 'mypassword'
server = ldap.Server(SERVER_NAME,port=389, get_info='ALL')
connection = ldap.Connection(server, user='{}\\{}'.format(DN, USERNAME), password=PASSWORD)
connection.open()
if connection.bind():
print('Authenticated!')
else:
print('Not Authenticated')
print(connection.result)
This is print result
Not Authenticated
{'result': 34, 'description': 'invalidDNSyntax', 'dn': '', 'message': >'invalid DN', 'referrals': None, 'saslCreds': None, 'type': 'bindResponse'}
PHP code - working:
<?php
$username="no-reply";
$pass="mypassword";
$ldapconfig['host'] = 'serverip';
$ldapconfig['port'] = '389';
$ldapconfig['basedn'] = 'dc=mydomain,dc=com,dc=br';
$ldapconfig['usersdn'] = 'ou=people';
$ds=ldap_connect($ldapconfig['host'], $ldapconfig['port']);
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
$dn="uid=".$username.",".$ldapconfig['usersdn'].",".$ldapconfig['basedn'];
if ($bind=ldap_bind($ds, $dn, $pass)) {
$ldap_result = #ldap_search($ds, $dn, "uid=".$username);
$result = ldap_get_entries($ds, $ldap_result);
print_r($result);
} else {
print_r(new Exception("Error"));
}
?>
I'm writing Python 3 code to use LDAP, but authentication is not working. However, my old code written in PHP works successfully. Where is the problem in my Python code?
The key words in the error message is 'invalid DN'. It's complaining about the format of the username in the call to ldap.Connection. You're doing:
user='{}\\{}'.format(DN, USERNAME)
Which would result in this string:
"dc=mydomain,dc=com,dc=br,ou=people\no-reply"
But that's not a valid format. Because you're using the distinguished name (DN) of the domain, it's assuming the whole string is a DN, but then the \no-reply at the end throws it off.
Your PHP code is doing this:
$dn = "uid=" . $username . "," . $ldapconfig['usersdn'] . "," . $ldapconfig['basedn'];
Which will result in this string:
"uid=no-reply,ou=people,dc=mydomain,dc=com,dc=br"
So modify your Python code to use the same value for the username.
You may even get away with passing just the username ("no-reply"), depending on what your LDAP server is. It may or may not work:
connection = ldap.Connection(server, user=USERNAME, password=PASSWORD)
I managed to solve the problem, it was necessary to rewrite the code simplifying the commands.
from ldap3 import Server, Connection, ALL, SUBTREE
username = 'no-reply'
password = 'mypassword'
serverName = 'ip_address'
DC = 'ou=people,dc=domain,dc=com,dc=contry'
server = Server(serverName, get_info=ALL)
conn = Connection(server, 'uid='+username+','+DC, password, auto_bind=True)
conn.search(search_base = DC,
search_filter = '(uid='+username+')',
search_scope = SUBTREE,
attributes = ['cn', 'givenName', 'company', 'title' ],
paged_size = 5)
print(conn.entries)

How to test an api that accesses an external service?

I am creating an application that receives some parameters and sends an email using a company server, when I do the test using postman it works, but using the same parameters to perform a test happens an error and I can't debug, only 500 error appears, can i see the test django server log?
urls.py
urlpatterns = [
path('modules/email/send', views_email.email_send.as_view(), name='email_send'),
]
views_email.py
class email_send(APIView):
permission_classes = (IsAuthenticated,)
def post(self, request): # ver anexo e como carregar os componentes
try:
body = json.loads(request.body.decode('utf-8'))
fromaddr = body.get("from")
to = body.get("to")
cc = body.get("cc")
subject = body.get("subject")
level = body.get("level")
level_color = body.get("level_color")
alert_name = body.get("alert_name")
body_html = body.get('body_html')
#Arquivos html
index_file = body.get("index_file")
header_file = body.get("header_file")
footer_file = body.get("footer_file")
# body_file = body.get("body_file")
message = MIMEMultipart("alternative")
message["Subject"] = subject
message["From"] = fromaddr
message["To"] = to
message["CC"] = cc
....
part1 = MIMEText(text, "plain")
part2 = MIMEText(html_string, "html")
message.attach(part1)
message.attach(part2)
server = smtplib.SMTP('server.com.br', 25)
response = server.sendmail(fromaddr, toaddr, message.as_string())
if response == {}:
response = 200
else:
pass
return Response(response, content_type="text/plain", status=status.HTTP_200_OK)
except Exception as e:
return Response(str(e), content_type="text/plain", status=status.HTTP_400_BAD_REQUEST)
test_urls.py
from rest_framework.test import RequestsClient
from rest_framework.authtoken.models import Token
import requests
client = RequestsClient()
token = Token.objects.get(user__username='admin')
class EmailSendTest(TestCase):
def test_email_send(self):
headers = {
'Content-Type': "application/json",
'Authorization': "Token " + token.key
}
payload = {
"from": "#",
"to": "#",
"cc": "",
"subject": "Test views_email",
"alert_name": "Test views_email",
"level": "level",
"level_color": "default",
"index_file": "index.html",
"header_file": "header.html",
"footer_file": "footer.html",
"body_html": "Test"
}
response = client.post(
'http://testserver/motor-alertas/modules/email/send',
json=payload,
headers=headers,
)
self.assertEquals(response.content, 200)
I run the tests with the following command:
python3 -m pytest --cov=. && \
python3 -m coverage xml -i

DEVICE_PASSWORD_VERIFIER challenge response in Amazon Cognito using boto3 and warrant

I'm using both the boto3 and warrant libraries to try to get a device authenticated to skip multi-factor authentication after it's been recognized. I've got through a user/password auth but can't seem to figure out the right way to authenticate the device. The following is my code:
from warrant import aws_srp
from warrant.aws_srp import AWSSRP
import boto3
client = boto3.client('cognito-idp')
import datetime
username='xxx'
password='xxx'
client_id='xxx'
aws = AWSSRP(username=username, password=password, pool_id='xxx',
client_id=client_id, client=client)
auth_init = client.initiate_auth(
AuthFlow='USER_SRP_AUTH',
AuthParameters={
'USERNAME': username,
'SRP_A': aws_srp.long_to_hex(aws.large_a_value),
},
ClientId=client_id,
)
cr = aws.process_challenge(auth_init['ChallengeParameters'])
response = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName=auth_init['ChallengeName'],
ChallengeResponses=cr
)
response = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName='SMS_MFA',
Session=response['Session'],
ChallengeResponses={
'USERNAME': username,
'SMS_MFA_CODE':'xxx'
}
)
response_dev = client.confirm_device(
AccessToken=response['AuthenticationResult']['AccessToken'],
DeviceKey=response['AuthenticationResult']['NewDeviceMetadata']['DeviceKey'],
DeviceSecretVerifierConfig={
"PasswordVerifier": aws_srp.long_to_hex(aws.large_a_value),
"Salt": aws_srp.long_to_hex(aws.small_a_value)
}
)
response = client.update_device_status(
AccessToken=response['AuthenticationResult']['AccessToken'],
DeviceKey=device,
DeviceRememberedStatus='remembered'
)
Then on a clean session, do:
device='xxx'
auth_init = client.initiate_auth(
AuthFlow='USER_SRP_AUTH',
AuthParameters={
'USERNAME': username,
'SRP_A': aws_srp.long_to_hex(aws.large_a_value),
'DEVICE_KEY':device
},
ClientId=client_id,
)
cr = aws.process_challenge(auth_init['ChallengeParameters'])
cr['DEVICE_KEY'] = device
response = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName='DEVICE_SRP_AUTH',
ChallengeResponses={
'USERNAME': username,
'SRP_A': aws_srp.long_to_hex(aws.large_a_value),
'DEVICE_KEY': device,
'TIMESTAMP': datetime.datetime.utcnow().strftime( "%a %b %d %H:%M:%S UTC %Y").upper()
}
)
challenge_params = response['ChallengeParameters']
challenge_params['USER_ID_FOR_SRP'] = challenge_params['USERNAME']
cr2 = aws.process_challenge(challenge_params)
response2 = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName=response['ChallengeName'],
ChallengeResponses={
'USERNAME': username,
'PASSWORD_CLAIM_SIGNATURE': cr2['PASSWORD_CLAIM_SIGNATURE'],
'PASSWORD_CLAIM_SECRET_BLOCK': response['ChallengeParameters']['SECRET_BLOCK'],
'DEVICE_KEY': device,
'TIMESTAMP': datetime.datetime.utcnow().strftime( "%a %b %d %H:%M:%S UTC %Y").upper()
}
)
Everything seems to work properly until the last respond_to_auth_challenge which results in:
botocore.errorfactory.NotAuthorizedException: An error occurred (NotAuthorizedException) when calling the RespondToAuthChallenge operation: Incorrect username or password.
Should I be using a different User/pass for a DEVICE_PASSWORD_VERIFIER challenge that I haven't included? The documentation is a bit light, just saying:
DEVICE_PASSWORD_VERIFIER: Similar to PASSWORD_VERIFIER, but for devices only. source
The reason your solution did not work is because in order for device verifier and salt to work, they have to be calculated based on device_group_key and device_key. Also calculation of a challenge response for device authentication differs from the standard password SRP flow. Here is how it worked out for me:
First, confirm and remember the device (Amazon Cognito Identity SDK for JavaScript source):
from warrant import aws_srp
from warrant.aws_srp import AWSSRP
import boto3
import base64
import os
def generate_hash_device(device_group_key, device_key):
# source: https://github.com/amazon-archives/amazon-cognito-identity-js/blob/6b87f1a30a998072b4d98facb49dcaf8780d15b0/src/AuthenticationHelper.js#L137
# random device password, which will be used for DEVICE_SRP_AUTH flow
device_password = base64.standard_b64encode(os.urandom(40)).decode('utf-8')
combined_string = '%s%s:%s' % (device_group_key, device_key, device_password)
combined_string_hash = aws_srp.hash_sha256(combined_string.encode('utf-8'))
salt = aws_srp.pad_hex(aws_srp.get_random(16))
x_value = aws_srp.hex_to_long(aws_srp.hex_hash(salt + combined_string_hash))
g = aws_srp.hex_to_long(aws_srp.g_hex)
big_n = aws_srp.hex_to_long(aws_srp.n_hex)
verifier_device_not_padded = pow(g, x_value, big_n)
verifier = aws_srp.pad_hex(verifier_device_not_padded)
device_secret_verifier_config = {
"PasswordVerifier": base64.standard_b64encode(bytearray.fromhex(verifier)).decode('utf-8'),
"Salt": base64.standard_b64encode(bytearray.fromhex(salt)).decode('utf-8')
}
return device_password, device_secret_verifier_config
client = boto3.client('cognito-idp')
username='xxx'
password='xxx'
client_id='xxx'
client_secret='xxx'
pool_id='xxx'
# 1. Login with the password via standard SRP flow
aws = AWSSRP(username=username, password=password, pool_id=pool_id,
client_id=client_id, client_secret=client_secret, client=client)
auth_init = client.initiate_auth(
AuthFlow='USER_SRP_AUTH',
AuthParameters=aws.get_auth_params(),
ClientId=client_id,
)
cr = aws.process_challenge(auth_init['ChallengeParameters'])
response = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName=auth_init['ChallengeName'],
ChallengeResponses=cr
)
# 2. Get device_key and device_group_key returned after successful login
device_key = response['AuthenticationResult']['NewDeviceMetadata']['DeviceKey']
device_group_key = response['AuthenticationResult']['NewDeviceMetadata']['DeviceGroupKey']
# 3. Generate random device password, device salt and verifier
device_password, device_secret_verifier_config = generate_hash_device(device_group_key, device_key)
response_dev = client.confirm_device(
AccessToken=response['AuthenticationResult']['AccessToken'],
DeviceKey=device_key,
DeviceSecretVerifierConfig=device_secret_verifier_config,
DeviceName='some_device_name'
)
# 4. Remember the device
response_dev_upd = client.update_device_status(
AccessToken=response['AuthenticationResult']['AccessToken'],
DeviceKey=device_key,
DeviceRememberedStatus='remembered'
)
Then on a clean session you can login using device credentials (Amazon Cognito Identity SDK for JavaScript source):
import re
import datetime
import base64
import hmac
import hashlib
import boto3
from warrant import aws_srp
from warrant.aws_srp import AWSSRP
class AWSSRPDEV(AWSSRP):
# source: https://github.com/amazon-archives/amazon-cognito-identity-js/blob/6b87f1a30a998072b4d98facb49dcaf8780d15b0/src/CognitoUser.js#L498
def __init__(self, username, device_group_key, device_key, device_password,
client_id, client, region=None, client_secret=None):
self.username = username
self.device_group_key = device_group_key
self.device_key = device_key
self.device_password = device_password
self.client_id = client_id
self.client_secret = client_secret
self.client = client or boto3.client('cognito-idp', region_name=region)
self.big_n = aws_srp.hex_to_long(aws_srp.n_hex)
self.g = aws_srp.hex_to_long(aws_srp.g_hex)
self.k = aws_srp.hex_to_long(aws_srp.hex_hash('00' + aws_srp.n_hex + '0' + aws_srp.g_hex))
self.small_a_value = self.generate_random_small_a()
self.large_a_value = self.calculate_a()
def get_auth_params(self):
auth_params = super(AWSSRPDEV, self).get_auth_params()
auth_params['DEVICE_KEY'] = self.device_key
return auth_params
def get_device_authentication_key(self, device_group_key, device_key, device_password, server_b_value, salt):
u_value = aws_srp.calculate_u(self.large_a_value, server_b_value)
if u_value == 0:
raise ValueError('U cannot be zero.')
username_password = '%s%s:%s' % (device_group_key, device_key, device_password)
username_password_hash = aws_srp.hash_sha256(username_password.encode('utf-8'))
x_value = aws_srp.hex_to_long(aws_srp.hex_hash(aws_srp.pad_hex(salt) + username_password_hash))
g_mod_pow_xn = pow(self.g, x_value, self.big_n)
int_value2 = server_b_value - self.k * g_mod_pow_xn
s_value = pow(int_value2, self.small_a_value + u_value * x_value, self.big_n)
hkdf = aws_srp.compute_hkdf(bytearray.fromhex(aws_srp.pad_hex(s_value)),
bytearray.fromhex(aws_srp.pad_hex(aws_srp.long_to_hex(u_value))))
return hkdf
def process_device_challenge(self, challenge_parameters):
username = challenge_parameters['USERNAME']
salt_hex = challenge_parameters['SALT']
srp_b_hex = challenge_parameters['SRP_B']
secret_block_b64 = challenge_parameters['SECRET_BLOCK']
# re strips leading zero from a day number (required by AWS Cognito)
timestamp = re.sub(r" 0(\d) ", r" \1 ",
datetime.datetime.utcnow().strftime("%a %b %d %H:%M:%S UTC %Y"))
hkdf = self.get_device_authentication_key(self.device_group_key,
self.device_key,
self.device_password,
aws_srp.hex_to_long(srp_b_hex),
salt_hex)
secret_block_bytes = base64.standard_b64decode(secret_block_b64)
msg = bytearray(self.device_group_key, 'utf-8') + bytearray(self.device_key, 'utf-8') + \
bytearray(secret_block_bytes) + bytearray(timestamp, 'utf-8')
hmac_obj = hmac.new(hkdf, msg, digestmod=hashlib.sha256)
signature_string = base64.standard_b64encode(hmac_obj.digest())
response = {'TIMESTAMP': timestamp,
'USERNAME': username,
'PASSWORD_CLAIM_SECRET_BLOCK': secret_block_b64,
'PASSWORD_CLAIM_SIGNATURE': signature_string.decode('utf-8'),
'DEVICE_KEY': self.device_key}
if self.client_secret is not None:
response.update({
"SECRET_HASH":
self.get_secret_hash(username, self.client_id, self.client_secret)})
return response
client = boto3.client('cognito-idp')
username='xxx'
client_id='xxx'
client_secret='xxx'
device_key = 'xxx'
device_group_key = 'xxx'
device_password = 'xxx'
aws_dev = AWSSRPDEV(username=username,
device_group_key=device_group_key, device_key=device_key, device_password=device_password,
client_id=client_id, client_secret=client_secret, client=client)
# Note that device auth flow doesn't start with client.initiate_auth(),
# but rather with client.respond_to_auth_challenge() straight away
response_auth = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName='DEVICE_SRP_AUTH',
ChallengeResponses=aws_dev.get_auth_params()
)
cr = aws_dev.process_device_challenge(response_auth['ChallengeParameters'])
response_verifier = client.respond_to_auth_challenge(
ClientId=client_id,
ChallengeName='DEVICE_PASSWORD_VERIFIER',
ChallengeResponses=cr
)
Note that in my case Cognito client does have a client_secret, however the code above should potentially work if it doesn't.
To make things a little bit more consice you can use Pycognito python library
So in case you want to pass sms mfs (or software token mfs) challenge:
from pycognito import Cognito
from pycognito.exceptions import SoftwareTokenMFAChallengeException, SMSMFAChallengeException
client_id = 'client_id'
user_pool_id = 'pool_id'
username = 'username'
password = 'password'
user = Cognito(user_pool_id,client_id, username=username)
try:
user.authenticate(password)
except SoftwareTokenMFAChallengeException:
code = input("Enter the code from your authenticator app: ")
user.respond_to_software_token_mfa_challenge(code)
except SMSMFAChallengeException:
code = input("Enter the SMS code: ")
user.respond_to_sms_mfa_challenge(code)
print(f"My access token: {user.access_token}")

Python IMAP proxy connection

I tried to login to IMAP5 server through a SOCKS5 proxy using python 3.5, but it doesn't login and shows this error:
command: LOGIN => Autologout internal error, we will remember you as ffcd2fca-96a9-4c64-89d5-361123783232
If i am not using proxy, then everything is ok.
I have some questions
1.Is it because of the proxy server and it forbids IMAP4 connection?
2.How can I solve that?
import ssl, time
from socks import create_connection
from socks import PROXY_TYPE_SOCKS4
from socks import PROXY_TYPE_SOCKS5
from socks import PROXY_TYPE_HTTP
from imaplib import IMAP4
from imaplib import IMAP4_PORT
from imaplib import IMAP4_SSL_PORT
from filter import get_user_pass
__author__ = "sstevan"
__license__ = "GPLv3"
__version__ = "0.1"
class SocksIMAP4(IMAP4):
"""
IMAP service trough SOCKS proxy. PySocks module required.
"""
PROXY_TYPES = {"socks4": PROXY_TYPE_SOCKS4,
"socks5": PROXY_TYPE_SOCKS5,
"http": PROXY_TYPE_HTTP}
def __init__(self, host, port=IMAP4_PORT, proxy_addr=None, proxy_port=None,
rdns=True, username=None, password=None, proxy_type="socks5"):
self.proxy_addr = proxy_addr
self.proxy_port = proxy_port
self.rdns = rdns
self.username = username
self.password = password
self.proxy_type = SocksIMAP4.PROXY_TYPES[proxy_type.lower()]
IMAP4.__init__(self, host, port)
def _create_socket(self):
return create_connection((self.host, self.port), proxy_type=self.proxy_type, proxy_addr=self.proxy_addr,
proxy_port=self.proxy_port, proxy_rdns=self.rdns, proxy_username=self.username,
proxy_password=self.password)
class SocksIMAP4SSL(SocksIMAP4):
def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None, proxy_addr=None,
proxy_port=None, rdns=True, username=None, password=None, proxy_type="socks5"):
if ssl_context is not None and keyfile is not None:
raise ValueError("ssl_context and keyfile arguments are mutually "
"exclusive")
if ssl_context is not None and certfile is not None:
raise ValueError("ssl_context and certfile arguments are mutually "
"exclusive")
self.keyfile = keyfile
self.certfile = certfile
if ssl_context is None:
ssl_context = ssl._create_stdlib_context(certfile=certfile,
keyfile=keyfile)
self.ssl_context = ssl_context
SocksIMAP4.__init__(self, host, port, proxy_addr=proxy_addr, proxy_port=proxy_port,
rdns=rdns, username=username, password=password, proxy_type=proxy_type)
def _create_socket(self):
sock = SocksIMAP4._create_socket(self)
server_hostname = self.host if ssl.HAS_SNI else None
return self.ssl_context.wrap_socket(sock, server_hostname=server_hostname)
def open(self, host='', port=IMAP4_PORT):
SocksIMAP4.open(self, host, port)
def connect_proxy(imap_server, imap_port, proxy_addr, proxy_port, proxy_type, email, password):
mailbox = SocksIMAP4SSL(host=imap_server, port=imap_port,
proxy_addr=proxy_addr, proxy_port=proxy_port, proxy_type=proxy_type)
try:
mailbox.login(email, password)
print("We are here")
print("OK ",)
except Exception as e:
print(e)
return False
print(mailbox.state)
mailbox.logout()
return True
if __name__ == "__main__":
imap_server = "imap.rambler.ru"
imap_port = 993
proxy_addr = "188.120.224.172"
proxy_port = 59923
proxy_type = "socks5"
email, password = get_user_pass("pm#mail11.rambler.ru:11")
if email is not None:
resp = connect_proxy(imap_server, imap_port, proxy_addr, proxy_port, proxy_type, email, password)
#resp = connect(email, password, "smtp.rambler.ru")
time.sleep(1)
EMAIL:PASSWORD pair is for test. Don't steal it:)
As I understood it was either Rambler's bug or a tiny feature to prevent some malicious users, like I am, from brute forcing accounts.
If anyone would ever see this thread and have some solutions, ideas or such problem, feel free to contact me.

How to set headers properly for suds soap client

I am trying to send a request to an api using suds client.
The request is formed like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v20="https://API">
<soapenv:Header>
<v20:apiHeader>
<v20:username>username</v20:username>
<v20:password>password</v20:password>
<!--Optional:-->
<v20:name>?</v20:name>
</v20:apiHeader>
</soapenv:Header>
<soapenv:Body>
<v20:getLoginDetails/>
</soapenv:Body>
</soapenv:Envelope>
I am sending the request for this like:
client = Client('https://wsdl', faults=False)
client.set_options(headers={'username': username 'authToken': auth_token, 'name': ''})
client.service.getLoginDetails()
the error I am receiving is:
(<HTTPStatus.INTERNAL_SERVER_ERROR: 500>, (Fault){
faultcode = "soap:Server"
faultstring = "apiHeader should not be null"
detail = ""
})
Is this how I should be sending the request? It is definitely something to do with the apiHeader I think; not sure what though, I get the same error using this:
username = Element('username').setText(name)
password= Element('password').setText(pass)
header_list = [username, pass]
self.client.set_options(soapheaders=header_list)
return self.client.service.getLoginDetails()
#Code for your server:
from spyne import Application, rpc, ServiceBase, Unicode
from spyne.model.complex import ComplexModel
from spyne.protocol.soap import Soap11
from spyne.server.wsgi import WsgiApplication
import json
class RequestHeader(ComplexModel):
user_name = Unicode
user_pass = Unicode
class ServiceTest(ServiceBase):
__in_header__ = RequestHeader
#rpc(Unicode, _returns=Unicode)
def getLoginDetails(ctx, request):
values = json.loads(request)
values['user_name'] = ctx.in_header.user_name
values['user_pass'] = ctx.in_header.user_pass
return json.dumps(values)
application = WsgiApplication(Application([ServiceTest],
tns='yourcompany.request',
in_protocol=Soap11(validator='lxml'),
out_protocol=Soap11()
))
if __name__ == '__main__':
from wsgiref.simple_server import make_server
server = make_server('0.0.0.0', 8000, application)
server.serve_forever()
#Code for your client:
import json
from suds.client import Client
#Prepare the request in a JSON format
request = json.dumps({})
#Create a client for the API and make the request
api_client = Client('http://localhost:8000/?wsdl')
header = api_client.factory.create("RequestHeader")
header.user_name = 'my_user_name'
header.user_pass = 'my_user_pass'
api_client.set_options(soapheaders=header)
response = api_client.service.getLoginDetails(request)
print(response)
#it would print:
#{"user_name": "my_user_name", "user_pass": "my_user_pass"}
I've made this work by using something similar to this:
from suds.sax.element import Element
from suds.sax.attribute import Attribute
code = Element('serviceCode').setText('PABB4BEIJING')
pwd = Element('servicePwd').setText('QWERTPABB')
reqsoapheader = Element('ReqSOAPHeader').insert(code)
reqsoap_attribute = Attribute('xmlns', "http://schemas.acme.eu/")
reqsoapheader.append(reqsoap_attribute)
client.set_options(soapheaders=reqsoapheader)
If you wish to add several elements to the SOAP header:
userName = Element('UserName').setText(config['fco.user'])
password = Element('Password').setText(config['fco.pwd'])
fdxns = Attribute('xmlns', "http://fdx.co.il/Authentication")
for field in userName, password:
field.append(fdxns)
soapheaders = [userName, password]
client.set_options(soapheaders=tuple(soapheaders))
This is working for me with Python 2.7, suds 0.4.

Categories