Skip to content

Commit

Permalink
Add support for basic auth for GeNode 3.3+ (GeoNode#223)
Browse files Browse the repository at this point in the history
Add support for basic auth for GeNode 3.3+
  • Loading branch information
Ricardo Garcia Silva authored Feb 7, 2022
1 parent c73ee5f commit 1dc0a9b
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 47 deletions.
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Improve compatibility with Python 3.7 when exporting SLD for raster layers
- Fix network access manager not using correct timeout for layer uploads
- Add support for HTTP Basic Auth when connecting to GeoNode deployments featuring version 3.3.0 or later


## [0.9.3] - 2022-01-20
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 51 additions & 12 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ In order to add a new GeoNode connection:
the GeoNode connection being created:

| Parameter | Description |
| --------- | ----------- |
|---------- | ----------- |
| Name | The name used by QGIS to refer to this connection |
| GeoNode URL | The base URL of the GeoNode being connected to (_e.g._ <https://stable.demo.geonde.org>) |
| Authentication | Whether to use authentication to connect to GeoNode or not. See the [Configuring authentication](#configuring-authentication) section below for more details on how to configure authenticated access to GeoNode |
| Authentication | Whether to use authentication to connect to GeoNode or not. See the [Configure authentication](#configure-authentication) section below for more details on how to configure authenticated access to GeoNode |
| Page size | How many search results per page shall be shown by QGIS. This defaults to `10` |

5. Optionally you may now click the `Test Connection` button. QGIS will then
Expand Down Expand Up @@ -65,20 +65,59 @@ connection. Upon acceptance of this dialogue, the connection will be removed.

### Configure authentication

The plugin is able to authenticate to remote GeoNode instances by using either HTTP Basic Auth (recommended) or OAuth2

#### Authentication with Basic Auth

![Basic Authentication example](images/user_guide/basic_auth_authentication_example.png)

Using HTTP Basic Auth is the recommended authentication method, as it is easier to set up. In order to
configure Basic Auth:

1. Open the main QGIS authentication settings dialogue by going to
`Settings -> Options...` in the main QGIS menu bar and then access the
`Authentication` section

2. Press the `Add new authentication configuration button`. A new dialogue is
shown. In this dialogue, fill in the following details:

| Parameter | Description |
|-----------|-------------|
| Name | The name used by QGIS to refer to the authentication configuration |
| Authentication type | Select the `Basic authentication` option from the dropdown |
| Username | Your GeoNode username |
| Password | Your GeoNode user password |

The remaining fields can be left at their default values

3. Now when [configuring a new GeoNode connection](#add-a-new-geonode-connection), select this newly created
authentication configuration in order to have the GeoNode connection use it


#### Authentication with OAuth2

This option is not recommended in most cases, since it involves a more advanced set up and also
requires requesting additional information from the remote GeoNode administrators.

!!! Note
Just in case you missed it, we recommend connecting to GeoNode using
[HTTP Basic Auth](#authentication-with-basic-auth) instead

This option may be viable when connecting to GeoNode using shared computing
resources, where you do not want to store your GeoNode user credentials locally.

!!! note
In order to be able to gain authenticated access to a GeoNode connection
you will need to request that one of the GeoNode administrators create an
**OAuth2** application and provide you with the following relevant details:
In order to be able to gain authenticated access to a GeoNode connection
via OAuth2 you will need to request that one of the GeoNode administrators
create an **OAuth2** application and provide you with the following relevant details:

- _Client ID_
- _Client Secret_

![Authentication example](images/user_guide/authentication_example.png)

The plugin is able to authenticate to remote GeoNode instances by using
OAuth2 authentication. Most OAuth2 grant types implemented in QGIS are
supported. We recommend using the `Authorization Code` grant type. In order
to configure such an authentication:
Most OAuth2 grant types implemented in QGIS are supported. We recommend using
the `Authorization Code` grant type. In order to configure such an authentication:

1. Open the main QGIS authentication settings dialogue by going to
`Settings -> Options...` in the main QGIS menu bar and then access the
Expand All @@ -98,7 +137,7 @@ to configure such an authentication:

The remaining fields can be left at their default values

4. Now when
3Now when
[configuring a new GeoNode connection](#add-a-new-geonode-connection),
select this newly created authentication configuration in order to have the
GeoNode connection use it
Expand Down Expand Up @@ -292,8 +331,8 @@ different actions. These are classified as a set of capabilities.
| MODIFY_LAYER_METADATA | >= 3.3.0 | Upload metadata fields of a loaded QGIS layer back to GeoNode |
| LOAD_VECTOR_LAYER_STYLE | >= 3.3.0 | Load SLD style onto QGIS when loading GeoNode dataset as a QGIS vector layer |
| LOAD_RASTER_LAYER_STYLE | - | Load SLD style onto QGIS when loading GeoNode dataset as QGIS raster layer |
| MODIFY_VECTOR_LAYER_STYLE | >= 3.3.0 | Upload vector layer symbology back to GeoNode |
| MODIFY_RASTER_LAYER_STYLE | >= 3.3.0 | Upload raster layer symbology back to GeoNode |
| MODIFY_VECTOR_LAYER_STYLE | >= 3.3.0 | Upload vector layer symbology back to GeoNode<br /><br />**NOTE**: This functionality is currently not supported when using HTTP Basic Authentication. Check <https://github.com/kartoza/qgis_geonode/issues/222> for more information |
| MODIFY_RASTER_LAYER_STYLE | >= 3.3.0 | Upload raster layer symbology back to GeoNode<br /><br />**NOTE**: This functionality is currently not supported when using HTTP Basic Authentication. Check <https://github.com/kartoza/qgis_geonode/issues/222> for more information |
| LOAD_VECTOR_DATASET_VIA_WMS | All | Load GeoNode vector dataset as a QGIS layer via OGC WMS |
| LOAD_VECTOR_DATASET_VIA_WFS | All | Load GeoNode vector dataset as a QGIS layer using via OGC WFS |
| LOAD_RASTER_DATASET_VIA_WMS | All | Load GeoNode raster dataset as a QGIS layer via OGC WMS |
Expand Down
107 changes: 74 additions & 33 deletions src/qgis_geonode/apiclient/geonode_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,15 +323,44 @@ def handle_layer_upload(self, result: bool):
"Could not upload layer to GeoNode"
)

def _get_sld_url(self, raw_style: typing.Dict) -> typing.Optional[str]:
auth_manager = qgis.core.QgsApplication.authManager()
auth_provider_name = auth_manager.configAuthMethodKey(self.auth_config).lower()
sld_url = raw_style.get("sld_url")
if auth_provider_name == "basic":
try:
sld_url = sld_url.replace("geoserver", "gs")
except AttributeError:
pass
return sld_url

def _get_service_urls(
self,
raw_links: typing.Dict,
dataset_type: models.GeonodeResourceType,
) -> typing.Dict[models.GeonodeService, str]:
result = {models.GeonodeService.OGC_WMS: _get_link(raw_links, "OGC:WMS")}
if dataset_type == models.GeonodeResourceType.VECTOR_LAYER:
result[models.GeonodeService.OGC_WFS] = _get_link(raw_links, "OGC:WFS")
elif dataset_type == models.GeonodeResourceType.RASTER_LAYER:
result[models.GeonodeService.OGC_WCS] = _get_link(raw_links, "OGC:WCS")
else:
log(f"Invalid dataset type: {dataset_type=}")
result = {}
auth_manager = qgis.core.QgsApplication.authManager()
auth_provider_name = auth_manager.configAuthMethodKey(self.auth_config).lower()
if auth_provider_name == "basic":
for service_type, retrieved_url in result.items():
try:
result[service_type] = retrieved_url.replace("geoserver", "gs")
except AttributeError:
pass
return result

def _get_common_model_properties(self, raw_dataset: typing.Dict) -> typing.Dict:
type_ = _get_resource_type(raw_dataset)
raw_links = raw_dataset.get("links", [])
if type_ == models.GeonodeResourceType.VECTOR_LAYER:
service_urls = _get_vector_service_urls(raw_links)
elif type_ == models.GeonodeResourceType.RASTER_LAYER:
service_urls = _get_raster_service_urls(raw_links)
else:
service_urls = {}
service_urls = self._get_service_urls(raw_links, type_)
raw_style = raw_dataset.get("default_style") or {}
return {
"pk": int(raw_dataset["pk"]),
Expand All @@ -353,7 +382,7 @@ def _get_common_model_properties(self, raw_dataset: typing.Dict) -> typing.Dict:
"keywords": [k["name"] for k in raw_dataset.get("keywords", [])],
"category": (raw_dataset.get("category") or {}).get("identifier"),
"default_style": models.BriefGeonodeStyle(
name=raw_style.get("name", ""), sld_url=raw_style.get("sld_url")
name=raw_style.get("name", ""), sld_url=self._get_sld_url(raw_style)
),
}

Expand Down Expand Up @@ -421,18 +450,48 @@ def build_search_query(
query.removeQueryItem(subtype_key)
return query

def _get_sld_url(self, raw_style: typing.Dict) -> typing.Optional[str]:
auth_manager = qgis.core.QgsApplication.authManager()
auth_provider_name = auth_manager.configAuthMethodKey(self.auth_config).lower()
sld_url = raw_style.get("sld_url")
if auth_provider_name == "basic":
try:
sld_url = sld_url.replace("geoserver", "gs")
except AttributeError:
pass
return sld_url

def _get_service_urls(
self,
raw_dataset: typing.Dict,
dataset_type: models.GeonodeResourceType,
) -> typing.Dict[models.GeonodeService, str]:
result = {
models.GeonodeService.OGC_WMS: raw_dataset["ows_url"],
}
if dataset_type == models.GeonodeResourceType.VECTOR_LAYER:
result[models.GeonodeService.OGC_WFS] = raw_dataset["ows_url"]
elif dataset_type == models.GeonodeResourceType.RASTER_LAYER:
result[models.GeonodeService.OGC_WCS] = raw_dataset["ows_url"]
else:
log(f"Invalid dataset type: {dataset_type=}")
result = {}
auth_manager = qgis.core.QgsApplication.authManager()
auth_provider_name = auth_manager.configAuthMethodKey(self.auth_config).lower()
if auth_provider_name == "basic":
for service_type, retrieved_url in result.items():
try:
result[service_type] = retrieved_url.replace("geoserver", "gs")
except AttributeError:
pass
return result

def _get_common_model_properties(self, raw_dataset: typing.Dict) -> typing.Dict:
type_ = {
"coverageStore": models.GeonodeResourceType.RASTER_LAYER,
"dataStore": models.GeonodeResourceType.VECTOR_LAYER,
}.get(raw_dataset.get("storeType"))
service_urls = {
models.GeonodeService.OGC_WMS: raw_dataset["ows_url"],
}
if type_ == models.GeonodeResourceType.VECTOR_LAYER:
service_urls[models.GeonodeService.OGC_WFS] = raw_dataset["ows_url"]
elif type_ == models.GeonodeResourceType.RASTER_LAYER:
service_urls[models.GeonodeService.OGC_WCS] = raw_dataset["ows_url"]
service_urls = self._get_service_urls(raw_dataset, type_)
raw_style = raw_dataset.get("default_style") or {}
return {
"pk": int(raw_dataset["pk"]),
Expand All @@ -452,7 +511,7 @@ def _get_common_model_properties(self, raw_dataset: typing.Dict) -> typing.Dict:
"keywords": [k["name"] for k in raw_dataset.get("keywords", [])],
"category": (raw_dataset.get("category") or {}).get("identifier"),
"default_style": models.BriefGeonodeStyle(
name=raw_style.get("name", ""), sld_url=raw_style.get("sld_url")
name=raw_style.get("name", ""), sld_url=self._get_sld_url(raw_style)
),
}

Expand Down Expand Up @@ -795,24 +854,6 @@ def _get_resource_type(
return result


def _get_vector_service_urls(
raw_links: typing.Dict,
) -> typing.Dict[models.GeonodeService, str]:
return {
models.GeonodeService.OGC_WMS: _get_link(raw_links, "OGC:WMS"),
models.GeonodeService.OGC_WFS: _get_link(raw_links, "OGC:WFS"),
}


def _get_raster_service_urls(
raw_links: typing.Dict,
) -> typing.Dict[models.GeonodeService, str]:
return {
models.GeonodeService.OGC_WMS: _get_link(raw_links, "OGC:WMS"),
models.GeonodeService.OGC_WCS: _get_link(raw_links, "OGC:WCS"),
}


def _get_spatial_extent(
geojson_polygon_geometry: typing.Dict,
) -> qgis.core.QgsRectangle:
Expand Down

0 comments on commit 1dc0a9b

Please sign in to comment.