CoSign API: Sending SOAP from Python client generated by suds - digital-signature

I need to access the signing appliance from Python. To this end, using suds, I generated a Python client from the WSDL at https://cosigndemo.arx.com:8080/sapiws/dss.asmx?wsdl .
I then used the generated client to build a simple signature request which more or less does the same as shown in the Java example provided by ARX (only, I'm asking for an invisible signature).
The problem is that when I send the request to the demo appliance, here's what I receive in return:
(DssSignResult){
Result =
(Result){
ResultMajor = "urn:oasis:names:tc:dss:1.0:resultmajor:ResponderError"
ResultMinor = "urn:oasis:names:tc:dss:1.0:resultminor:GeneralError"
ResultMessage =
(ResultMessage){
value = "Exception occured"
_lang = "en"
}
}
}
Here's the Python code I wrote:
from suds.client import Client
from suds.plugin import MessagePlugin
from suds.sax.attribute import Attribute
import logging
class MyPlugin(MessagePlugin):
def marshalled(self, context):
foo = context.envelope.getChild('ns2:Body').getChild('ns0:DssSign').getChild('ns0:SignRequest').getChild('ns1:InputDocuments').getChild('ns1:Document')
foo[0].attributes.append(Attribute('MimeType', 'application/pdf'))
print context.envelope
url = 'https://cosigndemo.arx.com:8080/sapiws/dss.asmx?wsdl'
client = Client(url, plugins=[MyPlugin()])
cid = client.factory.create("ns4:ClaimedIdentity")
cid.Name = "John Miller"
cid.SupportingInfo.LogonPassword = "12345678"
sigReq = client.factory.create("ns4:RequestBaseType")
sigReq._RequestID = "DummyRequestId"
sigReq.OptionalInputs.ClaimedIdentity = cid
sigReq.OptionalInputs.SignatureType="http://arx.com/SAPIWS/DSS/1.0/signature-field-create-sign"
sigReq.OptionalInputs.SAPISigFieldSettings._Name = "SigField"
sigReq.OptionalInputs.SAPISigFieldSettings._Invisible = "true"
sigReq.OptionalInputs.SAPISigFieldSettings._DependencyMode = "Independent"
sigReq.OptionalInputs.SAPISigFieldSettings._SignatureType = "Digital"
sigReq.OptionalInputs.SAPISigFieldSettings._EmptyFieldLabel = ""
sigReq.OptionalInputs.SAPISigFieldSettings._Page = "1"
sigReq.OptionalInputs.ReturnPDFTailOnly = "true"
sigReq.OptionalInputs.IncludeObject=None
sigReq.OptionalInputs.SignaturePlacement=None
doc = client.factory.create("ns4:DocumentType")
doc.Base64Data = open("/Users/mar/CoSignWSDL/factures-comptat217.pdf", "rb").read().encode("base64")
sigReq.InputDocuments.Document = doc
result = client.service.DssSign(sigReq)
print result
And here's the SOAP that (at least I think) it is sending to the appliance:
<SOAP-ENV:Envelope xmlns:ns3="http://arx.com/SAPIWS/DSS/1.0" xmlns:ns0="http://arx.com/SAPIWS/DSS/1.0/" xmlns:ns1="urn:oasis:names:tc:dss:1.0:core:schema" xmlns:ns2="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<ns2:Body>
<ns0:DssSign>
<ns0:SignRequest RequestID="DummyRequestId">
<ns1:OptionalInputs>
<ns1:ClaimedIdentity>
<ns1:Name>John Miller</ns1:Name>
<ns1:SupportingInfo>
<ns3:LogonPassword>12345678</ns3:LogonPassword>
</ns1:SupportingInfo>
</ns1:ClaimedIdentity>
<ns1:SignatureType>http://arx.com/SAPIWS/DSS/1.0/signature-field-create-sign</ns1:SignatureType>
<ns1:SAPISigFieldSettings Name="SigField" DependencyMode="Independent" SignatureType="Digital" Page="1" Invisible="true"/>
<ns1:ReturnPDFTailOnly>true</ns1:ReturnPDFTailOnly>
</ns1:OptionalInputs>
<ns1:InputDocuments>
<ns1:Document>
<ns1:Base64Data MimeType="application/pdf">...</ns1:Base64Data>
</ns1:Document>
</ns1:InputDocuments>
</ns0:SignRequest>
</ns0:DssSign>
</ns2:Body>
</SOAP-ENV:Envelope>
Note that I replaced the (very long) Base64Data content with "..." here for space reasons.
Why is it not working?
Update Problem solved thanks to the answer. Here is the working code that adds a sig to a PDF:
from suds.client import Client
from suds.plugin import MessagePlugin
from suds.sax.attribute import Attribute
from suds.bindings import binding
import logging
import xml.dom.minidom as minidom
import base64
class MyPlugin(MessagePlugin):
def marshalled(self, context):
documentNode = context.envelope.getChild('ns3:Body').getChild('ns0:DssSign').getChild('ns0:SignRequest').getChild('ns1:InputDocuments').getChild('ns1:Document')
documentNode[0].attributes.append(Attribute('MimeType', 'application/pdf'))
SAPISigFieldSettingsNode = context.envelope.getChild('ns3:Body').getChild('ns0:DssSign').getChild('ns0:SignRequest').getChild('ns1:OptionalInputs').getChild('ns1:SAPISigFieldSettings')
SAPISigFieldSettingsNode.setPrefix('ns2')
ReturnPDFTailOnlyNode = context.envelope.getChild('ns3:Body').getChild('ns0:DssSign').getChild('ns0:SignRequest').getChild('ns1:OptionalInputs').getChild('ns1:ReturnPDFTailOnly')
ReturnPDFTailOnlyNode.setPrefix('ns2')
signRequestNode = context.envelope.getChild('ns3:Body').getChild('ns0:DssSign').getChild('ns0:SignRequest')
signRequestNode.setPrefix('ns1')
binding.envns = ('SOAP-ENV', 'http://www.w3.org/2003/05/soap-envelope')
url = 'https://cosigndemo.arx.com:8080/sapiws/dss.asmx?wsdl'
client = Client(url, plugins=[MyPlugin()])
logging.basicConfig(level=logging.INFO)
logging.getLogger('suds.plugin').setLevel(logging.DEBUG)
cid = client.factory.create("ns4:ClaimedIdentity")
cid.Name = "John Miller"
cid.SupportingInfo.LogonPassword = "12345678"
sigReq = client.factory.create("ns4:RequestBaseType")
sigReq._RequestID = "DummyRequestId"
sigReq.OptionalInputs.ClaimedIdentity = cid
sigReq.OptionalInputs.SignatureType="http://arx.com/SAPIWS/DSS/1.0/signature-field-create-sign"
sigReq.OptionalInputs.SAPISigFieldSettings._Name = "SigField"
sigReq.OptionalInputs.SAPISigFieldSettings._Invisible = "true"
sigReq.OptionalInputs.SAPISigFieldSettings._DependencyMode = "Independent"
sigReq.OptionalInputs.SAPISigFieldSettings._SignatureType = "Digital"
sigReq.OptionalInputs.SAPISigFieldSettings._EmptyFieldLabel = ""
sigReq.OptionalInputs.SAPISigFieldSettings._Page = "1"
sigReq.OptionalInputs.ReturnPDFTailOnly = "true"
sigReq.OptionalInputs.IncludeObject=None
sigReq.OptionalInputs.SignaturePlacement=None
doc = client.factory.create("ns4:DocumentType")
f = open('/Users/mar/CoSignWSDL/factures-comptat217.pdf', 'r+b')
doc.Base64Data = f.read().encode("base64")
sigReq.InputDocuments.Document = doc
result = client.service.DssSign(sigReq)
signature = base64.b64decode(result.SignatureObject.Base64Signature.value)
f.seek(0, 2) #go to the end of the file
f.write(signature)
f.close()

Your SOAP request is passing ns0:SignRequest Should be: ns1:SignRequest
Also Bear in mind that there is a missing part in your code--checking the appliance's SSL certificate. It is not a must, but generally required in order to verify that the response wasn't sent by a fraudulent entity.
In .Net and Java it is done automatically when accessing web service in HTTPS (based on Microsoft certificate Store and Java certificate Store)- but as far as I see there is no equivalent in Python.

Related

including a scope in spotipy

I'm using spotipy to extract information from a playlist (specifically the ten most listened songs by the user) and I'm asked for a scope. I'm not sure where I should include the scope so that it gives me permission. I leave the code that I have written in case someone could help me.
import requests
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
scope= 'user-top-read'
client_credentials_manager =
SpotifyClientCredentials(client_id='6b78bf9e892b4e8184c8e906885108bc', client_secret='3c2c0c7374374ab98062f342860369a5')
sp = spotipy.Spotify(client_credentials_manager = client_credentials_manager(scope=scope)
playlist_link = "https://open.spotify.com/playlist/75bo0xrhJrSf0Gf0iqEPuS"
playlist_URI = playlist_link.split("/")[-1].split("?")[0]
track_uris = [x["track"]["uri"] for x in sp.playlist_tracks(playlist_URI)["items"]]
for track in sp.playlist_tracks(playlist_URI)["items"]:
#URI
track_uri = track["track"]["uri"]
#Track name
track_name = track["track"]["name"]
#Top ten artists
artist_uri = track["track"]["artists"][0]["uri"]
artist_info = sp.current_user_top_artists(limit=10, offset=0, time_range='medium_term')
print(artist_info)

How to fix unidentified character problem while passing data from TKinter to Photoshop via Python script?

I made a GUI Application which looks like this:
The ones marked red are Tkinter Text widgets and the ones marked yellow are Tkinter Entry widgets
After taking user input, the data is to be added to a PSD file and then rendered as an image. But Lets say, after taking the following data as input:
It renders the following Photoshop file:
How do I fix this issue that it does not recognize "\n" properly and hence the rendered document is rendered useless.
Here is the code which deals with converting of the accepted user data into strings and then adding it to Photoshop template and then rendering it:
def DataAdder2CSV():
global edate, eSNO, eage, egender, ename, ePID, econtact, ecomp, eallergy, ehistory, eR
e=edate.get()
a=eSNO.get()
d=eage.get()
f=egender.get()
b=ename.get()
c=ePID.get()
g=econtact.get()
h=ecomp.get(1.0,END)
i=eallergy.get(1.0,END)
j=ehistory.get(1.0,END)
k=eR.get(1.0,END)
data=[a,b,c,d,e,f,g,h,i,j,k]
file=open("Patient_Data.csv","a", newline="")
writer=csv.writer(file, delimiter=",")
writer.writerow(data)
file.close()
messagebox.showinfo("Prescription Generator", "Data has been saved to the database successfully!")
import win32com.client, os
objShell = win32com.client.Dispatch("WScript.Shell")
UserDocs = objShell.SpecialFolders("MyDocuments")
from tkinter import filedialog
ExpDir=filedialog.askdirectory(initialdir=UserDocs, title="Choose Destination Folder")
psApp = win32com.client.Dispatch("Photoshop.Application")
psApp.Open("D:\Coding\Python Scripts\Dr Nikhil Prescription App\Prescription Generator\Presc_Template.psd")
doc = psApp.Application.ActiveDocument
lf1 = doc.ArtLayers["name"]
tol1 = lf1.TextItem
tol1.contents = b
lf2 = doc.ArtLayers["age"]
tol2 = lf2.TextItem
tol2.contents = d
lf3 = doc.ArtLayers["gender"]
tol3 = lf3.TextItem
tol3.contents = f
lf4 = doc.ArtLayers["pid"]
tol4 = lf4.TextItem
tol4.contents = c
lf4 = doc.ArtLayers["date"]
tol4 = lf4.TextItem
tol4.contents = e
lf5 = doc.ArtLayers["contact"]
tol5 = lf5.TextItem
tol5.contents = g
lf6 = doc.ArtLayers["complaint"]
tol6 = lf6.TextItem
varH=" "+h.rstrip("\n")
tol6.contents =varH
lf7 = doc.ArtLayers["allergy"]
tol7 = lf7.TextItem
tol7.contents = i.rstrip("\n")
lf8 = doc.ArtLayers["history"]
tol8 = lf8.TextItem
varJ=" "+j.rstrip("\n")
tol8.contents =varJ
lf9 = doc.ArtLayers["R"]
tol9 = lf9.TextItem
tol9.contents = k.rstrip("\n")
options = win32com.client.Dispatch('Photoshop.ExportOptionsSaveForWeb')
options.Format = 13
options.PNG8 = False
pngfile =ExpDir+f"/{c}-{b}_({e}).png"
doc.Export(ExportIn=pngfile, ExportAs=2, Options=options)
messagebox.showinfo("Prescription Generator", "Prescription has been saved in the desired location successfully!")
There are 3 ways of expressing new line characters:
MacOS uses \r
Linux uses \n
Windows uses \r\n
Python and tkinter use \n but it looks like psApp.Application uses \r instead. That is why the document isn't rendered properly. For more info read the answers to this question.

Sign SAML Response in Python

I would need to sign a SAML assertion as it's possible to do on this page :
https://www.samltool.com/sign_response.php
From my side, I'm using Python, and my SAML is in a string.
What would you advise ?
Best regards,
Nico.
I finally made it using signxml lib.
Just to remind, I wanted to sign the SAML assertion.
I get the unsigned SAML from a file. Then I place this tag <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="placeholder"></ds:Signature> between Issuer tag and Subject tag, as it's recommended by signxml lib. And finally, I sign my SAML and verify the signature. Note that I changed the c14n_algorithm to be compatible with my services.
Here's the code :
import re
from lxml import etree
from signxml import XMLSigner, XMLVerifier
with open('saml_to_sign.xml', 'r') as file :
data_to_sign = file.read()
with open("/vagrant/my_cert.crt", "r") as cert,\
open("/vagrant/my_key.key", "r") as key:
certificate = cert.read()
private_key = key.read()
p = re.search('<Subject>', data_to_sign).start()
tmp_message = data_to_sign[:p]
tmp_message = tmp_message +\
'<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="placeholder"></ds:Signature>'
data_to_sign = tmp_message + data_to_sign[p:]
print(data_to_sign)
saml_root = etree.fromstring(data_to_sign)
signed_saml_root = XMLSigner(c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#")\
.sign(saml_root, key=private_key, cert=certificate)
verified_data = XMLVerifier().verify(signed_saml_root, x509_cert=certificate).signed_xml
signed_saml_root_str = etree.tostring(signed_saml_root, encoding='unicode')
print(signed_saml_root_str)

How to define FOR loop in python

Please I need help here. In the code below, I want the FOR loop to loop through all the accounts with the associated API KEYs and SECRETs listed and send bitcoins from them to the recipient email address one after the other as long as their balance is greater than zero:
#!/data/data/com.termux/files/usr/bin/python3.8
from coinbase.wallet.client import Client
import json
api_key1 = '<key>'
api_secret1 = '<secret>'
api_key2 = '<key>'
api_secret2 = '<secret>'
api_key3 = '<key>'
api_secret3 = '<secret>'
api_key4 = '<key>'
api_secret4 = '<secret>'
api_key5 = '<key>'
api_secret5 = '<secret>'
client = Client(api_key, api_secret)
accounts = client.get_accounts()['data']
for account in accounts:
sending_currency = account['currency']
if float(account['balance']['amount']) > 0:
#Send to other account
sending_account = client.get_account(account['id'])
sending_amount = account['balance']['amount']
print('sending %s %s from SENDER_EMAIL_ADDRESS' %(sending_amount, sending_currency))
sending_account.send_money(to = 'RECEPIENT_EMAIL_ADDRESS', amount = sending_amount, currency = sending_currency)
In order to attain what you have described, I would use a list of dictionaries, this will also be iterable and hence usable in the for loop. in order to do this I'd rewrite your code as such :
#!/data/data/com.termux/files/usr/bin/python3.8
from coinbase.wallet.client import Client
import json
credentials = [
{'api_key':'<key1>', 'api_secret':'<secret1>'},
{'api_key':'<key2>', 'api_secret':'<secret2>'},
{'api_key':'<key3>', 'api_secret':'<secret3>'}
........
]
while True:
for credential in credentials:
client = Client(credential['api_key'], credential['api_secret'])
accounts = client.get_accounts()['data']
for account in accounts:
sending_currency = account['currency']
if float(account['balance']['amount']) > 0:
#Send to other account
sending_account = client.get_account(account['id'])
sending_amount = account['balance']['amount']
print('sending %s %s from SENDER_EMAIL_ADDRESS' %(sending_amount, sending_currency))
sending_account.send_money(to = 'RECEPIENT_EMAIL_ADDRESS', amount = sending_amount, currency = sending_currency)
else:
........

AWS Cognito 90 day automated Password rotation

I have a requirement to create an automated password reset script. I created a custom field in order to try and track this and also hope I can access some of the standard fields. This script should find users with the following criteria:
The latest of any of the following 3 dates that are >= 90 days ago : Sign_Up, Forgot_Password, or custom:pwdCreateDate
I can't seem to find any boto3 cognito client ways of getting the information on this except for forgot password which shows up in admin_list_user_auth_events and that response doesn't include username in the response. I suppose since you provide username to get the events you can figure out a way to find the latest forgot password from the events and tie it to the username.
Has anyone else implemented any boto3 automation to set the account to force password reset based on any of these fields?
here is where i landed, take it with the understanding that coginito has some limitations which make true flawless password rotation difficult. Also know if you can make the script more efficient you should because in lambda you probably time out with more than about 350 users due to the 5RPS on the admin API.
Prerequisites : set the lambda function to 5 concurrency or you will exceed the limit of 5RPS. 1 mutable field in your cognito userpool attributes to put a date in. a custom lambda zip file that includes pandas saved to s3.
import os
import sys
# this adds the parent directory of bin so we can find the module
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))
sys.path.append(parent_dir)
#This addes venv lib/python2.7/site-packages/ to the search path
mod_path = os.path.abspath(parent_dir+"/lib/python"+str(sys.version_info[0])+"."+str(sys.version_info[1])+"/site-packages/")
sys.path.append(mod_path)
import boto3
import datetime
import pandas as pd
import time
current_path = os.path.dirname(os.path.realpath(__file__))
# Use this one for the parent directory
ENV_ROOT = os.path.abspath(os.path.join(current_path, os.path.pardir))
# Use this one for the current directory
#ENV_ROOT = os.path.abspath(os.path.join(current_path))
sys.path.append(ENV_ROOT)
#if __name__ == "__main__":
def lambda_handler(event, context):
user_pool_id = os.environ['USER_POOL_ID']
idp_client = boto3.client('cognito-idp')
users_list = []
page_token = None
dateToday = datetime.datetime.today().date()
def update_user(user) :
idp_client.admin_update_user_attributes(
UserPoolId = user_pool_id,
Username = user,
UserStatus = 'RESET_REQUIRED',
UserAttributes = [
{
'Name': 'custom:pwdCreateDate',
'Value': str(dateToday)
}
]
)
users = idp_client.list_users(
UserPoolId = user_pool_id
)
for user in users['Users']: users_list.append(user['Username'])
page_token = users['PaginationToken']
while 'PaginationToken' in users :
users = idp_client.list_users(
UserPoolId = user_pool_id,
PaginationToken = page_token
)
for user in users["Users"]: users_list.append(user["Username"])
if 'PaginationToken' in users :
page_token = users['PaginationToken']
attrPwdDates = []
for i in range(len(users_list)) :
userAttributes = idp_client.admin_get_user(
UserPoolId = user_pool_id,
Username = users_list[i]
)
for a in userAttributes['UserAttributes'] :
if a['Name'] == 'custom:pwdCreateDate' :
attrPwdDates.append(datetime.datetime.strptime(a['Value'], '%Y-%m-%d %H:%M:%S.%f').date())
time.sleep(1.0)
list_of_userattr_tuples = list(zip(users_list, attrPwdDates))
df1 = pd.DataFrame(list_of_userattr_tuples,columns = ['Username','Password_Last_Set'])
authPwdDates = []
for i in range(len(users_list)) :
authEvents = idp_client.admin_list_user_auth_events(
UserPoolId = user_pool_id,
Username = users_list[i]
)
for event in authEvents['AuthEvents'] :
if event['EventType'] == 'ForgotPassword' and event['EventResponse'] == 'Pass' :
authPwdDates.append(event['CreationDate'].date())
break
time.sleep(1.0)
list_of_userauth_tuples = list(zip(users_list, authPwdDates))
df2 = pd.DataFrame(list_of_userauth_tuples,columns = ['Username','Password_Last_Forgot'])
df3 = df1.merge(df2,how='left', on = 'Username')
df3[['Password_Last_Set','Password_Last_Forgot']] = df3[['Password_Last_Set','Password_Last_Forgot']].apply(pd.to_datetime)
cols = ['Password_Last_Set','Password_Last_Forgot']
df4 = df3.loc[df3[cols].max(axis=1)<=pd.Timestamp.now() - pd.Timedelta(90, unit='d'), 'Username']
for i,r in df4.iterrows() :
update_user(r['Username'])

Resources