1+ from requests import Session
2+ from bs4 import BeautifulSoup , Tag
3+ import sys
4+ import re
5+
6+ import config
7+ import totp
8+
9+ def login_with_drexel_connect (session : Session ):
10+ response = session .get (config .drexel_connect_base_url )
11+ soup = BeautifulSoup (response .text , "html.parser" )
12+
13+ csrf_token = extract_csrf_token (soup )
14+ form_action_path = extract_form_action_path (soup )
15+
16+ login_payload = {
17+ "j_username" : config .drexel_username ,
18+ "j_password" : config .drexel_password ,
19+ "csrf_token" : csrf_token ,
20+ "_eventId_proceed" : ""
21+ }
22+
23+ # this should send the credentials and send the MFA request
24+ response = session .post (config .drexel_connect_base_url + form_action_path , data = login_payload )
25+
26+ soup = BeautifulSoup (response .text , "html.parser" )
27+ data = parse_initial_mfa_page (soup )
28+
29+ response = session .post (config .drexel_connect_base_url + data ["url" ], data = data ["form-data" ])
30+ json_response = response .json ()
31+
32+ data = {
33+ json_response ["csrfN" ]: json_response ["csrfV" ],
34+ "_eventId" : json_response ["actValue" ],
35+ }
36+
37+ response = session .post (config .drexel_connect_base_url + json_response ["flowExURL" ], data = data )
38+ soup = BeautifulSoup (response .text , "html.parser" )
39+
40+ parsed_data = parse_final_mfa_page (soup )
41+
42+ totp_code = totp .get_token (config .drexel_mfa_secret_key )
43+
44+ data = {
45+ "csrf_token" : parsed_data ["csrf_token" ],
46+ "_eventId" : "proceed" ,
47+ "j_mfaToken" : totp_code
48+ }
49+
50+ response = session .post (config .drexel_connect_base_url + parsed_data ["url" ], data = data )
51+
52+ return session
53+
54+ def extract_csrf_token (soup : BeautifulSoup ) -> str :
55+ csrf_token_input_tag = soup .find ("input" , {"name" : "csrf_token" })
56+
57+ if not isinstance (csrf_token_input_tag , Tag ):
58+ raise Exception ("Could not find CSRF token." )
59+
60+ csrf_token = csrf_token_input_tag ["value" ]
61+
62+ if not isinstance (csrf_token , str ):
63+ raise Exception (f"CSRF token was not a string. Found: { csrf_token } of type: { type (csrf_token )} " )
64+
65+ return csrf_token
66+
67+ def extract_form_action_path (soup : BeautifulSoup ) -> str :
68+ # the form is a child of a div with id "login-box"
69+ login_box_div = soup .find ("div" , {"id" : "login-box" })
70+
71+ if not isinstance (login_box_div , Tag ):
72+ raise Exception ("Could not find login box div." )
73+
74+ login_form = login_box_div .find ("form" )
75+
76+ if not isinstance (login_form , Tag ):
77+ raise Exception ("Could not find login form." )
78+
79+ form_action_path = login_form ["action" ]
80+
81+ if not isinstance (form_action_path , str ):
82+ raise Exception (f"Form action path was not a string. Found: { form_action_path } of type: { type (form_action_path )} " )
83+
84+ return form_action_path
85+
86+ def parse_initial_mfa_page (soup : BeautifulSoup ) -> dict [str , str ]:
87+ data = {}
88+
89+ # get the first script tag that isn't empty
90+ script_tag = soup .find ("script" , string = lambda text : text and len (text ) > 0 )
91+
92+ if not isinstance (script_tag , Tag ):
93+ raise Exception ("Could not find non-empty script tag." )
94+
95+ script_content = script_tag .string
96+
97+ if not isinstance (script_content , str ):
98+ raise Exception (f"Script content was not a string. Found: { script_content } of type: { type (script_content )} " )
99+
100+ url_match = re .search (r"url:\s*['\"](/idp/profile/cas/login\?execution=[^'\"]+)['\"]" , script_content )
101+ if not url_match :
102+ raise Exception ("Could not find MFA URL." )
103+
104+ event_id_match = event_id_match = re .search (r"data:\s*'_eventId=([^'&]+)&csrf_token" , script_content )
105+ if not event_id_match :
106+ raise Exception ("Could not find MFA event ID." )
107+
108+ csrf_token_match = re .search (r"csrf_token=([^'&]+)" , script_content )
109+ if not csrf_token_match :
110+ raise Exception ("Could not find MFA CSRF token." )
111+
112+ data ["url" ] = url_match .group (1 )
113+ data ["form-data" ] = {
114+ "_eventId" : event_id_match .group (1 ),
115+ "csrf_token" : csrf_token_match .group (1 )
116+ }
117+
118+
119+ return data
120+
121+ def parse_final_mfa_page (soup : BeautifulSoup ) -> dict [str , str ]:
122+ data : dict [str , str ] = {}
123+
124+ # get form by id "otp"
125+ form = soup .find ("form" , {"id" : "otp" })
126+
127+ if not isinstance (form , Tag ):
128+ raise Exception ("Could not find OTP form." )
129+
130+ url = form ["action" ]
131+
132+ if not isinstance (url , str ):
133+ raise Exception (f"Action was not a string. Found: { url } of type: { type (url )} " )
134+
135+ csrf_token_input = form .find ("input" , {"name" : "csrf_token" })
136+
137+ if not isinstance (csrf_token_input , Tag ):
138+ raise Exception ("Could not find CSRF token input." )
139+
140+ csrf_token = csrf_token_input ["value" ]
141+
142+ if not isinstance (csrf_token , str ):
143+ raise Exception (f"CSRF token was not a string. Found: { csrf_token } of type: { type (csrf_token )} " )
144+
145+ data ["url" ] = url
146+ data ["csrf_token" ] = csrf_token
147+
148+ return data
0 commit comments