Skip to content

Commit ac1b13c

Browse files
authored
jwt console project (#48)
* jwt console project * removing commented lines * better message * updating redirect to dev center page * refactoring for using fewer files * fixing hard coding of demo doc path in example * only ask for consent if needed * removing unnecessary comments * adding note to consent dialog * removing flask dependency for jwt project
1 parent 101594a commit ac1b13c

7 files changed

Lines changed: 183 additions & 59 deletions

File tree

app/docusign/ds_client.py

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from ..ds_config import DS_CONFIG, DS_JWT
1212
from ..api_type import EXAMPLES_API_TYPE
1313
from ..error_handlers import process_error
14+
from ..jwt_helpers import get_jwt_token, get_private_key
1415

1516
SCOPES = [
1617
"signature"
@@ -99,22 +100,15 @@ def _jwt_auth(cls):
99100

100101
# Catch IO error
101102
try:
102-
private_key = cls._get_private_key().encode("ascii").decode("utf-8")
103+
private_key = get_private_key(DS_JWT["private_key_file"]).encode("ascii").decode("utf-8")
103104
except (OSError, IOError) as err:
104105
return render_template(
105106
"error.html",
106107
err=err
107108
)
108109

109110
try:
110-
cls.ds_app = api_client.request_jwt_user_token(
111-
client_id=DS_JWT["ds_client_id"],
112-
user_id=DS_JWT["ds_impersonated_user_id"],
113-
oauth_host_name=DS_JWT["authorization_server"],
114-
private_key_bytes=private_key,
115-
expires_in=4000,
116-
scopes=use_scopes
117-
)
111+
cls.ds_app = get_jwt_token(private_key, use_scopes, DS_JWT["authorization_server"], DS_JWT["ds_client_id"], DS_JWT["ds_impersonated_user_id"])
118112
return redirect(url_for("ds.ds_callback"))
119113

120114
except ApiException as err:
@@ -138,21 +132,6 @@ def _jwt_auth(cls):
138132
def destroy(cls):
139133
cls.ds_app = None
140134

141-
@staticmethod
142-
def _get_private_key():
143-
"""
144-
Check that the private key present in the file and if it is, get it from the file.
145-
In the opposite way get it from config variable.
146-
"""
147-
private_key_file = path.abspath(DS_JWT["private_key_file"])
148-
149-
if path.isfile(private_key_file):
150-
with open(private_key_file) as private_key_file:
151-
private_key = private_key_file.read()
152-
else:
153-
private_key = DS_JWT["private_key_file"]
154-
155-
return private_key
156135

157136
@classmethod
158137
def login(cls, auth_type):

app/eSignature/examples/eg002_signing_via_email.py

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,23 @@
22
from os import path
33

44
from docusign_esign import EnvelopesApi, EnvelopeDefinition, Document, Signer, CarbonCopy, SignHere, Tabs, Recipients
5-
from flask import session, request
65

76
from ...consts import demo_docs_path, pattern
8-
from ...docusign import create_api_client
9-
from ...ds_config import DS_CONFIG
7+
from ...jwt_helpers import create_api_client
108

119

1210
class Eg002SigningViaEmailController:
13-
@staticmethod
14-
def get_args():
15-
"""Get request and session arguments"""
16-
17-
# More data validation would be a good idea here
18-
# Strip anything other than characters listed
19-
signer_email = pattern.sub("", request.form.get("signer_email"))
20-
signer_name = pattern.sub("", request.form.get("signer_name"))
21-
cc_email = pattern.sub("", request.form.get("cc_email"))
22-
cc_name = pattern.sub("", request.form.get("cc_name"))
23-
envelope_args = {
24-
"signer_email": signer_email,
25-
"signer_name": signer_name,
26-
"cc_email": cc_email,
27-
"cc_name": cc_name,
28-
"status": "sent",
29-
}
30-
args = {
31-
"account_id": session["ds_account_id"],
32-
"base_path": session["ds_base_path"],
33-
"access_token": session["ds_access_token"],
34-
"envelope_args": envelope_args
35-
}
36-
return args
3711

3812
@classmethod
39-
def worker(cls, args):
13+
def worker(cls, args, doc_docx_path, doc_pdf_path):
4014
"""
4115
1. Create the envelope request object
4216
2. Send the envelope
4317
"""
4418

4519
envelope_args = args["envelope_args"]
4620
# 1. Create the envelope request object
47-
envelope_definition = cls.make_envelope(envelope_args)
21+
envelope_definition = cls.make_envelope(envelope_args, doc_docx_path, doc_pdf_path)
4822
api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"])
4923
# 2. call Envelopes::create API method
5024
# Exceptions will be caught by the calling function
@@ -56,7 +30,7 @@ def worker(cls, args):
5630
return {"envelope_id": envelope_id}
5731

5832
@classmethod
59-
def make_envelope(cls, args):
33+
def make_envelope(cls, args, doc_docx_path, doc_pdf_path):
6034
"""
6135
Creates envelope
6236
Document 1: An HTML document.
@@ -83,10 +57,10 @@ def make_envelope(cls, args):
8357
doc1_b64 = base64.b64encode(bytes(cls.create_document1(args), "utf-8")).decode("ascii")
8458
# read files 2 and 3 from a local directory
8559
# The reads could raise an exception if the file is not available!
86-
with open(path.join(demo_docs_path, DS_CONFIG["doc_docx"]), "rb") as file:
60+
with open(path.join(demo_docs_path, doc_docx_path), "rb") as file:
8761
doc2_docx_bytes = file.read()
8862
doc2_b64 = base64.b64encode(doc2_docx_bytes).decode("ascii")
89-
with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file:
63+
with open(path.join(demo_docs_path, doc_pdf_path), "rb") as file:
9064
doc3_pdf_bytes = file.read()
9165
doc3_b64 = base64.b64encode(doc3_pdf_bytes).decode("ascii")
9266

app/eSignature/views/eg002_signing_via_email.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,40 @@
33
from os import path
44

55
from docusign_esign.client.api_exception import ApiException
6-
from flask import render_template, session, Blueprint
6+
from flask import render_template, session, Blueprint, request
77

88
from ..examples.eg002_signing_via_email import Eg002SigningViaEmailController
99
from ...docusign import authenticate
1010
from ...ds_config import DS_CONFIG
1111
from ...error_handlers import process_error
12+
from ...consts import pattern
1213

1314
eg = "eg002" # reference (and url) for this example
1415
eg002 = Blueprint("eg002", __name__)
1516

17+
def get_args():
18+
"""Get request and session arguments"""
19+
20+
# More data validation would be a good idea here
21+
# Strip anything other than characters listed
22+
signer_email = pattern.sub("", request.form.get("signer_email"))
23+
signer_name = pattern.sub("", request.form.get("signer_name"))
24+
cc_email = pattern.sub("", request.form.get("cc_email"))
25+
cc_name = pattern.sub("", request.form.get("cc_name"))
26+
envelope_args = {
27+
"signer_email": signer_email,
28+
"signer_name": signer_name,
29+
"cc_email": cc_email,
30+
"cc_name": cc_name,
31+
"status": "sent",
32+
}
33+
args = {
34+
"account_id": session["ds_account_id"],
35+
"base_path": session["ds_base_path"],
36+
"access_token": session["ds_access_token"],
37+
"envelope_args": envelope_args
38+
}
39+
return args
1640

1741
@eg002.route("/eg002", methods=["POST"])
1842
@authenticate(eg=eg)
@@ -24,10 +48,11 @@ def sign_by_email():
2448
"""
2549

2650
# 1. Get required arguments
27-
args = Eg002SigningViaEmailController.get_args()
51+
#args = Eg002SigningViaEmailController.get_args()
52+
args = get_args()
2853
try:
2954
# 1. Call the worker method
30-
results = Eg002SigningViaEmailController.worker(args)
55+
results = Eg002SigningViaEmailController.worker(args, DS_CONFIG["doc_docx"], DS_CONFIG["doc_pdf"])
3156
except ApiException as err:
3257
return process_error(err)
3358

app/jwt_helpers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .jwt_helper import create_api_client, get_jwt_token, get_private_key

app/jwt_helpers/jwt_helper.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from docusign_esign import ApiClient
2+
from os import path
3+
4+
def get_jwt_token(private_key, scopes, auth_server, client_id, impersonated_user_id):
5+
"""Get the jwt token"""
6+
api_client = ApiClient()
7+
api_client.set_base_path(auth_server)
8+
response = api_client.request_jwt_user_token(
9+
client_id=client_id,
10+
user_id=impersonated_user_id,
11+
oauth_host_name=auth_server,
12+
private_key_bytes=private_key,
13+
expires_in=4000,
14+
scopes=scopes
15+
)
16+
return response
17+
18+
def get_private_key(private_key_path):
19+
"""
20+
Check that the private key present in the file and if it is, get it from the file.
21+
In the opposite way get it from config variable.
22+
"""
23+
private_key_file = path.abspath(private_key_path)
24+
25+
if path.isfile(private_key_file):
26+
with open(private_key_file) as private_key_file:
27+
private_key = private_key_file.read()
28+
else:
29+
private_key = private_key_path
30+
31+
return private_key
32+
33+
def create_api_client(base_path, access_token):
34+
"""Create api client and construct API headers"""
35+
api_client = ApiClient()
36+
api_client.host = base_path
37+
api_client.set_default_header(header_name="Authorization", header_value=f"Bearer {access_token}")
38+
39+
return api_client

jwt_config_sample.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
DS_JWT = {
2+
"ds_client_id": "{INTEGRATION_KEY_JWT}",
3+
"ds_impersonated_user_id": "{IMPERSONATED_USER_ID}", # The id of the user.
4+
"private_key_file": "./app/private.key", # Create a new file in your repo source folder named private.key then copy and paste your RSA private key there and save it.
5+
"authorization_server": "account-d.docusign.com",
6+
"doc_docx": "World_Wide_Corp_Battle_Plan_Trafalgar.docx",
7+
"doc_pdf": "World_Wide_Corp_lorem.pdf"
8+
}

jwt_console.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
from os import path
2+
import sys
3+
import subprocess
4+
5+
from docusign_esign import ApiClient
6+
from docusign_esign.client.api_exception import ApiException
7+
from app.jwt_helpers import get_jwt_token, get_private_key
8+
from app.eSignature.examples.eg002_signing_via_email import Eg002SigningViaEmailController
9+
from jwt_config import DS_JWT
10+
11+
# pip install DocuSign SDK
12+
subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'docusign_esign'])
13+
14+
SCOPES = [
15+
"signature", "impersonation"
16+
]
17+
18+
def get_consent_url():
19+
url_scopes = "+".join(SCOPES)
20+
21+
# Construct consent URL
22+
redirect_uri = "https://developers.docusign.com/platform/auth/consent"
23+
consent_url = f"https://{DS_JWT['authorization_server']}/oauth/auth?response_type=code&" \
24+
f"scope={url_scopes}&client_id={DS_JWT['ds_client_id']}&redirect_uri={redirect_uri}"
25+
26+
return consent_url
27+
28+
def get_token(private_key, api_client):
29+
30+
# Call request_jwt_user_token method
31+
token_response = get_jwt_token(private_key, SCOPES, DS_JWT["authorization_server"], DS_JWT["ds_client_id"], DS_JWT["ds_impersonated_user_id"])
32+
access_token = token_response.access_token
33+
34+
# Save API account ID
35+
user_info = api_client.get_user_info(access_token)
36+
accounts = user_info.get_accounts()
37+
api_account_id = accounts[0].account_id
38+
base_path = accounts[0].base_uri + "/restapi"
39+
40+
return {"access_token": access_token, "api_account_id": api_account_id, "base_path": base_path}
41+
42+
def get_args(api_account_id, access_token, base_path):
43+
signer_email = input("Please enter the signer's email address: ")
44+
signer_name = input("Please enter the signer's name: ")
45+
cc_email = input("Please enter the cc email address: ")
46+
cc_name = input("Please enter the cc name: ")
47+
48+
envelope_args = {
49+
"signer_email": signer_email,
50+
"signer_name": signer_name,
51+
"cc_email": cc_email,
52+
"cc_name": cc_name,
53+
"status": "sent",
54+
}
55+
args = {
56+
"account_id": api_account_id,
57+
"base_path": base_path,
58+
"access_token": access_token,
59+
"envelope_args": envelope_args
60+
}
61+
62+
return args
63+
64+
def run_example(private_key, api_client):
65+
jwt_values = get_token(private_key, api_client)
66+
args = get_args(jwt_values["api_account_id"], jwt_values["access_token"], jwt_values["base_path"])
67+
envelope_id = Eg002SigningViaEmailController.worker(args, DS_JWT["doc_docx"], DS_JWT["doc_pdf"])
68+
print("Your envelope has been sent.")
69+
print(envelope_id)
70+
71+
def main():
72+
73+
api_client = ApiClient()
74+
api_client.set_base_path(DS_JWT["authorization_server"])
75+
api_client.set_oauth_host_name(DS_JWT["authorization_server"])
76+
77+
private_key = get_private_key(DS_JWT["private_key_file"]).encode("ascii").decode("utf-8")
78+
79+
try:
80+
run_example(private_key, api_client)
81+
except ApiException as err:
82+
body = err.body.decode('utf8')
83+
84+
if "consent_required" in body:
85+
consent_url = get_consent_url()
86+
print("Open the following URL in your browser to grant consent to the application:")
87+
print(consent_url)
88+
consent_granted = input("Consent granted? Select one of the following: \n 1)Yes \n 2)No \n")
89+
if consent_granted == "1":
90+
run_example(private_key, api_client)
91+
else:
92+
sys.exit("Please grant consent")
93+
94+
else:
95+
process_error(err)
96+
97+
98+
main()

0 commit comments

Comments
 (0)