| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- import time
- from dataclasses import dataclass
- from typing import Optional
- from otppy import OTP
- from selenium import webdriver
- from selenium.common.exceptions import ElementClickInterceptedException
- from selenium.common.exceptions import NoSuchElementException
- from selenium.common.exceptions import StaleElementReferenceException
- from selenium.common.exceptions import TimeoutException
- from selenium.webdriver.common.by import By
- from selenium.webdriver.firefox.options import Options as FirefoxOptions
- from selenium.webdriver.support import expected_conditions as EC
- from selenium.webdriver.support.ui import WebDriverWait
- @dataclass
- class VPNCookie:
- domain: str
- cookie: str
- def login(
- username: str,
- password: str,
- mfa_secret: Optional[str] = None,
- vpn_site: str = "https://vpn.fhnw.ch",
- headless: bool = True,
- ) -> VPNCookie:
- options = FirefoxOptions()
- if headless:
- options.add_argument("--headless")
- driver = webdriver.Firefox(options=options)
- driver.get(vpn_site)
- try:
- w = WebDriverWait(driver, 8)
- w.until(EC.presence_of_element_located((By.NAME, "loginfmt")))
- except TimeoutException:
- raise ValueError("Could not find login page!")
- driver.find_element(By.NAME, "loginfmt").send_keys(username)
- driver.find_element(By.NAME, "passwd").send_keys(password)
- click_continue(driver) # Confirm username
- click_continue(driver) # Confirm password
- # Check if we have to enter a MFA code
- if mfa_secret is not None:
- for _ in range(6):
- has_mfa_code_entry(driver)
- mfa_code = get_mfa_code(mfa_secret)
- driver.find_element(By.NAME, "otc").send_keys(mfa_code)
- click_continue(driver, "idSubmit_SAOTCC_Continue") # Confirm otp
- # Check for any errors
- try:
- w = WebDriverWait(driver, 2)
- w.until(
- EC.presence_of_element_located((By.ID, "idSpan_SAOTCC_Error_OTC"))
- )
- except TimeoutException:
- break
- click_continue(driver) # Confirm stay signed in
- # Wait until we have the install page
- try:
- w = WebDriverWait(driver, 8)
- w.until(EC.presence_of_element_located((By.ID, "provisioning_action_label")))
- except TimeoutException:
- raise ValueError("Could not find install page!")
- webvpn_cookie = driver.get_cookie("webvpn")
- driver.close()
- if webvpn_cookie is None:
- raise ValueError(
- "Failed to find the webvpn cookie. Maybe the authentication has failed?"
- )
- webvpn_domain = webvpn_cookie["domain"]
- webvpn_value = webvpn_cookie["value"]
- return VPNCookie(domain=webvpn_domain, cookie=webvpn_value)
- def click_continue(driver: webdriver.Firefox, btn_id: str = "idSIButton9") -> bool:
- """Returns true if still on login page, false if not"""
- for _ in range(16):
- try:
- driver.find_element(By.ID, btn_id).click()
- return True
- except (ElementClickInterceptedException, StaleElementReferenceException):
- pass
- except NoSuchElementException:
- if ".fhnw.ch" in driver.current_url:
- return False
- time.sleep(0.5)
- return ".fhnw.ch" not in driver.current_url
- def get_mfa_code(secret: str) -> str:
- otp = OTP.fromb32(secret)
- code: str = otp.TOTP()[0]
- return code
- def has_mfa_code_entry(driver: webdriver.Firefox) -> bool:
- try:
- w = WebDriverWait(driver, 8)
- w.until(EC.presence_of_element_located((By.NAME, "otc")))
- return True
- except TimeoutException:
- return False
|