Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support automatic authentication #300

Merged
merged 7 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,24 @@ The only requirement to use this library is to open a free account with NASA [ED

### **Authentication**

Once you have an EDL account, you can authenticate using one of the following three methods:
By default, `earthaccess` with automatically look for your EDL account credentials in two locations:

1. Using a `.netrc` file
* Can use *earthaccess* to read your EDL credentials (username and password) from a `.netrc` file
2. Reading your EDL credentials from environment variables
* if available you can use environment variables **EARTHDATA_USERNAME** and **EARTHDATA_PASSWORD**
3. Interactively entering your EDL credentials
* You can be prompted for these credentials and save them to a `.netrc` file
1. A `~/.netrc` file
2. `EARTHDATA_USERNAME` and `EARTHDATA_PASSWORD` environment variables

If neither of these options are configured, you can authenticate by calling the `earthaccess.login()` method
and manually entering your EDL account credentials.

```python
import earthaccess

auth = earthaccess.login()

earthaccess.login()
```

Note you can pass `persist=True` to `earthaccess.login()` to have the EDL account credentials you enter
automatically saved to a `~/.netrc` file for future use.


Once you are authenticated with NASA EDL you can:

* Get a file from a DAAC using a `fsspec` session.
Expand Down
45 changes: 42 additions & 3 deletions earthaccess/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import logging
import threading
from importlib.metadata import version
from typing import Any

Expand All @@ -18,6 +20,8 @@
from .search import DataCollections, DataGranules
from .store import Store

logger = logging.getLogger(__name__)

__all__ = [
"login",
"search_datasets",
Expand All @@ -36,7 +40,42 @@
"Store",
]

__auth__ = Auth()
__store__: Any = None

__version__ = version("earthaccess")

_auth = Auth()
_store = None
_lock = threading.Lock()


def __getattr__(name): # type: ignore
MattF-NSIDC marked this conversation as resolved.
Show resolved Hide resolved
"""
Module-level getattr to handle automatic authentication when accessing
`earthaccess.__auth__` and `earthaccess.__store__`.

Other unhandled attributes raise as `AttributeError` as expected.
"""
global _auth, _store

if name == "__auth__" or name == "__store__":
with _lock:
if not _auth.authenticated:
for strategy in ["environment", "netrc"]:
try:
_auth.login(strategy=strategy)
except Exception as e:
logger.debug(
f"An error occurred during automatic authentication with {strategy=}: {str(e)}"
)
continue
else:
if not _auth.authenticated:
continue
MattF-NSIDC marked this conversation as resolved.
Show resolved Hide resolved
else:
_store = Store(_auth)
logger.debug(
f"Automatic authentication with {strategy=} was successful"
)
break
return _auth if name == "__auth__" else _store
else:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
33 changes: 18 additions & 15 deletions earthaccess/auth.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import getpass
import logging
import os
from netrc import NetrcParseError
from pathlib import Path
Expand All @@ -10,6 +11,8 @@

from .daac import DAACS

logger = logging.getLogger(__name__)


class SessionWithHeaderRedirection(requests.Session):
"""
Expand Down Expand Up @@ -76,7 +79,7 @@ def login(self, strategy: str = "netrc", persist: bool = False) -> Any:
an instance of Auth.
"""
if self.authenticated:
print("We are already authenticated with NASA EDL")
logger.debug("We are already authenticated with NASA EDL")
return self
if strategy == "interactive":
self._interactive(persist)
Expand All @@ -98,7 +101,7 @@ def refresh_tokens(self) -> bool:
if resp_tokens.ok:
self.token = resp_tokens.json()
self.tokens = [self.token]
print(
logger.debug(
f"earthaccess generated a token for CMR with expiration on: {self.token['expiration_date']}"
)
return True
Expand All @@ -111,7 +114,7 @@ def refresh_tokens(self) -> bool:
if resp_tokens.ok:
self.token = resp_tokens.json()
self.tokens.extend(self.token)
print(
logger.debug(
f"earthaccess generated a token for CMR with expiration on: {self.token['expiration_date']}"
)
return True
Expand All @@ -127,7 +130,7 @@ def refresh_tokens(self) -> bool:
if resp_tokens.ok:
self.token = resp_tokens.json()
self.tokens[0] = self.token
print(
logger.debug(
f"earthaccess generated a token for CMR with expiration on: {self.token['expiration_date']}"
)
return True
Expand Down Expand Up @@ -230,7 +233,7 @@ def _interactive(self, presist_credentials: bool = False) -> bool:
password = getpass.getpass(prompt="Enter your Earthdata password: ")
authenticated = self._get_credentials(username, password)
if authenticated:
print("Using user provided credentials for EDL")
logger.debug("Using user provided credentials for EDL")
if presist_credentials:
print("Persisting credentials to .netrc")
self._persist_user_credentials(username, password)
Expand All @@ -240,29 +243,29 @@ def _netrc(self) -> bool:
try:
my_netrc = Netrc()
except FileNotFoundError as err:
print(f"No .netrc found in {os.path.expanduser('~')}")
raise err
raise FileNotFoundError(
f"No .netrc found in {os.path.expanduser('~')}"
) from err
except NetrcParseError as err:
print("Unable to parse .netrc")
raise err
raise NetrcParseError("Unable to parse .netrc") from err
MattF-NSIDC marked this conversation as resolved.
Show resolved Hide resolved
if my_netrc["urs.earthdata.nasa.gov"] is not None:
username = my_netrc["urs.earthdata.nasa.gov"]["login"]
password = my_netrc["urs.earthdata.nasa.gov"]["password"]
else:
return False
authenticated = self._get_credentials(username, password)
if authenticated:
print("Using .netrc file for EDL")
logger.debug("Using .netrc file for EDL")
return authenticated

def _environment(self) -> bool:
username = os.getenv("EARTHDATA_USERNAME")
password = os.getenv("EARTHDATA_PASSWORD")
authenticated = self._get_credentials(username, password)
if authenticated:
print("Using environment variables for EDL")
logger.debug("Using environment variables for EDL")
else:
print(
logger.debug(
"EARTHDATA_USERNAME and EARTHDATA_PASSWORD are not set in the current environment, try "
"setting them or use a different strategy (netrc, interactive)"
)
Expand All @@ -279,7 +282,7 @@ def _get_credentials(
f"Authentication with Earthdata Login failed with:\n{token_resp.text}"
)
return False
print("You're now authenticated with NASA Earthdata Login")
logger.debug("You're now authenticated with NASA Earthdata Login")
self.username = username
self.password = password

Expand All @@ -288,13 +291,13 @@ def _get_credentials(

if len(self.tokens) == 0:
self.refresh_tokens()
print(
logger.debug(
f"earthaccess generated a token for CMR with expiration on: {self.token['expiration_date']}"
)
self.token = self.tokens[0]
elif len(self.tokens) > 0:
self.token = self.tokens[0]
print(
logger.debug(
f"Using token with expiration date: {self.token['expiration_date']}"
)
profile = self.get_user_profile()
Expand Down
Loading