From 950e0200b28ac5b40a2eae8587aadf4d1c77b2f6 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 29 Aug 2023 01:32:08 +0300 Subject: [PATCH] [#3970,#4095]Feature/Docusign Authentication - Added oauth setup for docusign auth flow - Changed library for getting s3 document using requests package Signed-off-by: Harold Wanyama --- cla-backend/cla/models/docusign_models.py | 90 ++++++++++------------- cla-backend/requirements.txt | 1 + 2 files changed, 41 insertions(+), 50 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 44634971a..007639d34 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -17,14 +17,14 @@ import xml.etree.ElementTree as ET from typing import Any, Dict, List, Optional from urllib.parse import urlparse -import jwt -import requests -import time -import cla import pydocusign # type: ignore import requests from attr import dataclass +from docusign_esign import ApiClient +from pydocusign.exceptions import DocuSignException # type: ignore + +import cla from cla.controllers.lf_group import LFGroup from cla.models import DoesNotExist, signing_service_interface from cla.models.dynamo_models import (Company, Document, Event, Gerrit, @@ -34,7 +34,6 @@ from cla.user_service import UserService from cla.utils import (append_email_help_sign_off_content, get_corporate_url, get_email_help_content, get_project_cla_group_instance) -from pydocusign.exceptions import DocuSignException # type: ignore api_base_url = os.environ.get('CLA_API_BASE', '') root_url = os.environ.get('DOCUSIGN_ROOT_URL', '') @@ -107,47 +106,26 @@ def __init__(self): self.s3storage = None def initialize(self, config): - - expiration_time = int(time.time()) + 3600 # 1 hour - - payload = { - "iss": integrator_key, - "sub": user_id, - "aud": auth_server, - "scope": "signature_impersonation", - "exp": expiration_time - } - - cla.log.debug(f"jwt data claims: {payload}") - try: - #sign the JWT - cla.log.debug(f'private key: {private_key}') - encoded_jwt = jwt.encode(payload, private_key, algorithm='RS256') - - # Request an access token using the JWT - headers = { - 'Content-Type': 'application/x-www-form-urlencoded' - } - data = { - 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', - 'assertion': encoded_jwt, - } + self.api_client = ApiClient() + self.api_client.set_base_path(auth_server) + response = self.api_client.request_jwt_user_token( + client_id=integrator_key, + user_id=user_id, + oauth_host_name=auth_server, + private_key_bytes=private_key.encode(), + expires_in=3600, + scopes=['signature', 'impersonation'] + ) - cla.log.debug(f"docusign token_endpoint : {token_endpoint}") + token = response.access_token - response = requests.post(token_endpoint, headers=headers, data=data) - if response.status_code != 200: - cla.log.debug(f'response: {response.content} {response.status_code}') - response.raise_for_status() - access_token = response.json().get('access_token') - cla.log.debug(f"access_token for docusign: {access_token}") + cla.log.debug(f"token: {token}") - cla.log.debug("Initializing docusign ...") - self.client = pydocusign.DocuSignClient(root_url=root_url,oauth2_token=access_token) + self.client = pydocusign.DocuSignClient(root_url=root_url,oauth2_token=token) - except (Exception, requests.exceptions.HTTPError) as ex: + except (Exception) as ex: cla.log.error("Error authenticating Docusign: {}".format(ex)) return {'errors': {'Error authenticating Docusign'}} @@ -1399,16 +1377,26 @@ def populate_sign_url(self, signature, callback_url=None, emailBody='CLA Sign Request for {}'.format(user_identifier), supportedLanguage='en', ) - + + content_type = document.get_document_content_type() - if document.get_document_s3_url() is not None: - pdf = self.get_document_resource(document.get_document_s3_url()) - elif content_type.startswith('url+'): - pdf_url = document.get_document_content() - pdf = self.get_document_resource(pdf_url) - else: - content = document.get_document_content() - pdf = io.BytesIO(content) + try: + cla.log.debug(f'{fn} - {sig_type} - docusign document content type: {content_type}') + if document.get_document_s3_url() is not None: + pdf = self.get_document_resource(document.get_document_s3_url()) + elif content_type.startswith('url+'): + pdf_url = document.get_document_content() + pdf = self.get_document_resource(pdf_url) + else: + cla.log.debug(f'{fn} - getting document content...') + content = document.get_document_content() + pdf = io.BytesIO(content) + + except Exception as e: + cla.log.warning(f'{fn} - {sig_type} - error getting document resource: {e}') + return + + doc_name = document.get_document_name() cla.log.debug(f'{fn} - {sig_type} - docusign document ' @@ -1438,6 +1426,7 @@ def populate_sign_url(self, signature, callback_url=None, status=pydocusign.Envelope.STATUS_SENT, recipients=[signer]) + cla.log.debug(f'{fn} - {sig_type} - sending signature request to DocuSign...') envelope = self.prepare_sign_request(envelope) if not send_as_email: @@ -1995,7 +1984,8 @@ def get_document_resource(self, url): # pylint: disable=no-self-use :return: A resource that can be read()'d. :rtype: Resource """ - return urllib.request.urlopen(url) + return requests.get(url, stream=True).raw + # return urllib.request.urlopen(url) def prepare_sign_request(self, envelope): """ diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index d4c5f7658..aa23febf6 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -34,6 +34,7 @@ pluggy==0.13.1 py==1.10.0 pyasn1==0.4.8 pydocusign==2.2 +docusign-esign==3.23.0 PyGithub==1.55 PyJWT==2.7.0 pylint==1.5.2