Skip to content

Commit

Permalink
Merge pull request #11574 from camptocamp/GSGGR-214-oidc-loggout
Browse files Browse the repository at this point in the history
Add possibility to add additional parameters to login end-point
  • Loading branch information
sbrunner authored Dec 13, 2024
2 parents 30d43ca + 7471022 commit aac6cc2
Show file tree
Hide file tree
Showing 21 changed files with 70 additions and 56 deletions.
8 changes: 4 additions & 4 deletions admin/c2cgeoportal_admin/views/layertree.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ def __init__(self, request: pyramid.request.Request):
self._request = request
self._dbsession = request.dbsession

@view_config(route_name="layertree", renderer="../templates/layertree.jinja2") # type: ignore
@view_config(route_name="layertree", renderer="../templates/layertree.jinja2") # type: ignore[misc]
def index(self) -> dict[str, int]:
node_limit = self._request.registry.settings["admin_interface"].get("layer_tree_max_nodes")
limit_exceeded = self._dbsession.query(LayergroupTreeitem).count() < node_limit
return {"limit_exceeded": limit_exceeded, "interfaces": self._dbsession.query(Interface).all()}

@view_config(route_name="layertree_children", renderer="fast_json") # type: ignore
@view_config(route_name="layertree_children", renderer="fast_json") # type: ignore[misc]
def children(self) -> list[dict[str, Any]]:
interface = self._request.params.get("interface", None)
group_id = self._request.params.get("group_id", None)
Expand Down Expand Up @@ -179,7 +179,7 @@ def _item_actions(self, item: TreeItem, parent_id: int | None = None) -> list[It

return actions

@view_config(route_name="layertree_unlink", request_method="DELETE", renderer="fast_json") # type: ignore
@view_config(route_name="layertree_unlink", request_method="DELETE", renderer="fast_json") # type: ignore[misc]
def unlink(self) -> dict[str, Any]:
group_id = self._request.matchdict.get("group_id")
item_id = self._request.matchdict.get("item_id")
Expand All @@ -195,7 +195,7 @@ def unlink(self) -> dict[str, Any]:
self._request.dbsession.flush()
return {"success": True, "redirect": self._request.route_url("layertree")}

@view_config(route_name="layertree_delete", request_method="DELETE", renderer="fast_json") # type: ignore
@view_config(route_name="layertree_delete", request_method="DELETE", renderer="fast_json") # type: ignore[misc]
def delete(self) -> DeleteResponse:
item_id = self._request.matchdict.get("item_id")
item = self._request.dbsession.query(TreeItem).get(item_id)
Expand Down
4 changes: 4 additions & 0 deletions doc/integrator/authentication_oidc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ Other options
``create_user``: If ``true``, a user will be create in the geomapfish database if not exists,
default is ``false``.

``login_extra_params``: Extra parameters to add to the login request.
See `Zitadel additional parameters <https://zitadel.com/docs/apis/openidoauth/endpoints#additional-parameters>`_.
Default is ``{}``.

``match_field``: The field to use to match the user in the database, can be ``username`` (default) or ``email``.

``update_fields``: The fields to update in the database, default is: ``[]``, allowed values are
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ mapping:
create_user:
type: bool
default: false
login_extra_params:
type: map
mapping:
regex;(.+):
type: str
match_field:
type: str
enum:
Expand Down
4 changes: 2 additions & 2 deletions geoportal/c2cgeoportal_geoportal/views/dev.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2011-2021, Camptocamp SA
# Copyright (c) 2011-2024, Camptocamp SA
# All rights reserved.

# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -49,7 +49,7 @@ def __init__(self, request: pyramid.request.Request):
super().__init__(request)
self.dev_url = self.request.registry.settings["devserver_url"]

@view_config(route_name="dev") # type: ignore
@view_config(route_name="dev") # type: ignore[misc]
def dev(self) -> pyramid.response.Response:
path = self.THEME_RE.sub("", self.request.path_info)
if self.request.path.endswith("/dynamic.js"):
Expand Down
2 changes: 1 addition & 1 deletion geoportal/c2cgeoportal_geoportal/views/dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def _interface(

return constants

@view_config(route_name="dynamic", renderer="json") # type: ignore
@view_config(route_name="dynamic", renderer="json") # type: ignore[misc]
def dynamic(self) -> dict[str, Any]:
is_allowed_host(self.request)

Expand Down
2 changes: 1 addition & 1 deletion geoportal/c2cgeoportal_geoportal/views/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def get_apijs(api_filename: str, api_name: str | None) -> str:

return "\n".join(api)

@view_config(route_name="apijs") # type: ignore
@view_config(route_name="apijs") # type: ignore[misc]
def apijs(self) -> pyramid.response.Response:
self.request.response.text = self.get_apijs(
self.request.registry.settings["static_files"]["api.js"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class GeometryProcessing:
def __init__(self, request: pyramid.request.Request):
self.request = request

@view_config(route_name="difference", renderer="geojson") # type: ignore
@view_config(route_name="difference", renderer="geojson") # type: ignore[misc]
def difference(self) -> BaseGeometry | None:
assert DBSession is not None

Expand Down
4 changes: 2 additions & 2 deletions geoportal/c2cgeoportal_geoportal/views/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
_INITIALIZED = False


@view_config(route_name="localejson") # type: ignore
@view_config(route_name="localejson") # type: ignore[misc]
def locale(request: pyramid.request.Request) -> pyramid.response.Response:
"""Get the locale json file for the API."""
response = HTTPFound(
Expand All @@ -65,7 +65,7 @@ def locale(request: pyramid.request.Request) -> pyramid.response.Response:
return response


@view_config(route_name="localepot") # type: ignore
@view_config(route_name="localepot") # type: ignore[misc]
def localepot(request: pyramid.request.Request) -> pyramid.response.Response:
"""Get the pot from an HTTP request."""

Expand Down
16 changes: 8 additions & 8 deletions geoportal/c2cgeoportal_geoportal/views/layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ def _proto_read(self, layer: "main.Layer") -> FeatureCollection:
raise feature
return feature

@view_config(route_name="layers_read_many", renderer="geojson") # type: ignore
@view_config(route_name="layers_read_many", renderer="geojson") # type: ignore[misc]
def read_many(self) -> FeatureCollection:
set_common_headers(self.request, "layers", Cache.PRIVATE_NO)

Expand All @@ -335,7 +335,7 @@ def read_many(self) -> FeatureCollection:

return FeatureCollection(features)

@view_config(route_name="layers_read_one", renderer="geojson") # type: ignore
@view_config(route_name="layers_read_one", renderer="geojson") # type: ignore[misc]
def read_one(self) -> Feature:
from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
Layer,
Expand Down Expand Up @@ -376,7 +376,7 @@ def read_one(self) -> Feature:

return feature

@view_config(route_name="layers_count", renderer="string") # type: ignore
@view_config(route_name="layers_count", renderer="string") # type: ignore[misc]
def count(self) -> int:
set_common_headers(self.request, "layers", Cache.PRIVATE_NO)

Expand All @@ -386,7 +386,7 @@ def count(self) -> int:
raise count
return cast(int, count)

@view_config(route_name="layers_create", renderer="geojson") # type: ignore
@view_config(route_name="layers_create", renderer="geojson") # type: ignore[misc]
def create(self) -> FeatureCollection | None:
set_common_headers(self.request, "layers", Cache.PRIVATE_NO)

Expand All @@ -410,7 +410,7 @@ def create(self) -> FeatureCollection | None:
self.request.response.status_int = 400
return {"error_type": "integrity_error", "message": str(e.orig.diag.message_primary)} # type: ignore[attr-defined]

@view_config(route_name="layers_update", renderer="geojson") # type: ignore
@view_config(route_name="layers_update", renderer="geojson") # type: ignore[misc]
def update(self) -> Feature:
set_common_headers(self.request, "layers", Cache.PRIVATE_NO)

Expand Down Expand Up @@ -464,7 +464,7 @@ def _get_validation_setting(cls, layer: "main.Layer", request: pyramid.request.R
return should_validate.lower() != "false"
return cast(bool, cls._get_settings(request).get("geometry_validation", False))

@view_config(route_name="layers_delete") # type: ignore
@view_config(route_name="layers_delete") # type: ignore[misc]
def delete(self) -> pyramid.response.Response:
if self.request.user is None:
raise HTTPForbidden()
Expand All @@ -477,7 +477,7 @@ def delete(self) -> pyramid.response.Response:
set_common_headers(self.request, "layers", Cache.PRIVATE_NO, response=response)
return response

@view_config(route_name="layers_metadata", renderer="xsd") # type: ignore
@view_config(route_name="layers_metadata", renderer="xsd") # type: ignore[misc]
def metadata(self) -> pyramid.response.Response:
set_common_headers(self.request, "layers", Cache.PRIVATE)

Expand All @@ -487,7 +487,7 @@ def metadata(self) -> pyramid.response.Response:

return get_layer_class(layer, with_last_update_columns=True)

@view_config(route_name="layers_enumerate_attribute_values", renderer="json") # type: ignore
@view_config(route_name="layers_enumerate_attribute_values", renderer="json") # type: ignore[misc]
def enumerate_attribute_values(self) -> dict[str, Any]:
set_common_headers(self.request, "layers", Cache.PUBLIC)

Expand Down
41 changes: 23 additions & 18 deletions geoportal/c2cgeoportal_geoportal/views/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from pyramid.view import forbidden_view_config, view_config
from sqlalchemy.orm.exc import NoResultFound # type: ignore[attr-defined]

import c2cgeoportal_commons.lib.url
from c2cgeoportal_commons import models
from c2cgeoportal_commons.lib.email_ import send_email_config
from c2cgeoportal_commons.models import static
Expand Down Expand Up @@ -90,7 +91,7 @@ def _referrer_log(self) -> None:
if not self.request.is_valid_referer:
_LOG.info("Invalid referrer for %s: %s", self.request.path_qs, repr(self.request.referrer))

@forbidden_view_config(renderer="login.html") # type: ignore
@forbidden_view_config(renderer="login.html") # type: ignore[misc]
def loginform403(self) -> dict[str, Any] | pyramid.response.Response:
if self.authentication_settings.get("openid_connect", {}).get("enabled", False):
return HTTPFound(
Expand All @@ -111,7 +112,7 @@ def loginform403(self) -> dict[str, Any] | pyramid.response.Response:
"two_fa": self.two_factor_auth,
}

@view_config(route_name="loginform", renderer="login.html") # type: ignore
@view_config(route_name="loginform", renderer="login.html") # type: ignore[misc]
def loginform(self) -> dict[str, Any]:
if self.authentication_settings.get("openid_connect", {}).get("enabled", False):
raise HTTPBadRequest("View disabled by OpenID Connect")
Expand All @@ -130,7 +131,7 @@ def _validate_2fa_totp(user: static.User, otp: str) -> bool:
return True
return False

@view_config(route_name="login") # type: ignore
@view_config(route_name="login") # type: ignore[misc]
def login(self) -> pyramid.response.Response:
assert models.DBSession is not None
if self.authentication_settings.get("openid_connect", {}).get("enabled", False):
Expand Down Expand Up @@ -289,7 +290,7 @@ def _oauth2_login(self, user: static.User) -> pyramid.response.Response:
response=Response(body, headers=headers.items()),
)

@view_config(route_name="logout") # type: ignore
@view_config(route_name="logout") # type: ignore[misc]
def logout(self) -> pyramid.response.Response:
if self.authentication_settings.get("openid_connect", {}).get("enabled", False):
client = oidc.get_oidc_client(self.request, self.request.host)
Expand Down Expand Up @@ -336,13 +337,13 @@ def _user(self, user: static.User | None = None) -> dict[str, Any]:
)
return result

@view_config(route_name="loginuser", renderer="json") # type: ignore
@view_config(route_name="loginuser", renderer="json") # type: ignore[misc]
def loginuser(self) -> dict[str, Any]:
_LOG.info("Client IP address: %s", self.request.client_addr)
set_common_headers(self.request, "login", Cache.PRIVATE_NO)
return self._user()

@view_config(route_name="change_password", renderer="json") # type: ignore
@view_config(route_name="change_password", renderer="json") # type: ignore[misc]
def change_password(self) -> pyramid.response.Response:
assert models.DBSession is not None

Expand Down Expand Up @@ -432,7 +433,7 @@ def _loginresetpassword(

return user, username, password, None

@view_config(route_name="loginresetpassword", renderer="json") # type: ignore
@view_config(route_name="loginresetpassword", renderer="json") # type: ignore[misc]
def loginresetpassword(self) -> dict[str, Any]:
if self.authentication_settings.get("openid_connect", {}).get("enabled", False):
raise HTTPBadRequest("View disabled by OpenID Connect")
Expand Down Expand Up @@ -464,7 +465,7 @@ def loginresetpassword(self) -> dict[str, Any]:

return {"success": True}

@view_config(route_name="oauth2introspect") # type: ignore
@view_config(route_name="oauth2introspect") # type: ignore[misc]
def oauth2introspect(self) -> pyramid.response.Response:
if self.authentication_settings.get("openid_connect", {}).get("enabled", False):
raise HTTPBadRequest("View disabled by OpenID Connect")
Expand Down Expand Up @@ -497,7 +498,7 @@ def oauth2introspect(self) -> pyramid.response.Response:
response=Response(body, headers=headers.items()),
)

@view_config(route_name="oauth2token") # type: ignore
@view_config(route_name="oauth2token") # type: ignore[misc]
def oauth2token(self) -> pyramid.response.Response:
if self.authentication_settings.get("openid_connect", {}).get("enabled", False):
raise HTTPBadRequest("View disabled by OpenID Connect")
Expand Down Expand Up @@ -529,7 +530,7 @@ def oauth2token(self) -> pyramid.response.Response:
response=Response(body, headers=headers.items()),
)

@view_config(route_name="oauth2revoke_token") # type: ignore
@view_config(route_name="oauth2revoke_token") # type: ignore[misc]
def oauth2revoke_token(self) -> pyramid.response.Response:
if self.authentication_settings.get("openid_connect", {}).get("enabled", False):
raise HTTPBadRequest("View disabled by OpenID Connect")
Expand Down Expand Up @@ -559,7 +560,7 @@ def oauth2revoke_token(self) -> pyramid.response.Response:
response=Response(body, headers=headers.items()),
)

@view_config(route_name="oauth2loginform", renderer="login.html") # type: ignore
@view_config(route_name="oauth2loginform", renderer="login.html") # type: ignore[misc]
def oauth2loginform(self) -> dict[str, Any]:
if self.authentication_settings.get("openid_connect", {}).get("enabled", False):
raise HTTPBadRequest("View disabled by OpenID Connect")
Expand All @@ -577,13 +578,13 @@ def oauth2loginform(self) -> dict[str, Any]:
"two_fa": self.two_factor_auth,
}

@view_config(route_name="notlogin", renderer="notlogin.html") # type: ignore
@view_config(route_name="notlogin", renderer="notlogin.html") # type: ignore[misc]
def notlogin(self) -> dict[str, Any]:
set_common_headers(self.request, "login", Cache.PUBLIC)

return {"lang": self.lang}

@view_config(route_name="oidc_login") # type: ignore
@view_config(route_name="oidc_login") # type: ignore[misc]
def oidc_login(self) -> pyramid.response.Response:
client = oidc.get_oidc_client(self.request, self.request.host)
if "came_from" in self.request.params:
Expand Down Expand Up @@ -618,17 +619,21 @@ def oidc_login(self) -> pyramid.response.Response:
)

