"""ERS authentication and session helpers.Functions:- make_session: create a requests.Session with retries and headers- ers_login: log in to ERS (USGS) and return an authenticated session- ers_login_from_file: read credentials.json and log in- save_cookies_for_gdal: export session cookies to a Mozilla cookie file so GDAL/rasterio can stream protected assets without downloading"""from__future__importannotationsimportjson,os,timeimportrequestsfrombs4importBeautifulSoupfromrequests.adaptersimportHTTPAdapter,Retryfromhttp.cookiejarimportMozillaCookieJarfrom.typesimportCredentialsfrom.exceptionsimportAuthErrorERS_LOGIN_URL="https://ers.cr.usgs.gov/login/"USER_AGENT="llstac/0.1 (+python-requests)"
def_submit_login_form(s:requests.Session,username:str,password:str)->None:# Load login page to capture hidden inputsr=s.get(ERS_LOGIN_URL,timeout=30)r.raise_for_status()soup=BeautifulSoup(r.text,"html.parser")form=soup.find("form")ifformisNone:# Try direct post as fallbackdata={"username":username,"password":password}r=s.post(ERS_LOGIN_URL,data=data,timeout=30,allow_redirects=True)r.raise_for_status()returndata={}forinpinform.find_all("input"):name=inp.get("name")ifnotname:continuedata[name]=inp.get("value","")data["username"]=usernamedata["password"]=passwordaction=form.get("action")orERS_LOGIN_URLifaction.startswith("/"):action=requests.compat.urljoin(ERS_LOGIN_URL,action)r=s.post(action,data=data,timeout=30,allow_redirects=True)r.raise_for_status()
[docs]defers_login(username:str,password:str,token:str|None=None)->requests.Session:""" Log in to ERS and return an authenticated session. Args: username (str): USGS ERS username. password (str): USGS ERS password. token (str, optional): Optional bearer token for authorization header. Returns: requests.Session: Authenticated session with ERS cookies. Note: If token is provided and accepted by the host, it is attached as Authorization header. """s=make_session()_submit_login_form(s,username,password)# Heuristic settletime.sleep(0.3)iftoken:s.headers.update({"Authorization":f"Bearer {token}"})returns
[docs]defers_login_from_file(credentials_path:str="credentials.json")->requests.Session:""" Read credentials.json and perform ERS login. Args: credentials_path (str): Path to credentials JSON file. Returns: requests.Session: Authenticated session. Note: credentials.json schema:: { "username": "...", "password": "...", "token": null } """ifnotos.path.exists(credentials_path):raiseAuthError(f"Credentials file not found: {credentials_path}")withopen(credentials_path,"r")asf:creds:Credentials=json.load(f)username=creds.get("username")oros.environ.get("USGS_USER")password=creds.get("password")oros.environ.get("USGS_PASS")token=creds.get("token")ifnotusernameornotpassword:raiseAuthError("ERS username or password not provided")returners_login(username,password,token=token)
[docs]defsave_cookies_for_gdal(session:requests.Session,cookiefile:str="usgs_cookies.txt")->str:""" Save current session cookies into a Mozilla cookie file. Args: session (requests.Session): Authenticated session. cookiefile (str): Path to save cookie file. Returns: str: Absolute path to saved cookie file. Note: Use with GDAL via environment variables:: CPL_CURL_COOKIEFILE=cookiefile CPL_CURL_COOKIEJAR=cookiefile """cj=MozillaCookieJar(cookiefile)# Populate from the session's cookie jarforcinsession.cookies:# requests cookie to cookielib cookie (min fields)cj.set_cookie(requests.cookies.create_cookie(name=c.name,value=c.value,domain=c.domainor"landsatlook.usgs.gov",path=c.pathor"/",secure=c.secure))cj.save(ignore_discard=True,ignore_expires=True)returnos.path.abspath(cookiefile)