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

Convenience method to allow customizing route dependencies #295

Merged
merged 19 commits into from
Feb 15, 2022

Conversation

alukach
Copy link
Contributor

@alukach alukach commented Nov 12, 2021

Related Issue(s): #

Description:

We're using stac-fastapi on a project and would like to add auth requirements to a number of endpoints. To the best of my knowledge, there's not an official way to do this in this codebase. This PR adds a convenience method (add_route_dependencies) to the main StacApi class. This allows us to do something like the following:

from fastapi import Depends, HTTPException, status, security
from stac_fastapi.api.app import StacApi

http_basic = security.HTTPBasic()

def must_be_bob(credentials: security. HTTPBasicCredentials = Depends(http_basic)):
    if credentials.username == 'bob':
        return True
    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="You're not Bob",
        headers={"WWW-Authenticate": "Basic"},
    )

api = StacApi(
    route_dependencies=[
        # Only Bob can edit our data...
        (
            [
                {"path": "/collections", "method": "POST"},
                {"path": "/collections", "method": "PUT"},
                {"path": "/collections/{collectionId}", "method": "DELETE"},
                {"path": "/collections/{collectionId}/items", "method": "POST"},
                {"path": "/collections/{collectionId}/items", "method": "PUT"},
                {"path": "/collections/{collectionId}/items/{itemId}", "method": "DELETE"},
            ],
            [Depends(must_be_bob)]
        ),
    ]
)

# Or, alternatively
api = StacApi()
api.add_route_dependencies(
    scopes=[
        {"path": "/collections", "method": "POST"},
        {"path": "/collections", "method": "PUT"},
        {"path": "/collections/{collectionId}", "method": "DELETE"},
        {"path": "/collections/{collectionId}/items", "method": "POST"},
        {"path": "/collections/{collectionId}/items", "method": "PUT"},
        {"path": "/collections/{collectionId}/items/{itemId}", "method": "DELETE"},
    ],
    dependencies=[Depends(must_be_bob)]
)

This manner of injecting dependencies into already-existing routes doesn't seem well documented in either the FastAPI documentation or this codebase, so I figure it may be of use to someone. It seems to work well and properly updates the OpenAPI spec as well:

image

I'm admittedly a little concerned that this is a bit heavy-handed (i.e. jamming more logic into that class), however wanted to present this solution as a starting point. I'm all ears if there's a better way to achieve our goal.

Update

To be clear about the status of this PR, I consider it still in the "proof-of-concept" stage. The following should occur before merging:

  • Get sign-off that this is of interest
  • Get agreement on interface
  • Write tests

PR Checklist:

  • Code is formatted and linted (run pre-commit run --all-files)
  • Tests pass (run make test)
  • Documentation has been updated to reflect changes, if applicable, and docs build successfully (run make docs)
  • Changes are added to the CHANGELOG.

Copy link
Member

@vincentsarago vincentsarago left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 looks good to me! I've taken the liberty to add this to TiTiler as well 🙏 @alukach

Copy link
Collaborator

@geospatial-jeff geospatial-jeff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really cool!

@vincentsarago
Copy link
Member

Can we add tests before merging 🙏

@alukach
Copy link
Contributor Author

alukach commented Nov 24, 2021

Sounds good. It sounds like we're fine with the idea and interface, so I'll go ahead and write some tests and maybe build out the docstring a bit more for better documentation.

@AsgerPetersen
Copy link

This is very handy indeed! Looking forward to having it in main! Thank you all.

@jonhealy1
Copy link
Collaborator

Cool yea!

@vincentsarago
Copy link
Member

@alukach do you think you could add tests and maybe some docs? It seems that people are interested having this merged in master 😄

@alukach
Copy link
Contributor Author

alukach commented Feb 15, 2022

Sorry about the delay all, I had forgotten about this PR 😬 (thanks for the ping, @vincentsarago!)

@vincentsarago @geospatial-jeff Added some tests in 146e28b. Can you run approve the workflow? I wanted them to be separate from the SqlAlchemy and the PgStac tests. To make them work, I had to create a dummy core client and dummy transaction client. I'm open to suggestions on how these tests can be cleaner.

@vincentsarago
Copy link
Member

thanks @alukach 🙏

@vincentsarago vincentsarago merged commit 1d58d23 into stac-utils:master Feb 15, 2022
@alukach alukach deleted the patch-1 branch February 15, 2022 22:13
@alukach
Copy link
Contributor Author

alukach commented Feb 15, 2022

@AsgerPetersen @jonhealy1 If either of you have a chance to try this out, let me know how it goes!

@keul
Copy link
Contributor

keul commented Oct 24, 2022

@alukach OK for authentication check (which is super cool) but… is it possible that this approach is not working for adding new query parameters to the defined route?

I mean: like in the FastAPI hello world example about dependencies: https://fastapi.tiangolo.com/tutorial/dependencies/#create-a-dependency-or-dependable

@alukach
Copy link
Contributor Author

alukach commented Oct 25, 2022

is it possible that this approach is not working for adding new query parameters to the defined route?

@keul Can you share more about what you're trying to do? There's a difference between having a dependency passed in as an argument to a FastAPI route (as you linked to) and dependencies inside a path operator (link). This PR does the later, it allows a dependency to be run before a route's logic is run, however you can't really do anything with the results. I believe that works as advertised, but I'm definitely open to the possibility that there may be a bug if you are able to provide a reproducible demonstration of it.

@keul
Copy link
Contributor

keul commented Oct 26, 2022

@alukach my need is to inject custom query parameters for the /collections endpoint (so, in the all_collections method).

As an example: what if I want to implement the collection pagination? https://github.com/radiantearth/stac-api-spec/tree/main/ogcapi-features#collection-pagination

But this is probably a topic for a new issue, sorry for the noise

@vincentsarago
Copy link
Member

@keul I guess you'll need to change this

def register_get_collections(self):
"""Register get collections endpoint (GET /collections).
Returns:
None
"""
self.router.add_api_route(
name="Get Collections",
path="/collections",
response_model=Collections
if self.settings.enable_response_models
else None,
response_class=self.response_class,
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["GET"],
endpoint=create_async_endpoint(
self.client.all_collections, EmptyRequest, self.response_class
),
)
on your app to add your page dependency (instead of EmptyRequest)

@alukach
Copy link
Contributor Author

alukach commented Oct 26, 2022

Yes, I agree with what @vincentsarago has said.

@keul When you say that you want to inject a custom query parameter, I suspect that you're leaving out the detail that you probably want to do something with that custom query parameter later. Assuming that is true, then you probably don't want to be augmenting an existing endpoint with the add_route_dependencies() convenience method and instead you probably want to be overriding an existing endpoint with a custom endpoint handler that implements the logic that you need.

But this is probably a topic for a new issue, sorry for the noise

Yeah, it may be best to open a discussion for something like this. You're welcome to mention my username if you'd like to get my attention for further conversation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants