Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
60db6d9
Fix project layout for flask debugging
michaeljb Mar 15, 2018
06ad86d
flake8 fixes
michaeljb Dec 12, 2018
3ff9429
Merged in python-upgrades (pull request #32)
michaeljb Dec 13, 2018
0114618
more mypy
michaeljb Feb 9, 2019
fd45ade
Merged in 430-stricter-mypy (pull request #39)
michaeljb Feb 11, 2019
4fbce9b
Prefix API order submission response "uri"
mfisher87 Feb 22, 2019
1bae4fe
Merged in EVEREST-430-apiv4 (pull request #38)
michaeljb Mar 6, 2019
a1e0a47
Add flake8-docstrings and lint
mfisher87 May 20, 2019
f6f6001
Merge branch 'flake8-docstring' into EVEREST-382-script-interface-fixes
mfisher87 May 20, 2019
b5f6c83
Merged in EVEREST-382-script-interface-fixes (pull request #67)
michaeljb May 20, 2019
baac63c
Merged in EVEREST-382-script-interface (pull request #66)
mfisher87 May 20, 2019
e9cf3de
Merged in EVEREST-382-PythonScript (pull request #63)
May 20, 2019
91ad4f1
Merged in HERMES-72-apache-proxy (pull request #99)
michaeljb Dec 2, 2019
91d1f77
Merged in phase-b (pull request #98)
michaeljb Jan 31, 2020
9e06719
Update nginx config for root response
trey-stafford Mar 6, 2025
a38702f
Remove trailing slash from get-links endpoint
trey-stafford Mar 6, 2025
dd4ca21
Add test endpoint for easy checking of service readiness
trey-stafford Mar 6, 2025
86ad9ed
Add reverse_proxy.py from hermes-api
trey-stafford Mar 7, 2025
36e9609
Pre-commit hooks after merging in reverse_proxy.py
trey-stafford Mar 7, 2025
734b8dc
Add ReverseProxied usage to flask app
trey-stafford Mar 7, 2025
db2665c
Get flask app secret key from envvar
trey-stafford Mar 11, 2025
ceb3d65
More specific type:ignore for mypy
trey-stafford Mar 11, 2025
213210b
Remove test endpoint
trey-stafford Mar 11, 2025
3790133
Remove trailing slash from auth endpoints; rename finish -> callback
trey-stafford Mar 11, 2025
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
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ repos:
- "@api.route"
# Ignore names of attrs in a ctx object in python_script.py that
# vulture incorrectly believes are unused.
# Also ignore typechecker imports in reverse_proxy.py.
- "--ignore-names"
- "check_hostname,verify_mode"
- "check_hostname,verify_mode,StartResponse,WSGIApplication,WSGIEnvironment,api_root"

- repo: https://github.com/codespell-project/codespell
rev: "v2.4.1"
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,16 @@ scripts/run_tests.sh

An example deep-link to initiate EDD downloads:

In dev:

```
earthdata-download://startDownload?getLinks=https://dev.hermes.trst2284.dev.int.nsidc.org/api/get-links?cmr_request_params=foo&downloadId=atl06_06&clientId=data_access_tool&authUrl=https://dev.hermes.trst2284.dev.int.nsidc.org/api/earthdata/auth?eddRedirect=earthdata-download%3A%2F%2FauthCallback
```

In integration:

```
earthdata-download://startDownload?getLinks=https://dev.hermes.trst2284.dev.int.nsidc.org/api/get-links?cmr_request_params=foo&downloadId=atl06_06&clientId=data_access_tool&authUrl=https://dev.hermes.trst2284.dev.int.nsidc.org/api/earthdata/auth/?eddRedirect=earthdata-download%3A%2F%2FauthCallback
earthdata-download://startDownload?getLinks=https://integration.nsidc.org/apps/data-access-tool/api/get-links?cmr_request_params=foo&downloadId=atl06_06&clientId=data_access_tool&authUrl=https://integration.nsidc.org/apps/data-access-tool/api/earthdata/auth?eddRedirect=earthdata-download%3A%2F%2FauthCallback
```

A button needs to be added to the Data Access Tool that will issue a GET request
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ services:
- EARTHDATA_APP_USERNAME
- EARTHDATA_APP_PASSWORD
- EARTHDATA_APP_CLIENT_ID
- DAT_FLASK_SECRET_KEY
networks:
- dat
dns_search: .
Expand Down
2 changes: 1 addition & 1 deletion nginx/dat.conf
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ server {

sendfile off;

location "/api" {
location "/" {
proxy_pass https://api;
proxy_read_timeout 90;
proxy_connect_timeout 90;
Expand Down
26 changes: 15 additions & 11 deletions src/dat_backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@
from werkzeug.wrappers import Response

from dat_backend.get_links import get_links
from dat_backend.reverse_proxy import ReverseProxied


app = Flask(__name__)
api = frx.Api(app)

app.wsgi_app = ReverseProxied(app.wsgi_app) # type: ignore[method-assign]

app.logger.setLevel(logging.INFO)

# TODO: regenerate and move into secrets storage.
secret_key = "87b8af58ade1d827860a52c25c55b0f75c8286195c62531a4cdc4bd152fe6116"
secret_key = os.environ.get("DAT_FLASK_SECRET_KEY")
app.secret_key = secret_key

RESPONSE_CODES = {
Expand Down Expand Up @@ -199,7 +201,7 @@ def post(self) -> Any:
# )


@api.route("/api/get-links/")
@api.route("/api/get-links")
class GetLinks(frx.Resource): # type: ignore[misc]

@api.response(200, "Success")
Expand Down Expand Up @@ -252,7 +254,7 @@ def get(self):
EARTHDATA_APP_PASSWORD = os.environ.get("EARTHDATA_APP_PASSWORD")


@api.route("/api/earthdata/auth/")
@api.route("/api/earthdata/auth")
class EarthdataAuth(frx.Resource): # type: ignore[misc]
@api.response(*RESPONSE_CODES[302]) # type: ignore[misc]
@api.response(*RESPONSE_CODES[500]) # type: ignore[misc]
Expand All @@ -269,10 +271,13 @@ def get(self) -> Response:
earthdata_authorize_url = "https://urs.earthdata.nasa.gov/oauth/authorize"
earthdata_authorize_url += "?client_id={0}".format(EARTHDATA_APP_CLIENT_ID)
earthdata_authorize_url += "&response_type=code"
edl_auth_finish_redirect_uri = url_for("earthdata_auth_finish", _external=True)
app.logger.info(f"Using {edl_auth_finish_redirect_uri=}")
edl_auth_callback_redirect_uri = url_for(
"earthdata_auth_callback",
_external=True,
)
app.logger.info(f"Using {edl_auth_callback_redirect_uri=}")
earthdata_authorize_url += "&redirect_uri={0}".format(
edl_auth_finish_redirect_uri
edl_auth_callback_redirect_uri
)

response = redirect(earthdata_authorize_url, code=302)
Expand All @@ -292,7 +297,7 @@ def earthdata_token_exchange(authorization_code: Optional[str]) -> Dict[str, Any
# TODO: This URL maybe needs to be parametrized like in Constants.py
earthdata_token_api = "https://urs.earthdata.nasa.gov/oauth/token"
grant_type = "authorization_code"
redirect_uri = url_for("earthdata_auth_finish", _external=True)
redirect_uri = url_for("earthdata_auth_callback", _external=True)

credentials = f"{EARTHDATA_APP_UID}:{EARTHDATA_APP_PASSWORD}"
auth = base64.b64encode(credentials.encode("ascii")).decode("ascii")
Expand All @@ -319,9 +324,8 @@ def earthdata_token_exchange(authorization_code: Optional[str]) -> Dict[str, Any
return authorization_result_json


# TODO: Consider renaming to `/login_callback/` or `/auth_callback/`
@api.route("/api/earthdata/auth_finish/")
class EarthdataAuthFinish(frx.Resource): # type: ignore[misc]
@api.route("/api/earthdata/auth_callback")
class EarthdataAuthCallback(frx.Resource): # type: ignore[misc]
@api.response(*RESPONSE_CODES[302]) # type: ignore[misc]
@api.response(*RESPONSE_CODES[500]) # type: ignore[misc]
def get(self) -> Response:
Expand Down
53 changes: 53 additions & 0 deletions src/dat_backend/reverse_proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from typing import Iterable, TYPE_CHECKING

# https://git.io/fhHMJ
if TYPE_CHECKING:
from wsgiref.types import StartResponse, WSGIApplication, WSGIEnvironment


class ReverseProxied(object):
"""Adapted from Flask Snippets.

https://web.archive.org/web/20190523102024/http://flask.pocoo.org/snippets/35/

Wrap the application in this middleware and configure the
front-end server to add these headers, to let you quietly bind
this to a URL other than / and to an HTTP scheme that is
different than what is used locally.

In nginx:
location /myprefix {
proxy_pass http://192.168.0.1:5001;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Script-Name /myprefix;
}

:param app: the WSGI application
"""

def __init__(self, app: "WSGIApplication"):
self.app = app
self.api_root = "/"

def __call__(
self, environ: "WSGIEnvironment", start_response: "StartResponse"
) -> Iterable[bytes]:
script_name = environ.get("HTTP_X_SCRIPT_NAME", "")
if script_name:
self.api_root = script_name
environ["SCRIPT_NAME"] = script_name
path_info = environ["PATH_INFO"]
if path_info.startswith(script_name):
environ["PATH_INFO"] = path_info[len(script_name) :]

scheme = environ.get("HTTP_X_FORWARDED_PROTO", "")
if scheme:
environ["wsgi.url_scheme"] = scheme

server = environ.get("HTTP_X_FORWARDED_HOST", "")
if server:
environ["HTTP_HOST"] = server

return self.app(environ, start_response)