try:
return HTTPFound(
location=client.authorization_code_flow.start_authentication(

url = c2cgeoportal_commons.lib.url.Url(
client.authorization_code_flow.start_authentication(
code_challenge=code_challenge,
code_challenge_method="S256",
),
headers=self.request.response.headers,
)
)
url.add_query(
self.authentication_settings.get("openid_connect", {}).get("login_extra_params", {})
)
return HTTPFound(location=url.url(), headers=self.request.response.headers)
finally:
client.authorization_code_flow.code_challenge = ""

@view_config(route_name="oidc_callback") # type: ignore
@view_config(route_name="oidc_callback") # type: ignore[misc]
def oidc_callback(self) -> pyramid.response.Response:
client = oidc.get_oidc_client(self.request, self.request.host)
assert models.DBSession is not None
Expand Down
8 changes: 4 additions & 4 deletions geoportal/c2cgeoportal_geoportal/views/mapserverproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ def __init__(self, request: Request) -> None:
OGCProxy.__init__(self, request)
self.user = self.request.user

@view_config(route_name="mapserverproxy") # type: ignore
@view_config(route_name="mapserverproxy_post") # type: ignore
@view_config(route_name="mapserverproxy") # type: ignore[misc]
@view_config(route_name="mapserverproxy_post") # type: ignore[misc]
def proxy(self) -> Response:
if self.user is None and "authentication_required" in self.request.params:
_LOG.debug("proxy() detected authentication_required")
Expand Down Expand Up @@ -166,11 +166,11 @@ def _setup_auth(self) -> None:
# Add functionalities params
self.params.update(get_mapserver_substitution_params(self.request))

@view_config(route_name="mapserverproxy_ogcapi_mapserver") # type: ignore
@view_config(route_name="mapserverproxy_ogcapi_mapserver") # type: ignore[misc]
def proxy_ogcapi_mapserver(self) -> Response:
return self.proxy_ogcapi("ogcapi")

@view_config(route_name="mapserverproxy_ogcapi_qgisserver") # type: ignore
@view_config(route_name="mapserverproxy_ogcapi_qgisserver") # type: ignore[misc]
def proxy_ogcapi_qgisserver(self) -> Response:
return self.proxy_ogcapi("wfs3")

Expand Down
2 changes: 1 addition & 1 deletion geoportal/c2cgeoportal_geoportal/views/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
_LOG = logging.getLogger(__name__)


@view_config(route_name="memory", renderer="fast_json") # type: ignore
@view_config(route_name="memory", renderer="fast_json") # type: ignore[misc]
def memory(request: pyramid.request.Request) -> dict[str, Any]:
"""Offer an authenticated view throw c2cwsgiutils to provide some memory information."""
auth_view(request)
Expand Down
2 changes: 1 addition & 1 deletion geoportal/c2cgeoportal_geoportal/views/pdfreport.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def _build_map(
],
}

@view_config(route_name="pdfreport", renderer="json") # type: ignore
@view_config(route_name="pdfreport", renderer="json") # type: ignore[misc]
def get_report(self) -> pyramid.response.Response:
assert models.DBSession is not None

Expand Down
Loading

0 comments on commit aac6cc2

Please sign in to comment.