Files
wevads-platform/scripts/gsuite_2fa_setup.py

364 lines
12 KiB
Python

#!/usr/bin/env python3
"""Setup 2FA on GSuite ifae.charity using 2fa.live for TOTP + Selenium"""
import time,json,os,re,traceback
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
import requests
EMAIL="weval@ifae.charity"
PASSWORD="yK6Rc98b4wJHz2"
SDIR="/opt/wevads/logs/selenium/screenshots"
os.makedirs(SDIR,exist_ok=True)
RESULTS={}
def log(m): print(f"[{datetime.now().strftime('%H:%M:%S')}] {m}",flush=True)
def ss(d,n): p=f"{SDIR}/gs_{n}_{int(time.time())}.png"; d.save_screenshot(p); log(f"SS: {p}"); return p
def get_driver():
opts=Options()
opts.add_argument("--headless=new")
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage")
opts.add_argument("--window-size=1920,1080")
opts.add_argument("--disable-blink-features=AutomationControlled")
opts.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
d=webdriver.Chrome(options=opts)
d.implicitly_wait(5)
return d
def step1_get_totp_from_2fa_live(d):
"""Get a TOTP secret from 2fa.live"""
log("STEP 1: Get TOTP secret from 2fa.live")
d.get("https://2fa.live/")
time.sleep(5)
ss(d,"01_2fa_live")
page=d.page_source
body=d.find_element(By.TAG_NAME,"body").text
log(f"2fa.live body: {body[:300]}")
# Look for secret key on the page
import pyotp
totp_secret=None
# Try to find base32 secret in page
for m in re.findall(r'[A-Z2-7]{16,64}',page):
try:
pyotp.TOTP(m).now()
totp_secret=m
log(f"Found TOTP secret: {m}")
break
except: continue
# Check inputs
if not totp_secret:
for inp in d.find_elements(By.TAG_NAME,"input"):
v=(inp.get_attribute("value") or "").strip()
if len(v)>=16 and re.match(r'^[A-Z2-7]+$',v):
try:
pyotp.TOTP(v).now()
totp_secret=v
log(f"TOTP from input: {v}")
break
except: continue
# Try text elements
if not totp_secret:
for el in d.find_elements(By.CSS_SELECTOR,"div,span,p,code,pre,td,input"):
txt=(el.text or el.get_attribute("value") or "").strip()
if 16<=len(txt)<=64 and re.match(r'^[A-Z2-7]+$',txt):
try:
pyotp.TOTP(txt).now()
totp_secret=txt
log(f"TOTP from element: {txt}")
break
except: continue
if not totp_secret:
# 2fa.live might need interaction - try clicking generate
for btn in d.find_elements(By.TAG_NAME,"button"):
if btn.is_displayed():
log(f"Clicking button: {btn.text}")
btn.click()
time.sleep(3)
break
ss(d,"01b_after_click")
page=d.page_source
for m in re.findall(r'[A-Z2-7]{16,64}',page):
try:
pyotp.TOTP(m).now()
totp_secret=m
log(f"Found TOTP after click: {m}")
break
except: continue
if not totp_secret:
# Generate our own
import pyotp
totp_secret=pyotp.random_base32()
log(f"Self-generated TOTP: {totp_secret}")
RESULTS["totp_secret"]=totp_secret
return totp_secret
def step2_google_login(d):
"""Login to Google with ifae.charity"""
log("STEP 2: Google Login")
d.get("https://accounts.google.com/signin")
time.sleep(4)
ss(d,"02_google_login")
# Enter email
email_input=WebDriverWait(d,15).until(EC.element_to_be_clickable((By.CSS_SELECTOR,"input[type='email']")))
email_input.send_keys(EMAIL)
email_input.send_keys(Keys.RETURN)
time.sleep(4)
ss(d,"02b_after_email")
# Enter password
pwd_input=WebDriverWait(d,15).until(EC.element_to_be_clickable((By.CSS_SELECTOR,"input[type='password']")))
pwd_input.send_keys(PASSWORD)
pwd_input.send_keys(Keys.RETURN)
time.sleep(5)
ss(d,"02c_after_pwd")
page=d.page_source.lower()
body=d.find_element(By.TAG_NAME,"body").text
log(f"After login: {body[:200]}")
# Check if 2FA already required
if "2-step" in page or "verification" in page or "verify" in page:
log("2FA already active - need current code or recovery")
RESULTS["2fa_already_active"]=True
return False
return True
def step3_enable_2fa(d, totp_secret):
"""Navigate to security settings and enable 2FA with TOTP"""
log("STEP 3: Enable 2FA")
import pyotp
# Go to security settings
d.get("https://myaccount.google.com/signinoptions/two-step-verification/enroll-welcome")
time.sleep(5)
ss(d,"03_2fa_enroll")
body=d.find_element(By.TAG_NAME,"body").text
log(f"2FA enroll page: {body[:300]}")
# Click Get Started / Next
for btn in d.find_elements(By.TAG_NAME,"button"):
txt=btn.text.lower()
if any(k in txt for k in ["get started","started","next","continue","commencer"]):
btn.click()
log(f"Clicked: {btn.text}")
time.sleep(4)
break
ss(d,"03b_after_start")
# May need to re-enter password
try:
pwd_input=d.find_element(By.CSS_SELECTOR,"input[type='password']")
if pwd_input.is_displayed():
pwd_input.send_keys(PASSWORD)
pwd_input.send_keys(Keys.RETURN)
time.sleep(4)
log("Re-entered password")
except: pass
ss(d,"03c_method_choice")
# Look for "Authenticator app" or "Can't use" option
body=d.find_element(By.TAG_NAME,"body").text
log(f"Method page: {body[:300]}")
# Click on authenticator app option if available
for el in d.find_elements(By.CSS_SELECTOR,"div,span,a,button,li"):
txt=el.text.lower()
if any(k in txt for k in ["authenticator","totp","app","another way","autre"]):
try:
el.click()
log(f"Clicked authenticator option: {el.text[:50]}")
time.sleep(3)
break
except: continue
ss(d,"03d_authenticator")
# Now should show QR code or setup key
# Look for "Can't scan it?" or "Enter setup key"
for el in d.find_elements(By.CSS_SELECTOR,"a,button,span,div"):
txt=el.text.lower()
if any(k in txt for k in ["can't scan","setup key","enter a setup","manual","clé"]):
try:
el.click()
log(f"Clicked manual entry: {el.text[:50]}")
time.sleep(3)
break
except: continue
ss(d,"03e_manual_key")
# Enter our TOTP secret key
# Find the input field for the key
for inp in d.find_elements(By.TAG_NAME,"input"):
itype=inp.get_attribute("type") or ""
if itype in ["text","tel"] and inp.is_displayed():
inp.clear()
inp.send_keys(totp_secret)
log(f"Entered TOTP secret in input")
time.sleep(1)
break
# Generate and enter verification code
code=pyotp.TOTP(totp_secret).now()
log(f"TOTP code: {code}")
# Click Next/Verify
for btn in d.find_elements(By.TAG_NAME,"button"):
txt=btn.text.lower()
if any(k in txt for k in ["next","verify","confirm","suivant","vérifier"]):
btn.click()
log(f"Clicked: {btn.text}")
time.sleep(4)
break
ss(d,"03f_verification")
# Enter code if needed
for inp in d.find_elements(By.TAG_NAME,"input"):
itype=inp.get_attribute("type") or ""
if itype in ["text","tel","number"] and inp.is_displayed():
inp.clear()
inp.send_keys(code)
log(f"Entered code: {code}")
break
# Confirm
for btn in d.find_elements(By.TAG_NAME,"button"):
txt=btn.text.lower()
if any(k in txt for k in ["verify","next","confirm","turn on","activer"]):
btn.click()
log(f"Clicked: {btn.text}")
time.sleep(5)
break
ss(d,"03g_2fa_done")
body=d.find_element(By.TAG_NAME,"body").text
log(f"After 2FA setup: {body[:300]}")
return True
def step4_gen_app_password(d):
"""Generate app password"""
log("STEP 4: Generate App Password")
d.get("https://myaccount.google.com/apppasswords")
time.sleep(5)
ss(d,"04_app_passwords")
body=d.find_element(By.TAG_NAME,"body").text
log(f"App passwords page: {body[:300]}")
# Enter app name
for inp in d.find_elements(By.TAG_NAME,"input"):
if inp.is_displayed() and inp.get_attribute("type") in ["text",""]:
inp.clear()
inp.send_keys("WEVADS-SMTP")
log("Entered app name")
break
# Click Create
for btn in d.find_elements(By.TAG_NAME,"button"):
txt=btn.text.lower()
if any(k in txt for k in ["create","generate","créer","générer"]):
btn.click()
log(f"Clicked: {btn.text}")
time.sleep(5)
break
ss(d,"04b_app_pwd_generated")
# Extract app password
body=d.find_element(By.TAG_NAME,"body").text
page=d.page_source
# Look for 16-char lowercase app password (xxxx xxxx xxxx xxxx)
app_pwd=None
matches=re.findall(r'[a-z]{4}\s[a-z]{4}\s[a-z]{4}\s[a-z]{4}',body+page)
if matches:
app_pwd=matches[0].replace(" ","")
log(f"APP PASSWORD: {app_pwd}")
if not app_pwd:
for el in d.find_elements(By.CSS_SELECTOR,"div,span,code,pre"):
txt=el.text.strip()
m=re.match(r'^[a-z]{4}\s[a-z]{4}\s[a-z]{4}\s[a-z]{4}$',txt)
if m:
app_pwd=txt.replace(" ","")
log(f"APP PASSWORD from element: {app_pwd}")
break
RESULTS["app_password"]=app_pwd
return app_pwd
def main():
log("="*50)
log("GSUITE ifae.charity - 2FA Setup + App Password")
log("="*50)
d=get_driver()
try:
totp=step1_get_totp_from_2fa_live(d)
if not totp:
log("FAIL: No TOTP secret"); return
ok=step2_google_login(d)
if not ok:
log("Login failed or 2FA already active")
# If 2FA already active, try to use existing TOTP
if RESULTS.get("2fa_already_active"):
log("2FA already active - skip to app password")
if ok:
step3_enable_2fa(d, totp)
app=step4_gen_app_password(d)
# Update DB
import psycopg2
db=psycopg2.connect(host="localhost",dbname="adx_system",user="admin",password="admin123")
db.autocommit=True
cur=db.cursor()
if app:
cur.execute("UPDATE admin.gsuite_accounts SET app_password=%s WHERE id=3",[app])
log(f"DB updated with app password: {app}")
if totp:
cur.execute("UPDATE admin.gsuite_accounts SET notes=%s WHERE id=3",
[json.dumps({"totp_secret":totp,"app_password":app,"updated":datetime.now().isoformat()})])
db.close()
RESULTS["status"]="completed"
with open("/opt/wevads/logs/selenium/ifae_results.json","w") as f:
json.dump(RESULTS,f,indent=2,default=str)
log("="*50)
log(f"TOTP: {RESULTS.get('totp_secret','N/A')}")
log(f"APP PWD: {RESULTS.get('app_password','N/A')}")
log(f"STATUS: {RESULTS.get('status')}")
log("="*50)
except Exception as e:
log(f"ERROR: {e}")
traceback.print_exc()
ss(d,"99_error")
finally:
d.quit()
if __name__=="__main__":
main()