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

cannot upload files via API #8541

Closed
ricardogsilva opened this issue Dec 22, 2021 · 2 comments · Fixed by #8543
Closed

cannot upload files via API #8541

ricardogsilva opened this issue Dec 22, 2021 · 2 comments · Fixed by #8543
Assignees
Labels
API v2 master security Pull requests that address a security vulnerability

Comments

@ricardogsilva
Copy link
Member

I'm working on the QGIS GeoNode plugin, hoping to implement upload of new datasets to GeoNode from whithin QGIS.

Unfortunately it seems to not be possible to use the GeoNode REST API to perform upload of files. This seems to be due to the REST API uploads following the same code path that was originally implemented for the django-based GUI.

I've traced down the problem to the decorator logged_in_or_basicauth(). This decorator is being applied to the geonode.upload.views.view() function, which is what the REST API is calling into.

The problem is that logged_in_or_basicauth tries to get the current user by calling django.contrib.auth.get_user(request) (find it here in the django docs) and fails because it cannot find the user. As mentioned in the linked documentation above, django.contrib.auth.get_user() tries to use the current django session to get the user. This cannot be applied in the context of the REST API, since it does not use sessions.

Steps to Reproduce the Problem

In order to reproduce this, you'd need to setup a dev instance of GeoNode, then configure an oauth application, then try to upload some sample file using the /api/v2/uploads/upload/ API endpoint. This is not a trivial setup though. This is the oauth2 config I am using:

image

Note that for now I'm using the Resource owner password-based authorization flow, which simplifies development. This is not related to the current issue though, as I have previously verified that this auth mode works OK when accessing other API endpoints.

Then I'm using the following code to get an access token and then perform an upload POST request:

import json
import typing
from pathlib import Path

import httpx

client = httpx.Client()

CLIENT_ID: typing.Final[str] = "DY4KBSDzHRC9PQHUBPODsrmobKeJXO5cO9uuoiv6"
CLIENT_SECRET: typing.Final[str] = (
    "7djkeaKvKkPcau3YRRCiGlBdGDNj6jSXrtDaIPkuJoElvglmOwhPaPqqvuX6hK7wVO1BVsBEtX"
    "59OGCfEizWkFCQNFNSXAQshNlfj02DjbgqngVgu8FGwoYD2VAvRDao"
)

BASE_URL: typing.Final[str] = "http://localhost"
API_URL: typing.Final[str] = f"{base_url}/api/v2"
TOKEN_URL: typing.Final[str] = f"{base_url}/o/token/"
USERINFO_URL: typing.Final[str] = f"{base_url}/o/userinfo/"
SAMPLE_DATA_BASE_PATH: typing.Final[Path] = Path.cwd().parent / "tests/sample-data"


def get_geonode_password_based_token(
    http_client: httpx.Client,
    token_url: str,
    client_id: str,
    client_secret: str,
    username: str,
    password: str
) -> typing.Optional[str]:
    response = http_client.post(
        token_url,
        data={
            "grant_type": "password",
            "username": username,
            "password": password
        },
        auth=(client_id, client_secret)
    )
    response.raise_for_status()
    return response.json()["access_token"]


access_token = get_geonode_password_based_token(
    client, TOKEN_URL, CLIENT_ID, CLIENT_SECRET, "admin", "admin")

shp_path = SAMPLE_DATA_BASE_PATH / "countries/ne_10m_admin_0_countries.shp"
dbf_path = SAMPLE_DATA_BASE_PATH / "countries/ne_10m_admin_0_countries.dbf"
prj_path = SAMPLE_DATA_BASE_PATH / "countries/ne_10m_admin_0_countries.prj"
shx_path = SAMPLE_DATA_BASE_PATH / "countries/ne_10m_admin_0_countries.shx"

with_data_response = client.post(
    f"{api_url}/uploads/upload/",
    headers={
        "Authorization": f"Bearer {access_token}"
    },
    files={
        "base_file": shp_path.read_bytes(),
        "dbf_file": dbf_path.read_bytes(),
        "shx_file": shx_path.read_bytes(),
        "prj_file": prj_path.read_bytes(),
    },
    data={
        "time": False,
        "mosaic": False,
        "abstract": "some abstract",
        "dataset_title": "some title",
        "permissions": json.dumps(
            {
                "users": {
                    "AnonymousUser": [
                        "view_resourcebase",
                        "download_resourcebase",
                    ],
                },
                "groups": {}
            },
        ),
        "metadata_uploaded_preserve": False,
        "metadata_upload_form": False,
        "style_upload_form": False,
        "charset": None,
    },
)
print(with_data_response.status_code)  # this outputs 401

The code above returns an HTTP status of 401 Unauthorized. I've been going through the code and found that this is generated by the aforementioned decorator function - the GeoNode view never gets to process the request.

Specifications

  • GeoNode version: master
  • Installation method (manual, GeoNode Docker, SPCGeoNode Docker): docker
@ricardogsilva
Copy link
Member Author

The fix for this seems simply a matter of replacing auth.get_user(request).is_authenticated with req.user.is_authenticated in the logged_in_or_basicauth and geonode.upload.views.view().

I haven't actually tried this change yet but I'm willing to test it and submit a PR with it

@giohappy
Copy link
Contributor

Ok @ricardogsilva let us know if the fix works.
It's good to see the Oauth2 based flow implemented. However, can you confirm that also a more simpler basic auth also works?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API v2 master security Pull requests that address a security vulnerability
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants