connect.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import time
  2. from dataclasses import dataclass
  3. from typing import Optional
  4. from otppy import OTP
  5. from selenium import webdriver
  6. from selenium.common.exceptions import ElementClickInterceptedException
  7. from selenium.common.exceptions import NoSuchElementException
  8. from selenium.common.exceptions import StaleElementReferenceException
  9. from selenium.common.exceptions import TimeoutException
  10. from selenium.webdriver.common.by import By
  11. from selenium.webdriver.firefox.options import Options as FirefoxOptions
  12. from selenium.webdriver.support import expected_conditions as EC
  13. from selenium.webdriver.support.ui import WebDriverWait
  14. @dataclass
  15. class VPNCookie:
  16. domain: str
  17. cookie: str
  18. def login(
  19. username: str,
  20. password: str,
  21. mfa_secret: Optional[str] = None,
  22. vpn_site: str = "https://vpn.fhnw.ch",
  23. headless: bool = True,
  24. ) -> VPNCookie:
  25. options = FirefoxOptions()
  26. if headless:
  27. options.add_argument("--headless")
  28. driver = webdriver.Firefox(options=options)
  29. driver.get(vpn_site)
  30. try:
  31. w = WebDriverWait(driver, 8)
  32. w.until(EC.presence_of_element_located((By.NAME, "loginfmt")))
  33. except TimeoutException:
  34. raise ValueError("Could not find login page!")
  35. driver.find_element(By.NAME, "loginfmt").send_keys(username)
  36. driver.find_element(By.NAME, "passwd").send_keys(password)
  37. click_continue(driver) # Confirm username
  38. click_continue(driver) # Confirm password
  39. # Check if we have to enter a MFA code
  40. if mfa_secret is not None:
  41. for _ in range(6):
  42. has_mfa_code_entry(driver)
  43. mfa_code = get_mfa_code(mfa_secret)
  44. driver.find_element(By.NAME, "otc").send_keys(mfa_code)
  45. click_continue(driver, "idSubmit_SAOTCC_Continue") # Confirm otp
  46. # Check for any errors
  47. try:
  48. w = WebDriverWait(driver, 2)
  49. w.until(
  50. EC.presence_of_element_located((By.ID, "idSpan_SAOTCC_Error_OTC"))
  51. )
  52. except TimeoutException:
  53. break
  54. click_continue(driver) # Confirm stay signed in
  55. # Wait until we have the install page
  56. try:
  57. w = WebDriverWait(driver, 8)
  58. w.until(EC.presence_of_element_located((By.ID, "provisioning_action_label")))
  59. except TimeoutException:
  60. raise ValueError("Could not find install page!")
  61. webvpn_cookie = driver.get_cookie("webvpn")
  62. driver.close()
  63. if webvpn_cookie is None:
  64. raise ValueError(
  65. "Failed to find the webvpn cookie. Maybe the authentication has failed?"
  66. )
  67. webvpn_domain = webvpn_cookie["domain"]
  68. webvpn_value = webvpn_cookie["value"]
  69. return VPNCookie(domain=webvpn_domain, cookie=webvpn_value)
  70. def click_continue(driver: webdriver.Firefox, btn_id: str = "idSIButton9") -> bool:
  71. """Returns true if still on login page, false if not"""
  72. for _ in range(16):
  73. try:
  74. driver.find_element(By.ID, btn_id).click()
  75. return True
  76. except (ElementClickInterceptedException, StaleElementReferenceException):
  77. pass
  78. except NoSuchElementException:
  79. if ".fhnw.ch" in driver.current_url:
  80. return False
  81. time.sleep(0.5)
  82. return ".fhnw.ch" not in driver.current_url
  83. def get_mfa_code(secret: str) -> str:
  84. otp = OTP.fromb32(secret)
  85. code: str = otp.TOTP()[0]
  86. return code
  87. def has_mfa_code_entry(driver: webdriver.Firefox) -> bool:
  88. try:
  89. w = WebDriverWait(driver, 8)
  90. w.until(EC.presence_of_element_located((By.NAME, "otc")))
  91. return True
  92. except TimeoutException:
  93. return False