def update_token(): print("Requesting an access token via JWT grant...", end='') private_key_bytes = str.encode(ds_config("DS_PRIVATE_KEY")) token = api_client.request_jwt_user_token( ds_config("DS_CLIENT_ID"), ds_config("DS_IMPERSONATED_USER_GUID"), aud(), private_key_bytes, TOKEN_EXPIRATION_IN_SECONDS) global account if account is None: account = get_account_info(api_client) base_uri = account['base_uri'] + '/restapi' api_client.host = base_uri api_client.token = token.access_token _token_received = True expiresTimestamp = (int(round(time.time())) + TOKEN_EXPIRATION_IN_SECONDS) print("\nDone. Continuing...")
def testToken(): try: if(ds_config("DS_CLIENT_ID") == "{CLIENT_ID}"): print(date() + "Problem: you need to configure this example, either via environment variables (recommended)\n" "or via the ds_configuration.js file.\n" "See the README file for more information\n") check_token() # An API problem except docusign.ApiException as e: print("\n\nDocuSign Exception!") # Special handling for consent_required body = e.body.decode('utf8') if("consent_required" in body): consent_scopes = "signature%20impersonation" consent_redirect_URL = "https://www.docusign.com" consent_url = "{}/oauth/auth?response_type=code&scope={}&client_id={}&redirect_uri={}".format(ds_config("DS_AUTH_SERVER"), consent_scopes, ds_config("DS_CLIENT_ID"),consent_redirect_URL) print(f"""\nC O N S E N T R E Q U I R E D Ask the user who will be impersonated to run the following url: {consent_url} It will ask the user to login and to approve access by your application. Alternatively, an Administrator can use Organization Administration to pre-approve one or more users.""") sys.exit(0) else: # Some other DocuSign API problem print (f" Reason: {e.reason}") print (f" Error response: {e.body.decode('utf8')}") sys.exit(0) # Not an API problem except Exception as e: print(date() + e)
def saveDoc(envelopeId, orderNumber): try: # api_client object created when checkToken() function was called in aws_worker api_client.set_default_header("Authorization", "Bearer " + api_client.token) accountID = get_account_id() envelope_api = EnvelopesApi(api_client) results_file = envelope_api.get_document(accountID , "combined" , envelopeId) # Create the output directory if needed output_directory = os.path.join(current_directory, r'output') if not os.path.exists(output_directory): os.makedirs(output_directory) if(not os.path.exists(output_directory)): print(date() + "Failed to create directory") filePath = os.path.join(current_directory, "output", ds_config("OUTPUT_FILE_PREFIX") + orderNumber + ".pdf") # Cannot create a file when file with the same name already exists if(os.path.exists(filePath)): # Remove the existing file os.remove(filePath) # Save the results file in the output directory and change the name of the file os.rename(results_file,filePath) # Create a file except ApiException as e: print(date() + "API exception: {}. saveDoc error".format(e)) # Catch exception while fetching and saving docs for envelope except Exception as e: print(date() + "Error while fetching and saving docs for envelope {}, order {}".format(envelopeId, orderNumber)) print(date() + "saveDoc error {}".format(e))
def processTest(test): # Exit the program if BREAK_TEST equals to true or if orderNumber contains "/break" if(ds_config("ENABLE_BREAK_TEST") == "True" and "/break" in ("" + test)): print(date() +"BREAKING worker test!") sys.exit(2) print(date() + "Processing test value {}".format(test)) # Create the test directory if needed test_directory = os.path.join(current_directory, r'test_messages') if not os.path.exists(test_directory): os.makedirs(test_directory) if(not os.path.exists(test_directory)): print(date() + "Failed to create directory") # First shuffle test1 to test2 (if it exists) and so on for i in range(9,0,-1): old_File_path = os.path.join(test_directory, "test" + str(i) + ".txt") new_File_path = os.path.join(test_directory, "test" + str(i+1) + ".txt") # If the old file exists if(os.path.exists(old_File_path)): # If the new file exists - remove it if(os.path.exists(new_File_path)): os.remove(new_File_path) # Rename the file name - only works if new_File_path does not exist os.rename(old_File_path, new_File_path) # The new test message will be placed in test1 - creating new file newFile= open(os.path.join(test_directory, "test1.txt"), "w+") newFile.write(test) print(date() + "New file created") newFile.close
def messageHandler(message): if(ds_config("DEBUG") == "True"): print(date() + "Processing message id: {}".format(message["MessageId"])) try: # Creates a Json object from the message body body = json.loads(message["Body"]) except Exception as e: body = False if(body): # Parse the information from message body. the information contains contains fields like test and xml test = body["test"] xml = body["xml"] process(test, xml) else: print(date() + "Null or bad body in message id {}. Ignoring.".format(message["MessageId"])) # Delete received message from queue sqs.delete_message(QueueUrl=ds_config("QUEUE_URL"),ReceiptHandle=message["ReceiptHandle"])
def get_account_info(client): client.host = ds_config("DS_AUTH_SERVER") response = client.call_api("/oauth/userinfo", "GET", response_type="object") if len(response) > 1 and 200 > response[1] > 300: raise Exception("can not get user info:") # %d".format(response[1]) accounts = response[0]['accounts'] target = ds_config("DS_TARGET_ACCOUNT_ID") if target is None or target == "FALSE": # Look for default for acct in accounts: if acct['is_default']: return acct # Look for specific account for acct in accounts: if acct['account_id'] == target: return acct raise Exception(f"\n\nUser does not have access to account {target}\n\n")
def startQueue(): # Maintain the array checkLogQ as a FIFO buffer with length 4. # When a new entry is added, remove oldest entry and shuffle. def addCheckLogQ(message): length = 4 # If checkLogQ size is smaller than 4 add the message if(checkLogQ.qsize() < length): checkLogQ.put(message) # If checkLogQ size is bigger than 4 else: # Remove the oldest message and add the new one checkLogQ.get() checkLogQ.put(message) # Prints all checkLogQ messages to the console def printCheckLogQ(): # Prints and Deletes all the elements in the checkLogQ for index in range(checkLogQ.qsize()): print(checkLogQ.get()) try: while(True): # Receive messages from queue, maximum waits for 20 seconds for message # receive_request - contain all the queue messages receive_request = (sqs.receive_message(QueueUrl=ds_config("QUEUE_URL"), WaitTimeSeconds=20, MaxNumberOfMessages=10)).get("Messages") addCheckLogQ(date() +"Awaiting a message...") # If receive_request is not None (when message is received) if(receive_request is not None): msgCount = len(receive_request) else: msgCount=0 addCheckLogQ(date() +"found {} message(s)".format(msgCount)) # If at least one message has been received if(msgCount): printCheckLogQ() for message in receive_request: messageHandler(message) # Catches all types of errors that may occur during the program except Exception as e: printCheckLogQ() print(date() + "Queue receive error: {}".format(e)) time.sleep(5) # Restart the program global restart restart = True
def send1(cls, test): try: time.sleep(0.5) url = ds_config("ENQUEUE_URL") + "?test=" + test request = urllib.request.Request(url) request.method = "GET" auth = RunTest.authObject() if(auth): base64string = base64.b64encode (bytes(auth, "utf-8")) request.add_header("Authorization", "Basic %s" % base64string.decode("utf-8")) response = urllib.request.urlopen(request) if(response.getcode() is not 200): print("send1: GET not worked, StatusCode= {}".format(response.getcode())) response.close() except Exception as e: print(f"send1: https error: {e}")
def process(test, xml): # Guarding against injection attacks # Check the incoming test variable to ensure that it ONLY contains the expected data (empty string "", "/break" or integers string) # matcher equals true when it finds wrong input pattern = "[^0-9]" matcher = re.search(pattern, test) validInput = test == "" or test == "/break" or not matcher if(validInput): if(not test == ""): # Message from test mode processTest(test) else: print(date() +"Wrong test value: {}".format(test)) print("test can only be: /break, empty string or integers string") # In test mode there is no xml sting, should be checked before trying to parse it if(not xml == ""): # Step 1. parse the xml root = ET.fromstring(xml) # get the namespace from the xml def get_namespace(element): ns = re.match(r'\{.*\}', element.tag) return ns.group(0) if ns else '' nameSpace = get_namespace(root) # Extract from the XML the fields values envelopeId = root.find(f'{nameSpace}EnvelopeStatus/{nameSpace}EnvelopeID').text subject = root.find(f'{nameSpace}EnvelopeStatus/{nameSpace}Subject').text senderName = root.find(f'{nameSpace}EnvelopeStatus/{nameSpace}UserName').text senderEmail = root.find(f'{nameSpace}EnvelopeStatus/{nameSpace}Email').text status = root.find(f'{nameSpace}EnvelopeStatus/{nameSpace}Status').text created = root.find(f'{nameSpace}EnvelopeStatus/{nameSpace}Created').text orderNumber = root.find(f'{nameSpace}EnvelopeStatus/{nameSpace}CustomFields/{nameSpace}CustomField/{nameSpace}Value').text if(status == "Completed"): completedMsg = "Completed: True" else: completedMsg = "" # For debugging, you can print the entire notification print("EnvelopeId: {}".format(envelopeId)) print("Subject: {}".format(subject)) print("Sender: {}, {}".format(senderName, senderEmail)) print("Order Number: {}".format(orderNumber)) print("Status: {}".format(status)) print("Sent: {}, {}".format(created, completedMsg)) # Step 2. Filter the notifications ignore = False # Guarding against injection attacks # Check the incoming orderNumber variable to ensure that it ONLY contains the expected data ("Test_Mode" or integers string) # Envelope might not have Custom field when orderNumber == None # matcher equals true when it finds wrong input matcher = re.search(pattern, orderNumber) validInput = orderNumber == "Test_Mode" or orderNumber == None or not matcher if(validInput): # Check if the envelope was sent from the test mode # If sent from test mode - ok to continue even if the status not equals to Completed if(not orderNumber == "Test_Mode"): if(not status == "Completed"): ignore = True if(ds_config("DEBUG") == "True"): print(date() +"IGNORED: envelope status is {}".format(status)) if(orderNumber == None or orderNumber == ""): ignore = True if(ds_config("DEBUG") == "True"): print(date() +"IGNORED: envelope does not have a {} envelope custom field.".format(ds_config("ENVELOPE_CUSTOM_FIELD"))) else: ignore = True print(date() + "Wrong orderNumber value: {}".format(orderNumber)) print("orderNumber can only be: Test_Mode or integers string") # Step 3. (Future) Check that this is not a duplicate notification # The queuing system delivers on an "at least once" basis. So there is a # chance that we have already processes this notification. # # For this example, we'll just repeat the document fetch if it is duplicate notification # Step 4 save the document - it can raise an exception which will be caught at startQueue if(not ignore): saveDoc(envelopeId, orderNumber)
def authObject(cls): if(not ds_config("BASIC_AUTH_NAME") is None and not ds_config("BASIC_AUTH_NAME") == "{BASIC_AUTH_NAME}" and not ds_config("BASIC_AUTH_PW") is None and not ds_config("BASIC_AUTH_PW") == "{BASIC_AUTH_PS}"): return ds_config("BASIC_AUTH_NAME") + ":" + ds_config("BASIC_AUTH_PW") else: return False
import docusign_esign as docusign import time import boto3 import json import queue import sys from date_pretty import date from process_notification import process from jwt_auth import * from ds_config_files import ds_config sqs = boto3.client('sqs', region_name=ds_config("QUEUE_REGION"), aws_access_key_id = ds_config("AWS_ACCOUNT"), aws_secret_access_key = ds_config("AWS_SECRET")) checkLogQ = queue.Queue() restart = True def main(): listenForever() # The function will listen forever, dispatching incoming notifications # to the processNotification library. # See https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/sqs-examples-send-receive-messages.html#sqs-examples-send-receive-messages-receiving def listenForever(): # Check that we can get a DocuSign token testToken() while(True): global restart if(restart): print(date() + "Starting queue worker") restart = False # Start the queue worker startQueue() time.sleep(5)